@depup/mysql2 3.23.4-depup.0 → 3.24.0-depup.0

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.
@@ -10,7 +10,7 @@ const NativeBuffer = require('buffer').Buffer;
10
10
  const Long = require('long');
11
11
  const StringParser = require('../parsers/string.js');
12
12
  const Types = require('../constants/types.js');
13
- const INVALID_DATE = new Date(NaN);
13
+ const ZERO_DATE = '0000-00-00';
14
14
 
15
15
  // this is nearly duplicate of previous function so generated code is not slower
16
16
  // due to "if (dateStrings)" branching
@@ -70,6 +70,27 @@ function jsonBigNumberReviver(key, value, context) {
70
70
  const dot = '.'.charCodeAt(0);
71
71
  const exponent = 'e'.charCodeAt(0);
72
72
  const exponentCapital = 'E'.charCodeAt(0);
73
+ const colon = ':'.charCodeAt(0);
74
+
75
+ // '+HH:MM' / '-HH:MM' -> signed minutes, memoized on the last seen value
76
+ // (the timezone is a per-connection constant in practice)
77
+ let lastTimezone;
78
+ let lastTimezoneOffset = null;
79
+ function timezoneOffsetMinutes(timezone) {
80
+ if (timezone === lastTimezone) {
81
+ return lastTimezoneOffset;
82
+ }
83
+ let offset = null;
84
+ if (/^[+-]\d{2}:\d{2}$/.test(timezone)) {
85
+ offset =
86
+ (timezone[0] === '-' ? -1 : 1) *
87
+ (parseInt(timezone.substring(1, 3), 10) * 60 +
88
+ parseInt(timezone.substring(4), 10));
89
+ }
90
+ lastTimezone = timezone;
91
+ lastTimezoneOffset = offset;
92
+ return offset;
93
+ }
73
94
 
74
95
  class Packet {
75
96
  constructor(id, buffer, start, end) {
@@ -151,18 +172,14 @@ class Packet {
151
172
  readInt64JSNumber() {
152
173
  const word0 = this.readInt32();
153
174
  const word1 = this.readInt32();
154
- const l = new Long(word0, word1, true);
155
- return l.toNumber();
175
+ // same value Long.toNumber() would produce, without the allocation
176
+ return word0 + 0x100000000 * word1;
156
177
  }
157
178
 
158
179
  readSInt64JSNumber() {
159
180
  const word0 = this.readInt32();
160
181
  const word1 = this.readInt32();
161
- if (!(word1 & 0x80000000)) {
162
- return word0 + 0x100000000 * word1;
163
- }
164
- const l = new Long(word0, word1, false);
165
- return l.toNumber();
182
+ return word0 + 0x100000000 * (word1 | 0);
166
183
  }
167
184
 
168
185
  readInt64String() {
@@ -182,17 +199,21 @@ class Packet {
182
199
  readInt64() {
183
200
  const word0 = this.readInt32();
184
201
  const word1 = this.readInt32();
185
- const res = new Long(word0, word1, true);
186
- const resNumber = res.toNumber();
187
- return Number.isSafeInteger(resNumber) ? resNumber : res.toString();
202
+ const resNumber = word0 + 0x100000000 * word1;
203
+ if (Number.isSafeInteger(resNumber)) {
204
+ return resNumber;
205
+ }
206
+ return new Long(word0, word1, true).toString();
188
207
  }
189
208
 
190
209
  readSInt64() {
191
210
  const word0 = this.readInt32();
192
211
  const word1 = this.readInt32();
193
- const res = new Long(word0, word1, false);
194
- const resNumber = res.toNumber();
195
- return Number.isSafeInteger(resNumber) ? resNumber : res.toString();
212
+ const resNumber = word0 + 0x100000000 * (word1 | 0);
213
+ if (Number.isSafeInteger(resNumber)) {
214
+ return resNumber;
215
+ }
216
+ return new Long(word0, word1, false).toString();
196
217
  }
197
218
 
198
219
  isEOF() {
@@ -309,7 +330,7 @@ class Packet {
309
330
  // possible MySQL flavours we still need to account for the
310
331
  // non-standard behaviour.
311
332
  if (y + m + d + H + M + S + ms === 0) {
312
- return INVALID_DATE;
333
+ return new Date(NaN);
313
334
  }
314
335
  if (timezone === 'Z') {
315
336
  return new Date(Date.UTC(y, m - 1, d, H, M, S, ms));
@@ -317,8 +338,10 @@ class Packet {
317
338
  return new Date(y, m - 1, d, H, M, S, ms);
318
339
  }
319
340
  let str = this.readDateTimeString(6, 'T', null);
320
- if (!str) {
321
- return INVALID_DATE;
341
+ if (str.startsWith(ZERO_DATE)) {
342
+ // fresh instance: Date is mutable, a shared invalid-date singleton
343
+ // could be corrupted by callers (see the parseDateTime tests)
344
+ return new Date(NaN);
322
345
  }
323
346
  if (str.length === 10) {
324
347
  str += 'T00:00:00';
@@ -335,7 +358,7 @@ class Packet {
335
358
  let M = 0;
336
359
  let S = 0;
337
360
  let ms = 0;
338
- let str;
361
+ let str = ZERO_DATE;
339
362
  if (length > 3) {
340
363
  y = this.readInt16();
341
364
  m = this.readInt8();
@@ -723,10 +746,75 @@ class Packet {
723
746
  }
724
747
 
725
748
  parseDateTime(timezone) {
726
- const str = this.readLengthCodedString('binary');
727
- if (str === null) {
749
+ const len = this.readLengthCodedNumber();
750
+ if (len === null) {
728
751
  return null;
729
752
  }
753
+ const b = this.buffer;
754
+ const s = this.offset;
755
+ // fast path for the wire format 'YYYY-MM-DD[ HH:MM:SS[.ffffff]]';
756
+ // parses digits straight from the buffer, avoiding both the intermediate
757
+ // string and the much slower Date-from-string constructor
758
+ if (
759
+ len >= 19 &&
760
+ b[s + 4] === minus &&
761
+ b[s + 7] === minus &&
762
+ b[s + 13] === colon &&
763
+ b[s + 16] === colon
764
+ ) {
765
+ const y =
766
+ (b[s] - 48) * 1000 +
767
+ (b[s + 1] - 48) * 100 +
768
+ (b[s + 2] - 48) * 10 +
769
+ (b[s + 3] - 48);
770
+ const mo = (b[s + 5] - 48) * 10 + (b[s + 6] - 48);
771
+ const d = (b[s + 8] - 48) * 10 + (b[s + 9] - 48);
772
+ if (mo === 0 || d === 0 || mo > 12 || d > 31) {
773
+ // matches Date-from-string behaviour for zero/invalid dates
774
+ this.offset += len;
775
+ return new Date(NaN);
776
+ }
777
+ if (y < 100) {
778
+ // new Date(y, ...) maps years 0-99 to 1900+y; let the string
779
+ // constructor handle this (out of MySQL's documented range anyway)
780
+ const str = StringParser.decode(b, 'binary', s, s + len);
781
+ this.offset += len;
782
+ return !timezone || timezone === 'local'
783
+ ? new Date(str)
784
+ : new Date(`${str}${timezone}`);
785
+ }
786
+ const h = (b[s + 11] - 48) * 10 + (b[s + 12] - 48);
787
+ const mi = (b[s + 14] - 48) * 10 + (b[s + 15] - 48);
788
+ const se = (b[s + 17] - 48) * 10 + (b[s + 18] - 48);
789
+ let ms = 0;
790
+ if (len > 20) {
791
+ // fractional part: Date resolution is ms, use the first 3 digits
792
+ let scale = 100;
793
+ for (let i = s + 20; i < s + len && scale >= 1; i++) {
794
+ ms += (b[i] - 48) * scale;
795
+ scale /= 10;
796
+ }
797
+ }
798
+ this.offset += len;
799
+ if (!timezone || timezone === 'local') {
800
+ return new Date(y, mo - 1, d, h, mi, se, ms);
801
+ }
802
+ const utc = Date.UTC(y, mo - 1, d, h, mi, se, ms);
803
+ if (timezone === 'Z') {
804
+ return new Date(utc);
805
+ }
806
+ const offsetMinutes = timezoneOffsetMinutes(timezone);
807
+ if (offsetMinutes !== null) {
808
+ return new Date(utc - offsetMinutes * 60000);
809
+ }
810
+ // not a fixed '+HH:MM' offset: preserve the legacy string behaviour
811
+ // (V8 accepts some timezone abbreviations in Date-from-string)
812
+ return new Date(
813
+ `${StringParser.decode(b, 'binary', s, s + len)}${timezone}`
814
+ );
815
+ }
816
+ const str = StringParser.decode(b, 'binary', s, s + len);
817
+ this.offset += len;
730
818
  if (!timezone || timezone === 'local') {
731
819
  return new Date(str);
732
820
  }
@@ -863,6 +951,44 @@ class Packet {
863
951
  this.offset += 8;
864
952
  }
865
953
 
954
+ writeFloat(n) {
955
+ this.buffer.writeFloatLE(n, this.offset);
956
+ this.offset += 4;
957
+ }
958
+
959
+ writeUIntLE(n, bytes) {
960
+ if (bytes === 8) {
961
+ this.buffer.writeBigUInt64LE(n, this.offset);
962
+ } else {
963
+ this.buffer.writeUIntLE(Number(n), this.offset, bytes);
964
+ }
965
+ this.offset += bytes;
966
+ }
967
+
968
+ // must match writeTime's choice of encoding byte for byte
969
+ static timeLength({ days, hours, minutes, seconds, microseconds }) {
970
+ if (!days && !hours && !minutes && !seconds && !microseconds) {
971
+ return 1;
972
+ }
973
+ return microseconds ? 13 : 9;
974
+ }
975
+
976
+ writeTime({ negative, days, hours, minutes, seconds, microseconds }) {
977
+ if (!days && !hours && !minutes && !seconds && !microseconds) {
978
+ this.writeInt8(0);
979
+ return;
980
+ }
981
+ this.writeInt8(microseconds ? 12 : 8);
982
+ this.writeInt8(negative ? 1 : 0);
983
+ this.writeInt32(days);
984
+ this.writeInt8(hours);
985
+ this.writeInt8(minutes);
986
+ this.writeInt8(seconds);
987
+ if (microseconds) {
988
+ this.writeInt32(microseconds);
989
+ }
990
+ }
991
+
866
992
  writeBuffer(b) {
867
993
  b.copy(this.buffer, this.offset);
868
994
  this.offset += b.length;
@@ -1009,6 +1135,9 @@ class Packet {
1009
1135
  }
1010
1136
 
1011
1137
  static MockBuffer() {
1138
+ if (Packet._mockBuffer) {
1139
+ return Packet._mockBuffer;
1140
+ }
1012
1141
  const noop = function () {};
1013
1142
  const res = Buffer.alloc(0);
1014
1143
  for (const op in NativeBuffer.prototype) {
@@ -1016,6 +1145,7 @@ class Packet {
1016
1145
  res[op] = noop;
1017
1146
  }
1018
1147
  }
1148
+ Packet._mockBuffer = res;
1019
1149
  return res;
1020
1150
  }
1021
1151
  }
@@ -5,7 +5,6 @@ const CommandCode = require('../constants/commands.js');
5
5
  const StringParser = require('../parsers/string.js');
6
6
  const CharsetToEncoding = require('../constants/charset_encodings.js');
7
7
  const ClientConstants = require('../constants/client.js');
8
- const Types = require('../constants/types.js');
9
8
  const { toParameter } = require('./encode_parameter.js');
10
9
 
11
10
  class Query {
@@ -17,85 +16,122 @@ class Query {
17
16
  this.clientFlags = clientFlags || 0;
18
17
  }
19
18
 
20
- serializeToBuffer(buffer) {
19
+ toPacket() {
21
20
  const useQueryAttributes =
22
21
  this.clientFlags & ClientConstants.CLIENT_QUERY_ATTRIBUTES;
23
- const sqlBuf = StringParser.encode(this.query, this.encoding);
24
- const packet = new Packet(0, buffer, 0, buffer.length);
25
- packet.offset = 4;
26
- packet.writeInt8(CommandCode.QUERY);
27
-
28
- if (useQueryAttributes) {
29
- const attrs = this.attributes;
30
- const names = attrs ? Object.keys(attrs) : [];
31
- const paramCount = names.length;
32
-
33
- packet.writeLengthCodedNumber(paramCount);
34
- packet.writeLengthCodedNumber(1); // parameter_set_count, always 1
35
-
36
- if (paramCount > 0) {
37
- const parameters = names.map((name) =>
38
- toParameter(attrs[name], this.encoding, 'local')
39
- );
22
+ const attributeCount =
23
+ useQueryAttributes && this.attributes
24
+ ? Object.keys(this.attributes).length
25
+ : 0;
40
26
 
41
- // null bitmap
42
- let bitmap = 0;
43
- let bitValue = 1;
44
- parameters.forEach((parameter) => {
45
- if (parameter.type === Types.NULL) {
46
- bitmap += bitValue;
47
- }
48
- bitValue *= 2;
49
- if (bitValue === 256) {
50
- packet.writeInt8(bitmap);
51
- bitmap = 0;
52
- bitValue = 1;
53
- }
54
- });
55
- if (bitValue !== 1) {
56
- packet.writeInt8(bitmap);
27
+ if (attributeCount === 0) {
28
+ // fast path: no attribute values to serialize, so the packet length is
29
+ // known upfront and the SQL can be encoded straight into the buffer
30
+ const headerLength = useQueryAttributes ? 7 : 5;
31
+ if (Buffer.isEncoding(this.encoding)) {
32
+ const sqlLength = Buffer.byteLength(this.query, this.encoding);
33
+ const length = headerLength + sqlLength;
34
+ const buffer = Buffer.allocUnsafe(length);
35
+ const packet = new Packet(0, buffer, 0, length);
36
+ packet.offset = 4;
37
+ packet.writeInt8(CommandCode.QUERY);
38
+ if (useQueryAttributes) {
39
+ packet.writeInt8(0); // parameter_count
40
+ packet.writeInt8(1); // parameter_set_count, always 1
57
41
  }
58
-
59
- packet.writeInt8(1); // new_params_bind_flag
60
-
61
- // types and names
62
- for (let i = 0; i < paramCount; i++) {
63
- packet.writeInt8(parameters[i].type);
64
- packet.writeInt8(0); // unsigned flag
65
- packet.writeLengthCodedString(names[i], this.encoding);
66
- }
67
-
68
- // values
69
- parameters.forEach((parameter) => {
70
- if (parameter.type !== Types.NULL) {
71
- parameter.writer.call(packet, parameter.value);
72
- }
73
- });
42
+ buffer.write(this.query, packet.offset, this.encoding);
43
+ packet.offset = length;
44
+ return packet;
74
45
  }
75
- }
76
-
77
- packet.writeBuffer(sqlBuf);
78
- return packet;
79
- }
80
-
81
- toPacket() {
82
- const useQueryAttributes =
83
- this.clientFlags & ClientConstants.CLIENT_QUERY_ATTRIBUTES;
84
-
85
- if (!useQueryAttributes) {
86
46
  const buf = StringParser.encode(this.query, this.encoding);
87
- const length = 5 + buf.length;
47
+ const length = headerLength + buf.length;
88
48
  const buffer = Buffer.allocUnsafe(length);
89
49
  const packet = new Packet(0, buffer, 0, length);
90
50
  packet.offset = 4;
91
51
  packet.writeInt8(CommandCode.QUERY);
52
+ if (useQueryAttributes) {
53
+ packet.writeInt8(0); // parameter_count
54
+ packet.writeInt8(1); // parameter_set_count, always 1
55
+ }
92
56
  packet.writeBuffer(buf);
93
57
  return packet;
94
58
  }
95
59
 
96
- // dry run to calculate required buffer length
97
- const p = this.serializeToBuffer(Packet.MockBuffer());
98
- return this.serializeToBuffer(Buffer.allocUnsafe(p.offset));
60
+ const names = Object.keys(this.attributes);
61
+ const parameters = new Array(attributeCount);
62
+ const nameBuffers = new Array(attributeCount);
63
+
64
+ // packet header, command, parameter count, parameter_set_count (always
65
+ // the single-byte form), null bitmap, new_params_bind_flag, type and
66
+ // unsigned byte per parameter
67
+ let length =
68
+ 5 +
69
+ Packet.lengthCodedNumberLength(attributeCount) +
70
+ 1 +
71
+ ((attributeCount + 7) >> 3) +
72
+ 1 +
73
+ attributeCount * 2;
74
+ for (let i = 0; i < attributeCount; i++) {
75
+ parameters[i] = toParameter(
76
+ this.attributes[names[i]],
77
+ this.encoding,
78
+ 'local'
79
+ );
80
+ const name = StringParser.encode(names[i], this.encoding);
81
+ nameBuffers[i] = name;
82
+ length += Packet.lengthCodedNumberLength(name.length) + name.length;
83
+ if (!parameters[i].isNull) {
84
+ length += parameters[i].length;
85
+ }
86
+ }
87
+ const sqlBuffer = StringParser.encode(this.query, this.encoding);
88
+ length += sqlBuffer.length;
89
+
90
+ const packet = new Packet(0, Buffer.allocUnsafe(length), 0, length);
91
+ packet.offset = 4;
92
+ packet.writeInt8(CommandCode.QUERY);
93
+ packet.writeLengthCodedNumber(attributeCount);
94
+ packet.writeLengthCodedNumber(1); // parameter_set_count, always 1
95
+
96
+ let bitmap = 0;
97
+ let bitValue = 1;
98
+ for (let i = 0; i < attributeCount; i++) {
99
+ if (parameters[i].isNull) {
100
+ bitmap |= bitValue;
101
+ }
102
+ bitValue *= 2;
103
+ if (bitValue === 256) {
104
+ packet.writeInt8(bitmap);
105
+ bitmap = 0;
106
+ bitValue = 1;
107
+ }
108
+ }
109
+ if (bitValue !== 1) {
110
+ packet.writeInt8(bitmap);
111
+ }
112
+
113
+ packet.writeInt8(1); // new_params_bind_flag
114
+
115
+ for (let i = 0; i < attributeCount; i++) {
116
+ packet.writeInt8(parameters[i].type);
117
+ packet.writeInt8(parameters[i].unsigned ? 0x80 : 0);
118
+ packet.writeLengthCodedBuffer(nameBuffers[i]);
119
+ }
120
+
121
+ for (let i = 0; i < attributeCount; i++) {
122
+ if (!parameters[i].isNull) {
123
+ parameters[i].writer.call(packet, parameters[i].value);
124
+ }
125
+ }
126
+
127
+ packet.writeBuffer(sqlBuffer);
128
+
129
+ if (packet.offset !== length) {
130
+ throw new Error(
131
+ `Internal error: COM_QUERY serialized ${packet.offset - 4} bytes, expected ${length - 4}`
132
+ );
133
+ }
134
+ return packet;
99
135
  }
100
136
  }
101
137