@peerbit/indexer-sqlite3 3.0.6 → 3.0.7
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/dist/assets/sqlite3/sqlite3.worker.min.js +6 -1
- package/dist/benchmark/query-planner.d.ts +2 -0
- package/dist/benchmark/query-planner.d.ts.map +1 -0
- package/dist/benchmark/query-planner.js +97 -0
- package/dist/benchmark/query-planner.js.map +1 -0
- package/dist/index.min.js +535 -183
- package/dist/index.min.js.map +3 -3
- package/dist/src/engine.d.ts +15 -4
- package/dist/src/engine.d.ts.map +1 -1
- package/dist/src/engine.js +364 -179
- package/dist/src/engine.js.map +1 -1
- package/dist/src/query-planner.d.ts +20 -0
- package/dist/src/query-planner.d.ts.map +1 -1
- package/dist/src/query-planner.js +191 -21
- package/dist/src/query-planner.js.map +1 -1
- package/dist/src/schema.d.ts +6 -2
- package/dist/src/schema.d.ts.map +1 -1
- package/dist/src/schema.js +11 -8
- package/dist/src/schema.js.map +1 -1
- package/dist/src/sqlite3-messages.worker.d.ts +8 -1
- package/dist/src/sqlite3-messages.worker.d.ts.map +1 -1
- package/dist/src/sqlite3-messages.worker.js.map +1 -1
- package/dist/src/sqlite3.browser.d.ts.map +1 -1
- package/dist/src/sqlite3.browser.js +21 -0
- package/dist/src/sqlite3.browser.js.map +1 -1
- package/dist/src/sqlite3.wasm.d.ts.map +1 -1
- package/dist/src/sqlite3.wasm.js +4 -1
- package/dist/src/sqlite3.wasm.js.map +1 -1
- package/dist/src/sqlite3.worker.js +6 -0
- package/dist/src/sqlite3.worker.js.map +1 -1
- package/dist/src/types.d.ts +4 -0
- package/dist/src/types.d.ts.map +1 -1
- package/package.json +6 -5
- package/src/engine.ts +464 -235
- package/src/query-planner.ts +247 -22
- package/src/schema.ts +21 -4
- package/src/sqlite3-messages.worker.ts +6 -0
- package/src/sqlite3.browser.ts +33 -0
- package/src/sqlite3.wasm.ts +4 -1
- package/src/sqlite3.worker.ts +6 -0
- package/src/types.ts +3 -0
package/src/engine.ts
CHANGED
|
@@ -91,7 +91,9 @@ async function getIgnoreFK(stmt: Statement, values: any[]) {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
const createBatchInsertSQL = (table: Table, rows: number) => {
|
|
94
|
-
const columns = table.fields
|
|
94
|
+
const columns = table.fields
|
|
95
|
+
.map((field) => escapeColumnName(field.name))
|
|
96
|
+
.join(", ");
|
|
95
97
|
const rowPlaceholder = `(${table.fields.map(() => "?").join(", ")})`;
|
|
96
98
|
return `insert into ${table.name} (${columns}) VALUES ${Array.from({
|
|
97
99
|
length: rows,
|
|
@@ -100,6 +102,15 @@ const createBatchInsertSQL = (table: Table, rows: number) => {
|
|
|
100
102
|
.join(", ")};`;
|
|
101
103
|
};
|
|
102
104
|
|
|
105
|
+
const createInsertReturningSQL = (table: Table) =>
|
|
106
|
+
`insert into ${table.name} (${table.fields.map((field) => escapeColumnName(field.name)).join(", ")}) VALUES (${table.fields.map((_x) => "?").join(", ")}) RETURNING ${table.primary};`;
|
|
107
|
+
|
|
108
|
+
const createInsertKnownIdSQL = (table: Table) =>
|
|
109
|
+
`insert into ${table.name} (${table.fields.map((field) => escapeColumnName(field.name)).join(", ")}) VALUES (${table.fields.map((_x) => "?").join(", ")});`;
|
|
110
|
+
|
|
111
|
+
const createReplaceSQL = (table: Table) =>
|
|
112
|
+
`insert or replace into ${table.name} (${table.fields.map((field) => escapeColumnName(field.name)).join(", ")}) VALUES (${table.fields.map((_x) => "?").join(", ")});`;
|
|
113
|
+
|
|
103
114
|
const canUseWithoutRowId = (table: Table) => {
|
|
104
115
|
if (table.inline || table.primary === false || !table.primaryField) {
|
|
105
116
|
return false;
|
|
@@ -146,20 +157,21 @@ export class SQLiteIndex<T extends Record<string, any>>
|
|
|
146
157
|
primaryKeyString!: string;
|
|
147
158
|
planner: QueryPlanner;
|
|
148
159
|
private scopeString?: string;
|
|
149
|
-
private _rootTables
|
|
150
|
-
private _tables
|
|
151
|
-
private _cursor
|
|
160
|
+
private _rootTables: Table[] = [];
|
|
161
|
+
private _tables: Map<string, Table> = new Map();
|
|
162
|
+
private _cursor: Map<
|
|
152
163
|
string,
|
|
153
164
|
{
|
|
154
165
|
fetch: (amount: number) => Promise<IndexedResult[]>;
|
|
155
166
|
/* countStatement: Statement; */
|
|
156
167
|
expire: number;
|
|
157
168
|
}
|
|
158
|
-
|
|
169
|
+
> = new Map(); // TODO choose limit better
|
|
159
170
|
private cursorPruner: ReturnType<typeof setInterval> | undefined;
|
|
160
171
|
|
|
161
172
|
iteratorTimeout: number;
|
|
162
173
|
closed: boolean = true;
|
|
174
|
+
private state: "closed" | "open" | "closing" = "closed";
|
|
163
175
|
private fkMode: FKMode;
|
|
164
176
|
|
|
165
177
|
id: string;
|
|
@@ -191,23 +203,91 @@ export class SQLiteIndex<T extends Record<string, any>>
|
|
|
191
203
|
return this.properties.persisted ?? true;
|
|
192
204
|
}
|
|
193
205
|
|
|
206
|
+
private static readonly _emptyTables = new Map<string, Table>();
|
|
207
|
+
private static readonly _emptyRootTables: Table[] = [];
|
|
208
|
+
private static readonly _emptyCursor = new Map();
|
|
209
|
+
|
|
210
|
+
private static closedIterator<
|
|
211
|
+
T extends Record<string, any>,
|
|
212
|
+
S extends Shape | undefined,
|
|
213
|
+
>(): types.IndexIterator<T, S> {
|
|
214
|
+
return {
|
|
215
|
+
all: async () => [],
|
|
216
|
+
close: async () => undefined,
|
|
217
|
+
done: () => true,
|
|
218
|
+
next: async () => [],
|
|
219
|
+
pending: async () => 0,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private async ifOpen<R>(fallback: R, fn: () => Promise<R>): Promise<R> {
|
|
224
|
+
if (this.isClosing()) {
|
|
225
|
+
return fallback;
|
|
226
|
+
}
|
|
227
|
+
this.assertOpen();
|
|
228
|
+
try {
|
|
229
|
+
return await fn();
|
|
230
|
+
} catch (error) {
|
|
231
|
+
if (this.isClosing()) {
|
|
232
|
+
return fallback;
|
|
233
|
+
}
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private async withWriteIfOpen<R>(
|
|
239
|
+
fallback: R,
|
|
240
|
+
fn: () => Promise<R>,
|
|
241
|
+
): Promise<R> {
|
|
242
|
+
if (this.isClosing()) {
|
|
243
|
+
return fallback;
|
|
244
|
+
}
|
|
245
|
+
this.assertOpen();
|
|
246
|
+
return this.withWriteBarrier(() => this.ifOpen(fallback, fn));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
private assertOpen() {
|
|
250
|
+
if (this.state !== "open") {
|
|
251
|
+
throw new types.NotStartedError();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private isClosing() {
|
|
256
|
+
return this.state === "closing";
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
private setClosing() {
|
|
260
|
+
this.state = "closing";
|
|
261
|
+
this.closed = true;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private setClosed() {
|
|
265
|
+
this.state = "closed";
|
|
266
|
+
this.closed = true;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
private setOpen() {
|
|
270
|
+
this.state = "open";
|
|
271
|
+
this.closed = false;
|
|
272
|
+
}
|
|
273
|
+
|
|
194
274
|
get tables() {
|
|
195
275
|
if (this.closed) {
|
|
196
|
-
|
|
276
|
+
return SQLiteIndex._emptyTables;
|
|
197
277
|
}
|
|
198
278
|
return this._tables;
|
|
199
279
|
}
|
|
200
280
|
|
|
201
281
|
get rootTables() {
|
|
202
282
|
if (this.closed) {
|
|
203
|
-
|
|
283
|
+
return SQLiteIndex._emptyRootTables;
|
|
204
284
|
}
|
|
205
285
|
return this._rootTables;
|
|
206
286
|
}
|
|
207
287
|
|
|
208
288
|
get cursor() {
|
|
209
289
|
if (this.closed) {
|
|
210
|
-
|
|
290
|
+
return SQLiteIndex._emptyCursor;
|
|
211
291
|
}
|
|
212
292
|
return this._cursor;
|
|
213
293
|
}
|
|
@@ -239,9 +319,12 @@ export class SQLiteIndex<T extends Record<string, any>>
|
|
|
239
319
|
}
|
|
240
320
|
|
|
241
321
|
async start(): Promise<void> {
|
|
242
|
-
if (this.
|
|
322
|
+
if (this.state === "open") {
|
|
243
323
|
return;
|
|
244
324
|
}
|
|
325
|
+
if (this.state === "closing") {
|
|
326
|
+
throw new types.NotStartedError();
|
|
327
|
+
}
|
|
245
328
|
|
|
246
329
|
if (this.primaryKeyArr == null || this.primaryKeyArr.length === 0) {
|
|
247
330
|
throw new Error("Not initialized");
|
|
@@ -265,6 +348,8 @@ export class SQLiteIndex<T extends Record<string, any>>
|
|
|
265
348
|
this._rootTables = tables.filter((x) => x.parent == null);
|
|
266
349
|
|
|
267
350
|
const allTables = tables;
|
|
351
|
+
const startupStatements: { id: string; sql: string }[] = [];
|
|
352
|
+
const startupTableStatements = new Map<string, string>();
|
|
268
353
|
|
|
269
354
|
for (const table of allTables) {
|
|
270
355
|
this._tables.set(table.name, table);
|
|
@@ -283,7 +368,28 @@ export class SQLiteIndex<T extends Record<string, any>>
|
|
|
283
368
|
? " strict, without rowid"
|
|
284
369
|
: " strict";
|
|
285
370
|
const sqlCreateTable = `create table if not exists ${table.name} (${[...table.fields, ...table.constraints].map((s) => s.definition).join(", ")})${tableOptions}`;
|
|
286
|
-
|
|
371
|
+
startupTableStatements.set(table.name, sqlCreateTable);
|
|
372
|
+
|
|
373
|
+
startupStatements.push(
|
|
374
|
+
{
|
|
375
|
+
id: putStatementKey(table),
|
|
376
|
+
sql: createInsertReturningSQL(table),
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
id: insertKnownIdStatementKey(table),
|
|
380
|
+
sql: createInsertKnownIdSQL(table),
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
id: replaceStatementKey(table),
|
|
384
|
+
sql: createReplaceSQL(table),
|
|
385
|
+
},
|
|
386
|
+
);
|
|
387
|
+
if (table.parent) {
|
|
388
|
+
startupStatements.push({
|
|
389
|
+
id: resolveChildrenStatement(table),
|
|
390
|
+
sql: selectChildren(table),
|
|
391
|
+
});
|
|
392
|
+
}
|
|
287
393
|
|
|
288
394
|
/* const fieldsToIndex = table.fields.filter(
|
|
289
395
|
(field) =>
|
|
@@ -317,28 +423,25 @@ export class SQLiteIndex<T extends Record<string, any>>
|
|
|
317
423
|
}
|
|
318
424
|
}
|
|
319
425
|
} */
|
|
426
|
+
}
|
|
320
427
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
sqlInsertKnownId,
|
|
333
|
-
insertKnownIdStatementKey(table),
|
|
334
|
-
);
|
|
335
|
-
await this.properties.db.prepare(sqlReplace, replaceStatementKey(table));
|
|
428
|
+
if (startupTableStatements.size > 0) {
|
|
429
|
+
const existingTables = await this.getExistingSQLiteObjects("table", [
|
|
430
|
+
...startupTableStatements.keys(),
|
|
431
|
+
]);
|
|
432
|
+
const missingTableStatements = [...startupTableStatements.entries()]
|
|
433
|
+
.filter(([tableName]) => !existingTables.has(tableName))
|
|
434
|
+
.map(([, sql]) => sql);
|
|
435
|
+
if (missingTableStatements.length > 0) {
|
|
436
|
+
await this.properties.db.exec(missingTableStatements.join(";"));
|
|
437
|
+
}
|
|
438
|
+
}
|
|
336
439
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
);
|
|
440
|
+
if (this.properties.db.prepareMany) {
|
|
441
|
+
await this.properties.db.prepareMany(startupStatements);
|
|
442
|
+
} else {
|
|
443
|
+
for (const statement of startupStatements) {
|
|
444
|
+
await this.properties.db.prepare(statement.sql, statement.id);
|
|
342
445
|
}
|
|
343
446
|
}
|
|
344
447
|
|
|
@@ -351,7 +454,30 @@ export class SQLiteIndex<T extends Record<string, any>>
|
|
|
351
454
|
}
|
|
352
455
|
}, this.iteratorTimeout);
|
|
353
456
|
|
|
354
|
-
this.
|
|
457
|
+
this.setOpen();
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
private async getExistingSQLiteObjects(
|
|
461
|
+
type: "table" | "index",
|
|
462
|
+
names: string[],
|
|
463
|
+
): Promise<Set<string>> {
|
|
464
|
+
if (names.length === 0) {
|
|
465
|
+
return new Set();
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const sql = `select name from sqlite_master where type = ? and name in (${names
|
|
469
|
+
.map(() => "?")
|
|
470
|
+
.join(", ")})`;
|
|
471
|
+
const statement =
|
|
472
|
+
this.properties.db.statements.get(sql) ||
|
|
473
|
+
(await this.properties.db.prepare(sql, sql));
|
|
474
|
+
const rows = await statement.all([type, ...names]);
|
|
475
|
+
await statement.reset?.();
|
|
476
|
+
return new Set(
|
|
477
|
+
(rows as Array<{ name?: string }>)
|
|
478
|
+
.map((row) => row.name)
|
|
479
|
+
.filter((name): name is string => typeof name === "string"),
|
|
480
|
+
);
|
|
355
481
|
}
|
|
356
482
|
|
|
357
483
|
private async clearStatements() {
|
|
@@ -362,78 +488,113 @@ export class SQLiteIndex<T extends Record<string, any>>
|
|
|
362
488
|
}
|
|
363
489
|
|
|
364
490
|
async stop(): Promise<void> {
|
|
365
|
-
if (this.closed) {
|
|
491
|
+
if (this.state === "closed") {
|
|
366
492
|
return;
|
|
367
493
|
}
|
|
368
|
-
this.
|
|
369
|
-
|
|
494
|
+
if (this.state === "closing") {
|
|
495
|
+
await this._writeBarrier.catch(() => undefined);
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
this.setClosing();
|
|
370
499
|
|
|
371
|
-
|
|
500
|
+
try {
|
|
501
|
+
clearInterval(this.cursorPruner!);
|
|
372
502
|
|
|
373
|
-
|
|
503
|
+
await this._writeBarrier.catch(() => undefined);
|
|
374
504
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
505
|
+
await this.clearStatements();
|
|
506
|
+
|
|
507
|
+
this._tables?.clear();
|
|
378
508
|
|
|
379
|
-
|
|
509
|
+
if (this._cursor) {
|
|
510
|
+
for (const [k, _v] of this._cursor) {
|
|
511
|
+
await this.clearupIterator(k);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
await this.planner.stop();
|
|
516
|
+
} finally {
|
|
517
|
+
this.setClosed();
|
|
518
|
+
}
|
|
380
519
|
}
|
|
381
520
|
|
|
382
521
|
async drop(): Promise<void> {
|
|
383
|
-
|
|
384
|
-
|
|
522
|
+
const wasOpen = this.state === "open";
|
|
523
|
+
if (wasOpen) {
|
|
524
|
+
this.setClosing();
|
|
385
525
|
}
|
|
386
526
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
527
|
+
try {
|
|
528
|
+
if (this.cursorPruner != null) {
|
|
529
|
+
clearInterval(this.cursorPruner);
|
|
530
|
+
this.cursorPruner = undefined;
|
|
531
|
+
}
|
|
391
532
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
533
|
+
if (wasOpen) {
|
|
534
|
+
await this._writeBarrier.catch(() => undefined);
|
|
535
|
+
}
|
|
397
536
|
|
|
398
|
-
|
|
537
|
+
const status = await this.properties.db.status?.();
|
|
538
|
+
if (status === "closed") {
|
|
539
|
+
this._tables?.clear();
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
399
542
|
|
|
400
|
-
|
|
401
|
-
// drop table faster by dropping constraints first
|
|
543
|
+
await this.clearStatements();
|
|
402
544
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
}
|
|
545
|
+
// drop root table and cascade
|
|
546
|
+
// drop table faster by dropping constraints first
|
|
406
547
|
|
|
407
|
-
|
|
548
|
+
if (this._rootTables) {
|
|
549
|
+
for (const table of this._rootTables) {
|
|
550
|
+
await this.properties.db.exec(`drop table if exists ${table.name}`);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
408
553
|
|
|
409
|
-
|
|
410
|
-
|
|
554
|
+
this._tables?.clear();
|
|
555
|
+
|
|
556
|
+
if (this._cursor) {
|
|
557
|
+
for (const [k, _v] of this._cursor) {
|
|
558
|
+
await this.clearupIterator(k);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
await this.planner.stop();
|
|
562
|
+
} finally {
|
|
563
|
+
this.setClosed();
|
|
411
564
|
}
|
|
412
|
-
await this.planner.stop();
|
|
413
565
|
}
|
|
414
566
|
|
|
415
567
|
private async resolveDependencies(
|
|
416
568
|
parentId: any,
|
|
417
569
|
table: Table,
|
|
418
570
|
): Promise<any[]> {
|
|
419
|
-
const stmt = this.
|
|
571
|
+
const stmt = await this.getOrPrepareStatement(
|
|
420
572
|
resolveChildrenStatement(table),
|
|
421
|
-
|
|
573
|
+
selectChildren(table),
|
|
574
|
+
);
|
|
422
575
|
const results = await stmt.all([parentId]);
|
|
423
576
|
await stmt.reset?.();
|
|
424
577
|
return results;
|
|
425
578
|
}
|
|
579
|
+
|
|
580
|
+
private async getOrPrepareStatement(key: string, sql: string) {
|
|
581
|
+
const existing = this.properties.db.statements.get(key);
|
|
582
|
+
if (existing) {
|
|
583
|
+
return existing;
|
|
584
|
+
}
|
|
585
|
+
return this.properties.db.prepare(sql, key);
|
|
586
|
+
}
|
|
426
587
|
async get(
|
|
427
588
|
id: types.IdKey,
|
|
428
589
|
options?: { shape: Shape },
|
|
429
590
|
): Promise<IndexedResult<T> | undefined> {
|
|
430
|
-
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
591
|
+
return this.ifOpen(undefined, async () => {
|
|
592
|
+
for (const table of this._rootTables) {
|
|
593
|
+
const { join: joinMap, selects } = selectAllFieldsFromTable(
|
|
594
|
+
table,
|
|
595
|
+
options?.shape,
|
|
596
|
+
);
|
|
597
|
+
const sql = `${generateSelectQuery(table, selects)} ${buildJoin(joinMap).join} where ${table.name}.${this.primaryKeyString} = ? limit 1`;
|
|
437
598
|
const stmt = await this.properties.db.prepare(sql, sql);
|
|
438
599
|
const rows = await stmt.get([
|
|
439
600
|
table.primaryField?.from?.type
|
|
@@ -456,14 +617,9 @@ export class SQLiteIndex<T extends Record<string, any>>
|
|
|
456
617
|
)) as unknown as T,
|
|
457
618
|
id,
|
|
458
619
|
};
|
|
459
|
-
} catch (error) {
|
|
460
|
-
if (this.closed) {
|
|
461
|
-
throw new types.NotStartedError();
|
|
462
|
-
}
|
|
463
|
-
throw error;
|
|
464
620
|
}
|
|
465
|
-
|
|
466
|
-
|
|
621
|
+
return undefined;
|
|
622
|
+
});
|
|
467
623
|
}
|
|
468
624
|
|
|
469
625
|
async put(
|
|
@@ -471,7 +627,7 @@ export class SQLiteIndex<T extends Record<string, any>>
|
|
|
471
627
|
_id?: any,
|
|
472
628
|
options?: { replace?: boolean },
|
|
473
629
|
): Promise<void> {
|
|
474
|
-
return this.
|
|
630
|
+
return this.withWriteIfOpen(undefined, async () => {
|
|
475
631
|
const classOfValue = value.constructor as Constructor<T>;
|
|
476
632
|
return insert(
|
|
477
633
|
async (values, table) => {
|
|
@@ -481,9 +637,10 @@ export class SQLiteIndex<T extends Record<string, any>>
|
|
|
481
637
|
if (preId != null) {
|
|
482
638
|
const shouldReplace = options?.replace ?? true;
|
|
483
639
|
if (!shouldReplace) {
|
|
484
|
-
statement = this.
|
|
640
|
+
statement = await this.getOrPrepareStatement(
|
|
485
641
|
insertKnownIdStatementKey(table),
|
|
486
|
-
|
|
642
|
+
createInsertKnownIdSQL(table),
|
|
643
|
+
);
|
|
487
644
|
try {
|
|
488
645
|
this.fkMode === "race-tolerant"
|
|
489
646
|
? await runIgnoreFK(statement, values)
|
|
@@ -493,26 +650,29 @@ export class SQLiteIndex<T extends Record<string, any>>
|
|
|
493
650
|
throw error;
|
|
494
651
|
}
|
|
495
652
|
await statement.reset?.();
|
|
496
|
-
statement = this.
|
|
653
|
+
statement = await this.getOrPrepareStatement(
|
|
497
654
|
replaceStatementKey(table),
|
|
498
|
-
|
|
655
|
+
createReplaceSQL(table),
|
|
656
|
+
);
|
|
499
657
|
this.fkMode === "race-tolerant"
|
|
500
658
|
? await runIgnoreFK(statement, values)
|
|
501
659
|
: await statement.run(values);
|
|
502
660
|
}
|
|
503
661
|
} else {
|
|
504
|
-
statement = this.
|
|
662
|
+
statement = await this.getOrPrepareStatement(
|
|
505
663
|
replaceStatementKey(table),
|
|
506
|
-
|
|
664
|
+
createReplaceSQL(table),
|
|
665
|
+
);
|
|
507
666
|
this.fkMode === "race-tolerant"
|
|
508
667
|
? await runIgnoreFK(statement, values)
|
|
509
668
|
: await statement.run(values);
|
|
510
669
|
}
|
|
511
670
|
return preId;
|
|
512
671
|
} else {
|
|
513
|
-
statement = this.
|
|
672
|
+
statement = await this.getOrPrepareStatement(
|
|
514
673
|
putStatementKey(table),
|
|
515
|
-
|
|
674
|
+
createInsertReturningSQL(table),
|
|
675
|
+
);
|
|
516
676
|
const out =
|
|
517
677
|
this.fkMode === "race-tolerant"
|
|
518
678
|
? await getIgnoreFK(statement, values)
|
|
@@ -566,6 +726,11 @@ export class SQLiteIndex<T extends Record<string, any>>
|
|
|
566
726
|
request?: types.IterateOptions,
|
|
567
727
|
options?: { shape?: S; reference?: boolean },
|
|
568
728
|
): types.IndexIterator<T, S> {
|
|
729
|
+
if (this.isClosing()) {
|
|
730
|
+
return SQLiteIndex.closedIterator<T, S>();
|
|
731
|
+
}
|
|
732
|
+
this.assertOpen();
|
|
733
|
+
|
|
569
734
|
// create a sql statement where the offset and the limit id dynamic and can be updated
|
|
570
735
|
// TODO don't use offset but sort and limit 'next' calls by the last value of the sort
|
|
571
736
|
|
|
@@ -591,92 +756,112 @@ export class SQLiteIndex<T extends Record<string, any>>
|
|
|
591
756
|
|
|
592
757
|
/* let totalCount: undefined | number = undefined; */
|
|
593
758
|
const fetch = async (amount: number | "all") => {
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
759
|
+
const closeAsDone = () => {
|
|
760
|
+
once = true;
|
|
761
|
+
hasMore = false;
|
|
762
|
+
kept = 0;
|
|
763
|
+
return [] as IndexedResult<types.ReturnTypeFromShape<T, S>>[];
|
|
764
|
+
};
|
|
765
|
+
if (this.isClosing()) {
|
|
766
|
+
return closeAsDone();
|
|
767
|
+
}
|
|
768
|
+
this.assertOpen();
|
|
769
|
+
try {
|
|
770
|
+
kept = undefined;
|
|
771
|
+
if (!once) {
|
|
772
|
+
planningScope = this.planner.scope(normalizedQuery);
|
|
597
773
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
774
|
+
let { sql, bindable: toBind } = convertSearchRequestToQuery(
|
|
775
|
+
normalizedQuery,
|
|
776
|
+
this.tables,
|
|
777
|
+
this._rootTables,
|
|
778
|
+
{
|
|
779
|
+
planner: planningScope,
|
|
780
|
+
shape: options?.shape,
|
|
781
|
+
fetchAll: amount === "all", // if we are to fetch all, we dont need stable sorting
|
|
782
|
+
},
|
|
783
|
+
);
|
|
608
784
|
|
|
609
|
-
|
|
610
|
-
|
|
785
|
+
sqlFetch = sql;
|
|
786
|
+
bindable = toBind;
|
|
611
787
|
|
|
612
|
-
|
|
788
|
+
await planningScope.beforePrepare();
|
|
613
789
|
|
|
614
|
-
|
|
790
|
+
stmt = await this.properties.db.prepare(sqlFetch, sqlFetch);
|
|
615
791
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
792
|
+
// Bump timeout timer
|
|
793
|
+
iterator.expire = Date.now() + this.iteratorTimeout;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
once = true;
|
|
619
797
|
|
|
620
|
-
|
|
798
|
+
const allResults = await planningScope.perform(async () => {
|
|
799
|
+
const allResults: Record<string, any>[] = await stmt.all([
|
|
800
|
+
...bindable,
|
|
801
|
+
...(amount !== "all" ? [amount, offset] : []),
|
|
802
|
+
]);
|
|
803
|
+
return allResults;
|
|
804
|
+
});
|
|
621
805
|
|
|
622
|
-
|
|
623
|
-
const allResults: Record<string, any>[] = await stmt.all([
|
|
806
|
+
/* const allResults: Record<string, any>[] = await stmt.all([
|
|
624
807
|
...bindable,
|
|
625
|
-
...(amount !== "all" ? [amount,
|
|
808
|
+
...(amount !== "all" ? [amount,
|
|
809
|
+
offset] : [])
|
|
626
810
|
]);
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
let results: IndexedResult<types.ReturnTypeFromShape<T, S>>[] =
|
|
637
|
-
await Promise.all(
|
|
638
|
-
allResults.map(async (row: any) => {
|
|
639
|
-
let selectedTable = this._rootTables.find(
|
|
640
|
-
(table) =>
|
|
641
|
-
row[getTablePrefixedField(table, this.primaryKeyString)] !=
|
|
642
|
-
null,
|
|
643
|
-
)!;
|
|
644
|
-
|
|
645
|
-
const value = await resolveInstanceFromValue<T, S>(
|
|
646
|
-
row,
|
|
647
|
-
this.tables,
|
|
648
|
-
selectedTable,
|
|
649
|
-
this.resolveDependencies.bind(this),
|
|
650
|
-
true,
|
|
651
|
-
options?.shape,
|
|
652
|
-
);
|
|
811
|
+
*/
|
|
812
|
+
let results: IndexedResult<types.ReturnTypeFromShape<T, S>>[] =
|
|
813
|
+
await Promise.all(
|
|
814
|
+
allResults.map(async (row: any) => {
|
|
815
|
+
let selectedTable = this._rootTables.find(
|
|
816
|
+
(table) =>
|
|
817
|
+
row[getTablePrefixedField(table, this.primaryKeyString)] !=
|
|
818
|
+
null,
|
|
819
|
+
)!;
|
|
653
820
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
821
|
+
const value = await resolveInstanceFromValue<T, S>(
|
|
822
|
+
row,
|
|
823
|
+
this.tables,
|
|
824
|
+
selectedTable,
|
|
825
|
+
this.resolveDependencies.bind(this),
|
|
826
|
+
true,
|
|
827
|
+
options?.shape,
|
|
828
|
+
);
|
|
829
|
+
|
|
830
|
+
return {
|
|
831
|
+
value,
|
|
832
|
+
id: types.toId(
|
|
833
|
+
convertFromSQLType(
|
|
834
|
+
row[
|
|
835
|
+
getTablePrefixedField(
|
|
836
|
+
selectedTable,
|
|
837
|
+
this.primaryKeyString,
|
|
838
|
+
)
|
|
839
|
+
],
|
|
840
|
+
selectedTable.primaryField!.from!.type,
|
|
841
|
+
),
|
|
662
842
|
),
|
|
663
|
-
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
);
|
|
843
|
+
};
|
|
844
|
+
}),
|
|
845
|
+
);
|
|
667
846
|
|
|
668
|
-
|
|
847
|
+
offset += results.length;
|
|
669
848
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
849
|
+
/* const uniqueIds = new Set(results.map((x) => x.id.primitive));
|
|
850
|
+
if (uniqueIds.size !== results.length) {
|
|
851
|
+
throw new Error("Duplicate ids in result set");
|
|
852
|
+
} */
|
|
674
853
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
854
|
+
if (amount === "all" || results.length < amount) {
|
|
855
|
+
hasMore = false;
|
|
856
|
+
await this.clearupIterator(requestId);
|
|
857
|
+
}
|
|
858
|
+
return results;
|
|
859
|
+
} catch (error) {
|
|
860
|
+
if (this.isClosing()) {
|
|
861
|
+
return closeAsDone();
|
|
862
|
+
}
|
|
863
|
+
throw error;
|
|
678
864
|
}
|
|
679
|
-
return results;
|
|
680
865
|
};
|
|
681
866
|
|
|
682
867
|
const iterator = {
|
|
@@ -701,12 +886,20 @@ export class SQLiteIndex<T extends Record<string, any>>
|
|
|
701
886
|
return results;
|
|
702
887
|
},
|
|
703
888
|
close: () => {
|
|
889
|
+
once = true;
|
|
704
890
|
hasMore = false;
|
|
705
891
|
kept = 0;
|
|
706
892
|
this.clearupIterator(requestId);
|
|
707
893
|
},
|
|
708
894
|
next: (amount: number) => fetch(amount),
|
|
709
895
|
pending: async () => {
|
|
896
|
+
if (this.isClosing()) {
|
|
897
|
+
once = true;
|
|
898
|
+
hasMore = false;
|
|
899
|
+
kept = 0;
|
|
900
|
+
return 0;
|
|
901
|
+
}
|
|
902
|
+
this.assertOpen();
|
|
710
903
|
if (!hasMore) {
|
|
711
904
|
return 0;
|
|
712
905
|
}
|
|
@@ -719,7 +912,12 @@ export class SQLiteIndex<T extends Record<string, any>>
|
|
|
719
912
|
hasMore = kept > 0;
|
|
720
913
|
return kept;
|
|
721
914
|
},
|
|
722
|
-
done: () =>
|
|
915
|
+
done: () => {
|
|
916
|
+
if (this.isClosing()) {
|
|
917
|
+
return true;
|
|
918
|
+
}
|
|
919
|
+
return once ? !hasMore : undefined;
|
|
920
|
+
},
|
|
723
921
|
};
|
|
724
922
|
}
|
|
725
923
|
|
|
@@ -734,31 +932,44 @@ export class SQLiteIndex<T extends Record<string, any>>
|
|
|
734
932
|
}
|
|
735
933
|
|
|
736
934
|
async getSize(): Promise<number> {
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
935
|
+
return this.ifOpen(0, async () => {
|
|
936
|
+
if (this.tables.size === 0) {
|
|
937
|
+
return 0;
|
|
938
|
+
}
|
|
740
939
|
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
940
|
+
/* const stmt = await this.properties.db.prepare(`select count(*) as total from ${this.rootTableName}`);
|
|
941
|
+
const result = await stmt.get()
|
|
942
|
+
stmt.finalize?.();
|
|
943
|
+
return result.total as number */
|
|
944
|
+
return this.count();
|
|
945
|
+
});
|
|
746
946
|
}
|
|
747
947
|
|
|
748
948
|
async del(query: types.DeleteOptions): Promise<types.IdKey[]> {
|
|
749
|
-
return this.
|
|
949
|
+
return this.withWriteIfOpen([], async () => {
|
|
750
950
|
let ret: types.IdKey[] = [];
|
|
751
951
|
let once = false;
|
|
752
952
|
let lastError: Error | undefined = undefined;
|
|
753
953
|
for (const table of this._rootTables) {
|
|
754
954
|
try {
|
|
955
|
+
const planningScope = this.planner.scope(
|
|
956
|
+
new PlannableQuery({
|
|
957
|
+
query: coerceLocalQueries(query.query),
|
|
958
|
+
}),
|
|
959
|
+
);
|
|
755
960
|
const { sql, bindable } = convertDeleteRequestToQuery(
|
|
756
961
|
query,
|
|
757
962
|
this.tables,
|
|
758
963
|
table,
|
|
964
|
+
{
|
|
965
|
+
planner: planningScope,
|
|
966
|
+
},
|
|
759
967
|
);
|
|
968
|
+
await planningScope.beforePrepare();
|
|
760
969
|
const stmt = await this.properties.db.prepare(sql, sql);
|
|
761
|
-
const results: any[] = await
|
|
970
|
+
const results: any[] = await planningScope.perform(async () =>
|
|
971
|
+
stmt.all(bindable),
|
|
972
|
+
);
|
|
762
973
|
|
|
763
974
|
// TODO types
|
|
764
975
|
for (const result of results) {
|
|
@@ -791,89 +1002,104 @@ export class SQLiteIndex<T extends Record<string, any>>
|
|
|
791
1002
|
}
|
|
792
1003
|
|
|
793
1004
|
async sum(query: types.SumOptions): Promise<number | bigint> {
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
1005
|
+
return this.ifOpen(0, async () => {
|
|
1006
|
+
let ret: number | bigint | undefined = undefined;
|
|
1007
|
+
let once = false;
|
|
1008
|
+
let lastError: Error | undefined = undefined;
|
|
797
1009
|
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
1010
|
+
let inlinedName = getInlineTableFieldName(query.key);
|
|
1011
|
+
for (const table of this._rootTables) {
|
|
1012
|
+
try {
|
|
1013
|
+
if (table.fields.find((x) => x.name === inlinedName) == null) {
|
|
1014
|
+
lastError = new MissingFieldError(
|
|
1015
|
+
"Missing field: " +
|
|
1016
|
+
(Array.isArray(query.key) ? query.key : [query.key]).join("."),
|
|
1017
|
+
);
|
|
1018
|
+
continue;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
const planningScope = this.planner.scope(
|
|
1022
|
+
new PlannableQuery({
|
|
1023
|
+
query: coerceLocalQueries(query.query),
|
|
1024
|
+
}),
|
|
805
1025
|
);
|
|
806
|
-
|
|
807
|
-
|
|
1026
|
+
const { sql, bindable } = convertSumRequestToQuery(
|
|
1027
|
+
query,
|
|
1028
|
+
this.tables,
|
|
1029
|
+
table,
|
|
1030
|
+
{
|
|
1031
|
+
planner: planningScope,
|
|
1032
|
+
},
|
|
1033
|
+
);
|
|
1034
|
+
await planningScope.beforePrepare();
|
|
1035
|
+
const stmt = await this.properties.db.prepare(sql, sql);
|
|
1036
|
+
const result = await planningScope.perform(async () =>
|
|
1037
|
+
stmt.get(bindable),
|
|
1038
|
+
);
|
|
1039
|
+
if (result != null) {
|
|
1040
|
+
const value = result.sum as number;
|
|
808
1041
|
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
const result = await stmt.get(bindable);
|
|
816
|
-
if (result != null) {
|
|
817
|
-
const value = result.sum as number;
|
|
818
|
-
|
|
819
|
-
if (ret == null) {
|
|
820
|
-
ret = value;
|
|
821
|
-
} else {
|
|
822
|
-
ret += value;
|
|
1042
|
+
if (ret == null) {
|
|
1043
|
+
ret = value;
|
|
1044
|
+
} else {
|
|
1045
|
+
ret += value;
|
|
1046
|
+
}
|
|
1047
|
+
once = true;
|
|
823
1048
|
}
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
1049
|
+
} catch (error) {
|
|
1050
|
+
if (error instanceof MissingFieldError) {
|
|
1051
|
+
lastError = error;
|
|
1052
|
+
continue;
|
|
1053
|
+
}
|
|
1054
|
+
throw error;
|
|
830
1055
|
}
|
|
831
|
-
throw error;
|
|
832
1056
|
}
|
|
833
|
-
}
|
|
834
1057
|
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
1058
|
+
if (!once) {
|
|
1059
|
+
throw lastError!;
|
|
1060
|
+
}
|
|
838
1061
|
|
|
839
|
-
|
|
1062
|
+
return ret != null ? ret : 0;
|
|
1063
|
+
});
|
|
840
1064
|
}
|
|
841
1065
|
|
|
842
1066
|
async count(request?: types.CountOptions): Promise<number> {
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
1067
|
+
return this.ifOpen(0, async () => {
|
|
1068
|
+
let ret: number = 0;
|
|
1069
|
+
let once = false;
|
|
1070
|
+
let lastError: Error | undefined = undefined;
|
|
1071
|
+
for (const table of this._rootTables) {
|
|
1072
|
+
try {
|
|
1073
|
+
const { sql, bindable } = convertCountRequestToQuery(
|
|
1074
|
+
request,
|
|
1075
|
+
this.tables,
|
|
1076
|
+
table,
|
|
1077
|
+
);
|
|
1078
|
+
const stmt = await this.properties.db.prepare(sql, sql);
|
|
1079
|
+
const result = await stmt.get(bindable);
|
|
1080
|
+
if (result != null) {
|
|
1081
|
+
ret += Number(result.count);
|
|
1082
|
+
once = true;
|
|
1083
|
+
}
|
|
1084
|
+
} catch (error) {
|
|
1085
|
+
if (error instanceof MissingFieldError) {
|
|
1086
|
+
lastError = error;
|
|
1087
|
+
continue;
|
|
1088
|
+
}
|
|
864
1089
|
|
|
865
|
-
|
|
1090
|
+
throw error;
|
|
1091
|
+
}
|
|
866
1092
|
}
|
|
867
|
-
}
|
|
868
1093
|
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
1094
|
+
if (!once) {
|
|
1095
|
+
throw lastError!;
|
|
1096
|
+
}
|
|
1097
|
+
return ret;
|
|
1098
|
+
});
|
|
873
1099
|
}
|
|
874
1100
|
|
|
875
1101
|
get cursorCount(): number {
|
|
876
|
-
return this.
|
|
1102
|
+
return this.closed ? 0 : this._cursor.size;
|
|
877
1103
|
}
|
|
878
1104
|
}
|
|
879
1105
|
|
|
@@ -951,7 +1177,10 @@ export class SQLiteIndices implements types.Indices {
|
|
|
951
1177
|
this.closed = false;
|
|
952
1178
|
|
|
953
1179
|
if (!this.properties.parent) {
|
|
954
|
-
await this.properties.db.
|
|
1180
|
+
const status = await this.properties.db.status();
|
|
1181
|
+
if (status !== "open") {
|
|
1182
|
+
await this.properties.db.open();
|
|
1183
|
+
}
|
|
955
1184
|
}
|
|
956
1185
|
|
|
957
1186
|
for (const scope of this.scopes.values()) {
|