@depup/mysql2 3.21.1-depup.0 → 3.23.1-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.
Files changed (50) hide show
  1. package/README.md +9 -3
  2. package/changes.json +8 -3
  3. package/lib/base/connection.js +8 -2
  4. package/lib/base/pool.js +35 -1
  5. package/lib/commands/auth_switch.js +22 -0
  6. package/lib/commands/client_handshake.js +32 -5
  7. package/lib/commands/execute.js +5 -3
  8. package/lib/commands/index.js +2 -0
  9. package/lib/commands/ping.js +1 -1
  10. package/lib/commands/prepare.js +10 -2
  11. package/lib/commands/query.js +2 -1
  12. package/lib/commands/reset_connection.js +29 -0
  13. package/lib/compressed_protocol.js +1 -1
  14. package/lib/connection_config.js +3 -0
  15. package/lib/constants/charset_encodings.js +25 -1
  16. package/lib/constants/commands.js +1 -0
  17. package/lib/constants/mariadb_client.js +19 -0
  18. package/lib/packets/binary_row.js +2 -2
  19. package/lib/packets/binlog_query_statusvars.js +1 -1
  20. package/lib/packets/column_definition.js +35 -1
  21. package/lib/packets/encode_parameter.js +12 -2
  22. package/lib/packets/execute.js +8 -2
  23. package/lib/packets/handshake.js +13 -1
  24. package/lib/packets/handshake_response.js +5 -1
  25. package/lib/packets/index.js +2 -0
  26. package/lib/packets/packet.js +51 -3
  27. package/lib/packets/reset_connection.js +17 -0
  28. package/lib/packets/resultset_header.js +1 -1
  29. package/lib/packets/ssl_request.js +6 -1
  30. package/lib/packets/text_row.js +1 -1
  31. package/lib/parsers/binary_parser.js +45 -6
  32. package/lib/parsers/parser_cache.js +3 -0
  33. package/lib/parsers/static_binary_parser.js +37 -2
  34. package/lib/parsers/static_text_parser.js +21 -2
  35. package/lib/parsers/text_parser.js +15 -9
  36. package/lib/pool_config.js +4 -0
  37. package/lib/promise/capture_local_err.js +25 -0
  38. package/lib/promise/connection.js +47 -38
  39. package/lib/promise/make_done_cb.js +5 -8
  40. package/lib/promise/pool.js +11 -11
  41. package/lib/promise/pool_cluster.js +9 -4
  42. package/lib/promise/prepared_statement_info.js +5 -2
  43. package/package.json +14 -9
  44. package/promise.d.ts +2 -0
  45. package/promise.js +16 -17
  46. package/typings/mysql/lib/Connection.d.ts +21 -1
  47. package/typings/mysql/lib/Pool.d.ts +7 -0
  48. package/typings/mysql/lib/parsers/typeCast.d.ts +11 -0
  49. package/typings/mysql/lib/protocol/packets/FieldPacket.d.ts +11 -0
  50. package/typings/mysql/lib/protocol/sequences/Query.d.ts +7 -1
package/README.md CHANGED
@@ -13,10 +13,16 @@ npm install @depup/mysql2
13
13
 
14
14
  | Field | Value |
15
15
  |-------|-------|
16
- | Original | [mysql2](https://www.npmjs.com/package/mysql2) @ 3.21.1 |
17
- | Processed | 2026-04-10 |
16
+ | Original | [mysql2](https://www.npmjs.com/package/mysql2) @ 3.23.1 |
17
+ | Processed | 2026-07-21 |
18
18
  | Smoke test | passed |
19
- | Deps updated | 0 |
19
+ | Deps updated | 1 |
20
+
21
+ ## Dependency Changes
22
+
23
+ | Dependency | From | To |
24
+ |------------|------|-----|
25
+ | iconv-lite | ^0.7.2 | ^0.7.3 |
20
26
 
21
27
  ---
22
28
 
package/changes.json CHANGED
@@ -1,5 +1,10 @@
1
1
  {
2
- "bumped": {},
3
- "timestamp": "2026-04-10T05:04:40.941Z",
4
- "totalUpdated": 0
2
+ "bumped": {
3
+ "iconv-lite": {
4
+ "from": "^0.7.2",
5
+ "to": "^0.7.3"
6
+ }
7
+ },
8
+ "timestamp": "2026-07-21T17:25:03.071Z",
9
+ "totalUpdated": 1
5
10
  }
@@ -87,6 +87,8 @@ class BaseConnection extends EventEmitter {
87
87
  },
88
88
  });
89
89
  this.serverCapabilityFlags = 0;
90
+ this._isMariaDB = false;
91
+ this._mariadbExtendedMetadata = false;
90
92
  this.authorized = false;
91
93
  this.sequenceId = 0;
92
94
  this.compressedSequenceId = 0;
@@ -186,7 +188,7 @@ class BaseConnection extends EventEmitter {
186
188
  });
187
189
  }
188
190
  }
189
- // in case there was no initial handshake but we need to read sting, assume it utf-8
191
+ // in case there was no initial handshake but we need to read string, assume it utf-8
190
192
  // most common example: "Too many connections" error ( packet is sent immediately on connection attempt, we don't know server encoding yet)
191
193
  // will be overwritten with actual encoding value as soon as server handshake packet is received
192
194
  this.serverEncoding = 'utf8';
@@ -285,7 +287,7 @@ class BaseConnection extends EventEmitter {
285
287
  bubbleErrorToConnection = true;
286
288
  }
287
289
  }
288
- // notify connection if some comands in the queue did not have callbacks
290
+ // notify connection if some commands in the queue did not have callbacks
289
291
  // or if this is pool connection ( so it can be removed from pool )
290
292
  if (bubbleErrorToConnection || this._pool) {
291
293
  this.emit('error', err);
@@ -928,6 +930,10 @@ class BaseConnection extends EventEmitter {
928
930
  return this.addCommand(new Commands.Ping(cb));
929
931
  }
930
932
 
933
+ reset(cb) {
934
+ return this.addCommand(new Commands.ResetConnection(cb));
935
+ }
936
+
931
937
  _registerSlave(opts, cb) {
932
938
  return this.addCommand(new Commands.RegisterSlave(opts, cb));
933
939
  }
package/lib/base/pool.js CHANGED
@@ -127,7 +127,37 @@ class BasePool extends EventEmitter {
127
127
  cb = this._connectionQueue.shift();
128
128
  process.nextTick(this.getConnection.bind(this, cb));
129
129
  }
130
- } else if (this._connectionQueue.length) {
130
+ return;
131
+ }
132
+
133
+ // Reset connection state if configured
134
+ if (this.config.resetOnRelease && connection.reset) {
135
+ connection.reset((err) => {
136
+ if (err) {
137
+ // If reset fails, remove connection from pool
138
+ connection._pool = null;
139
+ spliceConnection(this._allConnections, connection);
140
+ connection.destroy();
141
+
142
+ // Try to create a new connection for waiting callbacks
143
+ if (this._connectionQueue.length) {
144
+ cb = this._connectionQueue.shift();
145
+ process.nextTick(this.getConnection.bind(this, cb));
146
+ }
147
+ return;
148
+ }
149
+
150
+ // Reset successful, continue with normal release flow
151
+ this._handleSuccessfulRelease(connection);
152
+ });
153
+ } else {
154
+ this._handleSuccessfulRelease(connection);
155
+ }
156
+ }
157
+
158
+ _handleSuccessfulRelease(connection) {
159
+ let cb;
160
+ if (this._connectionQueue.length) {
131
161
  cb = this._connectionQueue.shift();
132
162
  process.nextTick(() => {
133
163
  connection._released = false;
@@ -155,6 +185,10 @@ class BasePool extends EventEmitter {
155
185
  }
156
186
  };
157
187
  }
188
+ while (this._connectionQueue.length > 0) {
189
+ const queuedCallback = this._connectionQueue.shift();
190
+ process.nextTick(() => queuedCallback(new Error('Pool is closed.')));
191
+ }
158
192
  let calledBack = false;
159
193
  let closedConnections = 0;
160
194
  let connection;
@@ -72,6 +72,28 @@ function authSwitchRequest(packet, connection, command) {
72
72
  return;
73
73
  }
74
74
 
75
+ if (pluginName === 'mysql_clear_password') {
76
+ const hasCustomPlugin =
77
+ connection.config.authPlugins &&
78
+ Object.prototype.hasOwnProperty.call(
79
+ connection.config.authPlugins,
80
+ 'mysql_clear_password'
81
+ );
82
+ if (!hasCustomPlugin && !connection.config.enableCleartextPlugin) {
83
+ const err = new Error(
84
+ 'Server requested authentication using mysql_clear_password, ' +
85
+ 'which sends the password in plaintext over the network and is ' +
86
+ 'disabled by default. To enable it, set the `enableCleartextPlugin` ' +
87
+ 'option to `true` in your connection configuration, or provide a ' +
88
+ 'custom `mysql_clear_password` auth plugin via the `authPlugins` ' +
89
+ 'option. Only use this over a secure connection (TLS/SSL).'
90
+ );
91
+ err.code = 'MYSQL_CLEAR_PASSWORD_NOT_ENABLED';
92
+ err.fatal = true;
93
+ throw err;
94
+ }
95
+ }
96
+
75
97
  const authPlugin = getAuthPlugin(pluginName, connection);
76
98
  if (!authPlugin) {
77
99
  throw new Error(
@@ -1,5 +1,5 @@
1
1
  // This file was modified by Oracle on June 17, 2021.
2
- // Handshake errors are now maked as fatal and the corresponding events are
2
+ // Handshake errors are now marked as fatal and the corresponding events are
3
3
  // emitted in the command instance itself.
4
4
  // Modifications copyright (c) 2021, Oracle and/or its affiliates.
5
5
 
@@ -13,6 +13,7 @@
13
13
  const Command = require('./command.js');
14
14
  const Packets = require('../packets/index.js');
15
15
  const ClientConstants = require('../constants/client.js');
16
+ const MariaDBClientConstants = require('../constants/mariadb_client.js');
16
17
  const CharsetToEncoding = require('../constants/charset_encodings.js');
17
18
  const auth41 = require('../auth_41.js');
18
19
  const { getAuthPlugin } = require('./auth_switch.js');
@@ -35,6 +36,7 @@ class ClientHandshake extends Command {
35
36
  super();
36
37
  this.handshake = null;
37
38
  this.clientFlags = clientFlags;
39
+ this.mariadbExtendedClientFlags = 0;
38
40
  this.authenticationFactor = 0;
39
41
  }
40
42
 
@@ -45,7 +47,8 @@ class ClientHandshake extends Command {
45
47
  sendSSLRequest(connection) {
46
48
  const sslRequest = new Packets.SSLRequest(
47
49
  this.clientFlags,
48
- connection.config.charsetNumber
50
+ connection.config.charsetNumber,
51
+ this.mariadbExtendedClientFlags
49
52
  );
50
53
  connection.writePacket(sslRequest.toPacket());
51
54
  }
@@ -105,7 +108,9 @@ class ClientHandshake extends Command {
105
108
  const canUseDirectAuth =
106
109
  !hasCustomAuthPlugin &&
107
110
  !hasLegacyAuthSwitchHandler &&
108
- this.canUseAuthMethodDirectly(serverAuthMethod, isSecureConnection);
111
+ this.canUseAuthMethodDirectly(serverAuthMethod, isSecureConnection) &&
112
+ (serverAuthMethod !== 'mysql_clear_password' ||
113
+ connection.config.enableCleartextPlugin);
109
114
 
110
115
  const clientAuthMethod = canUseDirectAuth
111
116
  ? serverAuthMethod
@@ -128,6 +133,7 @@ class ClientHandshake extends Command {
128
133
 
129
134
  const handshakeResponse = new Packets.HandshakeResponse({
130
135
  flags: this.clientFlags,
136
+ mariadbExtendedClientFlags: this.mariadbExtendedClientFlags,
131
137
  user: this.user,
132
138
  database: this.database,
133
139
  password: this.password,
@@ -263,6 +269,26 @@ class ClientHandshake extends Command {
263
269
  connection.serverCapabilityFlags = this.handshake.capabilityFlags;
264
270
  connection.serverEncoding = CharsetToEncoding[this.handshake.characterSet];
265
271
  connection.connectionId = this.handshake.connectionId;
272
+ // MariaDB needs a few protocol deviations, e.g. it rejects the JSON
273
+ // parameter type in COM_STMT_EXECUTE
274
+ connection._isMariaDB = /mariadb/i.test(this.handshake.serverVersion);
275
+ // If a MariaDB server offers extended column metadata, request it so
276
+ // MariaDB-specific column types (UUID, INET4, INET6, VECTOR and the JSON
277
+ // alias) can be identified. The server only reads the extended client
278
+ // capability bytes when the client leaves CLIENT_MYSQL (LONG_PASSWORD)
279
+ // unset, mirroring the server side of the negotiation.
280
+ if (
281
+ this.handshake.mariadbExtendedCapabilityFlags &
282
+ MariaDBClientConstants.MARIADB_CLIENT_EXTENDED_METADATA
283
+ ) {
284
+ this.clientFlags &= ~ClientConstants.LONG_PASSWORD;
285
+ this.mariadbExtendedClientFlags |=
286
+ MariaDBClientConstants.MARIADB_CLIENT_EXTENDED_METADATA;
287
+ }
288
+ connection._mariadbExtendedMetadata = Boolean(
289
+ this.mariadbExtendedClientFlags &
290
+ MariaDBClientConstants.MARIADB_CLIENT_EXTENDED_METADATA
291
+ );
266
292
  const serverSSLSupport =
267
293
  this.handshake.capabilityFlags & ClientConstants.SSL;
268
294
  // multi factor authentication is enabled with the
@@ -339,8 +365,9 @@ class ClientHandshake extends Command {
339
365
  }
340
366
  return ClientHandshake.prototype.handshakeResult;
341
367
  } catch (err) {
342
- // Authentication errors are fatal
343
- err.code = 'AUTH_SWITCH_PLUGIN_ERROR';
368
+ if (!err.code) {
369
+ err.code = 'AUTH_SWITCH_PLUGIN_ERROR';
370
+ }
344
371
  err.fatal = true;
345
372
 
346
373
  if (this.onResult) {
@@ -51,7 +51,8 @@ class Execute extends Command {
51
51
  connection.config.charsetNumber,
52
52
  connection.config.timezone,
53
53
  this._executeOptions.attributes,
54
- clientFlags
54
+ clientFlags,
55
+ connection._isMariaDB
55
56
  );
56
57
  //For reasons why this try-catch is here, please see
57
58
  // https://github.com/sidorares/node-mysql2/pull/689
@@ -72,12 +73,13 @@ class Execute extends Command {
72
73
  // disabling for now, but would be great to find reliable way to parse fields only once
73
74
  // fields reported by prepare can be empty at all or just incorrect - see #169
74
75
  //
75
- // perfomance optimisation: if we already have this field parsed in statement header, use one from header
76
+ // performance optimisation: if we already have this field parsed in statement header, use one from header
76
77
  // const field = this.statement.columns.length == this._fieldCount ?
77
78
  // this.statement.columns[this._receivedFieldsCount] : new Packets.ColumnDefinition(packet);
78
79
  const field = new Packets.ColumnDefinition(
79
80
  packet,
80
- connection.clientEncoding
81
+ connection.clientEncoding,
82
+ connection._mariadbExtendedMetadata
81
83
  );
82
84
  this._receivedFieldsCount++;
83
85
  this._fields[this._resultIndex].push(field);
@@ -10,6 +10,7 @@ const Ping = require('./ping.js');
10
10
  const RegisterSlave = require('./register_slave.js');
11
11
  const BinlogDump = require('./binlog_dump.js');
12
12
  const ChangeUser = require('./change_user.js');
13
+ const ResetConnection = require('./reset_connection.js');
13
14
  const Quit = require('./quit.js');
14
15
 
15
16
  module.exports = {
@@ -23,5 +24,6 @@ module.exports = {
23
24
  RegisterSlave,
24
25
  BinlogDump,
25
26
  ChangeUser,
27
+ ResetConnection,
26
28
  Quit,
27
29
  };
@@ -5,7 +5,7 @@ const CommandCode = require('../constants/commands');
5
5
  const Packet = require('../packets/packet');
6
6
 
7
7
  // TODO: time statistics?
8
- // usefull for queue size and network latency monitoring
8
+ // useful for queue size and network latency monitoring
9
9
  // store created,sent,reply timestamps
10
10
  class Ping extends Command {
11
11
  constructor(callback) {
@@ -87,7 +87,11 @@ class Prepare extends Command {
87
87
  }
88
88
  return this.prepareDone(connection);
89
89
  }
90
- const def = new Packets.ColumnDefinition(packet, connection.clientEncoding);
90
+ const def = new Packets.ColumnDefinition(
91
+ packet,
92
+ connection.clientEncoding,
93
+ connection._mariadbExtendedMetadata
94
+ );
91
95
  this.parameterDefinitions.push(def);
92
96
  if (this.parameterDefinitions.length === this.parameterCount) {
93
97
  return Prepare.prototype.parametersEOF;
@@ -99,7 +103,11 @@ class Prepare extends Command {
99
103
  if (packet.isEOF()) {
100
104
  return this.prepareDone(connection);
101
105
  }
102
- const def = new Packets.ColumnDefinition(packet, connection.clientEncoding);
106
+ const def = new Packets.ColumnDefinition(
107
+ packet,
108
+ connection.clientEncoding,
109
+ connection._mariadbExtendedMetadata
110
+ );
103
111
  this.fields.push(def);
104
112
  if (this.fields.length === this.fieldCount) {
105
113
  return Prepare.prototype.fieldsEOF;
@@ -199,7 +199,8 @@ class Query extends Command {
199
199
  if (this._fields[this._resultIndex].length !== this._fieldCount) {
200
200
  const field = new Packets.ColumnDefinition(
201
201
  packet,
202
- connection.clientEncoding
202
+ connection.clientEncoding,
203
+ connection._mariadbExtendedMetadata
203
204
  );
204
205
  this._fields[this._resultIndex].push(field);
205
206
  if (connection.config.debug) {
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ const Command = require('./command');
4
+ const Packets = require('../packets/index.js');
5
+
6
+ class ResetConnection extends Command {
7
+ constructor(callback) {
8
+ super();
9
+ this.onResult = callback;
10
+ }
11
+
12
+ start(packet, connection) {
13
+ const req = new Packets.ResetConnection();
14
+ connection.writePacket(req.toPacket());
15
+ return ResetConnection.prototype.resetConnectionResponse;
16
+ }
17
+
18
+ resetConnectionResponse(packet, connection) {
19
+ if (connection._statements) {
20
+ connection._statements.clear();
21
+ }
22
+ if (this.onResult) {
23
+ process.nextTick(this.onResult.bind(this, null));
24
+ }
25
+ return null;
26
+ }
27
+ }
28
+
29
+ module.exports = ResetConnection;
@@ -40,7 +40,7 @@ function handleCompressedPacket(packet) {
40
40
 
41
41
  if (deflatedLength !== 0) {
42
42
  connection.inflateQueue.push((task) => {
43
- zlib.inflate(body, (err, data) => {
43
+ zlib.inflate(body, { maxOutputLength: deflatedLength }, (err, data) => {
44
44
  if (err) {
45
45
  connection._handleNetworkError(err);
46
46
  return;
@@ -60,12 +60,14 @@ const validOptions = {
60
60
  uri: 1,
61
61
  user: 1,
62
62
  disableEval: 1,
63
+ enableCleartextPlugin: 1,
63
64
  // These options are used for Pool
64
65
  connectionLimit: 1,
65
66
  maxIdle: 1,
66
67
  idleTimeout: 1,
67
68
  Promise: 1,
68
69
  queueLimit: 1,
70
+ resetOnRelease: 1,
69
71
  waitForConnections: 1,
70
72
  jsonStrings: 1,
71
73
  gracefulEnd: 1,
@@ -152,6 +154,7 @@ class ConnectionConfig {
152
154
  options.nestTables === undefined ? undefined : options.nestTables;
153
155
  this.typeCast = options.typeCast === undefined ? true : options.typeCast;
154
156
  this.disableEval = Boolean(options.disableEval);
157
+ this.enableCleartextPlugin = Boolean(options.enableCleartextPlugin);
155
158
  if (this.timezone[0] === ' ') {
156
159
  // "+" is a url encoded char for space so it
157
160
  // gets translated to space when giving a
@@ -3,7 +3,7 @@
3
3
  // see tools/generate-charset-mapping.js
4
4
  // basicalliy result of "SHOW COLLATION" query
5
5
 
6
- module.exports = [
6
+ const encodings = [
7
7
  'utf8',
8
8
  'big5',
9
9
  'latin2',
@@ -315,3 +315,27 @@ module.exports = [
315
315
  'utf8',
316
316
  'utf8',
317
317
  ];
318
+
319
+ // MariaDB NO PAD collations mirror their PAD SPACE counterparts:
320
+ // collation id = PAD SPACE collation id + 1024
321
+ const padSpaceLength = encodings.length;
322
+ for (let id = 1025; id < 1024 + padSpaceLength; id++) {
323
+ encodings[id] = encodings[id - 1024];
324
+ }
325
+
326
+ // MariaDB UCA-14.0.0 (uca1400) collations use fixed 256-id blocks
327
+ // per character set, each covering the PAD SPACE and NO PAD variants
328
+ const uca1400Blocks = [
329
+ [2048, 'cesu8'], // utf8mb3
330
+ [2304, 'utf8'], // utf8mb4
331
+ [2560, 'ucs2'], // ucs2
332
+ [2816, 'utf16'], // utf16
333
+ [3072, 'utf32'], // utf32
334
+ ];
335
+ for (const [start, encoding] of uca1400Blocks) {
336
+ for (let id = start; id < start + 256; id++) {
337
+ encodings[id] = encoding;
338
+ }
339
+ }
340
+
341
+ module.exports = encodings;
@@ -32,5 +32,6 @@ module.exports = {
32
32
  STMT_FETCH: 0x1c,
33
33
  DAEMON: 0x1d, // deprecated
34
34
  BINLOG_DUMP_GTID: 0x1e,
35
+ RESET_CONNECTION: 0x1f, // introduced in 5.7.3
35
36
  UNKNOWN: 0xff, // bad!
36
37
  };
@@ -0,0 +1,19 @@
1
+ 'use strict';
2
+
3
+ // MariaDB-specific extended capability flags.
4
+ //
5
+ // MariaDB servers (10.2+) do not set the CLIENT_MYSQL (LONG_PASSWORD)
6
+ // capability and instead use 4 reserved bytes of the initial handshake packet
7
+ // to advertise these flags. Clients answer with their own extended
8
+ // capabilities in the last 4 reserved bytes of the handshake response
9
+ // (the server only reads them when the client leaves CLIENT_MYSQL unset).
10
+ //
11
+ // The flags below are the lower 32 bits shifted down, i.e.
12
+ // MARIADB_CLIENT_PROGRESS is documented as 1 << 32.
13
+ // https://mariadb.com/docs/server/reference/clientserver-protocol/1-connecting/connection
14
+ exports.MARIADB_CLIENT_PROGRESS = 0x00000001;
15
+ exports.MARIADB_CLIENT_COM_MULTI = 0x00000002; /* deprecated */
16
+ exports.MARIADB_CLIENT_STMT_BULK_OPERATIONS = 0x00000004;
17
+ exports.MARIADB_CLIENT_EXTENDED_METADATA = 0x00000008;
18
+ exports.MARIADB_CLIENT_CACHE_METADATA = 0x00000010;
19
+ exports.MARIADB_CLIENT_BULK_UNIT_RESULTS = 0x00000020;
@@ -12,7 +12,7 @@ class BinaryRow {
12
12
 
13
13
  static toPacket(columns, encoding) {
14
14
  // throw new Error('Not implemented');
15
- const sequenceId = 0; // TODO remove, this is calculated now in connecton
15
+ const sequenceId = 0; // TODO remove, this is calculated now in connection
16
16
  let length = 0;
17
17
  columns.forEach((val) => {
18
18
  if (val === null || typeof val === 'undefined') {
@@ -82,7 +82,7 @@ binaryReader[2] = Packet.prototype.readInt16; // short
82
82
  binaryReader[3] = Packet.prototype.readInt32; // long
83
83
  binaryReader[4] = Packet.prototype.readFloat; // float
84
84
  binaryReader[5] = Packet.prototype.readDouble; // double
85
- binaryReader[6] = Packet.prototype.assertInvalid; // null, should be skipped vie null bitmap
85
+ binaryReader[6] = Packet.prototype.assertInvalid; // null, should be skipped via null bitmap
86
86
  binaryReader[7] = Packet.prototype.readTimestamp; // timestamp, http://dev.mysql.com/doc/internals/en/prepared-statements.html#packet-ProtocolBinary::MYSQL_TYPE_TIMESTAMP
87
87
  binaryReader[8] = Packet.prototype.readInt64; // long long
88
88
  binaryReader[9] = Packet.prototype.readInt32; // int24
@@ -31,7 +31,7 @@ module.exports = function parseStatusVars(buffer) {
31
31
  offset += 4;
32
32
  break;
33
33
  case keys.SQL_MODE:
34
- // value is 8 bytes, but all dcumented flags are in first 4 bytes
34
+ // value is 8 bytes, but all documented flags are in first 4 bytes
35
35
  result.sqlMode = buffer.readUInt32LE(offset);
36
36
  offset += 8;
37
37
  break;
@@ -19,7 +19,7 @@ const fields = ['catalog', 'schema', 'table', 'orgTable', 'name', 'orgName'];
19
19
  // see https://github.com/sidorares/node-mysql2/pull/137
20
20
  //
21
21
  class ColumnDefinition {
22
- constructor(packet, clientEncoding) {
22
+ constructor(packet, clientEncoding, mariadbExtendedMetadata) {
23
23
  this._buf = packet.buffer;
24
24
  this._clientEncoding = clientEncoding;
25
25
  this._catalogLength = packet.readLengthCodedNumber();
@@ -41,6 +41,14 @@ class ColumnDefinition {
41
41
  this._orgNameLength = packet.readLengthCodedNumber();
42
42
  this._orgNameStart = packet.offset;
43
43
  packet.offset += this._orgNameLength;
44
+ // MariaDB-specific column types (UUID, INET4, INET6, VECTOR and the JSON
45
+ // alias) are sent as standard string/blob types on the wire; the real
46
+ // type is only visible here, in the extended metadata block negotiated
47
+ // via the MARIADB_CLIENT_EXTENDED_METADATA capability (parsed out of
48
+ // line and with prototype defaults to keep this hot constructor small)
49
+ if (mariadbExtendedMetadata) {
50
+ this._parseMariadbExtendedMetadata(packet);
51
+ }
44
52
  packet.skip(1); // length of the following fields (always 0x0c)
45
53
  this.characterSet = packet.readInt16();
46
54
  this.encoding = CharsetToEncoding[this.characterSet];
@@ -57,6 +65,27 @@ class ColumnDefinition {
57
65
  this.decimals = packet.readInt8();
58
66
  }
59
67
 
68
+ _parseMariadbExtendedMetadata(packet) {
69
+ // length-encoded block of { int<1> id, string<lenenc> value } pairs
70
+ const extendedMetadataLength = packet.readLengthCodedNumber() || 0;
71
+ const extendedMetadataEnd = Math.min(
72
+ packet.offset + extendedMetadataLength,
73
+ packet.end
74
+ );
75
+ while (packet.offset < extendedMetadataEnd) {
76
+ const id = packet.readInt8();
77
+ const value = packet.readLengthCodedString('ascii');
78
+ if (id === 0) {
79
+ this.extendedTypeName = value; // e.g. 'uuid', 'inet4', 'inet6', 'point'
80
+ } else if (id === 1) {
81
+ this.extendedFormat = value; // e.g. 'json'
82
+ }
83
+ }
84
+ // a malformed entry could read past the block; resynchronise so the
85
+ // fixed column definition fields that follow parse from the right offset
86
+ packet.offset = extendedMetadataEnd;
87
+ }
88
+
60
89
  inspect() {
61
90
  return {
62
91
  catalog: this.catalog,
@@ -288,4 +317,9 @@ addString('table');
288
317
  addString('orgTable');
289
318
  addString('orgName');
290
319
 
320
+ // MariaDB extended metadata defaults; instances only carry own values when
321
+ // the server actually tagged the column
322
+ ColumnDefinition.prototype.extendedTypeName = undefined;
323
+ ColumnDefinition.prototype.extendedFormat = undefined;
324
+
291
325
  module.exports = ColumnDefinition;
@@ -11,7 +11,7 @@ function isJSON(value) {
11
11
  );
12
12
  }
13
13
 
14
- function toParameter(value, encoding, timezone) {
14
+ function toParameter(value, encoding, timezone, jsonAsString) {
15
15
  let type = Types.VAR_STRING;
16
16
  let length;
17
17
  let writer = function (value) {
@@ -46,8 +46,18 @@ function toParameter(value, encoding, timezone) {
46
46
  };
47
47
  } else if (isJSON(value)) {
48
48
  value = JSON.stringify(value);
49
- type = Types.JSON;
49
+ // MariaDB rejects the JSON parameter type with "Incorrect
50
+ // arguments to mysqld_stmt_execute"; it expects JSON values
51
+ // as plain strings
52
+ if (!jsonAsString) {
53
+ type = Types.JSON;
54
+ }
50
55
  } else if (Buffer.isBuffer(value)) {
56
+ // send buffers as BLOB so servers treat the value as binary data
57
+ // rather than a string in the connection charset (MariaDB converts
58
+ // string parameters when storing into binary columns such as
59
+ // VECTOR, corrupting the value)
60
+ type = Types.BLOB;
51
61
  length = Packet.lengthCodedNumberLength(value.length) + value.length;
52
62
  writer = Packet.prototype.writeLengthCodedBuffer;
53
63
  }
@@ -15,7 +15,8 @@ class Execute {
15
15
  charsetNumber,
16
16
  timezone,
17
17
  attributes,
18
- clientFlags
18
+ clientFlags,
19
+ jsonAsString
19
20
  ) {
20
21
  this.id = id;
21
22
  this.parameters = parameters;
@@ -23,6 +24,7 @@ class Execute {
23
24
  this.timezone = timezone;
24
25
  this.attributes = attributes;
25
26
  this.clientFlags = clientFlags || 0;
27
+ this.jsonAsString = jsonAsString || false;
26
28
  }
27
29
 
28
30
  static fromPacket(packet, encoding) {
@@ -34,6 +36,7 @@ class Execute {
34
36
  while (i < packet.end - 1) {
35
37
  if (
36
38
  (packet.buffer[i + 1] === Types.VAR_STRING ||
39
+ packet.buffer[i + 1] === Types.BLOB ||
37
40
  packet.buffer[i + 1] === Types.NULL ||
38
41
  packet.buffer[i + 1] === Types.DOUBLE ||
39
42
  packet.buffer[i + 1] === Types.TINY ||
@@ -54,6 +57,7 @@ class Execute {
54
57
  for (let i = packet.offset + 1; i < packet.end - 1; i++) {
55
58
  if (
56
59
  (packet.buffer[i] === Types.VAR_STRING ||
60
+ packet.buffer[i] === Types.BLOB ||
57
61
  packet.buffer[i] === Types.NULL ||
58
62
  packet.buffer[i] === Types.DOUBLE ||
59
63
  packet.buffer[i] === Types.TINY ||
@@ -72,6 +76,8 @@ class Execute {
72
76
  for (let i = 0; i < types.length; i++) {
73
77
  if (types[i] === Types.VAR_STRING) {
74
78
  values.push(packet.readLengthCodedString(encoding));
79
+ } else if (types[i] === Types.BLOB) {
80
+ values.push(packet.readLengthCodedBuffer());
75
81
  } else if (types[i] === Types.DOUBLE) {
76
82
  values.push(packet.readDouble());
77
83
  } else if (types[i] === Types.TINY) {
@@ -119,7 +125,7 @@ class Execute {
119
125
  const bindParams =
120
126
  numParams > 0
121
127
  ? this.parameters.map((v) =>
122
- toParameter(v, this.encoding, this.timezone)
128
+ toParameter(v, this.encoding, this.timezone, this.jsonAsString)
123
129
  )
124
130
  : [];
125
131
  const attrParams = attrNames.map((name) =>
@@ -16,6 +16,7 @@ class Handshake {
16
16
  this.characterSet = args.characterSet;
17
17
  this.statusFlags = args.statusFlags;
18
18
  this.authPluginName = args.authPluginName;
19
+ this.mariadbExtendedCapabilityFlags = args.mariadbExtendedCapabilityFlags;
19
20
  }
20
21
 
21
22
  setScrambleData(cb) {
@@ -79,9 +80,20 @@ class Handshake {
79
80
  args.authPluginDataLength = 0;
80
81
  packet.skip(1);
81
82
  }
82
- packet.skip(10);
83
+ packet.skip(6);
84
+ if (args.capabilityFlags & ClientConstants.LONG_PASSWORD) {
85
+ // CLIENT_MYSQL (LONG_PASSWORD) set: a MySQL server, the remaining
86
+ // 4 reserved bytes are filler
87
+ packet.skip(4);
88
+ args.mariadbExtendedCapabilityFlags = 0;
89
+ } else {
90
+ // MariaDB servers (10.2+) leave CLIENT_MYSQL unset and use these
91
+ // 4 bytes to advertise MariaDB-specific capabilities
92
+ args.mariadbExtendedCapabilityFlags = packet.readInt32();
93
+ }
83
94
  } else {
84
95
  args.capabilityFlags = capabilityFlagsBuffer.readUInt16LE(0);
96
+ args.mariadbExtendedCapabilityFlags = 0;
85
97
  }
86
98
 
87
99
  const isSecureConnection =