diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c43bdf2..94531cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,12 +11,11 @@ env: jobs: build: - # ubuntu support dropped due to https://github.com/ankane/setup-mysql/commit/70636bf8d2c54521a1b871af766b58d76b468d94 - runs-on: macos-12 + runs-on: ubuntu-20.04 strategy: matrix: # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ - node-version: [14, 16] + node-version: [16, 18, 20] steps: - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} @@ -25,7 +24,7 @@ jobs: node-version: ${{ matrix.node-version }} - uses: ankane/setup-mysql@v1 with: - mysql-version: 5.7 + mysql-version: 8.0 - run: | sudo mysql -e "CREATE USER '${{ secrets.MYSQL_USER }}'@'localhost' IDENTIFIED BY '${{ secrets.MYSQL_PASSWORD }}'" sudo mysql -u root -e "GRANT ALL PRIVILEGES ON *.* TO '${{ secrets.MYSQL_USER }}'@'localhost'" @@ -34,6 +33,7 @@ jobs: - run: npm install - run: npm test env: + MYSQL_HOST: '127.0.0.1' MYSQL_USER: ${{ secrets.MYSQL_USER }} MYSQL_PASSWORD: ${{ secrets.MYSQL_PASSWORD }} CI: true @@ -42,11 +42,11 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: - - uses: actions/checkout@v2 - - name: Use Node.js 14 - uses: actions/setup-node@v2 + - uses: actions/checkout@v3 + - name: Use Node.js 18 + uses: actions/setup-node@v3 with: - node-version: 14 + node-version: 18 - name: Bootstrap project run: | npm ci --ignore-scripts @@ -58,13 +58,13 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 - - name: Use Node.js 14 - uses: actions/setup-node@v2 + - name: Use Node.js 18 + uses: actions/setup-node@v3 with: - node-version: 14 + node-version: 18 - name: Bootstrap project run: | npm ci --ignore-scripts diff --git a/README.md b/README.md index bcd67cb..e37accf 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ In your application root directory, enter this command to install the connector: npm install loopback-connector-mysql --save ``` -**Note**: The MySQL connector requires MySQL 5.0+. +**Note**: Since `loopback-connector-mysql` v7.x.x, this MySQL connector has dropped support for MySQL 5.7 and requires MySQL 8.0+. This installs the module from npm and adds it as a dependency to the application's `package.json` file. diff --git a/lib/migration.js b/lib/migration.js index f208680..6e69174 100644 --- a/lib/migration.js +++ b/lib/migration.js @@ -893,7 +893,11 @@ function mixinMigration(MySQL, mysql) { case 'int': case 'integer': case 'bigint': - columnType = integerOptions(p, columnType); + // As of MySQL 8.0.17, the display width attribute is deprecated for integer data types; + // you should expect support for it to be removed in a future version of MySQL. + // As of MySQL 8.0.17, the UNSIGNED attribute is deprecated for columns of type FLOAT, DOUBLE, and DECIMAL (and any synonyms); + // you should expect support for it to be removed in a future version of MySQL. + columnType = unsigned(p, columnType); break; case 'decimal': @@ -906,7 +910,6 @@ function mixinMigration(MySQL, mysql) { columnType = floatingPointOptions(p, columnType); break; } - columnType = unsigned(p, columnType); return columnType; } @@ -944,55 +947,6 @@ function mixinMigration(MySQL, mysql) { return columnType; } - function integerOptions(p, columnType) { - let tmp = 0; - if (p.display || p.limit) { - tmp = Number(p.display || p.limit); - } - if (tmp > 0) { - columnType += '(' + tmp + ')'; - } else if (p.unsigned) { - switch (columnType.toLowerCase()) { - default: - case 'int': - columnType += '(10)'; - break; - case 'mediumint': - columnType += '(8)'; - break; - case 'smallint': - columnType += '(5)'; - break; - case 'tinyint': - columnType += '(3)'; - break; - case 'bigint': - columnType += '(20)'; - break; - } - } else { - switch (columnType.toLowerCase()) { - default: - case 'int': - columnType += '(11)'; - break; - case 'mediumint': - columnType += '(9)'; - break; - case 'smallint': - columnType += '(6)'; - break; - case 'tinyint': - columnType += '(4)'; - break; - case 'bigint': - columnType += '(20)'; - break; - } - } - return columnType; - } - function dateOptionsByType(p, columnType) { switch (columnType.toLowerCase()) { default: diff --git a/lib/mysql.js b/lib/mysql.js index bf10905..3b1551e 100644 --- a/lib/mysql.js +++ b/lib/mysql.js @@ -9,7 +9,7 @@ const g = require('strong-globalize')(); /*! * Module dependencies */ -const mysql = require('mysql'); +const mysql = require('mysql2'); const SqlConnector = require('loopback-connector').SqlConnector; const ParameterizedSQL = SqlConnector.ParameterizedSQL; @@ -353,6 +353,11 @@ MySQL.prototype.toColumnValue = function(prop, val) { if (val === null) { if (this.isNullable(prop)) { return val; + } else if (prop.type === Date) { + // MySQL has disallowed comparison of date types with strings. + // https://bugs.mysql.com/bug.php?id=95466 + // https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-16.html + return new Date(); } else { try { const castNull = prop.type(val); @@ -569,19 +574,28 @@ MySQL.prototype.buildExpression = function(columnName, operator, operatorValue, let clause; switch (operator) { case 'regexp': - clause = columnName + ' REGEXP ?'; - // By default, MySQL regexp is not case sensitive. (https://dev.mysql.com/doc/refman/5.7/en/regexp.html) - // To allow case sensitive regexp query, it has to be binded to a `BINARY` type. - // If ignore case is not specified, search it as case sensitive. - if (!operatorValue.ignoreCase) { - clause = columnName + ' REGEXP BINARY ?'; + // https://dev.mysql.com/doc/refman/8.0/en/regexp.html#function_regexp-like + // REGEXP_LIKE(expr, pat[, match_type]) - match_type parameter now support c,i and m flags of RegExp + let matchType = ''; + if (operatorValue.ignoreCase === false) { + matchType += 'c'; + } else if (operatorValue.ignoreCase === true) { + matchType += 'i'; } - if (operatorValue.global) - g.warn('{{MySQL}} {{regex}} syntax does not respect the {{`g`}} flag'); + if (operatorValue.multiline) { + matchType += 'm'; + } - if (operatorValue.multiline) - g.warn('{{MySQL}} {{regex}} syntax does not respect the {{`m`}} flag'); + if (operatorValue.global) { + g.warn('{{MySQL}} {{regex}} syntax does not respect the {{`g`}} flag'); + } + + if (!!matchType) { + clause = `REGEXP_LIKE(${columnName}, ?, '${matchType}')`; + } else { + clause = `REGEXP_LIKE(${columnName}, ?)`; + } return new ParameterizedSQL(clause, [operatorValue.source]); diff --git a/package-lock.json b/package-lock.json index 2c4763b..8694c9f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "debug": "^4.1.1", "lodash": "^4.17.11", "loopback-connector": "^5.2.0", - "mysql": "^2.11.1", + "mysql2": "^3.3.5", "strong-globalize": "^6.0.4" }, "devDependencies": { @@ -373,14 +373,6 @@ "node": ">=0.10" } }, - "node_modules/bignumber.js": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", - "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==", - "engines": { - "node": "*" - } - }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -694,6 +686,14 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "engines": { + "node": ">=0.10" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -1152,6 +1152,14 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "dependencies": { + "is-property": "^1.0.2" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -1312,6 +1320,17 @@ "node": ">=8.12.0" } }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", @@ -1460,6 +1479,11 @@ "node": ">=8" } }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==" + }, "node_modules/is-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", @@ -1589,6 +1613,11 @@ "node": ">=10" } }, + "node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, "node_modules/loopback-connector": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/loopback-connector/-/loopback-connector-5.2.0.tgz", @@ -1893,24 +1922,50 @@ "safe-buffer": "^5.1.2" } }, - "node_modules/mysql": { - "version": "2.18.1", - "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz", - "integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==", + "node_modules/mysql2": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.3.5.tgz", + "integrity": "sha512-ZTQGAzxGeaX1PyeSiZFCgQ34uiXguaEpn3aTFN9Enm9JDnbwWo+4/CJnDdQZ3n0NaMeysi8vwtW/jNUb9VqVDw==", "dependencies": { - "bignumber.js": "9.0.0", - "readable-stream": "2.3.7", - "safe-buffer": "5.1.2", - "sqlstring": "2.3.1" + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.6.3", + "long": "^5.2.1", + "lru-cache": "^8.0.0", + "named-placeholders": "^1.1.3", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" }, "engines": { - "node": ">= 0.6" + "node": ">= 8.0" } }, - "node_modules/mysql/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "node_modules/mysql2/node_modules/lru-cache": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz", + "integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==", + "engines": { + "node": ">=16.14" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", + "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", + "dependencies": { + "lru-cache": "^7.14.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/named-placeholders/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "engines": { + "node": ">=12" + } }, "node_modules/nanoid": { "version": "3.1.20", @@ -2386,6 +2441,11 @@ } ] }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, "node_modules/semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -2412,6 +2472,11 @@ "upper-case-first": "^2.0.2" } }, + "node_modules/seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" + }, "node_modules/serialize-javascript": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", @@ -2573,9 +2638,9 @@ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "node_modules/sqlstring": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", - "integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A=", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", "engines": { "node": ">= 0.6" } @@ -3280,11 +3345,6 @@ "resolved": "https://registry.npmjs.org/bcp47/-/bcp47-1.1.2.tgz", "integrity": "sha1-NUvjMH/9CEM6ePXh4glYRfifx/4=" }, - "bignumber.js": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", - "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==" - }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -3537,6 +3597,11 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, + "denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==" + }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -3887,6 +3952,14 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, + "generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "requires": { + "is-property": "^1.0.2" + } + }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -3999,6 +4072,14 @@ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==" }, + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, "ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", @@ -4105,6 +4186,11 @@ "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "dev": true }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==" + }, "is-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", @@ -4213,6 +4299,11 @@ "chalk": "^4.0.0" } }, + "long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, "loopback-connector": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/loopback-connector/-/loopback-connector-5.2.0.tgz", @@ -4448,21 +4539,40 @@ "safe-buffer": "^5.1.2" } }, - "mysql": { - "version": "2.18.1", - "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz", - "integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==", + "mysql2": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.3.5.tgz", + "integrity": "sha512-ZTQGAzxGeaX1PyeSiZFCgQ34uiXguaEpn3aTFN9Enm9JDnbwWo+4/CJnDdQZ3n0NaMeysi8vwtW/jNUb9VqVDw==", "requires": { - "bignumber.js": "9.0.0", - "readable-stream": "2.3.7", - "safe-buffer": "5.1.2", - "sqlstring": "2.3.1" + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.6.3", + "long": "^5.2.1", + "lru-cache": "^8.0.0", + "named-placeholders": "^1.1.3", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" }, "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "lru-cache": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz", + "integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==" + } + } + }, + "named-placeholders": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", + "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", + "requires": { + "lru-cache": "^7.14.1" + }, + "dependencies": { + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==" } } }, @@ -4817,6 +4927,11 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, "semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -4837,6 +4952,11 @@ "upper-case-first": "^2.0.2" } }, + "seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" + }, "serialize-javascript": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", @@ -4978,9 +5098,9 @@ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "sqlstring": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", - "integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A=" + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==" }, "stable": { "version": "0.1.8", diff --git a/package.json b/package.json index b405a4f..5af5645 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "6.2.0", "description": "MySQL connector for loopback-datasource-juggler", "engines": { - "node": "14 || 16" + "node": ">=16" }, "main": "index.js", "scripts": { @@ -24,7 +24,7 @@ "debug": "^4.1.1", "lodash": "^4.17.11", "loopback-connector": "^5.2.0", - "mysql": "^2.11.1", + "mysql2": "^3.3.5", "strong-globalize": "^6.0.4" }, "devDependencies": { diff --git a/setup.sh b/setup.sh index 1801fbc..c57355d 100755 --- a/setup.sh +++ b/setup.sh @@ -48,9 +48,7 @@ printf "\n${RED}>> Finding old builds and cleaning up${PLAIN} ${GREEN}...${PLAIN docker rm -f $MYSQL_CONTAINER > /dev/null 2>&1 printf "\n${CYAN}Clean up complete.${PLAIN}\n" -## Pin mysql docker image to version as `mysql` node.js driver does not support v8 yet -## See https://github.com/mysqljs/mysql/issues/2002 -DOCKER_IMAGE=mysql:5.7.22 +DOCKER_IMAGE=mysql:8.0 ## pull latest mysql image printf "\n${RED}>> Pulling ${DOCKER_IMAGE} image${PLAIN} ${GREEN}...${PLAIN}" diff --git a/test/connection.test.js b/test/connection.test.js index 681ac0f..f6fa31f 100644 --- a/test/connection.test.js +++ b/test/connection.test.js @@ -45,10 +45,10 @@ describe('connections', function() { }); it('should use utf8 charset', function(done) { - const test_set = /utf8/; - const test_collo = /utf8_general_ci/; - const test_set_str = 'utf8'; - const test_set_collo = 'utf8_general_ci'; + const test_set = /utf8mb4/; + const test_collo = /utf8mb4_0900_ai_ci/; + const test_set_str = 'utf8mb4'; + const test_set_collo = 'utf8mb4_0900_ai_ci'; charsetTest(test_set, test_collo, test_set_str, test_set_collo, done); }); diff --git a/test/datetime.test.js b/test/datetime.test.js index 4aaa85d..11ff34d 100644 --- a/test/datetime.test.js +++ b/test/datetime.test.js @@ -23,7 +23,8 @@ describe('MySQL datetime handling', function() { // Modifying the connection timezones mid-flight is a pain, // but closing the existing connection requires more effort. function setConnectionTimezones(tz) { - db.connector.client._allConnections.forEach(function(con) { + // _allConnections is a Queue in mysql2 library + db.connector.client._allConnections.toArray().forEach(function(con) { con.config.timezone = tz; }); } diff --git a/test/migration.test.js b/test/migration.test.js index ed53553..622f1e9 100644 --- a/test/migration.test.js +++ b/test/migration.test.js @@ -27,7 +27,7 @@ describe('migrations', function() { should.not.exist(err); should.exist(result); result[0].Key.should.equal('PRI'); - result[0].Type.should.equal('bigint(20)'); + result[0].Type.should.equal('bigint'); done(); }); }); @@ -38,7 +38,7 @@ describe('migrations', function() { fields.should.be.eql({ id: { Field: 'id', - Type: 'int(11)', + Type: 'int', Null: 'NO', Key: 'PRI', Default: null, @@ -73,7 +73,7 @@ describe('migrations', function() { Extra: ''}, pendingPeriod: { Field: 'pendingPeriod', - Type: 'int(11)', + Type: 'int', Null: 'YES', Key: '', Default: null, @@ -197,25 +197,25 @@ describe('migrations', function() { getFields('NumberData', function(err, fields) { fields.should.be.eql({ id: {Field: 'id', - Type: 'int(11)', + Type: 'int', Null: 'NO', Key: 'PRI', Default: null, Extra: 'auto_increment'}, number: {Field: 'number', - Type: 'decimal(10,3) unsigned', + Type: 'decimal(10,3)', Null: 'NO', Key: 'MUL', Default: null, Extra: ''}, tinyInt: {Field: 'tinyInt', - Type: 'tinyint(2)', + Type: 'tinyint', Null: 'YES', Key: '', Default: null, Extra: ''}, mediumInt: {Field: 'mediumInt', - Type: 'mediumint(8) unsigned', + Type: 'mediumint unsigned', Null: 'NO', Key: '', Default: null, @@ -235,7 +235,7 @@ describe('migrations', function() { getFields('DateData', function(err, fields) { fields.should.be.eql({ id: {Field: 'id', - Type: 'int(11)', + Type: 'int', Null: 'NO', Key: 'PRI', Default: null, @@ -290,8 +290,8 @@ describe('migrations', function() { // add new column assert.ok(fields.newProperty, 'New column was not added'); if (fields.newProperty) { - assert.equal(fields.newProperty.Type, 'bigint(20) unsigned', - 'New column type is not bigint(20) unsigned'); + assert.equal(fields.newProperty.Type, 'bigint unsigned', + 'New column type is not bigint unsigned'); } // drop column - will not happen. // assert.ok(!fields.pendingPeriod, @@ -400,7 +400,7 @@ describe('migrations', function() { getFields('DefaultData', function(err, fields) { fields.should.be.eql({ id: {Field: 'id', - Type: 'int(11)', + Type: 'int', Null: 'NO', Key: 'PRI', Default: null, @@ -410,13 +410,13 @@ describe('migrations', function() { Null: 'YES', Key: '', Default: 'CURRENT_TIMESTAMP', - Extra: ''}, + Extra: 'DEFAULT_GENERATED'}, timestamp: {Field: 'timestamp', Type: 'timestamp', Null: 'YES', Key: '', Default: 'CURRENT_TIMESTAMP', - Extra: ''}, + Extra: 'DEFAULT_GENERATED'}, isAdmin: {Field: 'isAdmin', Type: 'tinyint(1)', Null: 'YES', @@ -424,7 +424,7 @@ describe('migrations', function() { Default: '0', Extra: ''}, number: {Field: 'number', - Type: 'int(10) unsigned', + Type: 'int unsigned', Null: 'NO', Key: 'MUL', Default: '256', @@ -502,7 +502,7 @@ describe('migrations', function() { query('INSERT INTO `DateData` ' + '(`dateTime`, `timestamp`) ' + 'VALUES("0000-00-00 00:00:00", "0000-00-00 00:00:00") ', function(err) { - const errMsg = 'ER_TRUNCATED_WRONG_VALUE: Incorrect datetime value: ' + + const errMsg = 'Incorrect datetime value: ' + '\'0000-00-00 00:00:00\' for column \'dateTime\' at row 1'; assert(err); assert.equal(err.message, errMsg); @@ -518,7 +518,7 @@ describe('migrations', function() { query('INSERT INTO `DateData` ' + '(`dateTime`, `timestamp`) ' + 'VALUES("1000-01-01 00:00:00", "0000-00-00 00:00:00") ', function(err) { - const errMsg = 'ER_TRUNCATED_WRONG_VALUE: Incorrect datetime value: ' + + const errMsg = 'Incorrect datetime value: ' + '\'0000-00-00 00:00:00\' for column \'timestamp\' at row 1'; assert(err); assert.equal(err.message, errMsg); diff --git a/test/mysql.autoupdate.test.js b/test/mysql.autoupdate.test.js index f98e024..f8288a3 100644 --- a/test/mysql.autoupdate.test.js +++ b/test/mysql.autoupdate.test.js @@ -222,18 +222,24 @@ describe('MySQL connector', function() { assert(isActual, 'isActual should return true after automigrate'); ds.discoverModelProperties('customer_test', function(err, props) { assert.equal(props.length, 5); + // Mysql versions on different OS versions return results in different orders + props.sort(function(a, b) { + return a.columnName > b.columnName ? 1 : -1; + }); const names = props.map(function(p) { return p.columnName; }); - assert.equal(props[0].nullable, 'N'); + + assert.equal(names[0], 'age'); + assert.equal(names[1], 'customer_discount'); + assert.equal(names[2], 'email'); + assert.equal(names[3], 'id'); + assert.equal(names[4], 'name'); + assert.equal(props[0].nullable, 'Y'); assert.equal(props[1].nullable, 'Y'); assert.equal(props[2].nullable, 'N'); - assert.equal(props[3].nullable, 'Y'); - assert.equal(names[0], 'id'); - assert.equal(names[1], 'name'); - assert.equal(names[2], 'email'); - assert.equal(names[3], 'age'); - assert.equal(names[4], 'customer_discount'); + assert.equal(props[3].nullable, 'N'); + assert.equal(props[4].nullable, 'Y'); ds.connector.execute('SHOW INDEXES FROM customer_test', function(err, indexes) { if (err) return done(err); @@ -250,16 +256,20 @@ describe('MySQL connector', function() { ds.discoverModelProperties('customer_test', function(err, props) { if (err) return done(err); assert.equal(props.length, 7); + // Mysql versions on different OS versions return results in different orders + props.sort(function(a, b) { + return a.columnName > b.columnName ? 1 : -1; + }); const names = props.map(function(p) { return p.columnName; }); - assert.equal(names[0], 'id'); - assert.equal(names[1], 'email'); + assert.equal(names[0], 'customer_address'); + assert.equal(names[1], 'customer_code'); assert.equal(names[2], 'customer_discount'); - assert.equal(names[3], 'firstName'); - assert.equal(names[4], 'lastName'); - assert.equal(names[5], 'customer_address'); - assert.equal(names[6], 'customer_code'); + assert.equal(names[3], 'email'); + assert.equal(names[4], 'firstName'); + assert.equal(names[5], 'id'); + assert.equal(names[6], 'lastName'); ds.connector.execute('SHOW INDEXES FROM customer_test', function(err, updatedindexes) { if (err) return done(err); assert(updatedindexes); @@ -268,10 +278,11 @@ describe('MySQL connector', function() { assert.equal(updatedindexes[1].Column_name, 'customer_code'); assert.equal(updatedindexes[2].Key_name, 'updated_name_index'); assert.equal(updatedindexes[3].Key_name, 'updated_name_index'); - // Mysql supports only index sorting in ascending; DESC is ignored assert.equal(updatedindexes[1].Collation, 'A'); assert.equal(updatedindexes[2].Collation, 'A'); - assert.equal(updatedindexes[3].Collation, 'A'); + // MySQL8 supports descending indexes: + // DESC in an index definition is no longer ignored but causes storage of key values in descending order. + assert.equal(updatedindexes[3].Collation, 'D'); assert.equal(updatedindexes[1].Non_unique, 0); assert.equal(updatedindexes[2].Non_unique, 0); assert.equal(updatedindexes[3].Non_unique, 0); @@ -761,7 +772,7 @@ describe('MySQL connector', function() { // validate that the foreign key exists and points to the right column assert(createTable); assert(createTable.length.should.be.equal(1)); - assert(/ON DELETE CASCADE ON UPDATE NO ACTION/.test(createTable[0]['Create Table']), 'Constraint must have correct trigger'); + assert(/ON DELETE CASCADE/.test(createTable[0]['Create Table']), 'Constraint must have correct trigger'); ds.createModel(schema_v2.name, schema_v2.properties, schema_v2.options); ds.isActual(function(err, isActual) { diff --git a/test/mysql.discover.test.js b/test/mysql.discover.test.js index 34266ab..d2b97c3 100644 --- a/test/mysql.discover.test.js +++ b/test/mysql.discover.test.js @@ -103,18 +103,19 @@ describe('Discover models including other users', function() { it('should return an array of all tables and views', function(done) { db.discoverModelDefinitions({ all: true, - limit: 3, }, function(err, models) { if (err) { console.error(err); done(err); } else { let others = false; - assert.equal(3, models.length); - models.forEach(function(m) { + models.find(function(m) { assert(m.owner); if (m.owner !== 'STRONGLOOP') { others = true; + return true; + } else { + return false; } }); assert(others, 'Should have tables/views owned by others'); @@ -133,7 +134,7 @@ describe('Discover model properties', function() { done(err); } else { models.forEach(function(m) { - assert(m.tableName === 'product'); + assert(m.tableName.toLowerCase() === 'product'); }); done(null, models); } @@ -150,7 +151,7 @@ describe('Discover model primary keys', function() { done(err); } else { models.forEach(function(m) { - assert(m.tableName === 'product'); + assert(m.tableName.toLowerCase() === 'product'); }); done(null, models); } @@ -164,7 +165,7 @@ describe('Discover model primary keys', function() { done(err); } else { models.forEach(function(m) { - assert(m.tableName === 'product'); + assert(m.tableName.toLowerCase() === 'product'); }); done(null, models); } @@ -206,7 +207,7 @@ describe('Discover model generated columns', function() { db.discoverModelProperties('product', function(err, models) { if (err) return done(err); models.forEach(function(model) { - assert(model.tableName === 'product'); + assert(model.tableName.toLowerCase() === 'product'); assert(!model.generated, 'STRONGLOOP.PRODUCT table should not have generated (identity) columns'); }); done(); @@ -216,7 +217,7 @@ describe('Discover model generated columns', function() { db.discoverModelProperties('testgen', function(err, models) { if (err) return done(err); models.forEach(function(model) { - assert(model.tableName === 'testgen'); + assert(model.tableName.toLowerCase() === 'testgen'); if (model.columnName === 'ID') { assert(model.generated, 'STRONGLOOP.TESTGEN.ID should be a generated (identity) column'); }