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