@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.
Files changed (41) hide show
  1. package/dist/assets/sqlite3/sqlite3.worker.min.js +6 -1
  2. package/dist/benchmark/query-planner.d.ts +2 -0
  3. package/dist/benchmark/query-planner.d.ts.map +1 -0
  4. package/dist/benchmark/query-planner.js +97 -0
  5. package/dist/benchmark/query-planner.js.map +1 -0
  6. package/dist/index.min.js +535 -183
  7. package/dist/index.min.js.map +3 -3
  8. package/dist/src/engine.d.ts +15 -4
  9. package/dist/src/engine.d.ts.map +1 -1
  10. package/dist/src/engine.js +364 -179
  11. package/dist/src/engine.js.map +1 -1
  12. package/dist/src/query-planner.d.ts +20 -0
  13. package/dist/src/query-planner.d.ts.map +1 -1
  14. package/dist/src/query-planner.js +191 -21
  15. package/dist/src/query-planner.js.map +1 -1
  16. package/dist/src/schema.d.ts +6 -2
  17. package/dist/src/schema.d.ts.map +1 -1
  18. package/dist/src/schema.js +11 -8
  19. package/dist/src/schema.js.map +1 -1
  20. package/dist/src/sqlite3-messages.worker.d.ts +8 -1
  21. package/dist/src/sqlite3-messages.worker.d.ts.map +1 -1
  22. package/dist/src/sqlite3-messages.worker.js.map +1 -1
  23. package/dist/src/sqlite3.browser.d.ts.map +1 -1
  24. package/dist/src/sqlite3.browser.js +21 -0
  25. package/dist/src/sqlite3.browser.js.map +1 -1
  26. package/dist/src/sqlite3.wasm.d.ts.map +1 -1
  27. package/dist/src/sqlite3.wasm.js +4 -1
  28. package/dist/src/sqlite3.wasm.js.map +1 -1
  29. package/dist/src/sqlite3.worker.js +6 -0
  30. package/dist/src/sqlite3.worker.js.map +1 -1
  31. package/dist/src/types.d.ts +4 -0
  32. package/dist/src/types.d.ts.map +1 -1
  33. package/package.json +6 -5
  34. package/src/engine.ts +464 -235
  35. package/src/query-planner.ts +247 -22
  36. package/src/schema.ts +21 -4
  37. package/src/sqlite3-messages.worker.ts +6 -0
  38. package/src/sqlite3.browser.ts +33 -0
  39. package/src/sqlite3.wasm.ts +4 -1
  40. package/src/sqlite3.worker.ts +6 -0
  41. 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.map((field) => escapeColumnName(field.name)).join(", ");
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!: Table[];
150
- private _tables!: Map<string, Table>;
151
- private _cursor!: Map<
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
- >; // TODO choose limit better
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
- throw new types.NotStartedError();
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
- throw new types.NotStartedError();
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
- throw new types.NotStartedError();
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.closed === false) {
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
- this.properties.db.exec(sqlCreateTable);
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
- // put and return the id
322
- let sqlPut = `insert into ${table.name} (${table.fields.map((field) => escapeColumnName(field.name)).join(", ")}) VALUES (${table.fields.map((_x) => "?").join(", ")}) RETURNING ${table.primary};`;
323
-
324
- // insert without replace when the caller already knows the id is fresh
325
- let sqlInsertKnownId = `insert into ${table.name} (${table.fields.map((field) => escapeColumnName(field.name)).join(", ")}) VALUES (${table.fields.map((_x) => "?").join(", ")});`;
326
-
327
- // insert or replace with id already defined
328
- let sqlReplace = `insert or replace into ${table.name} (${table.fields.map((field) => escapeColumnName(field.name)).join(", ")}) VALUES (${table.fields.map((_x) => "?").join(", ")});`;
329
-
330
- await this.properties.db.prepare(sqlPut, putStatementKey(table));
331
- await this.properties.db.prepare(
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
- if (table.parent) {
338
- await this.properties.db.prepare(
339
- selectChildren(table),
340
- resolveChildrenStatement(table),
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.closed = false;
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.closed = true;
369
- clearInterval(this.cursorPruner!);
494
+ if (this.state === "closing") {
495
+ await this._writeBarrier.catch(() => undefined);
496
+ return;
497
+ }
498
+ this.setClosing();
370
499
 
371
- await this.clearStatements();
500
+ try {
501
+ clearInterval(this.cursorPruner!);
372
502
 
373
- this._tables.clear();
503
+ await this._writeBarrier.catch(() => undefined);
374
504
 
375
- for (const [k, _v] of this._cursor) {
376
- await this.clearupIterator(k);
377
- }
505
+ await this.clearStatements();
506
+
507
+ this._tables?.clear();
378
508
 
379
- await this.planner.stop();
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
- if (!this.closed) {
384
- this.closed = true;
522
+ const wasOpen = this.state === "open";
523
+ if (wasOpen) {
524
+ this.setClosing();
385
525
  }
386
526
 
387
- if (this.cursorPruner != null) {
388
- clearInterval(this.cursorPruner);
389
- this.cursorPruner = undefined;
390
- }
527
+ try {
528
+ if (this.cursorPruner != null) {
529
+ clearInterval(this.cursorPruner);
530
+ this.cursorPruner = undefined;
531
+ }
391
532
 
392
- const status = await this.properties.db.status?.();
393
- if (status === "closed") {
394
- this._tables.clear();
395
- return;
396
- }
533
+ if (wasOpen) {
534
+ await this._writeBarrier.catch(() => undefined);
535
+ }
397
536
 
398
- await this.clearStatements();
537
+ const status = await this.properties.db.status?.();
538
+ if (status === "closed") {
539
+ this._tables?.clear();
540
+ return;
541
+ }
399
542
 
400
- // drop root table and cascade
401
- // drop table faster by dropping constraints first
543
+ await this.clearStatements();
402
544
 
403
- for (const table of this._rootTables) {
404
- await this.properties.db.exec(`drop table if exists ${table.name}`);
405
- }
545
+ // drop root table and cascade
546
+ // drop table faster by dropping constraints first
406
547
 
407
- this._tables.clear();
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
- for (const [k, _v] of this._cursor) {
410
- await this.clearupIterator(k);
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.properties.db.statements.get(
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
- for (const table of this._rootTables) {
431
- const { join: joinMap, selects } = selectAllFieldsFromTable(
432
- table,
433
- options?.shape,
434
- );
435
- const sql = `${generateSelectQuery(table, selects)} ${buildJoin(joinMap).join} where ${table.name}.${this.primaryKeyString} = ? limit 1`;
436
- try {
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
- return undefined;
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.withWriteBarrier(async () => {
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.properties.db.statements.get(
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.properties.db.statements.get(
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.properties.db.statements.get(
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.properties.db.statements.get(
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
- kept = undefined;
595
- if (!once) {
596
- planningScope = this.planner.scope(normalizedQuery);
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
- let { sql, bindable: toBind } = convertSearchRequestToQuery(
599
- normalizedQuery,
600
- this.tables,
601
- this._rootTables,
602
- {
603
- planner: planningScope,
604
- shape: options?.shape,
605
- fetchAll: amount === "all", // if we are to fetch all, we dont need stable sorting
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
- sqlFetch = sql;
610
- bindable = toBind;
785
+ sqlFetch = sql;
786
+ bindable = toBind;
611
787
 
612
- await planningScope.beforePrepare();
788
+ await planningScope.beforePrepare();
613
789
 
614
- stmt = await this.properties.db.prepare(sqlFetch, sqlFetch);
790
+ stmt = await this.properties.db.prepare(sqlFetch, sqlFetch);
615
791
 
616
- // Bump timeout timer
617
- iterator.expire = Date.now() + this.iteratorTimeout;
618
- }
792
+ // Bump timeout timer
793
+ iterator.expire = Date.now() + this.iteratorTimeout;
794
+ }
795
+
796
+ once = true;
619
797
 
620
- once = true;
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
- const allResults = await planningScope.perform(async () => {
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, offset] : []),
808
+ ...(amount !== "all" ? [amount,
809
+ offset] : [])
626
810
  ]);
627
- return allResults;
628
- });
629
-
630
- /* const allResults: Record<string, any>[] = await stmt.all([
631
- ...bindable,
632
- ...(amount !== "all" ? [amount,
633
- offset] : [])
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
- return {
655
- value,
656
- id: types.toId(
657
- convertFromSQLType(
658
- row[
659
- getTablePrefixedField(selectedTable, this.primaryKeyString)
660
- ],
661
- selectedTable.primaryField!.from!.type,
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
- offset += results.length;
847
+ offset += results.length;
669
848
 
670
- /* const uniqueIds = new Set(results.map((x) => x.id.primitive));
671
- if (uniqueIds.size !== results.length) {
672
- throw new Error("Duplicate ids in result set");
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
- if (amount === "all" || results.length < amount) {
676
- hasMore = false;
677
- await this.clearupIterator(requestId);
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: () => (once ? !hasMore : undefined),
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
- if (this.tables.size === 0) {
738
- return 0;
739
- }
935
+ return this.ifOpen(0, async () => {
936
+ if (this.tables.size === 0) {
937
+ return 0;
938
+ }
740
939
 
741
- /* const stmt = await this.properties.db.prepare(`select count(*) as total from ${this.rootTableName}`);
742
- const result = await stmt.get()
743
- stmt.finalize?.();
744
- return result.total as number */
745
- return this.count();
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.withWriteBarrier(async () => {
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 stmt.all(bindable);
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
- let ret: number | bigint | undefined = undefined;
795
- let once = false;
796
- let lastError: Error | undefined = undefined;
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
- let inlinedName = getInlineTableFieldName(query.key);
799
- for (const table of this._rootTables) {
800
- try {
801
- if (table.fields.find((x) => x.name === inlinedName) == null) {
802
- lastError = new MissingFieldError(
803
- "Missing field: " +
804
- (Array.isArray(query.key) ? query.key : [query.key]).join("."),
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
- continue;
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
- const { sql, bindable } = convertSumRequestToQuery(
810
- query,
811
- this.tables,
812
- table,
813
- );
814
- const stmt = await this.properties.db.prepare(sql, sql);
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
- once = true;
825
- }
826
- } catch (error) {
827
- if (error instanceof MissingFieldError) {
828
- lastError = error;
829
- continue;
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
- if (!once) {
836
- throw lastError!;
837
- }
1058
+ if (!once) {
1059
+ throw lastError!;
1060
+ }
838
1061
 
839
- return ret != null ? ret : 0;
1062
+ return ret != null ? ret : 0;
1063
+ });
840
1064
  }
841
1065
 
842
1066
  async count(request?: types.CountOptions): Promise<number> {
843
- let ret: number = 0;
844
- let once = false;
845
- let lastError: Error | undefined = undefined;
846
- for (const table of this._rootTables) {
847
- try {
848
- const { sql, bindable } = convertCountRequestToQuery(
849
- request,
850
- this.tables,
851
- table,
852
- );
853
- const stmt = await this.properties.db.prepare(sql, sql);
854
- const result = await stmt.get(bindable);
855
- if (result != null) {
856
- ret += Number(result.count);
857
- once = true;
858
- }
859
- } catch (error) {
860
- if (error instanceof MissingFieldError) {
861
- lastError = error;
862
- continue;
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
- throw error;
1090
+ throw error;
1091
+ }
866
1092
  }
867
- }
868
1093
 
869
- if (!once) {
870
- throw lastError!;
871
- }
872
- return ret;
1094
+ if (!once) {
1095
+ throw lastError!;
1096
+ }
1097
+ return ret;
1098
+ });
873
1099
  }
874
1100
 
875
1101
  get cursorCount(): number {
876
- return this.cursor.size;
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.open();
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()) {