mysql2 3.24.4-canary.9178c82 → 3.24.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/base/connection.js +44 -9
- package/lib/commands/execute.js +5 -1
- package/lib/commands/query.js +5 -1
- package/lib/compressed_protocol.js +100 -38
- package/lib/connection_config.js +59 -0
- package/lib/packets/column_definition.js +1 -1
- package/lib/packets/packet.js +12 -3
- package/lib/parsers/binary_parser.js +5 -1
- package/lib/parsers/local_date.js +158 -0
- package/lib/parsers/parser_cache.js +206 -40
- package/lib/parsers/static_binary_parser.js +7 -1
- package/lib/parsers/static_text_parser.js +5 -1
- package/lib/parsers/string.js +114 -0
- package/lib/parsers/text_parser.js +5 -4
- package/package.json +6 -1
package/lib/base/connection.js
CHANGED
|
@@ -43,6 +43,49 @@ let _connectionId = 0;
|
|
|
43
43
|
|
|
44
44
|
let convertNamedPlaceholders = null;
|
|
45
45
|
|
|
46
|
+
// Building a secure context parses every certificate in the ssl config
|
|
47
|
+
// (the bundled AWS profile alone carries over a hundred), which costs
|
|
48
|
+
// milliseconds per connection. The context is immutable once built, so
|
|
49
|
+
// pooled connections sharing one ssl config object share the context, as
|
|
50
|
+
// long as none of the material it was built from has been replaced.
|
|
51
|
+
const secureContexts = new WeakMap();
|
|
52
|
+
|
|
53
|
+
function getSecureContext(ssl) {
|
|
54
|
+
const cached = secureContexts.get(ssl);
|
|
55
|
+
if (
|
|
56
|
+
cached !== undefined &&
|
|
57
|
+
cached.ca === ssl.ca &&
|
|
58
|
+
cached.cert === ssl.cert &&
|
|
59
|
+
cached.ciphers === ssl.ciphers &&
|
|
60
|
+
cached.key === ssl.key &&
|
|
61
|
+
cached.passphrase === ssl.passphrase &&
|
|
62
|
+
cached.minVersion === ssl.minVersion &&
|
|
63
|
+
cached.maxVersion === ssl.maxVersion
|
|
64
|
+
) {
|
|
65
|
+
return cached.secureContext;
|
|
66
|
+
}
|
|
67
|
+
const secureContext = Tls.createSecureContext({
|
|
68
|
+
ca: ssl.ca,
|
|
69
|
+
cert: ssl.cert,
|
|
70
|
+
ciphers: ssl.ciphers,
|
|
71
|
+
key: ssl.key,
|
|
72
|
+
passphrase: ssl.passphrase,
|
|
73
|
+
minVersion: ssl.minVersion,
|
|
74
|
+
maxVersion: ssl.maxVersion,
|
|
75
|
+
});
|
|
76
|
+
secureContexts.set(ssl, {
|
|
77
|
+
secureContext,
|
|
78
|
+
ca: ssl.ca,
|
|
79
|
+
cert: ssl.cert,
|
|
80
|
+
ciphers: ssl.ciphers,
|
|
81
|
+
key: ssl.key,
|
|
82
|
+
passphrase: ssl.passphrase,
|
|
83
|
+
minVersion: ssl.minVersion,
|
|
84
|
+
maxVersion: ssl.maxVersion,
|
|
85
|
+
});
|
|
86
|
+
return secureContext;
|
|
87
|
+
}
|
|
88
|
+
|
|
46
89
|
class BaseConnection extends EventEmitter {
|
|
47
90
|
constructor(opts) {
|
|
48
91
|
super();
|
|
@@ -382,15 +425,7 @@ class BaseConnection extends EventEmitter {
|
|
|
382
425
|
if (this.config.debug) {
|
|
383
426
|
console.log('Upgrading connection to TLS');
|
|
384
427
|
}
|
|
385
|
-
const secureContext =
|
|
386
|
-
ca: this.config.ssl.ca,
|
|
387
|
-
cert: this.config.ssl.cert,
|
|
388
|
-
ciphers: this.config.ssl.ciphers,
|
|
389
|
-
key: this.config.ssl.key,
|
|
390
|
-
passphrase: this.config.ssl.passphrase,
|
|
391
|
-
minVersion: this.config.ssl.minVersion,
|
|
392
|
-
maxVersion: this.config.ssl.maxVersion,
|
|
393
|
-
});
|
|
428
|
+
const secureContext = getSecureContext(this.config.ssl);
|
|
394
429
|
const rejectUnauthorized = this.config.ssl.rejectUnauthorized;
|
|
395
430
|
const verifyIdentity = this.config.ssl.verifyIdentity;
|
|
396
431
|
const servername = Net.isIP(this.config.host)
|
package/lib/commands/execute.js
CHANGED
|
@@ -5,6 +5,7 @@ const Timers = require('timers');
|
|
|
5
5
|
const Command = require('./command.js');
|
|
6
6
|
const Query = require('./query.js');
|
|
7
7
|
const Packets = require('../packets/index.js');
|
|
8
|
+
const ConnectionConfig = require('../connection_config.js');
|
|
8
9
|
|
|
9
10
|
const getBinaryParser = require('../parsers/binary_parser.js');
|
|
10
11
|
const getStaticBinaryParser = require('../parsers/static_binary_parser.js');
|
|
@@ -45,7 +46,10 @@ class Execute extends Command {
|
|
|
45
46
|
|
|
46
47
|
start(packet, connection) {
|
|
47
48
|
this._connection = connection;
|
|
48
|
-
this.options =
|
|
49
|
+
this.options = ConnectionConfig.queryOptions(
|
|
50
|
+
connection.config,
|
|
51
|
+
this._executeOptions
|
|
52
|
+
);
|
|
49
53
|
this._setTimeout();
|
|
50
54
|
const clientFlags =
|
|
51
55
|
connection.config.clientFlags & (connection.serverCapabilityFlags || 0);
|
package/lib/commands/query.js
CHANGED
|
@@ -10,6 +10,7 @@ const Packets = require('../packets/index.js');
|
|
|
10
10
|
const getTextParser = require('../parsers/text_parser.js');
|
|
11
11
|
const staticParser = require('../parsers/static_text_parser.js');
|
|
12
12
|
const ServerStatus = require('../constants/server_status.js');
|
|
13
|
+
const ConnectionConfig = require('../connection_config.js');
|
|
13
14
|
|
|
14
15
|
const EmptyPacket = new Packets.Packet(0, Buffer.allocUnsafe(4), 0, 4);
|
|
15
16
|
|
|
@@ -56,7 +57,10 @@ class Query extends Command {
|
|
|
56
57
|
console.log(' Sending query command: %s', this.sql);
|
|
57
58
|
}
|
|
58
59
|
this._connection = connection;
|
|
59
|
-
this.options =
|
|
60
|
+
this.options = ConnectionConfig.queryOptions(
|
|
61
|
+
connection.config,
|
|
62
|
+
this._queryOptions
|
|
63
|
+
);
|
|
60
64
|
this._setTimeout();
|
|
61
65
|
|
|
62
66
|
const clientFlags =
|
|
@@ -6,6 +6,15 @@
|
|
|
6
6
|
const zlib = require('zlib');
|
|
7
7
|
const PacketParser = require('./packet_parser.js');
|
|
8
8
|
|
|
9
|
+
// the server sends payloads shorter than this uncompressed, and so does
|
|
10
|
+
// the client: deflate cannot shrink them
|
|
11
|
+
const MIN_COMPRESS_LENGTH = 50;
|
|
12
|
+
// zlib work below these sizes takes a few microseconds, less than the trip
|
|
13
|
+
// through the thread pool and the queue behind it, so it runs inline;
|
|
14
|
+
// larger payloads stay asynchronous and keep the event loop free
|
|
15
|
+
const MAX_SYNC_INFLATE_LENGTH = 16384;
|
|
16
|
+
const MAX_SYNC_DEFLATE_LENGTH = 4096;
|
|
17
|
+
|
|
9
18
|
class Queue {
|
|
10
19
|
constructor() {
|
|
11
20
|
this._queue = [];
|
|
@@ -40,6 +49,28 @@ function handleCompressedPacket(packet) {
|
|
|
40
49
|
// the queued (asynchronous) inflate step must be read out now
|
|
41
50
|
const numPackets = packet.numPackets;
|
|
42
51
|
|
|
52
|
+
// an inline step is only in order while no earlier packet is still
|
|
53
|
+
// being inflated on the thread pool
|
|
54
|
+
if (!connection.inflateQueue._running) {
|
|
55
|
+
if (deflatedLength === 0) {
|
|
56
|
+
connection._bumpCompressedSequenceId(numPackets);
|
|
57
|
+
connection._inflatedPacketsParser.execute(body);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (deflatedLength <= MAX_SYNC_INFLATE_LENGTH) {
|
|
61
|
+
let data;
|
|
62
|
+
try {
|
|
63
|
+
data = zlib.inflateSync(body, { maxOutputLength: deflatedLength });
|
|
64
|
+
} catch (err) {
|
|
65
|
+
connection._handleNetworkError(err);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
connection._bumpCompressedSequenceId(numPackets);
|
|
69
|
+
connection._inflatedPacketsParser.execute(data);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
43
74
|
if (deflatedLength !== 0) {
|
|
44
75
|
connection.inflateQueue.push((task) => {
|
|
45
76
|
zlib.inflate(body, { maxOutputLength: deflatedLength }, (err, data) => {
|
|
@@ -61,6 +92,31 @@ function handleCompressedPacket(packet) {
|
|
|
61
92
|
}
|
|
62
93
|
}
|
|
63
94
|
|
|
95
|
+
function writeCompressedFrame(
|
|
96
|
+
connection,
|
|
97
|
+
seqId,
|
|
98
|
+
packetLen,
|
|
99
|
+
buffer,
|
|
100
|
+
compressed
|
|
101
|
+
) {
|
|
102
|
+
const compressHeader = Buffer.allocUnsafe(7);
|
|
103
|
+
const compressedLength = compressed === null ? packetLen : compressed.length;
|
|
104
|
+
if (compressed === null) {
|
|
105
|
+
// http://dev.mysql.com/doc/internals/en/uncompressed-payload.html
|
|
106
|
+
// To send an uncompressed payload:
|
|
107
|
+
// - set length of payload before compression to 0
|
|
108
|
+
// - the compressed payload contains the uncompressed payload instead.
|
|
109
|
+
packetLen = 0;
|
|
110
|
+
}
|
|
111
|
+
compressHeader.writeUInt8(compressedLength & 0xff, 0);
|
|
112
|
+
compressHeader.writeUInt16LE(compressedLength >> 8, 1);
|
|
113
|
+
compressHeader.writeUInt8(seqId, 3);
|
|
114
|
+
compressHeader.writeUInt8(packetLen & 0xff, 4);
|
|
115
|
+
compressHeader.writeUInt16LE(packetLen >> 8, 5);
|
|
116
|
+
connection.writeUncompressed(compressHeader);
|
|
117
|
+
connection.writeUncompressed(compressed === null ? buffer : compressed);
|
|
118
|
+
}
|
|
119
|
+
|
|
64
120
|
function writeCompressed(buffer) {
|
|
65
121
|
// http://dev.mysql.com/doc/internals/en/example-several-mysql-packets.html
|
|
66
122
|
// note: sending a MySQL Packet of the size 2^24−5 to 2^24−1 via compression
|
|
@@ -82,49 +138,55 @@ function writeCompressed(buffer) {
|
|
|
82
138
|
|
|
83
139
|
const connection = this;
|
|
84
140
|
|
|
85
|
-
|
|
86
|
-
const
|
|
141
|
+
const packetLen = buffer.length;
|
|
142
|
+
const seqId = connection.compressedSequenceId;
|
|
143
|
+
connection._bumpCompressedSequenceId(1);
|
|
144
|
+
|
|
145
|
+
// an inline write is only in order while no earlier packet is still
|
|
146
|
+
// being deflated on the thread pool
|
|
147
|
+
if (!connection.deflateQueue._running) {
|
|
148
|
+
if (packetLen < MIN_COMPRESS_LENGTH) {
|
|
149
|
+
writeCompressedFrame(connection, seqId, packetLen, buffer, null);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (packetLen <= MAX_SYNC_DEFLATE_LENGTH) {
|
|
153
|
+
let compressed;
|
|
154
|
+
try {
|
|
155
|
+
compressed = zlib.deflateSync(buffer);
|
|
156
|
+
} catch (err) {
|
|
157
|
+
connection._handleFatalError(err);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
writeCompressedFrame(
|
|
161
|
+
connection,
|
|
162
|
+
seqId,
|
|
163
|
+
packetLen,
|
|
164
|
+
buffer,
|
|
165
|
+
compressed.length < packetLen ? compressed : null
|
|
166
|
+
);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
87
170
|
|
|
88
171
|
// seqqueue is used here because zlib async execution is routed via thread pool
|
|
89
172
|
// internally and when we have multiple compressed packets arriving we need
|
|
90
173
|
// to assemble uncompressed result sequentially
|
|
91
|
-
(
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
compressHeader.writeUInt16LE(packetLen >> 8, 5);
|
|
106
|
-
connection.writeUncompressed(compressHeader);
|
|
107
|
-
connection.writeUncompressed(compressed);
|
|
108
|
-
} else {
|
|
109
|
-
// http://dev.mysql.com/doc/internals/en/uncompressed-payload.html
|
|
110
|
-
// To send an uncompressed payload:
|
|
111
|
-
// - set length of payload before compression to 0
|
|
112
|
-
// - the compressed payload contains the uncompressed payload instead.
|
|
113
|
-
compressedLength = packetLen;
|
|
114
|
-
packetLen = 0;
|
|
115
|
-
compressHeader.writeUInt8(compressedLength & 0xff, 0);
|
|
116
|
-
compressHeader.writeUInt16LE(compressedLength >> 8, 1);
|
|
117
|
-
compressHeader.writeUInt8(seqId, 3);
|
|
118
|
-
compressHeader.writeUInt8(packetLen & 0xff, 4);
|
|
119
|
-
compressHeader.writeUInt16LE(packetLen >> 8, 5);
|
|
120
|
-
connection.writeUncompressed(compressHeader);
|
|
121
|
-
connection.writeUncompressed(buffer);
|
|
122
|
-
}
|
|
123
|
-
task.done();
|
|
124
|
-
});
|
|
174
|
+
connection.deflateQueue.push((task) => {
|
|
175
|
+
zlib.deflate(buffer, (err, compressed) => {
|
|
176
|
+
if (err) {
|
|
177
|
+
connection._handleFatalError(err);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
writeCompressedFrame(
|
|
181
|
+
connection,
|
|
182
|
+
seqId,
|
|
183
|
+
packetLen,
|
|
184
|
+
buffer,
|
|
185
|
+
compressed.length < packetLen ? compressed : null
|
|
186
|
+
);
|
|
187
|
+
task.done();
|
|
125
188
|
});
|
|
126
|
-
})
|
|
127
|
-
connection._bumpCompressedSequenceId(1);
|
|
189
|
+
});
|
|
128
190
|
}
|
|
129
191
|
|
|
130
192
|
function enableCompression(connection) {
|
package/lib/connection_config.js
CHANGED
|
@@ -196,6 +196,65 @@ class ConnectionConfig {
|
|
|
196
196
|
this.gracefulEnd = options.gracefulEnd || false;
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
// Same result as Object.assign({}, config, overrides), built from a
|
|
200
|
+
// fixed-shape literal: one allocation instead of a transition per option,
|
|
201
|
+
// and every later options read stays a monomorphic in-object load. The
|
|
202
|
+
// key list mirrors the constructor above; test/unit/connection/
|
|
203
|
+
// test-query-options.test.mts fails when the two drift apart.
|
|
204
|
+
static queryOptions(config, overrides) {
|
|
205
|
+
if (!(config instanceof ConnectionConfig)) {
|
|
206
|
+
return Object.assign({}, config, overrides);
|
|
207
|
+
}
|
|
208
|
+
const options = {
|
|
209
|
+
isServer: config.isServer,
|
|
210
|
+
stream: config.stream,
|
|
211
|
+
host: config.host,
|
|
212
|
+
port: config.port,
|
|
213
|
+
localAddress: config.localAddress,
|
|
214
|
+
socketPath: config.socketPath,
|
|
215
|
+
user: config.user,
|
|
216
|
+
password: config.password,
|
|
217
|
+
password2: config.password2,
|
|
218
|
+
password3: config.password3,
|
|
219
|
+
passwordSha1: config.passwordSha1,
|
|
220
|
+
database: config.database,
|
|
221
|
+
connectTimeout: config.connectTimeout,
|
|
222
|
+
insecureAuth: config.insecureAuth,
|
|
223
|
+
infileStreamFactory: config.infileStreamFactory,
|
|
224
|
+
supportBigNumbers: config.supportBigNumbers,
|
|
225
|
+
bigNumberStrings: config.bigNumberStrings,
|
|
226
|
+
decimalNumbers: config.decimalNumbers,
|
|
227
|
+
dateStrings: config.dateStrings,
|
|
228
|
+
debug: config.debug,
|
|
229
|
+
trace: config.trace,
|
|
230
|
+
stringifyObjects: config.stringifyObjects,
|
|
231
|
+
enableKeepAlive: config.enableKeepAlive,
|
|
232
|
+
keepAliveInitialDelay: config.keepAliveInitialDelay,
|
|
233
|
+
timezone: config.timezone,
|
|
234
|
+
queryFormat: config.queryFormat,
|
|
235
|
+
pool: config.pool,
|
|
236
|
+
ssl: config.ssl,
|
|
237
|
+
multipleStatements: config.multipleStatements,
|
|
238
|
+
rowsAsArray: config.rowsAsArray,
|
|
239
|
+
namedPlaceholders: config.namedPlaceholders,
|
|
240
|
+
nestTables: config.nestTables,
|
|
241
|
+
typeCast: config.typeCast,
|
|
242
|
+
disableEval: config.disableEval,
|
|
243
|
+
enableCleartextPlugin: config.enableCleartextPlugin,
|
|
244
|
+
maxPacketSize: config.maxPacketSize,
|
|
245
|
+
charsetNumber: config.charsetNumber,
|
|
246
|
+
compress: config.compress,
|
|
247
|
+
authPlugins: config.authPlugins,
|
|
248
|
+
authSwitchHandler: config.authSwitchHandler,
|
|
249
|
+
clientFlags: config.clientFlags,
|
|
250
|
+
connectAttributes: config.connectAttributes,
|
|
251
|
+
maxPreparedStatements: config.maxPreparedStatements,
|
|
252
|
+
jsonStrings: config.jsonStrings,
|
|
253
|
+
gracefulEnd: config.gracefulEnd,
|
|
254
|
+
};
|
|
255
|
+
return Object.assign(options, overrides);
|
|
256
|
+
}
|
|
257
|
+
|
|
199
258
|
static mergeFlags(default_flags, user_flags) {
|
|
200
259
|
let flags = 0x0,
|
|
201
260
|
i;
|
|
@@ -52,7 +52,7 @@ class ColumnDefinition {
|
|
|
52
52
|
packet.skip(1); // length of the following fields (always 0x0c)
|
|
53
53
|
this.characterSet = packet.readInt16();
|
|
54
54
|
this.encoding = CharsetToEncoding[this.characterSet];
|
|
55
|
-
this.name = StringParser.
|
|
55
|
+
this.name = StringParser.decodeShort(
|
|
56
56
|
this._buf,
|
|
57
57
|
this.encoding === 'binary' ? this._clientEncoding : this.encoding,
|
|
58
58
|
_nameStart,
|
package/lib/packets/packet.js
CHANGED
|
@@ -9,6 +9,7 @@ const ErrorCodeToName = require('../constants/errors.js');
|
|
|
9
9
|
const NativeBuffer = require('buffer').Buffer;
|
|
10
10
|
const Long = require('long');
|
|
11
11
|
const StringParser = require('../parsers/string.js');
|
|
12
|
+
const { localDate } = require('../parsers/local_date.js');
|
|
12
13
|
const Types = require('../constants/types.js');
|
|
13
14
|
const ZERO_DATE = '0000-00-00';
|
|
14
15
|
|
|
@@ -335,7 +336,7 @@ class Packet {
|
|
|
335
336
|
if (timezone === 'Z') {
|
|
336
337
|
return new Date(Date.UTC(y, m - 1, d, H, M, S, ms));
|
|
337
338
|
}
|
|
338
|
-
return
|
|
339
|
+
return localDate(y, m, d, H, M, S, ms);
|
|
339
340
|
}
|
|
340
341
|
let str = this.readDateTimeString(6, 'T', null);
|
|
341
342
|
if (str.startsWith(ZERO_DATE)) {
|
|
@@ -441,6 +442,14 @@ class Packet {
|
|
|
441
442
|
this.offset += len;
|
|
442
443
|
// TODO: Use characterSetCode to get proper encoding
|
|
443
444
|
// https://github.com/sidorares/node-mysql2/pull/374
|
|
445
|
+
if (len <= StringParser.SHORT_STRING_MAX_LENGTH) {
|
|
446
|
+
return StringParser.decodeShort(
|
|
447
|
+
this.buffer,
|
|
448
|
+
encoding,
|
|
449
|
+
this.offset - len,
|
|
450
|
+
this.offset
|
|
451
|
+
);
|
|
452
|
+
}
|
|
444
453
|
return StringParser.decode(
|
|
445
454
|
this.buffer,
|
|
446
455
|
encoding,
|
|
@@ -735,7 +744,7 @@ class Packet {
|
|
|
735
744
|
this.offset++; // -
|
|
736
745
|
const d = this.parseInt(2);
|
|
737
746
|
if (!timezone || timezone === 'local') {
|
|
738
|
-
return
|
|
747
|
+
return localDate(y, m, d, 0, 0, 0, 0);
|
|
739
748
|
}
|
|
740
749
|
if (timezone === 'Z') {
|
|
741
750
|
return new Date(Date.UTC(y, m - 1, d));
|
|
@@ -797,7 +806,7 @@ class Packet {
|
|
|
797
806
|
}
|
|
798
807
|
this.offset += len;
|
|
799
808
|
if (!timezone || timezone === 'local') {
|
|
800
|
-
return
|
|
809
|
+
return localDate(y, mo, d, h, mi, se, ms);
|
|
801
810
|
}
|
|
802
811
|
const utc = Date.UTC(y, mo - 1, d, h, mi, se, ms);
|
|
803
812
|
if (timezone === 'Z') {
|
|
@@ -6,6 +6,7 @@ const Types = require('../constants/types.js');
|
|
|
6
6
|
const helpers = require('../helpers');
|
|
7
7
|
const genFunc = require('generate-function');
|
|
8
8
|
const parserCache = require('./parser_cache.js');
|
|
9
|
+
const LocalDate = require('./local_date.js');
|
|
9
10
|
const typeNames = [];
|
|
10
11
|
for (const t in Types) {
|
|
11
12
|
typeNames[Types[t]] = t;
|
|
@@ -173,6 +174,9 @@ function compile(fields, options, config) {
|
|
|
173
174
|
parserFn('(function(){');
|
|
174
175
|
parserFn('return class BinaryRow {');
|
|
175
176
|
parserFn('constructor() {');
|
|
177
|
+
if (fields.some((field) => LocalDate.usesLocalDate(field, options, config))) {
|
|
178
|
+
parserFn('LocalDate.checkTimezone();');
|
|
179
|
+
}
|
|
176
180
|
parserFn('}');
|
|
177
181
|
|
|
178
182
|
parserFn('next(packet, fields, options) {');
|
|
@@ -264,7 +268,7 @@ function compile(fields, options, config) {
|
|
|
264
268
|
parserFn.toString()
|
|
265
269
|
);
|
|
266
270
|
}
|
|
267
|
-
return parserFn.toFunction({ wrap, wrapNull });
|
|
271
|
+
return parserFn.toFunction({ wrap, wrapNull, LocalDate });
|
|
268
272
|
}
|
|
269
273
|
|
|
270
274
|
function getBinaryParser(fields, options, config) {
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Types = require('../constants/types.js');
|
|
4
|
+
const helpers = require('../helpers.js');
|
|
5
|
+
|
|
6
|
+
// new Date(y, m, d, h, mi, s, ms) interprets its arguments as local wall
|
|
7
|
+
// time; the wall-time to UTC conversion inside that constructor dominates
|
|
8
|
+
// the cost of materializing a DATE, DATETIME or TIMESTAMP value. The
|
|
9
|
+
// conversion depends only on the wall-clock instant, and the offset it
|
|
10
|
+
// applies is constant across almost every hour (it changes only at DST
|
|
11
|
+
// transitions), so the offset is cached per wall-clock hour once the real
|
|
12
|
+
// constructor has confirmed it holds over the whole hour. Hours where it
|
|
13
|
+
// does not hold keep using the constructor.
|
|
14
|
+
|
|
15
|
+
const MS_PER_HOUR = 3600000;
|
|
16
|
+
const MS_PER_DAY = 86400000;
|
|
17
|
+
const MAX_CACHED_HOURS = 4096;
|
|
18
|
+
|
|
19
|
+
const hourOffsets = new Map();
|
|
20
|
+
let lastTimezone = readTimezone();
|
|
21
|
+
|
|
22
|
+
function readTimezone() {
|
|
23
|
+
try {
|
|
24
|
+
return process.env.TZ;
|
|
25
|
+
} catch {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Node resets its own time zone cache when process.env.TZ is assigned;
|
|
31
|
+
// called once per result set so the offsets cached here follow suit
|
|
32
|
+
function checkTimezone() {
|
|
33
|
+
const timezone = readTimezone();
|
|
34
|
+
if (timezone !== lastTimezone) {
|
|
35
|
+
lastTimezone = timezone;
|
|
36
|
+
hourOffsets.clear();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// days since 1970-01-01 in the proleptic Gregorian calendar, the same value
|
|
41
|
+
// ECMAScript's MakeDay produces for a month in 1..12 and any integer day
|
|
42
|
+
function daysFromCivil(year, month, day) {
|
|
43
|
+
const y = month <= 2 ? year - 1 : year;
|
|
44
|
+
const era = Math.floor(y / 400);
|
|
45
|
+
const yearOfEra = y - era * 400;
|
|
46
|
+
const dayOfYear = (((153 * ((month + 9) % 12) + 2) / 5) | 0) + day - 1;
|
|
47
|
+
const dayOfEra =
|
|
48
|
+
yearOfEra * 365 +
|
|
49
|
+
((yearOfEra / 4) | 0) -
|
|
50
|
+
((yearOfEra / 100) | 0) +
|
|
51
|
+
dayOfYear;
|
|
52
|
+
return era * 146097 + dayOfEra - 719468;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function localTime(wallTime) {
|
|
56
|
+
const wall = new Date(wallTime);
|
|
57
|
+
return new Date(
|
|
58
|
+
wall.getUTCFullYear(),
|
|
59
|
+
wall.getUTCMonth(),
|
|
60
|
+
wall.getUTCDate(),
|
|
61
|
+
wall.getUTCHours(),
|
|
62
|
+
wall.getUTCMinutes(),
|
|
63
|
+
wall.getUTCSeconds(),
|
|
64
|
+
wall.getUTCMilliseconds()
|
|
65
|
+
).getTime();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// the offset is trusted for the hour only when the constructor applies the
|
|
69
|
+
// same one at its start, middle and end; null marks an hour that contains a
|
|
70
|
+
// transition
|
|
71
|
+
function proveHourOffset(hour) {
|
|
72
|
+
const start = hour * MS_PER_HOUR;
|
|
73
|
+
const offset = start - localTime(start);
|
|
74
|
+
if (
|
|
75
|
+
offset !== start + MS_PER_HOUR / 2 - localTime(start + MS_PER_HOUR / 2) ||
|
|
76
|
+
offset !== start + MS_PER_HOUR - 1 - localTime(start + MS_PER_HOUR - 1) ||
|
|
77
|
+
Number.isNaN(offset)
|
|
78
|
+
) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
return offset;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// same value as new Date(year, month - 1, day, hours, minutes, seconds,
|
|
85
|
+
// milliseconds), for the integer arguments the wire protocols carry
|
|
86
|
+
function localDate(year, month, day, hours, minutes, seconds, milliseconds) {
|
|
87
|
+
if (year < 100 || month < 1 || month > 12) {
|
|
88
|
+
return new Date(
|
|
89
|
+
year,
|
|
90
|
+
month - 1,
|
|
91
|
+
day,
|
|
92
|
+
hours,
|
|
93
|
+
minutes,
|
|
94
|
+
seconds,
|
|
95
|
+
milliseconds
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
const wallTime =
|
|
99
|
+
daysFromCivil(year, month, day) * MS_PER_DAY +
|
|
100
|
+
hours * MS_PER_HOUR +
|
|
101
|
+
minutes * 60000 +
|
|
102
|
+
seconds * 1000 +
|
|
103
|
+
Math.trunc(milliseconds);
|
|
104
|
+
const hour = Math.floor(wallTime / MS_PER_HOUR);
|
|
105
|
+
let offset = hourOffsets.get(hour);
|
|
106
|
+
if (offset === undefined) {
|
|
107
|
+
if (hourOffsets.size >= MAX_CACHED_HOURS) {
|
|
108
|
+
hourOffsets.clear();
|
|
109
|
+
}
|
|
110
|
+
offset = proveHourOffset(hour);
|
|
111
|
+
hourOffsets.set(hour, offset);
|
|
112
|
+
}
|
|
113
|
+
if (offset === null) {
|
|
114
|
+
return new Date(
|
|
115
|
+
year,
|
|
116
|
+
month - 1,
|
|
117
|
+
day,
|
|
118
|
+
hours,
|
|
119
|
+
minutes,
|
|
120
|
+
seconds,
|
|
121
|
+
milliseconds
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
return new Date(wallTime - offset);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// whether a column's values are built through localDate, so the parser
|
|
128
|
+
// for a result set knows to call checkTimezone first
|
|
129
|
+
function usesLocalDate(field, options, config) {
|
|
130
|
+
const timezone = options.timezone || config.timezone;
|
|
131
|
+
if (timezone && timezone !== 'local') {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
const type = field.columnType;
|
|
135
|
+
if (
|
|
136
|
+
type !== Types.DATE &&
|
|
137
|
+
type !== Types.DATETIME &&
|
|
138
|
+
type !== Types.TIMESTAMP &&
|
|
139
|
+
type !== Types.NEWDATE
|
|
140
|
+
) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
return !helpers.typeMatch(
|
|
144
|
+
type,
|
|
145
|
+
options.dateStrings || config.dateStrings,
|
|
146
|
+
Types
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
module.exports = {
|
|
151
|
+
localDate,
|
|
152
|
+
checkTimezone,
|
|
153
|
+
usesLocalDate,
|
|
154
|
+
_daysFromCivil: daysFromCivil,
|
|
155
|
+
_clear() {
|
|
156
|
+
hourOffsets.clear();
|
|
157
|
+
},
|
|
158
|
+
};
|
|
@@ -6,58 +6,223 @@ const parserCache = createLRU({
|
|
|
6
6
|
max: 15000,
|
|
7
7
|
});
|
|
8
8
|
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
// A compiled row parser depends on the parsing options and, per column, on
|
|
10
|
+
// exactly the metadata the code generators bake into the function: type,
|
|
11
|
+
// charset, flags, decimals, name, MariaDB extended metadata, and the table
|
|
12
|
+
// name when rows are nested by table. The cache is keyed by an integer hash
|
|
13
|
+
// of that data and every hit is confirmed against the full metadata kept in
|
|
14
|
+
// the entry, so a hash collision can never hand out a parser compiled for
|
|
15
|
+
// different columns or options.
|
|
16
|
+
|
|
17
|
+
const TYPE_CAST_FALSE = 1;
|
|
18
|
+
const TYPE_CAST_FUNCTION = 2;
|
|
19
|
+
const TYPE_CAST_DEFAULT = 3;
|
|
20
|
+
|
|
21
|
+
function typeCastKind(typeCast) {
|
|
22
|
+
if (typeCast === false) {
|
|
23
|
+
return TYPE_CAST_FALSE;
|
|
24
|
+
}
|
|
25
|
+
if (typeof typeCast === 'function') {
|
|
26
|
+
return TYPE_CAST_FUNCTION;
|
|
27
|
+
}
|
|
28
|
+
return TYPE_CAST_DEFAULT;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// helpers.typeMatch reads an array by the type names it lists and anything
|
|
32
|
+
// else by truthiness
|
|
33
|
+
function dateStringsKey(dateStrings) {
|
|
34
|
+
if (Array.isArray(dateStrings)) {
|
|
35
|
+
return dateStrings.map(String);
|
|
36
|
+
}
|
|
37
|
+
return Boolean(dateStrings);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function nestTablesKey(nestTables) {
|
|
41
|
+
if (typeof nestTables === 'string') {
|
|
42
|
+
return nestTables;
|
|
16
43
|
}
|
|
17
|
-
return
|
|
44
|
+
return Boolean(nestTables);
|
|
18
45
|
}
|
|
19
46
|
|
|
20
|
-
function
|
|
21
|
-
|
|
22
|
-
type
|
|
23
|
-
|
|
24
|
-
options.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
]);
|
|
47
|
+
function optionBits(type, options, config, nestTables, dateStrings) {
|
|
48
|
+
return (
|
|
49
|
+
(type === 'binary' ? 1 : 0) |
|
|
50
|
+
(options.rowsAsArray ? 2 : 0) |
|
|
51
|
+
(options.supportBigNumbers || config.supportBigNumbers ? 4 : 0) |
|
|
52
|
+
(options.bigNumberStrings || config.bigNumberStrings ? 8 : 0) |
|
|
53
|
+
(typeCastKind(options.typeCast) << 4) |
|
|
54
|
+
(options.decimalNumbers ? 64 : 0) |
|
|
55
|
+
(config.jsonStrings ? 128 : 0) |
|
|
56
|
+
(nestTables === true ? 256 : 0) |
|
|
57
|
+
(typeof nestTables === 'string' ? 512 : 0) |
|
|
58
|
+
(dateStrings === true ? 1024 : 0) |
|
|
59
|
+
(Array.isArray(dateStrings) ? 2048 : 0)
|
|
60
|
+
);
|
|
61
|
+
}
|
|
36
62
|
|
|
63
|
+
function mixNumber(hash, value) {
|
|
64
|
+
return Math.imul(hash ^ (value | 0), 0x9e3779b1) | 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function mixString(hash, string) {
|
|
68
|
+
if (typeof string !== 'string') {
|
|
69
|
+
return mixNumber(hash, string === undefined ? -1 : -2);
|
|
70
|
+
}
|
|
71
|
+
hash = mixNumber(hash, string.length);
|
|
72
|
+
for (let i = 0; i < string.length; ++i) {
|
|
73
|
+
hash = (Math.imul(hash, 31) + string.charCodeAt(i)) | 0;
|
|
74
|
+
}
|
|
75
|
+
return hash;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function hashKey(type, fields, options, config) {
|
|
79
|
+
const nestTables = nestTablesKey(options.nestTables);
|
|
80
|
+
const dateStrings = dateStringsKey(options.dateStrings || config.dateStrings);
|
|
81
|
+
const includeTable = nestTables !== false;
|
|
82
|
+
let hash = mixNumber(
|
|
83
|
+
0x811c9dc5,
|
|
84
|
+
optionBits(type, options, config, nestTables, dateStrings)
|
|
85
|
+
);
|
|
86
|
+
hash = mixString(hash, String(options.timezone || config.timezone));
|
|
87
|
+
if (typeof nestTables === 'string') {
|
|
88
|
+
hash = mixString(hash, nestTables);
|
|
89
|
+
}
|
|
90
|
+
if (Array.isArray(dateStrings)) {
|
|
91
|
+
for (let i = 0; i < dateStrings.length; ++i) {
|
|
92
|
+
hash = mixString(hash, dateStrings[i]);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
hash = mixNumber(hash, fields.length);
|
|
37
96
|
for (let i = 0; i < fields.length; ++i) {
|
|
38
97
|
const field = fields[i];
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
98
|
+
hash = mixNumber(
|
|
99
|
+
hash,
|
|
100
|
+
field.columnType | (field.characterSet << 8) | (field.decimals << 24)
|
|
101
|
+
);
|
|
102
|
+
hash = mixNumber(hash, field.flags);
|
|
103
|
+
hash = mixString(hash, field.name);
|
|
104
|
+
if (field.extendedTypeName !== undefined) {
|
|
105
|
+
hash = mixString(hash, field.extendedTypeName);
|
|
106
|
+
}
|
|
107
|
+
if (field.extendedFormat !== undefined) {
|
|
108
|
+
hash = mixString(hash, field.extendedFormat);
|
|
109
|
+
}
|
|
110
|
+
if (includeTable) {
|
|
111
|
+
hash = mixString(hash, field.table);
|
|
112
|
+
}
|
|
46
113
|
}
|
|
114
|
+
// small non-negative integers keep the Map keys as Smis
|
|
115
|
+
return hash & 0x3fffffff;
|
|
116
|
+
}
|
|
47
117
|
|
|
48
|
-
|
|
118
|
+
function sameList(a, b) {
|
|
119
|
+
if (a.length !== b.length) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
for (let i = 0; i < a.length; ++i) {
|
|
123
|
+
if (a[i] !== b[i]) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return true;
|
|
49
128
|
}
|
|
50
129
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
130
|
+
class Entry {
|
|
131
|
+
constructor(parser, type, fields, options, config) {
|
|
132
|
+
this.parser = parser;
|
|
133
|
+
this.nestTables = nestTablesKey(options.nestTables);
|
|
134
|
+
this.dateStrings = dateStringsKey(
|
|
135
|
+
options.dateStrings || config.dateStrings
|
|
136
|
+
);
|
|
137
|
+
this.optionBits = optionBits(
|
|
138
|
+
type,
|
|
139
|
+
options,
|
|
140
|
+
config,
|
|
141
|
+
this.nestTables,
|
|
142
|
+
this.dateStrings
|
|
143
|
+
);
|
|
144
|
+
this.timezone = String(options.timezone || config.timezone);
|
|
145
|
+
const count = fields.length;
|
|
146
|
+
this.columnTypes = new Array(count);
|
|
147
|
+
this.characterSets = new Array(count);
|
|
148
|
+
this.flags = new Array(count);
|
|
149
|
+
this.decimals = new Array(count);
|
|
150
|
+
this.names = new Array(count);
|
|
151
|
+
this.extendedTypeNames = new Array(count);
|
|
152
|
+
this.extendedFormats = new Array(count);
|
|
153
|
+
this.tables = this.nestTables === false ? null : new Array(count);
|
|
154
|
+
for (let i = 0; i < count; ++i) {
|
|
155
|
+
const field = fields[i];
|
|
156
|
+
this.columnTypes[i] = field.columnType;
|
|
157
|
+
this.characterSets[i] = field.characterSet;
|
|
158
|
+
this.flags[i] = field.flags;
|
|
159
|
+
this.decimals[i] = field.decimals;
|
|
160
|
+
this.names[i] = field.name;
|
|
161
|
+
this.extendedTypeNames[i] = field.extendedTypeName;
|
|
162
|
+
this.extendedFormats[i] = field.extendedFormat;
|
|
163
|
+
if (this.tables !== null) {
|
|
164
|
+
this.tables[i] = field.table;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
54
168
|
|
|
55
|
-
|
|
56
|
-
|
|
169
|
+
matches(type, fields, options, config) {
|
|
170
|
+
const nestTables = nestTablesKey(options.nestTables);
|
|
171
|
+
const dateStrings = dateStringsKey(
|
|
172
|
+
options.dateStrings || config.dateStrings
|
|
173
|
+
);
|
|
174
|
+
if (
|
|
175
|
+
this.optionBits !==
|
|
176
|
+
optionBits(type, options, config, nestTables, dateStrings) ||
|
|
177
|
+
this.nestTables !== nestTables ||
|
|
178
|
+
this.timezone !== String(options.timezone || config.timezone) ||
|
|
179
|
+
this.names.length !== fields.length
|
|
180
|
+
) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
// equal option bits already guarantee both sides are lists (or the
|
|
184
|
+
// same boolean), so only the list contents are left to compare
|
|
185
|
+
if (
|
|
186
|
+
Array.isArray(dateStrings) &&
|
|
187
|
+
!sameList(this.dateStrings, dateStrings)
|
|
188
|
+
) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
for (let i = 0; i < fields.length; ++i) {
|
|
192
|
+
const field = fields[i];
|
|
193
|
+
if (
|
|
194
|
+
this.columnTypes[i] !== field.columnType ||
|
|
195
|
+
this.characterSets[i] !== field.characterSet ||
|
|
196
|
+
this.flags[i] !== field.flags ||
|
|
197
|
+
this.decimals[i] !== field.decimals ||
|
|
198
|
+
this.names[i] !== field.name ||
|
|
199
|
+
this.extendedTypeNames[i] !== field.extendedTypeName ||
|
|
200
|
+
this.extendedFormats[i] !== field.extendedFormat ||
|
|
201
|
+
(this.tables !== null && this.tables[i] !== field.table)
|
|
202
|
+
) {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return true;
|
|
57
207
|
}
|
|
208
|
+
}
|
|
58
209
|
|
|
59
|
-
|
|
60
|
-
|
|
210
|
+
function getParser(type, fields, options, config, compiler) {
|
|
211
|
+
const hash = hashKey(type, fields, options, config);
|
|
212
|
+
let entries = parserCache.get(hash);
|
|
213
|
+
if (entries !== undefined) {
|
|
214
|
+
for (let i = 0; i < entries.length; ++i) {
|
|
215
|
+
if (entries[i].matches(type, fields, options, config)) {
|
|
216
|
+
return entries[i].parser;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const parser = compiler(fields, options, config);
|
|
221
|
+
if (entries === undefined) {
|
|
222
|
+
entries = [];
|
|
223
|
+
parserCache.set(hash, entries);
|
|
224
|
+
}
|
|
225
|
+
entries.push(new Entry(parser, type, fields, options, config));
|
|
61
226
|
return parser;
|
|
62
227
|
}
|
|
63
228
|
|
|
@@ -73,5 +238,6 @@ module.exports = {
|
|
|
73
238
|
getParser: getParser,
|
|
74
239
|
setMaxCache: setMaxCache,
|
|
75
240
|
clearCache: clearCache,
|
|
76
|
-
|
|
241
|
+
_hashKey: hashKey,
|
|
242
|
+
_Entry: Entry,
|
|
77
243
|
};
|
|
@@ -4,13 +4,19 @@ const FieldFlags = require('../constants/field_flags.js');
|
|
|
4
4
|
const Charsets = require('../constants/charsets.js');
|
|
5
5
|
const Types = require('../constants/types.js');
|
|
6
6
|
const helpers = require('../helpers');
|
|
7
|
+
const LocalDate = require('./local_date.js');
|
|
7
8
|
|
|
8
9
|
const typeNames = [];
|
|
9
10
|
for (const t in Types) {
|
|
10
11
|
typeNames[Types[t]] = t;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
function getBinaryParser(fields,
|
|
14
|
+
function getBinaryParser(fields, queryOptions, config) {
|
|
15
|
+
if (
|
|
16
|
+
fields.some((field) => LocalDate.usesLocalDate(field, queryOptions, config))
|
|
17
|
+
) {
|
|
18
|
+
LocalDate.checkTimezone();
|
|
19
|
+
}
|
|
14
20
|
function readCode(field, config, options, fieldNum, packet) {
|
|
15
21
|
const supportBigNumbers = Boolean(
|
|
16
22
|
options.supportBigNumbers || config.supportBigNumbers
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const Types = require('../constants/types.js');
|
|
4
4
|
const Charsets = require('../constants/charsets.js');
|
|
5
5
|
const helpers = require('../helpers');
|
|
6
|
+
const LocalDate = require('./local_date.js');
|
|
6
7
|
|
|
7
8
|
const typeNames = [];
|
|
8
9
|
for (const t in Types) {
|
|
@@ -118,7 +119,10 @@ function createTypecastField(field, packet) {
|
|
|
118
119
|
};
|
|
119
120
|
}
|
|
120
121
|
|
|
121
|
-
function getTextParser(
|
|
122
|
+
function getTextParser(fields, options, config) {
|
|
123
|
+
if (fields.some((field) => LocalDate.usesLocalDate(field, options, config))) {
|
|
124
|
+
LocalDate.checkTimezone();
|
|
125
|
+
}
|
|
122
126
|
return {
|
|
123
127
|
next(packet, fields, options) {
|
|
124
128
|
const result = options.rowsAsArray ? [] : {};
|
package/lib/parsers/string.js
CHANGED
|
@@ -19,6 +19,120 @@ const hasFastSlices =
|
|
|
19
19
|
// buffer.write(); same stability story as the slice methods above
|
|
20
20
|
exports.hasFastUtf8Write = typeof Buffer.prototype.utf8Write === 'function';
|
|
21
21
|
|
|
22
|
+
// A native slice costs a fixed ~50ns per call, mostly the JS to C++
|
|
23
|
+
// transition, while String.fromCharCode with a handful of arguments stays
|
|
24
|
+
// inside V8 at a fraction of that. Every ASCII byte decodes to the same
|
|
25
|
+
// code unit under utf8, ascii and latin1, so short all-ASCII values skip
|
|
26
|
+
// the transition; anything else returns null and takes the native path.
|
|
27
|
+
function shortAscii(b, s, length) {
|
|
28
|
+
if (b[s] >= 128) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
switch (length) {
|
|
32
|
+
case 1: {
|
|
33
|
+
const c0 = b[s];
|
|
34
|
+
return c0 < 128 ? String.fromCharCode(c0) : null;
|
|
35
|
+
}
|
|
36
|
+
case 2: {
|
|
37
|
+
const c0 = b[s];
|
|
38
|
+
const c1 = b[s + 1];
|
|
39
|
+
return (c0 | c1) < 128 ? String.fromCharCode(c0, c1) : null;
|
|
40
|
+
}
|
|
41
|
+
case 3: {
|
|
42
|
+
const c0 = b[s];
|
|
43
|
+
const c1 = b[s + 1];
|
|
44
|
+
const c2 = b[s + 2];
|
|
45
|
+
return (c0 | c1 | c2) < 128 ? String.fromCharCode(c0, c1, c2) : null;
|
|
46
|
+
}
|
|
47
|
+
case 4: {
|
|
48
|
+
const c0 = b[s];
|
|
49
|
+
const c1 = b[s + 1];
|
|
50
|
+
const c2 = b[s + 2];
|
|
51
|
+
const c3 = b[s + 3];
|
|
52
|
+
return (c0 | c1 | c2 | c3) < 128
|
|
53
|
+
? String.fromCharCode(c0, c1, c2, c3)
|
|
54
|
+
: null;
|
|
55
|
+
}
|
|
56
|
+
case 5: {
|
|
57
|
+
const c0 = b[s];
|
|
58
|
+
const c1 = b[s + 1];
|
|
59
|
+
const c2 = b[s + 2];
|
|
60
|
+
const c3 = b[s + 3];
|
|
61
|
+
const c4 = b[s + 4];
|
|
62
|
+
return (c0 | c1 | c2 | c3 | c4) < 128
|
|
63
|
+
? String.fromCharCode(c0, c1, c2, c3, c4)
|
|
64
|
+
: null;
|
|
65
|
+
}
|
|
66
|
+
case 6: {
|
|
67
|
+
const c0 = b[s];
|
|
68
|
+
const c1 = b[s + 1];
|
|
69
|
+
const c2 = b[s + 2];
|
|
70
|
+
const c3 = b[s + 3];
|
|
71
|
+
const c4 = b[s + 4];
|
|
72
|
+
const c5 = b[s + 5];
|
|
73
|
+
return (c0 | c1 | c2 | c3 | c4 | c5) < 128
|
|
74
|
+
? String.fromCharCode(c0, c1, c2, c3, c4, c5)
|
|
75
|
+
: null;
|
|
76
|
+
}
|
|
77
|
+
case 7: {
|
|
78
|
+
const c0 = b[s];
|
|
79
|
+
const c1 = b[s + 1];
|
|
80
|
+
const c2 = b[s + 2];
|
|
81
|
+
const c3 = b[s + 3];
|
|
82
|
+
const c4 = b[s + 4];
|
|
83
|
+
const c5 = b[s + 5];
|
|
84
|
+
const c6 = b[s + 6];
|
|
85
|
+
return (c0 | c1 | c2 | c3 | c4 | c5 | c6) < 128
|
|
86
|
+
? String.fromCharCode(c0, c1, c2, c3, c4, c5, c6)
|
|
87
|
+
: null;
|
|
88
|
+
}
|
|
89
|
+
case 8: {
|
|
90
|
+
const c0 = b[s];
|
|
91
|
+
const c1 = b[s + 1];
|
|
92
|
+
const c2 = b[s + 2];
|
|
93
|
+
const c3 = b[s + 3];
|
|
94
|
+
const c4 = b[s + 4];
|
|
95
|
+
const c5 = b[s + 5];
|
|
96
|
+
const c6 = b[s + 6];
|
|
97
|
+
const c7 = b[s + 7];
|
|
98
|
+
return (c0 | c1 | c2 | c3 | c4 | c5 | c6 | c7) < 128
|
|
99
|
+
? String.fromCharCode(c0, c1, c2, c3, c4, c5, c6, c7)
|
|
100
|
+
: null;
|
|
101
|
+
}
|
|
102
|
+
default:
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
exports.SHORT_STRING_MAX_LENGTH = 8;
|
|
108
|
+
|
|
109
|
+
// decode() for values of at most SHORT_STRING_MAX_LENGTH bytes, with the
|
|
110
|
+
// ASCII shortcut in front of the native slice; kept apart from decode() so
|
|
111
|
+
// the long-value path stays as small and inlinable as before
|
|
112
|
+
exports.decodeShort = function (buffer, encoding, start, end) {
|
|
113
|
+
if (hasFastSlices && start >= 0 && start <= end && end <= buffer.length) {
|
|
114
|
+
switch (encoding) {
|
|
115
|
+
case 'utf8':
|
|
116
|
+
case 'utf-8': {
|
|
117
|
+
const short = shortAscii(buffer, start, end - start);
|
|
118
|
+
return short !== null ? short : buffer.utf8Slice(start, end);
|
|
119
|
+
}
|
|
120
|
+
case 'latin1':
|
|
121
|
+
case 'binary': {
|
|
122
|
+
const short = shortAscii(buffer, start, end - start);
|
|
123
|
+
return short !== null ? short : buffer.latin1Slice(start, end);
|
|
124
|
+
}
|
|
125
|
+
case 'ascii': {
|
|
126
|
+
const short = shortAscii(buffer, start, end - start);
|
|
127
|
+
return short !== null ? short : buffer.asciiSlice(start, end);
|
|
128
|
+
}
|
|
129
|
+
default:
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return exports.decode(buffer, encoding, start, end);
|
|
134
|
+
};
|
|
135
|
+
|
|
22
136
|
exports.decode = function (buffer, encoding, start, end, options) {
|
|
23
137
|
if (hasFastSlices) {
|
|
24
138
|
// replicate buffer.toString() bounds coercion exactly (the *Slice
|
|
@@ -5,6 +5,7 @@ const Charsets = require('../constants/charsets.js');
|
|
|
5
5
|
const helpers = require('../helpers');
|
|
6
6
|
const genFunc = require('generate-function');
|
|
7
7
|
const parserCache = require('./parser_cache.js');
|
|
8
|
+
const LocalDate = require('./local_date.js');
|
|
8
9
|
|
|
9
10
|
const typeNames = [];
|
|
10
11
|
for (const t in Types) {
|
|
@@ -139,6 +140,9 @@ function compile(fields, options, config) {
|
|
|
139
140
|
parserFn('this[`wrap${i}`] = wrap(fields[i], _this);');
|
|
140
141
|
parserFn('}');
|
|
141
142
|
}
|
|
143
|
+
if (fields.some((field) => LocalDate.usesLocalDate(field, options, config))) {
|
|
144
|
+
parserFn('LocalDate.checkTimezone();');
|
|
145
|
+
}
|
|
142
146
|
parserFn('}');
|
|
143
147
|
|
|
144
148
|
// next method
|
|
@@ -207,10 +211,7 @@ function compile(fields, options, config) {
|
|
|
207
211
|
parserFn.toString()
|
|
208
212
|
);
|
|
209
213
|
}
|
|
210
|
-
|
|
211
|
-
return parserFn.toFunction({ wrap });
|
|
212
|
-
}
|
|
213
|
-
return parserFn.toFunction();
|
|
214
|
+
return parserFn.toFunction({ wrap, LocalDate });
|
|
214
215
|
}
|
|
215
216
|
|
|
216
217
|
function getTextParser(fields, options, config) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mysql2",
|
|
3
|
-
"version": "3.24.4
|
|
3
|
+
"version": "3.24.4",
|
|
4
4
|
"description": "fast mysql driver. Implements core protocol, prepared statements, ssl and compression in native JS",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"typings": "typings/mysql/index",
|
|
@@ -20,6 +20,11 @@
|
|
|
20
20
|
"test:coverage": "c8 npm test",
|
|
21
21
|
"test:build": "rollup -c",
|
|
22
22
|
"typecheck": "cd \"test/tsc-build\" && tsc -p \"tsconfig.json\" && cd .. && tsc -p \"tsconfig.json\" --noEmit",
|
|
23
|
+
"src:typecheck": "tsc -p src/tsconfig.json --noEmit && tsc -p tools/src/tsconfig.json",
|
|
24
|
+
"src:build": "tsx tools/src/clean.mts && tsc -p src/tsconfig.json",
|
|
25
|
+
"src:test": "npm run src:build && tsx tools/src/prepare-tests.mts && poku --config=tools/src/poku.config.mts",
|
|
26
|
+
"src:test:bun": "npm run src:build && tsx tools/src/prepare-tests.mts && bun poku --config=tools/src/poku.config.mts",
|
|
27
|
+
"src:test:deno": "npm run src:build && tsx tools/src/prepare-tests.mts && deno run -A npm:poku --config=tools/src/poku.config.mts",
|
|
23
28
|
"benchmark": "node ./benchmarks/benchmark.js",
|
|
24
29
|
"wait-port": "wait-on"
|
|
25
30
|
},
|