diff --git a/lib/connection_config.js b/lib/connection_config.js index 236cf6747f..a722bb7c74 100644 --- a/lib/connection_config.js +++ b/lib/connection_config.js @@ -64,7 +64,8 @@ const validOptions = { idleTimeout: 1, Promise: 1, queueLimit: 1, - waitForConnections: 1 + waitForConnections: 1, + decimalStringTrimTrailingZero: 1, }; class ConnectionConfig { @@ -117,6 +118,7 @@ class ConnectionConfig { this.stringifyObjects = options.stringifyObjects || false; this.enableKeepAlive = options.enableKeepAlive !== false; this.keepAliveInitialDelay = options.keepAliveInitialDelay || 0; + this.decimalStringTrimTrailingZero = options.decimalStringTrimTrailingZero || false; if ( options.timezone && !/^(?:local|Z|[ +-]\d\d:\d\d)$/.test(options.timezone) diff --git a/lib/packets/packet.js b/lib/packets/packet.js index ccf3a8458c..3ac73544bf 100644 --- a/lib/packets/packet.js +++ b/lib/packets/packet.js @@ -377,7 +377,7 @@ class Packet { ); } - readLengthCodedString(encoding) { + readLengthCodedString(encoding, trimTrailingZero = false) { const len = this.readLengthCodedNumber(); // TODO: check manually first byte here to avoid polymorphic return type? if (len === null) { @@ -390,7 +390,10 @@ class Packet { this.buffer, encoding, this.offset - len, - this.offset + this.offset, + { + trimTrailingZero + } ); } @@ -425,7 +428,7 @@ class Packet { return StringParser.decode( this.buffer, encoding, - this.offset - len, + this.offset - len, this.offset ); } diff --git a/lib/parsers/string.js b/lib/parsers/string.js index 5523fb2c6a..0c513d6474 100644 --- a/lib/parsers/string.js +++ b/lib/parsers/string.js @@ -4,6 +4,18 @@ const Iconv = require('iconv-lite'); exports.decode = function(buffer, encoding, start, end, options) { if (Buffer.isEncoding(encoding)) { + if(options?.trimTrailingZero) { + for(let i = end - 1; i >= start; i--) { + if(buffer[i] === 48) { + end--; + } else if(buffer[i] === 46) { + end--; + break; + } else { + break; + } + } + } return buffer.toString(encoding, start, end); } diff --git a/lib/parsers/text_parser.js b/lib/parsers/text_parser.js index 49a128c049..ffa5970bc4 100644 --- a/lib/parsers/text_parser.js +++ b/lib/parsers/text_parser.js @@ -17,6 +17,8 @@ function readCodeFor(type, charset, encodingExpr, config, options) { const bigNumberStrings = options.bigNumberStrings || config.bigNumberStrings; const timezone = options.timezone || config.timezone; const dateStrings = options.dateStrings || config.dateStrings; + const decimalStringTrimTrailingZero = + options.decimalStringTrimTrailingZero || config.decimalStringTrimTrailingZero; switch (type) { case Types.TINY: @@ -40,7 +42,10 @@ function readCodeFor(type, charset, encodingExpr, config, options) { if (config.decimalNumbers) { return 'packet.parseLengthCodedFloat()'; } - return 'packet.readLengthCodedString("ascii")'; + if(decimalStringTrimTrailingZero) { + return 'packet.readLengthCodedString("ascii", true)'; + } + return 'packet.readLengthCodedString("ascii", false)'; case Types.DATE: if (helpers.typeMatch(type, dateStrings, Types)) { return 'packet.readLengthCodedString("ascii")'; diff --git a/test/common.js b/test/common.js index 9e9d057e9e..0e03d8b729 100644 --- a/test/common.js +++ b/test/common.js @@ -85,6 +85,7 @@ exports.createConnection = function(args) { namedPlaceholders: args && args.namedPlaceholders, connectTimeout: args && args.connectTimeout, ssl: (args && args.ssl) ?? config.ssl, + decimalStringTrimTrailingZero: (args && args.decimalStringTrimTrailingZero) ?? config.decimalStringTrimTrailingZero, }; const conn = driver.createConnection(params); diff --git a/test/unit/connection/test-connection_config.js b/test/unit/connection/test-connection_config.js index 0e1c0b1ca6..e4e370d15c 100644 --- a/test/unit/connection/test-connection_config.js +++ b/test/unit/connection/test-connection_config.js @@ -49,3 +49,11 @@ assert.strictEqual( ).password, 'pass!%40$%%5E&*()%5Cword%3A' ); + +assert.doesNotThrow( + () => + new ConnectionConfig({ + decimalStringTrimTrailingZero: true + }), + 'Error, the constructor accepts an object but throws an exception' +); diff --git a/test/unit/parsers/test-decimal-parser.js b/test/unit/parsers/test-decimal-parser.js new file mode 100644 index 0000000000..d59ff5a353 --- /dev/null +++ b/test/unit/parsers/test-decimal-parser.js @@ -0,0 +1,44 @@ +'use strict'; + +const assert = require('assert'); +const common = require('../../common'); + +const connection = common.createConnection({decimalStringTrimTrailingZero: true}); +connection.query('CREATE TEMPORARY TABLE t (a decimal(38,16), b varchar(39))'); +connection.query('INSERT INTO t values(\'1.00\', \'1.00\')'); +connection.query('INSERT INTO t values(\'1.01\', \'1.01\')'); +connection.query('INSERT INTO t values(\'1.10\', \'1.10\')'); +connection.query('INSERT INTO t values(\'1.010\', \'1.010\')'); +connection.query('INSERT INTO t values(\'0.00\', \'0.00\')'); +connection.query('INSERT INTO t values(\'100000.0000100000000000\', \'100000.0000100000000000\')'); +connection.query('INSERT INTO t values(\'100000.0000000000000000\', \'100000.0000000000000000\')'); + + +// JSON without encoding options - should result in unexpected behaviors +connection.query({ + sql: 'SELECT * FROM t', +}, (err, rows) => { + assert.ifError(err); + assert.equal(rows[0].a, "1"); + assert.equal(rows[0].b, "1.00"); + + assert.equal(rows[1].a, "1.01"); + assert.equal(rows[1].b, "1.01"); + + assert.equal(rows[2].a, "1.1"); + assert.equal(rows[2].b, "1.10"); + + assert.equal(rows[3].a, "1.01"); + assert.equal(rows[3].b, "1.010"); + + assert.equal(rows[4].a, "0"); + assert.equal(rows[4].b, "0.00"); + + assert.equal(rows[5].a, "100000.00001"); + assert.equal(rows[5].b, "100000.0000100000000000"); + + assert.equal(rows[6].a, "100000"); + assert.equal(rows[6].b, "100000.0000000000000000"); +}); + +connection.end(); diff --git a/typings/mysql/lib/Connection.d.ts b/typings/mysql/lib/Connection.d.ts index d0e2cc6929..c389f81698 100644 --- a/typings/mysql/lib/Connection.d.ts +++ b/typings/mysql/lib/Connection.d.ts @@ -291,6 +291,8 @@ export interface ConnectionOptions { authPlugins?: { [key: string]: AuthPlugin; }; + + decimalStringTrimTrailingZero?: boolean; } declare class Connection extends QueryableBase(ExecutableBase(EventEmitter)) {