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/sqlite_worker.js
CHANGED
|
@@ -1,264 +1,264 @@
|
|
|
1
|
-
// © Vexify 2026 All Rights Reserved.
|
|
2
|
-
/**
|
|
3
|
-
* better-sqlite3 兼容层 — worker 线程
|
|
4
|
-
* 在 worker 中持有一个持久 Database,主线程通过 postMessage + Atomics 同步桥调用。
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const { workerData } = require('worker_threads');
|
|
8
|
-
const Database = require('./database');
|
|
9
|
-
const { executeSQL, parseSQL, applyParams } = require('./sql');
|
|
10
|
-
|
|
11
|
-
const PARENT = require('worker_threads').parentPort;
|
|
12
|
-
const PORT = workerData.port || PARENT;
|
|
13
|
-
let db = null;
|
|
14
|
-
let sab = null;
|
|
15
|
-
let ctrl = null;
|
|
16
|
-
const userFunctions = {};
|
|
17
|
-
const userAggregates = {};
|
|
18
|
-
|
|
19
|
-
function startEngine(filename, options) {
|
|
20
|
-
const opts = options || {};
|
|
21
|
-
const isMemory = filename === ':memory:' || !filename;
|
|
22
|
-
if (opts.engine === 'native') {
|
|
23
|
-
const { JSQL: NativeJSQL } = require('./native_client');
|
|
24
|
-
const engine = new NativeJSQL({ mode: 'memory' });
|
|
25
|
-
db = {
|
|
26
|
-
start: async () => { await engine.start(); },
|
|
27
|
-
stop: async () => { await engine.stop(); },
|
|
28
|
-
hasTable: name => engine._tableNames.has(name),
|
|
29
|
-
createTable: (n, s) => engine.createTable(n, s),
|
|
30
|
-
dropTable: n => engine.dropTable(n),
|
|
31
|
-
insert: (n, rows) => engine.insert(n, rows),
|
|
32
|
-
find: (n, f, o) => engine.find(n, f, o),
|
|
33
|
-
updateById: (n, id, data) => engine.updateById(n, id, data),
|
|
34
|
-
updateByIds: (n, e) => engine.updateByIds(n, e),
|
|
35
|
-
removeByIds: (n, ids) => engine.removeByIds(n, ids),
|
|
36
|
-
count: (n, f) => engine.count ? engine.count(n, f) : engine.find(n, f || {}, {}).then(rs => rs.length),
|
|
37
|
-
getTableSchema: n => engine._schemas[n] || null,
|
|
38
|
-
beginTx: () => engine.beginTx(),
|
|
39
|
-
commitTx: id => engine.commitTx(id),
|
|
40
|
-
rollbackTx: id => engine.rollbackTx(id),
|
|
41
|
-
truncate: async () => { throw new Error('truncate not supported on native engine'); },
|
|
42
|
-
flush: async () => { if (engine.flush) await engine.flush(); },
|
|
43
|
-
listTables: () => Array.from(engine._tableNames || []),
|
|
44
|
-
_schemas: engine._schemas,
|
|
45
|
-
};
|
|
46
|
-
return db;
|
|
47
|
-
}
|
|
48
|
-
// 纯 JS 引擎
|
|
49
|
-
db = new Database(isMemory ? ':memory:' : filename, opts.mode === 'hybrid' ? { mode: 'hybrid' } : {});
|
|
50
|
-
return db;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function toRows(result) {
|
|
54
|
-
// 把 executeSQL 的 select 结果转成 better-sqlite3 风格的对象行数组
|
|
55
|
-
if (result && result.type === 'select' && Array.isArray(result.rows)) {
|
|
56
|
-
const cols = Array.isArray(result.columns) ? result.columns : [];
|
|
57
|
-
return result.rows.map(r => {
|
|
58
|
-
const o = {};
|
|
59
|
-
for (let i = 0; i < cols.length; i++) o[cols[i]] = r[i];
|
|
60
|
-
return o;
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
if (result && result.type === 'pragma' && result.value !== undefined) {
|
|
64
|
-
return result.value;
|
|
65
|
-
}
|
|
66
|
-
return undefined;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
let _lastInsertId = 0;
|
|
70
|
-
|
|
71
|
-
async function executeStatement(sql, params) {
|
|
72
|
-
if (params && typeof params === 'object' && !Array.isArray(params)) {
|
|
73
|
-
sql = applyParams(sql, params);
|
|
74
|
-
params = null;
|
|
75
|
-
}
|
|
76
|
-
const result = await executeSQL(db, sql, params, {
|
|
77
|
-
session: {
|
|
78
|
-
get lastInsertId() { return _lastInsertId; },
|
|
79
|
-
set lastInsertId(v) { _lastInsertId = Number(v); },
|
|
80
|
-
},
|
|
81
|
-
functions: userFunctions,
|
|
82
|
-
aggregates: userAggregates,
|
|
83
|
-
});
|
|
84
|
-
if (result && result.type === 'insert' && result.insertId !== null && result.insertId !== undefined) {
|
|
85
|
-
_lastInsertId = Number(result.insertId);
|
|
86
|
-
}
|
|
87
|
-
return result;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function cleanResult(r) {
|
|
91
|
-
if (!r || typeof r !== 'object') return r;
|
|
92
|
-
if (Array.isArray(r)) return r.map(cleanResult);
|
|
93
|
-
if (Buffer.isBuffer(r) || r instanceof Uint8Array || r instanceof ArrayBuffer || ArrayBuffer.isView(r)) return r;
|
|
94
|
-
const out = {};
|
|
95
|
-
for (const [k, v] of Object.entries(r)) {
|
|
96
|
-
if (k === 'result' || k === 'engine') continue;
|
|
97
|
-
if (v instanceof Date) out[k] = v.toISOString();
|
|
98
|
-
else if (v === undefined) out[k] = null;
|
|
99
|
-
else if (typeof v === 'object' && v !== null && !Array.isArray(v)) {
|
|
100
|
-
// 仅保留可 JSON 化的值(防止 Table/内部对象循环引用)
|
|
101
|
-
try { JSON.stringify(v); out[k] = v; } catch (e) { out[k] = null; }
|
|
102
|
-
} else out[k] = v;
|
|
103
|
-
}
|
|
104
|
-
return out;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
async function handleOp(op, args) {
|
|
108
|
-
let result;
|
|
109
|
-
switch (op) {
|
|
110
|
-
case 'start': {
|
|
111
|
-
await startEngine(args[0], args[1]).start();
|
|
112
|
-
result = { ok: true };
|
|
113
|
-
break;
|
|
114
|
-
}
|
|
115
|
-
case 'close': {
|
|
116
|
-
if (db && typeof db.stop === 'function') await db.stop();
|
|
117
|
-
result = { ok: true };
|
|
118
|
-
break;
|
|
119
|
-
}
|
|
120
|
-
case 'exec': {
|
|
121
|
-
result = await executeStatement(args[0], args[1]);
|
|
122
|
-
break;
|
|
123
|
-
}
|
|
124
|
-
case 'run': {
|
|
125
|
-
const r = await executeStatement(args[0], args[1]);
|
|
126
|
-
result = {
|
|
127
|
-
changes: r ? (r.affectedRows || 0) : 0,
|
|
128
|
-
lastInsertRowid: r && r.type === 'insert' ? Number(r.insertId || 0) : _lastInsertId,
|
|
129
|
-
};
|
|
130
|
-
break;
|
|
131
|
-
}
|
|
132
|
-
case 'get': {
|
|
133
|
-
const r = await executeStatement(args[0], args[1]);
|
|
134
|
-
const rows = toRows(r);
|
|
135
|
-
result = Array.isArray(rows) ? (rows.length > 0 ? rows[0] : undefined) : rows;
|
|
136
|
-
break;
|
|
137
|
-
}
|
|
138
|
-
case 'all': {
|
|
139
|
-
const r = await executeStatement(args[0], args[1]);
|
|
140
|
-
const rows = toRows(r);
|
|
141
|
-
result = rows === undefined || rows === null ? [] : rows;
|
|
142
|
-
break;
|
|
143
|
-
}
|
|
144
|
-
case 'raw': {
|
|
145
|
-
const r = await executeStatement(args[0], args[1]);
|
|
146
|
-
let rows = r && r.type === 'select' ? (r.rows || []) : (toRows(r) || []);
|
|
147
|
-
const cols = r && Array.isArray(r.columns) ? r.columns : (rows.length > 0 ? Object.keys(rows[0]) : []);
|
|
148
|
-
if (Array.isArray(rows) && rows.length > 0 && typeof rows[0] === 'object' && !Array.isArray(rows[0])) {
|
|
149
|
-
rows = rows.map(row => cols.map(c => row[c]));
|
|
150
|
-
}
|
|
151
|
-
result = rows;
|
|
152
|
-
break;
|
|
153
|
-
}
|
|
154
|
-
case 'columns': {
|
|
155
|
-
const r = await executeStatement(args[0], args[1]);
|
|
156
|
-
const cols = r && r.type === 'select' && Array.isArray(r.columns) ? r.columns : [];
|
|
157
|
-
result = cols.map((name, i) => ({
|
|
158
|
-
name,
|
|
159
|
-
type: 'TEXT',
|
|
160
|
-
column: name,
|
|
161
|
-
table: r && r.table ? r.table : null,
|
|
162
|
-
database: null,
|
|
163
|
-
}));
|
|
164
|
-
break;
|
|
165
|
-
}
|
|
166
|
-
case 'parse': {
|
|
167
|
-
const stmt = parseSQL(args[0]);
|
|
168
|
-
result = { type: stmt.type, name: stmt.name || stmt.table || null };
|
|
169
|
-
break;
|
|
170
|
-
}
|
|
171
|
-
case 'hasTable': {
|
|
172
|
-
result = typeof db.hasTable === 'function' ? db.hasTable(args[0]) : false;
|
|
173
|
-
break;
|
|
174
|
-
}
|
|
175
|
-
case 'registerFunction': {
|
|
176
|
-
const [name, fnStr, deterministic] = args;
|
|
177
|
-
// 用函数源码在 worker 中重建(无闭包依赖的纯函数)
|
|
178
|
-
try {
|
|
179
|
-
// eslint-disable-next-line no-new-func
|
|
180
|
-
const fn = new Function('return (' + fnStr + ')')();
|
|
181
|
-
userFunctions[String(name).toUpperCase()] = fn;
|
|
182
|
-
result = { ok: true };
|
|
183
|
-
} catch (e) {
|
|
184
|
-
throw new Error('Failed to register function ' + name + ': ' + e.message);
|
|
185
|
-
}
|
|
186
|
-
break;
|
|
187
|
-
}
|
|
188
|
-
case 'clearFunctions': {
|
|
189
|
-
for (const k of Object.keys(userFunctions)) delete userFunctions[k];
|
|
190
|
-
result = { ok: true };
|
|
191
|
-
break;
|
|
192
|
-
}
|
|
193
|
-
case 'registerAggregate': {
|
|
194
|
-
const [name, specStr, deterministic] = args;
|
|
195
|
-
try {
|
|
196
|
-
// eslint-disable-next-line no-new-func
|
|
197
|
-
const spec = new Function('return (' + specStr + ')')();
|
|
198
|
-
userAggregates[String(name).toUpperCase()] = spec;
|
|
199
|
-
result = { ok: true };
|
|
200
|
-
} catch (e) {
|
|
201
|
-
throw new Error('Failed to register aggregate ' + name + ': ' + e.message);
|
|
202
|
-
}
|
|
203
|
-
break;
|
|
204
|
-
}
|
|
205
|
-
case 'serialize': {
|
|
206
|
-
const { exportAllToJSON } = require('./migrate');
|
|
207
|
-
const tables = db.listTables ? db.listTables() : Array.from(db._tableNames || []);
|
|
208
|
-
const dump = await exportAllToJSON(db, tables);
|
|
209
|
-
result = Buffer.from(JSON.stringify(dump), 'utf8');
|
|
210
|
-
break;
|
|
211
|
-
}
|
|
212
|
-
case 'deserialize': {
|
|
213
|
-
const { importFromJSON } = require('./migrate');
|
|
214
|
-
const dump = JSON.parse(Buffer.from(args[0]).toString('utf8'));
|
|
215
|
-
await importFromJSON(db, dump, { overwrite: true });
|
|
216
|
-
if (typeof db.save === 'function') db.save();
|
|
217
|
-
result = { ok: true };
|
|
218
|
-
break;
|
|
219
|
-
}
|
|
220
|
-
case 'backup': {
|
|
221
|
-
// 复制到另一个 sqlite 兼容库
|
|
222
|
-
const { exportAllToJSON } = require('./migrate');
|
|
223
|
-
const target = args[0];
|
|
224
|
-
const tables = db.listTables ? db.listTables() : Array.from(db._tableNames || []);
|
|
225
|
-
const dump = await exportAllToJSON(db, tables);
|
|
226
|
-
// 目标由主线程用另一个 Database 加载,此处返回 dump
|
|
227
|
-
result = Buffer.from(JSON.stringify(dump), 'utf8');
|
|
228
|
-
break;
|
|
229
|
-
}
|
|
230
|
-
case 'clearAggregates': {
|
|
231
|
-
for (const k of Object.keys(userAggregates)) delete userAggregates[k];
|
|
232
|
-
result = { ok: true };
|
|
233
|
-
break;
|
|
234
|
-
}
|
|
235
|
-
default:
|
|
236
|
-
throw new Error(`Unknown op: ${op}`);
|
|
237
|
-
}
|
|
238
|
-
return result;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
PARENT.on('message', async (msg) => {
|
|
242
|
-
const { id, op, args, sab: sabArr } = msg;
|
|
243
|
-
if (sabArr) {
|
|
244
|
-
sab = sabArr;
|
|
245
|
-
ctrl = new Int32Array(sab);
|
|
246
|
-
}
|
|
247
|
-
let payload;
|
|
248
|
-
try {
|
|
249
|
-
payload = { ok: true, result: cleanResult(await handleOp(op, args)) };
|
|
250
|
-
} catch (e) {
|
|
251
|
-
payload = { ok: false, error: { message: e.message, code: e.code || 'SQLITE_ERROR' } };
|
|
252
|
-
}
|
|
253
|
-
PORT.postMessage({ id, ...payload });
|
|
254
|
-
if (ctrl) {
|
|
255
|
-
Atomics.store(ctrl, 0, id);
|
|
256
|
-
Atomics.notify(ctrl, 0);
|
|
257
|
-
}
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
PARENT.on('close', async () => {
|
|
261
|
-
try {
|
|
262
|
-
if (db && typeof db.stop === 'function') await db.stop();
|
|
263
|
-
} catch (e) { /* ignore */ }
|
|
264
|
-
});
|
|
1
|
+
// © Vexify 2026 All Rights Reserved.
|
|
2
|
+
/**
|
|
3
|
+
* better-sqlite3 兼容层 — worker 线程
|
|
4
|
+
* 在 worker 中持有一个持久 Database,主线程通过 postMessage + Atomics 同步桥调用。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { workerData } = require('worker_threads');
|
|
8
|
+
const Database = require('./database');
|
|
9
|
+
const { executeSQL, parseSQL, applyParams } = require('./sql');
|
|
10
|
+
|
|
11
|
+
const PARENT = require('worker_threads').parentPort;
|
|
12
|
+
const PORT = workerData.port || PARENT;
|
|
13
|
+
let db = null;
|
|
14
|
+
let sab = null;
|
|
15
|
+
let ctrl = null;
|
|
16
|
+
const userFunctions = {};
|
|
17
|
+
const userAggregates = {};
|
|
18
|
+
|
|
19
|
+
function startEngine(filename, options) {
|
|
20
|
+
const opts = options || {};
|
|
21
|
+
const isMemory = filename === ':memory:' || !filename;
|
|
22
|
+
if (opts.engine === 'native') {
|
|
23
|
+
const { JSQL: NativeJSQL } = require('./native_client');
|
|
24
|
+
const engine = new NativeJSQL({ mode: 'memory' });
|
|
25
|
+
db = {
|
|
26
|
+
start: async () => { await engine.start(); },
|
|
27
|
+
stop: async () => { await engine.stop(); },
|
|
28
|
+
hasTable: name => engine._tableNames.has(name),
|
|
29
|
+
createTable: (n, s) => engine.createTable(n, s),
|
|
30
|
+
dropTable: n => engine.dropTable(n),
|
|
31
|
+
insert: (n, rows) => engine.insert(n, rows),
|
|
32
|
+
find: (n, f, o) => engine.find(n, f, o),
|
|
33
|
+
updateById: (n, id, data) => engine.updateById(n, id, data),
|
|
34
|
+
updateByIds: (n, e) => engine.updateByIds(n, e),
|
|
35
|
+
removeByIds: (n, ids) => engine.removeByIds(n, ids),
|
|
36
|
+
count: (n, f) => engine.count ? engine.count(n, f) : engine.find(n, f || {}, {}).then(rs => rs.length),
|
|
37
|
+
getTableSchema: n => engine._schemas[n] || null,
|
|
38
|
+
beginTx: () => engine.beginTx(),
|
|
39
|
+
commitTx: id => engine.commitTx(id),
|
|
40
|
+
rollbackTx: id => engine.rollbackTx(id),
|
|
41
|
+
truncate: async () => { throw new Error('truncate not supported on native engine'); },
|
|
42
|
+
flush: async () => { if (engine.flush) await engine.flush(); },
|
|
43
|
+
listTables: () => Array.from(engine._tableNames || []),
|
|
44
|
+
_schemas: engine._schemas,
|
|
45
|
+
};
|
|
46
|
+
return db;
|
|
47
|
+
}
|
|
48
|
+
// 纯 JS 引擎
|
|
49
|
+
db = new Database(isMemory ? ':memory:' : filename, opts.mode === 'hybrid' ? { mode: 'hybrid' } : {});
|
|
50
|
+
return db;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function toRows(result) {
|
|
54
|
+
// 把 executeSQL 的 select 结果转成 better-sqlite3 风格的对象行数组
|
|
55
|
+
if (result && result.type === 'select' && Array.isArray(result.rows)) {
|
|
56
|
+
const cols = Array.isArray(result.columns) ? result.columns : [];
|
|
57
|
+
return result.rows.map(r => {
|
|
58
|
+
const o = {};
|
|
59
|
+
for (let i = 0; i < cols.length; i++) o[cols[i]] = r[i];
|
|
60
|
+
return o;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
if (result && result.type === 'pragma' && result.value !== undefined) {
|
|
64
|
+
return result.value;
|
|
65
|
+
}
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let _lastInsertId = 0;
|
|
70
|
+
|
|
71
|
+
async function executeStatement(sql, params) {
|
|
72
|
+
if (params && typeof params === 'object' && !Array.isArray(params)) {
|
|
73
|
+
sql = applyParams(sql, params);
|
|
74
|
+
params = null;
|
|
75
|
+
}
|
|
76
|
+
const result = await executeSQL(db, sql, params, {
|
|
77
|
+
session: {
|
|
78
|
+
get lastInsertId() { return _lastInsertId; },
|
|
79
|
+
set lastInsertId(v) { _lastInsertId = Number(v); },
|
|
80
|
+
},
|
|
81
|
+
functions: userFunctions,
|
|
82
|
+
aggregates: userAggregates,
|
|
83
|
+
});
|
|
84
|
+
if (result && result.type === 'insert' && result.insertId !== null && result.insertId !== undefined) {
|
|
85
|
+
_lastInsertId = Number(result.insertId);
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function cleanResult(r) {
|
|
91
|
+
if (!r || typeof r !== 'object') return r;
|
|
92
|
+
if (Array.isArray(r)) return r.map(cleanResult);
|
|
93
|
+
if (Buffer.isBuffer(r) || r instanceof Uint8Array || r instanceof ArrayBuffer || ArrayBuffer.isView(r)) return r;
|
|
94
|
+
const out = {};
|
|
95
|
+
for (const [k, v] of Object.entries(r)) {
|
|
96
|
+
if (k === 'result' || k === 'engine') continue;
|
|
97
|
+
if (v instanceof Date) out[k] = v.toISOString();
|
|
98
|
+
else if (v === undefined) out[k] = null;
|
|
99
|
+
else if (typeof v === 'object' && v !== null && !Array.isArray(v)) {
|
|
100
|
+
// 仅保留可 JSON 化的值(防止 Table/内部对象循环引用)
|
|
101
|
+
try { JSON.stringify(v); out[k] = v; } catch (e) { out[k] = null; }
|
|
102
|
+
} else out[k] = v;
|
|
103
|
+
}
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function handleOp(op, args) {
|
|
108
|
+
let result;
|
|
109
|
+
switch (op) {
|
|
110
|
+
case 'start': {
|
|
111
|
+
await startEngine(args[0], args[1]).start();
|
|
112
|
+
result = { ok: true };
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
case 'close': {
|
|
116
|
+
if (db && typeof db.stop === 'function') await db.stop();
|
|
117
|
+
result = { ok: true };
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
case 'exec': {
|
|
121
|
+
result = await executeStatement(args[0], args[1]);
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
case 'run': {
|
|
125
|
+
const r = await executeStatement(args[0], args[1]);
|
|
126
|
+
result = {
|
|
127
|
+
changes: r ? (r.affectedRows || 0) : 0,
|
|
128
|
+
lastInsertRowid: r && r.type === 'insert' ? Number(r.insertId || 0) : _lastInsertId,
|
|
129
|
+
};
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
case 'get': {
|
|
133
|
+
const r = await executeStatement(args[0], args[1]);
|
|
134
|
+
const rows = toRows(r);
|
|
135
|
+
result = Array.isArray(rows) ? (rows.length > 0 ? rows[0] : undefined) : rows;
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
case 'all': {
|
|
139
|
+
const r = await executeStatement(args[0], args[1]);
|
|
140
|
+
const rows = toRows(r);
|
|
141
|
+
result = rows === undefined || rows === null ? [] : rows;
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
case 'raw': {
|
|
145
|
+
const r = await executeStatement(args[0], args[1]);
|
|
146
|
+
let rows = r && r.type === 'select' ? (r.rows || []) : (toRows(r) || []);
|
|
147
|
+
const cols = r && Array.isArray(r.columns) ? r.columns : (rows.length > 0 ? Object.keys(rows[0]) : []);
|
|
148
|
+
if (Array.isArray(rows) && rows.length > 0 && typeof rows[0] === 'object' && !Array.isArray(rows[0])) {
|
|
149
|
+
rows = rows.map(row => cols.map(c => row[c]));
|
|
150
|
+
}
|
|
151
|
+
result = rows;
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
case 'columns': {
|
|
155
|
+
const r = await executeStatement(args[0], args[1]);
|
|
156
|
+
const cols = r && r.type === 'select' && Array.isArray(r.columns) ? r.columns : [];
|
|
157
|
+
result = cols.map((name, i) => ({
|
|
158
|
+
name,
|
|
159
|
+
type: 'TEXT',
|
|
160
|
+
column: name,
|
|
161
|
+
table: r && r.table ? r.table : null,
|
|
162
|
+
database: null,
|
|
163
|
+
}));
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
case 'parse': {
|
|
167
|
+
const stmt = parseSQL(args[0]);
|
|
168
|
+
result = { type: stmt.type, name: stmt.name || stmt.table || null };
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
case 'hasTable': {
|
|
172
|
+
result = typeof db.hasTable === 'function' ? db.hasTable(args[0]) : false;
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
case 'registerFunction': {
|
|
176
|
+
const [name, fnStr, deterministic] = args;
|
|
177
|
+
// 用函数源码在 worker 中重建(无闭包依赖的纯函数)
|
|
178
|
+
try {
|
|
179
|
+
// eslint-disable-next-line no-new-func
|
|
180
|
+
const fn = new Function('return (' + fnStr + ')')();
|
|
181
|
+
userFunctions[String(name).toUpperCase()] = fn;
|
|
182
|
+
result = { ok: true };
|
|
183
|
+
} catch (e) {
|
|
184
|
+
throw new Error('Failed to register function ' + name + ': ' + e.message);
|
|
185
|
+
}
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
case 'clearFunctions': {
|
|
189
|
+
for (const k of Object.keys(userFunctions)) delete userFunctions[k];
|
|
190
|
+
result = { ok: true };
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
case 'registerAggregate': {
|
|
194
|
+
const [name, specStr, deterministic] = args;
|
|
195
|
+
try {
|
|
196
|
+
// eslint-disable-next-line no-new-func
|
|
197
|
+
const spec = new Function('return (' + specStr + ')')();
|
|
198
|
+
userAggregates[String(name).toUpperCase()] = spec;
|
|
199
|
+
result = { ok: true };
|
|
200
|
+
} catch (e) {
|
|
201
|
+
throw new Error('Failed to register aggregate ' + name + ': ' + e.message);
|
|
202
|
+
}
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
case 'serialize': {
|
|
206
|
+
const { exportAllToJSON } = require('./migrate');
|
|
207
|
+
const tables = db.listTables ? db.listTables() : Array.from(db._tableNames || []);
|
|
208
|
+
const dump = await exportAllToJSON(db, tables);
|
|
209
|
+
result = Buffer.from(JSON.stringify(dump), 'utf8');
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
case 'deserialize': {
|
|
213
|
+
const { importFromJSON } = require('./migrate');
|
|
214
|
+
const dump = JSON.parse(Buffer.from(args[0]).toString('utf8'));
|
|
215
|
+
await importFromJSON(db, dump, { overwrite: true });
|
|
216
|
+
if (typeof db.save === 'function') db.save();
|
|
217
|
+
result = { ok: true };
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
case 'backup': {
|
|
221
|
+
// 复制到另一个 sqlite 兼容库
|
|
222
|
+
const { exportAllToJSON } = require('./migrate');
|
|
223
|
+
const target = args[0];
|
|
224
|
+
const tables = db.listTables ? db.listTables() : Array.from(db._tableNames || []);
|
|
225
|
+
const dump = await exportAllToJSON(db, tables);
|
|
226
|
+
// 目标由主线程用另一个 Database 加载,此处返回 dump
|
|
227
|
+
result = Buffer.from(JSON.stringify(dump), 'utf8');
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
case 'clearAggregates': {
|
|
231
|
+
for (const k of Object.keys(userAggregates)) delete userAggregates[k];
|
|
232
|
+
result = { ok: true };
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
default:
|
|
236
|
+
throw new Error(`Unknown op: ${op}`);
|
|
237
|
+
}
|
|
238
|
+
return result;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
PARENT.on('message', async (msg) => {
|
|
242
|
+
const { id, op, args, sab: sabArr } = msg;
|
|
243
|
+
if (sabArr) {
|
|
244
|
+
sab = sabArr;
|
|
245
|
+
ctrl = new Int32Array(sab);
|
|
246
|
+
}
|
|
247
|
+
let payload;
|
|
248
|
+
try {
|
|
249
|
+
payload = { ok: true, result: cleanResult(await handleOp(op, args)) };
|
|
250
|
+
} catch (e) {
|
|
251
|
+
payload = { ok: false, error: { message: e.message, code: e.code || 'SQLITE_ERROR' } };
|
|
252
|
+
}
|
|
253
|
+
PORT.postMessage({ id, ...payload });
|
|
254
|
+
if (ctrl) {
|
|
255
|
+
Atomics.store(ctrl, 0, id);
|
|
256
|
+
Atomics.notify(ctrl, 0);
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
PARENT.on('close', async () => {
|
|
261
|
+
try {
|
|
262
|
+
if (db && typeof db.stop === 'function') await db.stop();
|
|
263
|
+
} catch (e) { /* ignore */ }
|
|
264
|
+
});
|