Skip to content

Commit ebe536c

Browse files
authored
Merge pull request #1 from thomasgauvin/deoptimise-large-rows
Deoptimise large rows
2 parents f2cc820 + a771cae commit ebe536c

File tree

5 files changed

+162
-4
lines changed

5 files changed

+162
-4
lines changed

lib/commands/query.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const Readable = require('stream').Readable;
88
const Command = require('./command.js');
99
const Packets = require('../packets/index.js');
1010
const getTextParser = require('../parsers/text_parser.js');
11+
const staticParser = require('../parsers/static_text_parser.js');
1112
const ServerStatus = require('../constants/server_status.js');
1213

1314
const EmptyPacket = new Packets.Packet(0, Buffer.allocUnsafe(4), 0, 4);
@@ -212,7 +213,11 @@ class Query extends Command {
212213
if (this._receivedFieldsCount === this._fieldCount) {
213214
const fields = this._fields[this._resultIndex];
214215
this.emit('fields', fields);
215-
this._rowParser = new (getTextParser(fields, this.options, connection.config))(fields);
216+
if (this.options.useStaticParser) {
217+
this._rowParser = staticParser(fields, this.options, connection.config);
218+
} else {
219+
this._rowParser = new (getTextParser(fields, this.options, connection.config))(fields);
220+
}
216221
return Query.prototype.fieldsEOF;
217222
}
218223
return Query.prototype.readField;

lib/connection_config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ const validOptions = {
5959
typeCast: 1,
6060
uri: 1,
6161
user: 1,
62+
useStaticParser: 1,
6263
// These options are used for Pool
6364
connectionLimit: 1,
6465
maxIdle: 1,
@@ -147,6 +148,7 @@ class ConnectionConfig {
147148
this.nestTables =
148149
options.nestTables === undefined ? undefined : options.nestTables;
149150
this.typeCast = options.typeCast === undefined ? true : options.typeCast;
151+
this.useStaticParser = options.useStaticParser || false;
150152
if (this.timezone[0] === ' ') {
151153
// "+" is a url encoded char for space so it
152154
// gets translated to space when giving a

lib/parsers/static_text_parser.js

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
'use strict';
2+
3+
const Types = require('../constants/types.js');
4+
const Charsets = require('../constants/charsets.js');
5+
const helpers = require('../helpers');
6+
7+
const typeNames = [];
8+
for (const t in Types) {
9+
typeNames[Types[t]] = t;
10+
}
11+
12+
function readField({ packet, type, charset, encoding, config, options }) {
13+
const supportBigNumbers =
14+
options.supportBigNumbers || config.supportBigNumbers;
15+
const bigNumberStrings = options.bigNumberStrings || config.bigNumberStrings;
16+
const timezone = options.timezone || config.timezone;
17+
const dateStrings = options.dateStrings || config.dateStrings;
18+
19+
switch (type) {
20+
case Types.TINY:
21+
case Types.SHORT:
22+
case Types.LONG:
23+
case Types.INT24:
24+
case Types.YEAR:
25+
return packet.parseLengthCodedIntNoBigCheck();
26+
case Types.LONGLONG:
27+
if (supportBigNumbers && bigNumberStrings) {
28+
return packet.parseLengthCodedIntString();
29+
}
30+
return packet.parseLengthCodedInt(supportBigNumbers);
31+
case Types.FLOAT:
32+
case Types.DOUBLE:
33+
return packet.parseLengthCodedFloat();
34+
case Types.NULL:
35+
case Types.DECIMAL:
36+
case Types.NEWDECIMAL:
37+
if (config.decimalNumbers) {
38+
return packet.parseLengthCodedFloat();
39+
}
40+
return packet.readLengthCodedString("ascii");
41+
case Types.DATE:
42+
if (helpers.typeMatch(type, dateStrings, Types)) {
43+
return packet.readLengthCodedString("ascii");
44+
}
45+
return packet.parseDate(timezone);
46+
case Types.DATETIME:
47+
case Types.TIMESTAMP:
48+
if (helpers.typeMatch(type, dateStrings, Types)) {
49+
return packet.readLengthCodedString('ascii');
50+
}
51+
return packet.parseDateTime(timezone);
52+
case Types.TIME:
53+
return packet.readLengthCodedString('ascii');
54+
case Types.GEOMETRY:
55+
return packet.parseGeometryValue();
56+
case Types.JSON:
57+
// Since for JSON columns mysql always returns charset 63 (BINARY),
58+
// we have to handle it according to JSON specs and use "utf8",
59+
// see https://github.com/sidorares/node-mysql2/issues/409
60+
return JSON.parse(packet.readLengthCodedString('utf8'));
61+
default:
62+
if (charset === Charsets.BINARY) {
63+
return packet.readLengthCodedBuffer();
64+
}
65+
return packet.readLengthCodedString(encoding);
66+
}
67+
}
68+
69+
function compile(fields, options, config) {
70+
if (
71+
typeof config.typeCast === 'function' &&
72+
typeof options.typeCast !== 'function'
73+
) {
74+
options.typeCast = config.typeCast;
75+
}
76+
}
77+
78+
function createTypecastField(field, packet) {
79+
return {
80+
type: typeNames[field.columnType],
81+
length: field.columnLength,
82+
db: field.schema,
83+
table: field.table,
84+
name: field.name,
85+
string: function(encoding = field.encoding) {
86+
if (field.columnType === Types.JSON && encoding === field.encoding) {
87+
// Since for JSON columns mysql always returns charset 63 (BINARY),
88+
// we have to handle it according to JSON specs and use "utf8",
89+
// see https://github.com/sidorares/node-mysql2/issues/1661
90+
console.warn(`typeCast: JSON column "${field.name}" is interpreted as BINARY by default, recommended to manually set utf8 encoding: \`field.string("utf8")\``);
91+
}
92+
return packet.readLengthCodedString(encoding);
93+
},
94+
buffer: function() {
95+
return packet.readLengthCodedBuffer();
96+
},
97+
geometry: function() {
98+
return packet.parseGeometryValue();
99+
}
100+
};
101+
}
102+
103+
function getTextParser(fields, options, config) {
104+
return {
105+
next(packet, fields, options) {
106+
const result = options.rowsAsArray ? [] : {};
107+
for (let i = 0; i < fields.length; i++) {
108+
const field = fields[i];
109+
const typeCast = options.typeCast ? options.typeCast : config.typeCast;
110+
const next = () => {
111+
return readField({
112+
packet,
113+
type: field.columnType,
114+
encoding: field.encoding,
115+
charset: field.characterSet,
116+
config,
117+
options
118+
})
119+
}
120+
let value;
121+
if (typeof typeCast === 'function') {
122+
value = typeCast(createTypecastField(field, packet), next);
123+
} else {
124+
value = next();
125+
}
126+
if (options.rowsAsArray) {
127+
result.push(value);
128+
} else if (options.nestTables) {
129+
if (!result[field.table]) {
130+
result[field.table] = {};
131+
}
132+
result[field.table][field.name] = value;
133+
} else {
134+
result[field.name] = value;
135+
}
136+
}
137+
return result;
138+
}
139+
}
140+
}
141+
142+
module.exports = getTextParser;

test/integration/regressions/test-#433.test.cjs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,12 @@ connection.query(
6464
);
6565

6666
/* eslint quotes: 0 */
67-
const expectedError =
67+
const expectedErrorMysql =
6868
"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";
6969

70+
const expectedErrorMariaDB =
71+
"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";
72+
7073
process.on('exit', () => {
7174
testRows.map((tRow, index) => {
7275
const cols = testFields;
@@ -76,6 +79,10 @@ process.on('exit', () => {
7679
assert.equal(aRow[cols[2]], tRow[2]);
7780
assert.equal(aRow[cols[3]], tRow[3]);
7881
});
79-
80-
assert.equal(actualError, expectedError);
82+
83+
if (connection._handshakePacket.serverVersion.match(/MariaDB/)) {
84+
assert.equal(actualError, expectedErrorMariaDB);
85+
} else {
86+
assert.equal(actualError, expectedErrorMysql);
87+
}
8188
});

typings/mysql/lib/Connection.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,8 @@ export interface ConnectionOptions {
323323

324324
waitForConnections?: boolean;
325325

326+
useStaticParser?: boolean;
327+
326328
authPlugins?: {
327329
[key: string]: AuthPlugin;
328330
};

0 commit comments

Comments
 (0)