From b0fad7601d5f0cbbab43f20dbe27a9ae64c21ac1 Mon Sep 17 00:00:00 2001 From: rusher Date: Wed, 29 Nov 2023 15:15:31 +0100 Subject: [PATCH 01/16] [misc] timeout test correction --- test/integration/test-connection-opts.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/test-connection-opts.js b/test/integration/test-connection-opts.js index ecf179f2..1291c835 100644 --- a/test/integration/test-connection-opts.js +++ b/test/integration/test-connection-opts.js @@ -360,7 +360,7 @@ describe('connection option', () => { conn .query({ timeout: 1000, - sql: 'SELECT 1;select c1.* from information_schema.columns as c1, information_schema.tables, information_schema.tables as t2' + sql: 'select c1.* from information_schema.columns as c1, information_schema.tables, information_schema.tables as t2' }) .then(() => { conn.end(); @@ -386,7 +386,7 @@ describe('connection option', () => { conn .query({ timeout: 1000, - sql: 'SELECT 1;select c1.* from information_schema.columns as c1, information_schema.tables, information_schema.tables as t2' + sql: 'select c1.* from information_schema.columns as c1, information_schema.tables, information_schema.tables as t2' }) .then(() => { conn.end(); From 7222551fb0b8733263f073ab178aca9694a36452 Mon Sep 17 00:00:00 2001 From: rusher Date: Wed, 29 Nov 2023 15:15:57 +0100 Subject: [PATCH 02/16] [CONJS-271] wrong binary decoding of 00:00:00 TIME values #262 --- lib/io/packet.js | 19 +++++++++++++------ test/integration/datatype/test-time.js | 20 ++++++++++++++++++++ 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/lib/io/packet.js b/lib/io/packet.js index 6d0f6f17..464ece93 100644 --- a/lib/io/packet.js +++ b/lib/io/packet.js @@ -455,13 +455,20 @@ class Packet { readBinaryTime() { const len = this.buf[this.pos++]; - const negate = this.buf[this.pos++] === 1; - const hour = this.readUInt32() * 24 + this.readUInt8(); - const min = this.readUInt8(); - const sec = this.readUInt8(); + let negate = false; + let hour = 0; + let min = 0; + let sec = 0; let microSec = 0; - if (len > 8) { - microSec = this.readUInt32(); + + if (len > 0) { + negate = this.buf[this.pos++] === 1; + hour = this.readUInt32() * 24 + this.readUInt8(); + min = this.readUInt8(); + sec = this.readUInt8(); + if (len > 8) { + microSec = this.readUInt32(); + } } let val = appendZero(hour, 2) + ':' + appendZero(min, 2) + ':' + appendZero(sec, 2); if (microSec > 0) { diff --git a/test/integration/datatype/test-time.js b/test/integration/datatype/test-time.js index ece02f65..e4426a30 100644 --- a/test/integration/datatype/test-time.js +++ b/test/integration/datatype/test-time.js @@ -30,4 +30,24 @@ describe('time', () => { assert.equal(results[1].t2, '25:00:00'); await shareConn.commit(); }); + + it('prepare time data', async function () { + // skipping test for mysql since TIME doesn't have microseconds + if (!shareConn.info.isMariaDB()) this.skip(); + + await shareConn.query('DROP TABLE IF EXISTS time_data'); + await shareConn.query('CREATE TABLE time_data(t1 time(6), t2 time(6))'); + await shareConn.beginTransaction(); + await shareConn.execute('INSERT INTO time_data VALUES (?, ?)', ['-838:59:58', '-838:59:59.999999']); + await shareConn.execute('INSERT INTO time_data VALUES (?, ?)', ['00:00:00', '-838:59:59.999999']); + await shareConn.execute('INSERT INTO time_data VALUES (?, ?)', ['-1:00:00', '25:00:00']); + let results = await shareConn.execute('SELECT * FROM time_data'); + assert.equal(results[0].t1, '-838:59:58'); + assert.equal(results[0].t2, isXpand() ? '-838:59:59.000000' : '-838:59:59.999999'); + assert.equal(results[1].t1, '00:00:00'); + assert.equal(results[1].t2, isXpand() ? '-838:59:59.000000' : '-838:59:59.999999'); + assert.equal(results[2].t1, '-01:00:00'); + assert.equal(results[2].t2, '25:00:00'); + await shareConn.commit(); + }); }); From af3e77ecbcbd551aaaad9979aa09ad39a4b1686c Mon Sep 17 00:00:00 2001 From: rusher Date: Thu, 30 Nov 2023 12:24:20 +0100 Subject: [PATCH 03/16] [misc] JSON test correction --- test/integration/datatype/test-json.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/datatype/test-json.js b/test/integration/datatype/test-json.js index fa946078..fc6737ec 100644 --- a/test/integration/datatype/test-json.js +++ b/test/integration/datatype/test-json.js @@ -56,7 +56,7 @@ describe('json', () => { await conn.query('INSERT INTO `test-json-insert-type` values (?)', [JSON.stringify(obj2)]); await conn.execute('INSERT INTO `test-json-insert-type` values (?)', [JSON.stringify(obj2)]); const rows = await conn.query('SELECT * FROM `test-json-insert-type`'); - if (!serverPermitExtendedInfos) { + if (!serverPermitExtendedInfos && conn.info.isMariaDB()) { const val1 = JSON.parse(rows[0].val1); const val2 = JSON.parse(rows[1].val1); const val3 = JSON.parse(rows[2].val1); From 44aac6d78642ffb3b2c3ed3ea514a869e784e48e Mon Sep 17 00:00:00 2001 From: rusher Date: Thu, 30 Nov 2023 14:34:09 +0100 Subject: [PATCH 04/16] [CONJS-273] Bulk insert error when last bunch of parameters is reaching max_allowed_packet #258 --- lib/cmd/batch-bulk.js | 2 +- test/integration/test-batch.js | 44 ++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/lib/cmd/batch-bulk.js b/lib/cmd/batch-bulk.js index e1d2101a..7ec75419 100644 --- a/lib/cmd/batch-bulk.js +++ b/lib/cmd/batch-bulk.js @@ -250,7 +250,7 @@ class BatchBulk extends Parser { out.writeBuffer(lastCmdData, 0, lastCmdData.length); out.mark(); lastCmdData = null; - if (!this.rowIdx >= this.values.length) { + if (this.rowIdx >= this.values.length) { break; } this.vals = this.values[this.rowIdx++]; diff --git a/test/integration/test-batch.js b/test/integration/test-batch.js index 980d1fa7..f748bc97 100644 --- a/test/integration/test-batch.js +++ b/test/integration/test-batch.js @@ -848,6 +848,42 @@ describe('batch', function () { await conn.end(); }; + const bigBatchWith16mMaxAllowedPacketBig = async (useCompression, useBulk) => { + const conn = await base.createConnection({ + compress: useCompression, + maxAllowedPacket: 16 * 1024 * 1024, + bulk: useBulk + }); + conn.query('DROP TABLE IF EXISTS bigBatchWith16mMaxAllowedPacketBig'); + conn.query('CREATE TABLE bigBatchWith16mMaxAllowedPacketBig(id int, t LONGTEXT) CHARSET utf8mb4'); + await conn.query('FLUSH TABLES'); + await conn.query('START TRANSACTION'); + const testSize = 15 * 1024 * 1024; + const buf = Buffer.alloc(testSize); + for (let i = 0; i < testSize; i++) { + buf[i] = 97 + (i % 10); + } + const str = buf.toString(); + const values = []; + for (let i = 0; i < 5; i++) { + values.push([i, str]); + } + let res = await conn.batch('INSERT INTO `bigBatchWith16mMaxAllowedPacketBig` values (?, ?)', values); + assert.equal(res.affectedRows, 5); + + res = await conn.query('select * from `bigBatchWith16mMaxAllowedPacketBig`'); + assert.deepEqual(res, [ + { id: 0, t: str }, + { id: 1, t: str }, + { id: 2, t: str }, + { id: 3, t: str }, + { id: 4, t: str } + ]); + + await conn.query('ROLLBACK'); + await conn.end(); + }; + const bigBatchWith4mMaxAllowedPacket = async (useCompression, useBulk) => { const conn = await base.createConnection({ compress: useCompression, @@ -1403,6 +1439,14 @@ describe('batch', function () { await bigBatchWith16mMaxAllowedPacket(useCompression, true); }); + it('16M+ batch with 16M max_allowed_packet big insert', async function () { + // // skipping in maxscale due to a bug: https://jira.mariadb.org/browse/MXS-3588 + if (process.env.srv === 'maxscale' || process.env.srv === 'skysql-ha') this.skip(); + if (!RUN_LONG_TEST || maxAllowedSize <= testSize) return this.skip(); + this.timeout(320000); + await bigBatchWith16mMaxAllowedPacketBig(useCompression, true); + }); + it('16M+ batch with max_allowed_packet set to 4M', async function () { if (!RUN_LONG_TEST || maxAllowedSize <= 4 * 1024 * 1024) { this.skip(); From 316d77f3507c0a263deeef6e94b43d737dd03336 Mon Sep 17 00:00:00 2001 From: rusher Date: Thu, 30 Nov 2023 14:46:39 +0100 Subject: [PATCH 05/16] [misc] documentation correction of insert id when multi insert #261 --- documentation/callback-api.md | 6 +++--- documentation/promise-api.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/documentation/callback-api.md b/documentation/callback-api.md index c4bee3b1..6f6cb11b 100644 --- a/documentation/callback-api.md +++ b/documentation/callback-api.md @@ -523,9 +523,9 @@ https.get("https://node.green/#ES2018-features-Promise-prototype-finally-basic-s Queries issued from the Connector return two different kinds of results: a JSON object and an array, depending on the type of query you issue. Queries that write to the database, such as `INSERT`, `DELETE` and `UPDATE` commands return a JSON object with the following properties: -* `affectedRows`: Indicates the number of rows affected by the query. -* `insertId`: Shows the last auto-increment value from an `INSERT`. -* `warningStatus`: Indicates whether the query ended with a warning. +* `affectedRows`: An integer listing the number of affected rows. +* `insertId`: An integer noting the auto-increment ID. In case multiple rows have been inserted, this corresponds to the FIRST auto-increment value. +* `warningStatus`: An integer indicating whether the query ended with a warning. ```js connection.query( diff --git a/documentation/promise-api.md b/documentation/promise-api.md index a6e3ec0b..de7e1640 100644 --- a/documentation/promise-api.md +++ b/documentation/promise-api.md @@ -600,7 +600,7 @@ https.get( Queries return two different kinds of results, depending on the type of query you execute. When you execute write statements, (such as `INSERT`, `DELETE` and `UPDATE`), the method returns a JSON object with the following properties: * `affectedRows`: An integer listing the number of affected rows. -* `insertId`: An integer noting the auto-increment ID of the last row written to the table. +* `insertId`: An integer noting the auto-increment ID. In case multiple rows have been inserted, this corresponds to the FIRST auto-increment value. * `warningStatus`: An integer indicating whether the query ended with a warning. ```js From 94aa56311f0c8af95e29f281b29d9ddc9f514f5c Mon Sep 17 00:00:00 2001 From: rusher Date: Thu, 30 Nov 2023 16:23:58 +0100 Subject: [PATCH 06/16] [misc] remove EOL servers --- .travis.yml | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/.travis.yml b/.travis.yml index e9896b3f..1bf2a82d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -87,27 +87,16 @@ jobs: name: "CS 10.4" - env: srv=mariadb v=10.5 local=1 DISABLE_SSL=1 dist: bionic + name: "CS 10.5 - node.js 14" + node_js: 14 + - env: srv=mariadb v=10.11 local=1 + name: "CS 10.11 - node.js 16" node_js: 16 - name: "CS 10.5" - - env: srv=mariadb v=10.9 local=1 - name: "CS 10.9" - - env: srv=mariadb v=10.10 local=1 - name: "CS 10.10 - node.js 18" + - env: srv=mariadb v=11.0 local=1 CLEAR_TEXT=1 node_js: 18 - - env: srv=mariadb v=10.11 local=1 - name: "CS 10.11" - - env: srv=mariadb v=11.0 local=1 - name: "CS 11.0" + name: "CS 11.0 - node.js 18" - env: srv=mariadb v=11.1 local=1 - name: "CS 11.1" - - env: srv=mariadb v=10.6 local=1 - name: "CS 10.6 - node.js 14" - node_js: 14 - - env: srv=mariadb v=10.6 local=1 CLEAR_TEXT=1 - node_js: 16 - name: "CS 10.6 - node.js 16" - - env: srv=mariadb v=10.6 local=1 - name: "CS 10.6 - node.js 20" + name: "CS 11.1 - node.js 20" node_js: 20 - env: srv=mysql v=5.7 name: "MySQL 5.7" From cdb1fbb8a65ac3f31a3ce3b3f07430f7a6c9a263 Mon Sep 17 00:00:00 2001 From: rusher Date: Fri, 1 Dec 2023 13:30:37 +0100 Subject: [PATCH 07/16] [misc] help freeing memory in case of timeout --- lib/cmd/parser.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/cmd/parser.js b/lib/cmd/parser.js index 3fba2194..8be89181 100644 --- a/lib/cmd/parser.js +++ b/lib/cmd/parser.js @@ -55,6 +55,10 @@ class Parser extends Command { //* ERROR response //********************************************************************************************************* case 0xff: + // in case of timeout, free accumulated rows + this._columns = null; + this._rows = null; + const err = packet.readError(info, this.displaySql(), this.stack); //force in transaction status, since query will have created a transaction if autocommit is off //goal is to avoid unnecessary COMMIT/ROLLBACK. From 014d7381ee28c9d05730b0f1723575b142b26e0a Mon Sep 17 00:00:00 2001 From: rusher Date: Tue, 5 Dec 2023 15:37:53 +0100 Subject: [PATCH 08/16] [CONJS-274] permit disabling BULK insert for one batch --- lib/cmd/parser.js | 4 ++-- lib/connection.js | 8 +++++++- test/integration/test-batch.js | 5 +++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/cmd/parser.js b/lib/cmd/parser.js index 8be89181..29cb01a7 100644 --- a/lib/cmd/parser.js +++ b/lib/cmd/parser.js @@ -57,7 +57,7 @@ class Parser extends Command { case 0xff: // in case of timeout, free accumulated rows this._columns = null; - this._rows = null; + this._rows = []; const err = packet.readError(info, this.displaySql(), this.stack); //force in transaction status, since query will have created a transaction if autocommit is off @@ -217,7 +217,7 @@ class Parser extends Command { success(val) { this.successEnd(val); this._columns = null; - this._rows = null; + this._rows = []; } /** diff --git a/lib/connection.js b/lib/connection.js index a14c219e..d35a54e0 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -578,11 +578,17 @@ class Connection extends EventEmitter { if (_options && _options.fullResult) return false; // not using info.isMariaDB() directly in case of callback use, // without connection being completely finished. + const bulkEnable = + _options === undefined || _options === null + ? this.opts.bulk + : _options.bulk !== undefined && _options.bulk !== null + ? _options.bulk + : this.opts.bulk; if ( this.info.serverVersion && this.info.serverVersion.mariaDb && this.info.hasMinVersion(10, 2, 7) && - this.opts.bulk && + bulkEnable && (this.info.serverCapabilities & Capabilities.MARIADB_CLIENT_STMT_BULK_OPERATIONS) > 0n ) { //ensure that there is no stream object diff --git a/test/integration/test-batch.js b/test/integration/test-batch.js index f748bc97..bc940f93 100644 --- a/test/integration/test-batch.js +++ b/test/integration/test-batch.js @@ -808,6 +808,11 @@ describe('batch', function () { err.message.includes('This command is not supported in the prepared statement protocol yet'), err.message ); + // ensure option is taken in account + await conn.batch({ bulk: false, sql: 'SELECT ? as id, ? as t' }, [ + [1, 'john'], + [2, 'jack'] + ]); } } await conn.end(); From debbbf532290ed55568174a536e2249c4b52c672 Mon Sep 17 00:00:00 2001 From: diego Date: Wed, 6 Dec 2023 14:39:30 +0100 Subject: [PATCH 09/16] [CONJS-272] Error doesn't always have parameters according to option `logParam` --- lib/cmd/batch-bulk.js | 13 ++- lib/cmd/class/prepare-result-packet.js | 13 ++- lib/cmd/command.js | 4 +- lib/cmd/execute.js | 10 +- lib/cmd/parser.js | 58 ++++++------ lib/cmd/query.js | 4 +- lib/config/connection-options.js | 12 +-- lib/connection.js | 6 +- lib/io/packet.js | 4 +- lib/pool.js | 12 ++- test/integration/datatype/test-mapping.js | 4 +- test/integration/test-batch-geometry-type.js | 80 ++++++++-------- test/integration/test-big-query.js | 4 +- test/integration/test-error.js | 8 +- test/integration/test-execute.js | 98 ++++++++++++++++++++ test/unit/config/test-options.js | 2 +- 16 files changed, 226 insertions(+), 106 deletions(-) diff --git a/lib/cmd/batch-bulk.js b/lib/cmd/batch-bulk.js index 7ec75419..3a433b4f 100644 --- a/lib/cmd/batch-bulk.js +++ b/lib/cmd/batch-bulk.js @@ -213,7 +213,7 @@ class BatchBulk extends Parser { */ sendComStmtBulkExecute(out, opts, info) { if (opts.logger.query) - opts.logger.query(`BULK: (${this.prepare.id}) sql: ${opts.logger.logParam ? this.displaySql() : this.sql}`); + opts.logger.query(`BULK: (${this.prepare.id}) sql: ${opts.logParam ? this.displaySql() : this.sql}`); const parameterCount = this.prepare.parameterCount; this.rowIdx = 0; this.vals = this.values[this.rowIdx++]; @@ -353,10 +353,9 @@ class BatchBulk extends Parser { for (let i = 0; i < this.initialValues.length; i++) { if (i !== 0) sqlMsg += ','; let param = this.initialValues[i]; - sqlMsg = this.logParameters(sqlMsg, param); + sqlMsg = Parser.logParameters(this.opts, sqlMsg, param); if (sqlMsg.length > this.opts.debugLen) { - sqlMsg = sqlMsg.substring(0, this.opts.debugLen) + '...'; - break; + return sqlMsg.substring(0, this.opts.debugLen) + '...'; } } sqlMsg += ']'; @@ -482,9 +481,9 @@ class BatchBulk extends Parser { if (this.values[r].length < nbParameter) { this.emit('send_end'); this.throwNewError( - `Expect ${nbParameter} parameters, but at index ${r}, parameters only contains ${ - this.values[r].length - }\n ${this.displaySql()}`, + `Expect ${nbParameter} parameters, but at index ${r}, parameters only contains ${this.values[r].length}\n ${ + this.opts.logParam ? this.displaySql() : this.sql + }`, false, info, 'HY000', diff --git a/lib/cmd/class/prepare-result-packet.js b/lib/cmd/class/prepare-result-packet.js index 20594a89..98f02be3 100644 --- a/lib/cmd/class/prepare-result-packet.js +++ b/lib/cmd/class/prepare-result-packet.js @@ -5,6 +5,7 @@ const CommandParameter = require('../../command-parameter'); const Errors = require('../../misc/errors'); const ExecuteStream = require('../execute-stream'); +const Parser = require('../parser'); /** * Prepare result @@ -37,12 +38,22 @@ class PrepareResultPacket { } if (this.isClose()) { + let sql = this.query; + if (this.conn.opts.logParam) { + if (this.query.length > this.conn.opts.debugLen) { + sql = this.query.substring(0, this.conn.opts.debugLen) + '...'; + } else { + let sqlMsg = this.query + ' - parameters:'; + sql = Parser.logParameters(this.conn.opts, sqlMsg, values); + } + } + const error = Errors.createError( `Execute fails, prepare command as already been closed`, Errors.ER_PREPARE_CLOSED, null, '22000', - this.query + sql ); if (!_cb) { diff --git a/lib/cmd/command.js b/lib/cmd/command.js index e6602309..21e6f552 100644 --- a/lib/cmd/command.js +++ b/lib/cmd/command.js @@ -41,7 +41,7 @@ class Command extends EventEmitter { errno, info, sqlState, - this.displaySql(), + this.opts && this.opts.logParam ? this.displaySql() : this.sql, fatal, this.cmdParam ? this.cmdParam.stack : null, false @@ -80,7 +80,7 @@ class Command extends EventEmitter { * @param info connection information */ sendCancelled(msg, errno, info) { - const err = Errors.createError(msg, errno, info, 'HY000', this.displaySql()); + const err = Errors.createError(msg, errno, info, 'HY000', this.opts.logParam ? this.displaySql() : this.sql); this.emit('send_end'); this.throwError(err, info); } diff --git a/lib/cmd/execute.js b/lib/cmd/execute.js index 9d0ba573..200a5897 100644 --- a/lib/cmd/execute.js +++ b/lib/cmd/execute.js @@ -57,9 +57,7 @@ class Execute extends Parser { Buffer.isBuffer(value)) ) { if (opts.logger.query) - opts.logger.query( - `EXECUTE: (${this.prepare.id}) sql: ${opts.logger.logParam ? this.displaySql() : this.sql}` - ); + opts.logger.query(`EXECUTE: (${this.prepare.id}) sql: ${opts.logParam ? this.displaySql() : this.sql}`); if (!this.longDataStep) { this.longDataStep = true; this.registerStreamSendEvent(out, info); @@ -73,7 +71,7 @@ class Execute extends Parser { if (!this.longDataStep) { // no stream parameter, so can send directly if (opts.logger.query) - opts.logger.query(`EXECUTE: (${this.prepare.id}) sql: ${opts.logger.logParam ? this.displaySql() : this.sql}`); + opts.logger.query(`EXECUTE: (${this.prepare.id}) sql: ${opts.logParam ? this.displaySql() : this.sql}`); this.sendComStmtExecute(out, info); } } @@ -88,7 +86,9 @@ class Execute extends Parser { //validate parameter size. if (this.prepare.parameterCount > this.values.length) { this.sendCancelled( - `Parameter at position ${this.values.length} is not set\\nsql: ${this.displaySql()}`, + `Parameter at position ${this.values.length} is not set\\nsql: ${ + this.opts.logParam ? this.displaySql() : this.sql + }`, Errors.ER_MISSING_PARAMETER, info ); diff --git a/lib/cmd/parser.js b/lib/cmd/parser.js index 29cb01a7..ff65d24a 100644 --- a/lib/cmd/parser.js +++ b/lib/cmd/parser.js @@ -59,7 +59,7 @@ class Parser extends Command { this._columns = null; this._rows = []; - const err = packet.readError(info, this.displaySql(), this.stack); + const err = packet.readError(info, opts.logParam ? this.displaySql() : this.sql, this.stack); //force in transaction status, since query will have created a transaction if autocommit is off //goal is to avoid unnecessary COMMIT/ROLLBACK. info.status |= ServerStatus.STATUS_IN_TRANS; @@ -370,7 +370,10 @@ class Parser extends Command { //force in transaction status, since query will have created a transaction if autocommit is off //goal is to avoid unnecessary COMMIT/ROLLBACK. info.status |= ServerStatus.STATUS_IN_TRANS; - return this.throwError(packet.readError(info, this.displaySql(), this.stack), info); + return this.throwError( + packet.readError(info, this.opts.logParam ? this.displaySql() : this.sql, this.stack), + info + ); } if ((!info.eofDeprecated && packet.length() < 13) || (info.eofDeprecated && packet.length() < 0xffffff)) { @@ -444,7 +447,7 @@ class Parser extends Command { } let sqlMsg = this.sql + ' - parameters:'; - return this.logParameters(sqlMsg, this.initialValues); + return Parser.logParameters(this.opts, sqlMsg, this.initialValues); } if (this.sql.length > this.opts.debugLen) { return this.sql.substring(0, this.opts.debugLen) + '... - parameters:[]'; @@ -452,8 +455,8 @@ class Parser extends Command { return this.sql + ' - parameters:[]'; } - logParameters(sqlMsg, values) { - if (this.opts.namedPlaceholders) { + static logParameters(opts, sqlMsg, values) { + if (opts.namedPlaceholders) { sqlMsg += '{'; let first = true; for (let key in values) { @@ -465,9 +468,8 @@ class Parser extends Command { sqlMsg += "'" + key + "':"; let param = values[key]; sqlMsg = Parser.logParam(sqlMsg, param); - if (sqlMsg.length > this.opts.debugLen) { - sqlMsg = sqlMsg.substring(0, this.opts.debugLen) + '...'; - break; + if (sqlMsg.length > opts.debugLen) { + return sqlMsg.substring(0, opts.debugLen) + '...'; } } sqlMsg += '}'; @@ -478,15 +480,14 @@ class Parser extends Command { if (i !== 0) sqlMsg += ','; let param = values[i]; sqlMsg = Parser.logParam(sqlMsg, param); - if (sqlMsg.length > this.opts.debugLen) { - sqlMsg = sqlMsg.substring(0, this.opts.debugLen) + '...'; - break; + if (sqlMsg.length > opts.debugLen) { + return sqlMsg.substring(0, opts.debugLen) + '...'; } } } else { sqlMsg = Parser.logParam(sqlMsg, values); - if (sqlMsg.length > this.opts.debugLen) { - sqlMsg = sqlMsg.substring(0, this.opts.debugLen) + '...'; + if (sqlMsg.length > opts.debugLen) { + return sqlMsg.substring(0, opts.debugLen) + '...'; } } sqlMsg += ']'; @@ -583,7 +584,7 @@ class Parser extends Command { Errors.ER_LOCAL_INFILE_NOT_READABLE, info, '22000', - this.sql + this.opts.logParam ? this.displaySql() : this.sql ); error.cause = e; process.nextTick(this.reject, error); @@ -592,19 +593,22 @@ class Parser extends Command { return (this.onPacketReceive = this.readResponsePacket); } - stream.on('error', (err) => { - out.writeEmptyPacket(); - const error = Errors.createError( - `LOCAL INFILE command failed: ${err.message}`, - Errors.ER_LOCAL_INFILE_NOT_READABLE, - info, - '22000', - this.sql - ); - process.nextTick(this.reject, error); - this.reject = null; - this.resolve = null; - }); + stream.on( + 'error', + function (err) { + out.writeEmptyPacket(); + const error = Errors.createError( + `LOCAL INFILE command failed: ${err.message}`, + Errors.ER_LOCAL_INFILE_NOT_READABLE, + info, + '22000', + this.sql + ); + process.nextTick(this.reject, error); + this.reject = null; + this.resolve = null; + }.bind(this) + ); stream.on('data', (chunk) => { out.writeBuffer(chunk, 0, chunk.length); }); diff --git a/lib/cmd/query.js b/lib/cmd/query.js index 3ad04dd7..80df3a44 100644 --- a/lib/cmd/query.js +++ b/lib/cmd/query.js @@ -29,7 +29,7 @@ class Query extends Parser { * @param info connection information */ start(out, opts, info) { - if (opts.logger.query) opts.logger.query(`QUERY: ${opts.logger.logParam ? this.displaySql() : this.sql}`); + if (opts.logger.query) opts.logger.query(`QUERY: ${opts.logParam ? this.displaySql() : this.sql}`); this.onPacketReceive = this.readResponsePacket; if (this.initialValues === undefined) { //shortcut if no parameters @@ -50,7 +50,7 @@ class Query extends Parser { this.encodedSql, info, this.initialValues, - this.displaySql.bind(this) + this.opts.logParam ? this.displaySql.bind(this) : () => this.sql ); this.paramPositions = parsed.paramPositions; this.values = parsed.values; diff --git a/lib/config/connection-options.js b/lib/config/connection-options.js index 767ad39b..c45869f8 100644 --- a/lib/config/connection-options.js +++ b/lib/config/connection-options.js @@ -36,27 +36,26 @@ class ConnectionOptions { this.debug = opts.debug || false; this.debugCompress = opts.debugCompress || false; this.debugLen = opts.debugLen || 256; - + this.logParam = opts.logParam === undefined ? true : opts.logParam === true; if (opts.logger) { if (typeof opts.logger === 'function') { this.logger = { network: opts.logger, query: opts.logger, error: opts.logger, - warning: opts.logger, - logParam: true + warning: opts.logger }; } else { this.logger = { network: opts.logger.network, query: opts.logger.query, error: opts.logger.error, - warning: opts.logger.warning || console.log, - logParam: opts.logger.logParam == null ? true : opts.logger.logParam + warning: opts.logger.warning || console.log }; + if (opts.logger.logParam !== undefined) this.logParam = opts.logger.logParam; } } else { - this.logger = { network: null, query: null, error: null, warning: console.log, logParam: false }; + this.logger = { network: null, query: null, error: null, warning: console.log }; if ((this.debug || this.debugCompress) && !this.logger.network) { this.logger.network = console.log; } @@ -186,6 +185,7 @@ class ConnectionOptions { if (opts.charsetNumber && !isNaN(Number.parseInt(opts.charsetNumber))) { opts.charsetNumber = Number.parseInt(opts.charsetNumber); } + if (opts.logParam) opts.logParam = opts.logParam === 'true'; if (opts.compress) opts.compress = opts.compress === 'true'; if (opts.connectAttributes) opts.connectAttributes = JSON.parse(opts.connectAttributes); if (opts.connectTimeout) opts.connectTimeout = parseInt(opts.connectTimeout); diff --git a/lib/connection.js b/lib/connection.js index d35a54e0..7cdef44c 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -177,7 +177,7 @@ class Connection extends EventEmitter { Errors.ER_BATCH_WITH_NO_VALUES, this.info, 'HY000', - cmdParam.sql, + cmdParam.sql.length > this.opts.debugLen ? cmdParam.sql.substring(0, this.opts.debugLen) + '...' : cmdParam.sql, false, cmdParam.stack ); @@ -582,8 +582,8 @@ class Connection extends EventEmitter { _options === undefined || _options === null ? this.opts.bulk : _options.bulk !== undefined && _options.bulk !== null - ? _options.bulk - : this.opts.bulk; + ? _options.bulk + : this.opts.bulk; if ( this.info.serverVersion && this.info.serverVersion.mariaDb && diff --git a/lib/io/packet.js b/lib/io/packet.js index 464ece93..c8266127 100644 --- a/lib/io/packet.js +++ b/lib/io/packet.js @@ -448,8 +448,8 @@ class Packet { ? '.' + appendZero(microSec, 6).substring(0, scale) : '.' + appendZero(microSec, 6) : scale > 0 - ? '.' + appendZero(microSec, 6).substring(0, scale) - : '') + ? '.' + appendZero(microSec, 6).substring(0, scale) + : '') ); } diff --git a/lib/pool.js b/lib/pool.js index c6335403..3352363d 100644 --- a/lib/pool.js +++ b/lib/pool.js @@ -522,7 +522,15 @@ class Pool extends EventEmitter { getConnection(cmdParam) { if (this.#closed) { return Promise.reject( - Errors.createError('pool is closed', Errors.ER_POOL_ALREADY_CLOSED, null, 'HY000', null, false, cmdParam.stack) + Errors.createError( + 'pool is closed', + Errors.ER_POOL_ALREADY_CLOSED, + null, + 'HY000', + cmdParam === null ? null : cmdParam.sql, + false, + cmdParam.stack + ) ); } return this._doAcquire().then( @@ -538,7 +546,7 @@ class Pool extends EventEmitter { Errors.ER_POOL_ALREADY_CLOSED, null, 'HY000', - null, + cmdParam === null ? null : cmdParam.sql, false, cmdParam.stack ); diff --git a/test/integration/datatype/test-mapping.js b/test/integration/datatype/test-mapping.js index 38ddbbdf..48bf26e3 100644 --- a/test/integration/datatype/test-mapping.js +++ b/test/integration/datatype/test-mapping.js @@ -312,12 +312,12 @@ describe('mapping', () => { let rows = await shareConn.query('SELECT * FROM dataTypeWithNull'); assert.ok(Buffer.isBuffer(rows[0].test)); assert.ok(Buffer.isBuffer(rows[0].test2)); - assert.ok(typeof typeof rows[0].test3 === 'string' || typeof rows[0].test3 instanceof String); + assert.ok(typeof typeof rows[0].test3 === 'string' || (typeof rows[0].test3) instanceof String); rows = await shareConn.execute('SELECT * FROM dataTypeWithNull'); assert.ok(Buffer.isBuffer(rows[0].test)); assert.ok(Buffer.isBuffer(rows[0].test2)); - assert.ok(typeof typeof rows[0].test3 === 'string' || typeof rows[0].test3 instanceof String); + assert.ok(typeof typeof rows[0].test3 === 'string' || (typeof rows[0].test3) instanceof String); await shareConn.commit(); }); }); diff --git a/test/integration/test-batch-geometry-type.js b/test/integration/test-batch-geometry-type.js index 115c906a..dffcbeca 100644 --- a/test/integration/test-batch-geometry-type.js +++ b/test/integration/test-batch-geometry-type.js @@ -167,8 +167,8 @@ describe('batch geometry type', () => { type: 'LineString' } : serverPermitExtendedInfos - ? { type: 'LineString' } - : null + ? { type: 'LineString' } + : null }, { g: serverPermitExtendedInfos ? { type: 'LineString' } : null @@ -322,8 +322,8 @@ describe('batch geometry type', () => { coordinates: [] } : serverPermitExtendedInfos - ? { type: 'Polygon' } - : null + ? { type: 'Polygon' } + : null }, { g: serverPermitExtendedInfos ? { type: 'Polygon' } : null @@ -431,10 +431,10 @@ describe('batch geometry type', () => { coordinates: [] } : shareConn.info.hasMinVersion(10, 5, 2) && - process.env.srv !== 'maxscale' && - process.env.srv !== 'skysql-ha' - ? { type: 'MultiPoint' } - : null + process.env.srv !== 'maxscale' && + process.env.srv !== 'skysql-ha' + ? { type: 'MultiPoint' } + : null }, { g: supportBulk @@ -443,10 +443,10 @@ describe('batch geometry type', () => { coordinates: [] } : shareConn.info.hasMinVersion(10, 5, 2) && - process.env.srv !== 'maxscale' && - process.env.srv !== 'skysql-ha' - ? { type: 'MultiPoint' } - : null + process.env.srv !== 'maxscale' && + process.env.srv !== 'skysql-ha' + ? { type: 'MultiPoint' } + : null } ]); } else { @@ -560,10 +560,10 @@ describe('batch geometry type', () => { coordinates: [[]] } : shareConn.info.hasMinVersion(10, 5, 2) && - process.env.srv !== 'maxscale' && - process.env.srv !== 'skysql-ha' - ? { type: 'MultiLineString' } - : null + process.env.srv !== 'maxscale' && + process.env.srv !== 'skysql-ha' + ? { type: 'MultiLineString' } + : null }, { g: supportBulk @@ -572,10 +572,10 @@ describe('batch geometry type', () => { coordinates: [] } : shareConn.info.hasMinVersion(10, 5, 2) && - process.env.srv !== 'maxscale' && - process.env.srv !== 'skysql-ha' - ? { type: 'MultiLineString' } - : null + process.env.srv !== 'maxscale' && + process.env.srv !== 'skysql-ha' + ? { type: 'MultiLineString' } + : null }, { g: supportBulk @@ -584,10 +584,10 @@ describe('batch geometry type', () => { coordinates: [] } : shareConn.info.hasMinVersion(10, 5, 2) && - process.env.srv !== 'maxscale' && - process.env.srv !== 'skysql-ha' - ? { type: 'MultiLineString' } - : null + process.env.srv !== 'maxscale' && + process.env.srv !== 'skysql-ha' + ? { type: 'MultiLineString' } + : null } ]); } else { @@ -788,10 +788,10 @@ describe('batch geometry type', () => { coordinates: [[[]]] } : shareConn.info.hasMinVersion(10, 5, 2) && - process.env.srv !== 'maxscale' && - process.env.srv !== 'skysql-ha' - ? { type: 'MultiPolygon' } - : null + process.env.srv !== 'maxscale' && + process.env.srv !== 'skysql-ha' + ? { type: 'MultiPolygon' } + : null }, { g: supportBulk @@ -800,10 +800,10 @@ describe('batch geometry type', () => { coordinates: [[]] } : shareConn.info.hasMinVersion(10, 5, 2) && - process.env.srv !== 'maxscale' && - process.env.srv !== 'skysql-ha' - ? { type: 'MultiPolygon' } - : null + process.env.srv !== 'maxscale' && + process.env.srv !== 'skysql-ha' + ? { type: 'MultiPolygon' } + : null }, { g: supportBulk @@ -812,10 +812,10 @@ describe('batch geometry type', () => { coordinates: [] } : shareConn.info.hasMinVersion(10, 5, 2) && - process.env.srv !== 'maxscale' && - process.env.srv !== 'skysql-ha' - ? { type: 'MultiPolygon' } - : null + process.env.srv !== 'maxscale' && + process.env.srv !== 'skysql-ha' + ? { type: 'MultiPolygon' } + : null }, { g: supportBulk @@ -824,10 +824,10 @@ describe('batch geometry type', () => { coordinates: [] } : shareConn.info.hasMinVersion(10, 5, 2) && - process.env.srv !== 'maxscale' && - process.env.srv !== 'skysql-ha' - ? { type: 'MultiPolygon' } - : null + process.env.srv !== 'maxscale' && + process.env.srv !== 'skysql-ha' + ? { type: 'MultiPolygon' } + : null } ]); } else { diff --git a/test/integration/test-big-query.js b/test/integration/test-big-query.js index e0cfe7f4..fe2b9701 100644 --- a/test/integration/test-big-query.js +++ b/test/integration/test-big-query.js @@ -65,10 +65,10 @@ describe('Big query', function () { } catch (e) { assert.isTrue( e.sql.includes( - "insert into bigParameterBigParam(b) values(?) - parameters:[['test'],[0x6162636465666768696a6162636465666768696a6162636465666768696a6162636465666768696a6162636465666768696a6162636465666768696a6162636465666768696a6162636465666768696a6162636465666768696a6162...]" + "insert into bigParameterBigParam(b) values(?) - parameters:[['test'],[0x6162636465666768696a6162636465666768696a6162636465666768696a6162636465666768696a6162636465666768696a6162636465666768696a6162636465666768696a6162636465666768696a6162636465666768696a6162..." ) || e.sql.includes( - 'insert into bigParameterBigParam(b) values(?) - parameters:[0x6162636465666768696a6162636465666768696a6162636465666768696a6162636465666768696a6162636465666768696a6162636465666768696a6162636465666768696a6162636465666768696a6162636465666768696a61626364656667...]' + 'insert into bigParameterBigParam(b) values(?) - parameters:[0x6162636465666768696a6162636465666768696a6162636465666768696a6162636465666768696a6162636465666768696a6162636465666768696a6162636465666768696a6162636465666768696a6162636465666768696a61626364656667...' ) ); } diff --git a/test/integration/test-error.js b/test/integration/test-error.js index 0ced1361..ed4ebfcf 100644 --- a/test/integration/test-error.js +++ b/test/integration/test-error.js @@ -154,8 +154,8 @@ describe('Error', () => { } else { if (!isXpand()) { assert.isTrue(err.message.includes('You have an error in your SQL syntax')); - assert.isTrue(err.message.includes("sql: wrong query ?, ? - parameters:[123456789,'long paramete...]")); - assert.equal(err.sql, "wrong query ?, ? - parameters:[123456789,'long paramete...]"); + assert.isTrue(err.message.includes("sql: wrong query ?, ? - parameters:[123456789,'long paramete...")); + assert.equal(err.sql, "wrong query ?, ? - parameters:[123456789,'long paramete..."); assert.equal(err.sqlState, 42000); } assert.equal(err.errno, 1064); @@ -257,8 +257,8 @@ describe('Error', () => { assert.equal(err.errno, 1064); assert.equal(err.code, 'ER_PARSE_ERROR'); } - assert.isTrue(err.message.includes("sql: wrong query :par1, :par2 - parameters:{'par1':'some par...}")); - assert.equal(err.sql, "wrong query :par1, :par2 - parameters:{'par1':'some par...}"); + assert.isTrue(err.message.includes("sql: wrong query :par1, :par2 - parameters:{'par1':'some par...")); + assert.equal(err.sql, "wrong query :par1, :par2 - parameters:{'par1':'some par..."); conn.end(); done(); diff --git a/test/integration/test-execute.js b/test/integration/test-execute.js index b9edcbca..266a91a8 100644 --- a/test/integration/test-execute.js +++ b/test/integration/test-execute.js @@ -98,6 +98,51 @@ describe('prepare and execute', () => { conn.end(); }); + it('logger error', async () => { + let errorLogged = ''; + const conn = await base.createConnection({ + logger: { + error: (msg) => { + errorLogged += msg + '\n'; + } + } + }); + try { + await conn.query('SELECT * FROM nonexistant WHERE a = ? AND b= ?', ['a', true]); + } catch (e) { + // eat + } + console.log(errorLogged); + assert.isTrue( + errorLogged.includes( + "Table 'testn.nonexistant' doesn't exist\n" + + "sql: SELECT * FROM nonexistant WHERE a = ? AND b= ? - parameters:['a',true]" + ), + errorLogged + ); + conn.end(); + }); + + it('logger error without parameters', async () => { + let errorLogged = ''; + const conn = await base.createConnection({ + logger: { + error: (msg) => { + errorLogged += msg + '\n'; + } + }, + logParam: false + }); + try { + await conn.query('SELECT * FROM NONEXISTANT WHERE a = ? AND b= ?', ['a', true]); + } catch (e) { + // eat + } + console.log(errorLogged); + assert.isFalse(errorLogged.includes(" - parameters:['a',true]")); + conn.end(); + }); + it('prepare close with cache', async () => { const conn = await base.createConnection({ prepareCacheLength: 2 }); for (let i = 0; i < 10; i++) { @@ -166,11 +211,64 @@ describe('prepare and execute', () => { const prepare = await conn.prepare('select ?'); await prepare.execute('1'); await prepare.close(); + try { + await prepare.execute('1'); + throw new Error('must have thrown error'); + } catch (e) { + assert.equal(e.sql, "select ? - parameters:['1']"); + assert.isTrue(e.message.includes('Execute fails, prepare command as already been closed')); + } + try { + await prepare.execute([1, 2]); + throw new Error('must have thrown error'); + } catch (e) { + assert.equal(e.sql, 'select ? - parameters:[1,2]'); + assert.isTrue(e.message.includes('Execute fails, prepare command as already been closed')); + } + const prepare2 = await conn.prepare('select ?'); + await prepare2.execute('2'); + await prepare2.close(); + + conn.end(); + }); + + it('prepare after prepare close - no cache - error trunk', async () => { + const conn = await base.createConnection({ prepareCacheLength: 0, debugLen: 8 }); + const prepare = await conn.prepare('select ?'); + await prepare.execute('1'); + await prepare.close(); + try { + await prepare.execute('1'); + throw new Error('must have thrown error'); + } catch (e) { + assert.equal(e.sql, 'select ?...'); + assert.isTrue(e.message.includes('Execute fails, prepare command as already been closed')); + } + try { + await prepare.execute([1, 2]); + throw new Error('must have thrown error'); + } catch (e) { + assert.equal(e.sql, 'select ?...'); + assert.isTrue(e.message.includes('Execute fails, prepare command as already been closed')); + } + const prepare2 = await conn.prepare('select ?'); + await prepare2.execute('2'); + await prepare2.close(); + + conn.end(); + }); + + it('prepare after prepare close - no cache - parameter logged', async () => { + const conn = await base.createConnection({ prepareCacheLength: 0, logParam: false }); + const prepare = await conn.prepare('select ?'); + await prepare.execute('1'); + await prepare.close(); try { await prepare.execute('1'); throw new Error('must have thrown error'); } catch (e) { assert.isTrue(e.message.includes('Execute fails, prepare command as already been closed')); + assert.equal(e.sql, 'select ?'); } const prepare2 = await conn.prepare('select ?'); diff --git a/test/unit/config/test-options.js b/test/unit/config/test-options.js index e90f2bca..18a6905c 100644 --- a/test/unit/config/test-options.js +++ b/test/unit/config/test-options.js @@ -54,9 +54,9 @@ describe('test options', () => { error: null, network: null, query: null, - logParam: false, warning: console.log }, + logParam: true, metaAsArray: false, metaEnumerable: false, multipleStatements: false, From 79d8d156523569d2953f1bda8763aef7d61f6b29 Mon Sep 17 00:00:00 2001 From: diego Date: Wed, 6 Dec 2023 15:26:02 +0100 Subject: [PATCH 10/16] [misc] precise LGPL-2.1-or-later, not just LGPL-2.1 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5834a34f..7f9c6b89 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ **Non-blocking MariaDB and MySQL client for Node.js.** -MariaDB and MySQL client, 100% JavaScript, with TypeScript definition, with the Promise API. +MariaDB and MySQL client, 100% JavaScript, with TypeScript definition, with the Promise API, distributed under the LGPL license version 2.1 or later (LGPL-2.1-or-later) ## Documentation From f372e494cf2d1de29929c55194d2fe85466e4817 Mon Sep 17 00:00:00 2001 From: diego Date: Thu, 7 Dec 2023 09:47:45 +0100 Subject: [PATCH 11/16] [misc] test stability improvement --- test/integration/test-connection.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/integration/test-connection.js b/test/integration/test-connection.js index c66038e9..047b2dde 100644 --- a/test/integration/test-connection.js +++ b/test/integration/test-connection.js @@ -427,8 +427,8 @@ describe('connection', () => { it('connection timeout connect (wrong url) with callback', (done) => { const initTime = Date.now(); - dns.resolve4('www.google.fr', (err, res) => { - if (err) done(err); + dns.resolve4('www.google.com', (err, res) => { + if (err) done(); else if (res.length > 0) { const host = res[0]; const conn = base.createCallbackConnection({ @@ -531,8 +531,8 @@ describe('connection', () => { }); it('connection timeout connect (wrong url) with callback no function', (done) => { - dns.resolve4('www.google.fr', (err, res) => { - if (err) done(err); + dns.resolve4('www.google.com', (err, res) => { + if (err) done(); else if (res.length > 0) { const host = res[0]; const conn = base.createCallbackConnection({ @@ -566,7 +566,7 @@ describe('connection', () => { it('connection timeout connect (wrong url) with promise', (done) => { const initTime = Date.now(); - dns.resolve4('www.google.fr', (err, res) => { + dns.resolve4('www.google.com', (err, res) => { if (err) done(err); else if (res.length > 0) { const host = res[0]; @@ -601,8 +601,8 @@ describe('connection', () => { it('connection timeout error (wrong url)', function (done) { const initTime = Date.now(); - dns.resolve4('www.google.fr', (err, res) => { - if (err) done(err); + dns.resolve4('www.google.com', (err, res) => { + if (err) done(); else if (res.length > 0) { const host = res[0]; base.createConnection({ host: host, connectTimeout: 1000 }).catch((err) => { From 06a539dbfbea0237189243a4aa9177bd779ee94f Mon Sep 17 00:00:00 2001 From: rusher Date: Wed, 13 Dec 2023 18:07:46 +0100 Subject: [PATCH 12/16] [misc] using common default servers test suite --- .travis.yml | 103 ++++++++-------------------------------------------- 1 file changed, 16 insertions(+), 87 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1bf2a82d..285f6fe1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,113 +1,45 @@ -os: linux -dist: jammy language: node_js -services: docker node_js: 20 -addons: - hosts: - - mariadb.example.com - +version: ~> 1.0 before_install: - - git clone https://github.com/mariadb-corporation/connector-test-machine.git - - -install: - |- case $TRAVIS_OS_NAME in windows) powershell -Command Invoke-WebRequest -Uri https://uploader.codecov.io/latest/windows/codecov.exe -Outfile codecov.exe - ;; - linux) - curl -Os https://uploader.codecov.io/latest/linux/codecov - chmod +x codecov - ls -lrt - ;; - esac - - |- - case $TRAVIS_OS_NAME in - windows) choco install -y --force nodejs-lts # force refresh path export PATH=$(cmd.exe //c "refreshenv > nul & C:\Progra~1\Git\bin\bash -c 'echo \$PATH' ") - connector-test-machine/launch.bat -t "$srv" -v "$v" -d testn ;; linux) - source connector-test-machine/launch.sh -t "$srv" -v "$v" -d testn -l "$local" -c "$CLEAR_TEXT" -s "$DISABLE_SSL" + curl -Os https://uploader.codecov.io/latest/linux/codecov + chmod +x codecov + ls -lrt ;; esac env: - global: - - RUN_LONG_TEST=1 - - CLEAR_TEXT=0 + global: RUN_LONG_TEST=1 CLEAR_TEXT=0 DB=testn -stages: - - Minimal - - name: Enterprise - if: type = push AND fork = false - - Community +import: mariadb-corporation/connector-test-machine:common-build.yml@master jobs: - fast_finish: true - allow_failures: - - env: srv=build - - env: srv=xpand RUN_LONG_TEST=0 include: - - stage: Minimal - env: srv=mariadb v=10.6 local=1 packet=40 - name: "CS 10.6" - - env: srv=mariadb-es v=10.6 - name: "ES 10.6" - if: type = push AND fork = false - - - stage: Enterprise - env: srv=mariadb-es v=10.4 - name: "ES 10.4" - if: type = push AND fork = false - - env: srv=mariadb-es v=10.5 - name: "ES 10.5" - if: type = push AND fork = false - - env: srv=mariadb-es-test v=23.08 - name: "ES 23.08" - if: type = push AND fork = false - - env: srv=maxscale - name: "Maxscale" - - env: srv=xpand RUN_LONG_TEST=0 - name: "Xpand" - - - stage: Community - env: srv=mariadb v=10.6 - os: windows - language: shell - name: "CS 10.6 - Windows" - - env: srv=mariadb v=10.4 local=1 - dist: bionic - node_js: 16 - name: "CS 10.4" - - env: srv=mariadb v=10.5 local=1 DISABLE_SSL=1 - dist: bionic - name: "CS 10.5 - node.js 14" - node_js: 14 - - env: srv=mariadb v=10.11 local=1 + - stage: Language + env: srv=mariadb v=10.11 local=1 name: "CS 10.11 - node.js 16" node_js: 16 - - env: srv=mariadb v=11.0 local=1 CLEAR_TEXT=1 + - stage: Language + env: srv=mariadb v=10.11 local=1 CLEAR_TEXT=1 node_js: 18 - name: "CS 11.0 - node.js 18" - - env: srv=mariadb v=11.1 local=1 - name: "CS 11.1 - node.js 20" + name: "CS 10.11 - node.js 18" + - stage: Language + env: srv=mariadb v=10.11 local=1 + name: "CS 10.11 - node.js 20" node_js: 20 - - env: srv=mysql v=5.7 - name: "MySQL 5.7" - - env: srv=mysql v=8.0 - name: "MySQL 8.0" - - env: srv=mariadb v=10.6 BENCH=1 local=1 + - stage: Benchmarks + env: srv=mariadb v=10.11 BENCH=1 local=1 name: "Benchmarks" - node_js: 18 - dist: focal - - env: srv=build - name: "CS build" script: - npm install @@ -131,6 +63,3 @@ script: after_success: - if [ -z "$BENCH" ] ; then npm run coverage:report; fi - -after_failure: - - if [ "$srv" == "maxscale" ] ; then docker-compose -f ${COMPOSE_FILE} exec -u root maxscale tail -500 /var/log/maxscale/maxscale.log; fi \ No newline at end of file From c4a4eae97a01a8da336fb6aa9f780e3e7eea2191 Mon Sep 17 00:00:00 2001 From: rusher Date: Thu, 14 Dec 2023 13:56:16 +0100 Subject: [PATCH 13/16] [misc] makes one CI test with server ssl disabled --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 285f6fe1..a4b5ecab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,7 +34,7 @@ jobs: node_js: 18 name: "CS 10.11 - node.js 18" - stage: Language - env: srv=mariadb v=10.11 local=1 + env: srv=mariadb v=10.11 local=1 DISABLE_SSL=1 name: "CS 10.11 - node.js 20" node_js: 20 - stage: Benchmarks From f8ed899952d0360d2b1a7f9dedbf59f29edc3a94 Mon Sep 17 00:00:00 2001 From: rusher Date: Mon, 18 Dec 2023 12:18:16 +0100 Subject: [PATCH 14/16] Bump 3.2.3 --- CHANGELOG.md | 9 +++++++++ package.json | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8a7700b..200fe9ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Change Log +## [3.2.3](https://github.com/mariadb-corporation/mariadb-connector-nodejs/tree/3.2.3) (Dec 2023) +[Full Changelog](https://github.com/mariadb-corporation/mariadb-connector-nodejs/compare/3.2.2...3.2.3) + +* CONJS-271 wrong binary decoding of 00:00:00 TIME values +* CONJS-272 Error doesn't always have parameters according to option +* CONJS-273 Bulk insert error when last bunch of parameters is reaching max_allowed_packet +* CONJS-274 permit disabling BULK insert for one batch + + ## [3.2.2](https://github.com/mariadb-corporation/mariadb-connector-nodejs/tree/3.2.2) (Oct 2023) [Full Changelog](https://github.com/mariadb-corporation/mariadb-connector-nodejs/compare/3.2.1...3.2.2) diff --git a/package.json b/package.json index 66c11a04..662c541a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mariadb", - "version": "3.2.2", + "version": "3.2.3", "description": "fast mariadb or mysql connector.", "main": "promise.js", "types": "types/index.d.ts", From ac175ec31623d0dad353b8e7db9b7e91c260de6b Mon Sep 17 00:00:00 2001 From: rusher Date: Mon, 18 Dec 2023 21:55:40 +0100 Subject: [PATCH 15/16] [CONJS-207] Add support for connection redirection --- lib/cmd/handshake/authentication.js | 10 +- lib/cmd/parser.js | 109 ++++++++++++------- lib/cmd/ping.js | 7 +- lib/cmd/reset.js | 7 +- lib/config/connection-options.js | 2 + lib/connection.js | 82 +++++++++++++- lib/io/packet.js | 4 +- lib/misc/connection-information.js | 10 +- test/integration/datatype/test-mapping.js | 4 +- test/integration/test-batch-geometry-type.js | 80 +++++++------- test/integration/test-redirection.js | 96 ++++++++++++++++ test/unit/config/test-options.js | 3 +- 12 files changed, 321 insertions(+), 93 deletions(-) create mode 100644 test/integration/test-redirection.js diff --git a/lib/cmd/handshake/authentication.js b/lib/cmd/handshake/authentication.js index c25eb3ba..e80edb9e 100644 --- a/lib/cmd/handshake/authentication.js +++ b/lib/cmd/handshake/authentication.js @@ -69,7 +69,7 @@ class Authentication extends Command { packet.skipLengthCodedNumber(); //skip affected rows packet.skipLengthCodedNumber(); //skip last insert id info.status = packet.readUInt16(); - + let mustRedirect = false; if (info.status & ServerStatus.SESSION_STATE_CHANGED) { packet.skip(2); //skip warning count packet.skipLengthCodedNumber(); @@ -97,6 +97,11 @@ class Authentication extends Command { opts.emit('collation', info.collation); break; + case 'redirect_url': + mustRedirect = true; + info.redirect(value, this.successEnd); + break; + case 'connection_id': info.threadId = parseInt(value); break; @@ -116,7 +121,8 @@ class Authentication extends Command { } } } - return this.successEnd(); + if (!mustRedirect) this.successEnd(); + return; //********************************************************************************************************* //* ERR_Packet diff --git a/lib/cmd/parser.js b/lib/cmd/parser.js index ff65d24a..29ab7e92 100644 --- a/lib/cmd/parser.js +++ b/lib/cmd/parser.js @@ -139,7 +139,7 @@ class Parser extends Command { } const okPacket = new OkPacket(affectedRows, insertId, packet.readUInt16()); - + let mustRedirect = false; if (info.status & ServerStatus.SESSION_STATE_CHANGED) { packet.skipLengthCodedNumber(); while (packet.remaining()) { @@ -166,6 +166,11 @@ class Parser extends Command { opts.emit('collation', info.collation); break; + case 'redirect_url': + mustRedirect = true; + info.redirect(value, this.okPacketSuccess.bind(this, okPacket, info)); + break; + case 'connection_id': info.threadId = parseInt(value); break; @@ -185,7 +190,20 @@ class Parser extends Command { } } } + if (!mustRedirect) { + if ( + info.redirectRequest && + (info.status & ServerStatus.STATUS_IN_TRANS) === 0 && + (info.status & ServerStatus.MORE_RESULTS_EXISTS) === 0 + ) { + info.redirect(info.redirectRequest, this.okPacketSuccess.bind(this, okPacket, info)); + } else { + this.okPacketSuccess(okPacket, info); + } + } + } + okPacketSuccess(okPacket, info) { if (this._responseIndex === 0) { // fast path for standard single result if (info.status & ServerStatus.MORE_RESULTS_EXISTS) { @@ -387,47 +405,15 @@ class Parser extends Command { info.status = packet.readUInt16(); } - if (this.opts.metaAsArray) { - //return promise object as array : - // example for SELECT 1 => - // [ - // [ {"1": 1} ], //rows - // [ColumnDefinition] //meta - // ] - - if (info.status & ServerStatus.MORE_RESULTS_EXISTS || this.isOutParameter) { - if (!this._meta) this._meta = []; - this._meta[this._responseIndex] = this._columns; - this._responseIndex++; - return (this.onPacketReceive = this.readResponsePacket); - } - if (this._responseIndex === 0) { - this.success([this._rows[0], this._columns]); - } else { - if (!this._meta) this._meta = []; - this._meta[this._responseIndex] = this._columns; - this.success([this._rows, this._meta]); - } + if ( + info.redirectRequest && + (info.status & ServerStatus.STATUS_IN_TRANS) === 0 && + (info.status & ServerStatus.MORE_RESULTS_EXISTS) === 0 + ) { + info.redirect(info.redirectRequest, this.resultSetEndingPacketResult.bind(this, info)); } else { - //return promise object as rows that have meta property : - // example for SELECT 1 => - // [ - // {"1": 1}, - // meta: [ColumnDefinition] - // ] - Object.defineProperty(this._rows[this._responseIndex], 'meta', { - value: this._columns, - writable: true, - enumerable: this.opts.metaEnumerable - }); - - if (info.status & ServerStatus.MORE_RESULTS_EXISTS || this.isOutParameter) { - this._responseIndex++; - return (this.onPacketReceive = this.readResponsePacket); - } - this.success(this._responseIndex === 0 ? this._rows[0] : this._rows); + this.resultSetEndingPacketResult(info); } - return; } } @@ -435,6 +421,49 @@ class Parser extends Command { this.handleNewRows(this.parseRow(packet)); } + resultSetEndingPacketResult(info) { + if (this.opts.metaAsArray) { + //return promise object as array : + // example for SELECT 1 => + // [ + // [ {"1": 1} ], //rows + // [ColumnDefinition] //meta + // ] + + if (info.status & ServerStatus.MORE_RESULTS_EXISTS || this.isOutParameter) { + if (!this._meta) this._meta = []; + this._meta[this._responseIndex] = this._columns; + this._responseIndex++; + return (this.onPacketReceive = this.readResponsePacket); + } + if (this._responseIndex === 0) { + this.success([this._rows[0], this._columns]); + } else { + if (!this._meta) this._meta = []; + this._meta[this._responseIndex] = this._columns; + this.success([this._rows, this._meta]); + } + } else { + //return promise object as rows that have meta property : + // example for SELECT 1 => + // [ + // {"1": 1}, + // meta: [ColumnDefinition] + // ] + Object.defineProperty(this._rows[this._responseIndex], 'meta', { + value: this._columns, + writable: true, + enumerable: this.opts.metaEnumerable + }); + + if (info.status & ServerStatus.MORE_RESULTS_EXISTS || this.isOutParameter) { + this._responseIndex++; + return (this.onPacketReceive = this.readResponsePacket); + } + this.success(this._responseIndex === 0 ? this._rows[0] : this._rows); + } + } + /** * Display current SQL with parameters (truncated if too big) * diff --git a/lib/cmd/ping.js b/lib/cmd/ping.js index 0e3be024..ca92cedc 100644 --- a/lib/cmd/ping.js +++ b/lib/cmd/ping.js @@ -4,6 +4,7 @@ 'use strict'; const Command = require('./command'); +const ServerStatus = require('../const/server-status'); const PING_COMMAND = new Uint8Array([1, 0, 0, 0, 0x0e]); @@ -39,7 +40,11 @@ class Ping extends Command { packet.skipLengthCodedNumber(); //affected rows packet.skipLengthCodedNumber(); //insert ids info.status = packet.readUInt16(); - this.successEnd(null); + if (info.redirectRequest && (info.status & ServerStatus.STATUS_IN_TRANS) === 0) { + info.redirect(info.redirectRequest, this.successEnd.bind(this, null)); + } else { + this.successEnd(null); + } } } diff --git a/lib/cmd/reset.js b/lib/cmd/reset.js index e68a769f..f381ff86 100644 --- a/lib/cmd/reset.js +++ b/lib/cmd/reset.js @@ -4,6 +4,7 @@ 'use strict'; const Command = require('./command'); +const ServerStatus = require('../const/server-status'); const RESET_COMMAND = new Uint8Array([1, 0, 0, 0, 0x1f]); /** * send a COM_RESET_CONNECTION: permits to reset a connection without re-authentication. @@ -38,7 +39,11 @@ class Reset extends Command { packet.skipLengthCodedNumber(); //insert ids info.status = packet.readUInt16(); - this.successEnd(); + if (info.redirectRequest && (info.status & ServerStatus.STATUS_IN_TRANS) === 0) { + info.redirect(info.redirectRequest, this.successEnd.bind(this)); + } else { + this.successEnd(); + } } } diff --git a/lib/config/connection-options.js b/lib/config/connection-options.js index c45869f8..01a5f0b1 100644 --- a/lib/config/connection-options.js +++ b/lib/config/connection-options.js @@ -86,6 +86,7 @@ class ConnectionOptions { } // connection options + this.permitRedirect = opts.permitRedirect === undefined ? true : opts.permitRedirect; this.initSql = opts.initSql; this.connectTimeout = opts.connectTimeout === undefined ? 1000 : opts.connectTimeout; this.connectAttributes = opts.connectAttributes || false; @@ -185,6 +186,7 @@ class ConnectionOptions { if (opts.charsetNumber && !isNaN(Number.parseInt(opts.charsetNumber))) { opts.charsetNumber = Number.parseInt(opts.charsetNumber); } + if (opts.permitRedirect) opts.permitRedirect = opts.permitRedirect === 'true'; if (opts.logParam) opts.logParam = opts.logParam === 'true'; if (opts.compress) opts.compress = opts.compress === 'true'; if (opts.connectAttributes) opts.connectAttributes = JSON.parse(opts.connectAttributes); diff --git a/lib/connection.js b/lib/connection.js index 7cdef44c..90699d55 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -16,6 +16,7 @@ const tls = require('tls'); const Errors = require('./misc/errors'); const Utils = require('./misc/utils'); const Capabilities = require('./const/capabilities'); +const ConnectionOptions = require('./config/connection-options'); /*commands*/ const Authentication = require('./cmd/handshake/authentication'); @@ -35,6 +36,7 @@ const LruPrepareCache = require('./lru-prepare-cache'); const fsPromises = require('fs').promises; const Parse = require('./misc/parse'); const Collations = require('./const/collations'); +const ConnOptions = require('./config/connection-options'); const convertFixedTime = function (tz, conn) { if (tz === 'UTC' || tz === 'Etc/UTC' || tz === 'Z' || tz === 'Etc/GMT') { @@ -63,6 +65,7 @@ const convertFixedTime = function (tz, conn) { } return tz; }; +const redirectUrlFormat = /(mariadb|mysql):\/\/(([^/@:]+)?(:([^/]+))?@)?(([^/:]+)(:([0-9]+))?)(\/([^?]+)(\?(.*))?)?$/; /** * New Connection instance. @@ -93,7 +96,7 @@ class Connection extends EventEmitter { super(); this.opts = Object.assign(new EventEmitter(), options); - this.info = new ConnectionInformation(this.opts); + this.info = new ConnectionInformation(this.opts, this.redirect.bind(this)); this.prepareCache = this.opts.prepareCacheLength > 0 ? new LruPrepareCache(this.info, this.opts.prepareCacheLength) : null; this.addCommand = this.addCommandQueue; @@ -582,8 +585,8 @@ class Connection extends EventEmitter { _options === undefined || _options === null ? this.opts.bulk : _options.bulk !== undefined && _options.bulk !== null - ? _options.bulk - : this.opts.bulk; + ? _options.bulk + : this.opts.bulk; if ( this.info.serverVersion && this.info.serverVersion.mariaDb && @@ -1577,6 +1580,79 @@ class Connection extends EventEmitter { this.socket = undefined; } + /** + * Redirecting connection to server indicated value. + * @param value server host string + * @param resolve promise result when done + */ + redirect(value, resolve) { + if (this.opts.permitRedirect && value) { + // redirect only if : + // * when pipelining, having received all waiting responses. + // * not in a transaction + if (this.receiveQueue.length <= 1 && (this.info.status & ServerStatus.STATUS_IN_TRANS) === 0) { + this.info.redirectRequest = null; + const matchResults = value.match(redirectUrlFormat); + if (!matchResults) { + if (this.opts.logger.error) + this.opts.logger.error( + new Error( + `error parsing redirection string '${value}'. format must be 'mariadb/mysql://[[:]@][:]/[[?=[&=]]]'` + ) + ); + return resolve(); + } + + const options = { + host: matchResults[7] ? decodeURIComponent(matchResults[7]) : matchResults[6], + port: matchResults[9] ? parseInt(matchResults[9]) : 3306 + }; + + // actually only options accepted are user and password + // there might be additional possible options in the future + if (matchResults[3]) options.user = matchResults[3]; + if (matchResults[5]) options.password = matchResults[5]; + + const redirectOpts = ConnectionOptions.parseOptionDataType(options); + + const finalRedirectOptions = new ConnOptions(Object.assign({}, this.opts, redirectOpts)); + const conn = new Connection(finalRedirectOptions); + conn + .connect() + .then( + async function () { + const cmdParam = new CommandParameter(); + await new Promise(this.end.bind(this, cmdParam)); + this.status = Status.CONNECTED; + this.info = conn.info; + this.opts = conn.opts; + this.socket = conn.socket; + if (this.prepareCache) this.prepareCache.reset(); + this.streamOut = conn.streamOut; + this.streamIn = conn.streamIn; + resolve(); + }.bind(this) + ) + .catch( + function (e) { + if (this.opts.logger.error) { + const err = new Error(`fail to redirect to '${value}'`); + err.cause = e; + this.opts.logger.error(err); + } + resolve(); + }.bind(this) + ); + } else { + this.info.redirectRequest = value; + resolve(); + } + } else { + this.info.redirectRequest = null; + resolve(); + } + } + get threadId() { return this.info ? this.info.threadId : null; } diff --git a/lib/io/packet.js b/lib/io/packet.js index c8266127..464ece93 100644 --- a/lib/io/packet.js +++ b/lib/io/packet.js @@ -448,8 +448,8 @@ class Packet { ? '.' + appendZero(microSec, 6).substring(0, scale) : '.' + appendZero(microSec, 6) : scale > 0 - ? '.' + appendZero(microSec, 6).substring(0, scale) - : '') + ? '.' + appendZero(microSec, 6).substring(0, scale) + : '') ); } diff --git a/lib/misc/connection-information.js b/lib/misc/connection-information.js index 8a8ffe60..b84325d2 100644 --- a/lib/misc/connection-information.js +++ b/lib/misc/connection-information.js @@ -4,12 +4,16 @@ 'use strict'; class ConnectionInformation { - constructor(opts) { + #redirectFct; + constructor(opts, redirectFct) { this.threadId = -1; this.status = null; this.serverVersion = null; this.serverCapabilities = null; this.database = opts.database; + this.port = opts.port; + this.#redirectFct = redirectFct; + this.redirectRequest = null; } hasMinVersion(major, minor, patch) { @@ -28,6 +32,10 @@ class ConnectionInformation { ); } + redirect(value, resolve) { + return this.#redirectFct(value, resolve); + } + isMariaDB() { if (!this.serverVersion) throw new Error('cannot know if server is MariaDB until connection is established'); return this.serverVersion.mariaDb; diff --git a/test/integration/datatype/test-mapping.js b/test/integration/datatype/test-mapping.js index 48bf26e3..38ddbbdf 100644 --- a/test/integration/datatype/test-mapping.js +++ b/test/integration/datatype/test-mapping.js @@ -312,12 +312,12 @@ describe('mapping', () => { let rows = await shareConn.query('SELECT * FROM dataTypeWithNull'); assert.ok(Buffer.isBuffer(rows[0].test)); assert.ok(Buffer.isBuffer(rows[0].test2)); - assert.ok(typeof typeof rows[0].test3 === 'string' || (typeof rows[0].test3) instanceof String); + assert.ok(typeof typeof rows[0].test3 === 'string' || typeof rows[0].test3 instanceof String); rows = await shareConn.execute('SELECT * FROM dataTypeWithNull'); assert.ok(Buffer.isBuffer(rows[0].test)); assert.ok(Buffer.isBuffer(rows[0].test2)); - assert.ok(typeof typeof rows[0].test3 === 'string' || (typeof rows[0].test3) instanceof String); + assert.ok(typeof typeof rows[0].test3 === 'string' || typeof rows[0].test3 instanceof String); await shareConn.commit(); }); }); diff --git a/test/integration/test-batch-geometry-type.js b/test/integration/test-batch-geometry-type.js index dffcbeca..115c906a 100644 --- a/test/integration/test-batch-geometry-type.js +++ b/test/integration/test-batch-geometry-type.js @@ -167,8 +167,8 @@ describe('batch geometry type', () => { type: 'LineString' } : serverPermitExtendedInfos - ? { type: 'LineString' } - : null + ? { type: 'LineString' } + : null }, { g: serverPermitExtendedInfos ? { type: 'LineString' } : null @@ -322,8 +322,8 @@ describe('batch geometry type', () => { coordinates: [] } : serverPermitExtendedInfos - ? { type: 'Polygon' } - : null + ? { type: 'Polygon' } + : null }, { g: serverPermitExtendedInfos ? { type: 'Polygon' } : null @@ -431,10 +431,10 @@ describe('batch geometry type', () => { coordinates: [] } : shareConn.info.hasMinVersion(10, 5, 2) && - process.env.srv !== 'maxscale' && - process.env.srv !== 'skysql-ha' - ? { type: 'MultiPoint' } - : null + process.env.srv !== 'maxscale' && + process.env.srv !== 'skysql-ha' + ? { type: 'MultiPoint' } + : null }, { g: supportBulk @@ -443,10 +443,10 @@ describe('batch geometry type', () => { coordinates: [] } : shareConn.info.hasMinVersion(10, 5, 2) && - process.env.srv !== 'maxscale' && - process.env.srv !== 'skysql-ha' - ? { type: 'MultiPoint' } - : null + process.env.srv !== 'maxscale' && + process.env.srv !== 'skysql-ha' + ? { type: 'MultiPoint' } + : null } ]); } else { @@ -560,10 +560,10 @@ describe('batch geometry type', () => { coordinates: [[]] } : shareConn.info.hasMinVersion(10, 5, 2) && - process.env.srv !== 'maxscale' && - process.env.srv !== 'skysql-ha' - ? { type: 'MultiLineString' } - : null + process.env.srv !== 'maxscale' && + process.env.srv !== 'skysql-ha' + ? { type: 'MultiLineString' } + : null }, { g: supportBulk @@ -572,10 +572,10 @@ describe('batch geometry type', () => { coordinates: [] } : shareConn.info.hasMinVersion(10, 5, 2) && - process.env.srv !== 'maxscale' && - process.env.srv !== 'skysql-ha' - ? { type: 'MultiLineString' } - : null + process.env.srv !== 'maxscale' && + process.env.srv !== 'skysql-ha' + ? { type: 'MultiLineString' } + : null }, { g: supportBulk @@ -584,10 +584,10 @@ describe('batch geometry type', () => { coordinates: [] } : shareConn.info.hasMinVersion(10, 5, 2) && - process.env.srv !== 'maxscale' && - process.env.srv !== 'skysql-ha' - ? { type: 'MultiLineString' } - : null + process.env.srv !== 'maxscale' && + process.env.srv !== 'skysql-ha' + ? { type: 'MultiLineString' } + : null } ]); } else { @@ -788,10 +788,10 @@ describe('batch geometry type', () => { coordinates: [[[]]] } : shareConn.info.hasMinVersion(10, 5, 2) && - process.env.srv !== 'maxscale' && - process.env.srv !== 'skysql-ha' - ? { type: 'MultiPolygon' } - : null + process.env.srv !== 'maxscale' && + process.env.srv !== 'skysql-ha' + ? { type: 'MultiPolygon' } + : null }, { g: supportBulk @@ -800,10 +800,10 @@ describe('batch geometry type', () => { coordinates: [[]] } : shareConn.info.hasMinVersion(10, 5, 2) && - process.env.srv !== 'maxscale' && - process.env.srv !== 'skysql-ha' - ? { type: 'MultiPolygon' } - : null + process.env.srv !== 'maxscale' && + process.env.srv !== 'skysql-ha' + ? { type: 'MultiPolygon' } + : null }, { g: supportBulk @@ -812,10 +812,10 @@ describe('batch geometry type', () => { coordinates: [] } : shareConn.info.hasMinVersion(10, 5, 2) && - process.env.srv !== 'maxscale' && - process.env.srv !== 'skysql-ha' - ? { type: 'MultiPolygon' } - : null + process.env.srv !== 'maxscale' && + process.env.srv !== 'skysql-ha' + ? { type: 'MultiPolygon' } + : null }, { g: supportBulk @@ -824,10 +824,10 @@ describe('batch geometry type', () => { coordinates: [] } : shareConn.info.hasMinVersion(10, 5, 2) && - process.env.srv !== 'maxscale' && - process.env.srv !== 'skysql-ha' - ? { type: 'MultiPolygon' } - : null + process.env.srv !== 'maxscale' && + process.env.srv !== 'skysql-ha' + ? { type: 'MultiPolygon' } + : null } ]); } else { diff --git a/test/integration/test-redirection.js b/test/integration/test-redirection.js new file mode 100644 index 00000000..eedf620f --- /dev/null +++ b/test/integration/test-redirection.js @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (c) 2015-2023 MariaDB Corporation Ab + +'use strict'; + +require('../base.js'); +const base = require('../base.js'); +const Proxy = require('../tools/proxy'); +const Conf = require('../conf'); +const { assert } = require('chai'); +describe('redirection', () => { + it('basic redirection', async function () { + if (process.env.srv === 'skysql' || process.env.srv === 'skysql-ha') this.skip(); + const proxy = new Proxy({ + port: Conf.baseConfig.port, + host: Conf.baseConfig.host, + resetAfterUse: false + }); + await proxy.start(); + let conn = await base.createConnection({ port: proxy.port() }); + try { + assert.equal(proxy.port(), conn.info.port); + let permitRedirection = true; + try { + await conn.query('set @@session.redirect_url="mariadb://localhost:' + Conf.baseConfig.port + '"'); + } catch (e) { + // if server doesn't support redirection + permitRedirection = false; + } + if (permitRedirection) { + assert.equal(Conf.baseConfig.port, conn.info.port); + } + } finally { + conn.end(); + proxy.close(); + } + }); + + it('redirection during pipelining', async function () { + if (process.env.srv === 'skysql' || process.env.srv === 'skysql-ha') this.skip(); + const proxy = new Proxy({ + port: Conf.baseConfig.port, + host: Conf.baseConfig.host, + resetAfterUse: false + }); + await proxy.start(); + let conn = await base.createConnection({ port: proxy.port() }); + try { + assert.equal(proxy.port(), conn.info.port); + let permitRedirection = true; + conn.query('SELECT 1'); + conn.query('set @@session.redirect_url="mariadb://localhost:' + Conf.baseConfig.port + '"').catch((e) => { + permitRedirection = false; + }); + conn.query('SELECT 2'); + assert.equal(proxy.port(), conn.info.port); + await conn.query('SELECT 3'); + if (permitRedirection) { + assert.equal(Conf.baseConfig.port, conn.info.port); + } + } finally { + conn.end(); + proxy.close(); + } + }); + + it('redirection during transaction', async function () { + if (process.env.srv === 'skysql' || process.env.srv === 'skysql-ha') this.skip(); + const proxy = new Proxy({ + port: Conf.baseConfig.port, + host: Conf.baseConfig.host, + resetAfterUse: false + }); + await proxy.start(); + let conn = await base.createConnection({ port: proxy.port() }); + try { + assert.equal(proxy.port(), conn.info.port); + let permitRedirection = true; + try { + await conn.beginTransaction(); + await conn.query('set @@session.redirect_url="mariadb://localhost:' + Conf.baseConfig.port + '"'); + } catch (e) { + // if server doesn't support redirection + permitRedirection = false; + } + assert.equal(proxy.port(), conn.info.port); + if (permitRedirection) { + await conn.commit(); + assert.equal(Conf.baseConfig.port, conn.info.port); + } + } finally { + conn.end(); + proxy.close(); + } + }); +}); diff --git a/test/unit/config/test-options.js b/test/unit/config/test-options.js index 18a6905c..caa50a22 100644 --- a/test/unit/config/test-options.js +++ b/test/unit/config/test-options.js @@ -75,7 +75,8 @@ describe('test options', () => { keepEof: false, permitLocalInfile: false, bigNumberStrings: false, - supportBigNumbers: false + supportBigNumbers: false, + permitRedirect: true }; assert.deepEqual(expected, defaultOpts); assert.deepEqual(expected, defaultOptsCall); From 8e20d41bc47c7931c52e7861f50abef2a5f4db15 Mon Sep 17 00:00:00 2001 From: rusher Date: Mon, 18 Dec 2023 21:58:16 +0100 Subject: [PATCH 16/16] [misc] add missing change log --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 200fe9ec..5e5da692 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,10 +3,12 @@ ## [3.2.3](https://github.com/mariadb-corporation/mariadb-connector-nodejs/tree/3.2.3) (Dec 2023) [Full Changelog](https://github.com/mariadb-corporation/mariadb-connector-nodejs/compare/3.2.2...3.2.3) +* CONJS-207 Add support for connection redirection * CONJS-271 wrong binary decoding of 00:00:00 TIME values * CONJS-272 Error doesn't always have parameters according to option * CONJS-273 Bulk insert error when last bunch of parameters is reaching max_allowed_packet * CONJS-274 permit disabling BULK insert for one batch +* CONJS-207 Add support for connection redirection ## [3.2.2](https://github.com/mariadb-corporation/mariadb-connector-nodejs/tree/3.2.2) (Oct 2023)