jsql-neo 4.5.0 → 4.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/database.js +42 -0
- package/lib/mysql_server.js +14 -3
- package/lib/native_client.js +15 -6
- package/lib/sql.js +28 -7
- package/package.json +1 -1
package/lib/database.js
CHANGED
|
@@ -1142,9 +1142,46 @@ class Database {
|
|
|
1142
1142
|
async start() {
|
|
1143
1143
|
this._runHooks('onStart', []);
|
|
1144
1144
|
this._emit('start', {});
|
|
1145
|
+
if (this._dirMode && !this._sigInstalled) {
|
|
1146
|
+
this._sigInstalled = true;
|
|
1147
|
+
Database._installSignalHandler(this);
|
|
1148
|
+
}
|
|
1145
1149
|
return this;
|
|
1146
1150
|
}
|
|
1147
1151
|
|
|
1152
|
+
/**
|
|
1153
|
+
* 进程退出时兜底刷盘:拦截 SIGINT/SIGTERM,同步落盘脏表后再退出。
|
|
1154
|
+
* 使用静态共享 handler,避免多个数据库实例重复注册/互相覆盖。
|
|
1155
|
+
* @private
|
|
1156
|
+
*/
|
|
1157
|
+
static _installSignalHandler(db) {
|
|
1158
|
+
if (!Database._signalHandlerInstalled) {
|
|
1159
|
+
Database._signalHandlerInstalled = true;
|
|
1160
|
+
Database._signalDBs = new Set();
|
|
1161
|
+
const handler = (sig) => {
|
|
1162
|
+
for (const d of Database._signalDBs) {
|
|
1163
|
+
try {
|
|
1164
|
+
if (d._dirMode) {
|
|
1165
|
+
if (d._flushTimer) { clearTimeout(d._flushTimer); d._flushTimer = null; }
|
|
1166
|
+
d._flushDirty();
|
|
1167
|
+
d._saveMeta();
|
|
1168
|
+
}
|
|
1169
|
+
} catch (e) { /* 兜底失败不阻塞退出 */ }
|
|
1170
|
+
}
|
|
1171
|
+
process.exit(128 + (sig === 'SIGINT' ? 2 : 15));
|
|
1172
|
+
};
|
|
1173
|
+
process.on('SIGINT', handler);
|
|
1174
|
+
process.on('SIGTERM', handler);
|
|
1175
|
+
Database._signalHandler = handler;
|
|
1176
|
+
}
|
|
1177
|
+
Database._signalDBs.add(db);
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
/** 从进程退出兜底集合移除(stop 时调用) */
|
|
1181
|
+
static _uninstallSignalHandler(db) {
|
|
1182
|
+
if (Database._signalDBs) Database._signalDBs.delete(db);
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1148
1185
|
insert(tableName, data) {
|
|
1149
1186
|
const table = this._ensureTable(tableName);
|
|
1150
1187
|
if (!table) throw createError('ER_NO_SUCH_TABLE', tableName);
|
|
@@ -1333,11 +1370,16 @@ class Database {
|
|
|
1333
1370
|
this._runHooks('onStop', []);
|
|
1334
1371
|
this._emit('stop', {});
|
|
1335
1372
|
if (this._dirMode) {
|
|
1373
|
+
if (this._flushTimer) { clearTimeout(this._flushTimer); this._flushTimer = null; }
|
|
1336
1374
|
try {
|
|
1337
1375
|
this._flushDirty();
|
|
1338
1376
|
this._saveMeta();
|
|
1339
1377
|
} catch (e) {}
|
|
1340
1378
|
}
|
|
1379
|
+
if (this._sigInstalled) {
|
|
1380
|
+
this._sigInstalled = false;
|
|
1381
|
+
Database._uninstallSignalHandler(this);
|
|
1382
|
+
}
|
|
1341
1383
|
return this;
|
|
1342
1384
|
}
|
|
1343
1385
|
|
package/lib/mysql_server.js
CHANGED
|
@@ -104,8 +104,19 @@ function handshakePacket(connectionId, seed) {
|
|
|
104
104
|
return b.build();
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
|
|
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();
|
|
109
120
|
b.byte(0x00);
|
|
110
121
|
b.raw(encodeLenenc(affectedRows));
|
|
111
122
|
b.raw(encodeLenenc(insertId));
|
|
@@ -364,7 +375,7 @@ class MysqlConnection {
|
|
|
364
375
|
this.socket = socket;
|
|
365
376
|
this.server = server;
|
|
366
377
|
this.connectionId = ++server._connectionCounter;
|
|
367
|
-
this.seed =
|
|
378
|
+
this.seed = genSeed(20);
|
|
368
379
|
this.buffer = Buffer.alloc(0);
|
|
369
380
|
this.sequence = 0;
|
|
370
381
|
this.authenticated = false;
|
package/lib/native_client.js
CHANGED
|
@@ -257,14 +257,23 @@ class JSQL {
|
|
|
257
257
|
_runHooks(hookName, args) {
|
|
258
258
|
const hooks = this._hooks[hookName];
|
|
259
259
|
if (!hooks || hooks.length === 0) return true;
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
260
|
+
if (this._inHook) {
|
|
261
|
+
// 防止插件 hook 内重入 native 调用导致 N-API 崩溃(段错误)
|
|
262
|
+
throw new Error('ER_PLUGIN_REENTRY: plugin hook "' + hookName + '" re-entered native call; plugin must not call engine methods inside its own hook');
|
|
263
|
+
}
|
|
264
|
+
this._inHook = true;
|
|
265
|
+
try {
|
|
266
|
+
for (const fn of hooks) {
|
|
267
|
+
const r = fn(...args);
|
|
268
|
+
if (r === false) return false;
|
|
269
|
+
if (r !== undefined && args.length > 0) {
|
|
270
|
+
args[0] = r;
|
|
271
|
+
}
|
|
265
272
|
}
|
|
273
|
+
return true;
|
|
274
|
+
} finally {
|
|
275
|
+
this._inHook = false;
|
|
266
276
|
}
|
|
267
|
-
return true;
|
|
268
277
|
}
|
|
269
278
|
|
|
270
279
|
async start() {
|
package/lib/sql.js
CHANGED
|
@@ -284,16 +284,28 @@ class Parser {
|
|
|
284
284
|
if (t.value === 'PRIMARY') {
|
|
285
285
|
this.expectKeyword('PRIMARY'); this.expectKeyword('KEY');
|
|
286
286
|
this.expect('op', '(');
|
|
287
|
-
const
|
|
287
|
+
const pkCols = [this.parseTableName()];
|
|
288
|
+
while (this.peek().type === 'op' && this.peek().value === ',') {
|
|
289
|
+
this.next();
|
|
290
|
+
pkCols.push(this.parseTableName());
|
|
291
|
+
}
|
|
288
292
|
this.expect('op', ')');
|
|
289
|
-
|
|
293
|
+
for (const pkCol of pkCols) {
|
|
294
|
+
if (schema[pkCol]) schema[pkCol].primaryKey = true;
|
|
295
|
+
}
|
|
290
296
|
hasPk = true;
|
|
291
297
|
} else {
|
|
292
298
|
this.expectKeyword('UNIQUE');
|
|
293
299
|
this.expect('op', '(');
|
|
294
|
-
const
|
|
300
|
+
const uCols = [this.parseTableName()];
|
|
301
|
+
while (this.peek().type === 'op' && this.peek().value === ',') {
|
|
302
|
+
this.next();
|
|
303
|
+
uCols.push(this.parseTableName());
|
|
304
|
+
}
|
|
295
305
|
this.expect('op', ')');
|
|
296
|
-
|
|
306
|
+
for (const uCol of uCols) {
|
|
307
|
+
if (schema[uCol]) schema[uCol].unique = true;
|
|
308
|
+
}
|
|
297
309
|
}
|
|
298
310
|
} else if (t.type === 'keyword' && t.value === 'CONSTRAINT') {
|
|
299
311
|
this.expectKeyword('CONSTRAINT');
|
|
@@ -1012,7 +1024,7 @@ class Parser {
|
|
|
1012
1024
|
if (t.type === 'sysvar') return { type: 'sysvar', name: t.value };
|
|
1013
1025
|
if (t.type === 'keyword' && t.value === 'NULL') return { type: 'value', value: null };
|
|
1014
1026
|
if (t.type === 'keyword' && t.value === 'CASE') return this.parseCase();
|
|
1015
|
-
if (t.type === 'keyword' && this.peek().type === 'op' && this.peek().value === '(') {
|
|
1027
|
+
if (t.type === 'keyword' && !['SUM', 'AVG', 'MIN', 'MAX', 'COUNT'].includes(t.value) && this.peek().type === 'op' && this.peek().value === '(') {
|
|
1016
1028
|
const name = t.value;
|
|
1017
1029
|
this.next();
|
|
1018
1030
|
const args = [];
|
|
@@ -1608,7 +1620,10 @@ class SQLExecutor {
|
|
|
1608
1620
|
if (explicit.length > 0) {
|
|
1609
1621
|
const all = (await this.engine.find(statement.name, {}, { limit: 1e9, offset: 0 })).map(r => normalizeRow(r, schema));
|
|
1610
1622
|
const pkMap = new Map();
|
|
1611
|
-
for (const row of all)
|
|
1623
|
+
for (const row of all) {
|
|
1624
|
+
const keys = pkCols.map(c => row[c]).filter(v => v !== undefined && v !== null);
|
|
1625
|
+
if (keys.length === pkCols.length) pkMap.set(keyOf(row), keys);
|
|
1626
|
+
}
|
|
1612
1627
|
const conflicts = [];
|
|
1613
1628
|
const fresh = [];
|
|
1614
1629
|
for (const d of dataRows) {
|
|
@@ -1626,7 +1641,13 @@ class SQLExecutor {
|
|
|
1626
1641
|
for (const { d, existingId } of conflicts) {
|
|
1627
1642
|
const data = {};
|
|
1628
1643
|
for (const [col, val] of statement.onDuplicate) data[col] = val;
|
|
1629
|
-
this.engine.updateById
|
|
1644
|
+
if (this.engine.updateById && existingId.length === 1) {
|
|
1645
|
+
this.engine.updateById(statement.name, existingId[0], data);
|
|
1646
|
+
} else if (this.engine.update) {
|
|
1647
|
+
const filter = {};
|
|
1648
|
+
pkCols.forEach((c, i) => { filter[c] = existingId[i]; });
|
|
1649
|
+
this.engine.update(statement.name, filter, data);
|
|
1650
|
+
}
|
|
1630
1651
|
updated++;
|
|
1631
1652
|
}
|
|
1632
1653
|
await this.engine.flush();
|