@tachybase/module-multi-app 1.6.0 → 1.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -34
- package/README.zh-CN.md +34 -34
- package/client.d.ts +1 -1
- package/client.js +1 -1
- package/dist/externalVersion.js +5 -5
- package/dist/locale/en-US.json +48 -48
- package/dist/locale/es-ES.json +9 -9
- package/dist/locale/ko_KR.json +11 -11
- package/dist/locale/pt-BR.json +9 -9
- package/dist/locale/zh-CN.json +58 -58
- package/dist/node_modules/mariadb/callback.js +43 -8
- package/dist/node_modules/mariadb/check-node.js +30 -0
- package/dist/node_modules/mariadb/lib/cluster-callback.js +84 -0
- package/dist/node_modules/mariadb/lib/cluster.js +446 -0
- package/dist/node_modules/mariadb/lib/cmd/batch-bulk.js +576 -177
- package/dist/node_modules/mariadb/lib/cmd/change-user.js +54 -44
- package/dist/node_modules/mariadb/lib/cmd/class/ok-packet.js +3 -2
- package/dist/node_modules/mariadb/lib/cmd/class/prepare-cache-wrapper.js +46 -0
- package/dist/node_modules/mariadb/lib/cmd/class/prepare-result-packet.js +141 -0
- package/dist/node_modules/mariadb/lib/cmd/class/prepare-wrapper.js +70 -0
- package/dist/node_modules/mariadb/lib/cmd/close-prepare.js +38 -0
- package/dist/node_modules/mariadb/lib/cmd/column-definition.js +145 -47
- package/dist/node_modules/mariadb/lib/cmd/command.js +41 -75
- package/dist/node_modules/mariadb/lib/cmd/decoder/binary-decoder.js +282 -0
- package/dist/node_modules/mariadb/lib/cmd/decoder/text-decoder.js +210 -0
- package/dist/node_modules/mariadb/lib/cmd/{common-binary-cmd.js → encoder/binary-encoder.js} +34 -77
- package/dist/node_modules/mariadb/lib/cmd/encoder/text-encoder.js +311 -0
- package/dist/node_modules/mariadb/lib/cmd/execute-stream.js +61 -0
- package/dist/node_modules/mariadb/lib/cmd/execute.js +338 -0
- package/dist/node_modules/mariadb/lib/cmd/handshake/auth/caching-sha2-password-auth.js +25 -62
- package/dist/node_modules/mariadb/lib/cmd/handshake/auth/clear-password-auth.js +39 -6
- package/dist/node_modules/mariadb/lib/cmd/handshake/auth/ed25519-password-auth.js +48 -16
- package/dist/node_modules/mariadb/lib/cmd/handshake/auth/handshake.js +198 -0
- package/dist/node_modules/mariadb/lib/cmd/handshake/{initial-handshake.js → auth/initial-handshake.js} +10 -8
- package/dist/node_modules/mariadb/lib/cmd/handshake/auth/native-password-auth.js +22 -9
- package/dist/node_modules/mariadb/lib/cmd/handshake/auth/pam-password-auth.js +9 -4
- package/dist/node_modules/mariadb/lib/cmd/handshake/auth/parsec-auth.js +115 -0
- package/dist/node_modules/mariadb/lib/cmd/handshake/auth/plugin-auth.js +12 -5
- package/dist/node_modules/mariadb/lib/cmd/handshake/auth/sha256-password-auth.js +44 -33
- package/dist/node_modules/mariadb/lib/cmd/handshake/authentication.js +335 -0
- package/dist/node_modules/mariadb/lib/cmd/handshake/client-capabilities.js +20 -19
- package/dist/node_modules/mariadb/lib/cmd/handshake/ssl-request.js +6 -3
- package/dist/node_modules/mariadb/lib/cmd/parser.js +861 -0
- package/dist/node_modules/mariadb/lib/cmd/ping.js +17 -18
- package/dist/node_modules/mariadb/lib/cmd/prepare.js +170 -0
- package/dist/node_modules/mariadb/lib/cmd/query.js +281 -144
- package/dist/node_modules/mariadb/lib/cmd/quit.js +9 -6
- package/dist/node_modules/mariadb/lib/cmd/reset.js +15 -19
- package/dist/node_modules/mariadb/lib/cmd/stream.js +21 -6
- package/dist/node_modules/mariadb/lib/config/cluster-options.js +23 -0
- package/dist/node_modules/mariadb/lib/config/connection-options.js +196 -132
- package/dist/node_modules/mariadb/lib/config/pool-options.js +27 -19
- package/dist/node_modules/mariadb/lib/connection-callback.js +492 -120
- package/dist/node_modules/mariadb/lib/connection-promise.js +372 -0
- package/dist/node_modules/mariadb/lib/connection.js +1739 -1016
- package/dist/node_modules/mariadb/lib/const/capabilities.js +36 -30
- package/dist/node_modules/mariadb/lib/const/collations.js +972 -36
- package/dist/node_modules/mariadb/lib/const/connection_status.js +3 -0
- package/dist/node_modules/mariadb/lib/const/error-code.js +35 -11
- package/dist/node_modules/mariadb/lib/const/field-detail.js +3 -0
- package/dist/node_modules/mariadb/lib/const/field-type.js +7 -4
- package/dist/node_modules/mariadb/lib/const/server-status.js +4 -1
- package/dist/node_modules/mariadb/lib/const/state-change.js +3 -0
- package/dist/node_modules/mariadb/lib/filtered-cluster-callback.js +136 -0
- package/dist/node_modules/mariadb/lib/filtered-cluster.js +118 -0
- package/dist/node_modules/mariadb/lib/io/compression-input-stream.js +14 -13
- package/dist/node_modules/mariadb/lib/io/compression-output-stream.js +21 -18
- package/dist/node_modules/mariadb/lib/io/packet-input-stream.js +75 -64
- package/dist/node_modules/mariadb/lib/io/packet-node-encoded.js +13 -9
- package/dist/node_modules/mariadb/lib/io/packet-node-iconv.js +12 -10
- package/dist/node_modules/mariadb/lib/io/packet-output-stream.js +402 -134
- package/dist/node_modules/mariadb/lib/io/packet.js +287 -202
- package/dist/node_modules/mariadb/lib/lru-prepare-cache.js +84 -0
- package/dist/node_modules/mariadb/lib/misc/connection-information.js +15 -32
- package/dist/node_modules/mariadb/lib/misc/errors.js +68 -25
- package/dist/node_modules/mariadb/lib/misc/parse.js +207 -711
- package/dist/node_modules/mariadb/lib/misc/utils.js +34 -62
- package/dist/node_modules/mariadb/lib/pool-callback.js +213 -174
- package/dist/node_modules/mariadb/lib/pool-promise.js +228 -94
- package/dist/node_modules/mariadb/lib/pool.js +951 -0
- package/dist/node_modules/mariadb/package.json +1 -1
- package/dist/node_modules/mariadb/promise.js +1 -34
- package/dist/node_modules/mariadb/types/callback.d.ts +207 -0
- package/dist/node_modules/mariadb/types/index.d.ts +94 -674
- package/dist/node_modules/mariadb/types/share.d.ts +804 -0
- package/dist/node_modules/qs/package.json +1 -1
- package/dist/server/actions/apps.js +2 -2
- package/dist/server/app-lifecycle.d.ts +1 -1
- package/dist/server/app-lifecycle.js +4 -4
- package/dist/server/models/application.d.ts +1 -1
- package/package.json +7 -7
- package/server.d.ts +2 -2
- package/server.js +1 -1
- package/dist/node_modules/mariadb/lib/cmd/batch-rewrite.js +0 -372
- package/dist/node_modules/mariadb/lib/cmd/common-text-cmd.js +0 -427
- package/dist/node_modules/mariadb/lib/cmd/handshake/client-handshake-response.js +0 -126
- package/dist/node_modules/mariadb/lib/cmd/handshake/handshake.js +0 -292
- package/dist/node_modules/mariadb/lib/cmd/resultset.js +0 -607
- package/dist/node_modules/mariadb/lib/config/pool-cluster-options.js +0 -19
- package/dist/node_modules/mariadb/lib/filtered-pool-cluster.js +0 -81
- package/dist/node_modules/mariadb/lib/io/bulk-packet.js +0 -590
- package/dist/node_modules/mariadb/lib/io/rewrite-packet.js +0 -481
- package/dist/node_modules/mariadb/lib/pool-base.js +0 -611
- package/dist/node_modules/mariadb/lib/pool-cluster-callback.js +0 -66
- package/dist/node_modules/mariadb/lib/pool-cluster.js +0 -407
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
// SPDX-License-Identifier: LGPL-2.1-or-later
|
|
2
|
+
// Copyright (c) 2015-2025 MariaDB Corporation Ab
|
|
3
|
+
|
|
1
4
|
'use strict';
|
|
2
5
|
|
|
3
6
|
const EventEmitter = require('events');
|
|
4
|
-
const util = require('util');
|
|
5
7
|
const Queue = require('denque');
|
|
6
8
|
const Net = require('net');
|
|
7
9
|
const PacketInputStream = require('./io/packet-input-stream');
|
|
@@ -14,19 +16,55 @@ const tls = require('tls');
|
|
|
14
16
|
const Errors = require('./misc/errors');
|
|
15
17
|
const Utils = require('./misc/utils');
|
|
16
18
|
const Capabilities = require('./const/capabilities');
|
|
17
|
-
const
|
|
19
|
+
const ConnectionOptions = require('./config/connection-options');
|
|
18
20
|
|
|
19
21
|
/*commands*/
|
|
20
|
-
const
|
|
22
|
+
const Authentication = require('./cmd/handshake/authentication');
|
|
21
23
|
const Quit = require('./cmd/quit');
|
|
22
24
|
const Ping = require('./cmd/ping');
|
|
23
25
|
const Reset = require('./cmd/reset');
|
|
24
26
|
const Query = require('./cmd/query');
|
|
25
|
-
const
|
|
27
|
+
const Prepare = require('./cmd/prepare');
|
|
28
|
+
const OkPacket = require('./cmd/class/ok-packet');
|
|
29
|
+
const Execute = require('./cmd/execute');
|
|
30
|
+
const ClosePrepare = require('./cmd/close-prepare');
|
|
26
31
|
const BatchBulk = require('./cmd/batch-bulk');
|
|
27
|
-
const Stream = require('./cmd/stream');
|
|
28
32
|
const ChangeUser = require('./cmd/change-user');
|
|
29
33
|
const { Status } = require('./const/connection_status');
|
|
34
|
+
const LruPrepareCache = require('./lru-prepare-cache');
|
|
35
|
+
const fsPromises = require('fs').promises;
|
|
36
|
+
const Parse = require('./misc/parse');
|
|
37
|
+
const Collations = require('./const/collations');
|
|
38
|
+
const ConnOptions = require('./config/connection-options');
|
|
39
|
+
|
|
40
|
+
const convertFixedTime = function (tz, conn) {
|
|
41
|
+
if (tz === 'UTC' || tz === 'Etc/UTC' || tz === 'Z' || tz === 'Etc/GMT') {
|
|
42
|
+
return '+00:00';
|
|
43
|
+
} else if (tz.startsWith('Etc/GMT') || tz.startsWith('GMT')) {
|
|
44
|
+
let tzdiff;
|
|
45
|
+
let negate;
|
|
46
|
+
|
|
47
|
+
// strangely Etc/GMT+8 = GMT-08:00 = offset -8
|
|
48
|
+
if (tz.startsWith('Etc/GMT')) {
|
|
49
|
+
tzdiff = tz.substring(7);
|
|
50
|
+
negate = !tzdiff.startsWith('-');
|
|
51
|
+
} else {
|
|
52
|
+
tzdiff = tz.substring(3);
|
|
53
|
+
negate = tzdiff.startsWith('-');
|
|
54
|
+
}
|
|
55
|
+
let diff = parseInt(tzdiff.substring(1));
|
|
56
|
+
if (isNaN(diff)) {
|
|
57
|
+
throw Errors.createFatalError(
|
|
58
|
+
`Automatic timezone setting fails. wrong Server timezone '${tz}' conversion to +/-HH:00 conversion.`,
|
|
59
|
+
Errors.ER_WRONG_AUTO_TIMEZONE,
|
|
60
|
+
conn.info
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
return (negate ? '-' : '+') + (diff >= 10 ? diff : '0' + diff) + ':00';
|
|
64
|
+
}
|
|
65
|
+
return tz;
|
|
66
|
+
};
|
|
67
|
+
const redirectUrlFormat = /(mariadb|mysql):\/\/(([^/@:]+)?(:([^/]+))?@)?(([^/:]+)(:([0-9]+))?)(\/([^?]+)(\?(.*))?)?$/;
|
|
30
68
|
|
|
31
69
|
/**
|
|
32
70
|
* New Connection instance.
|
|
@@ -39,9 +77,44 @@ const { Status } = require('./const/connection_status');
|
|
|
39
77
|
* @fires Connection#error
|
|
40
78
|
*
|
|
41
79
|
*/
|
|
42
|
-
|
|
80
|
+
class Connection extends EventEmitter {
|
|
81
|
+
opts;
|
|
82
|
+
sendQueue = new Queue();
|
|
83
|
+
receiveQueue = new Queue();
|
|
84
|
+
waitingAuthenticationQueue = new Queue();
|
|
85
|
+
status = Status.NOT_CONNECTED;
|
|
86
|
+
socket = null;
|
|
87
|
+
timeout = null;
|
|
88
|
+
addCommand;
|
|
89
|
+
streamOut;
|
|
90
|
+
streamIn;
|
|
91
|
+
info;
|
|
92
|
+
prepareCache;
|
|
93
|
+
|
|
94
|
+
constructor(options) {
|
|
95
|
+
super();
|
|
96
|
+
|
|
97
|
+
this.opts = Object.assign(new EventEmitter(), options);
|
|
98
|
+
this.info = new ConnectionInformation(this.opts, this.redirect.bind(this));
|
|
99
|
+
this.prepareCache =
|
|
100
|
+
this.opts.prepareCacheLength > 0 ? new LruPrepareCache(this.info, this.opts.prepareCacheLength) : null;
|
|
101
|
+
this.addCommand = this.addCommandQueue;
|
|
102
|
+
this.streamOut = new PacketOutputStream(this.opts, this.info);
|
|
103
|
+
this.streamIn = new PacketInputStream(
|
|
104
|
+
this.unexpectedPacket.bind(this),
|
|
105
|
+
this.receiveQueue,
|
|
106
|
+
this.streamOut,
|
|
107
|
+
this.opts,
|
|
108
|
+
this.info
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
this.on('close_prepare', this._closePrepare.bind(this));
|
|
112
|
+
this.escape = Utils.escape.bind(this, this.opts, this.info);
|
|
113
|
+
this.escapeId = Utils.escapeId.bind(this, this.opts, this.info);
|
|
114
|
+
}
|
|
115
|
+
|
|
43
116
|
//*****************************************************************
|
|
44
|
-
// public
|
|
117
|
+
// public methods
|
|
45
118
|
//*****************************************************************
|
|
46
119
|
|
|
47
120
|
/**
|
|
@@ -49,259 +122,518 @@ function Connection(options) {
|
|
|
49
122
|
*
|
|
50
123
|
* @returns {Promise} promise
|
|
51
124
|
*/
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
});
|
|
125
|
+
connect() {
|
|
126
|
+
const conn = this;
|
|
127
|
+
this.status = Status.CONNECTING;
|
|
128
|
+
const authenticationParam = {
|
|
129
|
+
opts: this.opts
|
|
130
|
+
};
|
|
59
131
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
132
|
+
return new Promise(function (resolve, reject) {
|
|
133
|
+
conn.connectRejectFct = reject;
|
|
134
|
+
conn.connectResolveFct = resolve;
|
|
135
|
+
|
|
136
|
+
// Add a handshake to msg queue
|
|
137
|
+
const authentication = new Authentication(
|
|
138
|
+
authenticationParam,
|
|
139
|
+
conn.authSucceedHandler.bind(conn),
|
|
140
|
+
conn.authFailHandler.bind(conn),
|
|
141
|
+
conn.createSecureContext.bind(conn),
|
|
142
|
+
conn.getSocket.bind(conn)
|
|
143
|
+
);
|
|
72
144
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
145
|
+
// Capture stack trace for better error reporting
|
|
146
|
+
Error.captureStackTrace(authentication);
|
|
147
|
+
|
|
148
|
+
authentication.once('end', () => {
|
|
149
|
+
conn.receiveQueue.shift();
|
|
150
|
+
|
|
151
|
+
// conn.info.collation might not be initialized
|
|
152
|
+
// in case of handshake throwing error
|
|
153
|
+
if (!conn.opts.collation && conn.info.collation) {
|
|
154
|
+
conn.opts.emit('collation', conn.info.collation);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
process.nextTick(conn.nextSendCmd.bind(conn));
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
conn.receiveQueue.push(authentication);
|
|
161
|
+
conn.streamInitSocket.call(conn);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
89
164
|
|
|
90
165
|
/**
|
|
91
|
-
*
|
|
92
|
-
* All user variables will be reset, Prepare commands will be released.
|
|
93
|
-
* !!! mysql has a bug when CONNECT_ATTRS capability is set, that is default !!!!
|
|
166
|
+
* Execute a prepared statement with the given parameters
|
|
94
167
|
*
|
|
95
|
-
* @param
|
|
96
|
-
* @
|
|
168
|
+
* @param {Object} cmdParam - Command parameters
|
|
169
|
+
* @param {Object} prepare - Prepared statement
|
|
170
|
+
* @param {Function} resolve - Promise resolve function
|
|
171
|
+
* @param {Function} reject - Promise reject function
|
|
97
172
|
*/
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
173
|
+
executePromise(cmdParam, prepare, resolve, reject) {
|
|
174
|
+
const cmd = new Execute(resolve, this._logAndReject.bind(this, reject), this.opts, cmdParam, prepare);
|
|
175
|
+
this.addCommand(cmd, true);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Execute a batch of the same SQL statement with different parameter sets
|
|
180
|
+
*
|
|
181
|
+
* @param {Object|String} cmdParam - SQL statement or options object
|
|
182
|
+
* @param {function} resolve - promise resolve function
|
|
183
|
+
* @param {function} reject - promise reject function
|
|
184
|
+
*/
|
|
185
|
+
batch(cmdParam, resolve, reject) {
|
|
186
|
+
// Validate SQL parameter
|
|
187
|
+
if (!cmdParam.sql) {
|
|
188
|
+
return this.handleMissingSqlError(reject);
|
|
110
189
|
}
|
|
111
190
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
});
|
|
125
|
-
};
|
|
191
|
+
// Validate values parameter
|
|
192
|
+
if (!cmdParam.values) {
|
|
193
|
+
return this.handleMissingValuesError(cmdParam, reject);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Execute the batch operation
|
|
197
|
+
this.prepare(
|
|
198
|
+
cmdParam,
|
|
199
|
+
(prepare) => this.executeBatch(cmdParam, prepare, resolve, reject),
|
|
200
|
+
(err) => this._logAndReject(reject, err)
|
|
201
|
+
);
|
|
202
|
+
}
|
|
126
203
|
|
|
127
204
|
/**
|
|
128
|
-
*
|
|
205
|
+
* Handle missing SQL parameter error
|
|
129
206
|
*
|
|
130
|
-
* @
|
|
207
|
+
* @param {Function} reject - Promise reject function
|
|
208
|
+
* @private
|
|
131
209
|
*/
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
210
|
+
handleMissingSqlError(reject) {
|
|
211
|
+
const err = Errors.createError(
|
|
212
|
+
'sql parameter is mandatory',
|
|
213
|
+
Errors.ER_UNDEFINED_SQL,
|
|
214
|
+
this.info,
|
|
215
|
+
'HY000',
|
|
216
|
+
null,
|
|
217
|
+
false
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
// Add stack trace for better debugging
|
|
221
|
+
Error.captureStackTrace(err, this.handleMissingSqlError);
|
|
222
|
+
this._logAndReject(reject, err);
|
|
223
|
+
}
|
|
135
224
|
|
|
136
225
|
/**
|
|
137
|
-
*
|
|
226
|
+
* Handle missing values parameter error
|
|
138
227
|
*
|
|
139
|
-
* @
|
|
228
|
+
* @param {Object} cmdParam - Command parameters
|
|
229
|
+
* @param {Function} reject - Promise reject function
|
|
230
|
+
* @private
|
|
140
231
|
*/
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
232
|
+
handleMissingValuesError(cmdParam, reject) {
|
|
233
|
+
const sql = cmdParam.sql;
|
|
234
|
+
// Truncate SQL for debug output if it's too long
|
|
235
|
+
const debugSql = sql.length > this.opts.debugLen ? sql.substring(0, this.opts.debugLen) + '...' : sql;
|
|
236
|
+
|
|
237
|
+
const err = Errors.createError(
|
|
238
|
+
'Batch must have values set',
|
|
239
|
+
Errors.ER_BATCH_WITH_NO_VALUES,
|
|
240
|
+
this.info,
|
|
241
|
+
'HY000',
|
|
242
|
+
debugSql,
|
|
243
|
+
false,
|
|
244
|
+
cmdParam.stack
|
|
245
|
+
);
|
|
246
|
+
this._logAndReject(reject, err);
|
|
247
|
+
}
|
|
144
248
|
|
|
145
249
|
/**
|
|
146
|
-
*
|
|
250
|
+
* Execute batch operation with prepared statement
|
|
147
251
|
*
|
|
148
|
-
* @
|
|
252
|
+
* @param {Object} cmdParam - Command parameters
|
|
253
|
+
* @param {Object} prepare - Prepared statement
|
|
254
|
+
* @param {Function} resolve - Promise resolve function
|
|
255
|
+
* @param {Function} reject - Promise reject function
|
|
256
|
+
* @private
|
|
149
257
|
*/
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
258
|
+
executeBatch(cmdParam, prepare, resolve, reject) {
|
|
259
|
+
const usePlaceHolder = (cmdParam.opts && cmdParam.opts.namedPlaceholders) || this.opts.namedPlaceholders;
|
|
260
|
+
let values = this.formatBatchValues(cmdParam.values, usePlaceHolder, prepare.parameterCount);
|
|
261
|
+
cmdParam.values = values;
|
|
262
|
+
|
|
263
|
+
// Determine if bulk protocol can be used
|
|
264
|
+
const useBulk = this._canUseBulk(values, cmdParam.opts);
|
|
265
|
+
|
|
266
|
+
if (useBulk) {
|
|
267
|
+
this.executeBulkPromise(cmdParam, prepare, this.opts, resolve, reject);
|
|
268
|
+
} else {
|
|
269
|
+
this.executeIndividualBatches(cmdParam, prepare, resolve, reject);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
153
272
|
|
|
154
273
|
/**
|
|
155
|
-
* Execute
|
|
274
|
+
* Execute bulk operation using specialized bulk protocol
|
|
156
275
|
*
|
|
157
|
-
* @param
|
|
158
|
-
*
|
|
159
|
-
* @param
|
|
160
|
-
* @
|
|
276
|
+
* @param {Object} cmdParam - Command parameters
|
|
277
|
+
* @param {Object} prepare - Prepared statement
|
|
278
|
+
* @param {Object} opts - Options
|
|
279
|
+
* @param {Function} resolve - Promise resolve function
|
|
280
|
+
* @param {Function} reject - Promise reject function
|
|
281
|
+
* @private
|
|
161
282
|
*/
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
283
|
+
executeBulkPromise(cmdParam, prepare, opts, resolve, reject) {
|
|
284
|
+
const cmd = new BatchBulk(
|
|
285
|
+
(res) => {
|
|
286
|
+
prepare.close();
|
|
287
|
+
return resolve(res);
|
|
288
|
+
},
|
|
289
|
+
(err) => {
|
|
290
|
+
prepare.close();
|
|
291
|
+
if (opts.logger.error) opts.logger.error(err);
|
|
292
|
+
reject(err);
|
|
293
|
+
},
|
|
294
|
+
opts,
|
|
295
|
+
prepare,
|
|
296
|
+
cmdParam
|
|
297
|
+
);
|
|
298
|
+
this.addCommand(cmd, true);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Format batch values into the correct structure
|
|
303
|
+
*
|
|
304
|
+
* @param {Array} values - Original values array
|
|
305
|
+
* @param {Boolean} usePlaceHolder - Whether named placeholders are used
|
|
306
|
+
* @param {Number} parameterCount - Number of parameters in prepared statement
|
|
307
|
+
* @returns {Array} Formatted values array
|
|
308
|
+
* @private
|
|
309
|
+
*/
|
|
310
|
+
formatBatchValues(values, usePlaceHolder, parameterCount) {
|
|
311
|
+
// If values is not an array, wrap it
|
|
312
|
+
if (!Array.isArray(values)) {
|
|
313
|
+
return [[values]];
|
|
172
314
|
}
|
|
173
315
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
316
|
+
// For named placeholders, return as is
|
|
317
|
+
if (usePlaceHolder) {
|
|
318
|
+
return values;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// If already in correct format (array of arrays), return as is
|
|
322
|
+
if (Array.isArray(values[0])) {
|
|
323
|
+
return values;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// If only one parameter expected, convert flat array to array of single-item arrays
|
|
327
|
+
if (parameterCount === 1) {
|
|
328
|
+
// Pre-allocate result array for better performance
|
|
329
|
+
const result = new Array(values.length);
|
|
330
|
+
for (let i = 0; i < values.length; i++) {
|
|
331
|
+
result[i] = [values[i]];
|
|
332
|
+
}
|
|
333
|
+
return result;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Single set of parameters for multiple placeholders
|
|
337
|
+
return [values];
|
|
338
|
+
}
|
|
180
339
|
|
|
181
340
|
/**
|
|
182
|
-
* Execute batch
|
|
341
|
+
* Execute individual batch operations when bulk protocol can't be used
|
|
183
342
|
*
|
|
184
|
-
* @param
|
|
185
|
-
*
|
|
186
|
-
* @param
|
|
187
|
-
* @
|
|
343
|
+
* @param {Object} cmdParam - Command parameters
|
|
344
|
+
* @param {Object} prepare - Prepared statement
|
|
345
|
+
* @param {Function} resolve - Promise resolve function
|
|
346
|
+
* @param {Function} reject - Promise reject function
|
|
347
|
+
* @private
|
|
188
348
|
*/
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
349
|
+
executeIndividualBatches(cmdParam, prepare, resolve, reject) {
|
|
350
|
+
const results = [];
|
|
351
|
+
const batchSize = 1000; // Process in chunks to avoid memory issues
|
|
352
|
+
const totalBatches = Math.ceil(cmdParam.values.length / batchSize);
|
|
353
|
+
|
|
354
|
+
// Execute by chunks to avoid excessive memory usage
|
|
355
|
+
this.executeBatchChunk(cmdParam, prepare, 0, batchSize, totalBatches, results, resolve, reject);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Execute a chunk of the batch operations
|
|
360
|
+
*
|
|
361
|
+
* @param {Object} cmdParam - Command parameters
|
|
362
|
+
* @param {Object} prepare - Prepared statement
|
|
363
|
+
* @param {Number} chunkIndex - Current chunk index
|
|
364
|
+
* @param {Number} batchSize - Size of each batch chunk
|
|
365
|
+
* @param {Number} totalBatches - Total number of chunks
|
|
366
|
+
* @param {Array} results - Accumulated results
|
|
367
|
+
* @param {Function} resolve - Promise resolve function
|
|
368
|
+
* @param {Function} reject - Promise reject function
|
|
369
|
+
* @private
|
|
370
|
+
*/
|
|
371
|
+
executeBatchChunk(cmdParam, prepare, chunkIndex, batchSize, totalBatches, results, resolve, reject) {
|
|
372
|
+
const values = cmdParam.values;
|
|
373
|
+
const startIdx = chunkIndex * batchSize;
|
|
374
|
+
const endIdx = Math.min(startIdx + batchSize, values.length);
|
|
375
|
+
const executes = [];
|
|
376
|
+
|
|
377
|
+
// Create execute promises for this chunk
|
|
378
|
+
for (let i = startIdx; i < endIdx; i++) {
|
|
379
|
+
executes.push(prepare.execute(values[i], cmdParam.opts, null, cmdParam.stack));
|
|
199
380
|
}
|
|
200
381
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
382
|
+
// Execute all promises in this chunk
|
|
383
|
+
Promise.all(executes)
|
|
384
|
+
.then(
|
|
385
|
+
(chunkResults) => {
|
|
386
|
+
// Add results from this chunk to accumulated results
|
|
387
|
+
results.push(...chunkResults);
|
|
388
|
+
|
|
389
|
+
// If this was the last chunk, process results
|
|
390
|
+
if (chunkIndex === totalBatches - 1) {
|
|
391
|
+
const cmdOpt = Object.assign({}, this.opts, cmdParam.opts);
|
|
392
|
+
this.processBatchResults(results, cmdOpt, cmdParam, resolve);
|
|
393
|
+
prepare.close();
|
|
394
|
+
} else {
|
|
395
|
+
// Process next chunk
|
|
396
|
+
setImmediate(() => {
|
|
397
|
+
this.executeBatchChunk(
|
|
398
|
+
cmdParam,
|
|
399
|
+
prepare,
|
|
400
|
+
chunkIndex + 1,
|
|
401
|
+
batchSize,
|
|
402
|
+
totalBatches,
|
|
403
|
+
results,
|
|
404
|
+
resolve,
|
|
405
|
+
reject
|
|
406
|
+
);
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
},
|
|
410
|
+
(err) => {
|
|
411
|
+
prepare.close();
|
|
412
|
+
reject(err);
|
|
413
|
+
}
|
|
414
|
+
)
|
|
415
|
+
.catch((err) => {
|
|
416
|
+
prepare.close();
|
|
417
|
+
reject(err);
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Process batch results from individual executions
|
|
423
|
+
*
|
|
424
|
+
* @param {Array} results - Array of individual results
|
|
425
|
+
* @param {Object} cmdOpt - Command options
|
|
426
|
+
* @param {Object} cmdParam - Command parameters
|
|
427
|
+
* @param {Function} resolve - Promise resolve function
|
|
428
|
+
* @private
|
|
429
|
+
*/
|
|
430
|
+
processBatchResults(results, cmdOpt, cmdParam, resolve) {
|
|
431
|
+
// Handle empty results case
|
|
432
|
+
if (!results.length) {
|
|
433
|
+
resolve(cmdOpt.metaAsArray ? [[], []] : []);
|
|
434
|
+
return;
|
|
212
435
|
}
|
|
213
436
|
|
|
214
|
-
|
|
437
|
+
// Return full results when requested
|
|
438
|
+
const fullResult = cmdOpt.fullResult === undefined || cmdOpt.fullResult;
|
|
439
|
+
if (fullResult) {
|
|
440
|
+
if (cmdOpt.metaAsArray) {
|
|
441
|
+
const aggregateResults = results.reduce((accumulator, currentValue) => {
|
|
442
|
+
if (Array.isArray(currentValue[0])) {
|
|
443
|
+
accumulator.push(...currentValue[0]);
|
|
444
|
+
} else if (currentValue[0] instanceof OkPacket) {
|
|
445
|
+
accumulator.push(currentValue[0]);
|
|
446
|
+
} else {
|
|
447
|
+
accumulator.push([currentValue[0]]);
|
|
448
|
+
}
|
|
449
|
+
return accumulator;
|
|
450
|
+
}, []);
|
|
451
|
+
const meta = results[0][1];
|
|
452
|
+
resolve([aggregateResults, meta]);
|
|
453
|
+
} else {
|
|
454
|
+
const aggregateResults = results.reduce((accumulator, currentValue) => {
|
|
455
|
+
if (currentValue instanceof OkPacket) {
|
|
456
|
+
accumulator.push(currentValue);
|
|
457
|
+
} else if (!cmdOpt.rowsAsArray && Array.isArray(currentValue[0])) {
|
|
458
|
+
accumulator.push(...currentValue[0]);
|
|
459
|
+
} else {
|
|
460
|
+
accumulator.push(currentValue[0]);
|
|
461
|
+
}
|
|
462
|
+
return accumulator;
|
|
463
|
+
}, []);
|
|
464
|
+
const meta = results[0].meta;
|
|
465
|
+
Object.defineProperty(aggregateResults, 'meta', {
|
|
466
|
+
value: meta,
|
|
467
|
+
writable: true,
|
|
468
|
+
enumerable: cmdOpt.metaEnumerable
|
|
469
|
+
});
|
|
470
|
+
resolve(aggregateResults);
|
|
471
|
+
}
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
215
474
|
|
|
216
|
-
|
|
217
|
-
|
|
475
|
+
// Get first result to determine result type
|
|
476
|
+
const firstResult = cmdOpt.metaAsArray ? results[0][0] : results[0];
|
|
218
477
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
225
|
-
}
|
|
478
|
+
// Process based on result type
|
|
479
|
+
if (firstResult instanceof OkPacket) {
|
|
480
|
+
this.aggregateOkPackets(results, cmdOpt, resolve);
|
|
481
|
+
} else {
|
|
482
|
+
this.aggregateResultSets(results, cmdOpt, resolve);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
226
485
|
|
|
227
486
|
/**
|
|
228
|
-
*
|
|
229
|
-
* to permit streaming big result-set
|
|
487
|
+
* Aggregate OK packets from multiple executions
|
|
230
488
|
*
|
|
231
|
-
* @param
|
|
232
|
-
*
|
|
233
|
-
* @param
|
|
234
|
-
* @
|
|
489
|
+
* @param {Array} results - Array of individual results
|
|
490
|
+
* @param {Object} cmdOpt - Command options
|
|
491
|
+
* @param {Function} resolve - Promise resolve function
|
|
492
|
+
* @private
|
|
235
493
|
*/
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
494
|
+
aggregateOkPackets(results, cmdOpt, resolve) {
|
|
495
|
+
// Get first packet's insertId and last packet's warning status
|
|
496
|
+
const insertId = results[0].insertId;
|
|
497
|
+
const warningStatus = results[results.length - 1].warningStatus;
|
|
498
|
+
let affectedRows = 0;
|
|
499
|
+
|
|
500
|
+
if (cmdOpt.metaAsArray) {
|
|
501
|
+
// Use reduce for better performance with large result sets
|
|
502
|
+
affectedRows = results.reduce((sum, result) => sum + result[0].affectedRows, 0);
|
|
503
|
+
resolve([new OkPacket(affectedRows, insertId, warningStatus), []]);
|
|
244
504
|
} else {
|
|
245
|
-
|
|
505
|
+
affectedRows = results.reduce((sum, result) => sum + result.affectedRows, 0);
|
|
506
|
+
resolve(new OkPacket(affectedRows, insertId, warningStatus));
|
|
246
507
|
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Aggregate result sets from multiple executions
|
|
512
|
+
*
|
|
513
|
+
* @param {Array} results - Array of individual results
|
|
514
|
+
* @param {Object} cmdOpt - Command options
|
|
515
|
+
* @param {Function} resolve - Promise resolve function
|
|
516
|
+
* @private
|
|
517
|
+
*/
|
|
518
|
+
aggregateResultSets(results, cmdOpt, resolve) {
|
|
519
|
+
if (cmdOpt.metaAsArray) {
|
|
520
|
+
// Calculate total length to avoid resizing
|
|
521
|
+
const totalLength = results.reduce((sum, row) => sum + (row[0]?.length || 0), 0);
|
|
522
|
+
const rs = new Array(totalLength);
|
|
523
|
+
|
|
524
|
+
// Efficiently copy all results into a single array
|
|
525
|
+
let index = 0;
|
|
526
|
+
for (const row of results) {
|
|
527
|
+
if (row[0] && row[0].length) {
|
|
528
|
+
const rowData = row[0];
|
|
529
|
+
for (let i = 0; i < rowData.length; i++) {
|
|
530
|
+
rs[index++] = rowData[i];
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
resolve([rs.slice(0, index), results[0][1]]);
|
|
536
|
+
} else {
|
|
537
|
+
// Calculate total length to avoid resizing
|
|
538
|
+
const totalLength = results.reduce((sum, row) => sum + (Array.isArray(row) ? row.length : 0), 0);
|
|
539
|
+
const rs = new Array(totalLength);
|
|
540
|
+
|
|
541
|
+
// Efficiently copy all results into a single array
|
|
542
|
+
let index = 0;
|
|
543
|
+
for (const row of results) {
|
|
544
|
+
if (Array.isArray(row) && row.length) {
|
|
545
|
+
for (let i = 0; i < row.length; i++) {
|
|
546
|
+
rs[index++] = row[i];
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Create final result array and add metadata
|
|
552
|
+
const finalResult = rs.slice(0, index);
|
|
247
553
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
554
|
+
// Add metadata as non-enumerable property
|
|
555
|
+
if (results[0] && results[0].meta) {
|
|
556
|
+
Object.defineProperty(finalResult, 'meta', {
|
|
557
|
+
value: results[0].meta,
|
|
558
|
+
writable: true,
|
|
559
|
+
enumerable: cmdOpt.metaEnumerable
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
resolve(finalResult);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
253
566
|
|
|
254
567
|
/**
|
|
255
568
|
* Send an empty MySQL packet to ensure connection is active, and reset @@wait_timeout
|
|
256
|
-
* @param
|
|
257
|
-
* @
|
|
569
|
+
* @param {Object} cmdParam - command context
|
|
570
|
+
* @param {Function} resolve - success function
|
|
571
|
+
* @param {Function} reject - rejection function
|
|
258
572
|
*/
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
573
|
+
ping(cmdParam, resolve, reject) {
|
|
574
|
+
// Handle custom timeout if provided
|
|
575
|
+
if (cmdParam.opts && cmdParam.opts.timeout !== undefined) {
|
|
576
|
+
// Validate timeout value
|
|
577
|
+
if (cmdParam.opts.timeout < 0) {
|
|
578
|
+
const err = Errors.createError(
|
|
579
|
+
'Ping cannot have negative timeout value',
|
|
580
|
+
Errors.ER_BAD_PARAMETER_VALUE,
|
|
581
|
+
this.info,
|
|
582
|
+
'0A000'
|
|
583
|
+
);
|
|
584
|
+
this._logAndReject(reject, err);
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
let timeoutRef = setTimeout(() => {
|
|
589
|
+
// If timeout occurs, mark variable as cleared to avoid double resolving
|
|
590
|
+
timeoutRef = undefined;
|
|
591
|
+
|
|
592
|
+
// Create error with proper details
|
|
593
|
+
const err = Errors.createFatalError('Ping timeout', Errors.ER_PING_TIMEOUT, this.info, '0A000');
|
|
594
|
+
|
|
595
|
+
// Close connection properly
|
|
596
|
+
this.addCommand = this.addCommandDisabled;
|
|
597
|
+
clearTimeout(this.timeout);
|
|
598
|
+
|
|
599
|
+
if (this.status !== Status.CLOSING && this.status !== Status.CLOSED) {
|
|
600
|
+
this.sendQueue.clear();
|
|
601
|
+
this.status = Status.CLOSED;
|
|
602
|
+
this.socket.destroy();
|
|
274
603
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}, timeout);
|
|
289
|
-
return _addCommand(
|
|
290
|
-
new Ping(
|
|
291
|
-
() => {
|
|
292
|
-
clearTimeout(tOut);
|
|
604
|
+
|
|
605
|
+
this.clear();
|
|
606
|
+
this._logAndReject(reject, err);
|
|
607
|
+
}, cmdParam.opts.timeout);
|
|
608
|
+
|
|
609
|
+
// Create ping command with wrapped callbacks to handle timeout
|
|
610
|
+
this.addCommand(
|
|
611
|
+
new Ping(
|
|
612
|
+
cmdParam,
|
|
613
|
+
() => {
|
|
614
|
+
// Successful ping response - clear timeout if it hasn't fired yet
|
|
615
|
+
if (timeoutRef) {
|
|
616
|
+
clearTimeout(timeoutRef);
|
|
293
617
|
resolve();
|
|
294
|
-
},
|
|
295
|
-
(err) => {
|
|
296
|
-
clearTimeout(tOut);
|
|
297
|
-
reject(err);
|
|
298
618
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
619
|
+
},
|
|
620
|
+
(err) => {
|
|
621
|
+
// Error during ping - clear timeout if it hasn't fired yet
|
|
622
|
+
if (timeoutRef) {
|
|
623
|
+
clearTimeout(timeoutRef);
|
|
624
|
+
this._logAndReject(reject, err);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
),
|
|
628
|
+
true
|
|
629
|
+
);
|
|
630
|
+
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// Simple ping without custom timeout
|
|
635
|
+
this.addCommand(new Ping(cmdParam, resolve, reject), true);
|
|
636
|
+
}
|
|
305
637
|
|
|
306
638
|
/**
|
|
307
639
|
* Send a reset command that will
|
|
@@ -311,165 +643,163 @@ function Connection(options) {
|
|
|
311
643
|
* - delete user variables
|
|
312
644
|
* - remove temporary tables
|
|
313
645
|
* - remove all PREPARE statement
|
|
314
|
-
*
|
|
315
|
-
* @returns {Promise} promise
|
|
316
646
|
*/
|
|
317
|
-
|
|
647
|
+
reset(cmdParam, resolve, reject) {
|
|
318
648
|
if (
|
|
319
|
-
(info.isMariaDB() && info.hasMinVersion(10, 2, 4)) ||
|
|
320
|
-
(!info.isMariaDB() && info.hasMinVersion(5, 7, 3))
|
|
649
|
+
(this.info.isMariaDB() && this.info.hasMinVersion(10, 2, 4)) ||
|
|
650
|
+
(!this.info.isMariaDB() && this.info.hasMinVersion(5, 7, 3))
|
|
321
651
|
) {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
652
|
+
const conn = this;
|
|
653
|
+
const resetCmd = new Reset(
|
|
654
|
+
cmdParam,
|
|
655
|
+
() => {
|
|
656
|
+
if (conn.prepareCache) conn.prepareCache.reset();
|
|
657
|
+
let prom = Promise.resolve();
|
|
658
|
+
// re-execute init query / session query timeout
|
|
659
|
+
prom
|
|
660
|
+
.then(conn.handleCharset.bind(conn))
|
|
661
|
+
.then(conn.handleTimezone.bind(conn))
|
|
662
|
+
.then(conn.executeInitQuery.bind(conn))
|
|
663
|
+
.then(conn.executeSessionTimeout.bind(conn))
|
|
664
|
+
.then(resolve)
|
|
665
|
+
.catch(reject);
|
|
666
|
+
},
|
|
667
|
+
reject
|
|
668
|
+
);
|
|
669
|
+
this.addCommand(resetCmd, true);
|
|
670
|
+
return;
|
|
325
671
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
this.info.serverVersion +
|
|
330
|
-
' (requires server MariaDB version 10.2.4+ or MySQL 5.7.3+)'
|
|
331
|
-
)
|
|
672
|
+
|
|
673
|
+
const err = new Error(
|
|
674
|
+
`Reset command not permitted for server ${this.info.serverVersion.raw} (requires server MariaDB version 10.2.4+ or MySQL 5.7.3+)`
|
|
332
675
|
);
|
|
333
|
-
|
|
676
|
+
err.stack = cmdParam.stack;
|
|
677
|
+
this._logAndReject(reject, err);
|
|
678
|
+
}
|
|
334
679
|
|
|
335
680
|
/**
|
|
336
681
|
* Indicates the state of the connection as the driver knows it
|
|
337
682
|
* @returns {boolean}
|
|
338
683
|
*/
|
|
339
|
-
|
|
340
|
-
return
|
|
341
|
-
}
|
|
684
|
+
isValid() {
|
|
685
|
+
return this.status === Status.CONNECTED;
|
|
686
|
+
}
|
|
342
687
|
|
|
343
688
|
/**
|
|
344
689
|
* Terminate connection gracefully.
|
|
345
|
-
*
|
|
346
|
-
* @returns {Promise} promise
|
|
347
|
-
*/
|
|
348
|
-
this.end = () => {
|
|
349
|
-
_addCommand = _addCommandDisabled;
|
|
350
|
-
clearTimeout(_timeout);
|
|
351
|
-
|
|
352
|
-
if (
|
|
353
|
-
_status !== Status.CLOSING &&
|
|
354
|
-
_status !== Status.CLOSED &&
|
|
355
|
-
_status !== Status.NOT_CONNECTED
|
|
356
|
-
) {
|
|
357
|
-
_status = Status.CLOSING;
|
|
358
|
-
return new Promise(function (resolve, reject) {
|
|
359
|
-
const ended = () => {
|
|
360
|
-
_status = Status.CLOSED;
|
|
361
|
-
_socket.destroy();
|
|
362
|
-
_socket.unref();
|
|
363
|
-
_clear();
|
|
364
|
-
_receiveQueue.clear();
|
|
365
|
-
resolve();
|
|
366
|
-
};
|
|
367
|
-
const quitCmd = new Quit(ended, ended);
|
|
368
|
-
_sendQueue.push(quitCmd);
|
|
369
|
-
_receiveQueue.push(quitCmd);
|
|
370
|
-
if (_sendQueue.length === 1) {
|
|
371
|
-
process.nextTick(_nextSendCmd.bind(this));
|
|
372
|
-
}
|
|
373
|
-
});
|
|
374
|
-
}
|
|
375
|
-
return Promise.resolve();
|
|
376
|
-
};
|
|
377
|
-
|
|
378
|
-
/**
|
|
379
|
-
* Alias for destroy.
|
|
380
690
|
*/
|
|
381
|
-
|
|
382
|
-
this.
|
|
383
|
-
|
|
691
|
+
end(cmdParam, resolve, reject) {
|
|
692
|
+
this.addCommand = this.addCommandDisabled;
|
|
693
|
+
clearTimeout(this.timeout);
|
|
694
|
+
|
|
695
|
+
if (this.status < Status.CLOSING && this.status !== Status.NOT_CONNECTED) {
|
|
696
|
+
this.status = Status.CLOSING;
|
|
697
|
+
const ended = () => {
|
|
698
|
+
this.status = Status.CLOSED;
|
|
699
|
+
this.socket.destroy();
|
|
700
|
+
this.socket.unref();
|
|
701
|
+
this.clear();
|
|
702
|
+
this.receiveQueue.clear();
|
|
703
|
+
resolve();
|
|
704
|
+
};
|
|
705
|
+
const quitCmd = new Quit(cmdParam, ended, ended);
|
|
706
|
+
this.sendQueue.push(quitCmd);
|
|
707
|
+
this.receiveQueue.push(quitCmd);
|
|
708
|
+
if (this.sendQueue.length === 1) {
|
|
709
|
+
process.nextTick(this.nextSendCmd.bind(this));
|
|
710
|
+
}
|
|
711
|
+
} else resolve();
|
|
712
|
+
}
|
|
384
713
|
|
|
385
714
|
/**
|
|
386
715
|
* Force connection termination by closing the underlying socket and killing server process if any.
|
|
387
716
|
*/
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
clearTimeout(
|
|
391
|
-
if (
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
if (
|
|
717
|
+
destroy() {
|
|
718
|
+
this.addCommand = this.addCommandDisabled;
|
|
719
|
+
clearTimeout(this.timeout);
|
|
720
|
+
if (this.status < Status.CLOSING) {
|
|
721
|
+
this.status = Status.CLOSING;
|
|
722
|
+
this.sendQueue.clear();
|
|
723
|
+
if (this.receiveQueue.length > 0) {
|
|
395
724
|
//socket is closed, but server may still be processing a huge select
|
|
396
725
|
//only possibility is to kill process by another thread
|
|
397
726
|
//TODO reuse a pool connection to avoid connection creation
|
|
398
727
|
const self = this;
|
|
399
|
-
|
|
728
|
+
|
|
729
|
+
// relying on IP in place of DNS to ensure using same server
|
|
730
|
+
const remoteAddress = this.socket.remoteAddress;
|
|
731
|
+
const connOption = remoteAddress ? Object.assign({}, this.opts, { host: remoteAddress }) : this.opts;
|
|
732
|
+
|
|
733
|
+
const killCon = new Connection(connOption);
|
|
400
734
|
killCon
|
|
401
735
|
.connect()
|
|
402
736
|
.then(() => {
|
|
403
737
|
//*************************************************
|
|
404
738
|
//kill connection
|
|
405
739
|
//*************************************************
|
|
406
|
-
|
|
407
|
-
const destroyError = Errors.
|
|
740
|
+
new Promise(killCon.query.bind(killCon, { sql: `KILL ${self.info.threadId}` })).finally((err) => {
|
|
741
|
+
const destroyError = Errors.createFatalError(
|
|
408
742
|
'Connection destroyed, command was killed',
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
info,
|
|
412
|
-
'08S01',
|
|
413
|
-
Errors.ER_CMD_NOT_EXECUTED_DESTROYED
|
|
743
|
+
Errors.ER_CMD_NOT_EXECUTED_DESTROYED,
|
|
744
|
+
self.info
|
|
414
745
|
);
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
.
|
|
425
|
-
.
|
|
426
|
-
|
|
746
|
+
if (self.opts.logger.error) self.opts.logger.error(destroyError);
|
|
747
|
+
self.socketErrorDispatchToQueries(destroyError);
|
|
748
|
+
if (self.socket) {
|
|
749
|
+
const sok = self.socket;
|
|
750
|
+
process.nextTick(() => {
|
|
751
|
+
sok.destroy();
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
self.status = Status.CLOSED;
|
|
755
|
+
self.clear();
|
|
756
|
+
new Promise(killCon.end.bind(killCon)).catch(() => {});
|
|
757
|
+
});
|
|
427
758
|
})
|
|
428
|
-
.catch((
|
|
759
|
+
.catch(() => {
|
|
429
760
|
//*************************************************
|
|
430
761
|
//failing to create a kill connection, end normally
|
|
431
762
|
//*************************************************
|
|
432
763
|
const ended = () => {
|
|
433
|
-
let sock =
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
setImmediate(resolve);
|
|
764
|
+
let sock = self.socket;
|
|
765
|
+
self.clear();
|
|
766
|
+
self.status = Status.CLOSED;
|
|
437
767
|
sock.destroy();
|
|
438
|
-
|
|
768
|
+
self.receiveQueue.clear();
|
|
439
769
|
};
|
|
440
770
|
const quitCmd = new Quit(ended, ended);
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
if (
|
|
444
|
-
process.nextTick(
|
|
771
|
+
self.sendQueue.push(quitCmd);
|
|
772
|
+
self.receiveQueue.push(quitCmd);
|
|
773
|
+
if (self.sendQueue.length === 1) {
|
|
774
|
+
process.nextTick(self.nextSendCmd.bind(self));
|
|
445
775
|
}
|
|
446
776
|
});
|
|
447
777
|
} else {
|
|
448
|
-
|
|
449
|
-
|
|
778
|
+
this.status = Status.CLOSED;
|
|
779
|
+
this.socket.destroy();
|
|
780
|
+
this.clear();
|
|
450
781
|
}
|
|
451
782
|
}
|
|
452
|
-
|
|
453
|
-
};
|
|
783
|
+
}
|
|
454
784
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
}
|
|
785
|
+
pause() {
|
|
786
|
+
this.socket.pause();
|
|
787
|
+
}
|
|
458
788
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
}
|
|
789
|
+
resume() {
|
|
790
|
+
this.socket.resume();
|
|
791
|
+
}
|
|
462
792
|
|
|
463
|
-
|
|
464
|
-
|
|
793
|
+
format(sql, values) {
|
|
794
|
+
const err = Errors.createError(
|
|
465
795
|
'"Connection.format intentionally not implemented. please use Connection.query(sql, values), it will be more secure and faster',
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
'0A000',
|
|
470
|
-
Errors.ER_NOT_IMPLEMENTED_FORMAT
|
|
796
|
+
Errors.ER_NOT_IMPLEMENTED_FORMAT,
|
|
797
|
+
this.info,
|
|
798
|
+
'0A000'
|
|
471
799
|
);
|
|
472
|
-
|
|
800
|
+
if (this.opts.logger.error) this.opts.logger.error(err);
|
|
801
|
+
throw err;
|
|
802
|
+
}
|
|
473
803
|
|
|
474
804
|
//*****************************************************************
|
|
475
805
|
// additional public methods
|
|
@@ -480,594 +810,560 @@ function Connection(options) {
|
|
|
480
810
|
*
|
|
481
811
|
* @returns {*}
|
|
482
812
|
*/
|
|
483
|
-
|
|
484
|
-
if (!info.serverVersion)
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
813
|
+
serverVersion() {
|
|
814
|
+
if (!this.info.serverVersion) {
|
|
815
|
+
const err = new Error('cannot know if server information until connection is established');
|
|
816
|
+
if (this.opts.logger.error) this.opts.logger.error(err);
|
|
817
|
+
throw err;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
return this.info.serverVersion.raw;
|
|
821
|
+
}
|
|
488
822
|
|
|
489
823
|
/**
|
|
490
824
|
* Change option "debug" during connection.
|
|
491
825
|
* @param val debug value
|
|
492
826
|
*/
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
opts.
|
|
500
|
-
}
|
|
827
|
+
debug(val) {
|
|
828
|
+
if (typeof val === 'boolean') {
|
|
829
|
+
if (val && !this.opts.logger.network) this.opts.logger.network = console.log;
|
|
830
|
+
} else if (typeof val === 'function') {
|
|
831
|
+
this.opts.logger.network = val;
|
|
832
|
+
}
|
|
833
|
+
this.opts.emit('debug', val);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
debugCompress(val) {
|
|
837
|
+
if (val) {
|
|
838
|
+
if (typeof val === 'boolean') {
|
|
839
|
+
this.opts.debugCompress = val;
|
|
840
|
+
if (val && !this.opts.logger.network) this.opts.logger.network = console.log;
|
|
841
|
+
} else if (typeof val === 'function') {
|
|
842
|
+
this.opts.debugCompress = true;
|
|
843
|
+
this.opts.logger.network = val;
|
|
844
|
+
}
|
|
845
|
+
} else this.opts.debugCompress = false;
|
|
846
|
+
}
|
|
501
847
|
|
|
502
848
|
//*****************************************************************
|
|
503
849
|
// internal public testing methods
|
|
504
850
|
//*****************************************************************
|
|
505
851
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
return opts.collation;
|
|
510
|
-
};
|
|
511
|
-
|
|
512
|
-
TestMethods.prototype.getSocket = () => {
|
|
513
|
-
return _socket;
|
|
514
|
-
};
|
|
515
|
-
|
|
516
|
-
this.__tests = new TestMethods();
|
|
852
|
+
get __tests() {
|
|
853
|
+
return new TestMethods(this.info.collation, this.socket);
|
|
854
|
+
}
|
|
517
855
|
|
|
518
856
|
//*****************************************************************
|
|
519
857
|
// internal methods
|
|
520
858
|
//*****************************************************************
|
|
521
859
|
|
|
522
|
-
this._status = () => {
|
|
523
|
-
return _status;
|
|
524
|
-
};
|
|
525
|
-
|
|
526
860
|
/**
|
|
527
|
-
*
|
|
528
|
-
* events to permit streaming big result-set
|
|
861
|
+
* Determine if the bulk protocol can be used for batch operations
|
|
529
862
|
*
|
|
530
|
-
* @param
|
|
531
|
-
*
|
|
532
|
-
* @
|
|
533
|
-
* @
|
|
534
|
-
* @returns {Query} query
|
|
863
|
+
* @param {Array} values - Batch values array
|
|
864
|
+
* @param {Object} options - Batch options
|
|
865
|
+
* @return {boolean} Whether bulk protocol can be used
|
|
866
|
+
* @private
|
|
535
867
|
*/
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
868
|
+
_canUseBulk(values, options) {
|
|
869
|
+
// 1. Check compatibility with fullResult option
|
|
870
|
+
if (options && options.fullResult && (this.info.clientCapabilities & Capabilities.BULK_UNIT_RESULTS) === 0n) {
|
|
871
|
+
return false;
|
|
872
|
+
}
|
|
541
873
|
|
|
542
|
-
if
|
|
543
|
-
|
|
544
|
-
|
|
874
|
+
// 2. Determine if bulk operations are enabled
|
|
875
|
+
const bulkEnable =
|
|
876
|
+
options === undefined || options === null
|
|
877
|
+
? this.opts.bulk
|
|
878
|
+
: options.bulk !== undefined && options.bulk !== null
|
|
879
|
+
? options.bulk
|
|
880
|
+
: this.opts.bulk;
|
|
881
|
+
|
|
882
|
+
// 3. Check if server supports bulk operations
|
|
883
|
+
const serverSupportsBulk =
|
|
884
|
+
this.info.serverVersion &&
|
|
885
|
+
this.info.serverVersion.mariaDb &&
|
|
886
|
+
this.info.hasMinVersion(10, 2, 7) &&
|
|
887
|
+
(this.info.serverCapabilities & Capabilities.MARIADB_CLIENT_STMT_BULK_OPERATIONS) > 0n;
|
|
888
|
+
|
|
889
|
+
// If server doesn't support bulk or it's disabled, return false
|
|
890
|
+
if (!serverSupportsBulk || !bulkEnable) {
|
|
891
|
+
return false;
|
|
545
892
|
}
|
|
546
893
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
if (sql.values) _values = sql.values;
|
|
551
|
-
} else {
|
|
552
|
-
_sql = sql;
|
|
894
|
+
// 4. No need to validate values if none provided
|
|
895
|
+
if (values === undefined) {
|
|
896
|
+
return true;
|
|
553
897
|
}
|
|
554
898
|
|
|
555
|
-
|
|
556
|
-
if (
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
delete rows.meta;
|
|
560
|
-
_cb(null, rows, meta);
|
|
561
|
-
};
|
|
562
|
-
cmd = new Query(resolve, _cb, _cmdOpts, opts, _sql, _values);
|
|
899
|
+
// 5. Validate values based on placeholder type
|
|
900
|
+
if (!this.opts.namedPlaceholders) {
|
|
901
|
+
// For positional parameters
|
|
902
|
+
return this._validatePositionalParameters(values);
|
|
563
903
|
} else {
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
() => {},
|
|
567
|
-
_cmdOpts,
|
|
568
|
-
opts,
|
|
569
|
-
_sql,
|
|
570
|
-
_values
|
|
571
|
-
);
|
|
904
|
+
// For named parameters
|
|
905
|
+
return this._validateNamedParameters(values);
|
|
572
906
|
}
|
|
573
|
-
|
|
574
|
-
cmd._rows[cmd._responseIndex].push(row);
|
|
575
|
-
cmd.emit('data', row);
|
|
576
|
-
};
|
|
577
|
-
|
|
578
|
-
if (opts.trace) Error.captureStackTrace(cmd);
|
|
579
|
-
_addCommand(cmd);
|
|
580
|
-
return cmd;
|
|
581
|
-
};
|
|
907
|
+
}
|
|
582
908
|
|
|
583
909
|
/**
|
|
584
|
-
*
|
|
585
|
-
* events to permit streaming big result-set
|
|
910
|
+
* Validate batch values for positional parameters
|
|
586
911
|
*
|
|
587
|
-
* @param
|
|
588
|
-
*
|
|
589
|
-
* @
|
|
590
|
-
* @param cb callback
|
|
591
|
-
* @returns {Query} query
|
|
912
|
+
* @param {Array} values - Batch values array
|
|
913
|
+
* @return {boolean} Whether values are valid for bulk protocol
|
|
914
|
+
* @private
|
|
592
915
|
*/
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
_values = values,
|
|
597
|
-
_cb = cb;
|
|
598
|
-
|
|
599
|
-
if (typeof values === 'function') {
|
|
600
|
-
_cb = values;
|
|
601
|
-
_values = undefined;
|
|
602
|
-
}
|
|
916
|
+
_validatePositionalParameters(values) {
|
|
917
|
+
// Determine expected parameter length
|
|
918
|
+
const paramLen = Array.isArray(values[0]) ? values[0].length : values[0] ? 1 : 0;
|
|
603
919
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
if (sql.values) _values = sql.values;
|
|
608
|
-
} else {
|
|
609
|
-
_sql = sql;
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
if (_values !== undefined) {
|
|
613
|
-
_values = Array.isArray(_values) ? _values : [_values];
|
|
920
|
+
// If no parameters, can't use bulk
|
|
921
|
+
if (paramLen === 0) {
|
|
922
|
+
return false;
|
|
614
923
|
}
|
|
615
924
|
|
|
616
|
-
|
|
925
|
+
// Check parameter consistency and streaming
|
|
926
|
+
for (const row of values) {
|
|
927
|
+
const rowArray = Array.isArray(row) ? row : [row];
|
|
617
928
|
|
|
618
|
-
|
|
619
|
-
if (
|
|
620
|
-
|
|
621
|
-
Errors.createError(
|
|
622
|
-
'Batch must have values set',
|
|
623
|
-
_sql,
|
|
624
|
-
false,
|
|
625
|
-
info,
|
|
626
|
-
'HY000',
|
|
627
|
-
Errors.ER_BATCH_WITH_NO_VALUES
|
|
628
|
-
)
|
|
629
|
-
);
|
|
929
|
+
// All parameter sets must have same length
|
|
930
|
+
if (paramLen !== rowArray.length) {
|
|
931
|
+
return false;
|
|
630
932
|
}
|
|
631
|
-
return null;
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
let useBulk = canUseBulk(_values);
|
|
635
933
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
_cb(null, rows, meta);
|
|
643
|
-
};
|
|
644
|
-
cmd = new fct(resolve, _cb, _cmdOpts, opts, _sql, _values);
|
|
645
|
-
} else {
|
|
646
|
-
cmd = new fct(
|
|
647
|
-
() => {},
|
|
648
|
-
() => {},
|
|
649
|
-
_cmdOpts,
|
|
650
|
-
opts,
|
|
651
|
-
_sql,
|
|
652
|
-
_values
|
|
653
|
-
);
|
|
934
|
+
// Check for streaming data (not permitted)
|
|
935
|
+
for (const val of rowArray) {
|
|
936
|
+
if (this._isStreamingValue(val)) {
|
|
937
|
+
return false;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
654
940
|
}
|
|
655
|
-
cmd.handleNewRows = (row) => {
|
|
656
|
-
cmd._rows[cmd._responseIndex].push(row);
|
|
657
|
-
cmd.emit('data', row);
|
|
658
|
-
};
|
|
659
941
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
return cmd;
|
|
663
|
-
};
|
|
942
|
+
return true;
|
|
943
|
+
}
|
|
664
944
|
|
|
665
945
|
/**
|
|
666
|
-
*
|
|
946
|
+
* Validate batch values for named parameters
|
|
667
947
|
*
|
|
668
|
-
* @param values
|
|
669
|
-
* @return {boolean}
|
|
948
|
+
* @param {Array} values - Batch values array
|
|
949
|
+
* @return {boolean} Whether values are valid for bulk protocol
|
|
950
|
+
* @private
|
|
670
951
|
*/
|
|
671
|
-
|
|
672
|
-
//
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
info.hasMinVersion(10, 2, 7) &&
|
|
678
|
-
opts.bulk &&
|
|
679
|
-
(info.serverCapabilities & Capabilities.MARIADB_CLIENT_STMT_BULK_OPERATIONS) > BigInt(0);
|
|
680
|
-
|
|
681
|
-
if (useBulk) {
|
|
682
|
-
//ensure that there is no stream object
|
|
683
|
-
if (values !== undefined) {
|
|
684
|
-
if (!opts.namedPlaceholders) {
|
|
685
|
-
//ensure that all parameters have same length
|
|
686
|
-
//single array is considered as an array of single element.
|
|
687
|
-
const paramLen = Array.isArray(values[0]) ? values[0].length : values[0] ? 1 : 0;
|
|
688
|
-
if (paramLen == 0) return false;
|
|
689
|
-
for (let r = 0; r < values.length; r++) {
|
|
690
|
-
let row = values[r];
|
|
691
|
-
if (!Array.isArray(row)) row = [row];
|
|
692
|
-
if (paramLen !== row.length) {
|
|
693
|
-
return false;
|
|
694
|
-
}
|
|
695
|
-
for (let j = 0; j < paramLen; j++) {
|
|
696
|
-
const val = row[j];
|
|
697
|
-
if (
|
|
698
|
-
val !== null &&
|
|
699
|
-
typeof val === 'object' &&
|
|
700
|
-
typeof val.pipe === 'function' &&
|
|
701
|
-
typeof val.read === 'function'
|
|
702
|
-
) {
|
|
703
|
-
return false;
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
} else {
|
|
708
|
-
for (let r = 0; r < values.length; r++) {
|
|
709
|
-
let row = values[r];
|
|
710
|
-
const keys = Object.keys(row);
|
|
711
|
-
for (let j = 0; j < keys.length; j++) {
|
|
712
|
-
const val = row[keys[j]];
|
|
713
|
-
if (
|
|
714
|
-
val !== null &&
|
|
715
|
-
typeof val === 'object' &&
|
|
716
|
-
typeof val.pipe === 'function' &&
|
|
717
|
-
typeof val.read === 'function'
|
|
718
|
-
) {
|
|
719
|
-
return false;
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
}
|
|
952
|
+
_validateNamedParameters(values) {
|
|
953
|
+
// Check each row for streaming values
|
|
954
|
+
for (const row of values) {
|
|
955
|
+
for (const val of Object.values(row)) {
|
|
956
|
+
if (this._isStreamingValue(val)) {
|
|
957
|
+
return false;
|
|
723
958
|
}
|
|
724
959
|
}
|
|
725
960
|
}
|
|
726
|
-
return
|
|
727
|
-
}
|
|
961
|
+
return true;
|
|
962
|
+
}
|
|
728
963
|
|
|
729
964
|
/**
|
|
730
|
-
*
|
|
965
|
+
* Check if a value is a streaming value
|
|
731
966
|
*
|
|
967
|
+
* @param {*} val - Value to check
|
|
968
|
+
* @return {boolean} Whether value is a streaming value
|
|
732
969
|
* @private
|
|
733
970
|
*/
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
const handshake = new Handshake(
|
|
739
|
-
_authSucceed,
|
|
740
|
-
_authFail,
|
|
741
|
-
_createSecureContext.bind(this, _authFail),
|
|
742
|
-
_addCommandEnable.bind(this),
|
|
743
|
-
_getSocket
|
|
744
|
-
);
|
|
745
|
-
Error.captureStackTrace(handshake);
|
|
746
|
-
|
|
747
|
-
handshake.once('end', () => {
|
|
748
|
-
process.nextTick(_nextSendCmd);
|
|
749
|
-
});
|
|
750
|
-
|
|
751
|
-
_receiveQueue.push(handshake);
|
|
752
|
-
_initSocket(_authFail);
|
|
753
|
-
};
|
|
971
|
+
_isStreamingValue(val) {
|
|
972
|
+
return val != null && typeof val === 'object' && typeof val.pipe === 'function' && typeof val.read === 'function';
|
|
973
|
+
}
|
|
754
974
|
|
|
755
|
-
|
|
756
|
-
if (opts.sessionVariables) {
|
|
975
|
+
executeSessionVariableQuery() {
|
|
976
|
+
if (this.opts.sessionVariables) {
|
|
757
977
|
const values = [];
|
|
758
978
|
let sessionQuery = 'set ';
|
|
759
|
-
let keys = Object.keys(opts.sessionVariables);
|
|
979
|
+
let keys = Object.keys(this.opts.sessionVariables);
|
|
760
980
|
if (keys.length > 0) {
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
const cmd = new Query(resolve, errorHandling, null, opts, sessionQuery, values);
|
|
784
|
-
if (opts.trace) Error.captureStackTrace(cmd);
|
|
785
|
-
_addCommand(cmd);
|
|
981
|
+
for (let k = 0; k < keys.length; ++k) {
|
|
982
|
+
sessionQuery += (k !== 0 ? ',' : '') + '@@' + keys[k].replace(/[^a-z0-9_]/gi, '') + '=?';
|
|
983
|
+
values.push(this.opts.sessionVariables[keys[k]]);
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
return new Promise(
|
|
987
|
+
this.query.bind(this, {
|
|
988
|
+
sql: sessionQuery,
|
|
989
|
+
values: values
|
|
990
|
+
})
|
|
991
|
+
).catch((initialErr) => {
|
|
992
|
+
const err = Errors.createFatalError(
|
|
993
|
+
`Error setting session variable (value ${JSON.stringify(this.opts.sessionVariables)}). Error: ${
|
|
994
|
+
initialErr.message
|
|
995
|
+
}`,
|
|
996
|
+
Errors.ER_SETTING_SESSION_ERROR,
|
|
997
|
+
this.info,
|
|
998
|
+
'08S01',
|
|
999
|
+
sessionQuery
|
|
1000
|
+
);
|
|
1001
|
+
if (this.opts.logger.error) this.opts.logger.error(err);
|
|
1002
|
+
return Promise.reject(err);
|
|
786
1003
|
});
|
|
787
1004
|
}
|
|
788
1005
|
}
|
|
789
1006
|
return Promise.resolve();
|
|
790
|
-
}
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
/**
|
|
1010
|
+
* set charset to charset/collation if set or utf8mb4 if not.
|
|
1011
|
+
* @returns {Promise<void>}
|
|
1012
|
+
* @private
|
|
1013
|
+
*/
|
|
1014
|
+
handleCharset() {
|
|
1015
|
+
if (this.opts.collation) {
|
|
1016
|
+
// if index <= 255, skip command, since collation has already been set during handshake response.
|
|
1017
|
+
if (this.opts.collation.index <= 255) return Promise.resolve();
|
|
1018
|
+
const charset =
|
|
1019
|
+
this.opts.collation.charset === 'utf8' && this.opts.collation.maxLength === 4
|
|
1020
|
+
? 'utf8mb4'
|
|
1021
|
+
: this.opts.collation.charset;
|
|
1022
|
+
return new Promise(
|
|
1023
|
+
this.query.bind(this, {
|
|
1024
|
+
sql: `SET NAMES ${charset} COLLATE ${this.opts.collation.name}`
|
|
1025
|
+
})
|
|
1026
|
+
);
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
// MXS-4635: server can some information directly on first Ok_Packet, like not truncated collation
|
|
1030
|
+
// in this case, avoid useless SET NAMES utf8mb4 command
|
|
1031
|
+
if (
|
|
1032
|
+
!this.opts.charset &&
|
|
1033
|
+
this.info.collation &&
|
|
1034
|
+
this.info.collation.charset === 'utf8' &&
|
|
1035
|
+
this.info.collation.maxLength === 4
|
|
1036
|
+
) {
|
|
1037
|
+
this.info.collation = Collations.fromCharset('utf8mb4');
|
|
1038
|
+
return Promise.resolve();
|
|
1039
|
+
}
|
|
1040
|
+
const connCharset = this.opts.charset ? this.opts.charset : 'utf8mb4';
|
|
1041
|
+
this.info.collation = Collations.fromCharset(connCharset);
|
|
1042
|
+
return new Promise(
|
|
1043
|
+
this.query.bind(this, {
|
|
1044
|
+
sql: `SET NAMES ${connCharset}`
|
|
1045
|
+
})
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
791
1048
|
|
|
792
1049
|
/**
|
|
793
1050
|
* Asking server timezone if not set in case of 'auto'
|
|
794
1051
|
* @returns {Promise<void>}
|
|
795
1052
|
* @private
|
|
796
1053
|
*/
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
1054
|
+
handleTimezone() {
|
|
1055
|
+
const conn = this;
|
|
1056
|
+
if (this.opts.timezone === 'local') this.opts.timezone = undefined;
|
|
1057
|
+
if (this.opts.timezone === 'auto') {
|
|
1058
|
+
return new Promise(
|
|
1059
|
+
this.query.bind(this, {
|
|
1060
|
+
sql: 'SELECT @@system_time_zone stz, @@time_zone tz'
|
|
1061
|
+
})
|
|
1062
|
+
).then((res) => {
|
|
800
1063
|
const serverTimezone = res[0].tz === 'SYSTEM' ? res[0].stz : res[0].tz;
|
|
801
|
-
const
|
|
802
|
-
if (
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
opts.tz = null;
|
|
807
|
-
} else {
|
|
808
|
-
opts._localTz = localTz;
|
|
809
|
-
opts.tz = serverTimezone;
|
|
810
|
-
}
|
|
811
|
-
} else {
|
|
812
|
-
return Promise.reject(
|
|
813
|
-
Errors.createError(
|
|
814
|
-
"Automatic timezone setting fails. Server timezone '" +
|
|
815
|
-
serverTimezone +
|
|
816
|
-
"' does't have a corresponding IANA timezone. Option timezone must be set according to server timezone",
|
|
817
|
-
null,
|
|
818
|
-
true,
|
|
819
|
-
info,
|
|
820
|
-
'08S01',
|
|
821
|
-
Errors.ER_WRONG_AUTO_TIMEZONE
|
|
822
|
-
)
|
|
823
|
-
);
|
|
1064
|
+
const localTz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
1065
|
+
if (serverTimezone === localTz || convertFixedTime(serverTimezone, conn) === convertFixedTime(localTz, conn)) {
|
|
1066
|
+
//server timezone is identical to client tz, skipping setting
|
|
1067
|
+
this.opts.timezone = localTz;
|
|
1068
|
+
return Promise.resolve();
|
|
824
1069
|
}
|
|
825
|
-
return
|
|
1070
|
+
return this._setSessionTimezone(convertFixedTime(localTz, conn));
|
|
826
1071
|
});
|
|
827
1072
|
}
|
|
828
|
-
if (opts.tz && !opts.skipSetTimezone) {
|
|
829
|
-
let tz = opts.tz;
|
|
830
|
-
if (opts.tz === 'Etc/UTC') {
|
|
831
|
-
tz = '+00:00';
|
|
832
|
-
} else if (opts.tz.startsWith('Etc/GMT')) {
|
|
833
|
-
let zone = moment.tz.zone(opts.tz);
|
|
834
|
-
tz = zone.abbrs[0] + ':00';
|
|
835
|
-
}
|
|
836
1073
|
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
return Promise.resolve();
|
|
840
|
-
})
|
|
841
|
-
.catch((err) => {
|
|
842
|
-
console.log(
|
|
843
|
-
`warning: setting timezone '${opts.tz}' fails on server.\n look at https://mariadb.com/kb/en/mysql_tzinfo_to_sql/ to load IANA timezone.\nSetting timezone can be disabled with option \`skipSetTimezone\``
|
|
844
|
-
);
|
|
845
|
-
return Promise.resolve();
|
|
846
|
-
});
|
|
1074
|
+
if (this.opts.timezone) {
|
|
1075
|
+
return this._setSessionTimezone(convertFixedTime(this.opts.timezone, conn));
|
|
847
1076
|
}
|
|
848
1077
|
return Promise.resolve();
|
|
849
|
-
}
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
_setSessionTimezone(tz) {
|
|
1081
|
+
return new Promise(
|
|
1082
|
+
this.query.bind(this, {
|
|
1083
|
+
sql: 'SET time_zone=?',
|
|
1084
|
+
values: [tz]
|
|
1085
|
+
})
|
|
1086
|
+
).catch((err) => {
|
|
1087
|
+
const er = Errors.createFatalError(
|
|
1088
|
+
`setting timezone '${tz}' fails on server.\n look at https://mariadb.com/kb/en/mysql_tzinfo_to_sql/ to load IANA timezone. `,
|
|
1089
|
+
Errors.ER_WRONG_IANA_TIMEZONE,
|
|
1090
|
+
this.info
|
|
1091
|
+
);
|
|
1092
|
+
if (this.opts.logger.error) this.opts.logger.error(er);
|
|
1093
|
+
return Promise.reject(er);
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
850
1096
|
|
|
851
|
-
|
|
852
|
-
if (!opts.forceVersionCheck) {
|
|
1097
|
+
checkServerVersion() {
|
|
1098
|
+
if (!this.opts.forceVersionCheck) {
|
|
853
1099
|
return Promise.resolve();
|
|
854
1100
|
}
|
|
855
|
-
return
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
1101
|
+
return new Promise(
|
|
1102
|
+
this.query.bind(this, {
|
|
1103
|
+
sql: 'SELECT @@VERSION AS v'
|
|
1104
|
+
})
|
|
1105
|
+
).then(
|
|
1106
|
+
function (res) {
|
|
1107
|
+
this.info.serverVersion.raw = res[0].v;
|
|
1108
|
+
this.info.serverVersion.mariaDb = this.info.serverVersion.raw.includes('MariaDB');
|
|
1109
|
+
ConnectionInformation.parseVersionString(this.info);
|
|
1110
|
+
return Promise.resolve();
|
|
1111
|
+
}.bind(this)
|
|
1112
|
+
);
|
|
1113
|
+
}
|
|
862
1114
|
|
|
863
|
-
|
|
864
|
-
if (opts.initSql) {
|
|
865
|
-
const initialArr = Array.isArray(opts.initSql) ? opts.initSql : [opts.initSql];
|
|
1115
|
+
executeInitQuery() {
|
|
1116
|
+
if (this.opts.initSql) {
|
|
1117
|
+
const initialArr = Array.isArray(this.opts.initSql) ? this.opts.initSql : [this.opts.initSql];
|
|
866
1118
|
const initialPromises = [];
|
|
867
1119
|
initialArr.forEach((sql) => {
|
|
868
|
-
initialPromises.push(
|
|
1120
|
+
initialPromises.push(
|
|
1121
|
+
new Promise(
|
|
1122
|
+
this.query.bind(this, {
|
|
1123
|
+
sql: sql
|
|
1124
|
+
})
|
|
1125
|
+
)
|
|
1126
|
+
);
|
|
869
1127
|
});
|
|
870
1128
|
|
|
871
1129
|
return Promise.all(initialPromises).catch((initialErr) => {
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
true,
|
|
877
|
-
info,
|
|
878
|
-
'08S01',
|
|
879
|
-
Errors.ER_INITIAL_SQL_ERROR,
|
|
880
|
-
null
|
|
881
|
-
)
|
|
1130
|
+
const err = Errors.createFatalError(
|
|
1131
|
+
`Error executing initial sql command: ${initialErr.message}`,
|
|
1132
|
+
Errors.ER_INITIAL_SQL_ERROR,
|
|
1133
|
+
this.info
|
|
882
1134
|
);
|
|
1135
|
+
if (this.opts.logger.error) this.opts.logger.error(err);
|
|
1136
|
+
return Promise.reject(err);
|
|
883
1137
|
});
|
|
884
1138
|
}
|
|
885
1139
|
return Promise.resolve();
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
if (opts.queryTimeout) {
|
|
890
|
-
if (info.isMariaDB() && info.hasMinVersion(10, 1, 2)) {
|
|
891
|
-
const query =
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
executeSessionTimeout() {
|
|
1143
|
+
if (this.opts.queryTimeout) {
|
|
1144
|
+
if (this.info.isMariaDB() && this.info.hasMinVersion(10, 1, 2)) {
|
|
1145
|
+
const query = `SET max_statement_time=${this.opts.queryTimeout / 1000}`;
|
|
1146
|
+
new Promise(
|
|
1147
|
+
this.query.bind(this, {
|
|
1148
|
+
sql: query
|
|
1149
|
+
})
|
|
1150
|
+
).catch(
|
|
1151
|
+
function (initialErr) {
|
|
1152
|
+
const err = Errors.createFatalError(
|
|
1153
|
+
`Error setting session queryTimeout: ${initialErr.message}`,
|
|
900
1154
|
Errors.ER_INITIAL_TIMEOUT_ERROR,
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
1155
|
+
this.info,
|
|
1156
|
+
'08S01',
|
|
1157
|
+
query
|
|
1158
|
+
);
|
|
1159
|
+
if (this.opts.logger.error) this.opts.logger.error(err);
|
|
1160
|
+
return Promise.reject(err);
|
|
1161
|
+
}.bind(this)
|
|
1162
|
+
);
|
|
905
1163
|
} else {
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
info,
|
|
913
|
-
'HY000',
|
|
914
|
-
Errors.ER_TIMEOUT_NOT_SUPPORTED
|
|
915
|
-
)
|
|
1164
|
+
const err = Errors.createError(
|
|
1165
|
+
`Can only use queryTimeout for MariaDB server after 10.1.1. queryTimeout value: ${this.opts.queryTimeout}`,
|
|
1166
|
+
Errors.ER_TIMEOUT_NOT_SUPPORTED,
|
|
1167
|
+
this.info,
|
|
1168
|
+
'HY000',
|
|
1169
|
+
this.opts.queryTimeout
|
|
916
1170
|
);
|
|
1171
|
+
if (this.opts.logger.error) this.opts.logger.error(err);
|
|
1172
|
+
return Promise.reject(err);
|
|
917
1173
|
}
|
|
918
1174
|
}
|
|
919
1175
|
return Promise.resolve();
|
|
920
|
-
}
|
|
1176
|
+
}
|
|
921
1177
|
|
|
922
|
-
|
|
923
|
-
return
|
|
924
|
-
}
|
|
1178
|
+
getSocket() {
|
|
1179
|
+
return this.socket;
|
|
1180
|
+
}
|
|
925
1181
|
|
|
926
1182
|
/**
|
|
927
1183
|
* Initialize socket and associate events.
|
|
928
1184
|
* @private
|
|
929
1185
|
*/
|
|
930
|
-
|
|
931
|
-
if (opts.
|
|
932
|
-
|
|
933
|
-
} else {
|
|
934
|
-
_socket = Net.connect(opts.port, opts.host);
|
|
1186
|
+
streamInitSocket() {
|
|
1187
|
+
if (this.opts.connectTimeout) {
|
|
1188
|
+
this.timeout = setTimeout(this.connectTimeoutReached.bind(this), this.opts.connectTimeout, Date.now());
|
|
935
1189
|
}
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
opts.
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1190
|
+
if (this.opts.socketPath) {
|
|
1191
|
+
this.socket = Net.connect(this.opts.socketPath);
|
|
1192
|
+
} else if (this.opts.stream) {
|
|
1193
|
+
if (typeof this.opts.stream === 'function') {
|
|
1194
|
+
const tmpSocket = this.opts.stream(
|
|
1195
|
+
function (err, stream) {
|
|
1196
|
+
if (err) {
|
|
1197
|
+
this.authFailHandler(err);
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
this.socket = stream ? stream : Net.connect(this.opts.port, this.opts.host);
|
|
1201
|
+
this.socketInit();
|
|
1202
|
+
}.bind(this)
|
|
1203
|
+
);
|
|
1204
|
+
if (tmpSocket) {
|
|
1205
|
+
this.socket = tmpSocket;
|
|
1206
|
+
this.socketInit();
|
|
1207
|
+
}
|
|
1208
|
+
} else {
|
|
1209
|
+
this.authFailHandler(
|
|
1210
|
+
Errors.createError(
|
|
1211
|
+
'stream option is not a function. stream must be a function with (error, callback) parameter',
|
|
1212
|
+
Errors.ER_BAD_PARAMETER_VALUE,
|
|
1213
|
+
this.info
|
|
1214
|
+
)
|
|
1215
|
+
);
|
|
1216
|
+
}
|
|
1217
|
+
return;
|
|
1218
|
+
} else {
|
|
1219
|
+
this.socket = Net.connect(this.opts.port, this.opts.host);
|
|
1220
|
+
this.socket.setNoDelay(true);
|
|
944
1221
|
}
|
|
1222
|
+
this.socketInit();
|
|
1223
|
+
}
|
|
945
1224
|
|
|
946
|
-
|
|
1225
|
+
socketInit() {
|
|
1226
|
+
this.socket.on('data', this.streamIn.onData.bind(this.streamIn));
|
|
1227
|
+
this.socket.on('error', this.socketErrorHandler.bind(this));
|
|
1228
|
+
this.socket.on('end', this.socketErrorHandler.bind(this));
|
|
947
1229
|
|
|
948
|
-
|
|
949
|
-
_socket.on('error', _socketError);
|
|
950
|
-
_socket.on('end', _socketError);
|
|
951
|
-
_socket.on(
|
|
1230
|
+
this.socket.on(
|
|
952
1231
|
'connect',
|
|
953
1232
|
function () {
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
_socket.setTimeout(opts.socketTimeout, _socketTimeoutReached.bind(this, authFailHandler));
|
|
959
|
-
_socket.setNoDelay(true);
|
|
960
|
-
|
|
1233
|
+
if (this.status === Status.CONNECTING) {
|
|
1234
|
+
this.status = Status.AUTHENTICATING;
|
|
1235
|
+
this.socket.setNoDelay(true);
|
|
1236
|
+
this.socket.setTimeout(this.opts.socketTimeout, this.socketTimeoutReached.bind(this));
|
|
961
1237
|
// keep alive for socket. This won't reset server wait_timeout use pool option idleTimeout for that
|
|
962
|
-
if (opts.keepAliveDelay) {
|
|
963
|
-
|
|
1238
|
+
if (this.opts.keepAliveDelay >= 0) {
|
|
1239
|
+
this.socket.setKeepAlive(true, this.opts.keepAliveDelay);
|
|
1240
|
+
} else {
|
|
1241
|
+
this.socket.setKeepAlive(true);
|
|
964
1242
|
}
|
|
965
1243
|
}
|
|
966
1244
|
}.bind(this)
|
|
967
1245
|
);
|
|
968
1246
|
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
}
|
|
1247
|
+
this.socket.writeBuf = (buf) => this.socket.write(buf);
|
|
1248
|
+
this.socket.flush = () => {};
|
|
1249
|
+
this.streamOut.setStream(this.socket);
|
|
1250
|
+
}
|
|
973
1251
|
|
|
974
1252
|
/**
|
|
975
1253
|
* Authentication success result handler.
|
|
976
1254
|
*
|
|
977
1255
|
* @private
|
|
978
1256
|
*/
|
|
979
|
-
|
|
1257
|
+
authSucceedHandler() {
|
|
980
1258
|
//enable packet compression according to option
|
|
981
|
-
if (opts.
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
1259
|
+
if (this.opts.compress) {
|
|
1260
|
+
if (this.info.serverCapabilities & Capabilities.COMPRESS) {
|
|
1261
|
+
this.streamOut.setStream(new CompressionOutputStream(this.socket, this.opts, this.info));
|
|
1262
|
+
this.streamIn = new CompressionInputStream(this.streamIn, this.receiveQueue, this.opts, this.info);
|
|
1263
|
+
this.socket.removeAllListeners('data');
|
|
1264
|
+
this.socket.on('data', this.streamIn.onData.bind(this.streamIn));
|
|
1265
|
+
} else if (this.opts.logger.error) {
|
|
1266
|
+
this.opts.logger.error(
|
|
1267
|
+
Errors.createError(
|
|
1268
|
+
"connection is configured to use packet compression, but the server doesn't have this capability",
|
|
1269
|
+
Errors.ER_COMPRESSION_NOT_SUPPORTED,
|
|
1270
|
+
this.info
|
|
1271
|
+
)
|
|
991
1272
|
);
|
|
992
1273
|
}
|
|
993
1274
|
}
|
|
994
1275
|
|
|
995
|
-
|
|
1276
|
+
this.addCommand = this.opts.pipelining ? this.addCommandEnablePipeline : this.addCommandEnable;
|
|
1277
|
+
const conn = this;
|
|
1278
|
+
this.status = Status.INIT_CMD;
|
|
1279
|
+
this.executeSessionVariableQuery()
|
|
1280
|
+
.then(conn.handleCharset.bind(conn))
|
|
1281
|
+
.then(this.handleTimezone.bind(this))
|
|
1282
|
+
.then(this.checkServerVersion.bind(this))
|
|
1283
|
+
.then(this.executeInitQuery.bind(this))
|
|
1284
|
+
.then(this.executeSessionTimeout.bind(this))
|
|
1285
|
+
.then(() => {
|
|
1286
|
+
clearTimeout(this.timeout);
|
|
1287
|
+
conn.status = Status.CONNECTED;
|
|
1288
|
+
process.nextTick(conn.connectResolveFct, conn);
|
|
996
1289
|
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1290
|
+
const commands = conn.waitingAuthenticationQueue.toArray();
|
|
1291
|
+
commands.forEach((cmd) => {
|
|
1292
|
+
conn.addCommand(cmd, true);
|
|
1293
|
+
});
|
|
1294
|
+
conn.waitingAuthenticationQueue = null;
|
|
1001
1295
|
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
process.nextTick(rejected, err);
|
|
1005
|
-
};
|
|
1006
|
-
_status = Status.INIT_CMD;
|
|
1007
|
-
_executeSessionVariableQuery()
|
|
1008
|
-
.then(() => {
|
|
1009
|
-
return _checkServerTimezone();
|
|
1296
|
+
conn.connectRejectFct = null;
|
|
1297
|
+
conn.connectResolveFct = null;
|
|
1010
1298
|
})
|
|
1011
|
-
.
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
})
|
|
1024
|
-
.catch(errorInitialQueries);
|
|
1025
|
-
};
|
|
1299
|
+
.catch((err) => {
|
|
1300
|
+
if (!err.fatal) {
|
|
1301
|
+
const res = () => {
|
|
1302
|
+
conn.authFailHandler.call(conn, err);
|
|
1303
|
+
};
|
|
1304
|
+
conn.end(res, res);
|
|
1305
|
+
} else {
|
|
1306
|
+
conn.authFailHandler.call(conn, err);
|
|
1307
|
+
}
|
|
1308
|
+
return Promise.reject(err);
|
|
1309
|
+
});
|
|
1310
|
+
}
|
|
1026
1311
|
|
|
1027
1312
|
/**
|
|
1028
1313
|
* Authentication failed result handler.
|
|
1029
1314
|
*
|
|
1030
1315
|
* @private
|
|
1031
1316
|
*/
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1317
|
+
authFailHandler(err) {
|
|
1318
|
+
clearTimeout(this.timeout);
|
|
1319
|
+
if (this.connectRejectFct) {
|
|
1320
|
+
if (this.opts.logger.error) this.opts.logger.error(err);
|
|
1321
|
+
//remove handshake command
|
|
1322
|
+
this.receiveQueue.shift();
|
|
1323
|
+
this.fatalError(err, true);
|
|
1324
|
+
|
|
1325
|
+
process.nextTick(this.connectRejectFct, err);
|
|
1326
|
+
this.connectRejectFct = null;
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1039
1329
|
|
|
1040
1330
|
/**
|
|
1041
1331
|
* Create TLS socket and associate events.
|
|
1042
1332
|
*
|
|
1043
|
-
* @param
|
|
1333
|
+
* @param info current connection information
|
|
1044
1334
|
* @param callback callback function when done
|
|
1045
1335
|
* @private
|
|
1046
1336
|
*/
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1337
|
+
createSecureContext(info, callback) {
|
|
1338
|
+
info.requireValidCert =
|
|
1339
|
+
this.opts.ssl === true ||
|
|
1340
|
+
this.opts.ssl.rejectUnauthorized === undefined ||
|
|
1341
|
+
this.opts.ssl.rejectUnauthorized === true;
|
|
1342
|
+
|
|
1343
|
+
const baseConf = { socket: this.socket };
|
|
1344
|
+
if (info.isMariaDB()) {
|
|
1345
|
+
// for MariaDB servers, permit self-signed certificated
|
|
1346
|
+
// this will be replaced by fingerprint validation with ending OK_PACKET
|
|
1347
|
+
baseConf['rejectUnauthorized'] = false;
|
|
1348
|
+
}
|
|
1349
|
+
const sslOption = this.opts.ssl === true ? baseConf : Object.assign({}, this.opts.ssl, baseConf);
|
|
1053
1350
|
|
|
1054
1351
|
try {
|
|
1055
1352
|
const secureSocket = tls.connect(sslOption, callback);
|
|
1056
|
-
|
|
1057
|
-
secureSocket.on('
|
|
1058
|
-
secureSocket.on('
|
|
1059
|
-
secureSocket.on('end', _socketError);
|
|
1353
|
+
secureSocket.on('data', this.streamIn.onData.bind(this.streamIn));
|
|
1354
|
+
secureSocket.on('error', this.socketErrorHandler.bind(this));
|
|
1355
|
+
secureSocket.on('end', this.socketErrorHandler.bind(this));
|
|
1060
1356
|
secureSocket.writeBuf = (buf) => secureSocket.write(buf);
|
|
1061
1357
|
secureSocket.flush = () => {};
|
|
1062
1358
|
|
|
1063
|
-
|
|
1064
|
-
|
|
1359
|
+
this.socket.removeAllListeners('data');
|
|
1360
|
+
this.socket = secureSocket;
|
|
1065
1361
|
|
|
1066
|
-
|
|
1362
|
+
this.streamOut.setStream(secureSocket);
|
|
1067
1363
|
} catch (err) {
|
|
1068
|
-
|
|
1364
|
+
this.socketErrorHandler(err);
|
|
1069
1365
|
}
|
|
1070
|
-
}
|
|
1366
|
+
}
|
|
1071
1367
|
|
|
1072
1368
|
/**
|
|
1073
1369
|
* Handle packet when no packet is expected.
|
|
@@ -1076,184 +1372,116 @@ function Connection(options) {
|
|
|
1076
1372
|
* @param packet packet
|
|
1077
1373
|
* @private
|
|
1078
1374
|
*/
|
|
1079
|
-
|
|
1375
|
+
unexpectedPacket(packet) {
|
|
1080
1376
|
if (packet && packet.peek() === 0xff) {
|
|
1081
1377
|
//can receive unexpected error packet from server/proxy
|
|
1082
1378
|
//to inform that connection is closed (usually by timeout)
|
|
1083
|
-
let err = packet.readError(info);
|
|
1084
|
-
if (err.fatal &&
|
|
1379
|
+
let err = packet.readError(this.info);
|
|
1380
|
+
if (err.fatal && this.status < Status.CLOSING) {
|
|
1085
1381
|
this.emit('error', err);
|
|
1086
|
-
this.
|
|
1382
|
+
if (this.opts.logger.error) this.opts.logger.error(err);
|
|
1383
|
+
this.end(
|
|
1384
|
+
() => {},
|
|
1385
|
+
() => {}
|
|
1386
|
+
);
|
|
1087
1387
|
}
|
|
1088
|
-
} else if (
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
'(' +
|
|
1096
|
-
packet.pos +
|
|
1097
|
-
',' +
|
|
1098
|
-
packet.end +
|
|
1099
|
-
')\n' +
|
|
1100
|
-
Utils.log(opts, packet.buf, packet.pos, packet.end),
|
|
1101
|
-
null,
|
|
1102
|
-
true,
|
|
1103
|
-
info,
|
|
1104
|
-
'08S01',
|
|
1105
|
-
Errors.ER_UNEXPECTED_PACKET
|
|
1106
|
-
)
|
|
1388
|
+
} else if (this.status < Status.CLOSING) {
|
|
1389
|
+
const err = Errors.createFatalError(
|
|
1390
|
+
`receiving packet from server without active commands\nconn:${this.info.threadId ? this.info.threadId : -1}(${
|
|
1391
|
+
packet.pos
|
|
1392
|
+
},${packet.end})\n${Utils.log(this.opts, packet.buf, packet.pos, packet.end)}`,
|
|
1393
|
+
Errors.ER_UNEXPECTED_PACKET,
|
|
1394
|
+
this.info
|
|
1107
1395
|
);
|
|
1396
|
+
if (this.opts.logger.error) this.opts.logger.error(err);
|
|
1397
|
+
this.emit('error', err);
|
|
1108
1398
|
this.destroy();
|
|
1109
1399
|
}
|
|
1110
|
-
}
|
|
1111
|
-
|
|
1112
|
-
/**
|
|
1113
|
-
* Change transaction state.
|
|
1114
|
-
*
|
|
1115
|
-
* @param sql sql
|
|
1116
|
-
* @returns {Promise} promise
|
|
1117
|
-
* @private
|
|
1118
|
-
*/
|
|
1119
|
-
const _changeTransaction = (sql) => {
|
|
1120
|
-
//if command in progress, driver cannot rely on status and must execute query
|
|
1121
|
-
if (_status === Status.CLOSING || _status === Status.CLOSED) {
|
|
1122
|
-
return Promise.reject(
|
|
1123
|
-
Errors.createError(
|
|
1124
|
-
'Cannot execute new commands: connection closed',
|
|
1125
|
-
sql,
|
|
1126
|
-
true,
|
|
1127
|
-
info,
|
|
1128
|
-
'08S01',
|
|
1129
|
-
Errors.ER_CMD_CONNECTION_CLOSED
|
|
1130
|
-
)
|
|
1131
|
-
);
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
//Command in progress => must execute query
|
|
1135
|
-
//or if no command in progress, can rely on status to know if query is needed
|
|
1136
|
-
if (_receiveQueue.peekFront() || info.status & ServerStatus.STATUS_IN_TRANS) {
|
|
1137
|
-
return new Promise(function (resolve, reject) {
|
|
1138
|
-
const cmd = new Query(resolve, reject, null, opts, sql, null);
|
|
1139
|
-
if (opts.trace) Error.captureStackTrace(cmd);
|
|
1140
|
-
_addCommand(cmd);
|
|
1141
|
-
});
|
|
1142
|
-
}
|
|
1143
|
-
return Promise.resolve();
|
|
1144
|
-
};
|
|
1400
|
+
}
|
|
1145
1401
|
|
|
1146
1402
|
/**
|
|
1147
1403
|
* Handle connection timeout.
|
|
1148
1404
|
*
|
|
1149
1405
|
* @private
|
|
1150
1406
|
*/
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
const handshake =
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
info,
|
|
1162
|
-
'08S01',
|
|
1163
|
-
Errors.ER_CONNECTION_TIMEOUT,
|
|
1164
|
-
handshake ? handshake.stack : null
|
|
1165
|
-
)
|
|
1407
|
+
connectTimeoutReached(initialConnectionTime) {
|
|
1408
|
+
this.timeout = null;
|
|
1409
|
+
const handshake = this.receiveQueue.peekFront();
|
|
1410
|
+
const err = Errors.createFatalError(
|
|
1411
|
+
`Connection timeout: failed to create socket after ${Date.now() - initialConnectionTime}ms`,
|
|
1412
|
+
Errors.ER_CONNECTION_TIMEOUT,
|
|
1413
|
+
this.info,
|
|
1414
|
+
'08S01',
|
|
1415
|
+
null,
|
|
1416
|
+
handshake ? handshake.stack : null
|
|
1166
1417
|
);
|
|
1167
|
-
|
|
1418
|
+
if (this.opts.logger.error) this.opts.logger.error(err);
|
|
1419
|
+
this.authFailHandler(err);
|
|
1420
|
+
}
|
|
1168
1421
|
|
|
1169
1422
|
/**
|
|
1170
1423
|
* Handle socket timeout.
|
|
1171
1424
|
*
|
|
1172
1425
|
* @private
|
|
1173
1426
|
*/
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
'08S01',
|
|
1181
|
-
Errors.ER_SOCKET_TIMEOUT
|
|
1182
|
-
);
|
|
1183
|
-
const packetMsgs = info.getLastPackets();
|
|
1184
|
-
if (packetMsgs !== '') {
|
|
1185
|
-
err.message = err.message + '\nlast received packets:\n' + packetMsgs;
|
|
1186
|
-
}
|
|
1187
|
-
_fatalError(err, true);
|
|
1188
|
-
};
|
|
1427
|
+
socketTimeoutReached() {
|
|
1428
|
+
clearTimeout(this.timeout);
|
|
1429
|
+
const err = Errors.createFatalError('socket timeout', Errors.ER_SOCKET_TIMEOUT, this.info);
|
|
1430
|
+
if (this.opts.logger.error) this.opts.logger.error(err);
|
|
1431
|
+
this.fatalError(err, true);
|
|
1432
|
+
}
|
|
1189
1433
|
|
|
1190
1434
|
/**
|
|
1191
1435
|
* Add command to waiting queue until authentication.
|
|
1192
1436
|
*
|
|
1193
1437
|
* @param cmd command
|
|
1194
|
-
* @returns {*} current command
|
|
1195
1438
|
* @private
|
|
1196
1439
|
*/
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
};
|
|
1440
|
+
addCommandQueue(cmd) {
|
|
1441
|
+
this.waitingAuthenticationQueue.push(cmd);
|
|
1442
|
+
}
|
|
1201
1443
|
|
|
1202
1444
|
/**
|
|
1203
1445
|
* Add command to command sending and receiving queue.
|
|
1204
1446
|
*
|
|
1205
1447
|
* @param cmd command
|
|
1206
|
-
* @
|
|
1448
|
+
* @param expectResponse queue command response
|
|
1207
1449
|
* @private
|
|
1208
1450
|
*/
|
|
1209
|
-
|
|
1210
|
-
cmd.once('end', ()
|
|
1211
|
-
setImmediate(_nextSendCmd);
|
|
1212
|
-
});
|
|
1451
|
+
addCommandEnable(cmd, expectResponse) {
|
|
1452
|
+
cmd.once('end', this._sendNextCmdImmediate.bind(this));
|
|
1213
1453
|
|
|
1214
1454
|
//send immediately only if no current active receiver
|
|
1215
|
-
if (
|
|
1216
|
-
if (
|
|
1217
|
-
|
|
1218
|
-
_sendQueue.push(cmd);
|
|
1219
|
-
return cmd;
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
_receiveQueue.push(cmd);
|
|
1223
|
-
cmd.start(_out, opts, info);
|
|
1455
|
+
if (this.sendQueue.isEmpty() && this.receiveQueue.isEmpty()) {
|
|
1456
|
+
if (expectResponse) this.receiveQueue.push(cmd);
|
|
1457
|
+
cmd.start(this.streamOut, this.opts, this.info);
|
|
1224
1458
|
} else {
|
|
1225
|
-
|
|
1226
|
-
|
|
1459
|
+
if (expectResponse) this.receiveQueue.push(cmd);
|
|
1460
|
+
this.sendQueue.push(cmd);
|
|
1227
1461
|
}
|
|
1228
|
-
|
|
1229
|
-
};
|
|
1462
|
+
}
|
|
1230
1463
|
|
|
1231
1464
|
/**
|
|
1232
1465
|
* Add command to command sending and receiving queue using pipelining
|
|
1233
1466
|
*
|
|
1234
|
-
* @param cmd
|
|
1235
|
-
* @
|
|
1467
|
+
* @param cmd command
|
|
1468
|
+
* @param expectResponse queue command response
|
|
1236
1469
|
* @private
|
|
1237
1470
|
*/
|
|
1238
|
-
|
|
1239
|
-
cmd.once('send_end', ()
|
|
1240
|
-
setImmediate(_nextSendCmd);
|
|
1241
|
-
});
|
|
1471
|
+
addCommandEnablePipeline(cmd, expectResponse) {
|
|
1472
|
+
cmd.once('send_end', this._sendNextCmdImmediate.bind(this));
|
|
1242
1473
|
|
|
1243
|
-
|
|
1244
|
-
if (
|
|
1245
|
-
cmd.start(
|
|
1474
|
+
if (expectResponse) this.receiveQueue.push(cmd);
|
|
1475
|
+
if (this.sendQueue.isEmpty()) {
|
|
1476
|
+
cmd.start(this.streamOut, this.opts, this.info);
|
|
1246
1477
|
if (cmd.sending) {
|
|
1247
|
-
|
|
1248
|
-
cmd.prependOnceListener('send_end', ()
|
|
1249
|
-
_sendQueue.shift();
|
|
1250
|
-
});
|
|
1478
|
+
this.sendQueue.push(cmd);
|
|
1479
|
+
cmd.prependOnceListener('send_end', this.sendQueue.shift.bind(this.sendQueue));
|
|
1251
1480
|
}
|
|
1252
1481
|
} else {
|
|
1253
|
-
|
|
1482
|
+
this.sendQueue.push(cmd);
|
|
1254
1483
|
}
|
|
1255
|
-
|
|
1256
|
-
};
|
|
1484
|
+
}
|
|
1257
1485
|
|
|
1258
1486
|
/**
|
|
1259
1487
|
* Replacing command when connection is closing or closed to send a proper error message.
|
|
@@ -1261,111 +1489,95 @@ function Connection(options) {
|
|
|
1261
1489
|
* @param cmd command
|
|
1262
1490
|
* @private
|
|
1263
1491
|
*/
|
|
1264
|
-
|
|
1265
|
-
cmd.throwNewError(
|
|
1492
|
+
addCommandDisabled(cmd) {
|
|
1493
|
+
const err = cmd.throwNewError(
|
|
1266
1494
|
'Cannot execute new commands: connection closed',
|
|
1267
1495
|
true,
|
|
1268
|
-
info,
|
|
1496
|
+
this.info,
|
|
1269
1497
|
'08S01',
|
|
1270
1498
|
Errors.ER_CMD_CONNECTION_CLOSED
|
|
1271
1499
|
);
|
|
1272
|
-
|
|
1500
|
+
if (this.opts.logger.error) this.opts.logger.error(err);
|
|
1501
|
+
}
|
|
1273
1502
|
|
|
1274
1503
|
/**
|
|
1275
1504
|
* Handle socket error.
|
|
1276
1505
|
*
|
|
1277
|
-
* @param authFailHandler authentication handler
|
|
1278
1506
|
* @param err socket error
|
|
1279
1507
|
* @private
|
|
1280
1508
|
*/
|
|
1281
|
-
|
|
1282
|
-
if (
|
|
1283
|
-
if (
|
|
1284
|
-
|
|
1285
|
-
|
|
1509
|
+
socketErrorHandler(err) {
|
|
1510
|
+
if (this.status >= Status.CLOSING) return;
|
|
1511
|
+
if (this.socket) {
|
|
1512
|
+
this.socket.writeBuf = () => {};
|
|
1513
|
+
this.socket.flush = () => {};
|
|
1286
1514
|
}
|
|
1287
1515
|
|
|
1288
1516
|
//socket has been ended without error
|
|
1289
1517
|
if (!err) {
|
|
1290
|
-
err = Errors.
|
|
1518
|
+
err = Errors.createFatalError(
|
|
1291
1519
|
'socket has unexpectedly been closed',
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
info,
|
|
1295
|
-
'08S01',
|
|
1296
|
-
Errors.ER_SOCKET_UNEXPECTED_CLOSE
|
|
1520
|
+
Errors.ER_SOCKET_UNEXPECTED_CLOSE,
|
|
1521
|
+
this.info
|
|
1297
1522
|
);
|
|
1298
1523
|
} else {
|
|
1299
1524
|
err.fatal = true;
|
|
1300
|
-
|
|
1525
|
+
err.sqlState = 'HY000';
|
|
1301
1526
|
}
|
|
1302
1527
|
|
|
1303
|
-
|
|
1304
|
-
if (packetMsgs !== '') {
|
|
1305
|
-
err.message += '\nlast received packets:\n' + packetMsgs;
|
|
1306
|
-
}
|
|
1307
|
-
|
|
1308
|
-
switch (_status) {
|
|
1528
|
+
switch (this.status) {
|
|
1309
1529
|
case Status.CONNECTING:
|
|
1310
1530
|
case Status.AUTHENTICATING:
|
|
1311
|
-
const currentCmd =
|
|
1531
|
+
const currentCmd = this.receiveQueue.peekFront();
|
|
1312
1532
|
if (currentCmd && currentCmd.stack && err) {
|
|
1313
|
-
err.stack +=
|
|
1314
|
-
'\n From event:\n' + currentCmd.stack.substring(currentCmd.stack.indexOf('\n') + 1);
|
|
1533
|
+
err.stack += '\n From event:\n' + currentCmd.stack.substring(currentCmd.stack.indexOf('\n') + 1);
|
|
1315
1534
|
}
|
|
1316
|
-
authFailHandler(err);
|
|
1535
|
+
this.authFailHandler(err);
|
|
1317
1536
|
break;
|
|
1318
1537
|
|
|
1319
1538
|
default:
|
|
1320
|
-
|
|
1539
|
+
this.fatalError(err, false);
|
|
1321
1540
|
}
|
|
1322
|
-
}
|
|
1541
|
+
}
|
|
1323
1542
|
|
|
1324
1543
|
/**
|
|
1325
1544
|
* Fatal unexpected error : closing connection, and throw exception.
|
|
1326
|
-
*
|
|
1327
|
-
* @param self current connection
|
|
1328
|
-
* @private
|
|
1329
1545
|
*/
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
self.emit('error', err);
|
|
1356
|
-
self.emit('end');
|
|
1357
|
-
_clear();
|
|
1358
|
-
} else {
|
|
1359
|
-
self.emit('end');
|
|
1360
|
-
_clear();
|
|
1361
|
-
//error will be thrown if no error listener and no command did throw the exception
|
|
1362
|
-
if (!avoidThrowError && !errorThrownByCmd) throw err;
|
|
1363
|
-
}
|
|
1546
|
+
fatalError(err, avoidThrowError) {
|
|
1547
|
+
if (this.status >= Status.CLOSING) {
|
|
1548
|
+
this.socketErrorDispatchToQueries(err);
|
|
1549
|
+
return;
|
|
1550
|
+
}
|
|
1551
|
+
const mustThrowError = this.status !== Status.CONNECTING;
|
|
1552
|
+
this.status = Status.CLOSING;
|
|
1553
|
+
|
|
1554
|
+
//prevent executing new commands
|
|
1555
|
+
this.addCommand = this.addCommandDisabled;
|
|
1556
|
+
|
|
1557
|
+
if (this.socket) {
|
|
1558
|
+
this.socket.removeAllListeners();
|
|
1559
|
+
if (!this.socket.destroyed) this.socket.destroy();
|
|
1560
|
+
this.socket = undefined;
|
|
1561
|
+
}
|
|
1562
|
+
this.status = Status.CLOSED;
|
|
1563
|
+
|
|
1564
|
+
const errorThrownByCmd = this.socketErrorDispatchToQueries(err);
|
|
1565
|
+
if (mustThrowError) {
|
|
1566
|
+
if (this.opts.logger.error) this.opts.logger.error(err);
|
|
1567
|
+
if (this.listenerCount('error') > 0) {
|
|
1568
|
+
this.emit('error', err);
|
|
1569
|
+
this.emit('end');
|
|
1570
|
+
this.clear();
|
|
1364
1571
|
} else {
|
|
1365
|
-
|
|
1572
|
+
this.emit('end');
|
|
1573
|
+
this.clear();
|
|
1574
|
+
//error will be thrown if no error listener and no command did throw the exception
|
|
1575
|
+
if (!avoidThrowError && !errorThrownByCmd) throw err;
|
|
1366
1576
|
}
|
|
1367
|
-
}
|
|
1368
|
-
|
|
1577
|
+
} else {
|
|
1578
|
+
this.clear();
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1369
1581
|
|
|
1370
1582
|
/**
|
|
1371
1583
|
* Dispatch fatal error to current running queries.
|
|
@@ -1373,88 +1585,599 @@ function Connection(options) {
|
|
|
1373
1585
|
* @param err the fatal error
|
|
1374
1586
|
* @return {boolean} return if error has been relayed to queries
|
|
1375
1587
|
*/
|
|
1376
|
-
|
|
1588
|
+
socketErrorDispatchToQueries(err) {
|
|
1377
1589
|
let receiveCmd;
|
|
1378
1590
|
let errorThrownByCmd = false;
|
|
1379
|
-
while ((receiveCmd =
|
|
1591
|
+
while ((receiveCmd = this.receiveQueue.shift())) {
|
|
1380
1592
|
if (receiveCmd && receiveCmd.onPacketReceive) {
|
|
1381
1593
|
errorThrownByCmd = true;
|
|
1382
|
-
setImmediate(receiveCmd.throwError.bind(receiveCmd
|
|
1594
|
+
setImmediate(receiveCmd.throwError.bind(receiveCmd, err, this.info));
|
|
1383
1595
|
}
|
|
1384
1596
|
}
|
|
1385
1597
|
return errorThrownByCmd;
|
|
1386
|
-
}
|
|
1598
|
+
}
|
|
1387
1599
|
|
|
1388
1600
|
/**
|
|
1389
1601
|
* Will send next command in queue if any.
|
|
1390
1602
|
*
|
|
1391
1603
|
* @private
|
|
1392
1604
|
*/
|
|
1393
|
-
|
|
1605
|
+
nextSendCmd() {
|
|
1394
1606
|
let sendCmd;
|
|
1395
|
-
if ((sendCmd =
|
|
1607
|
+
if ((sendCmd = this.sendQueue.shift())) {
|
|
1396
1608
|
if (sendCmd.sending) {
|
|
1397
|
-
|
|
1609
|
+
this.sendQueue.unshift(sendCmd);
|
|
1398
1610
|
} else {
|
|
1399
|
-
sendCmd.start(
|
|
1611
|
+
sendCmd.start(this.streamOut, this.opts, this.info);
|
|
1400
1612
|
if (sendCmd.sending) {
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
});
|
|
1404
|
-
_sendQueue.unshift(sendCmd);
|
|
1613
|
+
this.sendQueue.unshift(sendCmd);
|
|
1614
|
+
sendCmd.prependOnceListener('send_end', this.sendQueue.shift.bind(this.sendQueue));
|
|
1405
1615
|
}
|
|
1406
1616
|
}
|
|
1407
1617
|
}
|
|
1408
|
-
}
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
/**
|
|
1621
|
+
* Change transaction state.
|
|
1622
|
+
*
|
|
1623
|
+
* @param cmdParam command parameter
|
|
1624
|
+
* @param resolve success function to call
|
|
1625
|
+
* @param reject error function to call
|
|
1626
|
+
* @private
|
|
1627
|
+
*/
|
|
1628
|
+
changeTransaction(cmdParam, resolve, reject) {
|
|
1629
|
+
//if command in progress, driver cannot rely on status and must execute query
|
|
1630
|
+
if (this.status >= Status.CLOSING) {
|
|
1631
|
+
const err = Errors.createFatalError(
|
|
1632
|
+
'Cannot execute new commands: connection closed',
|
|
1633
|
+
Errors.ER_CMD_CONNECTION_CLOSED,
|
|
1634
|
+
this.info,
|
|
1635
|
+
'08S01',
|
|
1636
|
+
cmdParam.sql
|
|
1637
|
+
);
|
|
1638
|
+
this._logAndReject(reject, err);
|
|
1639
|
+
return;
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
//Command in progress => must execute query
|
|
1643
|
+
//or if no command in progress, can rely on status to know if query is needed
|
|
1644
|
+
if (this.receiveQueue.peekFront() || this.info.status & ServerStatus.STATUS_IN_TRANS) {
|
|
1645
|
+
const cmd = new Query(resolve, (err) => this._logAndReject(reject, err), this.opts, cmdParam);
|
|
1646
|
+
this.addCommand(cmd, true);
|
|
1647
|
+
} else resolve();
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
changeUser(cmdParam, resolve, reject) {
|
|
1651
|
+
if (!this.info.isMariaDB()) {
|
|
1652
|
+
const err = Errors.createError(
|
|
1653
|
+
'method changeUser not available for MySQL server due to Bug #83472',
|
|
1654
|
+
Errors.ER_MYSQL_CHANGE_USER_BUG,
|
|
1655
|
+
this.info,
|
|
1656
|
+
'0A000'
|
|
1657
|
+
);
|
|
1658
|
+
this._logAndReject(reject, err);
|
|
1659
|
+
return;
|
|
1660
|
+
}
|
|
1661
|
+
if (this.status < Status.CLOSING) {
|
|
1662
|
+
this.addCommand = this.addCommandEnable;
|
|
1663
|
+
}
|
|
1664
|
+
let conn = this;
|
|
1665
|
+
if (cmdParam.opts && cmdParam.opts.collation && typeof cmdParam.opts.collation === 'string') {
|
|
1666
|
+
const val = cmdParam.opts.collation.toUpperCase();
|
|
1667
|
+
cmdParam.opts.collation = Collations.fromName(cmdParam.opts.collation.toUpperCase());
|
|
1668
|
+
if (cmdParam.opts.collation === undefined) return reject(new RangeError(`Unknown collation '${val}'`));
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
this.addCommand(
|
|
1672
|
+
new ChangeUser(
|
|
1673
|
+
cmdParam,
|
|
1674
|
+
this.opts,
|
|
1675
|
+
(res) => {
|
|
1676
|
+
if (conn.status < Status.CLOSING && conn.opts.pipelining) conn.addCommand = conn.addCommandEnablePipeline;
|
|
1677
|
+
if (cmdParam.opts && cmdParam.opts.collation) conn.opts.collation = cmdParam.opts.collation;
|
|
1678
|
+
conn
|
|
1679
|
+
.handleCharset()
|
|
1680
|
+
.then(() => {
|
|
1681
|
+
if (cmdParam.opts && cmdParam.opts.collation) {
|
|
1682
|
+
conn.info.collation = cmdParam.opts.collation;
|
|
1683
|
+
conn.opts.emit('collation', cmdParam.opts.collation);
|
|
1684
|
+
}
|
|
1685
|
+
resolve(res);
|
|
1686
|
+
})
|
|
1687
|
+
.catch((err) => {
|
|
1688
|
+
const res = () => conn.authFailHandler.call(conn, err);
|
|
1689
|
+
if (!err.fatal) {
|
|
1690
|
+
conn.end(res, res);
|
|
1691
|
+
} else {
|
|
1692
|
+
res();
|
|
1693
|
+
}
|
|
1694
|
+
reject(err);
|
|
1695
|
+
});
|
|
1696
|
+
},
|
|
1697
|
+
this.authFailHandler.bind(this, reject),
|
|
1698
|
+
this.getSocket.bind(this)
|
|
1699
|
+
),
|
|
1700
|
+
true
|
|
1701
|
+
);
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
query(cmdParam, resolve, reject) {
|
|
1705
|
+
if (!cmdParam.sql)
|
|
1706
|
+
return reject(
|
|
1707
|
+
Errors.createError(
|
|
1708
|
+
'sql parameter is mandatory',
|
|
1709
|
+
Errors.ER_UNDEFINED_SQL,
|
|
1710
|
+
this.info,
|
|
1711
|
+
'HY000',
|
|
1712
|
+
null,
|
|
1713
|
+
false,
|
|
1714
|
+
cmdParam.stack
|
|
1715
|
+
)
|
|
1716
|
+
);
|
|
1717
|
+
const cmd = new Query(resolve, (err) => this._logAndReject(reject, err), this.opts, cmdParam);
|
|
1718
|
+
this.addCommand(cmd, true);
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
prepare(cmdParam, resolve, reject) {
|
|
1722
|
+
if (!cmdParam.sql) {
|
|
1723
|
+
reject(Errors.createError('sql parameter is mandatory', Errors.ER_UNDEFINED_SQL, this.info, 'HY000'));
|
|
1724
|
+
return;
|
|
1725
|
+
}
|
|
1726
|
+
if (this.prepareCache && (this.sendQueue.isEmpty() || !this.receiveQueue.peekFront())) {
|
|
1727
|
+
// no command in queue, database is then considered ok, and cache can be search right now
|
|
1728
|
+
const cachedPrepare = this.prepareCache.get(cmdParam.sql);
|
|
1729
|
+
if (cachedPrepare) {
|
|
1730
|
+
resolve(cachedPrepare);
|
|
1731
|
+
return;
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
const cmd = new Prepare(resolve, (err) => this._logAndReject(reject, err), this.opts, cmdParam, this);
|
|
1736
|
+
this.addCommand(cmd, true);
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
prepareExecute(cmdParam, resolve, reject) {
|
|
1740
|
+
if (!cmdParam.sql) {
|
|
1741
|
+
reject(Errors.createError('sql parameter is mandatory', Errors.ER_UNDEFINED_SQL, this.info, 'HY000'));
|
|
1742
|
+
return;
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
if (this.prepareCache && (this.sendQueue.isEmpty() || !this.receiveQueue.peekFront())) {
|
|
1746
|
+
// no command in queue, current database is known, so cache can be search right now
|
|
1747
|
+
const cachedPrepare = this.prepareCache.get(cmdParam.sql);
|
|
1748
|
+
if (cachedPrepare) {
|
|
1749
|
+
this.executePromise(
|
|
1750
|
+
cmdParam,
|
|
1751
|
+
cachedPrepare,
|
|
1752
|
+
(res) => {
|
|
1753
|
+
resolve(res);
|
|
1754
|
+
cachedPrepare.close();
|
|
1755
|
+
},
|
|
1756
|
+
(err) => {
|
|
1757
|
+
reject(err);
|
|
1758
|
+
cachedPrepare.close();
|
|
1759
|
+
}
|
|
1760
|
+
);
|
|
1761
|
+
return;
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
// permit pipelining PREPARE and EXECUTE if mariadb 10.2.4+ and has no streaming
|
|
1766
|
+
const conn = this;
|
|
1767
|
+
if (this.opts.pipelining && this.info.isMariaDB() && this.info.hasMinVersion(10, 2, 4)) {
|
|
1768
|
+
let hasStreamingValue = false;
|
|
1769
|
+
const vals = cmdParam.values ? (Array.isArray(cmdParam.values) ? cmdParam.values : [cmdParam.values]) : [];
|
|
1770
|
+
for (let i = 0; i < vals.length; i++) {
|
|
1771
|
+
const val = vals[i];
|
|
1772
|
+
if (
|
|
1773
|
+
val != null &&
|
|
1774
|
+
typeof val === 'object' &&
|
|
1775
|
+
typeof val.pipe === 'function' &&
|
|
1776
|
+
typeof val.read === 'function'
|
|
1777
|
+
) {
|
|
1778
|
+
hasStreamingValue = true;
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
if (!hasStreamingValue) {
|
|
1782
|
+
let nbExecute = 0;
|
|
1783
|
+
const executeCommand = new Execute(
|
|
1784
|
+
(res) => {
|
|
1785
|
+
if (nbExecute++ === 0) {
|
|
1786
|
+
executeCommand.prepare.close();
|
|
1787
|
+
resolve(res);
|
|
1788
|
+
}
|
|
1789
|
+
},
|
|
1790
|
+
(err) => {
|
|
1791
|
+
if (nbExecute++ === 0) {
|
|
1792
|
+
if (conn.opts.logger.error) conn.opts.logger.error(err);
|
|
1793
|
+
reject(err);
|
|
1794
|
+
if (executeCommand.prepare) {
|
|
1795
|
+
executeCommand.prepare.close();
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
},
|
|
1799
|
+
conn.opts,
|
|
1800
|
+
cmdParam,
|
|
1801
|
+
null
|
|
1802
|
+
);
|
|
1803
|
+
cmdParam.executeCommand = executeCommand;
|
|
1804
|
+
const cmd = new Prepare(
|
|
1805
|
+
(prep) => {
|
|
1806
|
+
if (nbExecute > 0) prep.close();
|
|
1807
|
+
},
|
|
1808
|
+
(err) => {
|
|
1809
|
+
if (nbExecute++ === 0) {
|
|
1810
|
+
if (conn.opts.logger.error) conn.opts.logger.error(err);
|
|
1811
|
+
reject(err);
|
|
1812
|
+
}
|
|
1813
|
+
},
|
|
1814
|
+
conn.opts,
|
|
1815
|
+
cmdParam,
|
|
1816
|
+
conn
|
|
1817
|
+
);
|
|
1818
|
+
conn.addCommand(cmd, true);
|
|
1819
|
+
conn.addCommand(executeCommand, true);
|
|
1820
|
+
return;
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
// execute PREPARE, then EXECUTE
|
|
1825
|
+
const cmd = new Prepare(
|
|
1826
|
+
(prepare) => {
|
|
1827
|
+
conn.executePromise(
|
|
1828
|
+
cmdParam,
|
|
1829
|
+
prepare,
|
|
1830
|
+
(res) => {
|
|
1831
|
+
resolve(res);
|
|
1832
|
+
prepare.close();
|
|
1833
|
+
},
|
|
1834
|
+
(err) => {
|
|
1835
|
+
if (conn.opts.logger.error) conn.opts.logger.error(err);
|
|
1836
|
+
reject(err);
|
|
1837
|
+
prepare.close();
|
|
1838
|
+
}
|
|
1839
|
+
);
|
|
1840
|
+
},
|
|
1841
|
+
(err) => {
|
|
1842
|
+
if (conn.opts.logger.error) conn.opts.logger.error(err);
|
|
1843
|
+
reject(err);
|
|
1844
|
+
},
|
|
1845
|
+
this.opts,
|
|
1846
|
+
cmdParam,
|
|
1847
|
+
conn
|
|
1848
|
+
);
|
|
1849
|
+
conn.addCommand(cmd, true);
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
importFile(cmdParam, resolve, reject) {
|
|
1853
|
+
const conn = this;
|
|
1854
|
+
if (!cmdParam || !cmdParam.file) {
|
|
1855
|
+
return reject(
|
|
1856
|
+
Errors.createError(
|
|
1857
|
+
'SQL file parameter is mandatory',
|
|
1858
|
+
Errors.ER_MISSING_SQL_PARAMETER,
|
|
1859
|
+
conn.info,
|
|
1860
|
+
'HY000',
|
|
1861
|
+
null,
|
|
1862
|
+
false,
|
|
1863
|
+
cmdParam.stack
|
|
1864
|
+
)
|
|
1865
|
+
);
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
const prevAddCommand = this.addCommand.bind(conn);
|
|
1869
|
+
|
|
1870
|
+
this.waitingAuthenticationQueue = new Queue();
|
|
1871
|
+
this.addCommand = this.addCommandQueue;
|
|
1872
|
+
const tmpQuery = function (sql, resolve, reject) {
|
|
1873
|
+
const cmd = new Query(
|
|
1874
|
+
resolve,
|
|
1875
|
+
(err) => {
|
|
1876
|
+
if (conn.opts.logger.error) conn.opts.logger.error(err);
|
|
1877
|
+
reject(err);
|
|
1878
|
+
},
|
|
1879
|
+
conn.opts,
|
|
1880
|
+
{
|
|
1881
|
+
sql: sql,
|
|
1882
|
+
opts: {}
|
|
1883
|
+
}
|
|
1884
|
+
);
|
|
1885
|
+
prevAddCommand(cmd, true);
|
|
1886
|
+
};
|
|
1887
|
+
|
|
1888
|
+
let prevDatabase = null;
|
|
1889
|
+
return (
|
|
1890
|
+
cmdParam.skipDbCheck ? Promise.resolve() : new Promise(tmpQuery.bind(conn, 'SELECT DATABASE() as db'))
|
|
1891
|
+
).then((res) => {
|
|
1892
|
+
prevDatabase = res ? res[0].db : null;
|
|
1893
|
+
if (
|
|
1894
|
+
(cmdParam.skipDbCheck && !conn.opts.database) ||
|
|
1895
|
+
(!cmdParam.skipDbCheck && !cmdParam.database && !prevDatabase)
|
|
1896
|
+
) {
|
|
1897
|
+
return reject(
|
|
1898
|
+
Errors.createError(
|
|
1899
|
+
'Database parameter is not set and no database is selected',
|
|
1900
|
+
Errors.ER_MISSING_DATABASE_PARAMETER,
|
|
1901
|
+
conn.info,
|
|
1902
|
+
'HY000',
|
|
1903
|
+
null,
|
|
1904
|
+
false,
|
|
1905
|
+
cmdParam.stack
|
|
1906
|
+
)
|
|
1907
|
+
);
|
|
1908
|
+
}
|
|
1909
|
+
const searchDbPromise = cmdParam.database
|
|
1910
|
+
? new Promise(tmpQuery.bind(conn, `USE \`${cmdParam.database.replace(/`/gi, '``')}\``))
|
|
1911
|
+
: Promise.resolve();
|
|
1912
|
+
return searchDbPromise.then(() => {
|
|
1913
|
+
const endingFunction = () => {
|
|
1914
|
+
if (conn.status < Status.CLOSING) {
|
|
1915
|
+
conn.addCommand = conn.addCommandEnable.bind(conn);
|
|
1916
|
+
if (conn.status < Status.CLOSING && conn.opts.pipelining) {
|
|
1917
|
+
conn.addCommand = conn.addCommandEnablePipeline.bind(conn);
|
|
1918
|
+
}
|
|
1919
|
+
const commands = conn.waitingAuthenticationQueue.toArray();
|
|
1920
|
+
commands.forEach((cmd) => conn.addCommand(cmd, true));
|
|
1921
|
+
conn.waitingAuthenticationQueue = null;
|
|
1922
|
+
}
|
|
1923
|
+
};
|
|
1924
|
+
return fsPromises
|
|
1925
|
+
.open(cmdParam.file, 'r')
|
|
1926
|
+
.then(async (fd) => {
|
|
1927
|
+
const buf = {
|
|
1928
|
+
buffer: Buffer.allocUnsafe(16384),
|
|
1929
|
+
offset: 0,
|
|
1930
|
+
end: 0
|
|
1931
|
+
};
|
|
1932
|
+
|
|
1933
|
+
const queryPromises = [];
|
|
1934
|
+
let cmdError = null;
|
|
1935
|
+
while (!cmdError) {
|
|
1936
|
+
try {
|
|
1937
|
+
const res = await fd.read(buf.buffer, buf.end, buf.buffer.length - buf.end, null);
|
|
1938
|
+
if (res.bytesRead === 0) {
|
|
1939
|
+
// end of file reached.
|
|
1940
|
+
fd.close().catch(() => {});
|
|
1941
|
+
if (cmdError) {
|
|
1942
|
+
endingFunction();
|
|
1943
|
+
reject(cmdError);
|
|
1944
|
+
return;
|
|
1945
|
+
}
|
|
1946
|
+
await Promise.allSettled(queryPromises)
|
|
1947
|
+
.then(() => {
|
|
1948
|
+
// reset connection to initial database if was set
|
|
1949
|
+
if (
|
|
1950
|
+
!cmdParam.skipDbCheck &&
|
|
1951
|
+
prevDatabase &&
|
|
1952
|
+
cmdParam.database &&
|
|
1953
|
+
cmdParam.database !== prevDatabase
|
|
1954
|
+
) {
|
|
1955
|
+
return new Promise(tmpQuery.bind(conn, `USE \`${prevDatabase.replace(/`/gi, '``')}\``));
|
|
1956
|
+
}
|
|
1957
|
+
return Promise.resolve();
|
|
1958
|
+
})
|
|
1959
|
+
.then(() => {
|
|
1960
|
+
endingFunction();
|
|
1961
|
+
if (cmdError) {
|
|
1962
|
+
reject(cmdError);
|
|
1963
|
+
} else {
|
|
1964
|
+
resolve();
|
|
1965
|
+
}
|
|
1966
|
+
})
|
|
1967
|
+
.catch((err) => {
|
|
1968
|
+
endingFunction();
|
|
1969
|
+
reject(err);
|
|
1970
|
+
});
|
|
1971
|
+
return;
|
|
1972
|
+
} else {
|
|
1973
|
+
buf.end += res.bytesRead;
|
|
1974
|
+
const queries = Parse.parseQueries(buf);
|
|
1975
|
+
const queryIntermediatePromise = queries.flatMap((element) => {
|
|
1976
|
+
return new Promise(tmpQuery.bind(conn, element)).catch((err) => {
|
|
1977
|
+
cmdError = err;
|
|
1978
|
+
});
|
|
1979
|
+
});
|
|
1980
|
+
|
|
1981
|
+
queryPromises.push(...queryIntermediatePromise);
|
|
1982
|
+
if (buf.offset === buf.end) {
|
|
1983
|
+
buf.offset = 0;
|
|
1984
|
+
buf.end = 0;
|
|
1985
|
+
} else {
|
|
1986
|
+
// ensure that buffer can at least read 8k bytes,
|
|
1987
|
+
// either by copying remaining data on used part or growing buffer
|
|
1988
|
+
if (buf.offset > 8192) {
|
|
1989
|
+
// reuse buffer, copying remaining data begin of buffer
|
|
1990
|
+
buf.buffer.copy(buf.buffer, 0, buf.offset, buf.end);
|
|
1991
|
+
buf.end -= buf.offset;
|
|
1992
|
+
buf.offset = 0;
|
|
1993
|
+
} else if (buf.buffer.length - buf.end < 8192) {
|
|
1994
|
+
// grow buffer
|
|
1995
|
+
const tmpBuf = Buffer.allocUnsafe(buf.buffer.length << 1);
|
|
1996
|
+
buf.buffer.copy(tmpBuf, 0, buf.offset, buf.end);
|
|
1997
|
+
buf.buffer = tmpBuf;
|
|
1998
|
+
buf.end -= buf.offset;
|
|
1999
|
+
buf.offset = 0;
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
} catch (e) {
|
|
2004
|
+
fd.close().catch(() => {});
|
|
2005
|
+
endingFunction();
|
|
2006
|
+
Promise.allSettled(queryPromises).catch(() => {});
|
|
2007
|
+
return reject(
|
|
2008
|
+
Errors.createError(
|
|
2009
|
+
e.message,
|
|
2010
|
+
Errors.ER_SQL_FILE_ERROR,
|
|
2011
|
+
conn.info,
|
|
2012
|
+
'HY000',
|
|
2013
|
+
null,
|
|
2014
|
+
false,
|
|
2015
|
+
cmdParam.stack
|
|
2016
|
+
)
|
|
2017
|
+
);
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
if (cmdError) {
|
|
2021
|
+
endingFunction();
|
|
2022
|
+
reject(cmdError);
|
|
2023
|
+
}
|
|
2024
|
+
})
|
|
2025
|
+
.catch((err) => {
|
|
2026
|
+
endingFunction();
|
|
2027
|
+
if (err.code === 'ENOENT') {
|
|
2028
|
+
return reject(
|
|
2029
|
+
Errors.createError(
|
|
2030
|
+
`SQL file parameter '${cmdParam.file}' doesn't exists`,
|
|
2031
|
+
Errors.ER_MISSING_SQL_FILE,
|
|
2032
|
+
conn.info,
|
|
2033
|
+
'HY000',
|
|
2034
|
+
null,
|
|
2035
|
+
false,
|
|
2036
|
+
cmdParam.stack
|
|
2037
|
+
)
|
|
2038
|
+
);
|
|
2039
|
+
}
|
|
2040
|
+
return reject(
|
|
2041
|
+
Errors.createError(err.message, Errors.ER_SQL_FILE_ERROR, conn.info, 'HY000', null, false, cmdParam.stack)
|
|
2042
|
+
);
|
|
2043
|
+
});
|
|
2044
|
+
});
|
|
2045
|
+
});
|
|
2046
|
+
}
|
|
1409
2047
|
|
|
1410
2048
|
/**
|
|
1411
2049
|
* Clearing connection variables when ending.
|
|
1412
2050
|
*
|
|
1413
2051
|
* @private
|
|
1414
2052
|
*/
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
opts.removeAllListeners();
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
}
|
|
2053
|
+
clear() {
|
|
2054
|
+
this.sendQueue.clear();
|
|
2055
|
+
this.opts.removeAllListeners();
|
|
2056
|
+
this.streamOut = undefined;
|
|
2057
|
+
this.socket = undefined;
|
|
2058
|
+
}
|
|
1421
2059
|
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
2060
|
+
/**
|
|
2061
|
+
* Redirecting connection to server indicated value.
|
|
2062
|
+
* @param value server host string
|
|
2063
|
+
* @param resolve promise result when done
|
|
2064
|
+
*/
|
|
2065
|
+
redirect(value, resolve) {
|
|
2066
|
+
if (this.opts.permitRedirect && value) {
|
|
2067
|
+
// redirect only if :
|
|
2068
|
+
// * when pipelining, having received all waiting responses.
|
|
2069
|
+
// * not in a transaction
|
|
2070
|
+
if (this.receiveQueue.length <= 1 && (this.info.status & ServerStatus.STATUS_IN_TRANS) === 0) {
|
|
2071
|
+
this.info.redirectRequest = null;
|
|
2072
|
+
const matchResults = value.match(redirectUrlFormat);
|
|
2073
|
+
if (!matchResults) {
|
|
2074
|
+
if (this.opts.logger.error)
|
|
2075
|
+
this.opts.logger.error(
|
|
2076
|
+
new Error(
|
|
2077
|
+
`error parsing redirection string '${value}'. format must be 'mariadb/mysql://[<user>[:<password>]@]<host>[:<port>]/[<db>[?<opt1>=<value1>[&<opt2>=<value2>]]]'`
|
|
2078
|
+
)
|
|
2079
|
+
);
|
|
2080
|
+
return resolve();
|
|
2081
|
+
}
|
|
1425
2082
|
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
2083
|
+
const options = {
|
|
2084
|
+
host: matchResults[7] ? decodeURIComponent(matchResults[7]) : matchResults[6],
|
|
2085
|
+
port: matchResults[9] ? parseInt(matchResults[9]) : 3306
|
|
2086
|
+
};
|
|
2087
|
+
|
|
2088
|
+
if (options.host === this.opts.host && options.port === this.opts.port) {
|
|
2089
|
+
// redirection to the same host, skip loop redirection
|
|
2090
|
+
return resolve();
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
// actually only options accepted are user and password
|
|
2094
|
+
// there might be additional possible options in the future
|
|
2095
|
+
if (matchResults[3]) options.user = matchResults[3];
|
|
2096
|
+
if (matchResults[5]) options.password = matchResults[5];
|
|
2097
|
+
|
|
2098
|
+
const redirectOpts = ConnectionOptions.parseOptionDataType(options);
|
|
2099
|
+
|
|
2100
|
+
const finalRedirectOptions = new ConnOptions(Object.assign({}, this.opts, redirectOpts));
|
|
2101
|
+
const conn = new Connection(finalRedirectOptions);
|
|
2102
|
+
conn
|
|
2103
|
+
.connect()
|
|
2104
|
+
.then(
|
|
2105
|
+
async function () {
|
|
2106
|
+
await new Promise(this.end.bind(this, {}));
|
|
2107
|
+
this.status = Status.CONNECTED;
|
|
2108
|
+
this.info = conn.info;
|
|
2109
|
+
this.opts = conn.opts;
|
|
2110
|
+
this.socket = conn.socket;
|
|
2111
|
+
if (this.prepareCache) this.prepareCache.reset();
|
|
2112
|
+
this.streamOut = conn.streamOut;
|
|
2113
|
+
this.streamIn = conn.streamIn;
|
|
2114
|
+
resolve();
|
|
2115
|
+
}.bind(this)
|
|
2116
|
+
)
|
|
2117
|
+
.catch(
|
|
2118
|
+
function (e) {
|
|
2119
|
+
if (this.opts.logger.error) {
|
|
2120
|
+
const err = new Error(`fail to redirect to '${value}'`);
|
|
2121
|
+
err.cause = e;
|
|
2122
|
+
this.opts.logger.error(err);
|
|
2123
|
+
}
|
|
2124
|
+
resolve();
|
|
2125
|
+
}.bind(this)
|
|
2126
|
+
);
|
|
2127
|
+
} else {
|
|
2128
|
+
this.info.redirectRequest = value;
|
|
2129
|
+
resolve();
|
|
2130
|
+
}
|
|
2131
|
+
} else {
|
|
2132
|
+
this.info.redirectRequest = null;
|
|
2133
|
+
resolve();
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
get threadId() {
|
|
2138
|
+
return this.info ? this.info.threadId : null;
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
_sendNextCmdImmediate() {
|
|
2142
|
+
if (!this.sendQueue.isEmpty()) {
|
|
2143
|
+
setImmediate(this.nextSendCmd.bind(this));
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2147
|
+
_closePrepare(prepareResultPacket) {
|
|
2148
|
+
this.addCommand(
|
|
2149
|
+
new ClosePrepare(
|
|
2150
|
+
{},
|
|
2151
|
+
() => {},
|
|
2152
|
+
() => {},
|
|
2153
|
+
prepareResultPacket
|
|
2154
|
+
),
|
|
2155
|
+
false
|
|
2156
|
+
);
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
_logAndReject(reject, err) {
|
|
2160
|
+
if (this.opts.logger.error) this.opts.logger.error(err);
|
|
2161
|
+
reject(err);
|
|
2162
|
+
}
|
|
1456
2163
|
}
|
|
1457
2164
|
|
|
1458
|
-
|
|
2165
|
+
class TestMethods {
|
|
2166
|
+
#collation;
|
|
2167
|
+
#socket;
|
|
2168
|
+
|
|
2169
|
+
constructor(collation, socket) {
|
|
2170
|
+
this.#collation = collation;
|
|
2171
|
+
this.#socket = socket;
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
getCollation() {
|
|
2175
|
+
return this.#collation;
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
getSocket() {
|
|
2179
|
+
return this.#socket;
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
1459
2182
|
|
|
1460
2183
|
module.exports = Connection;
|