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.
- package/LICENSE +202 -202
- package/README.md +8097 -7971
- package/bin/jsql +320 -320
- package/bin/jsql-server +90 -90
- package/bin/jsql-server-control.js +177 -177
- package/index.d.ts +385 -357
- package/index.js +124 -118
- package/lib/ast.js +214 -0
- package/lib/btree.js +413 -413
- package/lib/cache.js +328 -328
- package/lib/client.js +125 -125
- package/lib/database.js +1744 -1744
- package/lib/date-types.js +182 -182
- package/lib/errors.js +89 -89
- package/lib/jsql_format.js +497 -497
- package/lib/migrate.js +254 -254
- package/lib/mod.js +292 -292
- package/lib/mongo_server.js +607 -607
- package/lib/multiserver.js +140 -140
- package/lib/mysql_compat.js +413 -413
- package/lib/mysql_server.js +1237 -1237
- package/lib/native_client.js +732 -732
- package/lib/nedb_compat.js +506 -506
- package/lib/pg_server.js +864 -864
- package/lib/plugin.js +34 -34
- package/lib/query.js +815 -815
- package/lib/redis_server.js +571 -571
- package/lib/sql.js +3460 -2925
- package/lib/sqlite_compat.js +323 -323
- package/lib/sqlite_worker.js +264 -264
- package/lib/table.js +1304 -1304
- package/lib/tui.js +502 -502
- package/lib/wasm.js +1 -1
- package/lib/wasm_client.js +718 -718
- package/lib/web_ui.js +257 -257
- package/native/jsql-neo-native.node +0 -0
- package/nativesrc/jsql-neo-core/Cargo.lock +455 -455
- package/nativesrc/jsql-neo-core/Cargo.toml +24 -24
- package/nativesrc/jsql-neo-core/src/engine/hybrid.rs +455 -455
- package/nativesrc/jsql-neo-core/src/engine/memory.rs +168 -168
- package/nativesrc/jsql-neo-core/src/engine/mod.rs +62 -62
- package/nativesrc/jsql-neo-core/src/engine/table.rs +700 -700
- package/nativesrc/jsql-neo-core/src/lib.rs +2 -2
- package/nativesrc/jsql-neo-core/src/storage/mod.rs +2 -2
- package/nativesrc/jsql-neo-core/src/storage/persistent.rs +1 -1
- package/nativesrc/jsql-neo-core/src/storage/wal.rs +85 -85
- package/nativesrc/jsql-neo-core/src/types.rs +93 -93
- package/nativesrc/jsql-neo-native/Cargo.lock +606 -606
- package/nativesrc/jsql-neo-native/Cargo.toml +16 -16
- package/nativesrc/jsql-neo-native/build.rs +3 -3
- package/nativesrc/jsql-neo-native/package.json +7 -7
- package/nativesrc/jsql-neo-native/src/lib.rs +269 -269
- package/nativesrc/jsql-neo-wasm/Cargo.lock +465 -465
- package/nativesrc/jsql-neo-wasm/Cargo.toml +15 -15
- package/nativesrc/jsql-neo-wasm/src/lib.rs +447 -447
- package/package.json +87 -86
- package/postinstall.js +158 -158
- package/test/btree.test.js +128 -128
- package/test/coverage.js +187 -187
- package/test/join.test.js +110 -110
- package/test/native.test.js +158 -158
- package/test/regress-5.1.0.js +271 -271
- package/test/smoke.js +58 -58
- package/test/sql-parser.test.js +273 -0
- package/test/wasm.test.js +110 -110
- package/wasm/browser.d.ts +89 -89
- package/wasm/browser.mjs +569 -569
- package/wasm/browser_bg.mjs +597 -597
- package/wasm/jsql_neo_wasm.d.ts +38 -38
- package/wasm/jsql_neo_wasm.js +520 -520
- package/wasm/jsql_neo_wasm_bg.wasm.d.ts +26 -26
- package/wasm/package.json +11 -11
package/lib/mysql_compat.js
CHANGED
|
@@ -1,413 +1,413 @@
|
|
|
1
|
-
// © Vexify 2026 All Rights Reserved.
|
|
2
|
-
/**
|
|
3
|
-
* MySQL API 兼容层 — createConnection / createPool / query
|
|
4
|
-
* 底层用 JSQL SQL 执行器,接口形状对齐 mysql / mysql2 包。
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const { executeSQL, applyParams, escapeValue, escapeId } = require('./sql');
|
|
8
|
-
const Database = require('./database');
|
|
9
|
-
const EventEmitter = require('events');
|
|
10
|
-
|
|
11
|
-
function isQueryResult(r) {
|
|
12
|
-
return !!r && typeof r === 'object' && Array.isArray(r.rows) && Array.isArray(r.columns);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function toResultPacket(r, table, rowsAsArray) {
|
|
16
|
-
if (isQueryResult(r)) {
|
|
17
|
-
const columns = r.columns || [];
|
|
18
|
-
const rawRows = r.rows || [];
|
|
19
|
-
const fields = columns.map(name => ({
|
|
20
|
-
name,
|
|
21
|
-
table: table || '',
|
|
22
|
-
type: 253,
|
|
23
|
-
length: 1024,
|
|
24
|
-
flags: 0,
|
|
25
|
-
charsetNr: 45,
|
|
26
|
-
}));
|
|
27
|
-
const rows = rowsAsArray
|
|
28
|
-
? rawRows
|
|
29
|
-
: rawRows.map(vals => {
|
|
30
|
-
const obj = {};
|
|
31
|
-
columns.forEach((c, j) => { obj[c] = vals[j]; });
|
|
32
|
-
return obj;
|
|
33
|
-
});
|
|
34
|
-
return { rows, fields };
|
|
35
|
-
}
|
|
36
|
-
return {
|
|
37
|
-
fieldCount: 0,
|
|
38
|
-
affectedRows: r && r.affectedRows !== undefined ? r.affectedRows : 0,
|
|
39
|
-
insertId: r && r.insertId !== undefined ? r.insertId : 0,
|
|
40
|
-
serverStatus: 2,
|
|
41
|
-
warningCount: 0,
|
|
42
|
-
message: r ? r.type + (r.table ? ' ' + r.table : '') : '',
|
|
43
|
-
protocol41: true,
|
|
44
|
-
changedRows: r && r.affectedRows !== undefined ? r.affectedRows : 0,
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
class Connection extends EventEmitter {
|
|
49
|
-
constructor(options = {}) {
|
|
50
|
-
super();
|
|
51
|
-
this.config = {
|
|
52
|
-
host: options.host || 'localhost',
|
|
53
|
-
port: options.port || 3306,
|
|
54
|
-
user: options.user || 'root',
|
|
55
|
-
password: options.password || '',
|
|
56
|
-
database: options.database || null,
|
|
57
|
-
multipleStatements: options.multipleStatements === true,
|
|
58
|
-
};
|
|
59
|
-
this.safety = options.safety !== false;
|
|
60
|
-
this.allowComments = options.allowComments === true;
|
|
61
|
-
this.database = options.database && typeof options.database === 'object' && !options.filename && typeof options.database.start === 'function'
|
|
62
|
-
? options.database
|
|
63
|
-
: (options.engine || null);
|
|
64
|
-
this.filename = options.filename || null;
|
|
65
|
-
this.engine = null;
|
|
66
|
-
this._ownEngine = false;
|
|
67
|
-
this.state = 'disconnected';
|
|
68
|
-
this.stream = {
|
|
69
|
-
destroyed: false,
|
|
70
|
-
writable: true,
|
|
71
|
-
destroy: () => { this.stream.destroyed = true; },
|
|
72
|
-
};
|
|
73
|
-
if (options.host !== undefined || options.database || options.engine || options.filename) {
|
|
74
|
-
queueMicrotask(() => {
|
|
75
|
-
this._getEngine().then(() => this.emit('connect'), err => {
|
|
76
|
-
if (this.listenerCount('error') > 0) this.emit('error', err);
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async _getEngine() {
|
|
83
|
-
if (this.engine) return this.engine;
|
|
84
|
-
if (this.database && typeof this.database.start === 'function') {
|
|
85
|
-
this.engine = this.database;
|
|
86
|
-
await this.engine.start();
|
|
87
|
-
} else if (this._pool) {
|
|
88
|
-
this.engine = await this._pool._sharedEngineFor(this.config.database || 'default');
|
|
89
|
-
} else if (this.filename) {
|
|
90
|
-
this.engine = new Database(this.filename);
|
|
91
|
-
await this.engine.start();
|
|
92
|
-
this._ownEngine = true;
|
|
93
|
-
} else {
|
|
94
|
-
this.engine = new Database(':memory:');
|
|
95
|
-
await this.engine.start();
|
|
96
|
-
this._ownEngine = true;
|
|
97
|
-
}
|
|
98
|
-
this.state = 'connected';
|
|
99
|
-
return this.engine;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
connect(cb) {
|
|
103
|
-
const p = this._getEngine().then(c => {
|
|
104
|
-
this.emit('connect');
|
|
105
|
-
return c;
|
|
106
|
-
});
|
|
107
|
-
if (cb) p.then(c => cb(null, c), err => cb(err));
|
|
108
|
-
return p;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
query(...args) {
|
|
112
|
-
let sql, values, cb, rowsAsArray;
|
|
113
|
-
if (typeof args[0] === 'object' && args[0] !== null && typeof args[0].sql === 'string') {
|
|
114
|
-
sql = args[0].sql;
|
|
115
|
-
rowsAsArray = args[0].rowsAsArray === true;
|
|
116
|
-
values = args[0].values !== undefined ? args[0].values : (Array.isArray(args[1]) ? args[1] : null);
|
|
117
|
-
cb = typeof args[1] === 'function' ? args[1] : (typeof args[2] === 'function' ? args[2] : null);
|
|
118
|
-
} else {
|
|
119
|
-
sql = args[0];
|
|
120
|
-
values = Array.isArray(args[1]) ? args[1] : null;
|
|
121
|
-
cb = typeof args[1] === 'function' ? args[1] : (typeof args[2] === 'function' ? args[2] : null);
|
|
122
|
-
}
|
|
123
|
-
const p = (async () => {
|
|
124
|
-
const engine = await this._getEngine();
|
|
125
|
-
const finalSql = applyParams(sql, values);
|
|
126
|
-
const r = await executeSQL(engine, finalSql, {
|
|
127
|
-
safety: this.safety,
|
|
128
|
-
allowComments: this.allowComments,
|
|
129
|
-
maxStatements: this.config.multipleStatements ? null : 1,
|
|
130
|
-
});
|
|
131
|
-
const results = Array.isArray(r) ? r : [r];
|
|
132
|
-
const last = results[results.length - 1];
|
|
133
|
-
const packet = toResultPacket(last, last ? last.table : null, rowsAsArray);
|
|
134
|
-
if (isQueryResult(last)) {
|
|
135
|
-
const arr = [packet.rows, packet.fields];
|
|
136
|
-
arr.rows = packet.rows;
|
|
137
|
-
arr.fields = packet.fields;
|
|
138
|
-
return arr;
|
|
139
|
-
}
|
|
140
|
-
const arr = [packet];
|
|
141
|
-
arr.rows = null;
|
|
142
|
-
arr.fields = null;
|
|
143
|
-
return arr;
|
|
144
|
-
})();
|
|
145
|
-
const q = new EventEmitter();
|
|
146
|
-
q.setMaxListeners = EventEmitter.prototype.setMaxListeners;
|
|
147
|
-
p.then(res => {
|
|
148
|
-
q.emit('result', res.rows || res[0]);
|
|
149
|
-
q.emit('fields', res.fields || []);
|
|
150
|
-
}, err => {
|
|
151
|
-
if (q.listenerCount('error') > 0) q.emit('error', err);
|
|
152
|
-
});
|
|
153
|
-
if (cb) {
|
|
154
|
-
p.then(res => {
|
|
155
|
-
if (res && res.rows && res.fields) cb(null, res.rows, res.fields);
|
|
156
|
-
else if (res && res[0] && res[0].insertId !== undefined) cb(null, res[0]);
|
|
157
|
-
else cb(null, res[0]);
|
|
158
|
-
}, err => cb(err));
|
|
159
|
-
return q;
|
|
160
|
-
}
|
|
161
|
-
p.setMaxListeners = q.setMaxListeners;
|
|
162
|
-
return p;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
execute(sql, values, cb) {
|
|
166
|
-
if (typeof values === 'function') {
|
|
167
|
-
cb = values;
|
|
168
|
-
values = undefined;
|
|
169
|
-
}
|
|
170
|
-
return this.query(sql, values, cb);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
beginTransaction(cb) {
|
|
174
|
-
const p = this.query('BEGIN');
|
|
175
|
-
if (cb) p.then(() => cb(null), err => cb(err));
|
|
176
|
-
return p;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
commit(cb) {
|
|
180
|
-
const p = this.query('COMMIT');
|
|
181
|
-
if (cb) p.then(() => cb(null), err => cb(err));
|
|
182
|
-
return p;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
rollback(cb) {
|
|
186
|
-
const p = this.query('ROLLBACK');
|
|
187
|
-
if (cb) p.then(() => cb(null), err => cb(err));
|
|
188
|
-
return p;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
ping(cb) {
|
|
192
|
-
const p = this._getEngine().then(() => true);
|
|
193
|
-
if (cb) p.then(ok => cb(null, ok), err => cb(err));
|
|
194
|
-
return p;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
release() {
|
|
198
|
-
this.state = 'released';
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
destroy() {
|
|
202
|
-
this.state = 'destroyed';
|
|
203
|
-
this.stream.destroy();
|
|
204
|
-
this.emit('close');
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
end(cb) {
|
|
208
|
-
const p = (async () => {
|
|
209
|
-
if (this._ownEngine && this.engine && typeof this.engine.stop === 'function') {
|
|
210
|
-
await this.engine.stop();
|
|
211
|
-
}
|
|
212
|
-
this.state = 'closed';
|
|
213
|
-
this.stream.destroy();
|
|
214
|
-
this.emit('close');
|
|
215
|
-
return undefined;
|
|
216
|
-
})();
|
|
217
|
-
if (cb) p.then(() => cb(null), err => cb(err));
|
|
218
|
-
return p;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
promise() {
|
|
222
|
-
return this;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
class Pool {
|
|
227
|
-
constructor(options = {}) {
|
|
228
|
-
this.config = options;
|
|
229
|
-
this.connectionLimit = options.connectionLimit || 10;
|
|
230
|
-
this.queueLimit = options.queueLimit || 0;
|
|
231
|
-
this.idleTimeout = options.idleTimeout != null ? options.idleTimeout : 300000;
|
|
232
|
-
this._connections = [];
|
|
233
|
-
this._waiters = [];
|
|
234
|
-
this._closed = false;
|
|
235
|
-
this._sharedEngines = new Map();
|
|
236
|
-
this._reaper = setInterval(() => this._reapIdle(), Math.max(1000, Math.floor(this.idleTimeout / 10) || 10000));
|
|
237
|
-
this._reaper.unref();
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
async _sharedEngineFor(database) {
|
|
241
|
-
const key = database || 'default';
|
|
242
|
-
let engine = this._sharedEngines.get(key);
|
|
243
|
-
if (!engine) {
|
|
244
|
-
const filename = (this.config && this.config.filename) || null;
|
|
245
|
-
engine = filename ? new Database(filename) : new Database(':memory:');
|
|
246
|
-
if (typeof engine.start === 'function') await engine.start();
|
|
247
|
-
this._sharedEngines.set(key, engine);
|
|
248
|
-
}
|
|
249
|
-
return engine;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
_createConnection() {
|
|
253
|
-
const conn = new Connection(this.config);
|
|
254
|
-
conn._pool = this;
|
|
255
|
-
const origDestroy = conn.destroy.bind(conn);
|
|
256
|
-
conn.destroy = () => {
|
|
257
|
-
this._remove(conn);
|
|
258
|
-
origDestroy();
|
|
259
|
-
};
|
|
260
|
-
return conn;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
_remove(conn) {
|
|
264
|
-
const idx = this._connections.indexOf(conn);
|
|
265
|
-
if (idx !== -1) this._connections.splice(idx, 1);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
_reapIdle() {
|
|
269
|
-
if (this.idleTimeout <= 0) return;
|
|
270
|
-
const now = Date.now();
|
|
271
|
-
for (const conn of this._connections.slice()) {
|
|
272
|
-
if (conn.state === 'released' && !conn._inUse && now - (conn._releasedAt || 0) > this.idleTimeout) {
|
|
273
|
-
this._remove(conn);
|
|
274
|
-
if (conn._ownEngine && conn.engine && typeof conn.engine.stop === 'function') {
|
|
275
|
-
conn.engine.stop().catch(() => {});
|
|
276
|
-
}
|
|
277
|
-
conn.state = 'destroyed';
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
_acquire() {
|
|
283
|
-
if (this._closed) return Promise.reject(new Error('Pool is closed'));
|
|
284
|
-
const free = this._connections.find(c => c.state === 'released' && !c._inUse);
|
|
285
|
-
if (free) {
|
|
286
|
-
free._inUse = true;
|
|
287
|
-
free.state = 'acquired';
|
|
288
|
-
return Promise.resolve(free);
|
|
289
|
-
}
|
|
290
|
-
if (this._connections.length < this.connectionLimit) {
|
|
291
|
-
return Promise.resolve().then(() => {
|
|
292
|
-
if (this._connections.length >= this.connectionLimit) {
|
|
293
|
-
return this._waitForConnection();
|
|
294
|
-
}
|
|
295
|
-
const conn = this._createConnection();
|
|
296
|
-
this._connections.push(conn);
|
|
297
|
-
conn._inUse = true;
|
|
298
|
-
conn.state = 'acquired';
|
|
299
|
-
return conn._getEngine().then(
|
|
300
|
-
() => conn,
|
|
301
|
-
err => {
|
|
302
|
-
this._remove(conn);
|
|
303
|
-
throw err;
|
|
304
|
-
}
|
|
305
|
-
);
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
return this._waitForConnection();
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
_waitForConnection() {
|
|
312
|
-
return new Promise((resolve, reject) => {
|
|
313
|
-
this._waiters.push({ resolve, reject });
|
|
314
|
-
if (this.queueLimit > 0 && this._waiters.length > this.queueLimit) {
|
|
315
|
-
const w = this._waiters.shift();
|
|
316
|
-
w.reject(new Error('Pool queue limit exceeded'));
|
|
317
|
-
}
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
_release(conn) {
|
|
322
|
-
conn._inUse = false;
|
|
323
|
-
conn.state = 'released';
|
|
324
|
-
conn._releasedAt = Date.now();
|
|
325
|
-
const w = this._waiters.shift();
|
|
326
|
-
if (w) {
|
|
327
|
-
conn._inUse = true;
|
|
328
|
-
conn.state = 'acquired';
|
|
329
|
-
w.resolve(conn);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
async _getConnection() {
|
|
334
|
-
return this._acquire();
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
async query(...args) {
|
|
338
|
-
const conn = await this._acquire();
|
|
339
|
-
try {
|
|
340
|
-
return await conn.query(...args);
|
|
341
|
-
} finally {
|
|
342
|
-
this._release(conn);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
async getConnection(cb) {
|
|
347
|
-
const p = (async () => {
|
|
348
|
-
const conn = await this._acquire();
|
|
349
|
-
let released = false;
|
|
350
|
-
const release = () => {
|
|
351
|
-
if (released) return;
|
|
352
|
-
released = true;
|
|
353
|
-
if (conn.state === 'destroyed') {
|
|
354
|
-
this._remove(conn);
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
this._release(conn);
|
|
358
|
-
};
|
|
359
|
-
return { connection: conn, release };
|
|
360
|
-
})();
|
|
361
|
-
if (cb) p.then(r => cb(null, r.connection, r.release), err => cb(err));
|
|
362
|
-
return p;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
async end(cb) {
|
|
366
|
-
const p = (async () => {
|
|
367
|
-
this._closed = true;
|
|
368
|
-
if (this._reaper) { clearInterval(this._reaper); this._reaper = null; }
|
|
369
|
-
const err = new Error('Pool is closed');
|
|
370
|
-
for (const w of this._waiters.splice(0)) w.reject(err);
|
|
371
|
-
const conns = this._connections.splice(0);
|
|
372
|
-
await Promise.allSettled(conns.map(c => c.end()));
|
|
373
|
-
const engines = Array.from(this._sharedEngines.values());
|
|
374
|
-
this._sharedEngines.clear();
|
|
375
|
-
await Promise.allSettled(engines.map(e => (typeof e.stop === 'function' ? e.stop() : null)));
|
|
376
|
-
return undefined;
|
|
377
|
-
})();
|
|
378
|
-
if (cb) p.then(() => cb(null), err => cb(err));
|
|
379
|
-
return p;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
promise() {
|
|
383
|
-
return this;
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
function createConnection(options) {
|
|
388
|
-
return new Connection(options || {});
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
function createPool(options) {
|
|
392
|
-
return new Pool(options || {});
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
class RowDataPacket {}
|
|
396
|
-
class OkPacket {}
|
|
397
|
-
class ResultSetHeader {}
|
|
398
|
-
class FieldPacket {}
|
|
399
|
-
|
|
400
|
-
module.exports = {
|
|
401
|
-
createConnection,
|
|
402
|
-
createPool,
|
|
403
|
-
createConnectionPromise: () => Promise.resolve(createConnection()),
|
|
404
|
-
Connection,
|
|
405
|
-
Pool,
|
|
406
|
-
RowDataPacket,
|
|
407
|
-
OkPacket,
|
|
408
|
-
ResultSetHeader,
|
|
409
|
-
FieldPacket,
|
|
410
|
-
escape: escapeValue,
|
|
411
|
-
escapeId,
|
|
412
|
-
format: applyParams,
|
|
413
|
-
};
|
|
1
|
+
// © Vexify 2026 All Rights Reserved.
|
|
2
|
+
/**
|
|
3
|
+
* MySQL API 兼容层 — createConnection / createPool / query
|
|
4
|
+
* 底层用 JSQL SQL 执行器,接口形状对齐 mysql / mysql2 包。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { executeSQL, applyParams, escapeValue, escapeId } = require('./sql');
|
|
8
|
+
const Database = require('./database');
|
|
9
|
+
const EventEmitter = require('events');
|
|
10
|
+
|
|
11
|
+
function isQueryResult(r) {
|
|
12
|
+
return !!r && typeof r === 'object' && Array.isArray(r.rows) && Array.isArray(r.columns);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function toResultPacket(r, table, rowsAsArray) {
|
|
16
|
+
if (isQueryResult(r)) {
|
|
17
|
+
const columns = r.columns || [];
|
|
18
|
+
const rawRows = r.rows || [];
|
|
19
|
+
const fields = columns.map(name => ({
|
|
20
|
+
name,
|
|
21
|
+
table: table || '',
|
|
22
|
+
type: 253,
|
|
23
|
+
length: 1024,
|
|
24
|
+
flags: 0,
|
|
25
|
+
charsetNr: 45,
|
|
26
|
+
}));
|
|
27
|
+
const rows = rowsAsArray
|
|
28
|
+
? rawRows
|
|
29
|
+
: rawRows.map(vals => {
|
|
30
|
+
const obj = {};
|
|
31
|
+
columns.forEach((c, j) => { obj[c] = vals[j]; });
|
|
32
|
+
return obj;
|
|
33
|
+
});
|
|
34
|
+
return { rows, fields };
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
fieldCount: 0,
|
|
38
|
+
affectedRows: r && r.affectedRows !== undefined ? r.affectedRows : 0,
|
|
39
|
+
insertId: r && r.insertId !== undefined ? r.insertId : 0,
|
|
40
|
+
serverStatus: 2,
|
|
41
|
+
warningCount: 0,
|
|
42
|
+
message: r ? r.type + (r.table ? ' ' + r.table : '') : '',
|
|
43
|
+
protocol41: true,
|
|
44
|
+
changedRows: r && r.affectedRows !== undefined ? r.affectedRows : 0,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
class Connection extends EventEmitter {
|
|
49
|
+
constructor(options = {}) {
|
|
50
|
+
super();
|
|
51
|
+
this.config = {
|
|
52
|
+
host: options.host || 'localhost',
|
|
53
|
+
port: options.port || 3306,
|
|
54
|
+
user: options.user || 'root',
|
|
55
|
+
password: options.password || '',
|
|
56
|
+
database: options.database || null,
|
|
57
|
+
multipleStatements: options.multipleStatements === true,
|
|
58
|
+
};
|
|
59
|
+
this.safety = options.safety !== false;
|
|
60
|
+
this.allowComments = options.allowComments === true;
|
|
61
|
+
this.database = options.database && typeof options.database === 'object' && !options.filename && typeof options.database.start === 'function'
|
|
62
|
+
? options.database
|
|
63
|
+
: (options.engine || null);
|
|
64
|
+
this.filename = options.filename || null;
|
|
65
|
+
this.engine = null;
|
|
66
|
+
this._ownEngine = false;
|
|
67
|
+
this.state = 'disconnected';
|
|
68
|
+
this.stream = {
|
|
69
|
+
destroyed: false,
|
|
70
|
+
writable: true,
|
|
71
|
+
destroy: () => { this.stream.destroyed = true; },
|
|
72
|
+
};
|
|
73
|
+
if (options.host !== undefined || options.database || options.engine || options.filename) {
|
|
74
|
+
queueMicrotask(() => {
|
|
75
|
+
this._getEngine().then(() => this.emit('connect'), err => {
|
|
76
|
+
if (this.listenerCount('error') > 0) this.emit('error', err);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async _getEngine() {
|
|
83
|
+
if (this.engine) return this.engine;
|
|
84
|
+
if (this.database && typeof this.database.start === 'function') {
|
|
85
|
+
this.engine = this.database;
|
|
86
|
+
await this.engine.start();
|
|
87
|
+
} else if (this._pool) {
|
|
88
|
+
this.engine = await this._pool._sharedEngineFor(this.config.database || 'default');
|
|
89
|
+
} else if (this.filename) {
|
|
90
|
+
this.engine = new Database(this.filename);
|
|
91
|
+
await this.engine.start();
|
|
92
|
+
this._ownEngine = true;
|
|
93
|
+
} else {
|
|
94
|
+
this.engine = new Database(':memory:');
|
|
95
|
+
await this.engine.start();
|
|
96
|
+
this._ownEngine = true;
|
|
97
|
+
}
|
|
98
|
+
this.state = 'connected';
|
|
99
|
+
return this.engine;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
connect(cb) {
|
|
103
|
+
const p = this._getEngine().then(c => {
|
|
104
|
+
this.emit('connect');
|
|
105
|
+
return c;
|
|
106
|
+
});
|
|
107
|
+
if (cb) p.then(c => cb(null, c), err => cb(err));
|
|
108
|
+
return p;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
query(...args) {
|
|
112
|
+
let sql, values, cb, rowsAsArray;
|
|
113
|
+
if (typeof args[0] === 'object' && args[0] !== null && typeof args[0].sql === 'string') {
|
|
114
|
+
sql = args[0].sql;
|
|
115
|
+
rowsAsArray = args[0].rowsAsArray === true;
|
|
116
|
+
values = args[0].values !== undefined ? args[0].values : (Array.isArray(args[1]) ? args[1] : null);
|
|
117
|
+
cb = typeof args[1] === 'function' ? args[1] : (typeof args[2] === 'function' ? args[2] : null);
|
|
118
|
+
} else {
|
|
119
|
+
sql = args[0];
|
|
120
|
+
values = Array.isArray(args[1]) ? args[1] : null;
|
|
121
|
+
cb = typeof args[1] === 'function' ? args[1] : (typeof args[2] === 'function' ? args[2] : null);
|
|
122
|
+
}
|
|
123
|
+
const p = (async () => {
|
|
124
|
+
const engine = await this._getEngine();
|
|
125
|
+
const finalSql = applyParams(sql, values);
|
|
126
|
+
const r = await executeSQL(engine, finalSql, {
|
|
127
|
+
safety: this.safety,
|
|
128
|
+
allowComments: this.allowComments,
|
|
129
|
+
maxStatements: this.config.multipleStatements ? null : 1,
|
|
130
|
+
});
|
|
131
|
+
const results = Array.isArray(r) ? r : [r];
|
|
132
|
+
const last = results[results.length - 1];
|
|
133
|
+
const packet = toResultPacket(last, last ? last.table : null, rowsAsArray);
|
|
134
|
+
if (isQueryResult(last)) {
|
|
135
|
+
const arr = [packet.rows, packet.fields];
|
|
136
|
+
arr.rows = packet.rows;
|
|
137
|
+
arr.fields = packet.fields;
|
|
138
|
+
return arr;
|
|
139
|
+
}
|
|
140
|
+
const arr = [packet];
|
|
141
|
+
arr.rows = null;
|
|
142
|
+
arr.fields = null;
|
|
143
|
+
return arr;
|
|
144
|
+
})();
|
|
145
|
+
const q = new EventEmitter();
|
|
146
|
+
q.setMaxListeners = EventEmitter.prototype.setMaxListeners;
|
|
147
|
+
p.then(res => {
|
|
148
|
+
q.emit('result', res.rows || res[0]);
|
|
149
|
+
q.emit('fields', res.fields || []);
|
|
150
|
+
}, err => {
|
|
151
|
+
if (q.listenerCount('error') > 0) q.emit('error', err);
|
|
152
|
+
});
|
|
153
|
+
if (cb) {
|
|
154
|
+
p.then(res => {
|
|
155
|
+
if (res && res.rows && res.fields) cb(null, res.rows, res.fields);
|
|
156
|
+
else if (res && res[0] && res[0].insertId !== undefined) cb(null, res[0]);
|
|
157
|
+
else cb(null, res[0]);
|
|
158
|
+
}, err => cb(err));
|
|
159
|
+
return q;
|
|
160
|
+
}
|
|
161
|
+
p.setMaxListeners = q.setMaxListeners;
|
|
162
|
+
return p;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
execute(sql, values, cb) {
|
|
166
|
+
if (typeof values === 'function') {
|
|
167
|
+
cb = values;
|
|
168
|
+
values = undefined;
|
|
169
|
+
}
|
|
170
|
+
return this.query(sql, values, cb);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
beginTransaction(cb) {
|
|
174
|
+
const p = this.query('BEGIN');
|
|
175
|
+
if (cb) p.then(() => cb(null), err => cb(err));
|
|
176
|
+
return p;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
commit(cb) {
|
|
180
|
+
const p = this.query('COMMIT');
|
|
181
|
+
if (cb) p.then(() => cb(null), err => cb(err));
|
|
182
|
+
return p;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
rollback(cb) {
|
|
186
|
+
const p = this.query('ROLLBACK');
|
|
187
|
+
if (cb) p.then(() => cb(null), err => cb(err));
|
|
188
|
+
return p;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
ping(cb) {
|
|
192
|
+
const p = this._getEngine().then(() => true);
|
|
193
|
+
if (cb) p.then(ok => cb(null, ok), err => cb(err));
|
|
194
|
+
return p;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
release() {
|
|
198
|
+
this.state = 'released';
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
destroy() {
|
|
202
|
+
this.state = 'destroyed';
|
|
203
|
+
this.stream.destroy();
|
|
204
|
+
this.emit('close');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
end(cb) {
|
|
208
|
+
const p = (async () => {
|
|
209
|
+
if (this._ownEngine && this.engine && typeof this.engine.stop === 'function') {
|
|
210
|
+
await this.engine.stop();
|
|
211
|
+
}
|
|
212
|
+
this.state = 'closed';
|
|
213
|
+
this.stream.destroy();
|
|
214
|
+
this.emit('close');
|
|
215
|
+
return undefined;
|
|
216
|
+
})();
|
|
217
|
+
if (cb) p.then(() => cb(null), err => cb(err));
|
|
218
|
+
return p;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
promise() {
|
|
222
|
+
return this;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
class Pool {
|
|
227
|
+
constructor(options = {}) {
|
|
228
|
+
this.config = options;
|
|
229
|
+
this.connectionLimit = options.connectionLimit || 10;
|
|
230
|
+
this.queueLimit = options.queueLimit || 0;
|
|
231
|
+
this.idleTimeout = options.idleTimeout != null ? options.idleTimeout : 300000;
|
|
232
|
+
this._connections = [];
|
|
233
|
+
this._waiters = [];
|
|
234
|
+
this._closed = false;
|
|
235
|
+
this._sharedEngines = new Map();
|
|
236
|
+
this._reaper = setInterval(() => this._reapIdle(), Math.max(1000, Math.floor(this.idleTimeout / 10) || 10000));
|
|
237
|
+
this._reaper.unref();
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async _sharedEngineFor(database) {
|
|
241
|
+
const key = database || 'default';
|
|
242
|
+
let engine = this._sharedEngines.get(key);
|
|
243
|
+
if (!engine) {
|
|
244
|
+
const filename = (this.config && this.config.filename) || null;
|
|
245
|
+
engine = filename ? new Database(filename) : new Database(':memory:');
|
|
246
|
+
if (typeof engine.start === 'function') await engine.start();
|
|
247
|
+
this._sharedEngines.set(key, engine);
|
|
248
|
+
}
|
|
249
|
+
return engine;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
_createConnection() {
|
|
253
|
+
const conn = new Connection(this.config);
|
|
254
|
+
conn._pool = this;
|
|
255
|
+
const origDestroy = conn.destroy.bind(conn);
|
|
256
|
+
conn.destroy = () => {
|
|
257
|
+
this._remove(conn);
|
|
258
|
+
origDestroy();
|
|
259
|
+
};
|
|
260
|
+
return conn;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
_remove(conn) {
|
|
264
|
+
const idx = this._connections.indexOf(conn);
|
|
265
|
+
if (idx !== -1) this._connections.splice(idx, 1);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
_reapIdle() {
|
|
269
|
+
if (this.idleTimeout <= 0) return;
|
|
270
|
+
const now = Date.now();
|
|
271
|
+
for (const conn of this._connections.slice()) {
|
|
272
|
+
if (conn.state === 'released' && !conn._inUse && now - (conn._releasedAt || 0) > this.idleTimeout) {
|
|
273
|
+
this._remove(conn);
|
|
274
|
+
if (conn._ownEngine && conn.engine && typeof conn.engine.stop === 'function') {
|
|
275
|
+
conn.engine.stop().catch(() => {});
|
|
276
|
+
}
|
|
277
|
+
conn.state = 'destroyed';
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
_acquire() {
|
|
283
|
+
if (this._closed) return Promise.reject(new Error('Pool is closed'));
|
|
284
|
+
const free = this._connections.find(c => c.state === 'released' && !c._inUse);
|
|
285
|
+
if (free) {
|
|
286
|
+
free._inUse = true;
|
|
287
|
+
free.state = 'acquired';
|
|
288
|
+
return Promise.resolve(free);
|
|
289
|
+
}
|
|
290
|
+
if (this._connections.length < this.connectionLimit) {
|
|
291
|
+
return Promise.resolve().then(() => {
|
|
292
|
+
if (this._connections.length >= this.connectionLimit) {
|
|
293
|
+
return this._waitForConnection();
|
|
294
|
+
}
|
|
295
|
+
const conn = this._createConnection();
|
|
296
|
+
this._connections.push(conn);
|
|
297
|
+
conn._inUse = true;
|
|
298
|
+
conn.state = 'acquired';
|
|
299
|
+
return conn._getEngine().then(
|
|
300
|
+
() => conn,
|
|
301
|
+
err => {
|
|
302
|
+
this._remove(conn);
|
|
303
|
+
throw err;
|
|
304
|
+
}
|
|
305
|
+
);
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
return this._waitForConnection();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
_waitForConnection() {
|
|
312
|
+
return new Promise((resolve, reject) => {
|
|
313
|
+
this._waiters.push({ resolve, reject });
|
|
314
|
+
if (this.queueLimit > 0 && this._waiters.length > this.queueLimit) {
|
|
315
|
+
const w = this._waiters.shift();
|
|
316
|
+
w.reject(new Error('Pool queue limit exceeded'));
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
_release(conn) {
|
|
322
|
+
conn._inUse = false;
|
|
323
|
+
conn.state = 'released';
|
|
324
|
+
conn._releasedAt = Date.now();
|
|
325
|
+
const w = this._waiters.shift();
|
|
326
|
+
if (w) {
|
|
327
|
+
conn._inUse = true;
|
|
328
|
+
conn.state = 'acquired';
|
|
329
|
+
w.resolve(conn);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async _getConnection() {
|
|
334
|
+
return this._acquire();
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async query(...args) {
|
|
338
|
+
const conn = await this._acquire();
|
|
339
|
+
try {
|
|
340
|
+
return await conn.query(...args);
|
|
341
|
+
} finally {
|
|
342
|
+
this._release(conn);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
async getConnection(cb) {
|
|
347
|
+
const p = (async () => {
|
|
348
|
+
const conn = await this._acquire();
|
|
349
|
+
let released = false;
|
|
350
|
+
const release = () => {
|
|
351
|
+
if (released) return;
|
|
352
|
+
released = true;
|
|
353
|
+
if (conn.state === 'destroyed') {
|
|
354
|
+
this._remove(conn);
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
this._release(conn);
|
|
358
|
+
};
|
|
359
|
+
return { connection: conn, release };
|
|
360
|
+
})();
|
|
361
|
+
if (cb) p.then(r => cb(null, r.connection, r.release), err => cb(err));
|
|
362
|
+
return p;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
async end(cb) {
|
|
366
|
+
const p = (async () => {
|
|
367
|
+
this._closed = true;
|
|
368
|
+
if (this._reaper) { clearInterval(this._reaper); this._reaper = null; }
|
|
369
|
+
const err = new Error('Pool is closed');
|
|
370
|
+
for (const w of this._waiters.splice(0)) w.reject(err);
|
|
371
|
+
const conns = this._connections.splice(0);
|
|
372
|
+
await Promise.allSettled(conns.map(c => c.end()));
|
|
373
|
+
const engines = Array.from(this._sharedEngines.values());
|
|
374
|
+
this._sharedEngines.clear();
|
|
375
|
+
await Promise.allSettled(engines.map(e => (typeof e.stop === 'function' ? e.stop() : null)));
|
|
376
|
+
return undefined;
|
|
377
|
+
})();
|
|
378
|
+
if (cb) p.then(() => cb(null), err => cb(err));
|
|
379
|
+
return p;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
promise() {
|
|
383
|
+
return this;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function createConnection(options) {
|
|
388
|
+
return new Connection(options || {});
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function createPool(options) {
|
|
392
|
+
return new Pool(options || {});
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
class RowDataPacket {}
|
|
396
|
+
class OkPacket {}
|
|
397
|
+
class ResultSetHeader {}
|
|
398
|
+
class FieldPacket {}
|
|
399
|
+
|
|
400
|
+
module.exports = {
|
|
401
|
+
createConnection,
|
|
402
|
+
createPool,
|
|
403
|
+
createConnectionPromise: () => Promise.resolve(createConnection()),
|
|
404
|
+
Connection,
|
|
405
|
+
Pool,
|
|
406
|
+
RowDataPacket,
|
|
407
|
+
OkPacket,
|
|
408
|
+
ResultSetHeader,
|
|
409
|
+
FieldPacket,
|
|
410
|
+
escape: escapeValue,
|
|
411
|
+
escapeId,
|
|
412
|
+
format: applyParams,
|
|
413
|
+
};
|