jsql-neo 3.5.2 → 4.0.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 -0
- package/bin/jsql +97 -0
- package/index.js +20 -0
- package/lib/database.js +484 -28
- package/lib/mod.js +316 -0
- package/lib/mysql_compat.js +232 -0
- package/lib/mysql_server.js +514 -0
- package/lib/native_client.js +470 -0
- package/lib/nedb_compat.js +506 -0
- package/lib/plugin.js +35 -0
- package/lib/sql.js +1160 -0
- package/lib/table.js +42 -21
- package/lib/wasm_client.js +176 -8
- package/native/jsql-neo-native.node +0 -0
- package/nativesrc/jsql-neo-core/Cargo.toml +24 -0
- package/nativesrc/jsql-neo-core/src/engine/hybrid.rs +449 -0
- package/nativesrc/jsql-neo-core/src/engine/memory.rs +147 -0
- package/nativesrc/jsql-neo-core/src/engine/mod.rs +41 -0
- package/nativesrc/jsql-neo-core/src/engine/table.rs +664 -0
- package/nativesrc/jsql-neo-core/src/lib.rs +3 -0
- package/nativesrc/jsql-neo-core/src/storage/mod.rs +1 -0
- package/nativesrc/jsql-neo-core/src/storage/persistent.rs +2 -0
- package/nativesrc/jsql-neo-core/src/storage/wal.rs +85 -0
- package/nativesrc/jsql-neo-core/src/types.rs +94 -0
- package/nativesrc/jsql-neo-native/Cargo.lock +606 -0
- package/nativesrc/jsql-neo-native/Cargo.toml +16 -0
- package/nativesrc/jsql-neo-native/jsql-neo-native.node +0 -0
- package/nativesrc/jsql-neo-native/package.json +7 -0
- package/nativesrc/jsql-neo-native/src/lib.rs +281 -0
- package/package.json +9 -1
- package/wasm/jsql_neo_wasm.d.ts +8 -0
- package/wasm/jsql_neo_wasm.js +455 -6
- package/wasm/jsql_neo_wasm_bg.js +22 -0
- package/wasm/jsql_neo_wasm_bg.wasm +0 -0
- package/wasm/jsql_neo_wasm_bg.wasm.d.ts +4 -0
- package/wasm/package.json +1 -8
package/lib/table.js
CHANGED
|
@@ -187,9 +187,12 @@ class Table {
|
|
|
187
187
|
const pkVal = data[this._primaryKey];
|
|
188
188
|
if (pkVal !== undefined && pkVal !== null) this._pkIndex.set(pkVal, rowIdx);
|
|
189
189
|
}
|
|
190
|
+
for (const [field, tree] of Object.entries(this._btrees)) {
|
|
191
|
+
const val = data[field];
|
|
192
|
+
if (val !== undefined && val !== null) tree.insert(val, rowIdx);
|
|
193
|
+
}
|
|
190
194
|
items[i] = data;
|
|
191
195
|
}
|
|
192
|
-
this._btreesDirty = true;
|
|
193
196
|
this._dirty = true;
|
|
194
197
|
this._db._markDirty();
|
|
195
198
|
for (const hook of this._hooks.afterInsert) {
|
|
@@ -353,26 +356,37 @@ class Table {
|
|
|
353
356
|
|
|
354
357
|
const toRemove = this._applyFilterOptimized(this._getVisibleRows(), query);
|
|
355
358
|
let count = 0;
|
|
359
|
+
const removeIndices = [];
|
|
356
360
|
|
|
357
361
|
for (const row of toRemove) {
|
|
358
362
|
const index = this._rows.indexOf(row);
|
|
359
363
|
if (index === -1) continue;
|
|
360
|
-
|
|
361
|
-
// 外键 CASCADE / SET NULL:查找所有引用此表的子表
|
|
362
364
|
this._cascadeForeignKeys(row);
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
this._rows[index]._deleted = true;
|
|
366
|
-
this._rows[index]._deletedAt = Date.now();
|
|
367
|
-
} else {
|
|
368
|
-
this._removeFromIndexes(index, row);
|
|
369
|
-
this._rows.splice(index, 1);
|
|
370
|
-
this._adjustBTreeAfterRemove(index);
|
|
371
|
-
}
|
|
365
|
+
this._removeFromIndexes(index, row);
|
|
366
|
+
removeIndices.push(index);
|
|
372
367
|
count++;
|
|
373
368
|
}
|
|
374
369
|
|
|
375
370
|
if (count > 0) {
|
|
371
|
+
removeIndices.sort((a, b) => b - a);
|
|
372
|
+
for (const idx of removeIndices) {
|
|
373
|
+
const lastIdx = this._rows.length - 1;
|
|
374
|
+
if (idx < lastIdx) {
|
|
375
|
+
const lastRow = this._rows[lastIdx];
|
|
376
|
+
this._rows[idx] = lastRow;
|
|
377
|
+
if (this._pkIndex && this._primaryKey) {
|
|
378
|
+
this._pkIndex.set(lastRow[this._primaryKey], idx);
|
|
379
|
+
}
|
|
380
|
+
for (const [field, tree] of Object.entries(this._btrees)) {
|
|
381
|
+
const val = lastRow[field];
|
|
382
|
+
if (val !== undefined && val !== null) {
|
|
383
|
+
tree.remove(val, lastIdx);
|
|
384
|
+
tree.insert(val, idx);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
this._rows.pop();
|
|
389
|
+
}
|
|
376
390
|
this._dirty = true;
|
|
377
391
|
this._db._markDirty();
|
|
378
392
|
for (const hook of this._hooks.afterRemove) {
|
|
@@ -402,14 +416,23 @@ class Table {
|
|
|
402
416
|
const row = this._rows[rowIndex];
|
|
403
417
|
if (!row || row._deleted) return 0;
|
|
404
418
|
this._cascadeForeignKeys(row);
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
this.
|
|
410
|
-
this.
|
|
411
|
-
|
|
419
|
+
this._removeFromIndexes(rowIndex, row);
|
|
420
|
+
const lastIdx = this._rows.length - 1;
|
|
421
|
+
if (rowIndex < lastIdx) {
|
|
422
|
+
const lastRow = this._rows[lastIdx];
|
|
423
|
+
this._rows[rowIndex] = lastRow;
|
|
424
|
+
if (this._pkIndex && this._primaryKey) {
|
|
425
|
+
this._pkIndex.set(lastRow[this._primaryKey], rowIndex);
|
|
426
|
+
}
|
|
427
|
+
for (const [field, tree] of Object.entries(this._btrees)) {
|
|
428
|
+
const val = lastRow[field];
|
|
429
|
+
if (val !== undefined && val !== null) {
|
|
430
|
+
tree.remove(val, lastIdx);
|
|
431
|
+
tree.insert(val, rowIndex);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
412
434
|
}
|
|
435
|
+
this._rows.pop();
|
|
413
436
|
this._dirty = true;
|
|
414
437
|
this._db._markDirty();
|
|
415
438
|
for (const hook of this._hooks.afterRemove) {
|
|
@@ -992,7 +1015,6 @@ class Table {
|
|
|
992
1015
|
}
|
|
993
1016
|
|
|
994
1017
|
_addToIndexes(rowIndex, data) {
|
|
995
|
-
this._ensureBTrees();
|
|
996
1018
|
if (this._pkIndex && this._primaryKey) {
|
|
997
1019
|
const pkVal = data[this._primaryKey];
|
|
998
1020
|
if (pkVal !== undefined && pkVal !== null) this._pkIndex.set(pkVal, rowIndex);
|
|
@@ -1015,7 +1037,6 @@ class Table {
|
|
|
1015
1037
|
}
|
|
1016
1038
|
|
|
1017
1039
|
_removeFromIndexes(rowIndex, data) {
|
|
1018
|
-
this._ensureBTrees();
|
|
1019
1040
|
if (this._pkIndex && this._primaryKey) {
|
|
1020
1041
|
this._pkIndex.delete(data[this._primaryKey]);
|
|
1021
1042
|
}
|
package/lib/wasm_client.js
CHANGED
|
@@ -122,24 +122,119 @@ class JSQL {
|
|
|
122
122
|
this._flushThreshold = opts.flushThreshold || 5000;
|
|
123
123
|
this._buffer = {};
|
|
124
124
|
this._bufferSize = 0;
|
|
125
|
+
this._tableNames = new Set();
|
|
126
|
+
this._schemas = {};
|
|
127
|
+
this._autoPlugins = opts.modules !== false;
|
|
128
|
+
|
|
129
|
+
this._plugins = [];
|
|
130
|
+
this._hooks = {
|
|
131
|
+
beforeInsert: [], afterInsert: [],
|
|
132
|
+
beforeUpdate: [], afterUpdate: [],
|
|
133
|
+
beforeDelete: [], afterDelete: [],
|
|
134
|
+
beforeFind: [], afterFind: [],
|
|
135
|
+
beforeCreateTable: [], afterCreateTable: [],
|
|
136
|
+
beforeDropTable: [], afterDropTable: [],
|
|
137
|
+
beforeFlush: [], afterFlush: [],
|
|
138
|
+
beforeCount: [], afterCount: [],
|
|
139
|
+
onStart: [], onStop: []
|
|
140
|
+
};
|
|
141
|
+
this._eventListeners = [];
|
|
142
|
+
if (this._autoPlugins) {
|
|
143
|
+
const { ModuleManager } = require('./mod');
|
|
144
|
+
new ModuleManager().applyTo(this);
|
|
145
|
+
}
|
|
125
146
|
}
|
|
126
147
|
|
|
127
|
-
|
|
148
|
+
use(plugin) {
|
|
149
|
+
if (typeof plugin === 'function') {
|
|
150
|
+
plugin = { install: plugin };
|
|
151
|
+
}
|
|
152
|
+
if (plugin.hooks) {
|
|
153
|
+
for (const [event, fn] of Object.entries(plugin.hooks)) {
|
|
154
|
+
if (this._hooks[event]) {
|
|
155
|
+
this._hooks[event].push(fn);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (typeof plugin.install === 'function') {
|
|
160
|
+
plugin.install(this, this._buildCtx(plugin));
|
|
161
|
+
}
|
|
162
|
+
if (typeof plugin.onEvent === 'function') {
|
|
163
|
+
this._eventListeners.push(plugin.onEvent);
|
|
164
|
+
}
|
|
165
|
+
this._plugins.push(plugin);
|
|
166
|
+
return this;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
_buildCtx(plugin) {
|
|
170
|
+
const self = this;
|
|
171
|
+
return {
|
|
172
|
+
name: plugin.name || 'anonymous',
|
|
173
|
+
engine: this,
|
|
174
|
+
plugin,
|
|
175
|
+
on(hook, fn) { return self.on(hook, fn); },
|
|
176
|
+
onEvent(fn) { return self.onEvent(fn); },
|
|
177
|
+
emit(eventName, data) { self._emit(eventName, data); },
|
|
178
|
+
tables() { return Array.from(self._tableNames); },
|
|
179
|
+
hasTable(name) { return self._tableNames.has(name); },
|
|
180
|
+
getTableSchema(name) { return self._schemas[name] || null; }
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
on(event, fn) {
|
|
185
|
+
if (this._hooks[event]) {
|
|
186
|
+
this._hooks[event].push(fn);
|
|
187
|
+
}
|
|
188
|
+
return this;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
onEvent(fn) {
|
|
192
|
+
this._eventListeners.push(fn);
|
|
193
|
+
return this;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
_emit(eventName, data) {
|
|
197
|
+
for (const fn of this._eventListeners) {
|
|
198
|
+
try { fn(eventName, data); } catch (e) { /* ignore */ }
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
_runHooks(hookName, args) {
|
|
203
|
+
const hooks = this._hooks[hookName];
|
|
204
|
+
if (!hooks || hooks.length === 0) return true;
|
|
205
|
+
for (const fn of hooks) {
|
|
206
|
+
const r = fn(...args);
|
|
207
|
+
if (r === false) return false;
|
|
208
|
+
if (r !== undefined && args.length > 0) {
|
|
209
|
+
args[0] = r;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async start() {
|
|
216
|
+
this._runHooks('onStart', []);
|
|
217
|
+
this._emit('start', {});
|
|
218
|
+
}
|
|
128
219
|
|
|
129
220
|
async _insertBatch(table, rows) {
|
|
130
|
-
const
|
|
131
|
-
const r = safeJsonParse(wasmBindings.jsql_insert_buf(table, bin));
|
|
221
|
+
const r = safeJsonParse(wasmBindings.jsql_insert_json(table, JSON.stringify(rows)));
|
|
132
222
|
if (r && r.error) throw new Error(r.error);
|
|
133
223
|
return r;
|
|
134
224
|
}
|
|
135
225
|
|
|
136
226
|
async _flush() {
|
|
227
|
+
if (!this._runHooks('beforeFlush', [])) return null;
|
|
228
|
+
const flushed = {};
|
|
137
229
|
for (const [table, rows] of Object.entries(this._buffer)) {
|
|
138
230
|
if (rows.length === 0) continue;
|
|
139
|
-
await this._insertBatch(table, rows);
|
|
231
|
+
const r = await this._insertBatch(table, rows);
|
|
232
|
+
flushed[table] = r;
|
|
140
233
|
}
|
|
141
234
|
this._buffer = {};
|
|
142
235
|
this._bufferSize = 0;
|
|
236
|
+
this._runHooks('afterFlush', []);
|
|
237
|
+
return flushed;
|
|
143
238
|
}
|
|
144
239
|
|
|
145
240
|
async flush() {
|
|
@@ -148,6 +243,7 @@ class JSQL {
|
|
|
148
243
|
|
|
149
244
|
async insert(table, data) {
|
|
150
245
|
const arr = Array.isArray(data) ? data : [data];
|
|
246
|
+
if (!this._runHooks('beforeInsert', [table, arr])) return [];
|
|
151
247
|
if (arr.length > 1) {
|
|
152
248
|
await this._flush();
|
|
153
249
|
let result;
|
|
@@ -157,68 +253,140 @@ class JSQL {
|
|
|
157
253
|
if (r && r.error) throw new Error(r.error);
|
|
158
254
|
if (!result) result = r;
|
|
159
255
|
}
|
|
256
|
+
this._emit('insert', { table, count: arr.length, ids: result });
|
|
257
|
+
this._runHooks('afterInsert', [table, arr, result]);
|
|
160
258
|
return result;
|
|
161
259
|
}
|
|
162
260
|
if (!this._buffer[table]) this._buffer[table] = [];
|
|
163
261
|
this._buffer[table].push(arr[0]);
|
|
164
262
|
this._bufferSize++;
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
263
|
+
const flushed = await this._flush();
|
|
264
|
+
return flushed && flushed[table] ? flushed[table] : null;
|
|
168
265
|
}
|
|
169
266
|
|
|
170
267
|
async createTable(name, schema) {
|
|
171
268
|
await this._flush();
|
|
269
|
+
if (!this._runHooks('beforeCreateTable', [name, schema])) return null;
|
|
172
270
|
const r = safeJsonParse(wasmBindings.jsql_create_table(name, JSON.stringify(schema)));
|
|
173
271
|
if (r && r.ok === false) throw new Error(r.error || 'create table failed');
|
|
272
|
+
this._tableNames.add(name);
|
|
273
|
+
this._schemas[name] = schema;
|
|
274
|
+
this._emit('createTable', { name, schema });
|
|
275
|
+
this._runHooks('afterCreateTable', [name, schema]);
|
|
174
276
|
return r;
|
|
175
277
|
}
|
|
176
278
|
|
|
177
279
|
async dropTable(name) {
|
|
178
280
|
await this._flush();
|
|
281
|
+
if (!this._runHooks('beforeDropTable', [name])) return null;
|
|
179
282
|
const r = safeJsonParse(wasmBindings.jsql_drop_table(name));
|
|
180
283
|
if (r && r.ok === false) throw new Error(r.error || 'drop table failed');
|
|
284
|
+
this._tableNames.delete(name);
|
|
285
|
+
delete this._schemas[name];
|
|
286
|
+
this._emit('dropTable', { name });
|
|
287
|
+
this._runHooks('afterDropTable', [name]);
|
|
181
288
|
return r;
|
|
182
289
|
}
|
|
183
290
|
|
|
184
291
|
async findById(table, id) {
|
|
185
292
|
await this._flush();
|
|
293
|
+
if (!this._runHooks('beforeFind', [table, { id }])) return null;
|
|
186
294
|
const r = safeJsonParse(wasmBindings.jsql_find_by_id(table, BigInt(id)));
|
|
187
295
|
if (r && r.error) throw new Error(r.error);
|
|
296
|
+
this._runHooks('afterFind', [table, { id }, r]);
|
|
188
297
|
return r;
|
|
189
298
|
}
|
|
190
299
|
|
|
191
300
|
async find(table, filter, opts = {}) {
|
|
192
301
|
await this._flush();
|
|
302
|
+
if (!this._runHooks('beforeFind', [table, { filter, opts }])) return [];
|
|
193
303
|
const filterStr = filter ? JSON.stringify(filter) : '';
|
|
194
304
|
const { limit = 100, offset = 0 } = opts;
|
|
195
305
|
const r = safeJsonParse(wasmBindings.jsql_find(table, filterStr, limit, offset));
|
|
196
306
|
if (r && r.error) throw new Error(r.error);
|
|
307
|
+
this._runHooks('afterFind', [table, { filter, opts }, r]);
|
|
197
308
|
return r;
|
|
198
309
|
}
|
|
199
310
|
|
|
200
311
|
async count(table) {
|
|
201
312
|
await this._flush();
|
|
313
|
+
this._runHooks('beforeCount', [table]);
|
|
202
314
|
const r = parseInt(wasmBindings.jsql_count(table), 10);
|
|
203
|
-
|
|
315
|
+
const n = isNaN(r) ? 0 : r;
|
|
316
|
+
this._runHooks('afterCount', [table, n]);
|
|
317
|
+
return n;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async updateByIds(table, entries) {
|
|
321
|
+
const pairs = entries.map(([id, data]) => [Number(id), data]);
|
|
322
|
+
if (!this._runHooks('beforeUpdate', [table, pairs])) return;
|
|
323
|
+
await this._flush();
|
|
324
|
+
const r = safeJsonParse(wasmBindings.jsql_update_by_ids(table, JSON.stringify(pairs)));
|
|
325
|
+
if (r && r.error) throw new Error(r.error);
|
|
326
|
+
this._emit('update', { table, entries: pairs, result: r });
|
|
327
|
+
this._runHooks('afterUpdate', [table, pairs, r]);
|
|
328
|
+
return r;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async removeByIds(table, ids) {
|
|
332
|
+
if (!this._runHooks('beforeDelete', [table, ids])) return;
|
|
333
|
+
await this._flush();
|
|
334
|
+
const r = safeJsonParse(wasmBindings.jsql_remove_by_ids(table, JSON.stringify(ids)));
|
|
335
|
+
if (r && r.error) throw new Error(r.error);
|
|
336
|
+
this._emit('delete', { table, ids, result: r });
|
|
337
|
+
this._runHooks('afterDelete', [table, ids, r]);
|
|
338
|
+
return r;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
async findByIds(table, ids) {
|
|
342
|
+
await this._flush();
|
|
343
|
+
if (!this._runHooks('beforeFind', [table, { ids }])) return null;
|
|
344
|
+
const r = safeJsonParse(wasmBindings.jsql_find_by_ids(table, JSON.stringify(ids)));
|
|
345
|
+
if (r && r.error) throw new Error(r.error);
|
|
346
|
+
this._runHooks('afterFind', [table, { ids }, r]);
|
|
347
|
+
return r;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
findByIdsRaw(table, ids) {
|
|
351
|
+
return this.findByIds(table, ids);
|
|
204
352
|
}
|
|
205
353
|
|
|
206
354
|
async updateById(table, id, data) {
|
|
355
|
+
if (!this._runHooks('beforeUpdate', [table, id, data])) return;
|
|
207
356
|
await this._flush();
|
|
208
357
|
const r = safeJsonParse(wasmBindings.jsql_update_by_id(table, BigInt(id), JSON.stringify(data)));
|
|
209
358
|
if (r && r.ok === false) throw new Error(r.error || 'update failed');
|
|
359
|
+
this._emit('update', { table, id, data });
|
|
360
|
+
this._runHooks('afterUpdate', [table, id, data, r]);
|
|
210
361
|
return r;
|
|
211
362
|
}
|
|
212
363
|
|
|
213
364
|
async removeById(table, id) {
|
|
365
|
+
if (!this._runHooks('beforeDelete', [table, id])) return;
|
|
214
366
|
await this._flush();
|
|
215
367
|
const r = safeJsonParse(wasmBindings.jsql_remove_by_id(table, BigInt(id)));
|
|
216
368
|
if (r && r.ok === false) throw new Error(r.error || 'remove failed');
|
|
369
|
+
this._emit('delete', { table, id });
|
|
370
|
+
this._runHooks('afterDelete', [table, id, r]);
|
|
217
371
|
return r;
|
|
218
372
|
}
|
|
219
373
|
|
|
374
|
+
async hasTable(name) {
|
|
375
|
+
return this._tableNames.has(name);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
async getTables() {
|
|
379
|
+
return Array.from(this._tableNames);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
async getTableSchema(name) {
|
|
383
|
+
return this._schemas[name] || null;
|
|
384
|
+
}
|
|
385
|
+
|
|
220
386
|
async stop() {
|
|
221
387
|
await this._flush();
|
|
388
|
+
this._runHooks('onStop', []);
|
|
389
|
+
this._emit('stop', {});
|
|
222
390
|
}
|
|
223
391
|
}
|
|
224
392
|
|
|
Binary file
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "jsql-neo-core"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
description = "Embedded database engine — core library (supports WASM)"
|
|
6
|
+
|
|
7
|
+
[lib]
|
|
8
|
+
crate-type = ["lib", "cdylib"]
|
|
9
|
+
|
|
10
|
+
[dependencies]
|
|
11
|
+
serde = { version = "1", features = ["derive"] }
|
|
12
|
+
serde_json = { version = "1", features = ["preserve_order"] }
|
|
13
|
+
chrono = { version = "0.4", default-features = false, features = ["clock", "wasmbind"] }
|
|
14
|
+
crc32fast = "1"
|
|
15
|
+
itoa = "1"
|
|
16
|
+
ryu = "1"
|
|
17
|
+
rustc-hash = "2"
|
|
18
|
+
bincode = "1"
|
|
19
|
+
wasm-bindgen = { version = "0.2", optional = true }
|
|
20
|
+
js-sys = { version = "0.3", optional = true }
|
|
21
|
+
|
|
22
|
+
[features]
|
|
23
|
+
default = []
|
|
24
|
+
wasm = ["wasm-bindgen", "js-sys"]
|