Skip to content

Commit 2abdce5

Browse files
author
Mark Dierolf
committed
Added support for a fast text protocol row parser and integrated it with query logic.
1 parent 37aa943 commit 2abdce5

2 files changed

Lines changed: 132 additions & 4 deletions

File tree

lib/commands/query.js

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const Command = require('./command.js');
99
const Packets = require('../packets/index.js');
1010
const getTextParser = require('../parsers/text_parser.js');
1111
const staticParser = require('../parsers/static_text_parser.js');
12+
const getFastTextParser = require('../parsers/fast_text_parser.js');
1213
const ServerStatus = require('../constants/server_status.js');
1314

1415
const EmptyPacket = new Packets.Packet(0, Buffer.allocUnsafe(4), 0, 4);
@@ -31,7 +32,7 @@ class Query extends Command {
3132
this._receivedFieldsCount = 0;
3233
this._resultIndex = 0;
3334
this._localStream = null;
34-
this._unpipeStream = function () {};
35+
this._unpipeStream = function() {};
3536
this._streamFactory = options.infileStreamFactory;
3637
this._connection = null;
3738
}
@@ -156,7 +157,7 @@ class Query extends Command {
156157
const onPause = () => {
157158
this._localStream.pause();
158159
};
159-
const onData = function (data) {
160+
const onData = function(data) {
160161
const dataWithHeader = Buffer.allocUnsafe(data.length + 4);
161162
data.copy(dataWithHeader, 4);
162163
connection.writePacket(
@@ -213,7 +214,13 @@ class Query extends Command {
213214
if (this._receivedFieldsCount === this._fieldCount) {
214215
const fields = this._fields[this._resultIndex];
215216
this.emit('fields', fields);
216-
if (this.options.disableEval) {
217+
if (this.options.useFastTextParser) {
218+
this._rowParser = new (getFastTextParser(
219+
fields,
220+
this.options,
221+
connection.config
222+
))(fields);
223+
} else if (this.options.disableEval) {
217224
this._rowParser = staticParser(fields, this.options, connection.config);
218225
} else {
219226
this._rowParser = new (getTextParser(
@@ -288,6 +295,7 @@ class Query extends Command {
288295
});
289296
this.on('end', () => {
290297
stream.push(null); // pushing null, indicating EOF
298+
stream.emit('close'); // notify readers that query has completed
291299
});
292300
this.on('fields', (fields) => {
293301
stream.emit('fields', fields); // replicate old emitter
@@ -301,7 +309,10 @@ class Query extends Command {
301309
_setTimeout() {
302310
if (this.timeout) {
303311
const timeoutHandler = this._handleTimeoutError.bind(this);
304-
this.queryTimeout = Timers.setTimeout(timeoutHandler, this.timeout);
312+
this.queryTimeout = Timers.setTimeout(
313+
timeoutHandler,
314+
this.timeout
315+
);
305316
}
306317
}
307318

lib/parsers/fast_text_parser.js

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
'use strict';
2+
3+
const Types = require('../constants/types.js');
4+
const Charsets = require('../constants/charsets.js');
5+
const helpers = require('../helpers');
6+
const genFunc = require('generate-function');
7+
const parserCache = require('./parser_cache.js');
8+
9+
const typeNames = [];
10+
for (const t in Types) {
11+
typeNames[Types[t]] = t;
12+
}
13+
14+
function readCodeFor(type, charset, encodingExpr, config, options) {
15+
const supportBigNumbers =
16+
options.supportBigNumbers || config.supportBigNumbers;
17+
const bigNumberStrings = options.bigNumberStrings || config.bigNumberStrings;
18+
const timezone = options.timezone || config.timezone;
19+
const dateStrings = options.dateStrings || config.dateStrings;
20+
21+
switch (type) {
22+
case Types.TINY:
23+
case Types.SHORT:
24+
case Types.LONG:
25+
case Types.INT24:
26+
case Types.YEAR:
27+
return 'packet.parseLengthCodedIntNoBigCheck()';
28+
case Types.LONGLONG:
29+
if (supportBigNumbers && bigNumberStrings) {
30+
return 'packet.parseLengthCodedIntString()';
31+
}
32+
return `packet.parseLengthCodedInt(${supportBigNumbers})`;
33+
case Types.FLOAT:
34+
case Types.DOUBLE:
35+
return 'packet.parseLengthCodedFloat()';
36+
case Types.NULL:
37+
return 'packet.readLengthCodedNumber()';
38+
case Types.DECIMAL:
39+
case Types.NEWDECIMAL:
40+
if (config.decimalNumbers) {
41+
return 'packet.parseLengthCodedFloat()';
42+
}
43+
return 'packet.readLengthCodedString("ascii")';
44+
case Types.DATE:
45+
if (helpers.typeMatch(type, dateStrings, Types)) {
46+
return 'packet.readLengthCodedString("ascii")';
47+
}
48+
return `packet.parseDate('${timezone}')`;
49+
case Types.DATETIME:
50+
case Types.TIMESTAMP:
51+
if (helpers.typeMatch(type, dateStrings, Types)) {
52+
return 'packet.readLengthCodedString("ascii")';
53+
}
54+
return `packet.parseDateTime('${timezone}')`;
55+
case Types.TIME:
56+
return 'packet.readLengthCodedString("ascii")';
57+
case Types.GEOMETRY:
58+
return 'packet.parseGeometryValue()';
59+
case Types.JSON:
60+
// Since for JSON columns mysql always returns charset 63 (BINARY),
61+
// we have to handle it according to JSON specs and use "utf8",
62+
// see https://github.com/sidorares/node-mysql2/issues/409
63+
return 'JSON.parse(packet.readLengthCodedString("utf8"))';
64+
default:
65+
if (charset === Charsets.BINARY) {
66+
return 'packet.readLengthCodedBuffer()';
67+
}
68+
return `packet.readLengthCodedString(${encodingExpr})`;
69+
}
70+
}
71+
72+
function compile(fields, options, config) {
73+
const parserFn = genFunc();
74+
75+
/* eslint-disable no-trailing-spaces */
76+
/* eslint-disable no-spaced-func */
77+
/* eslint-disable no-unexpected-multiline */
78+
parserFn('(function () {')('return class TextRow {');
79+
80+
// next method
81+
parserFn('next(packet, fields, options) {');
82+
parserFn('return [');
83+
84+
for (let i = 0; i < fields.length; i++) {
85+
const encodingExpr = `fields[${i}].encoding`;
86+
const readCode = readCodeFor(
87+
fields[i].columnType,
88+
fields[i].characterSet,
89+
encodingExpr,
90+
config,
91+
options
92+
);
93+
parserFn(`${readCode},`);
94+
}
95+
96+
parserFn('];');
97+
parserFn('}');
98+
parserFn('};')('})()');
99+
100+
/* eslint-enable no-trailing-spaces */
101+
/* eslint-enable no-spaced-func */
102+
/* eslint-enable no-unexpected-multiline */
103+
104+
if (config.debug) {
105+
helpers.printDebugWithCode(
106+
'Compiled text protocol row parser',
107+
parserFn.toString()
108+
);
109+
}
110+
return parserFn.toFunction();
111+
}
112+
113+
function getTextParser(fields, options, config) {
114+
return parserCache.getParser('fast', fields, options, config, compile);
115+
}
116+
117+
module.exports = getTextParser;

0 commit comments

Comments
 (0)