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_compat.js
CHANGED
|
@@ -1,323 +1,323 @@
|
|
|
1
|
-
// © Vexify 2026 All Rights Reserved.
|
|
2
|
-
/**
|
|
3
|
-
* better-sqlite3 兼容层 — 主线程
|
|
4
|
-
*
|
|
5
|
-
* 提供与 better-sqlite3 完全一致的同步 API:
|
|
6
|
-
* const Database = require('jsql-neo/sqlite');
|
|
7
|
-
* const db = new Database('file.db');
|
|
8
|
-
* const row = db.prepare('SELECT * FROM users WHERE id = ?').get(1);
|
|
9
|
-
*
|
|
10
|
-
* 底层通过 worker_threads + SharedArrayBuffer(Atomics) 同步桥调用 JSQL 引擎。
|
|
11
|
-
* 每个操作:主线程 postMessage 请求 → Atomics.wait 阻塞 → worker 完成后
|
|
12
|
-
* postMessage 结果 + Atomics.notify → 主线程 receiveMessageOnPort 同步取回。
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
const { Worker, MessageChannel, receiveMessageOnPort } = require('worker_threads');
|
|
16
|
-
const path = require('path');
|
|
17
|
-
|
|
18
|
-
const WORKER_PATH = path.join(__dirname, 'sqlite_worker.js');
|
|
19
|
-
|
|
20
|
-
class SqliteError extends Error {
|
|
21
|
-
constructor(message, code) {
|
|
22
|
-
super(message);
|
|
23
|
-
this.name = 'SqliteError';
|
|
24
|
-
this.code = code || 'SQLITE_ERROR';
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
class Database {
|
|
29
|
-
constructor(filename = ':memory:', options = {}) {
|
|
30
|
-
if (filename !== null && filename !== undefined && typeof filename !== 'string') {
|
|
31
|
-
throw new TypeError('The first argument must be a string or undefined');
|
|
32
|
-
}
|
|
33
|
-
this.name = filename || ':memory:';
|
|
34
|
-
this.memory = this.name === ':memory:';
|
|
35
|
-
this.readonly = !!options.readonly;
|
|
36
|
-
this.open = true;
|
|
37
|
-
this._options = options;
|
|
38
|
-
this._sab = new SharedArrayBuffer(4);
|
|
39
|
-
this._ctrl = new Int32Array(this._sab);
|
|
40
|
-
this._seq = 0;
|
|
41
|
-
const channel = new MessageChannel();
|
|
42
|
-
this._port = channel.port2;
|
|
43
|
-
this._worker = new Worker(WORKER_PATH, {
|
|
44
|
-
workerData: { filename: this.name, options, port: channel.port1 },
|
|
45
|
-
transferList: [channel.port1],
|
|
46
|
-
});
|
|
47
|
-
this._sync('start', [this.name, this._options]);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
_sync(op, args) {
|
|
51
|
-
if (!this.open && op !== 'close') throw new SqliteError('The database connection is not open', 'SQLITE_MISUSE');
|
|
52
|
-
const id = ++this._seq;
|
|
53
|
-
// 预先清空控制槽,避免读到旧值
|
|
54
|
-
Atomics.store(this._ctrl, 0, 0);
|
|
55
|
-
this._worker.postMessage({ id, op, args, sab: this._sab });
|
|
56
|
-
// 阻塞直到 worker 完成(ctrl[0] === id)
|
|
57
|
-
const wait = Atomics.wait(this._ctrl, 0, 0);
|
|
58
|
-
if (wait === 'not-equal') {
|
|
59
|
-
// 已经完成,直接取
|
|
60
|
-
} else if (wait === 'timed-out' || wait === 'not-equal') {
|
|
61
|
-
// 重新等待
|
|
62
|
-
}
|
|
63
|
-
// 取结果(同步从端口队列读取)
|
|
64
|
-
let result;
|
|
65
|
-
let guard = 0;
|
|
66
|
-
while (true) {
|
|
67
|
-
const msg = receiveMessageOnPort(this._port);
|
|
68
|
-
if (msg && msg.message && msg.message.id === id) {
|
|
69
|
-
result = msg.message;
|
|
70
|
-
break;
|
|
71
|
-
}
|
|
72
|
-
// 消息可能尚未到达(竞态),再等一次
|
|
73
|
-
const w2 = Atomics.wait(this._ctrl, 0, id - 1);
|
|
74
|
-
if (w2 !== 'ok' && ++guard > 1000) break;
|
|
75
|
-
}
|
|
76
|
-
if (!result) throw new SqliteError('Worker did not respond', 'SQLITE_INTERNAL');
|
|
77
|
-
if (!result.ok) throw new SqliteError(result.error.message, result.error.code);
|
|
78
|
-
return result.result;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
close() {
|
|
82
|
-
if (!this.open) return;
|
|
83
|
-
try {
|
|
84
|
-
this._sync('close', []);
|
|
85
|
-
} finally {
|
|
86
|
-
this.open = false;
|
|
87
|
-
this._worker.terminate();
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
prepare(sql) {
|
|
92
|
-
if (typeof sql !== 'string') throw new TypeError('The "sql" argument must be a string');
|
|
93
|
-
return new Statement(this, sql);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
exec(sql) {
|
|
97
|
-
if (typeof sql !== 'string') throw new TypeError('The "sql" argument must be a string');
|
|
98
|
-
this._sync('exec', [sql, null]);
|
|
99
|
-
return this;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
pragma(pragma, options = {}) {
|
|
103
|
-
if (typeof pragma !== 'string') throw new TypeError('The "pragma" argument must be a string');
|
|
104
|
-
const { simple } = options;
|
|
105
|
-
const sql = /^\s*pragma\b/i.test(pragma) ? pragma : 'PRAGMA ' + pragma;
|
|
106
|
-
const name = (pragma.replace(/^\s*pragma\b\s*/i, '').split(/[=(]/)[0] || '').trim();
|
|
107
|
-
const r = this._sync('all', [sql, null]);
|
|
108
|
-
if (simple) {
|
|
109
|
-
if (Array.isArray(r)) {
|
|
110
|
-
const first = r.length > 0 ? r[0] : null;
|
|
111
|
-
if (first && typeof first === 'object') {
|
|
112
|
-
const keys = Object.keys(first);
|
|
113
|
-
return keys.length === 1 ? first[keys[0]] : first;
|
|
114
|
-
}
|
|
115
|
-
return first;
|
|
116
|
-
}
|
|
117
|
-
return r;
|
|
118
|
-
}
|
|
119
|
-
if (!Array.isArray(r)) return [{ [name || 'value']: r }];
|
|
120
|
-
if (r.length > 0 && (typeof r[0] !== 'object' || r[0] === null)) {
|
|
121
|
-
return r.map(v => ({ [name || 'value']: v }));
|
|
122
|
-
}
|
|
123
|
-
return r;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
transaction(fn) {
|
|
127
|
-
if (typeof fn !== 'function') throw new TypeError('Expected a function');
|
|
128
|
-
const wrapped = (...params) => {
|
|
129
|
-
if (!this.open) throw new SqliteError('The database connection is not open', 'SQLITE_MISUSE');
|
|
130
|
-
this.exec('BEGIN');
|
|
131
|
-
try {
|
|
132
|
-
const result = fn.apply(this, params);
|
|
133
|
-
if (result && typeof result.then === 'function') {
|
|
134
|
-
this.exec('ROLLBACK');
|
|
135
|
-
throw new SqliteError('Transaction functions cannot be asynchronous', 'SQLITE_MISUSE');
|
|
136
|
-
}
|
|
137
|
-
this.exec('COMMIT');
|
|
138
|
-
return result;
|
|
139
|
-
} catch (err) {
|
|
140
|
-
try { this.exec('ROLLBACK'); } catch (e) {}
|
|
141
|
-
throw err;
|
|
142
|
-
}
|
|
143
|
-
};
|
|
144
|
-
wrapped.deferred = () => wrapped;
|
|
145
|
-
wrapped.immediate = () => wrapped;
|
|
146
|
-
wrapped.exclusive = () => wrapped;
|
|
147
|
-
wrapped.default = wrapped;
|
|
148
|
-
return wrapped;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function(name, options, fn) {
|
|
152
|
-
if (typeof options === 'function') { fn = options; options = {}; }
|
|
153
|
-
if (typeof fn !== 'function') throw new TypeError('Expected a function');
|
|
154
|
-
const fnStr = fn.toString();
|
|
155
|
-
if (/\[native code\]/.test(fnStr)) {
|
|
156
|
-
throw new SqliteError(`Cannot register native function '${name}'. Only source-available functions work over the worker bridge.`, 'SQLITE_FEATURE_NOT_SUPPORTED');
|
|
157
|
-
}
|
|
158
|
-
this._sync('registerFunction', [name, fnStr, !!(options && options.deterministic)]);
|
|
159
|
-
return this;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
aggregate(name, options, factory) {
|
|
163
|
-
if (typeof options === 'function') { factory = options; options = {}; }
|
|
164
|
-
if (typeof options === 'object' && options !== null && factory === undefined) { factory = options; options = {}; }
|
|
165
|
-
if (typeof factory !== 'function' && (typeof factory !== 'object' || factory === null)) {
|
|
166
|
-
throw new TypeError('Expected a function or aggregate object');
|
|
167
|
-
}
|
|
168
|
-
// 单函数形式: (values) => result;对象形式: { start, step, result }
|
|
169
|
-
let specStr;
|
|
170
|
-
if (typeof factory === 'function') {
|
|
171
|
-
specStr = factory.toString();
|
|
172
|
-
if (/\[native code\]/.test(specStr)) {
|
|
173
|
-
throw new SqliteError(`Cannot register native aggregate '${name}'`, 'SQLITE_FEATURE_NOT_SUPPORTED');
|
|
174
|
-
}
|
|
175
|
-
} else {
|
|
176
|
-
const parts = [];
|
|
177
|
-
for (const key of ['start', 'step', 'result']) {
|
|
178
|
-
const f = factory[key];
|
|
179
|
-
if (typeof f === 'function') parts.push(`${key}: ${f.toString()}`);
|
|
180
|
-
}
|
|
181
|
-
specStr = '{ ' + parts.join(', ') + ' }';
|
|
182
|
-
}
|
|
183
|
-
this._sync('registerAggregate', [name, specStr, !!(options && options.deterministic)]);
|
|
184
|
-
return this;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
table(name, options, fn) {
|
|
188
|
-
throw new SqliteError('db.table() is not supported by the worker bridge', 'SQLITE_FEATURE_NOT_SUPPORTED');
|
|
189
|
-
}
|
|
190
|
-
serialize() {
|
|
191
|
-
return Buffer.from(this._sync('serialize', []));
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
deserialize(buffer) {
|
|
195
|
-
const buf = Buffer.from(buffer);
|
|
196
|
-
this._sync('deserialize', [buf]);
|
|
197
|
-
return this;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
loadExtension() {
|
|
201
|
-
throw new SqliteError('db.loadExtension() is not supported: JSQL-NEO does not support loading native SQLite extensions', 'SQLITE_FEATURE_NOT_SUPPORTED');
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
backup(destination, options) {
|
|
205
|
-
const dump = this._sync('backup', []);
|
|
206
|
-
const target = new Database(destination, {});
|
|
207
|
-
try {
|
|
208
|
-
target.deserialize(dump);
|
|
209
|
-
return Promise.resolve({ totalPages: 1, remainingPages: 0, idle: true });
|
|
210
|
-
} catch (e) {
|
|
211
|
-
target.close();
|
|
212
|
-
throw e;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
unsafeMode() {
|
|
217
|
-
this._unsafeMode = true;
|
|
218
|
-
return this;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
get defaultSafeIntegers() { return false; }
|
|
222
|
-
set defaultSafeIntegers(v) {}
|
|
223
|
-
|
|
224
|
-
get db() { return this; }
|
|
225
|
-
|
|
226
|
-
[Symbol.for('nodejs.util.inspect.custom')]() {
|
|
227
|
-
return `Database { name: '${this.name}', open: ${this.open} }`;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
class Statement {
|
|
232
|
-
constructor(database, sql) {
|
|
233
|
-
this.database = database;
|
|
234
|
-
this._src = sql;
|
|
235
|
-
this._params = [];
|
|
236
|
-
this._rawMode = false;
|
|
237
|
-
this._pluck = false;
|
|
238
|
-
this._safeIntegers = false;
|
|
239
|
-
const trimmed = sql.trim().toLowerCase();
|
|
240
|
-
this._type = trimmed.startsWith('select') || trimmed.startsWith('pragma') || trimmed.startsWith('with') ? 'select'
|
|
241
|
-
: trimmed.startsWith('insert') ? 'insert'
|
|
242
|
-
: trimmed.startsWith('update') ? 'update'
|
|
243
|
-
: trimmed.startsWith('delete') ? 'delete'
|
|
244
|
-
: trimmed.startsWith('create') || trimmed.startsWith('drop') || trimmed.startsWith('alter') ? 'ddl'
|
|
245
|
-
: 'other';
|
|
246
|
-
this._reader = this._type === 'select' || /^\s*(select|pragma|with)\b/i.test(sql);
|
|
247
|
-
this._tableName = null;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
get reader() { return this._reader; }
|
|
251
|
-
get safeIntegers() { return this._safeIntegers; }
|
|
252
|
-
set safeIntegers(v) { this._safeIntegers = !!v; }
|
|
253
|
-
get source() { return this._src; }
|
|
254
|
-
|
|
255
|
-
bind(...params) {
|
|
256
|
-
if (params.length === 1 && Array.isArray(params[0])) params = params[0];
|
|
257
|
-
this._params = params;
|
|
258
|
-
return this;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
raw(enabled = true) {
|
|
262
|
-
this._rawMode = !!enabled;
|
|
263
|
-
return this;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
pluck(enabled = true) {
|
|
267
|
-
this._pluck = !!enabled;
|
|
268
|
-
return this;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
expand() { return this; }
|
|
272
|
-
returning() { return this; }
|
|
273
|
-
|
|
274
|
-
_call(mode, params) {
|
|
275
|
-
let p = params;
|
|
276
|
-
if (p === undefined || p === null) p = this._params;
|
|
277
|
-
if (Array.isArray(p) && p.length === 1 && typeof p[0] === 'object' && p[0] !== null && !Array.isArray(p[0]) && !(p[0] instanceof Date) && !Buffer.isBuffer(p[0])) {
|
|
278
|
-
const hasNamed = /[@:$][A-Za-z_][A-Za-z0-9_]*/.test(this.source);
|
|
279
|
-
p = hasNamed ? p[0] : p;
|
|
280
|
-
} else if (!Array.isArray(p) && (typeof p !== 'object' || p === null)) {
|
|
281
|
-
p = [p];
|
|
282
|
-
}
|
|
283
|
-
return this.database._sync(mode, [this.source, p]);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
run(...params) {
|
|
287
|
-
const r = this._call('run', params.length > 0 ? params : undefined);
|
|
288
|
-
return r;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
get(...params) {
|
|
292
|
-
return this._call('get', params.length > 0 ? params : undefined);
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
all(...params) {
|
|
296
|
-
const r = this._call('all', params.length > 0 ? params : undefined);
|
|
297
|
-
if (this._rawMode) return this._call('raw', params.length > 0 ? params : undefined);
|
|
298
|
-
if (this._pluck) {
|
|
299
|
-
const cols = r && r.length > 0 ? Object.keys(r[0]) : [];
|
|
300
|
-
return cols.length > 0 ? r.map(row => row[cols[0]]) : [];
|
|
301
|
-
}
|
|
302
|
-
return r;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
iterate(...params) {
|
|
306
|
-
const rows = this.all(...params);
|
|
307
|
-
let i = 0;
|
|
308
|
-
const it = {
|
|
309
|
-
next: () => i < rows.length ? { value: rows[i++], done: false } : { value: undefined, done: true },
|
|
310
|
-
[Symbol.iterator]: () => it,
|
|
311
|
-
};
|
|
312
|
-
return it;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
columns() {
|
|
316
|
-
return this._call('columns', []);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
module.exports = Database;
|
|
321
|
-
module.exports.default = Database;
|
|
322
|
-
module.exports.Database = Database;
|
|
323
|
-
module.exports.SqliteError = SqliteError;
|
|
1
|
+
// © Vexify 2026 All Rights Reserved.
|
|
2
|
+
/**
|
|
3
|
+
* better-sqlite3 兼容层 — 主线程
|
|
4
|
+
*
|
|
5
|
+
* 提供与 better-sqlite3 完全一致的同步 API:
|
|
6
|
+
* const Database = require('jsql-neo/sqlite');
|
|
7
|
+
* const db = new Database('file.db');
|
|
8
|
+
* const row = db.prepare('SELECT * FROM users WHERE id = ?').get(1);
|
|
9
|
+
*
|
|
10
|
+
* 底层通过 worker_threads + SharedArrayBuffer(Atomics) 同步桥调用 JSQL 引擎。
|
|
11
|
+
* 每个操作:主线程 postMessage 请求 → Atomics.wait 阻塞 → worker 完成后
|
|
12
|
+
* postMessage 结果 + Atomics.notify → 主线程 receiveMessageOnPort 同步取回。
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const { Worker, MessageChannel, receiveMessageOnPort } = require('worker_threads');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
const WORKER_PATH = path.join(__dirname, 'sqlite_worker.js');
|
|
19
|
+
|
|
20
|
+
class SqliteError extends Error {
|
|
21
|
+
constructor(message, code) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.name = 'SqliteError';
|
|
24
|
+
this.code = code || 'SQLITE_ERROR';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
class Database {
|
|
29
|
+
constructor(filename = ':memory:', options = {}) {
|
|
30
|
+
if (filename !== null && filename !== undefined && typeof filename !== 'string') {
|
|
31
|
+
throw new TypeError('The first argument must be a string or undefined');
|
|
32
|
+
}
|
|
33
|
+
this.name = filename || ':memory:';
|
|
34
|
+
this.memory = this.name === ':memory:';
|
|
35
|
+
this.readonly = !!options.readonly;
|
|
36
|
+
this.open = true;
|
|
37
|
+
this._options = options;
|
|
38
|
+
this._sab = new SharedArrayBuffer(4);
|
|
39
|
+
this._ctrl = new Int32Array(this._sab);
|
|
40
|
+
this._seq = 0;
|
|
41
|
+
const channel = new MessageChannel();
|
|
42
|
+
this._port = channel.port2;
|
|
43
|
+
this._worker = new Worker(WORKER_PATH, {
|
|
44
|
+
workerData: { filename: this.name, options, port: channel.port1 },
|
|
45
|
+
transferList: [channel.port1],
|
|
46
|
+
});
|
|
47
|
+
this._sync('start', [this.name, this._options]);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
_sync(op, args) {
|
|
51
|
+
if (!this.open && op !== 'close') throw new SqliteError('The database connection is not open', 'SQLITE_MISUSE');
|
|
52
|
+
const id = ++this._seq;
|
|
53
|
+
// 预先清空控制槽,避免读到旧值
|
|
54
|
+
Atomics.store(this._ctrl, 0, 0);
|
|
55
|
+
this._worker.postMessage({ id, op, args, sab: this._sab });
|
|
56
|
+
// 阻塞直到 worker 完成(ctrl[0] === id)
|
|
57
|
+
const wait = Atomics.wait(this._ctrl, 0, 0);
|
|
58
|
+
if (wait === 'not-equal') {
|
|
59
|
+
// 已经完成,直接取
|
|
60
|
+
} else if (wait === 'timed-out' || wait === 'not-equal') {
|
|
61
|
+
// 重新等待
|
|
62
|
+
}
|
|
63
|
+
// 取结果(同步从端口队列读取)
|
|
64
|
+
let result;
|
|
65
|
+
let guard = 0;
|
|
66
|
+
while (true) {
|
|
67
|
+
const msg = receiveMessageOnPort(this._port);
|
|
68
|
+
if (msg && msg.message && msg.message.id === id) {
|
|
69
|
+
result = msg.message;
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
// 消息可能尚未到达(竞态),再等一次
|
|
73
|
+
const w2 = Atomics.wait(this._ctrl, 0, id - 1);
|
|
74
|
+
if (w2 !== 'ok' && ++guard > 1000) break;
|
|
75
|
+
}
|
|
76
|
+
if (!result) throw new SqliteError('Worker did not respond', 'SQLITE_INTERNAL');
|
|
77
|
+
if (!result.ok) throw new SqliteError(result.error.message, result.error.code);
|
|
78
|
+
return result.result;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
close() {
|
|
82
|
+
if (!this.open) return;
|
|
83
|
+
try {
|
|
84
|
+
this._sync('close', []);
|
|
85
|
+
} finally {
|
|
86
|
+
this.open = false;
|
|
87
|
+
this._worker.terminate();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
prepare(sql) {
|
|
92
|
+
if (typeof sql !== 'string') throw new TypeError('The "sql" argument must be a string');
|
|
93
|
+
return new Statement(this, sql);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
exec(sql) {
|
|
97
|
+
if (typeof sql !== 'string') throw new TypeError('The "sql" argument must be a string');
|
|
98
|
+
this._sync('exec', [sql, null]);
|
|
99
|
+
return this;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
pragma(pragma, options = {}) {
|
|
103
|
+
if (typeof pragma !== 'string') throw new TypeError('The "pragma" argument must be a string');
|
|
104
|
+
const { simple } = options;
|
|
105
|
+
const sql = /^\s*pragma\b/i.test(pragma) ? pragma : 'PRAGMA ' + pragma;
|
|
106
|
+
const name = (pragma.replace(/^\s*pragma\b\s*/i, '').split(/[=(]/)[0] || '').trim();
|
|
107
|
+
const r = this._sync('all', [sql, null]);
|
|
108
|
+
if (simple) {
|
|
109
|
+
if (Array.isArray(r)) {
|
|
110
|
+
const first = r.length > 0 ? r[0] : null;
|
|
111
|
+
if (first && typeof first === 'object') {
|
|
112
|
+
const keys = Object.keys(first);
|
|
113
|
+
return keys.length === 1 ? first[keys[0]] : first;
|
|
114
|
+
}
|
|
115
|
+
return first;
|
|
116
|
+
}
|
|
117
|
+
return r;
|
|
118
|
+
}
|
|
119
|
+
if (!Array.isArray(r)) return [{ [name || 'value']: r }];
|
|
120
|
+
if (r.length > 0 && (typeof r[0] !== 'object' || r[0] === null)) {
|
|
121
|
+
return r.map(v => ({ [name || 'value']: v }));
|
|
122
|
+
}
|
|
123
|
+
return r;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
transaction(fn) {
|
|
127
|
+
if (typeof fn !== 'function') throw new TypeError('Expected a function');
|
|
128
|
+
const wrapped = (...params) => {
|
|
129
|
+
if (!this.open) throw new SqliteError('The database connection is not open', 'SQLITE_MISUSE');
|
|
130
|
+
this.exec('BEGIN');
|
|
131
|
+
try {
|
|
132
|
+
const result = fn.apply(this, params);
|
|
133
|
+
if (result && typeof result.then === 'function') {
|
|
134
|
+
this.exec('ROLLBACK');
|
|
135
|
+
throw new SqliteError('Transaction functions cannot be asynchronous', 'SQLITE_MISUSE');
|
|
136
|
+
}
|
|
137
|
+
this.exec('COMMIT');
|
|
138
|
+
return result;
|
|
139
|
+
} catch (err) {
|
|
140
|
+
try { this.exec('ROLLBACK'); } catch (e) {}
|
|
141
|
+
throw err;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
wrapped.deferred = () => wrapped;
|
|
145
|
+
wrapped.immediate = () => wrapped;
|
|
146
|
+
wrapped.exclusive = () => wrapped;
|
|
147
|
+
wrapped.default = wrapped;
|
|
148
|
+
return wrapped;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function(name, options, fn) {
|
|
152
|
+
if (typeof options === 'function') { fn = options; options = {}; }
|
|
153
|
+
if (typeof fn !== 'function') throw new TypeError('Expected a function');
|
|
154
|
+
const fnStr = fn.toString();
|
|
155
|
+
if (/\[native code\]/.test(fnStr)) {
|
|
156
|
+
throw new SqliteError(`Cannot register native function '${name}'. Only source-available functions work over the worker bridge.`, 'SQLITE_FEATURE_NOT_SUPPORTED');
|
|
157
|
+
}
|
|
158
|
+
this._sync('registerFunction', [name, fnStr, !!(options && options.deterministic)]);
|
|
159
|
+
return this;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
aggregate(name, options, factory) {
|
|
163
|
+
if (typeof options === 'function') { factory = options; options = {}; }
|
|
164
|
+
if (typeof options === 'object' && options !== null && factory === undefined) { factory = options; options = {}; }
|
|
165
|
+
if (typeof factory !== 'function' && (typeof factory !== 'object' || factory === null)) {
|
|
166
|
+
throw new TypeError('Expected a function or aggregate object');
|
|
167
|
+
}
|
|
168
|
+
// 单函数形式: (values) => result;对象形式: { start, step, result }
|
|
169
|
+
let specStr;
|
|
170
|
+
if (typeof factory === 'function') {
|
|
171
|
+
specStr = factory.toString();
|
|
172
|
+
if (/\[native code\]/.test(specStr)) {
|
|
173
|
+
throw new SqliteError(`Cannot register native aggregate '${name}'`, 'SQLITE_FEATURE_NOT_SUPPORTED');
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
const parts = [];
|
|
177
|
+
for (const key of ['start', 'step', 'result']) {
|
|
178
|
+
const f = factory[key];
|
|
179
|
+
if (typeof f === 'function') parts.push(`${key}: ${f.toString()}`);
|
|
180
|
+
}
|
|
181
|
+
specStr = '{ ' + parts.join(', ') + ' }';
|
|
182
|
+
}
|
|
183
|
+
this._sync('registerAggregate', [name, specStr, !!(options && options.deterministic)]);
|
|
184
|
+
return this;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
table(name, options, fn) {
|
|
188
|
+
throw new SqliteError('db.table() is not supported by the worker bridge', 'SQLITE_FEATURE_NOT_SUPPORTED');
|
|
189
|
+
}
|
|
190
|
+
serialize() {
|
|
191
|
+
return Buffer.from(this._sync('serialize', []));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
deserialize(buffer) {
|
|
195
|
+
const buf = Buffer.from(buffer);
|
|
196
|
+
this._sync('deserialize', [buf]);
|
|
197
|
+
return this;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
loadExtension() {
|
|
201
|
+
throw new SqliteError('db.loadExtension() is not supported: JSQL-NEO does not support loading native SQLite extensions', 'SQLITE_FEATURE_NOT_SUPPORTED');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
backup(destination, options) {
|
|
205
|
+
const dump = this._sync('backup', []);
|
|
206
|
+
const target = new Database(destination, {});
|
|
207
|
+
try {
|
|
208
|
+
target.deserialize(dump);
|
|
209
|
+
return Promise.resolve({ totalPages: 1, remainingPages: 0, idle: true });
|
|
210
|
+
} catch (e) {
|
|
211
|
+
target.close();
|
|
212
|
+
throw e;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
unsafeMode() {
|
|
217
|
+
this._unsafeMode = true;
|
|
218
|
+
return this;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
get defaultSafeIntegers() { return false; }
|
|
222
|
+
set defaultSafeIntegers(v) {}
|
|
223
|
+
|
|
224
|
+
get db() { return this; }
|
|
225
|
+
|
|
226
|
+
[Symbol.for('nodejs.util.inspect.custom')]() {
|
|
227
|
+
return `Database { name: '${this.name}', open: ${this.open} }`;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
class Statement {
|
|
232
|
+
constructor(database, sql) {
|
|
233
|
+
this.database = database;
|
|
234
|
+
this._src = sql;
|
|
235
|
+
this._params = [];
|
|
236
|
+
this._rawMode = false;
|
|
237
|
+
this._pluck = false;
|
|
238
|
+
this._safeIntegers = false;
|
|
239
|
+
const trimmed = sql.trim().toLowerCase();
|
|
240
|
+
this._type = trimmed.startsWith('select') || trimmed.startsWith('pragma') || trimmed.startsWith('with') ? 'select'
|
|
241
|
+
: trimmed.startsWith('insert') ? 'insert'
|
|
242
|
+
: trimmed.startsWith('update') ? 'update'
|
|
243
|
+
: trimmed.startsWith('delete') ? 'delete'
|
|
244
|
+
: trimmed.startsWith('create') || trimmed.startsWith('drop') || trimmed.startsWith('alter') ? 'ddl'
|
|
245
|
+
: 'other';
|
|
246
|
+
this._reader = this._type === 'select' || /^\s*(select|pragma|with)\b/i.test(sql);
|
|
247
|
+
this._tableName = null;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
get reader() { return this._reader; }
|
|
251
|
+
get safeIntegers() { return this._safeIntegers; }
|
|
252
|
+
set safeIntegers(v) { this._safeIntegers = !!v; }
|
|
253
|
+
get source() { return this._src; }
|
|
254
|
+
|
|
255
|
+
bind(...params) {
|
|
256
|
+
if (params.length === 1 && Array.isArray(params[0])) params = params[0];
|
|
257
|
+
this._params = params;
|
|
258
|
+
return this;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
raw(enabled = true) {
|
|
262
|
+
this._rawMode = !!enabled;
|
|
263
|
+
return this;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
pluck(enabled = true) {
|
|
267
|
+
this._pluck = !!enabled;
|
|
268
|
+
return this;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
expand() { return this; }
|
|
272
|
+
returning() { return this; }
|
|
273
|
+
|
|
274
|
+
_call(mode, params) {
|
|
275
|
+
let p = params;
|
|
276
|
+
if (p === undefined || p === null) p = this._params;
|
|
277
|
+
if (Array.isArray(p) && p.length === 1 && typeof p[0] === 'object' && p[0] !== null && !Array.isArray(p[0]) && !(p[0] instanceof Date) && !Buffer.isBuffer(p[0])) {
|
|
278
|
+
const hasNamed = /[@:$][A-Za-z_][A-Za-z0-9_]*/.test(this.source);
|
|
279
|
+
p = hasNamed ? p[0] : p;
|
|
280
|
+
} else if (!Array.isArray(p) && (typeof p !== 'object' || p === null)) {
|
|
281
|
+
p = [p];
|
|
282
|
+
}
|
|
283
|
+
return this.database._sync(mode, [this.source, p]);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
run(...params) {
|
|
287
|
+
const r = this._call('run', params.length > 0 ? params : undefined);
|
|
288
|
+
return r;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
get(...params) {
|
|
292
|
+
return this._call('get', params.length > 0 ? params : undefined);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
all(...params) {
|
|
296
|
+
const r = this._call('all', params.length > 0 ? params : undefined);
|
|
297
|
+
if (this._rawMode) return this._call('raw', params.length > 0 ? params : undefined);
|
|
298
|
+
if (this._pluck) {
|
|
299
|
+
const cols = r && r.length > 0 ? Object.keys(r[0]) : [];
|
|
300
|
+
return cols.length > 0 ? r.map(row => row[cols[0]]) : [];
|
|
301
|
+
}
|
|
302
|
+
return r;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
iterate(...params) {
|
|
306
|
+
const rows = this.all(...params);
|
|
307
|
+
let i = 0;
|
|
308
|
+
const it = {
|
|
309
|
+
next: () => i < rows.length ? { value: rows[i++], done: false } : { value: undefined, done: true },
|
|
310
|
+
[Symbol.iterator]: () => it,
|
|
311
|
+
};
|
|
312
|
+
return it;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
columns() {
|
|
316
|
+
return this._call('columns', []);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
module.exports = Database;
|
|
321
|
+
module.exports.default = Database;
|
|
322
|
+
module.exports.Database = Database;
|
|
323
|
+
module.exports.SqliteError = SqliteError;
|