mysql2 3.20.0 → 3.20.1-canary.2bdc12a4

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.
@@ -43,11 +43,15 @@ class Execute extends Command {
43
43
  this._connection = connection;
44
44
  this.options = Object.assign({}, connection.config, this._executeOptions);
45
45
  this._setTimeout();
46
+ const clientFlags =
47
+ connection.config.clientFlags & (connection.serverCapabilityFlags || 0);
46
48
  const executePacket = new Packets.Execute(
47
49
  this.statement.id,
48
50
  this.parameters,
49
51
  connection.config.charsetNumber,
50
- connection.config.timezone
52
+ connection.config.timezone,
53
+ this._executeOptions.attributes,
54
+ clientFlags
51
55
  );
52
56
  //For reasons why this try-catch is here, please see
53
57
  // https://github.com/sidorares/node-mysql2/pull/689
@@ -53,9 +53,13 @@ class Query extends Command {
53
53
  this.options = Object.assign({}, connection.config, this._queryOptions);
54
54
  this._setTimeout();
55
55
 
56
+ const clientFlags =
57
+ connection.config.clientFlags & (connection.serverCapabilityFlags || 0);
56
58
  const cmdPacket = new Packets.Query(
57
59
  this.sql,
58
- connection.config.charsetNumber
60
+ connection.config.charsetNumber,
61
+ this._queryOptions.attributes,
62
+ clientFlags
59
63
  );
60
64
  connection.writePacket(cmdPacket.toPacket(1));
61
65
  return Query.prototype.resultsetHeader;
@@ -239,6 +239,7 @@ class ConnectionConfig {
239
239
  'TRANSACTIONS',
240
240
  'SESSION_TRACK',
241
241
  'CONNECT_ATTRS',
242
+ 'CLIENT_QUERY_ATTRIBUTES',
242
243
  ];
243
244
  if (options && options.multipleStatements) {
244
245
  defaultFlags.push('MULTI_STATEMENTS');
@@ -31,6 +31,7 @@ exports.CONNECT_ATTRS = 0x00100000; /* permits connection attributes */
31
31
  exports.PLUGIN_AUTH_LENENC_CLIENT_DATA = 0x00200000; /* Understands length-encoded integer for auth response data in Protocol::HandshakeResponse41. */
32
32
  exports.CAN_HANDLE_EXPIRED_PASSWORDS = 0x00400000; /* Announces support for expired password extension. */
33
33
  exports.SESSION_TRACK = 0x00800000; /* Can set SERVER_SESSION_STATE_CHANGED in the Status Flags and send session-state change data after a OK packet. */
34
+ exports.CLIENT_QUERY_ATTRIBUTES = 0x08000000; /* support query attributes in COM_QUERY and COM_STMT_EXECUTE */
34
35
 
35
36
  exports.SSL_VERIFY_SERVER_CERT = 0x40000000;
36
37
  exports.REMEMBER_OPTIONS = 0x80000000;
@@ -5,4 +5,5 @@ module.exports = {
5
5
  READ_ONLY: 1,
6
6
  FOR_UPDATE: 2,
7
7
  SCROLLABLE: 3,
8
+ PARAMETER_COUNT_AVAILABLE: 8,
8
9
  };
@@ -0,0 +1,69 @@
1
+ 'use strict';
2
+
3
+ const Types = require('../constants/types');
4
+ const Packet = require('../packets/packet');
5
+
6
+ function isJSON(value) {
7
+ return (
8
+ Array.isArray(value) ||
9
+ value.constructor === Object ||
10
+ (typeof value.toJSON === 'function' && !Buffer.isBuffer(value))
11
+ );
12
+ }
13
+
14
+ function toParameter(value, encoding, timezone) {
15
+ let type = Types.VAR_STRING;
16
+ let length;
17
+ let writer = function (value) {
18
+ // eslint-disable-next-line no-invalid-this
19
+ return Packet.prototype.writeLengthCodedString.call(this, value, encoding);
20
+ };
21
+ if (value !== null) {
22
+ switch (typeof value) {
23
+ case 'undefined':
24
+ throw new TypeError('Bind parameters must not contain undefined');
25
+
26
+ case 'number':
27
+ type = Types.DOUBLE;
28
+ length = 8;
29
+ writer = Packet.prototype.writeDouble;
30
+ break;
31
+
32
+ case 'boolean':
33
+ value = value | 0;
34
+ type = Types.TINY;
35
+ length = 1;
36
+ writer = Packet.prototype.writeInt8;
37
+ break;
38
+
39
+ case 'object':
40
+ if (Object.prototype.toString.call(value) === '[object Date]') {
41
+ type = Types.DATETIME;
42
+ length = 12;
43
+ writer = function (value) {
44
+ // eslint-disable-next-line no-invalid-this
45
+ return Packet.prototype.writeDate.call(this, value, timezone);
46
+ };
47
+ } else if (isJSON(value)) {
48
+ value = JSON.stringify(value);
49
+ type = Types.JSON;
50
+ } else if (Buffer.isBuffer(value)) {
51
+ length = Packet.lengthCodedNumberLength(value.length) + value.length;
52
+ writer = Packet.prototype.writeLengthCodedBuffer;
53
+ }
54
+ break;
55
+
56
+ default:
57
+ value = value.toString();
58
+ }
59
+ } else {
60
+ value = '';
61
+ type = Types.NULL;
62
+ }
63
+ if (!length) {
64
+ length = Packet.lengthCodedStringLength(value, encoding);
65
+ }
66
+ return { value, type, length, writer };
67
+ }
68
+
69
+ module.exports = { toParameter, isJSON };
@@ -2,83 +2,27 @@
2
2
 
3
3
  const CursorType = require('../constants/cursor');
4
4
  const CommandCodes = require('../constants/commands');
5
+ const ClientConstants = require('../constants/client');
5
6
  const Types = require('../constants/types');
6
7
  const Packet = require('../packets/packet');
7
8
  const CharsetToEncoding = require('../constants/charset_encodings.js');
8
-
9
- function isJSON(value) {
10
- return (
11
- Array.isArray(value) ||
12
- value.constructor === Object ||
13
- (typeof value.toJSON === 'function' && !Buffer.isBuffer(value))
14
- );
15
- }
16
-
17
- /**
18
- * Converts a value to an object describing type, String/Buffer representation and length
19
- * @param {*} value
20
- */
21
- function toParameter(value, encoding, timezone) {
22
- let type = Types.VAR_STRING;
23
- let length;
24
- let writer = function (value) {
25
- // eslint-disable-next-line no-invalid-this
26
- return Packet.prototype.writeLengthCodedString.call(this, value, encoding);
27
- };
28
- if (value !== null) {
29
- switch (typeof value) {
30
- case 'undefined':
31
- throw new TypeError('Bind parameters must not contain undefined');
32
-
33
- case 'number':
34
- type = Types.DOUBLE;
35
- length = 8;
36
- writer = Packet.prototype.writeDouble;
37
- break;
38
-
39
- case 'boolean':
40
- value = value | 0;
41
- type = Types.TINY;
42
- length = 1;
43
- writer = Packet.prototype.writeInt8;
44
- break;
45
-
46
- case 'object':
47
- if (Object.prototype.toString.call(value) === '[object Date]') {
48
- type = Types.DATETIME;
49
- length = 12;
50
- writer = function (value) {
51
- // eslint-disable-next-line no-invalid-this
52
- return Packet.prototype.writeDate.call(this, value, timezone);
53
- };
54
- } else if (isJSON(value)) {
55
- value = JSON.stringify(value);
56
- type = Types.JSON;
57
- } else if (Buffer.isBuffer(value)) {
58
- length = Packet.lengthCodedNumberLength(value.length) + value.length;
59
- writer = Packet.prototype.writeLengthCodedBuffer;
60
- }
61
- break;
62
-
63
- default:
64
- value = value.toString();
65
- }
66
- } else {
67
- value = '';
68
- type = Types.NULL;
69
- }
70
- if (!length) {
71
- length = Packet.lengthCodedStringLength(value, encoding);
72
- }
73
- return { value, type, length, writer };
74
- }
9
+ const { toParameter } = require('./encode_parameter.js');
75
10
 
76
11
  class Execute {
77
- constructor(id, parameters, charsetNumber, timezone) {
12
+ constructor(
13
+ id,
14
+ parameters,
15
+ charsetNumber,
16
+ timezone,
17
+ attributes,
18
+ clientFlags
19
+ ) {
78
20
  this.id = id;
79
21
  this.parameters = parameters;
80
22
  this.encoding = CharsetToEncoding[charsetNumber];
81
23
  this.timezone = timezone;
24
+ this.attributes = attributes;
25
+ this.clientFlags = clientFlags || 0;
82
26
  }
83
27
 
84
28
  static fromPacket(packet, encoding) {
@@ -145,39 +89,48 @@ class Execute {
145
89
  return { stmtId, flags, iterationCount, values };
146
90
  }
147
91
 
148
- toPacket() {
149
- // TODO: don't try to calculate packet length in advance, allocate some big buffer in advance (header + 256 bytes?)
150
- // and copy + reallocate if not enough
151
- // 0 + 4 - length, seqId
152
- // 4 + 1 - COM_EXECUTE
153
- // 5 + 4 - stmtId
154
- // 9 + 1 - flags
155
- // 10 + 4 - iteration-count (always 1)
156
- let length = 14;
157
- let parameters;
158
- if (this.parameters && this.parameters.length > 0) {
159
- length += Math.floor((this.parameters.length + 7) / 8);
160
- length += 1; // new-params-bound-flag
161
- length += 2 * this.parameters.length; // type byte for each parameter if new-params-bound-flag is set
162
- parameters = this.parameters.map((value) =>
163
- toParameter(value, this.encoding, this.timezone)
164
- );
165
- length += parameters.reduce(
166
- (accumulator, parameter) => accumulator + parameter.length,
167
- 0
168
- );
169
- }
170
- const buffer = Buffer.allocUnsafe(length);
171
- const packet = new Packet(0, buffer, 0, length);
92
+ _serializeToBuffer(buffer) {
93
+ const useQueryAttributes =
94
+ this.clientFlags & ClientConstants.CLIENT_QUERY_ATTRIBUTES;
95
+
96
+ const attrNames =
97
+ useQueryAttributes && this.attributes ? Object.keys(this.attributes) : [];
98
+ const numParams = this.parameters ? this.parameters.length : 0;
99
+ const numAttrs = attrNames.length;
100
+ const totalParams = numParams + numAttrs;
101
+
102
+ const packet = new Packet(0, buffer, 0, buffer.length);
172
103
  packet.offset = 4;
173
104
  packet.writeInt8(CommandCodes.STMT_EXECUTE);
174
105
  packet.writeInt32(this.id);
175
- packet.writeInt8(CursorType.NO_CURSOR); // flags
106
+
107
+ let cursorFlags = CursorType.NO_CURSOR;
108
+ if (useQueryAttributes) {
109
+ cursorFlags |= CursorType.PARAMETER_COUNT_AVAILABLE;
110
+ }
111
+ packet.writeInt8(cursorFlags);
176
112
  packet.writeInt32(1); // iteration-count, always 1
177
- if (parameters) {
113
+
114
+ if (useQueryAttributes) {
115
+ packet.writeLengthCodedNumber(totalParams);
116
+ }
117
+
118
+ if (totalParams > 0) {
119
+ const bindParams =
120
+ numParams > 0
121
+ ? this.parameters.map((v) =>
122
+ toParameter(v, this.encoding, this.timezone)
123
+ )
124
+ : [];
125
+ const attrParams = attrNames.map((name) =>
126
+ toParameter(this.attributes[name], this.encoding, this.timezone)
127
+ );
128
+ const allParams = bindParams.concat(attrParams);
129
+
130
+ // null bitmap
178
131
  let bitmap = 0;
179
132
  let bitValue = 1;
180
- parameters.forEach((parameter) => {
133
+ allParams.forEach((parameter) => {
181
134
  if (parameter.type === Types.NULL) {
182
135
  bitmap += bitValue;
183
136
  }
@@ -191,24 +144,34 @@ class Execute {
191
144
  if (bitValue !== 1) {
192
145
  packet.writeInt8(bitmap);
193
146
  }
194
- // TODO: explain meaning of the flag
195
- // afaik, if set n*2 bytes with type of parameter are sent before parameters
196
- // if not, previous execution types are used (TODO prooflink)
147
+
197
148
  packet.writeInt8(1); // new-params-bound-flag
198
- // Write parameter types
199
- parameters.forEach((parameter) => {
200
- packet.writeInt8(parameter.type); // field type
201
- packet.writeInt8(0); // parameter flag
202
- });
203
- // Write parameter values
204
- parameters.forEach((parameter) => {
149
+
150
+ // types (and names for attributes)
151
+ for (let i = 0; i < allParams.length; i++) {
152
+ packet.writeInt8(allParams[i].type);
153
+ packet.writeInt8(0); // unsigned flag
154
+ if (useQueryAttributes) {
155
+ const name = i < numParams ? '' : attrNames[i - numParams];
156
+ packet.writeLengthCodedString(name, this.encoding);
157
+ }
158
+ }
159
+
160
+ // values
161
+ allParams.forEach((parameter) => {
205
162
  if (parameter.type !== Types.NULL) {
206
163
  parameter.writer.call(packet, parameter.value);
207
164
  }
208
165
  });
209
166
  }
167
+
210
168
  return packet;
211
169
  }
170
+
171
+ toPacket() {
172
+ const p = this._serializeToBuffer(Packet.MockBuffer());
173
+ return this._serializeToBuffer(Buffer.allocUnsafe(p.offset));
174
+ }
212
175
  }
213
176
 
214
177
  module.exports = Execute;
@@ -4,24 +4,99 @@ const Packet = require('../packets/packet.js');
4
4
  const CommandCode = require('../constants/commands.js');
5
5
  const StringParser = require('../parsers/string.js');
6
6
  const CharsetToEncoding = require('../constants/charset_encodings.js');
7
+ const ClientConstants = require('../constants/client.js');
8
+ const Types = require('../constants/types.js');
9
+ const { toParameter } = require('./encode_parameter.js');
7
10
 
8
11
  class Query {
9
- constructor(sql, charsetNumber) {
12
+ constructor(sql, charsetNumber, attributes, clientFlags) {
10
13
  this.query = sql;
11
14
  this.charsetNumber = charsetNumber;
12
15
  this.encoding = CharsetToEncoding[charsetNumber];
16
+ this.attributes = attributes;
17
+ this.clientFlags = clientFlags || 0;
13
18
  }
14
19
 
15
- toPacket() {
16
- const buf = StringParser.encode(this.query, this.encoding);
17
- const length = 5 + buf.length;
18
- const buffer = Buffer.allocUnsafe(length);
19
- const packet = new Packet(0, buffer, 0, length);
20
+ serializeToBuffer(buffer) {
21
+ const useQueryAttributes =
22
+ 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);
20
25
  packet.offset = 4;
21
26
  packet.writeInt8(CommandCode.QUERY);
22
- packet.writeBuffer(buf);
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
+ );
40
+
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);
57
+ }
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
+ });
74
+ }
75
+ }
76
+
77
+ packet.writeBuffer(sqlBuf);
23
78
  return packet;
24
79
  }
80
+
81
+ toPacket() {
82
+ const useQueryAttributes =
83
+ this.clientFlags & ClientConstants.CLIENT_QUERY_ATTRIBUTES;
84
+
85
+ if (!useQueryAttributes) {
86
+ const buf = StringParser.encode(this.query, this.encoding);
87
+ const length = 5 + buf.length;
88
+ const buffer = Buffer.allocUnsafe(length);
89
+ const packet = new Packet(0, buffer, 0, length);
90
+ packet.offset = 4;
91
+ packet.writeInt8(CommandCode.QUERY);
92
+ packet.writeBuffer(buf);
93
+ return packet;
94
+ }
95
+
96
+ // dry run to calculate required buffer length
97
+ const p = this.serializeToBuffer(Packet.MockBuffer());
98
+ return this.serializeToBuffer(Buffer.allocUnsafe(p.offset));
99
+ }
25
100
  }
26
101
 
27
102
  module.exports = Query;
@@ -39,7 +39,7 @@ class ResultSetHeader {
39
39
  if (isSet('SESSION_TRACK') && packet.offset < packet.end) {
40
40
  this.info = packet.readLengthCodedString(encoding);
41
41
 
42
- if (this.serverStatus && ServerSatusFlags.SERVER_SESSION_STATE_CHANGED) {
42
+ if (this.serverStatus & ServerSatusFlags.SERVER_SESSION_STATE_CHANGED) {
43
43
  // session change info record - see
44
44
  // https://dev.mysql.com/doc/internals/en/packet-OK_Packet.html#cs-sect-packet-ok-sessioninfo
45
45
  let len =
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mysql2",
3
- "version": "3.20.0",
3
+ "version": "3.20.1-canary.2bdc12a4",
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",
@@ -8,6 +8,8 @@ import {
8
8
  Query as BaseQuery,
9
9
  QueryOptions,
10
10
  QueryError,
11
+ ExecuteValues,
12
+ QueryValues,
11
13
  } from './lib/protocol/sequences/Query.js';
12
14
  import {
13
15
  PoolCluster as BasePoolCluster,
@@ -35,6 +37,8 @@ export {
35
37
  PoolNamespace,
36
38
  QueryOptions,
37
39
  QueryError,
40
+ ExecuteValues,
41
+ QueryValues,
38
42
  PrepareStatementInfo,
39
43
  };
40
44
 
@@ -43,6 +43,13 @@ export interface QueryOptions {
43
43
  */
44
44
  values?: QueryValues;
45
45
 
46
+ /**
47
+ * Query attributes sent alongside the query (MySQL 8.0.25+).
48
+ * Requires the `component_query_attributes` server component to be read via
49
+ * `mysql_query_attribute_string()`.
50
+ */
51
+ attributes?: Record<string, string | number | boolean | null | Buffer | Date>;
52
+
46
53
  /**
47
54
  * This overrides the namedPlaceholders option set at the connection level.
48
55
  */