Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion lib/commands/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
const Command = require('./command.js');
const Packets = require('../packets/index.js');
const getTextParser = require('../parsers/text_parser.js');
const staticParser = require('../parsers/static_text_parser.js');
const ServerStatus = require('../constants/server_status.js');

const EmptyPacket = new Packets.Packet(0, Buffer.allocUnsafe(4), 0, 4);
Expand Down Expand Up @@ -212,7 +213,11 @@
if (this._receivedFieldsCount === this._fieldCount) {
const fields = this._fields[this._resultIndex];
this.emit('fields', fields);
this._rowParser = new (getTextParser(fields, this.options, connection.config))(fields);
if (this.options.useStaticParser) {
this._rowParser = staticParser(fields, this.options, connection.config);

Check warning on line 217 in lib/commands/query.js

View check run for this annotation

Codecov / codecov/patch

lib/commands/query.js#L217

Added line #L217 was not covered by tests
} else {
this._rowParser = new (getTextParser(fields, this.options, connection.config))(fields);
}
return Query.prototype.fieldsEOF;
}
return Query.prototype.readField;
Expand Down
2 changes: 2 additions & 0 deletions lib/connection_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ const validOptions = {
typeCast: 1,
uri: 1,
user: 1,
useStaticParser: 1,
// These options are used for Pool
connectionLimit: 1,
maxIdle: 1,
Expand Down Expand Up @@ -147,6 +148,7 @@ class ConnectionConfig {
this.nestTables =
options.nestTables === undefined ? undefined : options.nestTables;
this.typeCast = options.typeCast === undefined ? true : options.typeCast;
this.useStaticParser = options.useStaticParser || false;
if (this.timezone[0] === ' ') {
// "+" is a url encoded char for space so it
// gets translated to space when giving a
Expand Down
142 changes: 142 additions & 0 deletions lib/parsers/static_text_parser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
'use strict';

const Types = require('../constants/types.js');
const Charsets = require('../constants/charsets.js');
const helpers = require('../helpers');

const typeNames = [];
for (const t in Types) {
typeNames[Types[t]] = t;
}

function readField({ packet, type, charset, encoding, config, options }) {
const supportBigNumbers =
options.supportBigNumbers || config.supportBigNumbers;
const bigNumberStrings = options.bigNumberStrings || config.bigNumberStrings;
const timezone = options.timezone || config.timezone;
const dateStrings = options.dateStrings || config.dateStrings;

switch (type) {
case Types.TINY:
case Types.SHORT:
case Types.LONG:
case Types.INT24:
case Types.YEAR:
return packet.parseLengthCodedIntNoBigCheck();
case Types.LONGLONG:
if (supportBigNumbers && bigNumberStrings) {
return packet.parseLengthCodedIntString();
}
return packet.parseLengthCodedInt(supportBigNumbers);
case Types.FLOAT:
case Types.DOUBLE:
return packet.parseLengthCodedFloat();
case Types.NULL:
case Types.DECIMAL:
case Types.NEWDECIMAL:
if (config.decimalNumbers) {
return packet.parseLengthCodedFloat();
}
return packet.readLengthCodedString("ascii");
case Types.DATE:
if (helpers.typeMatch(type, dateStrings, Types)) {
return packet.readLengthCodedString("ascii");
}
return packet.parseDate(timezone);
case Types.DATETIME:
case Types.TIMESTAMP:
if (helpers.typeMatch(type, dateStrings, Types)) {
return packet.readLengthCodedString('ascii');
}
return packet.parseDateTime(timezone);
case Types.TIME:
return packet.readLengthCodedString('ascii');
case Types.GEOMETRY:
return packet.parseGeometryValue();
case Types.JSON:
// Since for JSON columns mysql always returns charset 63 (BINARY),
// we have to handle it according to JSON specs and use "utf8",
// see https://github.com/sidorares/node-mysql2/issues/409
return JSON.parse(packet.readLengthCodedString('utf8'));
default:
if (charset === Charsets.BINARY) {
return packet.readLengthCodedBuffer();
}
return packet.readLengthCodedString(encoding);
}
}

Check warning on line 67 in lib/parsers/static_text_parser.js

View check run for this annotation

Codecov / codecov/patch

lib/parsers/static_text_parser.js#L12-L67

Added lines #L12 - L67 were not covered by tests

function compile(fields, options, config) {

Check failure on line 69 in lib/parsers/static_text_parser.js

View workflow job for this annotation

GitHub Actions / lint-js

'compile' is defined but never used
if (
typeof config.typeCast === 'function' &&
typeof options.typeCast !== 'function'
) {
options.typeCast = config.typeCast;
}
}

Check warning on line 76 in lib/parsers/static_text_parser.js

View check run for this annotation

Codecov / codecov/patch

lib/parsers/static_text_parser.js#L69-L76

Added lines #L69 - L76 were not covered by tests

function createTypecastField(field, packet) {
return {
type: typeNames[field.columnType],
length: field.columnLength,
db: field.schema,
table: field.table,
name: field.name,
string: function(encoding = field.encoding) {
if (field.columnType === Types.JSON && encoding === field.encoding) {
// Since for JSON columns mysql always returns charset 63 (BINARY),
// we have to handle it according to JSON specs and use "utf8",
// see https://github.com/sidorares/node-mysql2/issues/1661
console.warn(`typeCast: JSON column "${field.name}" is interpreted as BINARY by default, recommended to manually set utf8 encoding: \`field.string("utf8")\``);
}
return packet.readLengthCodedString(encoding);
},
buffer: function() {
return packet.readLengthCodedBuffer();
},
geometry: function() {
return packet.parseGeometryValue();
}
};
}

Check warning on line 101 in lib/parsers/static_text_parser.js

View check run for this annotation

Codecov / codecov/patch

lib/parsers/static_text_parser.js#L78-L101

Added lines #L78 - L101 were not covered by tests

function getTextParser(fields, options, config) {
return {
next(packet, fields, options) {
const result = options.rowsAsArray ? [] : {};
for (let i = 0; i < fields.length; i++) {
const field = fields[i];
const typeCast = options.typeCast ? options.typeCast : config.typeCast;
const next = () => {

Check failure on line 110 in lib/parsers/static_text_parser.js

View workflow job for this annotation

GitHub Actions / lint-js

Unexpected block statement surrounding arrow body; move the returned value immediately after the `=>`
return readField({
packet,
type: field.columnType,
encoding: field.encoding,
charset: field.characterSet,
config,
options
})
}
let value;
if (typeof typeCast === 'function') {
value = typeCast(createTypecastField(field, packet), next);
} else {
value = next();
}
if (options.rowsAsArray) {
result.push(value);
} else if (options.nestTables) {
if (!result[field.table]) {
result[field.table] = {};
}
result[field.table][field.name] = value;
} else {
result[field.name] = value;
}
}
return result;
}
}
}

Check warning on line 140 in lib/parsers/static_text_parser.js

View check run for this annotation

Codecov / codecov/patch

lib/parsers/static_text_parser.js#L103-L140

Added lines #L103 - L140 were not covered by tests

module.exports = getTextParser;
13 changes: 10 additions & 3 deletions test/integration/regressions/test-#433.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,12 @@ connection.query(
);

/* eslint quotes: 0 */
const expectedError =
const expectedErrorMysql =
"You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '`МояТаблица' at line 1";

const expectedErrorMariaDB =
"You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '`МояТаблица' at line 1";

process.on('exit', () => {
testRows.map((tRow, index) => {
const cols = testFields;
Expand All @@ -76,6 +79,10 @@ process.on('exit', () => {
assert.equal(aRow[cols[2]], tRow[2]);
assert.equal(aRow[cols[3]], tRow[3]);
});

assert.equal(actualError, expectedError);

if (connection._handshakePacket.serverVersion.match(/MariaDB/)) {
assert.equal(actualError, expectedErrorMariaDB);
} else {
assert.equal(actualError, expectedErrorMysql);
}
});
2 changes: 2 additions & 0 deletions typings/mysql/lib/Connection.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,8 @@ export interface ConnectionOptions {

waitForConnections?: boolean;

useStaticParser?: boolean;

authPlugins?: {
[key: string]: AuthPlugin;
};
Expand Down
Loading