oceanbase 0.0.1

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 (124) hide show
  1. package/License +19 -0
  2. package/README.md +250 -0
  3. package/index.d.ts +1 -0
  4. package/index.js +77 -0
  5. package/lib/auth_41.js +95 -0
  6. package/lib/auth_plugins/caching_sha2_password.js +108 -0
  7. package/lib/auth_plugins/caching_sha2_password.md +18 -0
  8. package/lib/auth_plugins/index.js +8 -0
  9. package/lib/auth_plugins/mysql_clear_password.js +17 -0
  10. package/lib/auth_plugins/mysql_native_password.js +34 -0
  11. package/lib/auth_plugins/sha256_password.js +68 -0
  12. package/lib/base/connection.js +978 -0
  13. package/lib/base/pool.js +237 -0
  14. package/lib/base/pool_connection.js +70 -0
  15. package/lib/commands/auth_switch.js +111 -0
  16. package/lib/commands/binlog_dump.js +109 -0
  17. package/lib/commands/change_user.js +68 -0
  18. package/lib/commands/client_handshake.js +241 -0
  19. package/lib/commands/close_statement.js +18 -0
  20. package/lib/commands/command.js +54 -0
  21. package/lib/commands/execute.js +112 -0
  22. package/lib/commands/index.js +27 -0
  23. package/lib/commands/ping.js +36 -0
  24. package/lib/commands/prepare.js +143 -0
  25. package/lib/commands/query.js +366 -0
  26. package/lib/commands/quit.js +29 -0
  27. package/lib/commands/register_slave.js +27 -0
  28. package/lib/commands/server_handshake.js +203 -0
  29. package/lib/compressed_protocol.js +127 -0
  30. package/lib/connection.js +12 -0
  31. package/lib/connection_config.js +326 -0
  32. package/lib/constants/charset_encodings.js +316 -0
  33. package/lib/constants/charsets.js +317 -0
  34. package/lib/constants/client.js +40 -0
  35. package/lib/constants/commands.js +36 -0
  36. package/lib/constants/cursor.js +8 -0
  37. package/lib/constants/encoding_charset.js +50 -0
  38. package/lib/constants/errors.js +3973 -0
  39. package/lib/constants/field_flags.js +20 -0
  40. package/lib/constants/server_status.js +44 -0
  41. package/lib/constants/session_track.js +11 -0
  42. package/lib/constants/ssl_profiles.js +11 -0
  43. package/lib/constants/types.js +64 -0
  44. package/lib/create_connection.js +10 -0
  45. package/lib/create_pool.js +10 -0
  46. package/lib/create_pool_cluster.js +9 -0
  47. package/lib/helpers.js +86 -0
  48. package/lib/packet_parser.js +195 -0
  49. package/lib/packets/auth_next_factor.js +35 -0
  50. package/lib/packets/auth_switch_request.js +38 -0
  51. package/lib/packets/auth_switch_request_more_data.js +33 -0
  52. package/lib/packets/auth_switch_response.js +30 -0
  53. package/lib/packets/binary_row.js +95 -0
  54. package/lib/packets/binlog_dump.js +33 -0
  55. package/lib/packets/binlog_query_statusvars.js +115 -0
  56. package/lib/packets/change_user.js +97 -0
  57. package/lib/packets/close_statement.js +21 -0
  58. package/lib/packets/column_definition.js +291 -0
  59. package/lib/packets/execute.js +214 -0
  60. package/lib/packets/handshake.js +112 -0
  61. package/lib/packets/handshake_response.js +144 -0
  62. package/lib/packets/index.js +152 -0
  63. package/lib/packets/packet.js +931 -0
  64. package/lib/packets/prepare_statement.js +27 -0
  65. package/lib/packets/prepared_statement_header.js +16 -0
  66. package/lib/packets/query.js +27 -0
  67. package/lib/packets/register_slave.js +46 -0
  68. package/lib/packets/resultset_header.js +124 -0
  69. package/lib/packets/ssl_request.js +25 -0
  70. package/lib/packets/text_row.js +47 -0
  71. package/lib/parsers/binary_parser.js +235 -0
  72. package/lib/parsers/parser_cache.js +68 -0
  73. package/lib/parsers/static_binary_parser.js +213 -0
  74. package/lib/parsers/static_text_parser.js +152 -0
  75. package/lib/parsers/string.js +50 -0
  76. package/lib/parsers/text_parser.js +214 -0
  77. package/lib/pool.js +12 -0
  78. package/lib/pool_cluster.js +369 -0
  79. package/lib/pool_config.js +30 -0
  80. package/lib/pool_connection.js +12 -0
  81. package/lib/promise/connection.js +222 -0
  82. package/lib/promise/inherit_events.js +27 -0
  83. package/lib/promise/make_done_cb.js +19 -0
  84. package/lib/promise/pool.js +112 -0
  85. package/lib/promise/pool_cluster.js +54 -0
  86. package/lib/promise/pool_connection.js +19 -0
  87. package/lib/promise/prepared_statement_info.js +32 -0
  88. package/lib/results_stream.js +38 -0
  89. package/lib/server.js +37 -0
  90. package/package.json +80 -0
  91. package/promise.d.ts +131 -0
  92. package/promise.js +202 -0
  93. package/typings/mysql/LICENSE.txt +15 -0
  94. package/typings/mysql/index.d.ts +95 -0
  95. package/typings/mysql/info.txt +1 -0
  96. package/typings/mysql/lib/Auth.d.ts +30 -0
  97. package/typings/mysql/lib/Connection.d.ts +453 -0
  98. package/typings/mysql/lib/Pool.d.ts +69 -0
  99. package/typings/mysql/lib/PoolCluster.d.ts +90 -0
  100. package/typings/mysql/lib/PoolConnection.d.ts +10 -0
  101. package/typings/mysql/lib/Server.d.ts +11 -0
  102. package/typings/mysql/lib/constants/CharsetToEncoding.d.ts +8 -0
  103. package/typings/mysql/lib/constants/Charsets.d.ts +326 -0
  104. package/typings/mysql/lib/constants/Types.d.ts +70 -0
  105. package/typings/mysql/lib/constants/index.d.ts +5 -0
  106. package/typings/mysql/lib/parsers/ParserCache.d.ts +4 -0
  107. package/typings/mysql/lib/parsers/index.d.ts +18 -0
  108. package/typings/mysql/lib/parsers/typeCast.d.ts +54 -0
  109. package/typings/mysql/lib/protocol/packets/Field.d.ts +10 -0
  110. package/typings/mysql/lib/protocol/packets/FieldPacket.d.ts +27 -0
  111. package/typings/mysql/lib/protocol/packets/OkPacket.d.ts +23 -0
  112. package/typings/mysql/lib/protocol/packets/ProcedurePacket.d.ts +13 -0
  113. package/typings/mysql/lib/protocol/packets/ResultSetHeader.d.ts +18 -0
  114. package/typings/mysql/lib/protocol/packets/RowDataPacket.d.ts +9 -0
  115. package/typings/mysql/lib/protocol/packets/index.d.ts +28 -0
  116. package/typings/mysql/lib/protocol/packets/params/ErrorPacketParams.d.ts +6 -0
  117. package/typings/mysql/lib/protocol/packets/params/OkPacketParams.d.ts +9 -0
  118. package/typings/mysql/lib/protocol/sequences/ExecutableBase.d.ts +40 -0
  119. package/typings/mysql/lib/protocol/sequences/Prepare.d.ts +65 -0
  120. package/typings/mysql/lib/protocol/sequences/Query.d.ts +170 -0
  121. package/typings/mysql/lib/protocol/sequences/QueryableBase.d.ts +40 -0
  122. package/typings/mysql/lib/protocol/sequences/Sequence.d.ts +5 -0
  123. package/typings/mysql/lib/protocol/sequences/promise/ExecutableBase.d.ts +21 -0
  124. package/typings/mysql/lib/protocol/sequences/promise/QueryableBase.d.ts +21 -0
@@ -0,0 +1,978 @@
1
+ // This file was modified by Oracle on June 1, 2021.
2
+ // The changes involve new logic to handle an additional ERR Packet sent by
3
+ // the MySQL server when the connection is closed unexpectedly.
4
+ // Modifications copyright (c) 2021, Oracle and/or its affiliates.
5
+
6
+ // This file was modified by Oracle on June 17, 2021.
7
+ // The changes involve logic to ensure the socket connection is closed when
8
+ // there is a fatal error.
9
+ // Modifications copyright (c) 2021, Oracle and/or its affiliates.
10
+
11
+ // This file was modified by Oracle on September 21, 2021.
12
+ // The changes involve passing additional authentication factor passwords
13
+ // to the ChangeUser Command instance.
14
+ // Modifications copyright (c) 2021, Oracle and/or its affiliates.
15
+
16
+ 'use strict';
17
+
18
+ const Net = require('net');
19
+ const Tls = require('tls');
20
+ const Timers = require('timers');
21
+ const EventEmitter = require('events').EventEmitter;
22
+ const Readable = require('stream').Readable;
23
+ const Queue = require('denque');
24
+ const SqlString = require('sqlstring');
25
+ const { createLRU } = require('lru.min');
26
+ const PacketParser = require('../packet_parser.js');
27
+ const Packets = require('../packets/index.js');
28
+ const Commands = require('../commands/index.js');
29
+ const ConnectionConfig = require('../connection_config.js');
30
+ const CharsetToEncoding = require('../constants/charset_encodings.js');
31
+
32
+ let _connectionId = 0;
33
+
34
+ let convertNamedPlaceholders = null;
35
+
36
+ class BaseConnection extends EventEmitter {
37
+ constructor(opts) {
38
+ super();
39
+ this.config = opts.config;
40
+ // TODO: fill defaults
41
+ // if no params, connect to /var/lib/mysql/mysql.sock ( /tmp/mysql.sock on OSX )
42
+ // if host is given, connect to host:3306
43
+ // TODO: use `/usr/local/mysql/bin/mysql_config --socket` output? as default socketPath
44
+ // if there is no host/port and no socketPath parameters?
45
+ if (!opts.config.stream) {
46
+ if (opts.config.socketPath) {
47
+ this.stream = Net.connect(opts.config.socketPath);
48
+ } else {
49
+ this.stream = Net.connect(opts.config.port, opts.config.host);
50
+
51
+ // Optionally enable keep-alive on the socket.
52
+ if (this.config.enableKeepAlive) {
53
+ this.stream.on('connect', () => {
54
+ this.stream.setKeepAlive(true, this.config.keepAliveInitialDelay);
55
+ });
56
+ }
57
+
58
+ // Enable TCP_NODELAY flag. This is needed so that the network packets
59
+ // are sent immediately to the server
60
+ this.stream.setNoDelay(true);
61
+ }
62
+ // if stream is a function, treat it as "stream agent / factory"
63
+ } else if (typeof opts.config.stream === 'function') {
64
+ this.stream = opts.config.stream(opts);
65
+ } else {
66
+ this.stream = opts.config.stream;
67
+ }
68
+
69
+ this._internalId = _connectionId++;
70
+ this._commands = new Queue();
71
+ this._command = null;
72
+ this._paused = false;
73
+ this._paused_packets = new Queue();
74
+ this._statements = createLRU({
75
+ max: this.config.maxPreparedStatements,
76
+ onEviction: function (_, statement) {
77
+ statement.close();
78
+ },
79
+ });
80
+ this.serverCapabilityFlags = 0;
81
+ this.authorized = false;
82
+ this.sequenceId = 0;
83
+ this.compressedSequenceId = 0;
84
+ this.threadId = null;
85
+ this._handshakePacket = null;
86
+ this._fatalError = null;
87
+ this._protocolError = null;
88
+ this._outOfOrderPackets = [];
89
+ this.clientEncoding = CharsetToEncoding[this.config.charsetNumber];
90
+ this.stream.on('error', this._handleNetworkError.bind(this));
91
+ // see https://gist.github.com/khoomeister/4985691#use-that-instead-of-bind
92
+ this.packetParser = new PacketParser((p) => {
93
+ this.handlePacket(p);
94
+ });
95
+ this.stream.on('data', (data) => {
96
+ if (this.connectTimeout) {
97
+ Timers.clearTimeout(this.connectTimeout);
98
+ this.connectTimeout = null;
99
+ }
100
+ this.packetParser.execute(data);
101
+ });
102
+ this.stream.on('end', () => {
103
+ // emit the end event so that the pooled connection can close the connection
104
+ this.emit('end');
105
+ });
106
+ this.stream.on('close', () => {
107
+ // we need to set this flag everywhere where we want connection to close
108
+ if (this._closing) {
109
+ return;
110
+ }
111
+ if (!this._protocolError) {
112
+ // no particular error message before disconnect
113
+ this._protocolError = new Error(
114
+ 'Connection lost: The server closed the connection.'
115
+ );
116
+ this._protocolError.fatal = true;
117
+ this._protocolError.code = 'PROTOCOL_CONNECTION_LOST';
118
+ }
119
+ this._notifyError(this._protocolError);
120
+ });
121
+ let handshakeCommand;
122
+ if (!this.config.isServer) {
123
+ handshakeCommand = new Commands.ClientHandshake(this.config.clientFlags);
124
+ handshakeCommand.on('end', () => {
125
+ // this happens when handshake finishes early either because there was
126
+ // some fatal error or the server sent an error packet instead of
127
+ // an hello packet (for example, 'Too many connections' error)
128
+ if (
129
+ !handshakeCommand.handshake ||
130
+ this._fatalError ||
131
+ this._protocolError
132
+ ) {
133
+ return;
134
+ }
135
+ this._handshakePacket = handshakeCommand.handshake;
136
+ this.threadId = handshakeCommand.handshake.connectionId;
137
+ this.emit('connect', handshakeCommand.handshake);
138
+ });
139
+ handshakeCommand.on('error', (err) => {
140
+ this._closing = true;
141
+ this._notifyError(err);
142
+ });
143
+ this.addCommand(handshakeCommand);
144
+ }
145
+ // in case there was no initial handshake but we need to read sting, assume it utf-8
146
+ // most common example: "Too many connections" error ( packet is sent immediately on connection attempt, we don't know server encoding yet)
147
+ // will be overwritten with actual encoding value as soon as server handshake packet is received
148
+ this.serverEncoding = 'utf8';
149
+ if (this.config.connectTimeout) {
150
+ const timeoutHandler = this._handleTimeoutError.bind(this);
151
+ this.connectTimeout = Timers.setTimeout(
152
+ timeoutHandler,
153
+ this.config.connectTimeout
154
+ );
155
+ }
156
+ }
157
+
158
+ _addCommandClosedState(cmd) {
159
+ const err = new Error(
160
+ "Can't add new command when connection is in closed state"
161
+ );
162
+ err.fatal = true;
163
+ if (cmd.onResult) {
164
+ cmd.onResult(err);
165
+ } else {
166
+ this.emit('error', err);
167
+ }
168
+ }
169
+
170
+ _handleFatalError(err) {
171
+ err.fatal = true;
172
+ // stop receiving packets
173
+ this.stream.removeAllListeners('data');
174
+ this.addCommand = this._addCommandClosedState;
175
+ this.write = () => {
176
+ this.emit('error', new Error("Can't write in closed state"));
177
+ };
178
+ this._notifyError(err);
179
+ this._fatalError = err;
180
+ }
181
+
182
+ _handleNetworkError(err) {
183
+ if (this.connectTimeout) {
184
+ Timers.clearTimeout(this.connectTimeout);
185
+ this.connectTimeout = null;
186
+ }
187
+ // Do not throw an error when a connection ends with a RST,ACK packet
188
+ if (err.code === 'ECONNRESET' && this._closing) {
189
+ return;
190
+ }
191
+ this._handleFatalError(err);
192
+ }
193
+
194
+ _handleTimeoutError() {
195
+ if (this.connectTimeout) {
196
+ Timers.clearTimeout(this.connectTimeout);
197
+ this.connectTimeout = null;
198
+ }
199
+ this.stream.destroy && this.stream.destroy();
200
+ const err = new Error('connect ETIMEDOUT');
201
+ err.errorno = 'ETIMEDOUT';
202
+ err.code = 'ETIMEDOUT';
203
+ err.syscall = 'connect';
204
+ this._handleNetworkError(err);
205
+ }
206
+
207
+ // notify all commands in the queue and bubble error as connection "error"
208
+ // called on stream error or unexpected termination
209
+ _notifyError(err) {
210
+ if (this.connectTimeout) {
211
+ Timers.clearTimeout(this.connectTimeout);
212
+ this.connectTimeout = null;
213
+ }
214
+ // prevent from emitting 'PROTOCOL_CONNECTION_LOST' after EPIPE or ECONNRESET
215
+ if (this._fatalError) {
216
+ return;
217
+ }
218
+ let command;
219
+ // if there is no active command, notify connection
220
+ // if there are commands and all of them have callbacks, pass error via callback
221
+ let bubbleErrorToConnection = !this._command;
222
+ if (this._command && this._command.onResult) {
223
+ this._command.onResult(err);
224
+ this._command = null;
225
+ // connection handshake is special because we allow it to be implicit
226
+ // if error happened during handshake, but there are others commands in queue
227
+ // then bubble error to other commands and not to connection
228
+ } else if (
229
+ !(
230
+ this._command &&
231
+ this._command.constructor === Commands.ClientHandshake &&
232
+ this._commands.length > 0
233
+ )
234
+ ) {
235
+ bubbleErrorToConnection = true;
236
+ }
237
+ while ((command = this._commands.shift())) {
238
+ if (command.onResult) {
239
+ command.onResult(err);
240
+ } else {
241
+ bubbleErrorToConnection = true;
242
+ }
243
+ }
244
+ // notify connection if some comands in the queue did not have callbacks
245
+ // or if this is pool connection ( so it can be removed from pool )
246
+ if (bubbleErrorToConnection || this._pool) {
247
+ this.emit('error', err);
248
+ }
249
+ // close connection after emitting the event in case of a fatal error
250
+ if (err.fatal) {
251
+ this.close();
252
+ }
253
+ }
254
+
255
+ write(buffer) {
256
+ const result = this.stream.write(buffer, (err) => {
257
+ if (err) {
258
+ this._handleNetworkError(err);
259
+ }
260
+ });
261
+
262
+ if (!result) {
263
+ this.stream.emit('pause');
264
+ }
265
+ }
266
+
267
+ // http://dev.mysql.com/doc/internals/en/sequence-id.html
268
+ //
269
+ // The sequence-id is incremented with each packet and may wrap around.
270
+ // It starts at 0 and is reset to 0 when a new command
271
+ // begins in the Command Phase.
272
+ // http://dev.mysql.com/doc/internals/en/example-several-mysql-packets.html
273
+ _resetSequenceId() {
274
+ this.sequenceId = 0;
275
+ this.compressedSequenceId = 0;
276
+ }
277
+
278
+ _bumpCompressedSequenceId(numPackets) {
279
+ this.compressedSequenceId += numPackets;
280
+ this.compressedSequenceId %= 256;
281
+ }
282
+
283
+ _bumpSequenceId(numPackets) {
284
+ this.sequenceId += numPackets;
285
+ this.sequenceId %= 256;
286
+ }
287
+
288
+ writePacket(packet) {
289
+ const MAX_PACKET_LENGTH = 16777215;
290
+ const length = packet.length();
291
+ let chunk, offset, header;
292
+ if (length < MAX_PACKET_LENGTH) {
293
+ packet.writeHeader(this.sequenceId);
294
+ if (this.config.debug) {
295
+ console.log(
296
+ `${this._internalId} ${this.connectionId} <== ${this._command._commandName}#${this._command.stateName()}(${[this.sequenceId, packet._name, packet.length()].join(',')})`
297
+ );
298
+ console.log(
299
+ `${this._internalId} ${this.connectionId} <== ${packet.buffer.toString('hex')}`
300
+ );
301
+ }
302
+ this._bumpSequenceId(1);
303
+ this.write(packet.buffer);
304
+ } else {
305
+ if (this.config.debug) {
306
+ console.log(
307
+ `${this._internalId} ${this.connectionId} <== Writing large packet, raw content not written:`
308
+ );
309
+ console.log(
310
+ `${this._internalId} ${this.connectionId} <== ${this._command._commandName}#${this._command.stateName()}(${[this.sequenceId, packet._name, packet.length()].join(',')})`
311
+ );
312
+ }
313
+ for (offset = 4; offset < 4 + length; offset += MAX_PACKET_LENGTH) {
314
+ chunk = packet.buffer.slice(offset, offset + MAX_PACKET_LENGTH);
315
+ if (chunk.length === MAX_PACKET_LENGTH) {
316
+ header = Buffer.from([0xff, 0xff, 0xff, this.sequenceId]);
317
+ } else {
318
+ header = Buffer.from([
319
+ chunk.length & 0xff,
320
+ (chunk.length >> 8) & 0xff,
321
+ (chunk.length >> 16) & 0xff,
322
+ this.sequenceId,
323
+ ]);
324
+ }
325
+ this._bumpSequenceId(1);
326
+ this.write(header);
327
+ this.write(chunk);
328
+ }
329
+ }
330
+ }
331
+
332
+ // 0.11+ environment
333
+ startTLS(onSecure) {
334
+ if (this.config.debug) {
335
+ console.log('Upgrading connection to TLS');
336
+ }
337
+ const secureContext = Tls.createSecureContext({
338
+ ca: this.config.ssl.ca,
339
+ cert: this.config.ssl.cert,
340
+ ciphers: this.config.ssl.ciphers,
341
+ key: this.config.ssl.key,
342
+ passphrase: this.config.ssl.passphrase,
343
+ minVersion: this.config.ssl.minVersion,
344
+ maxVersion: this.config.ssl.maxVersion,
345
+ });
346
+ const rejectUnauthorized = this.config.ssl.rejectUnauthorized;
347
+ const verifyIdentity = this.config.ssl.verifyIdentity;
348
+ const servername = Net.isIP(this.config.host)
349
+ ? undefined
350
+ : this.config.host;
351
+
352
+ let secureEstablished = false;
353
+ this.stream.removeAllListeners('data');
354
+ const secureSocket = Tls.connect(
355
+ {
356
+ rejectUnauthorized,
357
+ requestCert: rejectUnauthorized,
358
+ checkServerIdentity: verifyIdentity
359
+ ? Tls.checkServerIdentity
360
+ : function () {
361
+ return undefined;
362
+ },
363
+ secureContext,
364
+ isServer: false,
365
+ socket: this.stream,
366
+ servername,
367
+ },
368
+ () => {
369
+ secureEstablished = true;
370
+ if (rejectUnauthorized) {
371
+ if (typeof servername === 'string' && verifyIdentity) {
372
+ const cert = secureSocket.getPeerCertificate(true);
373
+ const serverIdentityCheckError = Tls.checkServerIdentity(
374
+ servername,
375
+ cert
376
+ );
377
+ if (serverIdentityCheckError) {
378
+ onSecure(serverIdentityCheckError);
379
+ return;
380
+ }
381
+ }
382
+ }
383
+ onSecure();
384
+ }
385
+ );
386
+ // error handler for secure socket
387
+ secureSocket.on('error', (err) => {
388
+ if (secureEstablished) {
389
+ this._handleNetworkError(err);
390
+ } else {
391
+ onSecure(err);
392
+ }
393
+ });
394
+ secureSocket.on('data', (data) => {
395
+ this.packetParser.execute(data);
396
+ });
397
+ this.stream = secureSocket;
398
+ }
399
+
400
+ protocolError(message, code) {
401
+ // Starting with MySQL 8.0.24, if the client closes the connection
402
+ // unexpectedly, the server will send a last ERR Packet, which we can
403
+ // safely ignore.
404
+ // https://dev.mysql.com/worklog/task/?id=12999
405
+ if (this._closing) {
406
+ return;
407
+ }
408
+
409
+ const err = new Error(message);
410
+ err.fatal = true;
411
+ err.code = code || 'PROTOCOL_ERROR';
412
+ this.emit('error', err);
413
+ }
414
+
415
+ get state() {
416
+ // Error state has highest priority
417
+ if (this._fatalError || this._protocolError) {
418
+ return 'error';
419
+ }
420
+
421
+ // Closing state has second priority
422
+ if (this._closing || (this.stream && this.stream.destroyed)) {
423
+ return 'disconnected';
424
+ }
425
+
426
+ // Authenticated state has third priority
427
+ if (this.authorized) {
428
+ return 'authenticated';
429
+ }
430
+
431
+ // Connected state: handshake completed but not yet authorized
432
+ // This matches the original mysql driver's 'connected' state
433
+ if (this._handshakePacket) {
434
+ return 'connected';
435
+ }
436
+
437
+ // Protocol handshake state: connection established, handshake in progress
438
+ if (this.stream && !this.stream.destroyed) {
439
+ return 'protocol_handshake';
440
+ }
441
+
442
+ // Default: not connected
443
+ return 'disconnected';
444
+ }
445
+
446
+ get fatalError() {
447
+ return this._fatalError;
448
+ }
449
+
450
+ handlePacket(packet) {
451
+ if (this._paused) {
452
+ this._paused_packets.push(packet);
453
+ return;
454
+ }
455
+ if (this.config.debug) {
456
+ if (packet) {
457
+ console.log(
458
+ ` raw: ${packet.buffer
459
+ .slice(packet.offset, packet.offset + packet.length())
460
+ .toString('hex')}`
461
+ );
462
+ console.trace();
463
+ const commandName = this._command
464
+ ? this._command._commandName
465
+ : '(no command)';
466
+ const stateName = this._command
467
+ ? this._command.stateName()
468
+ : '(no command)';
469
+ console.log(
470
+ `${this._internalId} ${this.connectionId} ==> ${commandName}#${stateName}(${[packet.sequenceId, packet.type(), packet.length()].join(',')})`
471
+ );
472
+ }
473
+ }
474
+ if (!this._command) {
475
+ const marker = packet.peekByte();
476
+ // If it's an Err Packet, we should use it.
477
+ if (marker === 0xff) {
478
+ const error = Packets.Error.fromPacket(packet);
479
+ this.protocolError(error.message, error.code);
480
+ } else {
481
+ // Otherwise, it means it's some other unexpected packet.
482
+ this.protocolError(
483
+ 'Unexpected packet while no commands in the queue',
484
+ 'PROTOCOL_UNEXPECTED_PACKET'
485
+ );
486
+ }
487
+ this.close();
488
+ return;
489
+ }
490
+ if (packet) {
491
+ // Note: when server closes connection due to inactivity, Err packet ER_CLIENT_INTERACTION_TIMEOUT from MySQL 8.0.24, sequenceId will be 0
492
+ if (this.sequenceId !== packet.sequenceId) {
493
+ const err = new Error(
494
+ `Warning: got packets out of order. Expected ${this.sequenceId} but received ${packet.sequenceId}`
495
+ );
496
+ err.expected = this.sequenceId;
497
+ err.received = packet.sequenceId;
498
+ this.emit('warn', err); // REVIEW
499
+ console.error(err.message);
500
+ }
501
+ this._bumpSequenceId(packet.numPackets);
502
+ }
503
+ try {
504
+ if (this._fatalError) {
505
+ // skip remaining packets after client is in the error state
506
+ return;
507
+ }
508
+ const done = this._command.execute(packet, this);
509
+ if (done) {
510
+ this._command = this._commands.shift();
511
+ if (this._command) {
512
+ this.sequenceId = 0;
513
+ this.compressedSequenceId = 0;
514
+ this.handlePacket();
515
+ }
516
+ }
517
+ } catch (err) {
518
+ this._handleFatalError(err);
519
+ this.stream.destroy();
520
+ }
521
+ }
522
+
523
+ addCommand(cmd) {
524
+ // this.compressedSequenceId = 0;
525
+ // this.sequenceId = 0;
526
+ if (this.config.debug) {
527
+ const commandName = cmd.constructor.name;
528
+ console.log(`Add command: ${commandName}`);
529
+ cmd._commandName = commandName;
530
+ }
531
+ if (!this._command) {
532
+ this._command = cmd;
533
+ this.handlePacket();
534
+ } else {
535
+ this._commands.push(cmd);
536
+ }
537
+ return cmd;
538
+ }
539
+
540
+ format(sql, values) {
541
+ if (typeof this.config.queryFormat === 'function') {
542
+ return this.config.queryFormat.call(
543
+ this,
544
+ sql,
545
+ values,
546
+ this.config.timezone
547
+ );
548
+ }
549
+ const opts = {
550
+ sql: sql,
551
+ values: values,
552
+ };
553
+ this._resolveNamedPlaceholders(opts);
554
+ return SqlString.format(
555
+ opts.sql,
556
+ opts.values,
557
+ this.config.stringifyObjects,
558
+ this.config.timezone
559
+ );
560
+ }
561
+
562
+ escape(value) {
563
+ return SqlString.escape(value, false, this.config.timezone);
564
+ }
565
+
566
+ escapeId(value) {
567
+ return SqlString.escapeId(value, false);
568
+ }
569
+
570
+ raw(sql) {
571
+ return SqlString.raw(sql);
572
+ }
573
+
574
+ _resolveNamedPlaceholders(options) {
575
+ let unnamed;
576
+ if (this.config.namedPlaceholders || options.namedPlaceholders) {
577
+ if (Array.isArray(options.values)) {
578
+ // if an array is provided as the values, assume the conversion is not necessary.
579
+ // this allows the usage of unnamed placeholders even if the namedPlaceholders flag is enabled.
580
+ return;
581
+ }
582
+ if (convertNamedPlaceholders === null) {
583
+ convertNamedPlaceholders = require('named-placeholders')();
584
+ }
585
+ unnamed = convertNamedPlaceholders(options.sql, options.values);
586
+ options.sql = unnamed[0];
587
+ options.values = unnamed[1];
588
+ }
589
+ }
590
+
591
+ query(sql, values, cb) {
592
+ let cmdQuery;
593
+ if (sql.constructor === Commands.Query) {
594
+ cmdQuery = sql;
595
+ } else {
596
+ cmdQuery = BaseConnection.createQuery(sql, values, cb, this.config);
597
+ }
598
+ this._resolveNamedPlaceholders(cmdQuery);
599
+ const rawSql = this.format(
600
+ cmdQuery.sql,
601
+ cmdQuery.values !== undefined ? cmdQuery.values : []
602
+ );
603
+ cmdQuery.sql = rawSql;
604
+ return this.addCommand(cmdQuery);
605
+ }
606
+
607
+ pause() {
608
+ this._paused = true;
609
+ this.stream.pause();
610
+ }
611
+
612
+ resume() {
613
+ let packet;
614
+ this._paused = false;
615
+ while ((packet = this._paused_packets.shift())) {
616
+ this.handlePacket(packet);
617
+ // don't resume if packet handler paused connection
618
+ if (this._paused) {
619
+ return;
620
+ }
621
+ }
622
+ this.stream.resume();
623
+ }
624
+
625
+ // TODO: named placeholders support
626
+ prepare(options, cb) {
627
+ if (typeof options === 'string') {
628
+ options = { sql: options };
629
+ }
630
+ return this.addCommand(new Commands.Prepare(options, cb));
631
+ }
632
+
633
+ unprepare(sql) {
634
+ let options = {};
635
+ if (typeof sql === 'object') {
636
+ options = sql;
637
+ } else {
638
+ options.sql = sql;
639
+ }
640
+ const key = BaseConnection.statementKey(options);
641
+ const stmt = this._statements.get(key);
642
+ if (stmt) {
643
+ this._statements.delete(key);
644
+ stmt.close();
645
+ }
646
+ return stmt;
647
+ }
648
+
649
+ execute(sql, values, cb) {
650
+ let options = {
651
+ infileStreamFactory: this.config.infileStreamFactory,
652
+ };
653
+ if (typeof sql === 'object') {
654
+ // execute(options, cb)
655
+ options = {
656
+ ...options,
657
+ ...sql,
658
+ sql: sql.sql,
659
+ values: sql.values,
660
+ };
661
+ if (typeof values === 'function') {
662
+ cb = values;
663
+ } else {
664
+ options.values = options.values || values;
665
+ }
666
+ } else if (typeof values === 'function') {
667
+ // execute(sql, cb)
668
+ cb = values;
669
+ options.sql = sql;
670
+ options.values = undefined;
671
+ } else {
672
+ // execute(sql, values, cb)
673
+ options.sql = sql;
674
+ options.values = values;
675
+ }
676
+ this._resolveNamedPlaceholders(options);
677
+ // check for values containing undefined
678
+ if (options.values) {
679
+ //If namedPlaceholder is not enabled and object is passed as bind parameters
680
+ if (!Array.isArray(options.values)) {
681
+ throw new TypeError(
682
+ 'Bind parameters must be array if namedPlaceholders parameter is not enabled'
683
+ );
684
+ }
685
+ options.values.forEach((val) => {
686
+ //If namedPlaceholder is not enabled and object is passed as bind parameters
687
+ if (!Array.isArray(options.values)) {
688
+ throw new TypeError(
689
+ 'Bind parameters must be array if namedPlaceholders parameter is not enabled'
690
+ );
691
+ }
692
+ if (val === undefined) {
693
+ throw new TypeError(
694
+ 'Bind parameters must not contain undefined. To pass SQL NULL specify JS null'
695
+ );
696
+ }
697
+ if (typeof val === 'function') {
698
+ throw new TypeError(
699
+ 'Bind parameters must not contain function(s). To pass the body of a function as a string call .toString() first'
700
+ );
701
+ }
702
+ });
703
+ }
704
+ const executeCommand = new Commands.Execute(options, cb);
705
+ const prepareCommand = new Commands.Prepare(options, (err, stmt) => {
706
+ if (err) {
707
+ // skip execute command if prepare failed, we have main
708
+ // combined callback here
709
+ executeCommand.start = function () {
710
+ return null;
711
+ };
712
+ if (cb) {
713
+ cb(err);
714
+ } else {
715
+ executeCommand.emit('error', err);
716
+ }
717
+ executeCommand.emit('end');
718
+ return;
719
+ }
720
+ executeCommand.statement = stmt;
721
+ });
722
+ this.addCommand(prepareCommand);
723
+ this.addCommand(executeCommand);
724
+ return executeCommand;
725
+ }
726
+
727
+ changeUser(options, callback) {
728
+ if (!callback && typeof options === 'function') {
729
+ callback = options;
730
+ options = {};
731
+ }
732
+ const charsetNumber = options.charset
733
+ ? ConnectionConfig.getCharsetNumber(options.charset)
734
+ : this.config.charsetNumber;
735
+ return this.addCommand(
736
+ new Commands.ChangeUser(
737
+ {
738
+ user: options.user || this.config.user,
739
+ // for the purpose of multi-factor authentication, or not, the main
740
+ // password (used for the 1st authentication factor) can also be
741
+ // provided via the "password1" option
742
+ password:
743
+ options.password ||
744
+ options.password1 ||
745
+ this.config.password ||
746
+ this.config.password1,
747
+ password2: options.password2 || this.config.password2,
748
+ password3: options.password3 || this.config.password3,
749
+ passwordSha1: options.passwordSha1 || this.config.passwordSha1,
750
+ database: options.database || this.config.database,
751
+ timeout: options.timeout,
752
+ charsetNumber: charsetNumber,
753
+ currentConfig: this.config,
754
+ },
755
+ (err) => {
756
+ if (err) {
757
+ err.fatal = true;
758
+ }
759
+ if (callback) {
760
+ callback(err);
761
+ }
762
+ }
763
+ )
764
+ );
765
+ }
766
+
767
+ // transaction helpers
768
+ beginTransaction(cb) {
769
+ return this.query('START TRANSACTION', cb);
770
+ }
771
+
772
+ commit(cb) {
773
+ return this.query('COMMIT', cb);
774
+ }
775
+
776
+ rollback(cb) {
777
+ return this.query('ROLLBACK', cb);
778
+ }
779
+
780
+ ping(cb) {
781
+ return this.addCommand(new Commands.Ping(cb));
782
+ }
783
+
784
+ _registerSlave(opts, cb) {
785
+ return this.addCommand(new Commands.RegisterSlave(opts, cb));
786
+ }
787
+
788
+ _binlogDump(opts, cb) {
789
+ return this.addCommand(new Commands.BinlogDump(opts, cb));
790
+ }
791
+
792
+ // currently just alias to close
793
+ destroy() {
794
+ this.close();
795
+ }
796
+
797
+ close() {
798
+ if (this.connectTimeout) {
799
+ Timers.clearTimeout(this.connectTimeout);
800
+ this.connectTimeout = null;
801
+ }
802
+ this._closing = true;
803
+ this.stream.end();
804
+ this.addCommand = this._addCommandClosedState;
805
+ }
806
+
807
+ createBinlogStream(opts) {
808
+ // TODO: create proper stream class
809
+ // TODO: use through2
810
+ let test = 1;
811
+ const stream = new Readable({ objectMode: true });
812
+ stream._read = function () {
813
+ return {
814
+ data: test++,
815
+ };
816
+ };
817
+ this._registerSlave(opts, () => {
818
+ const dumpCmd = this._binlogDump(opts);
819
+ dumpCmd.on('event', (ev) => {
820
+ stream.push(ev);
821
+ });
822
+ dumpCmd.on('eof', () => {
823
+ stream.push(null);
824
+ // if non-blocking, then close stream to prevent errors
825
+ if (opts.flags && opts.flags & 0x01) {
826
+ this.close();
827
+ }
828
+ });
829
+ // TODO: pipe errors as well
830
+ });
831
+ return stream;
832
+ }
833
+
834
+ connect(cb) {
835
+ if (!cb) {
836
+ return;
837
+ }
838
+ if (this._fatalError || this._protocolError) {
839
+ return cb(this._fatalError || this._protocolError);
840
+ }
841
+ if (this._handshakePacket) {
842
+ return cb(null, this);
843
+ }
844
+ let connectCalled = 0;
845
+ function callbackOnce(isErrorHandler) {
846
+ return function (param) {
847
+ if (!connectCalled) {
848
+ if (isErrorHandler) {
849
+ cb(param);
850
+ } else {
851
+ cb(null, param);
852
+ }
853
+ }
854
+ connectCalled = 1;
855
+ };
856
+ }
857
+ this.once('error', callbackOnce(true));
858
+ this.once('connect', callbackOnce(false));
859
+ }
860
+
861
+ // ===================================
862
+ // outgoing server connection methods
863
+ // ===================================
864
+ writeColumns(columns) {
865
+ this.writePacket(Packets.ResultSetHeader.toPacket(columns.length));
866
+ columns.forEach((column) => {
867
+ this.writePacket(
868
+ Packets.ColumnDefinition.toPacket(column, this.serverConfig.encoding)
869
+ );
870
+ });
871
+ this.writeEof();
872
+ }
873
+
874
+ // row is array of columns, not hash
875
+ writeTextRow(column) {
876
+ this.writePacket(
877
+ Packets.TextRow.toPacket(column, this.serverConfig.encoding)
878
+ );
879
+ }
880
+
881
+ writeBinaryRow(column) {
882
+ this.writePacket(
883
+ Packets.BinaryRow.toPacket(column, this.serverConfig.encoding)
884
+ );
885
+ }
886
+
887
+ writeTextResult(rows, columns, binary = false) {
888
+ this.writeColumns(columns);
889
+ rows.forEach((row) => {
890
+ const arrayRow = new Array(columns.length);
891
+ columns.forEach((column) => {
892
+ arrayRow.push(row[column.name]);
893
+ });
894
+ if (binary) {
895
+ this.writeBinaryRow(arrayRow);
896
+ } else this.writeTextRow(arrayRow);
897
+ });
898
+ this.writeEof();
899
+ }
900
+
901
+ writeEof(warnings, statusFlags) {
902
+ this.writePacket(Packets.EOF.toPacket(warnings, statusFlags));
903
+ }
904
+
905
+ writeOk(args) {
906
+ if (!args) {
907
+ args = { affectedRows: 0 };
908
+ }
909
+ this.writePacket(Packets.OK.toPacket(args, this.serverConfig.encoding));
910
+ }
911
+
912
+ writeError(args) {
913
+ // if we want to send error before initial hello was sent, use default encoding
914
+ const encoding = this.serverConfig ? this.serverConfig.encoding : 'cesu8';
915
+ this.writePacket(Packets.Error.toPacket(args, encoding));
916
+ }
917
+
918
+ serverHandshake(args) {
919
+ this.serverConfig = args;
920
+ this.serverConfig.encoding =
921
+ CharsetToEncoding[this.serverConfig.characterSet];
922
+ return this.addCommand(new Commands.ServerHandshake(args));
923
+ }
924
+
925
+ // ===============================================================
926
+ end(callback) {
927
+ if (this.config.isServer) {
928
+ this._closing = true;
929
+ const quitCmd = new EventEmitter();
930
+ setImmediate(() => {
931
+ this.stream.end();
932
+ quitCmd.emit('end');
933
+ });
934
+ return quitCmd;
935
+ }
936
+ // trigger error if more commands enqueued after end command
937
+ const quitCmd = this.addCommand(new Commands.Quit(callback));
938
+ this.addCommand = this._addCommandClosedState;
939
+ return quitCmd;
940
+ }
941
+
942
+ static createQuery(sql, values, cb, config) {
943
+ let options = {
944
+ rowsAsArray: config.rowsAsArray,
945
+ infileStreamFactory: config.infileStreamFactory,
946
+ };
947
+ if (typeof sql === 'object') {
948
+ // query(options, cb)
949
+ options = {
950
+ ...options,
951
+ ...sql,
952
+ sql: sql.sql,
953
+ values: sql.values,
954
+ };
955
+ if (typeof values === 'function') {
956
+ cb = values;
957
+ } else if (values !== undefined) {
958
+ options.values = values;
959
+ }
960
+ } else if (typeof values === 'function') {
961
+ // query(sql, cb)
962
+ cb = values;
963
+ options.sql = sql;
964
+ options.values = undefined;
965
+ } else {
966
+ // query(sql, values, cb)
967
+ options.sql = sql;
968
+ options.values = values;
969
+ }
970
+ return new Commands.Query(options, cb);
971
+ }
972
+
973
+ static statementKey(options) {
974
+ return `${typeof options.nestTables}/${options.nestTables}/${options.rowsAsArray}${options.sql}`;
975
+ }
976
+ }
977
+
978
+ module.exports = BaseConnection;