diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml new file mode 100644 index 0000000..4ed276d --- /dev/null +++ b/.github/workflows/integration.yml @@ -0,0 +1,38 @@ +name: Integration Tests +on: + push: + branches: + - '**' # every branch + - '!stage*' # exclude branches beginning with stage + pull_request: + branches: + - '**' # every branch + - '!stage*' # exclude branches beginning with stage +jobs: + CI: + if: github.event_name == 'push' || github.event_name == 'pull_request' + runs-on: ubuntu-latest + strategy: + matrix: + matlab_version: ["R2019a"] + mysql_version: ["8.0.18", "5.7", "5.6"] + include: + - matlab_version: "R2018b" + mysql_version: "5.7" + - matlab_version: "R2016b" + mysql_version: "5.7" + steps: + - uses: actions/checkout@v2 + - name: Run primary tests + env: + MATLAB_UID: "1001" + MATLAB_GID: "116" + MATLAB_USER: ${{ secrets.matlab_user }} + MATLAB_HOSTID: ${{ secrets.matlab_hostid }} + MATLAB_VERSION: ${{ matrix.matlab_version }} + MYSQL_TAG: ${{ matrix.mysql_version }} + MATLAB_LICENSE: ${{ secrets[format('matlab_license_{0}', matrix.matlab_version)] }} + DOCKER_CLIENT_TIMEOUT: "120" + COMPOSE_HTTP_TIMEOUT: "120" + run: | + docker-compose -f LNX-docker-compose.yml up --build --exit-code-from app \ No newline at end of file diff --git a/.gitignore b/.gitignore index 541fbe7..aaaa181 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,4 @@ win.* macos* *.prj matlab.prf -*.mltbx \ No newline at end of file +*.mltbx diff --git a/LNX-docker-compose.yml b/LNX-docker-compose.yml new file mode 100644 index 0000000..4b5854a --- /dev/null +++ b/LNX-docker-compose.yml @@ -0,0 +1,105 @@ +# docker-compose -f LNX-docker-compose.yml --env-file LNX.env up --build --exit-code-from app +version: '2.4' +x-net: &net + networks: + - main +services: + db: + <<: *net + image: datajoint/mysql:${MYSQL_TAG} + environment: + - MYSQL_ROOT_PASSWORD=simple + fakeservices.datajoint.io: + <<: *net + image: raphaelguzman/nginx:v0.0.10 + environment: + - ADD_db_TYPE=DATABASE + - ADD_db_ENDPOINT=db:3306 + depends_on: + db: + condition: service_healthy + app: + <<: *net + environment: + - MATLAB_LICENSE + - MATLAB_USER + - DJ_HOST=fakeservices.datajoint.io + - DJ_USER=root + - DJ_PASS=simple + - DJ_TEST_HOST=fakeservices.datajoint.io + - DJ_TEST_USER=datajoint + - DJ_TEST_PASSWORD=datajoint + image: raphaelguzman/matlab:${MATLAB_VERSION}-MIN + depends_on: + fakeservices.datajoint.io: + condition: service_healthy + user: ${MATLAB_UID}:${MATLAB_GID} + working_dir: /main + command: + - /bin/bash + - -c + - | + set -e + export ORIG_DIR=$$(pwd) + mkdir ~/Documents + cd /src + # Verify all mex have been updated + if [ "distribution/mexa64/mym.mexa64" -nt "distribution/mexmaci64/mym.mexmaci64" ]; then + echo "MACOS64 Mex binary appears outdated. Failing check..." + exit 1 + fi + if [ "distribution/mexa64/mym.mexa64" -nt "distribution/mexw64/mym.mexw64" ]; then + echo "WIN64 Mex binary appears outdated. Failing check..." + exit 1 + fi + # Compile mym, package into toolbox, and install + matlab -nodisplay -r " + try\ + websave([tempdir 'GHToolbox.mltbx'],\ + ['https://github.com/datajoint/GHToolbox' \ + '/releases/download/' subsref(webread(['https://api.github.com/repos' \ + '/datajoint/GHToolbox' \ + '/releases/latest']),\ + substruct('.', 'tag_name')) \ + '/GHToolbox.mltbx']);\ + matlab.addons.toolbox.installToolbox([tempdir 'GHToolbox.mltbx']);\ + origDir = pwd;\ + cd('distribution/mexa64');\ + docs = help('mym');\ + cd(origDir);\ + ghtb.package('mym',\ + 'Raphael Guzman',\ + 'raphael.h.guzman@gmail.com',\ + ['MySQL API for MATLAB with support for BLOB objects'],\ + docs,\ + {'.vscode', '.git', '.gitignore', 'build', 'lib', 'maria-plugin',\ + 'mex_compilation', 'mysql-connector', 'notebook', 'src', 'zlib',\ + '*.txt', '*.env', '*.prf', '*.md', '*.yml', 'tests', '.github',\ + 'distribution/mexa64/libmysqlclient.so.18.4.'},\ + @() strjoin(arrayfun(@(x) num2str(x),\ + cell2mat(struct2cell(mym('version'))),\ + 'uni', false),\ + '.'),\ + {'distribution/mexa64', 'distribution/mexmaci64',\ + 'distribution/mexw64', 'mym.m'},\ + 'toolboxVersionDir', 'distribution/mexa64',\ + 'toolboxRootDir', '.');\ + matlab.addons.toolbox.installToolbox('mym.mltbx');\ + cd(getenv('ORIG_DIR'));\ + addpath('tests');\ + res=run(Main);\ + disp(res);\ + if all([res.Passed]) exit, else exit(1), end;\ + ,\ + catch ME,\ + disp(getReport(ME, 'extended'));\ + exit(1);\ + ,\ + end;\ + " + mac_address: $MATLAB_HOSTID + volumes: + - ./tests:/main/tests + - .:/src +networks: + main: diff --git a/README.md b/README.md index edc518f..1b189cf 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,47 @@ work of Robert Almgren from University of Toronto [sourceforge project](http://s See mym.m for further documentation. +## Running Tests Locally + +* Create an `.env` with desired development environment values e.g. +``` sh +MATLAB_USER=raphael +MATLAB_LICENSE="#\ BEGIN----...---------END" # For image usage instructions see https://github.com/guzman-raphael/matlab, https://hub.docker.com/r/raphaelguzman/matlab +MATLAB_VERSION=R2018b +MATLAB_HOSTID=XX:XX:XX:XX:XX:XX +MATLAB_UID=1000 +MATLAB_GID=1000 +MYSQL_TAG=5.7 +``` +* `cp local-docker-compose.yml docker-compose.yml` +* `docker-compose up` (Note configured `JUPYTER_PASSWORD`) +* Select a means of running MATLAB e.g. Jupyter Notebook, GUI, or Terminal (see bottom) +* Run desired tests. Some examples are as follows: + +| Use Case | MATLAB Code | +| ---------------------------- | ------------------------------------------------------------------------------ | +| Run all tests | `run(Main)` | +| Run one class of tests | `run(TestTls)` | +| Run one specific test | `runtests('TestTls/TestTls_testInsecureConn')` | +| Run tests based on test name | `import matlab.unittest.TestSuite;`
`import matlab.unittest.selectors.HasName;`
`import matlab.unittest.constraints.ContainsSubstring;`
`suite = TestSuite.fromClass(?Main, ... `
    `HasName(ContainsSubstring('Conn')));`
`run(suite)`| + + +### Launch Jupyter Notebook +* Navigate to `localhost:8888` +* Input Jupyter password +* Launch a notebook i.e. `New > MATLAB` + + +### Launch MATLAB GUI (supports remote interactive debugger) +* Shell into `mym_app_1` i.e. `docker exec -it mym_app_1 bash` +* Launch Matlab by runnning command `matlab` + + +### Launch MATLAB Terminal +* Shell into `mym_app_1` i.e. `docker exec -it mym_app_1 bash` +* Launch Matlab with no GUI by runnning command `matlab -nodisplay` + + ## Installation ### (Recommended) Using GHToolbox (FileExchange Community Toolbox) diff --git a/distribution/mexa64/mym.m b/distribution/mexa64/mym.m index da2675c..0800791 100644 --- a/distribution/mexa64/mym.m +++ b/distribution/mexa64/mym.m @@ -43,8 +43,13 @@ % host: default is local host. Use colon for port number % user: default is Unix login name. % password: default says connect without password. -% Examples: mym('open','arkiv') % connect on default port -% mym('open','arkiv:2215') +% use_tls: (optional) TLS encryption type. Values are as follows: +% - 'true': TLS encryption is required. +% - 'false': TLS encryption is disabled. +% - 'nan'(default): TLS encryption utilized if available. +% Examples: mym('open','arkiv') % connect on default port +% mym('open','arkiv:2215') % TLS Preferred +% mym('open','arkiv','root','simple','true') % TLS Required % If successful, open returns 0 if successful, and throw an error % otherwise. The program can maintain up to 20 independent connections. % Any command may be preceded by a connection handle -- an integer from 0 @@ -67,7 +72,8 @@ % Example: mym('use cme') % mym('status') % ------------- -% Display information about the connection and the server. +% Display information about the connection and the server. Connections +% utilizing TLS encryption will be displayed as '(encrypted)'. % Return 0 if connection is open and functioning % 1 if connection is closed % 2 if should be open but we cannot ping the server @@ -119,6 +125,14 @@ % solution is to use the following command: % mym(INSERT INTO tbl(id,txt) VALUES(1000,"{S}")','abc{dfg}h'); % +% mym('serialize {placeholder1}, ...',matlab_variable1, ...) +% ------------- +% Return : cell array of vectors of type ubyte, a vector per matlab variable +% +% mym('deserialize', serialized_input) +% ------------- +% Return : matlab variable of the appropriate type +% % mym('version') % -------------- % Displays the version of mym when no output arguments are given. @@ -154,7 +168,7 @@ % - use std::max(a, b) instead of max(a, b) % v1.0.5 - added the preamble 'u', permitting to save binary fields without using compression % - corrected a bug in mym('closeall') -% - corrected various mistakes in the help file (thanks to J�rg Buchholz) +% - corrected various mistakes in the help file (thanks to Jörg Buchholz) % v1.0.4 corrected the behaviour of mYm with time fields, now return a string dump of the field % v1.0.3 minor corrections % v1.0.2 put mYm under GPL license, official release diff --git a/distribution/mexa64/mym.mexa64 b/distribution/mexa64/mym.mexa64 index a259fb8..99319fa 100755 Binary files a/distribution/mexa64/mym.mexa64 and b/distribution/mexa64/mym.mexa64 differ diff --git a/distribution/mexmaci64/mym.m b/distribution/mexmaci64/mym.m index da2675c..0800791 100644 --- a/distribution/mexmaci64/mym.m +++ b/distribution/mexmaci64/mym.m @@ -43,8 +43,13 @@ % host: default is local host. Use colon for port number % user: default is Unix login name. % password: default says connect without password. -% Examples: mym('open','arkiv') % connect on default port -% mym('open','arkiv:2215') +% use_tls: (optional) TLS encryption type. Values are as follows: +% - 'true': TLS encryption is required. +% - 'false': TLS encryption is disabled. +% - 'nan'(default): TLS encryption utilized if available. +% Examples: mym('open','arkiv') % connect on default port +% mym('open','arkiv:2215') % TLS Preferred +% mym('open','arkiv','root','simple','true') % TLS Required % If successful, open returns 0 if successful, and throw an error % otherwise. The program can maintain up to 20 independent connections. % Any command may be preceded by a connection handle -- an integer from 0 @@ -67,7 +72,8 @@ % Example: mym('use cme') % mym('status') % ------------- -% Display information about the connection and the server. +% Display information about the connection and the server. Connections +% utilizing TLS encryption will be displayed as '(encrypted)'. % Return 0 if connection is open and functioning % 1 if connection is closed % 2 if should be open but we cannot ping the server @@ -119,6 +125,14 @@ % solution is to use the following command: % mym(INSERT INTO tbl(id,txt) VALUES(1000,"{S}")','abc{dfg}h'); % +% mym('serialize {placeholder1}, ...',matlab_variable1, ...) +% ------------- +% Return : cell array of vectors of type ubyte, a vector per matlab variable +% +% mym('deserialize', serialized_input) +% ------------- +% Return : matlab variable of the appropriate type +% % mym('version') % -------------- % Displays the version of mym when no output arguments are given. @@ -154,7 +168,7 @@ % - use std::max(a, b) instead of max(a, b) % v1.0.5 - added the preamble 'u', permitting to save binary fields without using compression % - corrected a bug in mym('closeall') -% - corrected various mistakes in the help file (thanks to J�rg Buchholz) +% - corrected various mistakes in the help file (thanks to Jörg Buchholz) % v1.0.4 corrected the behaviour of mYm with time fields, now return a string dump of the field % v1.0.3 minor corrections % v1.0.2 put mYm under GPL license, official release diff --git a/distribution/mexmaci64/mym.mexmaci64 b/distribution/mexmaci64/mym.mexmaci64 index 9eabcd5..2ad0b14 100755 Binary files a/distribution/mexmaci64/mym.mexmaci64 and b/distribution/mexmaci64/mym.mexmaci64 differ diff --git a/distribution/mexw64/mym.m b/distribution/mexw64/mym.m index 677de1f..0800791 100644 --- a/distribution/mexw64/mym.m +++ b/distribution/mexw64/mym.m @@ -1,164 +1,178 @@ -% MYM - Interact with a MySQL database server -% Copyright 2005, EPFL (Yannick Maret) -% -% Copyright notice: this code is a heavily modified version of the original -% work of Robert Almgren from University of Toronto. -% -% If no output arguments are given, then display results. Otherwise returns -% requested data silently. -% -% INSTALLATION -% ------------ -% (Recommended) Using GHToolbox (FileExchange Community Toolbox): -% 1. Install *GHToolbox* using using an appropriate method in -% https://github.com/datajoint/GHToolbox -% 2. run: `ghtb.install('datajoint/mym')` -% -% Greater than R2016b: -% 1. Utilize MATLAB built-in GUI i.e. *Top Ribbon -> Add-Ons -> Get Add-Ons* -% 2. Search and Select `mym` -% 3. Select *Add from GitHub* -% -% Less than R2016b: -% 1. Utilize MATLAB built-in GUI i.e. *Top Ribbon -> Add-Ons -> Get Add-Ons* -% 2. Search and Select `mym` -% 3. Select *Download from GitHub* -% 4. Save `mym.mltbx` locally -% 5. Navigate in MATLAB tree browser to saved toolbox file -% 6. Right-Click and Select *Install* -% 7. Select *Install* -% -% From Source: -% 1. Download `mym.mltbx` locally -% 2. Navigate in MATLAB tree browser to saved toolbox file -% 3. Right-Click and Select *Install* -% 4. Select *Install* -% -% mym() or mym -% ------------ -% shows status of all open connections (returns nothing). -% mym('open', host, user, password) -% --------------------------------- -% Open a connection with specified parameters, or defaults if none -% host: default is local host. Use colon for port number -% user: default is Unix login name. -% password: default says connect without password. -% Examples: mym('open','arkiv') % connect on default port -% mym('open','arkiv:2215') -% If successful, open returns 0 if successful, and throw an error -% otherwise. The program can maintain up to 20 independent connections. -% Any command may be preceded by a connection handle -- an integer from 0 -% to 10 -- to apply the command to that connection. -% Examples: mym(5,'open','host2') % connection 5 to host 2 -% mym % status of all connections -% When no connection handle is given, mym use 0 by default. If the -% corresponding connection is open, it is closed and opened again. -% It is possible to ask mym to look for an available connection -% handle by using -1. The used connection handle is then returned. -% Example: cid = mym(-1, 'open', 'host2') % cid contains the used -% connection handle -% mym('close') -% ------------ -% Close the current connection. Use mym('closeall') to closes all open -% connections. -% mym('use',db) or mym('use db') -% --------------------------------- -% Set the current database to db -% Example: mym('use cme') -% mym('status') -% ------------- -% Display information about the connection and the server. -% Return 0 if connection is open and functioning -% 1 if connection is closed -% 2 if should be open but we cannot ping the server -% mym(query) -% ---------- -% Send the given query or command to the MySQL server. If arguments are -% given on the left, then each argument is set to the column of the -% returned query. Dates and times in result are converted to Matlab -% format: dates are serial day number, and times are fraction of day. -% String variables are returned as cell arrays. -% Example: p = mym('select price from contract where date="1997-04-30"'); -% % Returns price for contract that occured on April 30, 1997. -% Note: All string comparisons are case-insensitive -% Placeholders: in a query the following placeholders can be used: {S}, -% {Si}, {M}, {F}, and {B}. -% Example: i = 1000; -% B = [1 2; 3 4]; -% mym('INSERT INTO test(id,value) VALUES("{Si}","{M}")',i,B); -% A = mym('SELECT value FROM test WHERE id ="{Si}")', 1000); -% % Insert the array B into table test with id=1000. Then the -% % value is retrieved and put into A. -% {S} is remplaced by a string given by the corresponding argument arg. -% Arg can be a matlab string or a scalar. The format of the string -% for double scalars is [sign]d.ddddddEddd; for integers the format -% is [sign]dddddd. -% {Sn} is the same as {S} but for double scalar only. The format of the -% string is [sign]d.ddddEddd, where the number of decimal after the -% dot is given by n. -% {Si} is the same as {S} but for double scalar only. The corresponding -% double is first converted to an integer (using floor). -% {M} is replaced by the binary representation of the corresponding -% argument (it can be a scalar, cell, numeric or cell array, or a -% structure). -% {B} is replaced by the binary representation of the uint8 vector -% given in the corresponding argument. -% {F} is the same as {B} but for the file whose name is given in the -% corresponding argument. -% Note: 1) {M}, {B} and {F} need to be put in fields of type BLOB -% 2) {M}, {B} and {F} binary representations are compressed -- only -% if the space gain is larger than 10% --. We use zlib v1.2.3! -% The compression can be switched off by using {uM}, {uB} and -% {uF}, instead. -% 3) {M} does not work if the endian of the client used to store -% the BLOB is different than that used to fetch it. -% 4) time fields are returned as string dump -% 5) this kind of insert does not work properly: -% mym(INSERT INTO tbl(id,txt) VALUES(1000,"abc{dfg}h")'); -% as the "abc{dfg}h" is mistaken for a mYm command. A possible -% solution is to use the following command: -% mym(INSERT INTO tbl(id,txt) VALUES(1000,"{S}")','abc{dfg}h'); -% -% mym('version') -% -------------- -% Displays the version of mym when no output arguments are given. -% With a single output argument, a struct with the fields 'major', -% 'minor' and 'bugfix' is returned. These fields contain numeric -% scalars corresponding to the version major.minor.bugfix -% -% SOURCE -% ------ -% https://github.com/datajoint/mym -% -% HISTORY -% ------- -% v1.36 - fixed bug for Linux64 related to cross-platform compatibility. Blobs written on windows can be read under -% Linux 32/64 and vice-versa. -% v1.35 - fixed bug with incorrect pointer increment under Linux -% v1.3 - fixed bug with saving corrupted cells -% v1.2 - fixed bug with memory allocation, result now is returned as a structure -% v1.1 - fixed bug with port number being ignored when specified -% -% v1.0.9 - a space is now used when the variable corresponding to a string placeholder is empty -% - we now use strtod instead of sscanf(s, "%lf") -% - add support for stored procedure -% v1.0.8 - corrected a problem occurring with MySQL commands that do not return results -% - the M$ Windows binary now use the correct runtime DLL (MSVC80.DLL insteaLd of MSVC80D.DL) -% v1.0.7 - logical values are now correctly considered as numerical value when using placeholder {Si} -% - corrected a bug occuring when closing a connection that was not openned -% - added the possibility to get the next free connection ID when oppening a connection -% v1.0.6 - corrected a bug where mym('use', 'a_schema') worked fine while mym(conn, 'use', 'a_schema') did not work -% - corrected a segmentation violation that happened when issuing a MySQL command when not connected -% - corrected the mex command (this file) -% - corrected a bug where it was impossible to open a connection silently -% - use std::max(a, b) instead of max(a, b) -% v1.0.5 - added the preamble 'u', permitting to save binary fields without using compression -% - corrected a bug in mym('closeall') -% - corrected various mistakes in the help file (thanks to J�rg Buchholz) -% v1.0.4 corrected the behaviour of mYm with time fields, now return a string dump of the field -% v1.0.3 minor corrections -% v1.0.2 put mYm under GPL license, official release -% v1.0.1 corrected a bug where non-matlab binary objects were not returned -% v1.0.0 initial release - - +% MYM - Interact with a MySQL database server +% Copyright 2005, EPFL (Yannick Maret) +% +% Copyright notice: this code is a heavily modified version of the original +% work of Robert Almgren from University of Toronto. +% +% If no output arguments are given, then display results. Otherwise returns +% requested data silently. +% +% INSTALLATION +% ------------ +% (Recommended) Using GHToolbox (FileExchange Community Toolbox): +% 1. Install *GHToolbox* using using an appropriate method in +% https://github.com/datajoint/GHToolbox +% 2. run: `ghtb.install('datajoint/mym')` +% +% Greater than R2016b: +% 1. Utilize MATLAB built-in GUI i.e. *Top Ribbon -> Add-Ons -> Get Add-Ons* +% 2. Search and Select `mym` +% 3. Select *Add from GitHub* +% +% Less than R2016b: +% 1. Utilize MATLAB built-in GUI i.e. *Top Ribbon -> Add-Ons -> Get Add-Ons* +% 2. Search and Select `mym` +% 3. Select *Download from GitHub* +% 4. Save `mym.mltbx` locally +% 5. Navigate in MATLAB tree browser to saved toolbox file +% 6. Right-Click and Select *Install* +% 7. Select *Install* +% +% From Source: +% 1. Download `mym.mltbx` locally +% 2. Navigate in MATLAB tree browser to saved toolbox file +% 3. Right-Click and Select *Install* +% 4. Select *Install* +% +% mym() or mym +% ------------ +% shows status of all open connections (returns nothing). +% mym('open', host, user, password) +% --------------------------------- +% Open a connection with specified parameters, or defaults if none +% host: default is local host. Use colon for port number +% user: default is Unix login name. +% password: default says connect without password. +% use_tls: (optional) TLS encryption type. Values are as follows: +% - 'true': TLS encryption is required. +% - 'false': TLS encryption is disabled. +% - 'nan'(default): TLS encryption utilized if available. +% Examples: mym('open','arkiv') % connect on default port +% mym('open','arkiv:2215') % TLS Preferred +% mym('open','arkiv','root','simple','true') % TLS Required +% If successful, open returns 0 if successful, and throw an error +% otherwise. The program can maintain up to 20 independent connections. +% Any command may be preceded by a connection handle -- an integer from 0 +% to 10 -- to apply the command to that connection. +% Examples: mym(5,'open','host2') % connection 5 to host 2 +% mym % status of all connections +% When no connection handle is given, mym use 0 by default. If the +% corresponding connection is open, it is closed and opened again. +% It is possible to ask mym to look for an available connection +% handle by using -1. The used connection handle is then returned. +% Example: cid = mym(-1, 'open', 'host2') % cid contains the used +% connection handle +% mym('close') +% ------------ +% Close the current connection. Use mym('closeall') to closes all open +% connections. +% mym('use',db) or mym('use db') +% --------------------------------- +% Set the current database to db +% Example: mym('use cme') +% mym('status') +% ------------- +% Display information about the connection and the server. Connections +% utilizing TLS encryption will be displayed as '(encrypted)'. +% Return 0 if connection is open and functioning +% 1 if connection is closed +% 2 if should be open but we cannot ping the server +% mym(query) +% ---------- +% Send the given query or command to the MySQL server. If arguments are +% given on the left, then each argument is set to the column of the +% returned query. Dates and times in result are converted to Matlab +% format: dates are serial day number, and times are fraction of day. +% String variables are returned as cell arrays. +% Example: p = mym('select price from contract where date="1997-04-30"'); +% % Returns price for contract that occured on April 30, 1997. +% Note: All string comparisons are case-insensitive +% Placeholders: in a query the following placeholders can be used: {S}, +% {Si}, {M}, {F}, and {B}. +% Example: i = 1000; +% B = [1 2; 3 4]; +% mym('INSERT INTO test(id,value) VALUES("{Si}","{M}")',i,B); +% A = mym('SELECT value FROM test WHERE id ="{Si}")', 1000); +% % Insert the array B into table test with id=1000. Then the +% % value is retrieved and put into A. +% {S} is remplaced by a string given by the corresponding argument arg. +% Arg can be a matlab string or a scalar. The format of the string +% for double scalars is [sign]d.ddddddEddd; for integers the format +% is [sign]dddddd. +% {Sn} is the same as {S} but for double scalar only. The format of the +% string is [sign]d.ddddEddd, where the number of decimal after the +% dot is given by n. +% {Si} is the same as {S} but for double scalar only. The corresponding +% double is first converted to an integer (using floor). +% {M} is replaced by the binary representation of the corresponding +% argument (it can be a scalar, cell, numeric or cell array, or a +% structure). +% {B} is replaced by the binary representation of the uint8 vector +% given in the corresponding argument. +% {F} is the same as {B} but for the file whose name is given in the +% corresponding argument. +% Note: 1) {M}, {B} and {F} need to be put in fields of type BLOB +% 2) {M}, {B} and {F} binary representations are compressed -- only +% if the space gain is larger than 10% --. We use zlib v1.2.3! +% The compression can be switched off by using {uM}, {uB} and +% {uF}, instead. +% 3) {M} does not work if the endian of the client used to store +% the BLOB is different than that used to fetch it. +% 4) time fields are returned as string dump +% 5) this kind of insert does not work properly: +% mym(INSERT INTO tbl(id,txt) VALUES(1000,"abc{dfg}h")'); +% as the "abc{dfg}h" is mistaken for a mYm command. A possible +% solution is to use the following command: +% mym(INSERT INTO tbl(id,txt) VALUES(1000,"{S}")','abc{dfg}h'); +% +% mym('serialize {placeholder1}, ...',matlab_variable1, ...) +% ------------- +% Return : cell array of vectors of type ubyte, a vector per matlab variable +% +% mym('deserialize', serialized_input) +% ------------- +% Return : matlab variable of the appropriate type +% +% mym('version') +% -------------- +% Displays the version of mym when no output arguments are given. +% With a single output argument, a struct with the fields 'major', +% 'minor' and 'bugfix' is returned. These fields contain numeric +% scalars corresponding to the version major.minor.bugfix +% +% SOURCE +% ------ +% https://github.com/datajoint/mym +% +% HISTORY +% ------- +% v1.36 - fixed bug for Linux64 related to cross-platform compatibility. Blobs written on windows can be read under +% Linux 32/64 and vice-versa. +% v1.35 - fixed bug with incorrect pointer increment under Linux +% v1.3 - fixed bug with saving corrupted cells +% v1.2 - fixed bug with memory allocation, result now is returned as a structure +% v1.1 - fixed bug with port number being ignored when specified +% +% v1.0.9 - a space is now used when the variable corresponding to a string placeholder is empty +% - we now use strtod instead of sscanf(s, "%lf") +% - add support for stored procedure +% v1.0.8 - corrected a problem occurring with MySQL commands that do not return results +% - the M$ Windows binary now use the correct runtime DLL (MSVC80.DLL insteaLd of MSVC80D.DL) +% v1.0.7 - logical values are now correctly considered as numerical value when using placeholder {Si} +% - corrected a bug occuring when closing a connection that was not openned +% - added the possibility to get the next free connection ID when oppening a connection +% v1.0.6 - corrected a bug where mym('use', 'a_schema') worked fine while mym(conn, 'use', 'a_schema') did not work +% - corrected a segmentation violation that happened when issuing a MySQL command when not connected +% - corrected the mex command (this file) +% - corrected a bug where it was impossible to open a connection silently +% - use std::max(a, b) instead of max(a, b) +% v1.0.5 - added the preamble 'u', permitting to save binary fields without using compression +% - corrected a bug in mym('closeall') +% - corrected various mistakes in the help file (thanks to Jörg Buchholz) +% v1.0.4 corrected the behaviour of mYm with time fields, now return a string dump of the field +% v1.0.3 minor corrections +% v1.0.2 put mYm under GPL license, official release +% v1.0.1 corrected a bug where non-matlab binary objects were not returned +% v1.0.0 initial release + + diff --git a/distribution/mexw64/mym.mexw64 b/distribution/mexw64/mym.mexw64 index 96686d1..2e18b02 100644 Binary files a/distribution/mexw64/mym.mexw64 and b/distribution/mexw64/mym.mexw64 differ diff --git a/local-docker-compose.yml b/local-docker-compose.yml index 733df60..daa5bb0 100644 --- a/local-docker-compose.yml +++ b/local-docker-compose.yml @@ -16,7 +16,7 @@ services: # - ./mysql/data:/var/lib/mysql fakeservices.datajoint.io: <<: *net - image: raphaelguzman/nginx:v0.0.8 + image: raphaelguzman/nginx:v0.0.10 environment: - ADD_db_TYPE=DATABASE - ADD_db_ENDPOINT=db:3306 @@ -77,7 +77,7 @@ services: docs,\ {'.vscode', '.git', '.gitignore', 'build', 'lib', 'maria-plugin',\ 'mex_compilation', 'mysql-connector', 'notebook', 'src', 'zlib',\ - '*docker-compose.yml', '*.txt', '*.env', '*.prf', '*.md',\ + '*.txt', '*.env', '*.prf', '*.md', '*.yml', 'tests', '.github',\ 'distribution/mexa64/libmysqlclient.so.18.4.'},\ @() strjoin(arrayfun(@(x) num2str(x),\ cell2mat(struct2cell(mym('version'))),\ @@ -88,6 +88,8 @@ services: 'toolboxVersionDir', 'distribution/mexa64',\ 'toolboxRootDir', '.');\ matlab.addons.toolbox.installToolbox('mym.mltbx');\ + addpath('/src/tests');\ + savepath;\ cd(tempdir);\ disp(mym('version'));\ " @@ -105,4 +107,4 @@ services: # - ./notebook:/home/muser/notebooks # - ./matlab.prf:/tmp/matlab.prf networks: - main: \ No newline at end of file + main: diff --git a/src/mym.cpp b/src/mym.cpp index 3b874f8..848aeba 100644 --- a/src/mym.cpp +++ b/src/mym.cpp @@ -311,7 +311,7 @@ static void field2int(const char*s, enum_field_types t, unsigned int flags, void * This is based on an original by Kimmo Uutela **********************************************************************/ static char* getstring(const mxArray*a) { - int llen = mxGetM(a)*mxGetN(a)*sizeof(mxChar)+1; + int llen = (int)mxGetM(a) * (int)mxGetN(a) * sizeof(mxChar) + 1; char*c = (char*) mxCalloc(llen, sizeof(char)); if (mxGetString(a, c, llen)) mexErrMsgTxt("Can\'t copy string in getstring()"); @@ -337,7 +337,11 @@ static void updateplugindir() { char environment_string[1000]; strcpy(environment_string,"LIBMYSQL_PLUGIN_DIR="); strcat(environment_string,mym_directory); - putenv(environment_string); + #ifdef _WINDOWS + _putenv(environment_string); + #else + putenv(environment_string); + #endif // //Confirm Path // printf("Path: %s\n", mym_directory); @@ -394,7 +398,7 @@ void mexFunction(int nlhs, mxArray*plhs[], int nrhs, const mxArray*prhs[]) { mexPrintf("cid = %d jarg = %d\n", cid, jarg); /*********************************************************************/ // Parse the result based on the first argument - enum querytype { OPEN, CLOSE, CLOSE_ALL, USE, STATUS, CMD, VERSION } q; + enum querytype { OPEN, CLOSE, CLOSE_ALL, USE, STATUS, CMD, SERIALIZE, DESERIALIZE, VERSION } q; char*query = NULL; if (nrhs<=jarg) q = STATUS; @@ -414,11 +418,15 @@ void mexFunction(int nlhs, mxArray*plhs[], int nrhs, const mxArray*prhs[]) { q = STATUS; else if (!strcasecmp(query, "version")) q = VERSION; + else if (strcasestr(query, "deserialize") != NULL) + q = DESERIALIZE; + else if (strcasestr(query, "serialize") != NULL) + q = SERIALIZE; else q = CMD; } // Check that the arguments are all character strings - if (q!=CMD) + if ((q!=CMD) & (q!=SERIALIZE) & (q!=DESERIALIZE)) for (int j = jarg; jMIN_LEN_ZLIB)) { // compress if needed + // compress field + const uLongf max_len = compressBound(plen[i]); + if (max_len>cmp_buf_len){ + pcmp = (Bytef*)mxRealloc(pcmp, max_len); + cmp_buf_len = max_len; + } + uLongf len = cmp_buf_len; + if (compress(pcmp, &len, (Bytef*)pd[i], plen[i])!=Z_OK) + mexErrMsgIdAndTxt("mYm:Serialization:Compression", + "Compression failed"); + const float cmp_rate = plen[i]/(LEN_ZLIB_ID+1.f+sizeof(_uint64)+len); + if (cmp_rate>MIN_CMP_RATE_ZLIB) { + const size_t len_old = plen[i]; + plen[i] = LEN_ZLIB_ID+1+sizeof(_uint64)+len; + pd[i] = (char*)mxRealloc(pd[i], plen[i]); + memcpy(pd[i], (const char*)ZLIB_ID, LEN_ZLIB_ID); + *(pd[i]+LEN_ZLIB_ID) = 0; + + *((_uint64*)(pd[i]+LEN_ZLIB_ID+1)) = (_uint64) len_old; + memcpy(pd[i]+LEN_ZLIB_ID+1+sizeof(_uint64), pcmp, len); + //BUG: *((_uint64*)(pd[i]+LEN_ZLIB_ID+1+sizeof(_uint64))) = ( + // _uint64) len; + } + } + nb += plen[i]; + } + mxFree(query); + mxFree(po); + mxFree(pc); + mxFree(pec); + mxFree(ps); + mxFree(pa); + if (pcmp!=NULL) + mxFree(pcmp); + + // return the serialized items as cell array of unsigned byte vectors + if (nlhs > 0) { + mxArray *cell_array_ptr ; + mxArray *vec ; + cell_array_ptr = mxCreateCellMatrix(nac,1); + if (cell_array_ptr != NULL) { + for (unsigned i=0; i(input_dims[0], input_dims[1])); + } + else { + mexErrMsgIdAndTxt("mYm:Deserialization:Mismatch", + "There must be only one input and one output variable\n"); + } + } else if (q == VERSION) { if (nrhs > (jarg+1)) mexErrMsgTxt("Version command does not take additional inputs"); @@ -1361,7 +1534,8 @@ char* serializeString(size_t &rnBytes, const mxArray*rpArray, const char*rpArg, mexErrMsgTxt("String placeholders only accept CHAR 1-by-M arrays or M-by-1!"); // matlab string p_buf = (char*)mxCalloc(length+1, sizeof(char)); - mxGetString(rpArray, p_buf, length+1); + p_buf = mxArrayToString(rpArray); + p_buf = char2hex(p_buf, strlen(p_buf), length + 1); rnBytes = length; } else if (mxIsNumeric(rpArray)||mxIsLogical(rpArray)) { @@ -1761,7 +1935,7 @@ mxArray* deserialize(const char* rpSerial, const size_t rlength) { } if (p_serial != 0 && !strcasecmp(p_serial, "dj0")) mexErrMsgIdAndTxt("mYm:CrossPlatform:Compatibility", - "Blob data ingested utilizing DataJoint-Python version >=0.12 not yet supported."); + "Blob data ingested utilizing DataJoint-Python version >=0.12 not yet supported."); if (strcmp(p_serial, ID_MATLAB)==0) { p_serial = p_serial+LEN_ID_MATLAB+1; try { @@ -1798,3 +1972,78 @@ mxArray* deserialize(const char* rpSerial, const size_t rlength) { mxFree(p_cmp); return p_res; } +char *hex2char(char *original_val, const size_t char_length) { + const uint8_t *pnt = (uint8_t *)original_val; + uint8_t *result_pnt = new uint8_t[char_length*4]; + unsigned int offset = 0; + for( unsigned int a = 0; a < char_length; ++a ) + { + if (pnt[a]<=0x7F) { + result_pnt[a+offset] = pnt[a]; + } + else if(pnt[a]<=0x7FF) { + result_pnt[a+offset] = ((pnt[a]>>6) + 0xC0); + result_pnt[a+offset+1] = ((pnt[a] & 0x3F) + 0x80); + offset += 1; + } + else if(0xD800<=pnt[a] && pnt[a]<=0xDFFF) { + mexErrMsgIdAndTxt("mYm:Deserialization:UTF8", + "Invalid block of UTF8 detected."); + } //invalid block of utf8 + else if(pnt[a]<=0xFFFF) { + result_pnt[a+offset] = ((pnt[a]>>12) + 0xE0); + result_pnt[a+offset+1] = (((pnt[a]>>6) & 0x3F) + 0x80); + result_pnt[a+offset+2] = ((pnt[a] & 0x3F) + 0x80); + offset += 2; + } + else if(pnt[a]<=0x10FFFF) { + result_pnt[a+offset] = ((pnt[a]>>18) + 0xF0); + result_pnt[a+offset+1] = (((pnt[a]>>12) & 0x3F) + 0x80); + result_pnt[a+offset+2] = (((pnt[a]>>6) & 0x3F) + 0x80); + result_pnt[a+offset+3] = ((pnt[a] & 0x3F) + 0x80); + offset += 3; + } + } + result_pnt[char_length+offset]= 0x00; + return (char *)result_pnt; +} +char *char2hex(char *original_val, const size_t vlength, const size_t char_length) { + unsigned int idx = 0; + unsigned int curr_length = 0; + unsigned char u0,u1,u2,u3; + uint8_t *result_pnt = new uint8_t[char_length]; + for(unsigned int a = 0; a < vlength;) + { + u0 = original_val[a]; + curr_length = 1; + if (((u0 & 0xF8) == 0xF0) && ((a + curr_length + 3) <= vlength)) { + curr_length += 3; + u1 = original_val[a+1]; + u2 = original_val[a+2]; + u3 = original_val[a+3]; + result_pnt[idx] = (((u0-0xF0)<<18) + ((u1-0x80)<<12) + ((u2-0x80)<<6) + (u3-0x80)); + } + else if (((u0 & 0xF0) == 0xE0) && ((a + curr_length + 2) <= vlength)) { + curr_length += 2; + u1 = original_val[a+1]; + u2 = original_val[a+2]; + if (u0 == 0xED && (u1 & 0xA0) == 0xA0) { + mexErrMsgIdAndTxt("mYm:Serialization:UTF8", + "Invalid block of UTF8 detected."); + } + result_pnt[idx] = (((u0-0xE0)<<12) + ((u1-0x80)<<6) + (u2-0x80)); + } + else if (((u0 & 0xE0) == 0xC0) && ((a + curr_length + 1) <= vlength)) { + curr_length++; + u1 = original_val[a+1]; + result_pnt[idx] = (((u0-0xC0)<<6) + (u1-0x80)); + } + else { + result_pnt[idx] = u0; + } + idx++; + a += curr_length; + } + result_pnt[idx]= 0x00; + return (char*)result_pnt; +} \ No newline at end of file diff --git a/src/mym.h b/src/mym.h index 1cad476..0168441 100644 --- a/src/mym.h +++ b/src/mym.h @@ -33,8 +33,8 @@ // mym version information #define MYM_VERSION_MAJOR 2 -#define MYM_VERSION_MINOR 7 -#define MYM_VERSION_BUGFIX 4 +#define MYM_VERSION_MINOR 8 +#define MYM_VERSION_BUGFIX 0 // some local defintion @@ -76,6 +76,9 @@ const bool debug = false; // turn on information messages you get errors on other platforms, move the declarations outside the WIN32 block */ inline int strcasecmp(const char *s1, const char *s2) { return strcmp(s1, s2); } inline int strncasecmp(const char *s1, const char *s2, size_t n) { return strncmp(s1, s2, n); } + // In windows, strcasecmp and strcasestr dont do a case insensitive execution, so user must + // make sure that everything is case sensitive + inline const char * strcasestr(const char *s1, const char *s2) { return strstr(s1, s2); } #endif #include // Definitions for MySQL client API @@ -181,5 +184,7 @@ enum CMD_FLAGS { static void getSerialFct(const char* rpt, const mxArray* rparg, pfserial& rpf, bool& rpec); mxArray* deserialize(const char* rpSerial, const size_t rlength); +char *hex2char(char *original_val, const size_t vlength); +char *char2hex(char *original_val, const size_t vlength, const size_t char_length); #endif // MY_MAT_H diff --git a/src/mym.m b/src/mym.m index da2675c..0800791 100644 --- a/src/mym.m +++ b/src/mym.m @@ -43,8 +43,13 @@ % host: default is local host. Use colon for port number % user: default is Unix login name. % password: default says connect without password. -% Examples: mym('open','arkiv') % connect on default port -% mym('open','arkiv:2215') +% use_tls: (optional) TLS encryption type. Values are as follows: +% - 'true': TLS encryption is required. +% - 'false': TLS encryption is disabled. +% - 'nan'(default): TLS encryption utilized if available. +% Examples: mym('open','arkiv') % connect on default port +% mym('open','arkiv:2215') % TLS Preferred +% mym('open','arkiv','root','simple','true') % TLS Required % If successful, open returns 0 if successful, and throw an error % otherwise. The program can maintain up to 20 independent connections. % Any command may be preceded by a connection handle -- an integer from 0 @@ -67,7 +72,8 @@ % Example: mym('use cme') % mym('status') % ------------- -% Display information about the connection and the server. +% Display information about the connection and the server. Connections +% utilizing TLS encryption will be displayed as '(encrypted)'. % Return 0 if connection is open and functioning % 1 if connection is closed % 2 if should be open but we cannot ping the server @@ -119,6 +125,14 @@ % solution is to use the following command: % mym(INSERT INTO tbl(id,txt) VALUES(1000,"{S}")','abc{dfg}h'); % +% mym('serialize {placeholder1}, ...',matlab_variable1, ...) +% ------------- +% Return : cell array of vectors of type ubyte, a vector per matlab variable +% +% mym('deserialize', serialized_input) +% ------------- +% Return : matlab variable of the appropriate type +% % mym('version') % -------------- % Displays the version of mym when no output arguments are given. @@ -154,7 +168,7 @@ % - use std::max(a, b) instead of max(a, b) % v1.0.5 - added the preamble 'u', permitting to save binary fields without using compression % - corrected a bug in mym('closeall') -% - corrected various mistakes in the help file (thanks to J�rg Buchholz) +% - corrected various mistakes in the help file (thanks to Jörg Buchholz) % v1.0.4 corrected the behaviour of mYm with time fields, now return a string dump of the field % v1.0.3 minor corrections % v1.0.2 put mYm under GPL license, official release diff --git a/tests/+lib/celleq.m b/tests/+lib/celleq.m new file mode 100644 index 0000000..4544550 --- /dev/null +++ b/tests/+lib/celleq.m @@ -0,0 +1,137 @@ +function [result, why] = celleq(cell1, cell2, funh2string, ignorenan) + % CELLEQ performs an equality comparison between two cell arrays by + % recursively comparing the elements of the cell array, their values and + % sub-values + % + % USAGE: + % + % celleq(cell1, cell2) + % Performs a comparison and returns true if all the elements + % of the cell arrays are identical. It will fail if + % elements include function handles or other objects which don't + % have a defined eq method. + % + % [iseq, info] = celleq(cell1, cell2) + % This syntax returns a logical iseq and a second output info which + % is a structure that contains a field "Reason" which gives you a + % text stack of why the difference occurred as well as a field + % "Where" which contains the indices of the element and subelement + % where the comparison failed. If iseq is true, info contains empty + % strings in its fields. + % + % [...] = celleq(cell1, cell2, funh2string, ignorenan) + % Illustrates an alternate syntax for the function with an additional + % input arguments. funh2string, if true, instructs function handle + % comparisons to return true if the string representations of the + % function handles are the same. ignorenan, if true, will return true + % for nan == nan. By default both properties are set to false + % + % METHOD: + % 1. Compare sizes of cell arrays + % 2. Recursively compare the elements of the cell array and keep track of + % the recursion path (to populate the info variable if comparison fails) + % + % EXAMPLE: + % + % c1 = {1:5, 'blah', {'hello', @disp, {[7 6 NaN 3], false}}, 16}; + % c2 = {1:5, 'blah', {'hello', @disp, {[7 6 NaN 3], true }}, 16}; + % celleq(c1, c2) + % [iseq, info] = celleq(c1, c2) + % [iseq, info] = celleq(c1, c2, true) + % [iseq, info] = celleq(c1, c2, true, true) + % + % LICENSE + % Copyright (c) 2016, The MathWorks, Inc. + % All rights reserved. + % + % Redistribution and use in source and binary forms, with or without + % modification, are permitted provided that the following conditions are met: + % + % * Redistributions of source code must retain the above copyright notice, this + % list of conditions and the following disclaimer. + % + % * Redistributions in binary form must reproduce the above copyright notice, + % this list of conditions and the following disclaimer in the documentation + % and/or other materials provided with the distribution. + % * In all cases, the software is, and all modifications and derivatives of the + % software shall be, licensed to you solely for use in conjunction with + % MathWorks products and service offerings. + % + % THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + % AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + % IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + % DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + % FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + % DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + % SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + % CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + % OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + % OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + if nargin < 3 + funh2string = false; + end + if nargin < 4 + ignorenan = false; + end + result = true; % Prove me wrong! + why = struct('Reason','','Where',''); + if any(size(cell1) ~= size(cell2)) + result = false; + why = struct('Reason','Sizes are different','Where',''); + return + end + for i = 1:numel(cell1) + why = struct('Reason','','Where',sprintf('{%d}',i)); + if any(size(cell1{i}) ~= size(cell2{i})) + result = false; + why.Reason = 'Sizes are different'; + return + end + if ~strcmp(class(cell1{i}),class(cell2{i})) + result = false; + why.Reason = 'Different Classes/Types'; + return + end + % At this point we know they have the same size and class + try + whysub = struct('Reason',['Unequal ' class(cell1{i}) 's'],... + 'Where',''); + + switch class(cell1{i}) + case 'cell' + [result, whysub] = lib.celleq(cell1{i},cell2{i},funh2string, ... + ignorenan); + case 'struct' + [result, whysub] = lib.structeq(cell1{i},cell2{i},funh2string, ... + ignorenan); + case 'function_handle' + if funh2string + result = strcmp(func2str(cell1{i}), func2str(cell1{i})); + else + result = false; + end + case {'double', 'single'} + if ignorenan + cell1{i}(isnan(cell1{i})) = 0; + cell2{i}(isnan(cell2{i})) = 0; + elseif any(isnan(cell1{i}(:))) + whysub.Reason = [whysub.Reason ' that contain NaNs']; + end + result = eq(cell1{i},cell2{i}); + otherwise + result = eq(cell1{i},cell2{i}); + end + % result could be a vector + result = all(result(:)); + if ~result + why.Reason = sprintf('Unequal Subcell <- %s',whysub.Reason); + why.Where = [why.Where whysub.Where]; + return; + end + catch ME + result = false; + why.Reason = ['Subcell comparison failed: ' ME.message]; + return + end + end +end \ No newline at end of file diff --git a/tests/+lib/structeq.m b/tests/+lib/structeq.m new file mode 100644 index 0000000..741b4d0 --- /dev/null +++ b/tests/+lib/structeq.m @@ -0,0 +1,119 @@ +function [result, why] = structeq(struct1, struct2, funh2string, ignorenan) + % STRUCTEQ performs an equality comparison between two structures by + % recursively comparing the elements of the struct array, their fields and + % subfields. This function requires companion function CELLEQ to compare + % two cell arrays. + % + % USAGE: + % + % structeq(struct1, struct2) + % Performs a comparison and returns true if all the subfields and + % properties of the structures are identical. It will fail if + % subfields include function handles or other objects which don't + % have a defined eq method. + % + % [iseq, info] = structeq(struct1, struct2) + % This syntax returns a logical iseq and a second output info which + % is a structure that contains a field "Reason" which gives you a + % text stack of why the difference occurred as well as a field + % "Where" which contains the indices and subfields of the structure + % where the comparison failed. If iseq is true, info contains empty + % strings in its fields. + % + % [...] = structeq(struct1, struct2, funh2string, ignorenan) + % Illustrates an alternate syntax for the function with additional + % input arguments. See the help for CELLEQ for more information on the + % meaning of the arguments + % + % METHOD: + % 1. Compare sizes of struct arrays + % 2. Compare numbers of fields + % 3. Compare field names of the arrays + % 4. For every element of the struct arrays, convert the field values into + % a cell array and do a cell array comparison recursively (this can result + % in multiple recursive calls to CELLEQ and STRUCTEQ) + % + % EXAMPLE: + % % Compare two handle graphics hierarchies + % figure; + % g = surf(peaks(50)); + % rotate3d + % hg1 = handle2struct(gcf); + % set(g,'XDataMode', 'manual'); + % hg2 = handle2struct(gcf); + % + % structeq(hg1, hg2) + % [iseq, info] = structeq(hg1, hg2) + % [iseq, info] = structeq(hg1, hg2, true) + % + % LICENSE + % Copyright (c) 2016, The MathWorks, Inc. + % All rights reserved. + % + % Redistribution and use in source and binary forms, with or without + % modification, are permitted provided that the following conditions are met: + % + % * Redistributions of source code must retain the above copyright notice, this + % list of conditions and the following disclaimer. + % + % * Redistributions in binary form must reproduce the above copyright notice, + % this list of conditions and the following disclaimer in the documentation + % and/or other materials provided with the distribution. + % * In all cases, the software is, and all modifications and derivatives of the + % software shall be, licensed to you solely for use in conjunction with + % MathWorks products and service offerings. + % + % THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + % AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + % IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + % DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + % FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + % DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + % SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + % CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + % OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + % OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + if nargin < 3 + funh2string = false; + end + if nargin < 4 + ignorenan = false; + end + why = struct('Reason','','Where',''); + if any(size(struct1) ~= size(struct2)) + result = false; + why = struct('Reason','Sizes are different',Where,''); + return + end + fields1 = fieldnames(struct1); + fields2 = fieldnames(struct2); + % Check field lengths + if length(fields1) ~= length(fields2) + result = false; + why = struct('Reason','Number of fields are different','Where',''); + return + end + % Check field names + result = lib.celleq(fields1,fields2); + result = all(result); + if ~result + why = struct('Reason','Field names are different','Where',''); + return + end + for i = 1:numel(struct1) + props1 = struct2cell(struct1(i)); + props2 = struct2cell(struct2(i)); + [result, subwhy] = lib.celleq(props1,props2,funh2string,ignorenan); + result = all(result); + if ~result + + [fieldidx, subwhy.Where] = strtok(subwhy.Where, '}'); + fieldidx = str2double(fieldidx(2:end)); + %str2double(regexp(subwhy.Where,'{([0-9]+)}','tokens','once')); + where = sprintf('(%d).%s%s',i,fields1{fieldidx},subwhy.Where(2:end)); + why = struct('Reason',sprintf('Properties are different <- %s', ... + subwhy.Reason),'Where',where); + return + end + end +end \ No newline at end of file diff --git a/tests/Main.m b/tests/Main.m new file mode 100644 index 0000000..abd263b --- /dev/null +++ b/tests/Main.m @@ -0,0 +1,6 @@ +classdef Main < ... + TestConnection & ... + TestExternal & ... + TestInsertFetch & ... + TestTls +end \ No newline at end of file diff --git a/tests/Prep.m b/tests/Prep.m new file mode 100644 index 0000000..4698db5 --- /dev/null +++ b/tests/Prep.m @@ -0,0 +1,112 @@ +classdef Prep < matlab.unittest.TestCase + % Setup and teardown for tests. + properties (Constant) + CONN_INFO_ROOT = struct(... + 'host', getenv('DJ_HOST'), ... + 'user', getenv('DJ_USER'), ... + 'password', getenv('DJ_PASS')); + CONN_INFO = struct(... + 'host', getenv('DJ_TEST_HOST'), ... + 'user', getenv('DJ_TEST_USER'), ... + 'password', getenv('DJ_TEST_PASSWORD')); + PREFIX = 'djtest'; + end + + methods (TestClassSetup) + function init(testCase) + disp('---------------INIT---------------'); + clear functions; + feature('DefaultCharacterSet','UTF-8'); + ghtb.install('guzman-raphael/compareVersions', 'override', true); + + disp(mym('version')); + curr_conn = mym(-1, 'open', testCase.CONN_INFO_ROOT.host, ... + testCase.CONN_INFO_ROOT.user, testCase.CONN_INFO_ROOT.password, ... + 'false'); + + res = mym(curr_conn, 'select @@version as version'); + if compareVersions(res.version,'5.8') + cmd = {... + 'CREATE USER IF NOT EXISTS ''datajoint''@''%%'' ' + 'IDENTIFIED BY ''datajoint'';' + }; + mym(curr_conn, sprintf('%s',cmd{:})); + + cmd = {... + ['GRANT ALL PRIVILEGES ON `' testCase.PREFIX '%%`.* TO ''datajoint''@''%%'';'] + }; + mym(curr_conn, sprintf('%s',cmd{:})); + + cmd = {... + 'CREATE USER IF NOT EXISTS ''djview''@''%%'' ' + 'IDENTIFIED BY ''djview'';' + }; + mym(curr_conn, sprintf('%s',cmd{:})); + + cmd = {... + ['GRANT SELECT ON `' testCase.PREFIX '%%`.* TO ''djview''@''%%'';'] + }; + mym(curr_conn, sprintf('%s',cmd{:})); + + cmd = {... + 'CREATE USER IF NOT EXISTS ''djssl''@''%%'' ' + 'IDENTIFIED BY ''djssl'' ' + 'REQUIRE SSL;' + }; + mym(curr_conn, sprintf('%s',cmd{:})); + + cmd = {... + ['GRANT SELECT ON `' testCase.PREFIX '%%`.* TO ''djssl''@''%%'';'] + }; + mym(curr_conn, sprintf('%s',cmd{:})); + else + cmd = {... + ['GRANT ALL PRIVILEGES ON `' testCase.PREFIX '%%`.* TO ''datajoint''@''%%'' '] + 'IDENTIFIED BY ''datajoint'';' + }; + mym(curr_conn, sprintf('%s',cmd{:})); + + cmd = {... + ['GRANT SELECT ON `' testCase.PREFIX '%%`.* TO ''djview''@''%%'' '] + 'IDENTIFIED BY ''djview'';' + }; + mym(curr_conn, sprintf('%s',cmd{:})); + + cmd = {... + ['GRANT SELECT ON `' testCase.PREFIX '%%`.* TO ''djssl''@''%%'' '] + 'IDENTIFIED BY ''djssl'' ' + 'REQUIRE SSL;' + }; + mym(curr_conn, sprintf('%s',cmd{:})); + end + mym(curr_conn, 'close'); + end + end + methods (TestClassTeardown) + function dispose(testCase) + disp('---------------DISP---------------'); + curr_conn = mym(-1, 'open', testCase.CONN_INFO_ROOT.host, ... + testCase.CONN_INFO_ROOT.user, testCase.CONN_INFO_ROOT.password, ... + 'false'); + + mym(curr_conn, 'SET FOREIGN_KEY_CHECKS=0;'); + res = mym(curr_conn, ... + ['SELECT CAST(schema_name AS char(50)) as db ' ... + 'FROM information_schema.schemata ' ... + 'where schema_name like "' testCase.PREFIX '_%";']); + for i = 1:length(res.db) + mym(curr_conn, ... + ['DROP DATABASE ' res.db{i} ';']); + end + mym(curr_conn, 'SET FOREIGN_KEY_CHECKS=1;'); + + cmd = {... + 'DROP USER ''datajoint''@''%%'';' + 'DROP USER ''djview''@''%%'';' + 'DROP USER ''djssl''@''%%'';' + }; + mym(curr_conn, sprintf('%s',cmd{:})); + mym(curr_conn, 'close'); + end + end +end \ No newline at end of file diff --git a/tests/TestConnection.m b/tests/TestConnection.m new file mode 100644 index 0000000..4dbd23e --- /dev/null +++ b/tests/TestConnection.m @@ -0,0 +1,45 @@ +classdef TestConnection < Prep + % TestConnection tests typical connection scenarios. + methods (Test) + function TestConnection_testNewConnection(testCase) + % force new connections test + st = dbstack; + disp(['---------------' st(1).name '---------------']); + conn1 = mym(-1, 'open', testCase.CONN_INFO.host, ... + testCase.CONN_INFO.user, testCase.CONN_INFO.password, ... + 'false'); + + conn2 = mym(-1, 'open', testCase.CONN_INFO.host, ... + testCase.CONN_INFO.user, testCase.CONN_INFO.password, ... + 'false'); + + connections = evalc('mym(''status'')'); + connections = splitlines(connections); + connections(end)=[]; + + testCase.verifyEqual(length(connections), 2); + mym(conn1, 'close'); + mym(conn2, 'close'); + end + function TestConnection_testReuseConnection(testCase) + % reuse existing connections test + st = dbstack; + disp(['---------------' st(1).name '---------------']); + conn1 = mym('open', testCase.CONN_INFO.host, ... + testCase.CONN_INFO.user, testCase.CONN_INFO.password, ... + 'false'); + + conn2 = mym('open', testCase.CONN_INFO.host, ... + testCase.CONN_INFO.user, testCase.CONN_INFO.password, ... + 'false'); + + connections = evalc('mym(''status'')'); + connections = splitlines(connections); + connections(end)=[]; + + testCase.verifyEqual(conn1, conn2); + testCase.verifyEqual(length(connections), 1); + mym(conn1, 'close'); + end + end +end \ No newline at end of file diff --git a/tests/TestExternal.m b/tests/TestExternal.m new file mode 100644 index 0000000..6f76040 --- /dev/null +++ b/tests/TestExternal.m @@ -0,0 +1,110 @@ +classdef TestExternal < Prep + % TestExternal tests external storage serialization/deserialization. + methods (Test) + function TestExternal_testArraySerialization(testCase) + % array serialization test + st = dbstack; + disp(['---------------' st(1).name '---------------']); + multi(:,:,1) = [4,3;2,7]; + multi(:,:,2) = [0,5;3,8]; + array_tests = { + [5,10], + [2,3,4;4,5,3;5,9,7], + [5], + 1, + [], + [2.3,2.56,2.45], + [4.5342;123.3145;345.2133], + multi, + 'hello', + '' + }; + testCase.TestExternal_verify(testCase, array_tests); + end + function TestExternal_testStructSerialization(testCase) + % struct serialization test + st = dbstack; + disp(['---------------' st(1).name '---------------']); + multi(:,:,1) = [4,3;2,7]; + multi(:,:,2) = [0,5;3,8]; + array_tests = { + struct(... + 'x',[8,5,3;4,6,7;4,8,2],... + 'y',[4,2,8;5,5,8;3,3,8]... + ), + struct('sample',multi), + struct('lvl1',struct('lvl2',multi)), + struct('sample','hi'), + struct('lvl1',struct('lvl2',{[8,5,3;4,6,7;4,8,2], ... + {'now',{[4,2,8;5,5,8;3,3,8]}}, {multi}})), + % ,struct("sample","bye") + }; + testCase.TestExternal_verify(testCase, array_tests); + end + function TestExternal_testCellSerialization(testCase) + % cell serialization test + st = dbstack; + disp(['---------------' st(1).name '---------------']); + multi(:,:,1) = [4,3;2,7]; + multi(:,:,2) = [0,5;3,8]; + array_tests = { + {[8,5,3;4,6,7;4,8,2], [4,2,8;5,5,8;3,3,8]}, + {[8,5,3;4,6,7;4,8,2], [4,2,8;5,5,8;3,3,8], multi}, + {[8,5,3;4,6,7;4,8,2], {0,{[4,2,8;5,5,8;3,3,8]}}, {multi}}, + {'bye', 'now'}, + {[8,5,3;4,6,7;4,8,2], {struct('lvl1',struct('lvl2',multi)), ... + {[4,2,8;5,5,8;3,3,8],'yes'}}, {multi}} + }; + testCase.TestExternal_verify(testCase, array_tests); + end + function TestExternal_testdj0Error(testCase) + st = dbstack; + disp(['---------------' st(1).name '---------------']); + % https://github.com/datajoint/mym/issues/43 + % normal dj0 + % python: pack([1,2,3]).hex().upper() + value = ['646A300002030000000000000004000000000000000A010001040000000000000' ... + '00A01000204000000000000000A010003']; + hexstring = value'; + reshapedString = reshape(hexstring,2,length(value)/2); + hexMtx = reshapedString.'; + decMtx = hex2dec(hexMtx); + packed = uint8(decMtx); + try + unpacked = mym('deserialize', packed); + catch ME + testCase.verifyEqual(ME.identifier, 'mYm:CrossPlatform:Compatibility'); + end + % compressed dj0 + % python: pack([1,2,3]*28).hex().upper() + value = ['5A4C31323300FD03000000000000789C4BC93260600A6180001628CDC5C8C088C4' ... + '664262338FAA195533AA6678A80100444706E9']; + hexstring = value'; + reshapedString = reshape(hexstring,2,length(value)/2); + hexMtx = reshapedString.'; + decMtx = hex2dec(hexMtx); + packed = uint8(decMtx); + try + unpacked = mym('deserialize', packed); + catch ME + testCase.verifyEqual(ME.identifier, 'mYm:CrossPlatform:Compatibility'); + end + end + end + methods (Static) + function TestExternal_verify(testCase, array_tests) + for i = 1 : length(array_tests) + % single serialization test + packed_cell = mym('serialize {M}', array_tests{i}); + packed = packed_cell{1}; + unpacked = mym('deserialize', packed); + testCase.verifyTrue(lib.celleq(array_tests(i),{unpacked})); + end + % Check multiple at once (extras are ignored) + packed_cell = mym('serialize {M}, {M}, {M}, {M}, {M}, {M}, {M}, {M}, {M}, {M}', ... + array_tests{:,1}); + unpacked = cellfun(@(x) mym('deserialize', x), packed_cell,'UniformOutput',false)'; + testCase.verifyTrue(lib.celleq(array_tests',unpacked)); + end + end +end \ No newline at end of file diff --git a/tests/TestInsertFetch.m b/tests/TestInsertFetch.m new file mode 100644 index 0000000..b996d82 --- /dev/null +++ b/tests/TestInsertFetch.m @@ -0,0 +1,133 @@ +classdef TestInsertFetch < Prep + % TestExternal tests inserting and fetching the same result. + methods (Test) + function TestInsertFetch_testInsertFetch(testCase) + % insert/fetch test. + st = dbstack; + disp(['---------------' st(1).name '---------------']); + curr_conn = mym(-1, 'open', testCase.CONN_INFO.host, testCase.CONN_INFO.user, ... + testCase.CONN_INFO.password, 'false'); + mym(curr_conn, ['create database `' testCase.PREFIX '_insert`;']); + + % test random string + testCase.TestInsertFetch_check(testCase, curr_conn, 'varchar(7)','','S','raphael'); + % test 8-byte ASCII string + testCase.TestInsertFetch_check(testCase, curr_conn, 'varchar(32)','','S', ... + 'lteachen'); + testCase.TestInsertFetch_check(testCase, curr_conn, 'longblob','','M', ... + int64([1;2])); + testCase.TestInsertFetch_check(testCase, curr_conn, 'varchar(4)','','S','ýýýý'); + testCase.TestInsertFetch_check(testCase, curr_conn, 'datetime','','S', ... + '2018-01-24 14:34:16'); + + data = '1d751e2e-1e74-faf8-4ab4-85fde8ef72be'; + data = strrep(data, '-', ''); + v = data; + hexstring = v'; + reshapedString = reshape(hexstring,2,16); + hexMtx = reshapedString.'; + decMtx = hex2dec(hexMtx); + v = uint8(decMtx)'; + testCase.TestInsertFetch_check(testCase, curr_conn, 'binary(16)','uuid','B',v); + + data = '1d751e2e-1e74-faf8-4ab4-85fde8ef72be'; + data = strrep(data, '-', ''); + v = data; + hexstring = v'; + reshapedString = reshape(hexstring,2,16); + hexMtx = reshapedString.'; + decMtx = hex2dec(hexMtx); + v = uint8(decMtx); + v_char = char(v)'; + testCase.TestInsertFetch_check(testCase, curr_conn, 'varchar(16)','','S',v_char); + + mym(curr_conn, 'close'); + end + function TestInsertFetch_testNullableBlob(testCase) + % https://github.com/datajoint/datajoint-matlab/issues/195 + curr_conn = mym(-1, 'open', testCase.CONN_INFO.host, testCase.CONN_INFO.user, ... + testCase.CONN_INFO.password, 'false'); + mym(curr_conn, ['create database `' testCase.PREFIX '_nullable`']); + mym(['create table `' testCase.PREFIX '_nullable`.`blob_field` ' ... + '(id int, data longblob default null)']); + mym(['insert into `' testCase.PREFIX '_nullable`.`blob_field` (`id`) values (0)']); + res = mym(curr_conn, ['select * from `' testCase.PREFIX '_nullable`.`blob_field`']); + mym(curr_conn, 'close'); + end + function TestInsertFetch_testString(testCase) + % https://github.com/datajoint/mym/issues/47 + curr_conn = mym(-1, 'open', testCase.CONN_INFO.host, testCase.CONN_INFO.user, ... + testCase.CONN_INFO.password, 'false'); + mym(curr_conn, ['create database `' testCase.PREFIX '_string`']); + mym(['create table `' testCase.PREFIX '_string`.`various` ' ... + '(id int, data varchar(30) default null)']); + mym(['insert into `' testCase.PREFIX '_string`.`various` (`id`, `data`) ' ... + 'values (0, "{S}")'], ''); + res = mym(curr_conn, ['select length(data) as len from `' testCase.PREFIX ... + '_string`.`various`']); + testCase.verifyEqual(double(res.len), 0); + mym(curr_conn, 'close'); + end + function TestInsertFetch_testdj0Error(testCase) + % https://github.com/datajoint/mym/issues/43 + curr_conn = mym(-1, 'open', testCase.CONN_INFO.host, testCase.CONN_INFO.user, ... + testCase.CONN_INFO.password, 'false'); + mym(curr_conn, ['create database `' testCase.PREFIX '_dj0`']); + mym(['create table `' testCase.PREFIX '_dj0`.`blob` ' ... + '(id int, data longblob)']); + % normal dj0 + % python: pack([1,2,3]).hex().upper() + mym(['insert into `' testCase.PREFIX '_dj0`.`blob` (`id`, `data`) values (0, ' ... + 'X''646A300002030000000000000004000000000000000A01000104000000000000000A0' ... + '1000204000000000000000A010003'')']); + try + res = mym(curr_conn, ... + ['select * from `' testCase.PREFIX '_dj0`.`blob` where id=0']); + assert(false); + catch ME + mym(curr_conn, 'close'); + if ~strcmp(ME.identifier,'mYm:CrossPlatform:Compatibility') + rethrow(ME); + end + end + % compressed dj0 + % python: pack([1,2,3]*28).hex().upper() + curr_conn = mym(-1, 'open', testCase.CONN_INFO.host, testCase.CONN_INFO.user, ... + testCase.CONN_INFO.password, 'false'); + mym(['insert into `' testCase.PREFIX '_dj0`.`blob` (`id`, `data`) values (0, ' ... + 'X''5A4C31323300FD03000000000000789C4BC93260600A6180001628CDC5C8C088C4664' ... + '262338FAA195533AA6678A80100444706E9'')']); + try + res = mym(curr_conn, ... + ['select * from `' testCase.PREFIX '_dj0`.`blob` where id=0']); + assert(false); + catch ME + mym(curr_conn, 'close'); + if ~strcmp(ME.identifier,'mYm:CrossPlatform:Compatibility') + rethrow(ME); + end + end + end + end + methods (Static) + function TestInsertFetch_check(testCase, conn_id, mysql_datatype, dj_datatype, ... + flag, data) + table_name = ['test_' strrep(mysql_datatype, '(', '')]; + table_name = strrep(table_name, ')', ''); + if dj_datatype + dj_type_comment = [' comment ":' dj_datatype ':"']; + else + dj_type_comment = []; + end + mym(conn_id, ['create table `' testCase.PREFIX '_insert`.`' table_name ... + '` (id int, name ' mysql_datatype dj_type_comment ');']); + mym(conn_id, ['INSERT INTO `' testCase.PREFIX '_insert`.`' table_name ... + '` (`id`,`name`) VALUES (0,"{' flag '}") '],data); + res = mym(conn_id, ['select * from `' testCase.PREFIX '_insert`.`' table_name '`']); + % Verify that multiple fetch will return same data. + res = mym(conn_id,['select * from `' testCase.PREFIX '_insert`.`' table_name '`']); + ret = res.name{1}; + testCase.verifyTrue(all(ret == data)); + end + end +end \ No newline at end of file diff --git a/tests/TestTls.m b/tests/TestTls.m new file mode 100644 index 0000000..5b1e6c5 --- /dev/null +++ b/tests/TestTls.m @@ -0,0 +1,82 @@ +classdef TestTls < Prep + % TestTls tests TLS connection scenarios. + methods (Test) + function TestTls_testSecureConn(testCase) + % secure connection test + st = dbstack; + disp(['---------------' st(1).name '---------------']); + curr_conn = mym(-1, 'open', testCase.CONN_INFO.host, testCase.CONN_INFO.user, ... + testCase.CONN_INFO.password, 'true'); + + connections = evalc('mym(''status'')'); + connections = splitlines(connections); + connections(end)=[]; + testCase.verifyTrue(contains(connections{curr_conn+1},'encrypted')); + + res = mym(curr_conn, 'SHOW STATUS LIKE ''Ssl_cipher'';'); + testCase.verifyTrue(length(res.Value{1}) > 0); + mym(curr_conn, 'close'); + end + function TestTls_testInsecureConn(testCase) + % insecure connection test + st = dbstack; + disp(['---------------' st(1).name '---------------']); + curr_conn = mym(-1, 'open', testCase.CONN_INFO.host, testCase.CONN_INFO.user, ... + testCase.CONN_INFO.password, 'false'); + + connections = evalc('mym(''status'')'); + connections = splitlines(connections); + connections(end)=[]; + testCase.verifyTrue(~contains(connections{curr_conn+1},'encrypted')); + + res = mym(curr_conn, 'SHOW STATUS LIKE ''Ssl_cipher'';'); + testCase.verifyEqual( ... + res.Value{1}, ... + ''); + mym(curr_conn, 'close'); + end + function TestTls_testPreferredConn(testCase) + % preferred connection test + st = dbstack; + disp(['---------------' st(1).name '---------------']); + conn = []; + conn(1) = check(mym(-1, 'open', testCase.CONN_INFO.host, ... + testCase.CONN_INFO.user, testCase.CONN_INFO.password)); + + conn(2) = check(mym(-1, 'open', testCase.CONN_INFO.host, ... + testCase.CONN_INFO.user, testCase.CONN_INFO.password, 'none')); + + conn(3) = check(mym(-1, 'open', testCase.CONN_INFO.host, ... + testCase.CONN_INFO.user, testCase.CONN_INFO.password, 'anything')); + + for idx = 1:length(conn) + mym(conn(idx), 'close'); + end + + function conn_id = check(conn_id) + connections = evalc('mym(''status'')'); + connections = splitlines(connections); + connections(end)=[]; + testCase.verifyTrue(contains(connections{conn_id+1},'encrypted')); + + res = mym(conn_id, 'SHOW STATUS LIKE ''Ssl_cipher'';'); + testCase.verifyTrue(length(res.Value{1}) > 0); + end + end + function TestTls_testRejectException(testCase) + % test exception on require TLS + st = dbstack; + disp(['---------------' st(1).name '---------------']); + try + curr_conn = mym(-1, 'open', testCase.CONN_INFO.host, ... + 'djssl', 'djssl', 'false'); + testCase.verifyTrue(false); + mym(curr_conn, 'close'); + catch ME + testCase.verifyEqual(ME.identifier, 'MySQL:Error'); + testCase.verifyTrue(contains(ME.message,'requires secure connection') || ... + contains(ME.message,'Access denied')); %MySQL8 or MySQL5 + end + end + end +end \ No newline at end of file