jsql-neo 5.4.2 → 5.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/LICENSE +202 -202
  2. package/README.md +8097 -7971
  3. package/bin/jsql +320 -320
  4. package/bin/jsql-server +90 -90
  5. package/bin/jsql-server-control.js +177 -177
  6. package/index.d.ts +385 -357
  7. package/index.js +124 -118
  8. package/lib/ast.js +214 -0
  9. package/lib/btree.js +413 -413
  10. package/lib/cache.js +328 -328
  11. package/lib/client.js +125 -125
  12. package/lib/database.js +1744 -1744
  13. package/lib/date-types.js +182 -182
  14. package/lib/errors.js +89 -89
  15. package/lib/jsql_format.js +497 -497
  16. package/lib/migrate.js +254 -254
  17. package/lib/mod.js +292 -292
  18. package/lib/mongo_server.js +607 -607
  19. package/lib/multiserver.js +140 -140
  20. package/lib/mysql_compat.js +413 -413
  21. package/lib/mysql_server.js +1237 -1237
  22. package/lib/native_client.js +732 -732
  23. package/lib/nedb_compat.js +506 -506
  24. package/lib/pg_server.js +864 -864
  25. package/lib/plugin.js +34 -34
  26. package/lib/query.js +815 -815
  27. package/lib/redis_server.js +571 -571
  28. package/lib/sql.js +3460 -2925
  29. package/lib/sqlite_compat.js +323 -323
  30. package/lib/sqlite_worker.js +264 -264
  31. package/lib/table.js +1304 -1304
  32. package/lib/tui.js +502 -502
  33. package/lib/wasm.js +1 -1
  34. package/lib/wasm_client.js +718 -718
  35. package/lib/web_ui.js +257 -257
  36. package/native/jsql-neo-native.node +0 -0
  37. package/nativesrc/jsql-neo-core/Cargo.lock +455 -455
  38. package/nativesrc/jsql-neo-core/Cargo.toml +24 -24
  39. package/nativesrc/jsql-neo-core/src/engine/hybrid.rs +455 -455
  40. package/nativesrc/jsql-neo-core/src/engine/memory.rs +168 -168
  41. package/nativesrc/jsql-neo-core/src/engine/mod.rs +62 -62
  42. package/nativesrc/jsql-neo-core/src/engine/table.rs +700 -700
  43. package/nativesrc/jsql-neo-core/src/lib.rs +2 -2
  44. package/nativesrc/jsql-neo-core/src/storage/mod.rs +2 -2
  45. package/nativesrc/jsql-neo-core/src/storage/persistent.rs +1 -1
  46. package/nativesrc/jsql-neo-core/src/storage/wal.rs +85 -85
  47. package/nativesrc/jsql-neo-core/src/types.rs +93 -93
  48. package/nativesrc/jsql-neo-native/Cargo.lock +606 -606
  49. package/nativesrc/jsql-neo-native/Cargo.toml +16 -16
  50. package/nativesrc/jsql-neo-native/build.rs +3 -3
  51. package/nativesrc/jsql-neo-native/package.json +7 -7
  52. package/nativesrc/jsql-neo-native/src/lib.rs +269 -269
  53. package/nativesrc/jsql-neo-wasm/Cargo.lock +465 -465
  54. package/nativesrc/jsql-neo-wasm/Cargo.toml +15 -15
  55. package/nativesrc/jsql-neo-wasm/src/lib.rs +447 -447
  56. package/package.json +87 -86
  57. package/postinstall.js +158 -158
  58. package/test/btree.test.js +128 -128
  59. package/test/coverage.js +187 -187
  60. package/test/join.test.js +110 -110
  61. package/test/native.test.js +158 -158
  62. package/test/regress-5.1.0.js +271 -271
  63. package/test/smoke.js +58 -58
  64. package/test/sql-parser.test.js +273 -0
  65. package/test/wasm.test.js +110 -110
  66. package/wasm/browser.d.ts +89 -89
  67. package/wasm/browser.mjs +569 -569
  68. package/wasm/browser_bg.mjs +597 -597
  69. package/wasm/jsql_neo_wasm.d.ts +38 -38
  70. package/wasm/jsql_neo_wasm.js +520 -520
  71. package/wasm/jsql_neo_wasm_bg.wasm.d.ts +26 -26
  72. package/wasm/package.json +11 -11
@@ -1,1237 +1,1237 @@
1
- // © Vexify 2026 All Rights Reserved.
2
- /**
3
- * MySQL 协议服务端 — TCP 3306
4
- * 实现 MySQL 握手/认证/命令循环,任何 mysql/mysql2 客户端可直接连接。
5
- */
6
-
7
- const net = require('net');
8
- const crypto = require('crypto');
9
- const path = require('path');
10
- const fs = require('fs');
11
- const { executeSQL, parseSQL, splitStatements, applyParams } = require('./sql');
12
- const Database = require('./database');
13
-
14
- const SERVER_VERSION = '8.0.0-jsql-neo';
15
- const CLIENT_PROTOCOL_41 = 0x00000001 << 9;
16
- const CLIENT_SECURE_CONNECTION = 0x00008000;
17
- const CLIENT_PLUGIN_AUTH = 0x00080000;
18
- const CLIENT_CONNECT_WITH_DB = 0x00000008;
19
- const CLIENT_LONG_PASSWORD = 0x00000001;
20
- const CLIENT_TRANSACTIONS = 0x00002000;
21
- const CLIENT_MULTI_STATEMENTS = 0x00010000;
22
- const CLIENT_MULTI_RESULTS = 0x00020000;
23
- const CLIENT_LONG_FLAG = 0x00000004;
24
- const CLIENT_DEPRECATE_EOF = 0x01000000;
25
- const SERVER_STATUS_AUTOCOMMIT = 0x0002;
26
- const CHARSET_UTF8 = 0x21;
27
-
28
- const MYSQL_TYPE_TINY = 1, MYSQL_TYPE_LONG = 3, MYSQL_TYPE_LONGLONG = 8,
29
- MYSQL_TYPE_DATE = 10, MYSQL_TYPE_DATETIME = 12, MYSQL_TYPE_DOUBLE = 5,
30
- MYSQL_TYPE_STRING = 254, MYSQL_TYPE_VAR_STRING = 253, MYSQL_TYPE_BLOB = 252,
31
- MYSQL_TYPE_JSON = 245, MYSQL_TYPE_NULL = 6;
32
-
33
- function encodeLenenc(value) {
34
- if (typeof value === 'bigint') value = Number(value);
35
- if (value === null) return Buffer.from([0xfb]);
36
- if (value < 0xfb) return Buffer.from([value]);
37
- if (value <= 0xffff) {
38
- const b = Buffer.alloc(3);
39
- b[0] = 0xfc; b.writeUInt16LE(value, 1);
40
- return b;
41
- }
42
- if (value <= 0xffffff) {
43
- const b = Buffer.alloc(4);
44
- b[0] = 0xfd; b.writeUIntLE(value, 1, 3);
45
- return b;
46
- }
47
- const b = Buffer.alloc(9);
48
- b[0] = 0xfe; b.writeUInt32LE(value, 1); b.writeUInt32LE(Math.floor(value / 4294967296), 5);
49
- return b;
50
- }
51
-
52
- function encodeLenencString(str) {
53
- const buf = Buffer.from(String(str), 'utf8');
54
- return Buffer.concat([encodeLenenc(buf.length), buf]);
55
- }
56
-
57
- function parseLenenc(buf, offset) {
58
- const first = buf[offset];
59
- if (first < 0xfb) return { value: first, size: 1 };
60
- if (first === 0xfb) return { value: null, size: 1 };
61
- if (first === 0xfc) return { value: buf.readUInt16LE(offset + 1), size: 3 };
62
- if (first === 0xfd) return { value: buf.readUIntLE(offset + 1, 3), size: 4 };
63
- return { value: buf.readUInt32LE(offset + 1), size: 5 };
64
- }
65
-
66
- function readLenenc(buf, offset) {
67
- const { value, size } = parseLenenc(buf, offset);
68
- if (value === null) return { value: null, off: offset + size };
69
- const str = buf.slice(offset + size, offset + size + value).toString('utf8');
70
- return { value: str, off: offset + size + value };
71
- }
72
-
73
- class PacketBuilder {
74
- constructor() {
75
- this.bufs = [];
76
- }
77
- byte(v) { this.bufs.push(Buffer.from([v & 0xff])); return this; }
78
- int16(v) { const b = Buffer.alloc(2); b.writeUInt16LE(v & 0xffff); this.bufs.push(b); return this; }
79
- int32(v) { const b = Buffer.alloc(4); b.writeUInt32LE(v >>> 0); this.bufs.push(b); return this; }
80
- raw(buf) { this.bufs.push(buf); return this; }
81
- string(str) { this.bufs.push(Buffer.from(str, 'utf8')); return this; }
82
- nul(str) { this.bufs.push(Buffer.concat([Buffer.from(String(str), 'utf8'), Buffer.from([0])])); return this; }
83
- bytes(list) { this.bufs.push(Buffer.from(list)); return this; }
84
- build() { return Buffer.concat(this.bufs); }
85
- }
86
-
87
- function handshakePacket(connectionId, seed) {
88
- const b = new PacketBuilder();
89
- b.byte(10);
90
- b.nul(SERVER_VERSION);
91
- b.int32(connectionId);
92
- b.bytes(seed.slice(0, 8));
93
- b.byte(0);
94
- const caps = CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION | CLIENT_PLUGIN_AUTH | CLIENT_CONNECT_WITH_DB | CLIENT_LONG_PASSWORD | CLIENT_TRANSACTIONS | CLIENT_MULTI_STATEMENTS | CLIENT_MULTI_RESULTS | CLIENT_LONG_FLAG;
95
- b.int16(caps & 0xffff);
96
- b.byte(CHARSET_UTF8);
97
- b.int16(SERVER_STATUS_AUTOCOMMIT);
98
- b.int16((caps >>> 16) & 0xffff);
99
- b.byte(21);
100
- b.bytes([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
101
- b.bytes(seed.slice(8, 20));
102
- b.byte(0);
103
- b.nul('mysql_native_password');
104
- return b.build();
105
- }
106
-
107
- /**
108
- * 生成握手 seed:使用可打印 ASCII(33-126),避开 0x00。
109
- * 老客户端按 C 字符串读取 seed,若含 \0 会提前截断导致握手失败/断连。
110
- */
111
- function genSeed(len = 20) {
112
- const out = Buffer.alloc(len);
113
- for (let i = 0; i < len; i++) {
114
- out[i] = 33 + Math.floor(Math.random() * 94);
115
- }
116
- return out;
117
- }
118
-
119
- function okPacket(affectedRows = 0, insertId = 0, status = SERVER_STATUS_AUTOCOMMIT) { const b = new PacketBuilder();
120
- b.byte(0x00);
121
- b.raw(encodeLenenc(affectedRows));
122
- b.raw(encodeLenenc(insertId));
123
- b.int16(status);
124
- b.int16(0);
125
- return b.build();
126
- }
127
-
128
- function eofPacket() {
129
- const b = new PacketBuilder();
130
- b.byte(0xfe);
131
- b.int16(0);
132
- b.int16(SERVER_STATUS_AUTOCOMMIT);
133
- return b.build();
134
- }
135
-
136
- function errPacket(errno, message, sqlState = 'HY000') {
137
- const b = new PacketBuilder();
138
- b.byte(0xff);
139
- b.int16(errno);
140
- b.byte(0x23);
141
- b.string(sqlState);
142
- b.string(String(message).slice(0, 200));
143
- return b.build();
144
- }
145
-
146
- function toMysqlErrno(e) {
147
- if (e && typeof e.code === 'number') return e.code;
148
- const m = e && e.message ? String(e.message).match(/^(ER_[A-Z_]+)/) : null;
149
- if (m) {
150
- const known = {
151
- ER_DUP_ENTRY: 1062,
152
- ER_NO_SUCH_TABLE: 1146,
153
- ER_TABLE_EXISTS: 1050,
154
- ER_TABLE_EXISTS_ERROR: 1050,
155
- ER_PARSE_ERROR: 1064,
156
- ER_BAD_FIELD_ERROR: 1054,
157
- ER_BAD_NULL_ERROR: 1048,
158
- ER_ACCESS_DENIED_ERROR: 1045,
159
- ER_DBACCESS_DENIED_ERROR: 1044,
160
- ER_BAD_DB_ERROR: 1049,
161
- ER_WRONG_DB_NAME: 1102,
162
- ER_WRONG_TABLE_NAME: 1103,
163
- ER_WRONG_COLUMN_NAME: 1166,
164
- ER_DATA_TOO_LONG: 1406,
165
- ER_OUT_OF_RANGE: 1264,
166
- ER_CHECK_CONSTRAINT: 3819,
167
- ER_NO_DEFAULT_FOR_FIELD: 1364,
168
- ER_CANT_DROP_FIELD_OR_KEY: 1091,
169
- ER_CANT_DROP_DATABASE: 1008,
170
- ER_EMPTY_QUERY: 1065,
171
- ER_UNKNOWN_TABLE: 1109,
172
- ER_NON_UNIQ_ERROR: 1052,
173
- ER_WRONG_FIELD_WITH_GROUP: 1055,
174
- ER_WRONG_VALUE_COUNT_ON_ROW: 1136,
175
- ER_MISSING_TABLE: 1052,
176
- ER_SP_DOES_NOT_EXIST: 1305,
177
- ER_NOT_SUPPORTED_YET: 1235,
178
- ER_LOCK_DEADLOCK: 1213,
179
- ER_LOCK_WAIT_TIMEOUT: 1205,
180
- ER_UNKNOWN_ERROR: 1105,
181
- };
182
- if (known[m[1]]) return known[m[1]];
183
- }
184
- return 1105;
185
- }
186
-
187
- function columnDefinition(column) {
188
- const b = new PacketBuilder();
189
- b.raw(encodeLenencString('def'));
190
- b.raw(encodeLenencString(column.table || ''));
191
- b.raw(encodeLenencString(column.table || ''));
192
- b.raw(encodeLenencString(column.table || ''));
193
- b.raw(encodeLenencString(column.name || ''));
194
- b.raw(encodeLenencString(column.name || ''));
195
- b.raw(encodeLenenc(0x0c));
196
- b.int16(column.charset || CHARSET_UTF8);
197
- b.int32(column.length || 1024);
198
- b.byte(column.type !== undefined ? column.type : MYSQL_TYPE_VAR_STRING);
199
- b.int16(0);
200
- b.byte(0);
201
- b.int16(0);
202
- return b.build();
203
- }
204
-
205
- function columnTypeFromSchema(def) {
206
- const type = def && def.type ? String(def.type).toLowerCase() : 'string';
207
- if (type === 'integer' || type === 'int' || type === 'bigint') return MYSQL_TYPE_LONG;
208
- if (type === 'number' || type === 'float' || type === 'double' || type === 'real') return MYSQL_TYPE_DOUBLE;
209
- if (type === 'boolean' || type === 'bool') return MYSQL_TYPE_TINY;
210
- if (type === 'date') return MYSQL_TYPE_DATE;
211
- if (type === 'datetime' || type === 'timestamp') return MYSQL_TYPE_DATETIME;
212
- if (type === 'object' || type === 'array') return MYSQL_TYPE_JSON;
213
- if (type === 'binary') return MYSQL_TYPE_BLOB;
214
- return MYSQL_TYPE_VAR_STRING;
215
- }
216
-
217
- function resultSetPacket(result, tableSchema, baseSeq, rawRow) {
218
- const packets = [];
219
- let sequence = baseSeq || 0;
220
- const push = (buf) => {
221
- const header = Buffer.alloc(4);
222
- header.writeUIntLE(buf.length, 0, 3);
223
- header[3] = sequence;
224
- sequence++;
225
- packets.push(header);
226
- packets.push(buf);
227
- };
228
-
229
- const inferType = (name, value) => {
230
- if (tableSchema && tableSchema[name]) return columnTypeFromSchema(tableSchema[name]);
231
- if (value === null || value === undefined) return MYSQL_TYPE_NULL;
232
- if (typeof value === 'number') return Number.isInteger(value) ? MYSQL_TYPE_LONGLONG : MYSQL_TYPE_DOUBLE;
233
- if (typeof value === 'boolean') return MYSQL_TYPE_TINY;
234
- if (typeof value === 'object') return MYSQL_TYPE_JSON;
235
- return MYSQL_TYPE_VAR_STRING;
236
- };
237
-
238
- push(encodeLenenc((result.columns || []).length));
239
- const sampleRow = (result.rows && result.rows[0]) || null;
240
- for (let ci = 0; ci < (result.columns || []).length; ci++) {
241
- const name = result.columns[ci];
242
- let sample;
243
- if (sampleRow !== null) {
244
- if (Array.isArray(sampleRow)) sample = sampleRow[ci];
245
- else sample = sampleRow[name];
246
- }
247
- const col = {
248
- name,
249
- table: result.table || '',
250
- type: inferType(name, sample),
251
- length: 1024,
252
- };
253
- push(columnDefinition(col));
254
- }
255
- push(eofPacket());
256
-
257
- for (const row of result.rows || []) {
258
- const parts = [];
259
- for (let i = 0; i < (result.columns || []).length; i++) {
260
- const v = row[i];
261
- if (v === null || v === undefined) {
262
- parts.push(Buffer.from([0xfb]));
263
- } else if (typeof v === 'boolean') {
264
- parts.push(encodeLenencString(v ? '1' : '0'));
265
- } else if (typeof v === 'object') {
266
- parts.push(encodeLenencString(JSON.stringify(v)));
267
- } else {
268
- parts.push(encodeLenencString(v));
269
- }
270
- }
271
- push(Buffer.concat(parts));
272
- }
273
- push(eofPacket());
274
- return { packets, sequence };
275
- }
276
-
277
- function binaryResultSetPacket(result, tableSchema, baseSeq) {
278
- const packets = [];
279
- let sequence = baseSeq || 0;
280
- const push = (buf) => {
281
- const header = Buffer.alloc(4);
282
- header.writeUIntLE(buf.length, 0, 3);
283
- header[3] = sequence;
284
- sequence++;
285
- packets.push(header);
286
- packets.push(buf);
287
- };
288
-
289
- const inferType = (name, value) => {
290
- if (tableSchema && tableSchema[name]) return columnTypeFromSchema(tableSchema[name]);
291
- if (value === null || value === undefined) return MYSQL_TYPE_NULL;
292
- if (typeof value === 'number') return Number.isInteger(value) ? MYSQL_TYPE_LONGLONG : MYSQL_TYPE_DOUBLE;
293
- if (typeof value === 'boolean') return MYSQL_TYPE_TINY;
294
- if (typeof value === 'object') return MYSQL_TYPE_JSON;
295
- return MYSQL_TYPE_VAR_STRING;
296
- };
297
-
298
- const cols = result.columns || [];
299
- push(encodeLenenc(cols.length));
300
- const sampleRow = (result.rows && result.rows[0]) || null;
301
- const types = [];
302
- for (let ci = 0; ci < cols.length; ci++) {
303
- let sample;
304
- if (sampleRow !== null) {
305
- if (Array.isArray(sampleRow)) sample = sampleRow[ci];
306
- else sample = sampleRow[cols[ci]];
307
- }
308
- const t = inferType(cols[ci], sample);
309
- types.push(t);
310
- push(columnDefinition({ name: cols[ci], table: result.table || '', type: t, length: 1024 }));
311
- }
312
- push(eofPacket());
313
-
314
- const encodeValue = (v, type) => {
315
- if (v === null || v === undefined) return null;
316
- if (type === MYSQL_TYPE_LONG || type === MYSQL_TYPE_LONGLONG || type === MYSQL_TYPE_DOUBLE) {
317
- const num = Number(v);
318
- if (type === MYSQL_TYPE_DOUBLE) {
319
- if (!Number.isFinite(num)) return encodeLenencString(String(v));
320
- const b = Buffer.alloc(8); b.writeDoubleLE(num, 0); return b;
321
- }
322
- if (Number.isInteger(num) && num <= 2147483647 && num >= -2147483648) {
323
- const b = Buffer.alloc(4); b.writeInt32LE(num, 0); return b;
324
- }
325
- if (!Number.isFinite(num)) return encodeLenencString(String(v));
326
- const b = Buffer.alloc(8); b.writeBigInt64LE(BigInt(Math.trunc(num)), 0); return b;
327
- }
328
- if (type === MYSQL_TYPE_TINY) {
329
- const b = Buffer.alloc(1); b.writeInt8(v ? 1 : 0, 0); return b;
330
- }
331
- if (type === MYSQL_TYPE_DATE || type === MYSQL_TYPE_DATETIME) {
332
- const s = String(v);
333
- const m = s.match(/^(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2}):(\d{2}))?/);
334
- if (!m) return encodeLenencString(String(v));
335
- const hasTime = !!m[4];
336
- const buf = hasTime ? Buffer.alloc(8) : Buffer.alloc(5);
337
- let off = 0;
338
- buf[off++] = hasTime ? 7 : 4;
339
- buf.writeUInt16LE(parseInt(m[1], 10), off); off += 2;
340
- buf[off++] = parseInt(m[2], 10);
341
- buf[off++] = parseInt(m[3], 10);
342
- if (hasTime) {
343
- buf[off++] = parseInt(m[4], 10);
344
- buf[off++] = parseInt(m[5], 10);
345
- buf[off++] = parseInt(m[6], 10);
346
- }
347
- return buf;
348
- }
349
- return encodeLenencString(typeof v === 'object' ? JSON.stringify(v) : v);
350
- };
351
-
352
- for (const row of result.rows || []) {
353
- const parts = [Buffer.from([0x00])];
354
- const nb = Buffer.alloc(Math.ceil(cols.length / 8));
355
- const encoded = [];
356
- for (let i = 0; i < cols.length; i++) {
357
- const v = Array.isArray(row) ? row[i] : row[cols[i]];
358
- if (v === null || v === undefined) {
359
- nb[Math.floor(i / 8)] |= (1 << (i % 8));
360
- encoded.push(null);
361
- } else {
362
- encoded.push(encodeValue(v, types[i]));
363
- }
364
- }
365
- parts.push(nb);
366
- for (const e of encoded) if (e !== null) parts.push(e);
367
- push(Buffer.concat(parts));
368
- }
369
- push(eofPacket());
370
- return { packets, sequence };
371
- }
372
-
373
- class MysqlConnection {
374
- constructor(socket, server) {
375
- this.socket = socket;
376
- this.server = server;
377
- this.connectionId = ++server._connectionCounter;
378
- this.seed = genSeed(20);
379
- this.buffer = Buffer.alloc(0);
380
- this.sequence = 0;
381
- this.authenticated = false;
382
- this.user = null;
383
- this.authFails = 0;
384
- this.multiStatements = false;
385
- this._stmts = new Map();
386
- this._stmtSeq = 0;
387
- this.session = {
388
- lastInsertId: 0,
389
- rowCount: 0,
390
- foundRows: 0,
391
- connectionId: server._connectionCounter,
392
- currentDb: null,
393
- sysvars: {},
394
- };
395
- if (server.handshakeTimeout > 0) {
396
- this._authTimer = setTimeout(() => {
397
- if (!this.authenticated) {
398
- this.server._onSecurityEvent({ type: 'auth-timeout', user: this.user, remote: socket.remoteAddress });
399
- socket.destroy();
400
- }
401
- }, server.handshakeTimeout);
402
- this._authTimer.unref();
403
- }
404
- this.socket.on('data', chunk => this._onData(chunk));
405
- this.socket.on('error', () => {});
406
- this._armIdleTimer();
407
- this._send(handshakePacket(this.connectionId, this.seed));
408
- }
409
-
410
- _armIdleTimer() {
411
- if (this._idleTimer) { clearTimeout(this._idleTimer); this._idleTimer = null; }
412
- const ttl = this.server.idleTimeout;
413
- if (!this.authenticated) return;
414
- if (ttl <= 0) return;
415
- this._idleTimer = setTimeout(() => {
416
- this.server._onSecurityEvent({ type: 'idle-timeout', user: this.user, remote: this.socket.remoteAddress });
417
- this.socket.destroy();
418
- }, ttl);
419
- this._idleTimer.unref();
420
- }
421
-
422
- _send(payload) {
423
- if (!this.socket.writable) return;
424
- const header = Buffer.alloc(4);
425
- header.writeUIntLE(payload.length, 0, 3);
426
- header[3] = this.sequence;
427
- this.sequence = (this.sequence + 1) & 0xff;
428
- this.socket.write(Buffer.concat([header, payload]));
429
- }
430
-
431
- _onData(chunk) {
432
- this.buffer = Buffer.concat([this.buffer, chunk]);
433
- this._armIdleTimer();
434
- if (this.buffer.length > this.server.maxPacketSize + 4) {
435
- this._malicious('packet exceeds maxPacketSize (' + this.server.maxPacketSize + ')');
436
- return;
437
- }
438
- while (true) {
439
- if (this.buffer.length < 4) return;
440
- const len = this.buffer.readUIntLE(0, 3);
441
- if (len > this.server.maxPacketSize) {
442
- this._malicious('packet length ' + len + ' exceeds maxPacketSize');
443
- return;
444
- }
445
- if (this.buffer.length < 4 + len) return;
446
- const seq = this.buffer[3];
447
- const payload = this.buffer.slice(4, 4 + len);
448
- this.buffer = this.buffer.slice(4 + len);
449
- this._handlePacket(payload, seq);
450
- if (this.socket.destroyed) return;
451
- }
452
- }
453
-
454
- _malicious(reason) {
455
- this.socket.destroy();
456
- this.server._onSecurityEvent({ type: 'malicious', reason, user: this.user, remote: this.socket.remoteAddress });
457
- }
458
-
459
- _handlePacket(payload, cmdSeq) {
460
- if (!this.authenticated) {
461
- this.sequence = (cmdSeq + 1) & 0xff;
462
- this._handleAuth(payload);
463
- return;
464
- }
465
- this.sequence = (cmdSeq + 1) & 0xff;
466
- const cmd = payload[0];
467
- const body = payload.slice(1);
468
- try {
469
- switch (cmd) {
470
- case 0x01: this.socket.end(); break; // COM_QUIT
471
- case 0x02: { // COM_INIT_DB
472
- const db = body.toString('utf8');
473
- if (!this.server._safeDbName(db)) {
474
- this._malicious('COM_INIT_DB with invalid database name');
475
- return;
476
- }
477
- this.server._switchDb(this, db)
478
- .then(() => this._send(okPacket()))
479
- .catch(e => this._send(errPacket(e.code || 1049, e.message)));
480
- break;
481
- }
482
- case 0x03: {
483
- const sql = body.toString('utf8');
484
- if (sql.length === 0) {
485
- this._send(errPacket(1065, 'Query was empty'));
486
- break;
487
- }
488
- this._handleQuery(sql);
489
- break;
490
- }
491
- case 0x11: this._handleChangeUser(body, cmdSeq); break; // COM_CHANGE_USER
492
- case 0x0e: this._send(okPacket()); break; // COM_PING
493
- case 0x1f: this._send(okPacket()); break; // COM_RESET_CONNECTION
494
- case 0x16: this._handleStmtPrepare(body); break; // COM_STMT_PREPARE
495
- case 0x17: this._handleStmtExecute(body); break; // COM_STMT_EXECUTE
496
- case 0x18: break; // COM_STMT_SEND_LONG_DATA (忽略)
497
- case 0x19: this._handleStmtClose(body); break; // COM_STMT_CLOSE
498
- case 0x1a: this._send(okPacket()); break; // COM_STMT_RESET
499
- case 0x0a: { // COM_PROCESS_INFO
500
- const seq = this.sequence;
501
- this.sequence = (this.sequence + 1) & 0xff;
502
- this._send(encodeLenenc(1));
503
- this._send(columnDefinition({ name: 'Id', type: MYSQL_TYPE_LONG }));
504
- this._send(eofPacket());
505
- this._send(Buffer.from([0x31]));
506
- this._send(eofPacket());
507
- break;
508
- }
509
- case 0x09: this._send(okPacket(0, 0, SERVER_STATUS_AUTOCOMMIT)); break; // COM_STATISTICS
510
- default:
511
- this._send(errPacket(1105, `Unsupported command: ${cmd}`));
512
- }
513
- } catch (e) {
514
- this._send(errPacket(toMysqlErrno(e), e.message));
515
- }
516
- }
517
-
518
- _handleChangeUser(body, cmdSeq) {
519
- (async () => {
520
- try {
521
- let pos = 0;
522
- const userEnd = body.indexOf(0, pos);
523
- if (userEnd === -1) throw new Error('malformed COM_CHANGE_USER');
524
- const user = body.slice(pos, userEnd).toString('utf8');
525
- pos = userEnd + 1;
526
- const lenenc = parseLenenc(body, pos);
527
- let authResponse = Buffer.alloc(0);
528
- if (lenenc.value !== null) {
529
- authResponse = body.slice(pos + lenenc.size, pos + lenenc.size + lenenc.value);
530
- pos += lenenc.size + lenenc.value;
531
- } else {
532
- pos += lenenc.size;
533
- }
534
- let db = null;
535
- if (pos < body.length) {
536
- const dbEnd = body.indexOf(0, pos);
537
- if (dbEnd !== -1) {
538
- db = body.slice(pos, dbEnd).toString('utf8');
539
- }
540
- }
541
- const valid = this.server._checkAuth(user, authResponse, this.seed);
542
- if (!valid) {
543
- this._send(errPacket(1045, `Access denied for user '${user}'`));
544
- return;
545
- }
546
- if (db) {
547
- if (!this.server._canAccessDb(user, db)) {
548
- this._send(errPacket(1044, `Access denied for user '${user}' to database '${db}'`));
549
- return;
550
- }
551
- try {
552
- await this.server._getDatabase(db);
553
- } catch (e) {
554
- this._send(errPacket(1049, `Unknown database '${db}'`));
555
- return;
556
- }
557
- }
558
- this.user = user;
559
- this.currentDb = db;
560
- this._send(okPacket());
561
- } catch (e) {
562
- this._send(errPacket(toMysqlErrno(e), e.message));
563
- }
564
- })();
565
- }
566
-
567
- async _handleAuth(payload) {
568
- try {
569
- const caps = payload.readUInt32LE(0);
570
- let pos = 32; // 4 caps + 4 maxpacket + 1 charset + 23 reserved
571
- const userEnd = payload.indexOf(0, pos);
572
- this.user = payload.slice(pos, userEnd).toString('utf8');
573
- pos = userEnd + 1;
574
- let authResponse = Buffer.alloc(0);
575
- if (caps & CLIENT_SECURE_CONNECTION) {
576
- const lenenc = parseLenenc(payload, pos);
577
- if (lenenc.value === null) { pos += lenenc.size; }
578
- else {
579
- authResponse = payload.slice(pos + lenenc.size, pos + lenenc.size + lenenc.value);
580
- pos += lenenc.size + lenenc.value;
581
- }
582
- } else {
583
- const end = payload.indexOf(0, pos);
584
- if (end !== -1) {
585
- authResponse = payload.slice(pos, end);
586
- pos = end + 1;
587
- }
588
- }
589
- let db = null;
590
- if (caps & CLIENT_CONNECT_WITH_DB) {
591
- const dbEnd = payload.indexOf(0, pos);
592
- if (dbEnd !== -1) {
593
- db = payload.slice(pos, dbEnd).toString('utf8');
594
- pos = dbEnd + 1;
595
- }
596
- }
597
- this.currentDb = db;
598
-
599
- const valid = this.server._checkAuth(this.user, authResponse, this.seed);
600
- if (!valid) {
601
- this.authFails++;
602
- this.server._onSecurityEvent({ type: 'auth-fail', user: this.user, fails: this.authFails, remote: this.socket.remoteAddress });
603
- if (this.authFails >= this.server.maxAuthFails) {
604
- this.socket.destroy();
605
- return;
606
- }
607
- this._send(errPacket(1045, `Access denied for user '${this.user}'`));
608
- this.socket.end();
609
- return;
610
- }
611
- this.multiStatements = !!(caps & CLIENT_MULTI_STATEMENTS);
612
- this.authenticated = true;
613
- if (this._authTimer) { clearTimeout(this._authTimer); this._authTimer = null; }
614
- this._armIdleTimer();
615
- if (db) {
616
- if (!this.server._canAccessDb(this.user, db)) {
617
- this._send(errPacket(1044, `Access denied for user '${this.user}' to database '${db}'`));
618
- this.socket.end();
619
- return;
620
- }
621
- try {
622
- if (!this.server._dbExists(db)) await this.server.createDatabase(db);
623
- await this.server._getDatabase(db);
624
- this.currentDb = db;
625
- } catch (e) {
626
- this._send(errPacket(1049, `Unknown database '${db}'`));
627
- this.socket.end();
628
- return;
629
- }
630
- }
631
- this._send(okPacket());
632
- } catch (e) {
633
- this._send(errPacket(1105, 'auth failed: ' + e.message));
634
- this.socket.end();
635
- }
636
- }
637
-
638
- countPlaceholders(sql) {
639
- let n = 0;
640
- let inStr = null;
641
- for (let i = 0; i < sql.length; i++) {
642
- const c = sql[i];
643
- if (inStr) {
644
- if (c === '\\' && i + 1 < sql.length) { i++; continue; }
645
- if (c === inStr) inStr = null;
646
- continue;
647
- }
648
- if (c === "'" || c === '"' || c === '`') { inStr = c; continue; }
649
- if (c === '?' && sql[i + 1] === '?') { i++; continue; }
650
- if (c === '?') n++;
651
- }
652
- return n;
653
- }
654
-
655
- _handleStmtPrepare(body) {
656
- try {
657
- const sql = body.toString('utf8');
658
- const numParams = this.countPlaceholders(sql);
659
- let numColumns = 0;
660
- try {
661
- const stmt = parseSQL(sql);
662
- if (stmt && stmt.type === 'select' && stmt.columns) numColumns = stmt.columns.length;
663
- } catch (e) { /* 无法解析的 SQL 仍可 prepare */ }
664
- const stmtId = ++this._stmtSeq;
665
- this._stmts.set(stmtId, { sql, numParams, numColumns });
666
-
667
- const pkt = Buffer.alloc(12);
668
- let off = 0;
669
- pkt[off++] = 0x00;
670
- pkt.writeUInt32LE(stmtId, off); off += 4;
671
- pkt.writeUInt16LE(numColumns, off); off += 2;
672
- pkt.writeUInt16LE(numParams, off); off += 2;
673
- pkt[off++] = 0x00;
674
- pkt.writeUInt16LE(0, off);
675
- this._send(pkt);
676
-
677
- if (numParams > 0) {
678
- for (let i = 0; i < numParams; i++) {
679
- this._send(columnDefinition({ name: '?', type: MYSQL_TYPE_VAR_STRING }));
680
- }
681
- this._send(eofPacket());
682
- }
683
- if (numColumns > 0) {
684
- for (let i = 0; i < numColumns; i++) {
685
- this._send(columnDefinition({ name: 'col' + (i + 1), type: MYSQL_TYPE_VAR_STRING }));
686
- }
687
- this._send(eofPacket());
688
- }
689
- } catch (e) {
690
- this._send(errPacket(toMysqlErrno(e), e.message));
691
- }
692
- }
693
-
694
- _handleStmtClose(body) {
695
- if (body.length >= 4) {
696
- const stmtId = body.readUInt32LE(0);
697
- this._stmts.delete(stmtId);
698
- }
699
- }
700
-
701
- _handleStmtExecute(body) {
702
- try {
703
- if (body.length < 9) throw new Error('malformed COM_STMT_EXECUTE');
704
- const stmtId = body.readUInt32LE(0);
705
- const stmt = this._stmts.get(stmtId);
706
- if (!stmt) {
707
- this._send(errPacket(1243, 'Unknown prepared statement handler (' + stmtId + ') given to mysqld_stmt_execute'));
708
- return;
709
- }
710
- const flags = body[4];
711
- const iteration = body.readUInt32LE(5);
712
- let values = null;
713
- if (stmt.numParams > 0) {
714
- let off = 9;
715
- const numParams = stmt.numParams;
716
- const nullBitmapLen = Math.ceil(numParams / 8);
717
- if (body.length < off + nullBitmapLen) throw new Error('malformed COM_STMT_EXECUTE params');
718
- const nullBitmap = body.slice(off, off + nullBitmapLen);
719
- off += nullBitmapLen;
720
- if (off >= body.length) throw new Error('malformed COM_STMT_EXECUTE params');
721
- const newParamsBound = body[off];
722
- off += 1;
723
- let types = stmt.types;
724
- if (newParamsBound & 0x01) {
725
- if (body.length < off + numParams * 2) throw new Error('malformed COM_STMT_EXECUTE types');
726
- types = [];
727
- for (let i = 0; i < numParams; i++) types.push(body.readUInt16LE(off + i * 2));
728
- stmt.types = types;
729
- off += numParams * 2;
730
- }
731
- if (!types) throw new Error('Parameter types unknown for prepared statement');
732
- values = [];
733
- for (let i = 0; i < numParams; i++) {
734
- const isNull = nullBitmap[Math.floor(i / 8)] & (1 << (i % 8));
735
- if (isNull) { values.push(null); continue; }
736
- const type = types[i] & 0xff;
737
- const parsed = this._readParamValue(body, off, type);
738
- values.push(parsed.value);
739
- off = parsed.off;
740
- }
741
- }
742
- this._handleQuery(stmt.sql, values !== null && values.length > 0 ? values : undefined);
743
- } catch (e) {
744
- this._send(errPacket(toMysqlErrno(e), e.message));
745
- }
746
- }
747
-
748
- _readParamValue(buf, off, type) {
749
- switch (type) {
750
- case MYSQL_TYPE_NULL: return { value: null, off };
751
- case 0x01: { const v = buf.readInt8(off); return { value: v, off: off + 1 }; } // TINY
752
- case 0x02: { const v = buf.readInt16LE(off); return { value: v, off: off + 2 }; } // SHORT
753
- case 0x03: { const v = buf.readInt32LE(off); return { value: v, off: off + 4 }; } // LONG
754
- case 0x08: { const v = buf.readBigInt64LE(off); return { value: Number(v), off: off + 8 }; } // LONGLONG
755
- case 0x04: { const v = buf.readFloatLE(off); return { value: v, off: off + 4 }; } // FLOAT
756
- case MYSQL_TYPE_DOUBLE: { const v = buf.readDoubleLE(off); return { value: v, off: off + 8 }; }
757
- case 0x0a: case 0x07: case 0x0b: case MYSQL_TYPE_DATETIME: { // DATE/TIMESTAMP/DATETIME/TIME
758
- const len = buf[off];
759
- off += 1;
760
- if (len === 0) return { value: null, off };
761
- let value;
762
- if (type === 0x0b) { // TIME
763
- let sign = 1;
764
- let p = off;
765
- if (buf[p] !== 0) sign = -1;
766
- p += 1;
767
- const days = buf.readUInt32LE(p); p += 4;
768
- const hour = buf[p++];
769
- const min = buf[p++];
770
- const sec = buf[p++];
771
- value = sign * (days * 24 + hour) + ':' + String(min).padStart(2, '0') + ':' + String(sec).padStart(2, '0');
772
- } else {
773
- let p = off;
774
- const year = buf.readUInt16LE(p); p += 2;
775
- const month = buf[p++];
776
- const day = buf[p++];
777
- let hour = 0, minute = 0, second = 0;
778
- if (len >= 7) { hour = buf[p++]; minute = buf[p++]; second = buf[p++]; }
779
- value = String(year).padStart(4, '0') + '-' + String(month).padStart(2, '0') + '-' + String(day).padStart(2, '0');
780
- if (len >= 7) value += ' ' + String(hour).padStart(2, '0') + ':' + String(minute).padStart(2, '0') + ':' + String(second).padStart(2, '0');
781
- }
782
- off += len;
783
- return { value, off };
784
- }
785
- case 0xfc: case 0xfd: case 0xfe: case 0xf9: case 0xf6: { // BLOB / VAR_STRING / STRING / ...
786
- const { value, off: noff } = readLenenc(buf, off);
787
- return { value, off: noff };
788
- }
789
- case 0xf0: { // NULL
790
- return { value: null, off };
791
- }
792
- default: {
793
- const { value, off: noff } = readLenenc(buf, off);
794
- return { value, off: noff };
795
- }
796
- }
797
- }
798
-
799
- _handleQuery(sql, values) {
800
- (async () => {
801
- try {
802
- const hasParams = values !== undefined && values !== null && values.length > 0;
803
- const processedSql = hasParams ? applyParams(sql, values) : sql;
804
- const statements = splitStatements(processedSql);
805
- if (statements.length > 1 && !this.multiStatements) {
806
- throw new Error(`too many statements (${statements.length} > 1)`);
807
- }
808
- for (const stmtSql of statements) {
809
- const stmt = parseSQL(stmtSql);
810
- switch (stmt.type) {
811
- case 'use': {
812
- if (!this.server._canAccessDb(this.user, stmt.database)) {
813
- this._send(errPacket(1044, `Access denied for user '${this.user}' to database '${stmt.database}'`));
814
- continue;
815
- }
816
- try {
817
- await this.server._getDatabase(stmt.database);
818
- this.currentDb = stmt.database;
819
- this._send(okPacket());
820
- } catch (e) {
821
- this._send(errPacket(1049, `Unknown database '${stmt.database}'`));
822
- }
823
- continue;
824
- }
825
- case 'showDatabases': {
826
- const dbs = await this.server.listDatabases();
827
- const allowed = this.server._userDbList(this.user);
828
- const names = allowed === null ? dbs : dbs.filter(n => allowed.includes(n));
829
- const { packets, sequence } = resultSetPacket({
830
- type: 'showDatabases', columns: ['Database'], rows: names.map(n => [n]),
831
- }, null, this.sequence);
832
- this.sequence = sequence;
833
- this.socket.write(Buffer.concat(packets));
834
- continue;
835
- }
836
- case 'createDatabase': {
837
- if (!this.server._canAccessDb(this.user, stmt.database)) {
838
- this._send(errPacket(1044, `Access denied for user '${this.user}' to database '${stmt.database}'`));
839
- continue;
840
- }
841
- await this.server.createDatabase(stmt.database, { ifNotExists: stmt.ifNotExists });
842
- this._send(okPacket());
843
- continue;
844
- }
845
- case 'dropDatabase': {
846
- if (!this.server._canAccessDb(this.user, stmt.database)) {
847
- this._send(errPacket(1044, `Access denied for user '${this.user}' to database '${stmt.database}'`));
848
- continue;
849
- }
850
- await this.server.dropDatabase(stmt.database, { ifExists: stmt.ifExists });
851
- this._send(okPacket());
852
- continue;
853
- }
854
- default:
855
- break;
856
- }
857
- const routed = await this.server._route(stmt, stmtSql, this.currentDb, this.user);
858
- const engine = routed.engine;
859
- const sqlToRun = routed.sql;
860
- const r = await executeSQL(engine, sqlToRun, {
861
- allowComments: this.server.allowComments,
862
- safety: this.server.safety,
863
- maxStatements: 1,
864
- session: this.session,
865
- });
866
- if (r.type === 'select' || r.type === 'showTables' || r.type === 'showDatabases' || r.type === 'describe'
867
- || r.type === 'showColumns' || r.type === 'showIndex' || r.type === 'showCreateTable'
868
- || r.type === 'showVariables' || r.type === 'showStatus' || r.type === 'showGrants' || r.type === 'showWarnings') {
869
- this.session.foundRows = (r.raw && r.raw.length !== undefined) ? r.raw.length : (r.rows ? r.rows.length : 0);
870
- let schema = null;
871
- if (r.table) {
872
- schema = engine.getTableSchema
873
- ? await engine.getTableSchema(r.table)
874
- : (engine._schemas ? engine._schemas[r.table] : null);
875
- }
876
- const { packets, sequence } = hasParams
877
- ? binaryResultSetPacket(r, schema, this.sequence)
878
- : resultSetPacket(r, schema, this.sequence);
879
- this.sequence = sequence;
880
- this.socket.write(Buffer.concat(packets));
881
- } else {
882
- if (r.type === 'insert') {
883
- this.session.lastInsertId = r.insertId;
884
- this.session.rowCount = r.affectedRows;
885
- } else if (r.type === 'update' || r.type === 'delete' || r.type === 'truncate') {
886
- this.session.rowCount = r.affectedRows;
887
- }
888
- this._send(okPacket(r.affectedRows || 0, r.insertId || 0));
889
- }
890
- }
891
- } catch (e) {
892
- this._send(errPacket(toMysqlErrno(e), e.message));
893
- }
894
- })();
895
- }
896
- }
897
-
898
- class MysqlServer {
899
- constructor(options = {}) {
900
- this.options = options;
901
- this.port = options.port ?? 3306;
902
- this.host = options.host || '127.0.0.1';
903
- this.user = options.user || null;
904
- this.password = options.password || null;
905
- this.auth = options.auth || null;
906
- this.safety = options.safety !== false;
907
- this.allowComments = options.allowComments === true;
908
- this.maxPacketSize = options.maxPacketSize || 1024 * 1024;
909
- this.handshakeTimeout = options.handshakeTimeout != null ? options.handshakeTimeout : 10000;
910
- this.idleTimeout = options.idleTimeout != null ? options.idleTimeout : 300000;
911
- this.maxAuthFails = options.maxAuthFails || 3;
912
- this.maxConnections = options.maxConnections || 128;
913
- this._engine = null;
914
- this._ownEngine = false;
915
- this._databases = new Map();
916
- this._dbDir = options.dataDir && options.dataDir !== ':memory:' ? path.resolve(options.dataDir) : null;
917
- this._defaultDbName = options.defaultDatabase || 'default';
918
- this._connectionCounter = 0;
919
- this._sockets = new Set();
920
- this._securityHandler = typeof options.onSecurityEvent === 'function' ? options.onSecurityEvent : null;
921
- }
922
-
923
- _onSecurityEvent(event) {
924
- if (this._securityHandler) {
925
- try { this._securityHandler(event); } catch (e) {}
926
- }
927
- }
928
-
929
- async _getEngine(dbName) {
930
- if (dbName) return this._getDatabase(dbName);
931
- if (this._dbDir) {
932
- return this._getDatabase(this._defaultDbName, { autoCreate: true });
933
- }
934
- if (this._engine) return this._engine;
935
- if (this.options.engine || this.options.database) {
936
- this._engine = this.options.engine || this.options.database;
937
- if (typeof this._engine.start === 'function') await this._engine.start();
938
- } else {
939
- this._engine = new Database(this.options.filename || ':memory:');
940
- if (typeof this._engine.start === 'function') await this._engine.start();
941
- this._ownEngine = true;
942
- }
943
- return this._engine;
944
- }
945
-
946
- _dbExists(name) {
947
- if (this._databases.has(name)) return true;
948
- const dir = this._dbPath(name);
949
- return !!(dir && fs.existsSync(dir));
950
- }
951
-
952
- // 语句路由:解析库前缀(db.table)与 SHOW TABLES FROM db,
953
- // 决定执行引擎,并把 db.table 改写为 table 后交给对应库引擎执行。
954
- async _route(stmt, sql, currentDb, user) {
955
- if (stmt.type === 'showTables' && stmt.database) {
956
- if (user && this.auth && !this._canAccessDb(user, stmt.database)) {
957
- const err = new Error(`Access denied for user '${user}' to database '${stmt.database}'`);
958
- err.code = 1044;
959
- throw err;
960
- }
961
- return { engine: await this._getDatabase(stmt.database), sql };
962
- }
963
- const tables = [];
964
- const collect = (s) => {
965
- if (!s) return;
966
- if (s.type === 'createTable') tables.push(s.name);
967
- else if (s.type === 'dropTable') tables.push(s.table);
968
- else if (s.type === 'insert') tables.push(s.name);
969
- else if (s.type === 'update') tables.push(s.table);
970
- else if (s.type === 'delete') tables.push(s.table);
971
- else if (s.type === 'describe') tables.push(s.table);
972
- else if (s.type === 'select' && s.from) {
973
- for (const t of s.from.tables) tables.push(t.table);
974
- for (const j of s.from.joins) tables.push(j.item.table);
975
- }
976
- };
977
- collect(stmt);
978
- const isInfoSchema = tables.some(t => t && String(t).toLowerCase().startsWith('information_schema.'));
979
- if (isInfoSchema) {
980
- return { engine: await this._getEngine(currentDb), sql };
981
- }
982
- let db = null;
983
- for (const t of tables) {
984
- if (t && t.indexOf('.') !== -1) {
985
- const d = t.split('.')[0];
986
- if (db && d !== db) {
987
- const err = new Error(`Cross-database references are not supported in one statement`);
988
- err.code = 1105;
989
- throw err;
990
- }
991
- db = d;
992
- }
993
- }
994
- if (db) {
995
- if (user && this.auth && !this._canAccessDb(user, db)) {
996
- const err = new Error(`Access denied for user '${user}' to database '${db}'`);
997
- err.code = 1044;
998
- throw err;
999
- }
1000
- if (!this._dbExists(db)) await this.createDatabase(db);
1001
- const engine = await this._getDatabase(db);
1002
- let s = sql;
1003
- for (const t of tables) {
1004
- if (t && t.indexOf('.') !== -1) {
1005
- s = s.replace(new RegExp('\\b' + t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\b', 'g'), t.split('.')[1]);
1006
- }
1007
- }
1008
- return { engine, sql: s };
1009
- }
1010
- if (this._dbDir && !currentDb && this.auth && !this._canAccessDb(user, this._defaultDbName)) {
1011
- const err = new Error('No database selected');
1012
- err.code = 1046;
1013
- throw err;
1014
- }
1015
- return { engine: await this._getEngine(currentDb), sql };
1016
- }
1017
-
1018
- _safeDbName(name) {
1019
- if (typeof name !== 'string' || name.length === 0 || name.length > 64) return false;
1020
- if (!/^[a-zA-Z0-9_$.\-]+$/.test(name)) return false;
1021
- if (name === '.' || name === '..' || name.includes('..')) return false;
1022
- return true;
1023
- }
1024
-
1025
- _dbPath(name) {
1026
- if (!this._dbDir) return null;
1027
- if (!this._safeDbName(name)) return null;
1028
- return path.join(this._dbDir, name);
1029
- }
1030
-
1031
- async _getDatabase(name, opts = {}) {
1032
- if (!this._safeDbName(name)) {
1033
- const err = new Error(`Unknown database '${name}'`);
1034
- err.code = 1049;
1035
- throw err;
1036
- }
1037
- if (this._databases.has(name)) return this._databases.get(name);
1038
- const dir = this._dbPath(name);
1039
- if (dir && !fs.existsSync(dir)) {
1040
- if (opts.autoCreate) {
1041
- fs.mkdirSync(dir, { recursive: true });
1042
- } else {
1043
- const err = new Error(`Unknown database '${name}'`);
1044
- err.code = 1049;
1045
- throw err;
1046
- }
1047
- }
1048
- const engine = dir ? new Database(dir, { mode: 'hybrid' }) : new Database(':memory:');
1049
- if (typeof engine.start === 'function') await engine.start();
1050
- this._databases.set(name, engine);
1051
- return engine;
1052
- }
1053
-
1054
- async listDatabases() {
1055
- if (!this._dbDir) return ['jsql'];
1056
- let names = [];
1057
- if (fs.existsSync(this._dbDir)) {
1058
- names = fs.readdirSync(this._dbDir).filter(n => {
1059
- const p = path.join(this._dbDir, n);
1060
- return fs.statSync(p).isDirectory() && this._safeDbName(n);
1061
- });
1062
- }
1063
- for (const n of this._databases.keys()) {
1064
- if (!names.includes(n)) names.push(n);
1065
- }
1066
- return names.sort();
1067
- }
1068
-
1069
- async createDatabase(name, opts = {}) {
1070
- if (!this._safeDbName(name)) {
1071
- const err = new Error(`Invalid database name '${name}'`);
1072
- err.code = 1105;
1073
- throw err;
1074
- }
1075
- if (this._databases.has(name)) {
1076
- if (opts.ifNotExists) return this._databases.get(name);
1077
- const err = new Error(`Can't create database '${name}'; database exists`);
1078
- err.code = 1007;
1079
- throw err;
1080
- }
1081
- const dir = this._dbPath(name);
1082
- if (dir && fs.existsSync(dir)) {
1083
- if (opts.ifNotExists) return this._getDatabase(name);
1084
- const err = new Error(`Can't create database '${name}'; database exists`);
1085
- err.code = 1007;
1086
- throw err;
1087
- }
1088
- if (dir) fs.mkdirSync(dir, { recursive: true });
1089
- const engine = dir ? new Database(dir, { mode: 'hybrid' }) : new Database(':memory:');
1090
- if (typeof engine.start === 'function') await engine.start();
1091
- this._databases.set(name, engine);
1092
- return engine;
1093
- }
1094
-
1095
- async dropDatabase(name, opts = {}) {
1096
- if (!this._safeDbName(name)) {
1097
- const err = new Error(`Unknown database '${name}'`);
1098
- err.code = 1049;
1099
- throw err;
1100
- }
1101
- const engine = this._databases.get(name);
1102
- if (!engine) {
1103
- if (opts.ifExists) return;
1104
- const err = new Error(`Can't drop database '${name}'; database doesn't exist`);
1105
- err.code = 1008;
1106
- throw err;
1107
- }
1108
- if (typeof engine.stop === 'function') {
1109
- try { await engine.stop(); } catch (e) {}
1110
- }
1111
- this._databases.delete(name);
1112
- const dir = this._dbPath(name);
1113
- if (dir && fs.existsSync(dir)) {
1114
- fs.rmSync(dir, { recursive: true, force: true });
1115
- }
1116
- }
1117
-
1118
- _userDbList(user) {
1119
- if (!this.auth) return null;
1120
- const entry = this.auth[user];
1121
- if (entry && typeof entry === 'object') {
1122
- if (Array.isArray(entry.databases)) return entry.databases;
1123
- return null;
1124
- }
1125
- return [];
1126
- }
1127
-
1128
- _canAccessDb(user, dbName) {
1129
- const list = this._userDbList(user);
1130
- if (list === null) return true;
1131
- return list.includes(dbName);
1132
- }
1133
-
1134
- async _switchDb(conn, dbName) {
1135
- if (!this._canAccessDb(conn.user, dbName)) {
1136
- const err = new Error(`Access denied for user '${conn.user}' to database '${dbName}'`);
1137
- err.code = 1044;
1138
- throw err;
1139
- }
1140
- await this._getDatabase(dbName);
1141
- conn.currentDb = dbName;
1142
- }
1143
-
1144
- _checkAuth(user, authResponse, seed) {
1145
- if (this.auth) {
1146
- if (!Object.prototype.hasOwnProperty.call(this.auth, user)) return false;
1147
- const entry = this.auth[user];
1148
- const pwd = entry && typeof entry === 'object' ? entry.password : entry;
1149
- if (!pwd) return authResponse.length === 0;
1150
- const pwdHash1 = crypto.createHash('sha1').update(pwd).digest();
1151
- const pwdHash2 = crypto.createHash('sha1').update(pwdHash1).digest();
1152
- const seedHash = crypto.createHash('sha1').update(Buffer.concat([seed, pwdHash2])).digest();
1153
- const expected = Buffer.alloc(20);
1154
- for (let i = 0; i < 20; i++) expected[i] = pwdHash1[i] ^ seedHash[i];
1155
- return authResponse.length === 20 && crypto.timingSafeEqual(expected, authResponse);
1156
- }
1157
- if (this.user === null) {
1158
- // 未配置认证:默认拒绝所有登录(防配置遗漏导致认证旁路)。
1159
- // 仅当显式设置 noAuth: true 时允许无认证连接(仅限本地开发)。
1160
- return this.options.noAuth === true;
1161
- }
1162
- if (user !== this.user) return false;
1163
- if (!this.password) return authResponse.length === 0;
1164
- const pwdHash1 = crypto.createHash('sha1').update(this.password).digest();
1165
- const pwdHash2 = crypto.createHash('sha1').update(pwdHash1).digest();
1166
- const seedHash = crypto.createHash('sha1').update(Buffer.concat([seed, pwdHash2])).digest();
1167
- const expected = Buffer.alloc(20);
1168
- for (let i = 0; i < 20; i++) expected[i] = pwdHash1[i] ^ seedHash[i];
1169
- return authResponse.length === 20 && crypto.timingSafeEqual(expected, authResponse);
1170
- }
1171
-
1172
- listen(cb) {
1173
- const ready = this._dbDir ? Promise.resolve() : this._getEngine();
1174
- ready.then(() => {
1175
- this._server = net.createServer(socket => {
1176
- if (this._sockets.size >= this.maxConnections) {
1177
- this._onSecurityEvent({ type: 'max-connections', remote: socket.remoteAddress });
1178
- socket.destroy();
1179
- return;
1180
- }
1181
- this._sockets.add(socket);
1182
- socket.on('close', () => this._sockets.delete(socket));
1183
- new MysqlConnection(socket, this);
1184
- });
1185
- this._server.once('error', err => {
1186
- if (err && err.code === 'EADDRINUSE') {
1187
- const friendly = new Error(
1188
- `MysqlServer 端口 ${this.port} 已被占用(EADDRINUSE)。` +
1189
- `请换一个空闲端口,或传 port: 0 让系统分配临时端口。`
1190
- );
1191
- friendly.code = 'EADDRINUSE';
1192
- friendly.cause = err;
1193
- if (cb) cb(friendly);
1194
- else throw friendly;
1195
- } else if (cb) cb(err);
1196
- });
1197
- this._server.listen(this.port, this.host, cb || (() => {}));
1198
- }).catch(err => {
1199
- if (cb) cb(err);
1200
- else throw err;
1201
- });
1202
- return this;
1203
- }
1204
-
1205
- get address() {
1206
- return this._server ? this._server.address() : null;
1207
- }
1208
-
1209
- close(cb) {
1210
- const done = () => {
1211
- for (const s of this._sockets) s.destroy();
1212
- const stops = [];
1213
- if (this._ownEngine && this._engine && typeof this._engine.stop === 'function') {
1214
- stops.push(this._engine.stop());
1215
- }
1216
- for (const engine of this._databases.values()) {
1217
- if (engine && typeof engine.stop === 'function') stops.push(engine.stop());
1218
- }
1219
- this._databases.clear();
1220
- if (stops.length > 0) {
1221
- Promise.allSettled(stops).then(() => cb && cb());
1222
- } else if (cb) cb();
1223
- };
1224
- if (this._server) {
1225
- this._server.close(() => done());
1226
- } else {
1227
- done();
1228
- }
1229
- return this;
1230
- }
1231
- }
1232
-
1233
- function createMysqlServer(options) {
1234
- return new MysqlServer(options || {});
1235
- }
1236
-
1237
- module.exports = { createMysqlServer, MysqlServer, MysqlConnection };
1
+ // © Vexify 2026 All Rights Reserved.
2
+ /**
3
+ * MySQL 协议服务端 — TCP 3306
4
+ * 实现 MySQL 握手/认证/命令循环,任何 mysql/mysql2 客户端可直接连接。
5
+ */
6
+
7
+ const net = require('net');
8
+ const crypto = require('crypto');
9
+ const path = require('path');
10
+ const fs = require('fs');
11
+ const { executeSQL, parseSQL, splitStatements, applyParams } = require('./sql');
12
+ const Database = require('./database');
13
+
14
+ const SERVER_VERSION = '8.0.0-jsql-neo';
15
+ const CLIENT_PROTOCOL_41 = 0x00000001 << 9;
16
+ const CLIENT_SECURE_CONNECTION = 0x00008000;
17
+ const CLIENT_PLUGIN_AUTH = 0x00080000;
18
+ const CLIENT_CONNECT_WITH_DB = 0x00000008;
19
+ const CLIENT_LONG_PASSWORD = 0x00000001;
20
+ const CLIENT_TRANSACTIONS = 0x00002000;
21
+ const CLIENT_MULTI_STATEMENTS = 0x00010000;
22
+ const CLIENT_MULTI_RESULTS = 0x00020000;
23
+ const CLIENT_LONG_FLAG = 0x00000004;
24
+ const CLIENT_DEPRECATE_EOF = 0x01000000;
25
+ const SERVER_STATUS_AUTOCOMMIT = 0x0002;
26
+ const CHARSET_UTF8 = 0x21;
27
+
28
+ const MYSQL_TYPE_TINY = 1, MYSQL_TYPE_LONG = 3, MYSQL_TYPE_LONGLONG = 8,
29
+ MYSQL_TYPE_DATE = 10, MYSQL_TYPE_DATETIME = 12, MYSQL_TYPE_DOUBLE = 5,
30
+ MYSQL_TYPE_STRING = 254, MYSQL_TYPE_VAR_STRING = 253, MYSQL_TYPE_BLOB = 252,
31
+ MYSQL_TYPE_JSON = 245, MYSQL_TYPE_NULL = 6;
32
+
33
+ function encodeLenenc(value) {
34
+ if (typeof value === 'bigint') value = Number(value);
35
+ if (value === null) return Buffer.from([0xfb]);
36
+ if (value < 0xfb) return Buffer.from([value]);
37
+ if (value <= 0xffff) {
38
+ const b = Buffer.alloc(3);
39
+ b[0] = 0xfc; b.writeUInt16LE(value, 1);
40
+ return b;
41
+ }
42
+ if (value <= 0xffffff) {
43
+ const b = Buffer.alloc(4);
44
+ b[0] = 0xfd; b.writeUIntLE(value, 1, 3);
45
+ return b;
46
+ }
47
+ const b = Buffer.alloc(9);
48
+ b[0] = 0xfe; b.writeUInt32LE(value, 1); b.writeUInt32LE(Math.floor(value / 4294967296), 5);
49
+ return b;
50
+ }
51
+
52
+ function encodeLenencString(str) {
53
+ const buf = Buffer.from(String(str), 'utf8');
54
+ return Buffer.concat([encodeLenenc(buf.length), buf]);
55
+ }
56
+
57
+ function parseLenenc(buf, offset) {
58
+ const first = buf[offset];
59
+ if (first < 0xfb) return { value: first, size: 1 };
60
+ if (first === 0xfb) return { value: null, size: 1 };
61
+ if (first === 0xfc) return { value: buf.readUInt16LE(offset + 1), size: 3 };
62
+ if (first === 0xfd) return { value: buf.readUIntLE(offset + 1, 3), size: 4 };
63
+ return { value: buf.readUInt32LE(offset + 1), size: 5 };
64
+ }
65
+
66
+ function readLenenc(buf, offset) {
67
+ const { value, size } = parseLenenc(buf, offset);
68
+ if (value === null) return { value: null, off: offset + size };
69
+ const str = buf.slice(offset + size, offset + size + value).toString('utf8');
70
+ return { value: str, off: offset + size + value };
71
+ }
72
+
73
+ class PacketBuilder {
74
+ constructor() {
75
+ this.bufs = [];
76
+ }
77
+ byte(v) { this.bufs.push(Buffer.from([v & 0xff])); return this; }
78
+ int16(v) { const b = Buffer.alloc(2); b.writeUInt16LE(v & 0xffff); this.bufs.push(b); return this; }
79
+ int32(v) { const b = Buffer.alloc(4); b.writeUInt32LE(v >>> 0); this.bufs.push(b); return this; }
80
+ raw(buf) { this.bufs.push(buf); return this; }
81
+ string(str) { this.bufs.push(Buffer.from(str, 'utf8')); return this; }
82
+ nul(str) { this.bufs.push(Buffer.concat([Buffer.from(String(str), 'utf8'), Buffer.from([0])])); return this; }
83
+ bytes(list) { this.bufs.push(Buffer.from(list)); return this; }
84
+ build() { return Buffer.concat(this.bufs); }
85
+ }
86
+
87
+ function handshakePacket(connectionId, seed) {
88
+ const b = new PacketBuilder();
89
+ b.byte(10);
90
+ b.nul(SERVER_VERSION);
91
+ b.int32(connectionId);
92
+ b.bytes(seed.slice(0, 8));
93
+ b.byte(0);
94
+ const caps = CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION | CLIENT_PLUGIN_AUTH | CLIENT_CONNECT_WITH_DB | CLIENT_LONG_PASSWORD | CLIENT_TRANSACTIONS | CLIENT_MULTI_STATEMENTS | CLIENT_MULTI_RESULTS | CLIENT_LONG_FLAG;
95
+ b.int16(caps & 0xffff);
96
+ b.byte(CHARSET_UTF8);
97
+ b.int16(SERVER_STATUS_AUTOCOMMIT);
98
+ b.int16((caps >>> 16) & 0xffff);
99
+ b.byte(21);
100
+ b.bytes([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
101
+ b.bytes(seed.slice(8, 20));
102
+ b.byte(0);
103
+ b.nul('mysql_native_password');
104
+ return b.build();
105
+ }
106
+
107
+ /**
108
+ * 生成握手 seed:使用可打印 ASCII(33-126),避开 0x00。
109
+ * 老客户端按 C 字符串读取 seed,若含 \0 会提前截断导致握手失败/断连。
110
+ */
111
+ function genSeed(len = 20) {
112
+ const out = Buffer.alloc(len);
113
+ for (let i = 0; i < len; i++) {
114
+ out[i] = 33 + Math.floor(Math.random() * 94);
115
+ }
116
+ return out;
117
+ }
118
+
119
+ function okPacket(affectedRows = 0, insertId = 0, status = SERVER_STATUS_AUTOCOMMIT) { const b = new PacketBuilder();
120
+ b.byte(0x00);
121
+ b.raw(encodeLenenc(affectedRows));
122
+ b.raw(encodeLenenc(insertId));
123
+ b.int16(status);
124
+ b.int16(0);
125
+ return b.build();
126
+ }
127
+
128
+ function eofPacket() {
129
+ const b = new PacketBuilder();
130
+ b.byte(0xfe);
131
+ b.int16(0);
132
+ b.int16(SERVER_STATUS_AUTOCOMMIT);
133
+ return b.build();
134
+ }
135
+
136
+ function errPacket(errno, message, sqlState = 'HY000') {
137
+ const b = new PacketBuilder();
138
+ b.byte(0xff);
139
+ b.int16(errno);
140
+ b.byte(0x23);
141
+ b.string(sqlState);
142
+ b.string(String(message).slice(0, 200));
143
+ return b.build();
144
+ }
145
+
146
+ function toMysqlErrno(e) {
147
+ if (e && typeof e.code === 'number') return e.code;
148
+ const m = e && e.message ? String(e.message).match(/^(ER_[A-Z_]+)/) : null;
149
+ if (m) {
150
+ const known = {
151
+ ER_DUP_ENTRY: 1062,
152
+ ER_NO_SUCH_TABLE: 1146,
153
+ ER_TABLE_EXISTS: 1050,
154
+ ER_TABLE_EXISTS_ERROR: 1050,
155
+ ER_PARSE_ERROR: 1064,
156
+ ER_BAD_FIELD_ERROR: 1054,
157
+ ER_BAD_NULL_ERROR: 1048,
158
+ ER_ACCESS_DENIED_ERROR: 1045,
159
+ ER_DBACCESS_DENIED_ERROR: 1044,
160
+ ER_BAD_DB_ERROR: 1049,
161
+ ER_WRONG_DB_NAME: 1102,
162
+ ER_WRONG_TABLE_NAME: 1103,
163
+ ER_WRONG_COLUMN_NAME: 1166,
164
+ ER_DATA_TOO_LONG: 1406,
165
+ ER_OUT_OF_RANGE: 1264,
166
+ ER_CHECK_CONSTRAINT: 3819,
167
+ ER_NO_DEFAULT_FOR_FIELD: 1364,
168
+ ER_CANT_DROP_FIELD_OR_KEY: 1091,
169
+ ER_CANT_DROP_DATABASE: 1008,
170
+ ER_EMPTY_QUERY: 1065,
171
+ ER_UNKNOWN_TABLE: 1109,
172
+ ER_NON_UNIQ_ERROR: 1052,
173
+ ER_WRONG_FIELD_WITH_GROUP: 1055,
174
+ ER_WRONG_VALUE_COUNT_ON_ROW: 1136,
175
+ ER_MISSING_TABLE: 1052,
176
+ ER_SP_DOES_NOT_EXIST: 1305,
177
+ ER_NOT_SUPPORTED_YET: 1235,
178
+ ER_LOCK_DEADLOCK: 1213,
179
+ ER_LOCK_WAIT_TIMEOUT: 1205,
180
+ ER_UNKNOWN_ERROR: 1105,
181
+ };
182
+ if (known[m[1]]) return known[m[1]];
183
+ }
184
+ return 1105;
185
+ }
186
+
187
+ function columnDefinition(column) {
188
+ const b = new PacketBuilder();
189
+ b.raw(encodeLenencString('def'));
190
+ b.raw(encodeLenencString(column.table || ''));
191
+ b.raw(encodeLenencString(column.table || ''));
192
+ b.raw(encodeLenencString(column.table || ''));
193
+ b.raw(encodeLenencString(column.name || ''));
194
+ b.raw(encodeLenencString(column.name || ''));
195
+ b.raw(encodeLenenc(0x0c));
196
+ b.int16(column.charset || CHARSET_UTF8);
197
+ b.int32(column.length || 1024);
198
+ b.byte(column.type !== undefined ? column.type : MYSQL_TYPE_VAR_STRING);
199
+ b.int16(0);
200
+ b.byte(0);
201
+ b.int16(0);
202
+ return b.build();
203
+ }
204
+
205
+ function columnTypeFromSchema(def) {
206
+ const type = def && def.type ? String(def.type).toLowerCase() : 'string';
207
+ if (type === 'integer' || type === 'int' || type === 'bigint') return MYSQL_TYPE_LONG;
208
+ if (type === 'number' || type === 'float' || type === 'double' || type === 'real') return MYSQL_TYPE_DOUBLE;
209
+ if (type === 'boolean' || type === 'bool') return MYSQL_TYPE_TINY;
210
+ if (type === 'date') return MYSQL_TYPE_DATE;
211
+ if (type === 'datetime' || type === 'timestamp') return MYSQL_TYPE_DATETIME;
212
+ if (type === 'object' || type === 'array') return MYSQL_TYPE_JSON;
213
+ if (type === 'binary') return MYSQL_TYPE_BLOB;
214
+ return MYSQL_TYPE_VAR_STRING;
215
+ }
216
+
217
+ function resultSetPacket(result, tableSchema, baseSeq, rawRow) {
218
+ const packets = [];
219
+ let sequence = baseSeq || 0;
220
+ const push = (buf) => {
221
+ const header = Buffer.alloc(4);
222
+ header.writeUIntLE(buf.length, 0, 3);
223
+ header[3] = sequence;
224
+ sequence++;
225
+ packets.push(header);
226
+ packets.push(buf);
227
+ };
228
+
229
+ const inferType = (name, value) => {
230
+ if (tableSchema && tableSchema[name]) return columnTypeFromSchema(tableSchema[name]);
231
+ if (value === null || value === undefined) return MYSQL_TYPE_NULL;
232
+ if (typeof value === 'number') return Number.isInteger(value) ? MYSQL_TYPE_LONGLONG : MYSQL_TYPE_DOUBLE;
233
+ if (typeof value === 'boolean') return MYSQL_TYPE_TINY;
234
+ if (typeof value === 'object') return MYSQL_TYPE_JSON;
235
+ return MYSQL_TYPE_VAR_STRING;
236
+ };
237
+
238
+ push(encodeLenenc((result.columns || []).length));
239
+ const sampleRow = (result.rows && result.rows[0]) || null;
240
+ for (let ci = 0; ci < (result.columns || []).length; ci++) {
241
+ const name = result.columns[ci];
242
+ let sample;
243
+ if (sampleRow !== null) {
244
+ if (Array.isArray(sampleRow)) sample = sampleRow[ci];
245
+ else sample = sampleRow[name];
246
+ }
247
+ const col = {
248
+ name,
249
+ table: result.table || '',
250
+ type: inferType(name, sample),
251
+ length: 1024,
252
+ };
253
+ push(columnDefinition(col));
254
+ }
255
+ push(eofPacket());
256
+
257
+ for (const row of result.rows || []) {
258
+ const parts = [];
259
+ for (let i = 0; i < (result.columns || []).length; i++) {
260
+ const v = row[i];
261
+ if (v === null || v === undefined) {
262
+ parts.push(Buffer.from([0xfb]));
263
+ } else if (typeof v === 'boolean') {
264
+ parts.push(encodeLenencString(v ? '1' : '0'));
265
+ } else if (typeof v === 'object') {
266
+ parts.push(encodeLenencString(JSON.stringify(v)));
267
+ } else {
268
+ parts.push(encodeLenencString(v));
269
+ }
270
+ }
271
+ push(Buffer.concat(parts));
272
+ }
273
+ push(eofPacket());
274
+ return { packets, sequence };
275
+ }
276
+
277
+ function binaryResultSetPacket(result, tableSchema, baseSeq) {
278
+ const packets = [];
279
+ let sequence = baseSeq || 0;
280
+ const push = (buf) => {
281
+ const header = Buffer.alloc(4);
282
+ header.writeUIntLE(buf.length, 0, 3);
283
+ header[3] = sequence;
284
+ sequence++;
285
+ packets.push(header);
286
+ packets.push(buf);
287
+ };
288
+
289
+ const inferType = (name, value) => {
290
+ if (tableSchema && tableSchema[name]) return columnTypeFromSchema(tableSchema[name]);
291
+ if (value === null || value === undefined) return MYSQL_TYPE_NULL;
292
+ if (typeof value === 'number') return Number.isInteger(value) ? MYSQL_TYPE_LONGLONG : MYSQL_TYPE_DOUBLE;
293
+ if (typeof value === 'boolean') return MYSQL_TYPE_TINY;
294
+ if (typeof value === 'object') return MYSQL_TYPE_JSON;
295
+ return MYSQL_TYPE_VAR_STRING;
296
+ };
297
+
298
+ const cols = result.columns || [];
299
+ push(encodeLenenc(cols.length));
300
+ const sampleRow = (result.rows && result.rows[0]) || null;
301
+ const types = [];
302
+ for (let ci = 0; ci < cols.length; ci++) {
303
+ let sample;
304
+ if (sampleRow !== null) {
305
+ if (Array.isArray(sampleRow)) sample = sampleRow[ci];
306
+ else sample = sampleRow[cols[ci]];
307
+ }
308
+ const t = inferType(cols[ci], sample);
309
+ types.push(t);
310
+ push(columnDefinition({ name: cols[ci], table: result.table || '', type: t, length: 1024 }));
311
+ }
312
+ push(eofPacket());
313
+
314
+ const encodeValue = (v, type) => {
315
+ if (v === null || v === undefined) return null;
316
+ if (type === MYSQL_TYPE_LONG || type === MYSQL_TYPE_LONGLONG || type === MYSQL_TYPE_DOUBLE) {
317
+ const num = Number(v);
318
+ if (type === MYSQL_TYPE_DOUBLE) {
319
+ if (!Number.isFinite(num)) return encodeLenencString(String(v));
320
+ const b = Buffer.alloc(8); b.writeDoubleLE(num, 0); return b;
321
+ }
322
+ if (Number.isInteger(num) && num <= 2147483647 && num >= -2147483648) {
323
+ const b = Buffer.alloc(4); b.writeInt32LE(num, 0); return b;
324
+ }
325
+ if (!Number.isFinite(num)) return encodeLenencString(String(v));
326
+ const b = Buffer.alloc(8); b.writeBigInt64LE(BigInt(Math.trunc(num)), 0); return b;
327
+ }
328
+ if (type === MYSQL_TYPE_TINY) {
329
+ const b = Buffer.alloc(1); b.writeInt8(v ? 1 : 0, 0); return b;
330
+ }
331
+ if (type === MYSQL_TYPE_DATE || type === MYSQL_TYPE_DATETIME) {
332
+ const s = String(v);
333
+ const m = s.match(/^(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2}):(\d{2}))?/);
334
+ if (!m) return encodeLenencString(String(v));
335
+ const hasTime = !!m[4];
336
+ const buf = hasTime ? Buffer.alloc(8) : Buffer.alloc(5);
337
+ let off = 0;
338
+ buf[off++] = hasTime ? 7 : 4;
339
+ buf.writeUInt16LE(parseInt(m[1], 10), off); off += 2;
340
+ buf[off++] = parseInt(m[2], 10);
341
+ buf[off++] = parseInt(m[3], 10);
342
+ if (hasTime) {
343
+ buf[off++] = parseInt(m[4], 10);
344
+ buf[off++] = parseInt(m[5], 10);
345
+ buf[off++] = parseInt(m[6], 10);
346
+ }
347
+ return buf;
348
+ }
349
+ return encodeLenencString(typeof v === 'object' ? JSON.stringify(v) : v);
350
+ };
351
+
352
+ for (const row of result.rows || []) {
353
+ const parts = [Buffer.from([0x00])];
354
+ const nb = Buffer.alloc(Math.ceil(cols.length / 8));
355
+ const encoded = [];
356
+ for (let i = 0; i < cols.length; i++) {
357
+ const v = Array.isArray(row) ? row[i] : row[cols[i]];
358
+ if (v === null || v === undefined) {
359
+ nb[Math.floor(i / 8)] |= (1 << (i % 8));
360
+ encoded.push(null);
361
+ } else {
362
+ encoded.push(encodeValue(v, types[i]));
363
+ }
364
+ }
365
+ parts.push(nb);
366
+ for (const e of encoded) if (e !== null) parts.push(e);
367
+ push(Buffer.concat(parts));
368
+ }
369
+ push(eofPacket());
370
+ return { packets, sequence };
371
+ }
372
+
373
+ class MysqlConnection {
374
+ constructor(socket, server) {
375
+ this.socket = socket;
376
+ this.server = server;
377
+ this.connectionId = ++server._connectionCounter;
378
+ this.seed = genSeed(20);
379
+ this.buffer = Buffer.alloc(0);
380
+ this.sequence = 0;
381
+ this.authenticated = false;
382
+ this.user = null;
383
+ this.authFails = 0;
384
+ this.multiStatements = false;
385
+ this._stmts = new Map();
386
+ this._stmtSeq = 0;
387
+ this.session = {
388
+ lastInsertId: 0,
389
+ rowCount: 0,
390
+ foundRows: 0,
391
+ connectionId: server._connectionCounter,
392
+ currentDb: null,
393
+ sysvars: {},
394
+ };
395
+ if (server.handshakeTimeout > 0) {
396
+ this._authTimer = setTimeout(() => {
397
+ if (!this.authenticated) {
398
+ this.server._onSecurityEvent({ type: 'auth-timeout', user: this.user, remote: socket.remoteAddress });
399
+ socket.destroy();
400
+ }
401
+ }, server.handshakeTimeout);
402
+ this._authTimer.unref();
403
+ }
404
+ this.socket.on('data', chunk => this._onData(chunk));
405
+ this.socket.on('error', () => {});
406
+ this._armIdleTimer();
407
+ this._send(handshakePacket(this.connectionId, this.seed));
408
+ }
409
+
410
+ _armIdleTimer() {
411
+ if (this._idleTimer) { clearTimeout(this._idleTimer); this._idleTimer = null; }
412
+ const ttl = this.server.idleTimeout;
413
+ if (!this.authenticated) return;
414
+ if (ttl <= 0) return;
415
+ this._idleTimer = setTimeout(() => {
416
+ this.server._onSecurityEvent({ type: 'idle-timeout', user: this.user, remote: this.socket.remoteAddress });
417
+ this.socket.destroy();
418
+ }, ttl);
419
+ this._idleTimer.unref();
420
+ }
421
+
422
+ _send(payload) {
423
+ if (!this.socket.writable) return;
424
+ const header = Buffer.alloc(4);
425
+ header.writeUIntLE(payload.length, 0, 3);
426
+ header[3] = this.sequence;
427
+ this.sequence = (this.sequence + 1) & 0xff;
428
+ this.socket.write(Buffer.concat([header, payload]));
429
+ }
430
+
431
+ _onData(chunk) {
432
+ this.buffer = Buffer.concat([this.buffer, chunk]);
433
+ this._armIdleTimer();
434
+ if (this.buffer.length > this.server.maxPacketSize + 4) {
435
+ this._malicious('packet exceeds maxPacketSize (' + this.server.maxPacketSize + ')');
436
+ return;
437
+ }
438
+ while (true) {
439
+ if (this.buffer.length < 4) return;
440
+ const len = this.buffer.readUIntLE(0, 3);
441
+ if (len > this.server.maxPacketSize) {
442
+ this._malicious('packet length ' + len + ' exceeds maxPacketSize');
443
+ return;
444
+ }
445
+ if (this.buffer.length < 4 + len) return;
446
+ const seq = this.buffer[3];
447
+ const payload = this.buffer.slice(4, 4 + len);
448
+ this.buffer = this.buffer.slice(4 + len);
449
+ this._handlePacket(payload, seq);
450
+ if (this.socket.destroyed) return;
451
+ }
452
+ }
453
+
454
+ _malicious(reason) {
455
+ this.socket.destroy();
456
+ this.server._onSecurityEvent({ type: 'malicious', reason, user: this.user, remote: this.socket.remoteAddress });
457
+ }
458
+
459
+ _handlePacket(payload, cmdSeq) {
460
+ if (!this.authenticated) {
461
+ this.sequence = (cmdSeq + 1) & 0xff;
462
+ this._handleAuth(payload);
463
+ return;
464
+ }
465
+ this.sequence = (cmdSeq + 1) & 0xff;
466
+ const cmd = payload[0];
467
+ const body = payload.slice(1);
468
+ try {
469
+ switch (cmd) {
470
+ case 0x01: this.socket.end(); break; // COM_QUIT
471
+ case 0x02: { // COM_INIT_DB
472
+ const db = body.toString('utf8');
473
+ if (!this.server._safeDbName(db)) {
474
+ this._malicious('COM_INIT_DB with invalid database name');
475
+ return;
476
+ }
477
+ this.server._switchDb(this, db)
478
+ .then(() => this._send(okPacket()))
479
+ .catch(e => this._send(errPacket(e.code || 1049, e.message)));
480
+ break;
481
+ }
482
+ case 0x03: {
483
+ const sql = body.toString('utf8');
484
+ if (sql.length === 0) {
485
+ this._send(errPacket(1065, 'Query was empty'));
486
+ break;
487
+ }
488
+ this._handleQuery(sql);
489
+ break;
490
+ }
491
+ case 0x11: this._handleChangeUser(body, cmdSeq); break; // COM_CHANGE_USER
492
+ case 0x0e: this._send(okPacket()); break; // COM_PING
493
+ case 0x1f: this._send(okPacket()); break; // COM_RESET_CONNECTION
494
+ case 0x16: this._handleStmtPrepare(body); break; // COM_STMT_PREPARE
495
+ case 0x17: this._handleStmtExecute(body); break; // COM_STMT_EXECUTE
496
+ case 0x18: break; // COM_STMT_SEND_LONG_DATA (忽略)
497
+ case 0x19: this._handleStmtClose(body); break; // COM_STMT_CLOSE
498
+ case 0x1a: this._send(okPacket()); break; // COM_STMT_RESET
499
+ case 0x0a: { // COM_PROCESS_INFO
500
+ const seq = this.sequence;
501
+ this.sequence = (this.sequence + 1) & 0xff;
502
+ this._send(encodeLenenc(1));
503
+ this._send(columnDefinition({ name: 'Id', type: MYSQL_TYPE_LONG }));
504
+ this._send(eofPacket());
505
+ this._send(Buffer.from([0x31]));
506
+ this._send(eofPacket());
507
+ break;
508
+ }
509
+ case 0x09: this._send(okPacket(0, 0, SERVER_STATUS_AUTOCOMMIT)); break; // COM_STATISTICS
510
+ default:
511
+ this._send(errPacket(1105, `Unsupported command: ${cmd}`));
512
+ }
513
+ } catch (e) {
514
+ this._send(errPacket(toMysqlErrno(e), e.message));
515
+ }
516
+ }
517
+
518
+ _handleChangeUser(body, cmdSeq) {
519
+ (async () => {
520
+ try {
521
+ let pos = 0;
522
+ const userEnd = body.indexOf(0, pos);
523
+ if (userEnd === -1) throw new Error('malformed COM_CHANGE_USER');
524
+ const user = body.slice(pos, userEnd).toString('utf8');
525
+ pos = userEnd + 1;
526
+ const lenenc = parseLenenc(body, pos);
527
+ let authResponse = Buffer.alloc(0);
528
+ if (lenenc.value !== null) {
529
+ authResponse = body.slice(pos + lenenc.size, pos + lenenc.size + lenenc.value);
530
+ pos += lenenc.size + lenenc.value;
531
+ } else {
532
+ pos += lenenc.size;
533
+ }
534
+ let db = null;
535
+ if (pos < body.length) {
536
+ const dbEnd = body.indexOf(0, pos);
537
+ if (dbEnd !== -1) {
538
+ db = body.slice(pos, dbEnd).toString('utf8');
539
+ }
540
+ }
541
+ const valid = this.server._checkAuth(user, authResponse, this.seed);
542
+ if (!valid) {
543
+ this._send(errPacket(1045, `Access denied for user '${user}'`));
544
+ return;
545
+ }
546
+ if (db) {
547
+ if (!this.server._canAccessDb(user, db)) {
548
+ this._send(errPacket(1044, `Access denied for user '${user}' to database '${db}'`));
549
+ return;
550
+ }
551
+ try {
552
+ await this.server._getDatabase(db);
553
+ } catch (e) {
554
+ this._send(errPacket(1049, `Unknown database '${db}'`));
555
+ return;
556
+ }
557
+ }
558
+ this.user = user;
559
+ this.currentDb = db;
560
+ this._send(okPacket());
561
+ } catch (e) {
562
+ this._send(errPacket(toMysqlErrno(e), e.message));
563
+ }
564
+ })();
565
+ }
566
+
567
+ async _handleAuth(payload) {
568
+ try {
569
+ const caps = payload.readUInt32LE(0);
570
+ let pos = 32; // 4 caps + 4 maxpacket + 1 charset + 23 reserved
571
+ const userEnd = payload.indexOf(0, pos);
572
+ this.user = payload.slice(pos, userEnd).toString('utf8');
573
+ pos = userEnd + 1;
574
+ let authResponse = Buffer.alloc(0);
575
+ if (caps & CLIENT_SECURE_CONNECTION) {
576
+ const lenenc = parseLenenc(payload, pos);
577
+ if (lenenc.value === null) { pos += lenenc.size; }
578
+ else {
579
+ authResponse = payload.slice(pos + lenenc.size, pos + lenenc.size + lenenc.value);
580
+ pos += lenenc.size + lenenc.value;
581
+ }
582
+ } else {
583
+ const end = payload.indexOf(0, pos);
584
+ if (end !== -1) {
585
+ authResponse = payload.slice(pos, end);
586
+ pos = end + 1;
587
+ }
588
+ }
589
+ let db = null;
590
+ if (caps & CLIENT_CONNECT_WITH_DB) {
591
+ const dbEnd = payload.indexOf(0, pos);
592
+ if (dbEnd !== -1) {
593
+ db = payload.slice(pos, dbEnd).toString('utf8');
594
+ pos = dbEnd + 1;
595
+ }
596
+ }
597
+ this.currentDb = db;
598
+
599
+ const valid = this.server._checkAuth(this.user, authResponse, this.seed);
600
+ if (!valid) {
601
+ this.authFails++;
602
+ this.server._onSecurityEvent({ type: 'auth-fail', user: this.user, fails: this.authFails, remote: this.socket.remoteAddress });
603
+ if (this.authFails >= this.server.maxAuthFails) {
604
+ this.socket.destroy();
605
+ return;
606
+ }
607
+ this._send(errPacket(1045, `Access denied for user '${this.user}'`));
608
+ this.socket.end();
609
+ return;
610
+ }
611
+ this.multiStatements = !!(caps & CLIENT_MULTI_STATEMENTS);
612
+ this.authenticated = true;
613
+ if (this._authTimer) { clearTimeout(this._authTimer); this._authTimer = null; }
614
+ this._armIdleTimer();
615
+ if (db) {
616
+ if (!this.server._canAccessDb(this.user, db)) {
617
+ this._send(errPacket(1044, `Access denied for user '${this.user}' to database '${db}'`));
618
+ this.socket.end();
619
+ return;
620
+ }
621
+ try {
622
+ if (!this.server._dbExists(db)) await this.server.createDatabase(db);
623
+ await this.server._getDatabase(db);
624
+ this.currentDb = db;
625
+ } catch (e) {
626
+ this._send(errPacket(1049, `Unknown database '${db}'`));
627
+ this.socket.end();
628
+ return;
629
+ }
630
+ }
631
+ this._send(okPacket());
632
+ } catch (e) {
633
+ this._send(errPacket(1105, 'auth failed: ' + e.message));
634
+ this.socket.end();
635
+ }
636
+ }
637
+
638
+ countPlaceholders(sql) {
639
+ let n = 0;
640
+ let inStr = null;
641
+ for (let i = 0; i < sql.length; i++) {
642
+ const c = sql[i];
643
+ if (inStr) {
644
+ if (c === '\\' && i + 1 < sql.length) { i++; continue; }
645
+ if (c === inStr) inStr = null;
646
+ continue;
647
+ }
648
+ if (c === "'" || c === '"' || c === '`') { inStr = c; continue; }
649
+ if (c === '?' && sql[i + 1] === '?') { i++; continue; }
650
+ if (c === '?') n++;
651
+ }
652
+ return n;
653
+ }
654
+
655
+ _handleStmtPrepare(body) {
656
+ try {
657
+ const sql = body.toString('utf8');
658
+ const numParams = this.countPlaceholders(sql);
659
+ let numColumns = 0;
660
+ try {
661
+ const stmt = parseSQL(sql);
662
+ if (stmt && stmt.type === 'select' && stmt.columns) numColumns = stmt.columns.length;
663
+ } catch (e) { /* 无法解析的 SQL 仍可 prepare */ }
664
+ const stmtId = ++this._stmtSeq;
665
+ this._stmts.set(stmtId, { sql, numParams, numColumns });
666
+
667
+ const pkt = Buffer.alloc(12);
668
+ let off = 0;
669
+ pkt[off++] = 0x00;
670
+ pkt.writeUInt32LE(stmtId, off); off += 4;
671
+ pkt.writeUInt16LE(numColumns, off); off += 2;
672
+ pkt.writeUInt16LE(numParams, off); off += 2;
673
+ pkt[off++] = 0x00;
674
+ pkt.writeUInt16LE(0, off);
675
+ this._send(pkt);
676
+
677
+ if (numParams > 0) {
678
+ for (let i = 0; i < numParams; i++) {
679
+ this._send(columnDefinition({ name: '?', type: MYSQL_TYPE_VAR_STRING }));
680
+ }
681
+ this._send(eofPacket());
682
+ }
683
+ if (numColumns > 0) {
684
+ for (let i = 0; i < numColumns; i++) {
685
+ this._send(columnDefinition({ name: 'col' + (i + 1), type: MYSQL_TYPE_VAR_STRING }));
686
+ }
687
+ this._send(eofPacket());
688
+ }
689
+ } catch (e) {
690
+ this._send(errPacket(toMysqlErrno(e), e.message));
691
+ }
692
+ }
693
+
694
+ _handleStmtClose(body) {
695
+ if (body.length >= 4) {
696
+ const stmtId = body.readUInt32LE(0);
697
+ this._stmts.delete(stmtId);
698
+ }
699
+ }
700
+
701
+ _handleStmtExecute(body) {
702
+ try {
703
+ if (body.length < 9) throw new Error('malformed COM_STMT_EXECUTE');
704
+ const stmtId = body.readUInt32LE(0);
705
+ const stmt = this._stmts.get(stmtId);
706
+ if (!stmt) {
707
+ this._send(errPacket(1243, 'Unknown prepared statement handler (' + stmtId + ') given to mysqld_stmt_execute'));
708
+ return;
709
+ }
710
+ const flags = body[4];
711
+ const iteration = body.readUInt32LE(5);
712
+ let values = null;
713
+ if (stmt.numParams > 0) {
714
+ let off = 9;
715
+ const numParams = stmt.numParams;
716
+ const nullBitmapLen = Math.ceil(numParams / 8);
717
+ if (body.length < off + nullBitmapLen) throw new Error('malformed COM_STMT_EXECUTE params');
718
+ const nullBitmap = body.slice(off, off + nullBitmapLen);
719
+ off += nullBitmapLen;
720
+ if (off >= body.length) throw new Error('malformed COM_STMT_EXECUTE params');
721
+ const newParamsBound = body[off];
722
+ off += 1;
723
+ let types = stmt.types;
724
+ if (newParamsBound & 0x01) {
725
+ if (body.length < off + numParams * 2) throw new Error('malformed COM_STMT_EXECUTE types');
726
+ types = [];
727
+ for (let i = 0; i < numParams; i++) types.push(body.readUInt16LE(off + i * 2));
728
+ stmt.types = types;
729
+ off += numParams * 2;
730
+ }
731
+ if (!types) throw new Error('Parameter types unknown for prepared statement');
732
+ values = [];
733
+ for (let i = 0; i < numParams; i++) {
734
+ const isNull = nullBitmap[Math.floor(i / 8)] & (1 << (i % 8));
735
+ if (isNull) { values.push(null); continue; }
736
+ const type = types[i] & 0xff;
737
+ const parsed = this._readParamValue(body, off, type);
738
+ values.push(parsed.value);
739
+ off = parsed.off;
740
+ }
741
+ }
742
+ this._handleQuery(stmt.sql, values !== null && values.length > 0 ? values : undefined);
743
+ } catch (e) {
744
+ this._send(errPacket(toMysqlErrno(e), e.message));
745
+ }
746
+ }
747
+
748
+ _readParamValue(buf, off, type) {
749
+ switch (type) {
750
+ case MYSQL_TYPE_NULL: return { value: null, off };
751
+ case 0x01: { const v = buf.readInt8(off); return { value: v, off: off + 1 }; } // TINY
752
+ case 0x02: { const v = buf.readInt16LE(off); return { value: v, off: off + 2 }; } // SHORT
753
+ case 0x03: { const v = buf.readInt32LE(off); return { value: v, off: off + 4 }; } // LONG
754
+ case 0x08: { const v = buf.readBigInt64LE(off); return { value: Number(v), off: off + 8 }; } // LONGLONG
755
+ case 0x04: { const v = buf.readFloatLE(off); return { value: v, off: off + 4 }; } // FLOAT
756
+ case MYSQL_TYPE_DOUBLE: { const v = buf.readDoubleLE(off); return { value: v, off: off + 8 }; }
757
+ case 0x0a: case 0x07: case 0x0b: case MYSQL_TYPE_DATETIME: { // DATE/TIMESTAMP/DATETIME/TIME
758
+ const len = buf[off];
759
+ off += 1;
760
+ if (len === 0) return { value: null, off };
761
+ let value;
762
+ if (type === 0x0b) { // TIME
763
+ let sign = 1;
764
+ let p = off;
765
+ if (buf[p] !== 0) sign = -1;
766
+ p += 1;
767
+ const days = buf.readUInt32LE(p); p += 4;
768
+ const hour = buf[p++];
769
+ const min = buf[p++];
770
+ const sec = buf[p++];
771
+ value = sign * (days * 24 + hour) + ':' + String(min).padStart(2, '0') + ':' + String(sec).padStart(2, '0');
772
+ } else {
773
+ let p = off;
774
+ const year = buf.readUInt16LE(p); p += 2;
775
+ const month = buf[p++];
776
+ const day = buf[p++];
777
+ let hour = 0, minute = 0, second = 0;
778
+ if (len >= 7) { hour = buf[p++]; minute = buf[p++]; second = buf[p++]; }
779
+ value = String(year).padStart(4, '0') + '-' + String(month).padStart(2, '0') + '-' + String(day).padStart(2, '0');
780
+ if (len >= 7) value += ' ' + String(hour).padStart(2, '0') + ':' + String(minute).padStart(2, '0') + ':' + String(second).padStart(2, '0');
781
+ }
782
+ off += len;
783
+ return { value, off };
784
+ }
785
+ case 0xfc: case 0xfd: case 0xfe: case 0xf9: case 0xf6: { // BLOB / VAR_STRING / STRING / ...
786
+ const { value, off: noff } = readLenenc(buf, off);
787
+ return { value, off: noff };
788
+ }
789
+ case 0xf0: { // NULL
790
+ return { value: null, off };
791
+ }
792
+ default: {
793
+ const { value, off: noff } = readLenenc(buf, off);
794
+ return { value, off: noff };
795
+ }
796
+ }
797
+ }
798
+
799
+ _handleQuery(sql, values) {
800
+ (async () => {
801
+ try {
802
+ const hasParams = values !== undefined && values !== null && values.length > 0;
803
+ const processedSql = hasParams ? applyParams(sql, values) : sql;
804
+ const statements = splitStatements(processedSql);
805
+ if (statements.length > 1 && !this.multiStatements) {
806
+ throw new Error(`too many statements (${statements.length} > 1)`);
807
+ }
808
+ for (const stmtSql of statements) {
809
+ const stmt = parseSQL(stmtSql);
810
+ switch (stmt.type) {
811
+ case 'use': {
812
+ if (!this.server._canAccessDb(this.user, stmt.database)) {
813
+ this._send(errPacket(1044, `Access denied for user '${this.user}' to database '${stmt.database}'`));
814
+ continue;
815
+ }
816
+ try {
817
+ await this.server._getDatabase(stmt.database);
818
+ this.currentDb = stmt.database;
819
+ this._send(okPacket());
820
+ } catch (e) {
821
+ this._send(errPacket(1049, `Unknown database '${stmt.database}'`));
822
+ }
823
+ continue;
824
+ }
825
+ case 'showDatabases': {
826
+ const dbs = await this.server.listDatabases();
827
+ const allowed = this.server._userDbList(this.user);
828
+ const names = allowed === null ? dbs : dbs.filter(n => allowed.includes(n));
829
+ const { packets, sequence } = resultSetPacket({
830
+ type: 'showDatabases', columns: ['Database'], rows: names.map(n => [n]),
831
+ }, null, this.sequence);
832
+ this.sequence = sequence;
833
+ this.socket.write(Buffer.concat(packets));
834
+ continue;
835
+ }
836
+ case 'createDatabase': {
837
+ if (!this.server._canAccessDb(this.user, stmt.database)) {
838
+ this._send(errPacket(1044, `Access denied for user '${this.user}' to database '${stmt.database}'`));
839
+ continue;
840
+ }
841
+ await this.server.createDatabase(stmt.database, { ifNotExists: stmt.ifNotExists });
842
+ this._send(okPacket());
843
+ continue;
844
+ }
845
+ case 'dropDatabase': {
846
+ if (!this.server._canAccessDb(this.user, stmt.database)) {
847
+ this._send(errPacket(1044, `Access denied for user '${this.user}' to database '${stmt.database}'`));
848
+ continue;
849
+ }
850
+ await this.server.dropDatabase(stmt.database, { ifExists: stmt.ifExists });
851
+ this._send(okPacket());
852
+ continue;
853
+ }
854
+ default:
855
+ break;
856
+ }
857
+ const routed = await this.server._route(stmt, stmtSql, this.currentDb, this.user);
858
+ const engine = routed.engine;
859
+ const sqlToRun = routed.sql;
860
+ const r = await executeSQL(engine, sqlToRun, {
861
+ allowComments: this.server.allowComments,
862
+ safety: this.server.safety,
863
+ maxStatements: 1,
864
+ session: this.session,
865
+ });
866
+ if (r.type === 'select' || r.type === 'showTables' || r.type === 'showDatabases' || r.type === 'describe'
867
+ || r.type === 'showColumns' || r.type === 'showIndex' || r.type === 'showCreateTable'
868
+ || r.type === 'showVariables' || r.type === 'showStatus' || r.type === 'showGrants' || r.type === 'showWarnings') {
869
+ this.session.foundRows = (r.raw && r.raw.length !== undefined) ? r.raw.length : (r.rows ? r.rows.length : 0);
870
+ let schema = null;
871
+ if (r.table) {
872
+ schema = engine.getTableSchema
873
+ ? await engine.getTableSchema(r.table)
874
+ : (engine._schemas ? engine._schemas[r.table] : null);
875
+ }
876
+ const { packets, sequence } = hasParams
877
+ ? binaryResultSetPacket(r, schema, this.sequence)
878
+ : resultSetPacket(r, schema, this.sequence);
879
+ this.sequence = sequence;
880
+ this.socket.write(Buffer.concat(packets));
881
+ } else {
882
+ if (r.type === 'insert') {
883
+ this.session.lastInsertId = r.insertId;
884
+ this.session.rowCount = r.affectedRows;
885
+ } else if (r.type === 'update' || r.type === 'delete' || r.type === 'truncate') {
886
+ this.session.rowCount = r.affectedRows;
887
+ }
888
+ this._send(okPacket(r.affectedRows || 0, r.insertId || 0));
889
+ }
890
+ }
891
+ } catch (e) {
892
+ this._send(errPacket(toMysqlErrno(e), e.message));
893
+ }
894
+ })();
895
+ }
896
+ }
897
+
898
+ class MysqlServer {
899
+ constructor(options = {}) {
900
+ this.options = options;
901
+ this.port = options.port ?? 3306;
902
+ this.host = options.host || '127.0.0.1';
903
+ this.user = options.user || null;
904
+ this.password = options.password || null;
905
+ this.auth = options.auth || null;
906
+ this.safety = options.safety !== false;
907
+ this.allowComments = options.allowComments === true;
908
+ this.maxPacketSize = options.maxPacketSize || 1024 * 1024;
909
+ this.handshakeTimeout = options.handshakeTimeout != null ? options.handshakeTimeout : 10000;
910
+ this.idleTimeout = options.idleTimeout != null ? options.idleTimeout : 300000;
911
+ this.maxAuthFails = options.maxAuthFails || 3;
912
+ this.maxConnections = options.maxConnections || 128;
913
+ this._engine = null;
914
+ this._ownEngine = false;
915
+ this._databases = new Map();
916
+ this._dbDir = options.dataDir && options.dataDir !== ':memory:' ? path.resolve(options.dataDir) : null;
917
+ this._defaultDbName = options.defaultDatabase || 'default';
918
+ this._connectionCounter = 0;
919
+ this._sockets = new Set();
920
+ this._securityHandler = typeof options.onSecurityEvent === 'function' ? options.onSecurityEvent : null;
921
+ }
922
+
923
+ _onSecurityEvent(event) {
924
+ if (this._securityHandler) {
925
+ try { this._securityHandler(event); } catch (e) {}
926
+ }
927
+ }
928
+
929
+ async _getEngine(dbName) {
930
+ if (dbName) return this._getDatabase(dbName);
931
+ if (this._dbDir) {
932
+ return this._getDatabase(this._defaultDbName, { autoCreate: true });
933
+ }
934
+ if (this._engine) return this._engine;
935
+ if (this.options.engine || this.options.database) {
936
+ this._engine = this.options.engine || this.options.database;
937
+ if (typeof this._engine.start === 'function') await this._engine.start();
938
+ } else {
939
+ this._engine = new Database(this.options.filename || ':memory:');
940
+ if (typeof this._engine.start === 'function') await this._engine.start();
941
+ this._ownEngine = true;
942
+ }
943
+ return this._engine;
944
+ }
945
+
946
+ _dbExists(name) {
947
+ if (this._databases.has(name)) return true;
948
+ const dir = this._dbPath(name);
949
+ return !!(dir && fs.existsSync(dir));
950
+ }
951
+
952
+ // 语句路由:解析库前缀(db.table)与 SHOW TABLES FROM db,
953
+ // 决定执行引擎,并把 db.table 改写为 table 后交给对应库引擎执行。
954
+ async _route(stmt, sql, currentDb, user) {
955
+ if (stmt.type === 'showTables' && stmt.database) {
956
+ if (user && this.auth && !this._canAccessDb(user, stmt.database)) {
957
+ const err = new Error(`Access denied for user '${user}' to database '${stmt.database}'`);
958
+ err.code = 1044;
959
+ throw err;
960
+ }
961
+ return { engine: await this._getDatabase(stmt.database), sql };
962
+ }
963
+ const tables = [];
964
+ const collect = (s) => {
965
+ if (!s) return;
966
+ if (s.type === 'createTable') tables.push(s.name);
967
+ else if (s.type === 'dropTable') tables.push(s.table);
968
+ else if (s.type === 'insert') tables.push(s.name);
969
+ else if (s.type === 'update') tables.push(s.table);
970
+ else if (s.type === 'delete') tables.push(s.table);
971
+ else if (s.type === 'describe') tables.push(s.table);
972
+ else if (s.type === 'select' && s.from) {
973
+ for (const t of s.from.tables) tables.push(t.table);
974
+ for (const j of s.from.joins) tables.push(j.item.table);
975
+ }
976
+ };
977
+ collect(stmt);
978
+ const isInfoSchema = tables.some(t => t && String(t).toLowerCase().startsWith('information_schema.'));
979
+ if (isInfoSchema) {
980
+ return { engine: await this._getEngine(currentDb), sql };
981
+ }
982
+ let db = null;
983
+ for (const t of tables) {
984
+ if (t && t.indexOf('.') !== -1) {
985
+ const d = t.split('.')[0];
986
+ if (db && d !== db) {
987
+ const err = new Error(`Cross-database references are not supported in one statement`);
988
+ err.code = 1105;
989
+ throw err;
990
+ }
991
+ db = d;
992
+ }
993
+ }
994
+ if (db) {
995
+ if (user && this.auth && !this._canAccessDb(user, db)) {
996
+ const err = new Error(`Access denied for user '${user}' to database '${db}'`);
997
+ err.code = 1044;
998
+ throw err;
999
+ }
1000
+ if (!this._dbExists(db)) await this.createDatabase(db);
1001
+ const engine = await this._getDatabase(db);
1002
+ let s = sql;
1003
+ for (const t of tables) {
1004
+ if (t && t.indexOf('.') !== -1) {
1005
+ s = s.replace(new RegExp('\\b' + t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\b', 'g'), t.split('.')[1]);
1006
+ }
1007
+ }
1008
+ return { engine, sql: s };
1009
+ }
1010
+ if (this._dbDir && !currentDb && this.auth && !this._canAccessDb(user, this._defaultDbName)) {
1011
+ const err = new Error('No database selected');
1012
+ err.code = 1046;
1013
+ throw err;
1014
+ }
1015
+ return { engine: await this._getEngine(currentDb), sql };
1016
+ }
1017
+
1018
+ _safeDbName(name) {
1019
+ if (typeof name !== 'string' || name.length === 0 || name.length > 64) return false;
1020
+ if (!/^[a-zA-Z0-9_$.\-]+$/.test(name)) return false;
1021
+ if (name === '.' || name === '..' || name.includes('..')) return false;
1022
+ return true;
1023
+ }
1024
+
1025
+ _dbPath(name) {
1026
+ if (!this._dbDir) return null;
1027
+ if (!this._safeDbName(name)) return null;
1028
+ return path.join(this._dbDir, name);
1029
+ }
1030
+
1031
+ async _getDatabase(name, opts = {}) {
1032
+ if (!this._safeDbName(name)) {
1033
+ const err = new Error(`Unknown database '${name}'`);
1034
+ err.code = 1049;
1035
+ throw err;
1036
+ }
1037
+ if (this._databases.has(name)) return this._databases.get(name);
1038
+ const dir = this._dbPath(name);
1039
+ if (dir && !fs.existsSync(dir)) {
1040
+ if (opts.autoCreate) {
1041
+ fs.mkdirSync(dir, { recursive: true });
1042
+ } else {
1043
+ const err = new Error(`Unknown database '${name}'`);
1044
+ err.code = 1049;
1045
+ throw err;
1046
+ }
1047
+ }
1048
+ const engine = dir ? new Database(dir, { mode: 'hybrid' }) : new Database(':memory:');
1049
+ if (typeof engine.start === 'function') await engine.start();
1050
+ this._databases.set(name, engine);
1051
+ return engine;
1052
+ }
1053
+
1054
+ async listDatabases() {
1055
+ if (!this._dbDir) return ['jsql'];
1056
+ let names = [];
1057
+ if (fs.existsSync(this._dbDir)) {
1058
+ names = fs.readdirSync(this._dbDir).filter(n => {
1059
+ const p = path.join(this._dbDir, n);
1060
+ return fs.statSync(p).isDirectory() && this._safeDbName(n);
1061
+ });
1062
+ }
1063
+ for (const n of this._databases.keys()) {
1064
+ if (!names.includes(n)) names.push(n);
1065
+ }
1066
+ return names.sort();
1067
+ }
1068
+
1069
+ async createDatabase(name, opts = {}) {
1070
+ if (!this._safeDbName(name)) {
1071
+ const err = new Error(`Invalid database name '${name}'`);
1072
+ err.code = 1105;
1073
+ throw err;
1074
+ }
1075
+ if (this._databases.has(name)) {
1076
+ if (opts.ifNotExists) return this._databases.get(name);
1077
+ const err = new Error(`Can't create database '${name}'; database exists`);
1078
+ err.code = 1007;
1079
+ throw err;
1080
+ }
1081
+ const dir = this._dbPath(name);
1082
+ if (dir && fs.existsSync(dir)) {
1083
+ if (opts.ifNotExists) return this._getDatabase(name);
1084
+ const err = new Error(`Can't create database '${name}'; database exists`);
1085
+ err.code = 1007;
1086
+ throw err;
1087
+ }
1088
+ if (dir) fs.mkdirSync(dir, { recursive: true });
1089
+ const engine = dir ? new Database(dir, { mode: 'hybrid' }) : new Database(':memory:');
1090
+ if (typeof engine.start === 'function') await engine.start();
1091
+ this._databases.set(name, engine);
1092
+ return engine;
1093
+ }
1094
+
1095
+ async dropDatabase(name, opts = {}) {
1096
+ if (!this._safeDbName(name)) {
1097
+ const err = new Error(`Unknown database '${name}'`);
1098
+ err.code = 1049;
1099
+ throw err;
1100
+ }
1101
+ const engine = this._databases.get(name);
1102
+ if (!engine) {
1103
+ if (opts.ifExists) return;
1104
+ const err = new Error(`Can't drop database '${name}'; database doesn't exist`);
1105
+ err.code = 1008;
1106
+ throw err;
1107
+ }
1108
+ if (typeof engine.stop === 'function') {
1109
+ try { await engine.stop(); } catch (e) {}
1110
+ }
1111
+ this._databases.delete(name);
1112
+ const dir = this._dbPath(name);
1113
+ if (dir && fs.existsSync(dir)) {
1114
+ fs.rmSync(dir, { recursive: true, force: true });
1115
+ }
1116
+ }
1117
+
1118
+ _userDbList(user) {
1119
+ if (!this.auth) return null;
1120
+ const entry = this.auth[user];
1121
+ if (entry && typeof entry === 'object') {
1122
+ if (Array.isArray(entry.databases)) return entry.databases;
1123
+ return null;
1124
+ }
1125
+ return [];
1126
+ }
1127
+
1128
+ _canAccessDb(user, dbName) {
1129
+ const list = this._userDbList(user);
1130
+ if (list === null) return true;
1131
+ return list.includes(dbName);
1132
+ }
1133
+
1134
+ async _switchDb(conn, dbName) {
1135
+ if (!this._canAccessDb(conn.user, dbName)) {
1136
+ const err = new Error(`Access denied for user '${conn.user}' to database '${dbName}'`);
1137
+ err.code = 1044;
1138
+ throw err;
1139
+ }
1140
+ await this._getDatabase(dbName);
1141
+ conn.currentDb = dbName;
1142
+ }
1143
+
1144
+ _checkAuth(user, authResponse, seed) {
1145
+ if (this.auth) {
1146
+ if (!Object.prototype.hasOwnProperty.call(this.auth, user)) return false;
1147
+ const entry = this.auth[user];
1148
+ const pwd = entry && typeof entry === 'object' ? entry.password : entry;
1149
+ if (!pwd) return authResponse.length === 0;
1150
+ const pwdHash1 = crypto.createHash('sha1').update(pwd).digest();
1151
+ const pwdHash2 = crypto.createHash('sha1').update(pwdHash1).digest();
1152
+ const seedHash = crypto.createHash('sha1').update(Buffer.concat([seed, pwdHash2])).digest();
1153
+ const expected = Buffer.alloc(20);
1154
+ for (let i = 0; i < 20; i++) expected[i] = pwdHash1[i] ^ seedHash[i];
1155
+ return authResponse.length === 20 && crypto.timingSafeEqual(expected, authResponse);
1156
+ }
1157
+ if (this.user === null) {
1158
+ // 未配置认证:默认拒绝所有登录(防配置遗漏导致认证旁路)。
1159
+ // 仅当显式设置 noAuth: true 时允许无认证连接(仅限本地开发)。
1160
+ return this.options.noAuth === true;
1161
+ }
1162
+ if (user !== this.user) return false;
1163
+ if (!this.password) return authResponse.length === 0;
1164
+ const pwdHash1 = crypto.createHash('sha1').update(this.password).digest();
1165
+ const pwdHash2 = crypto.createHash('sha1').update(pwdHash1).digest();
1166
+ const seedHash = crypto.createHash('sha1').update(Buffer.concat([seed, pwdHash2])).digest();
1167
+ const expected = Buffer.alloc(20);
1168
+ for (let i = 0; i < 20; i++) expected[i] = pwdHash1[i] ^ seedHash[i];
1169
+ return authResponse.length === 20 && crypto.timingSafeEqual(expected, authResponse);
1170
+ }
1171
+
1172
+ listen(cb) {
1173
+ const ready = this._dbDir ? Promise.resolve() : this._getEngine();
1174
+ ready.then(() => {
1175
+ this._server = net.createServer(socket => {
1176
+ if (this._sockets.size >= this.maxConnections) {
1177
+ this._onSecurityEvent({ type: 'max-connections', remote: socket.remoteAddress });
1178
+ socket.destroy();
1179
+ return;
1180
+ }
1181
+ this._sockets.add(socket);
1182
+ socket.on('close', () => this._sockets.delete(socket));
1183
+ new MysqlConnection(socket, this);
1184
+ });
1185
+ this._server.once('error', err => {
1186
+ if (err && err.code === 'EADDRINUSE') {
1187
+ const friendly = new Error(
1188
+ `MysqlServer 端口 ${this.port} 已被占用(EADDRINUSE)。` +
1189
+ `请换一个空闲端口,或传 port: 0 让系统分配临时端口。`
1190
+ );
1191
+ friendly.code = 'EADDRINUSE';
1192
+ friendly.cause = err;
1193
+ if (cb) cb(friendly);
1194
+ else throw friendly;
1195
+ } else if (cb) cb(err);
1196
+ });
1197
+ this._server.listen(this.port, this.host, cb || (() => {}));
1198
+ }).catch(err => {
1199
+ if (cb) cb(err);
1200
+ else throw err;
1201
+ });
1202
+ return this;
1203
+ }
1204
+
1205
+ get address() {
1206
+ return this._server ? this._server.address() : null;
1207
+ }
1208
+
1209
+ close(cb) {
1210
+ const done = () => {
1211
+ for (const s of this._sockets) s.destroy();
1212
+ const stops = [];
1213
+ if (this._ownEngine && this._engine && typeof this._engine.stop === 'function') {
1214
+ stops.push(this._engine.stop());
1215
+ }
1216
+ for (const engine of this._databases.values()) {
1217
+ if (engine && typeof engine.stop === 'function') stops.push(engine.stop());
1218
+ }
1219
+ this._databases.clear();
1220
+ if (stops.length > 0) {
1221
+ Promise.allSettled(stops).then(() => cb && cb());
1222
+ } else if (cb) cb();
1223
+ };
1224
+ if (this._server) {
1225
+ this._server.close(() => done());
1226
+ } else {
1227
+ done();
1228
+ }
1229
+ return this;
1230
+ }
1231
+ }
1232
+
1233
+ function createMysqlServer(options) {
1234
+ return new MysqlServer(options || {});
1235
+ }
1236
+
1237
+ module.exports = { createMysqlServer, MysqlServer, MysqlConnection };