@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
@@ -56,7 +56,9 @@ async function getIgnoreFK(stmt, values) {
56
56
  }
57
57
  }
58
58
  const createBatchInsertSQL = (table, rows) => {
59
- const columns = table.fields.map((field) => escapeColumnName(field.name)).join(", ");
59
+ const columns = table.fields
60
+ .map((field) => escapeColumnName(field.name))
61
+ .join(", ");
60
62
  const rowPlaceholder = `(${table.fields.map(() => "?").join(", ")})`;
61
63
  return `insert into ${table.name} (${columns}) VALUES ${Array.from({
62
64
  length: rows,
@@ -64,6 +66,9 @@ const createBatchInsertSQL = (table, rows) => {
64
66
  .map(() => rowPlaceholder)
65
67
  .join(", ")};`;
66
68
  };
69
+ const createInsertReturningSQL = (table) => `insert into ${table.name} (${table.fields.map((field) => escapeColumnName(field.name)).join(", ")}) VALUES (${table.fields.map((_x) => "?").join(", ")}) RETURNING ${table.primary};`;
70
+ const createInsertKnownIdSQL = (table) => `insert into ${table.name} (${table.fields.map((field) => escapeColumnName(field.name)).join(", ")}) VALUES (${table.fields.map((_x) => "?").join(", ")});`;
71
+ const createReplaceSQL = (table) => `insert or replace into ${table.name} (${table.fields.map((field) => escapeColumnName(field.name)).join(", ")}) VALUES (${table.fields.map((_x) => "?").join(", ")});`;
67
72
  const canUseWithoutRowId = (table) => {
68
73
  if (table.inline || table.primary === false || !table.primaryField) {
69
74
  return false;
@@ -103,12 +108,13 @@ export class SQLiteIndex {
103
108
  primaryKeyString;
104
109
  planner;
105
110
  scopeString;
106
- _rootTables;
107
- _tables;
108
- _cursor; // TODO choose limit better
111
+ _rootTables = [];
112
+ _tables = new Map();
113
+ _cursor = new Map(); // TODO choose limit better
109
114
  cursorPruner;
110
115
  iteratorTimeout;
111
116
  closed = true;
117
+ state = "closed";
112
118
  fkMode;
113
119
  id;
114
120
  constructor(properties, options) {
@@ -128,21 +134,75 @@ export class SQLiteIndex {
128
134
  persisted() {
129
135
  return this.properties.persisted ?? true;
130
136
  }
137
+ static _emptyTables = new Map();
138
+ static _emptyRootTables = [];
139
+ static _emptyCursor = new Map();
140
+ static closedIterator() {
141
+ return {
142
+ all: async () => [],
143
+ close: async () => undefined,
144
+ done: () => true,
145
+ next: async () => [],
146
+ pending: async () => 0,
147
+ };
148
+ }
149
+ async ifOpen(fallback, fn) {
150
+ if (this.isClosing()) {
151
+ return fallback;
152
+ }
153
+ this.assertOpen();
154
+ try {
155
+ return await fn();
156
+ }
157
+ catch (error) {
158
+ if (this.isClosing()) {
159
+ return fallback;
160
+ }
161
+ throw error;
162
+ }
163
+ }
164
+ async withWriteIfOpen(fallback, fn) {
165
+ if (this.isClosing()) {
166
+ return fallback;
167
+ }
168
+ this.assertOpen();
169
+ return this.withWriteBarrier(() => this.ifOpen(fallback, fn));
170
+ }
171
+ assertOpen() {
172
+ if (this.state !== "open") {
173
+ throw new types.NotStartedError();
174
+ }
175
+ }
176
+ isClosing() {
177
+ return this.state === "closing";
178
+ }
179
+ setClosing() {
180
+ this.state = "closing";
181
+ this.closed = true;
182
+ }
183
+ setClosed() {
184
+ this.state = "closed";
185
+ this.closed = true;
186
+ }
187
+ setOpen() {
188
+ this.state = "open";
189
+ this.closed = false;
190
+ }
131
191
  get tables() {
132
192
  if (this.closed) {
133
- throw new types.NotStartedError();
193
+ return SQLiteIndex._emptyTables;
134
194
  }
135
195
  return this._tables;
136
196
  }
137
197
  get rootTables() {
138
198
  if (this.closed) {
139
- throw new types.NotStartedError();
199
+ return SQLiteIndex._emptyRootTables;
140
200
  }
141
201
  return this._rootTables;
142
202
  }
143
203
  get cursor() {
144
204
  if (this.closed) {
145
- throw new types.NotStartedError();
205
+ return SQLiteIndex._emptyCursor;
146
206
  }
147
207
  return this._cursor;
148
208
  }
@@ -166,9 +226,12 @@ export class SQLiteIndex {
166
226
  return this;
167
227
  }
168
228
  async start() {
169
- if (this.closed === false) {
229
+ if (this.state === "open") {
170
230
  return;
171
231
  }
232
+ if (this.state === "closing") {
233
+ throw new types.NotStartedError();
234
+ }
172
235
  if (this.primaryKeyArr == null || this.primaryKeyArr.length === 0) {
173
236
  throw new Error("Not initialized");
174
237
  }
@@ -179,6 +242,8 @@ export class SQLiteIndex {
179
242
  false, undefined, false);
180
243
  this._rootTables = tables.filter((x) => x.parent == null);
181
244
  const allTables = tables;
245
+ const startupStatements = [];
246
+ const startupTableStatements = new Map();
182
247
  for (const table of allTables) {
183
248
  this._tables.set(table.name, table);
184
249
  for (const child of table.children) {
@@ -193,7 +258,23 @@ export class SQLiteIndex {
193
258
  ? " strict, without rowid"
194
259
  : " strict";
195
260
  const sqlCreateTable = `create table if not exists ${table.name} (${[...table.fields, ...table.constraints].map((s) => s.definition).join(", ")})${tableOptions}`;
196
- this.properties.db.exec(sqlCreateTable);
261
+ startupTableStatements.set(table.name, sqlCreateTable);
262
+ startupStatements.push({
263
+ id: putStatementKey(table),
264
+ sql: createInsertReturningSQL(table),
265
+ }, {
266
+ id: insertKnownIdStatementKey(table),
267
+ sql: createInsertKnownIdSQL(table),
268
+ }, {
269
+ id: replaceStatementKey(table),
270
+ sql: createReplaceSQL(table),
271
+ });
272
+ if (table.parent) {
273
+ startupStatements.push({
274
+ id: resolveChildrenStatement(table),
275
+ sql: selectChildren(table),
276
+ });
277
+ }
197
278
  /* const fieldsToIndex = table.fields.filter(
198
279
  (field) =>
199
280
  field.key !== ARRAY_INDEX_COLUMN && field.key !== table.primary,
@@ -226,17 +307,24 @@ export class SQLiteIndex {
226
307
  }
227
308
  }
228
309
  } */
229
- // put and return the id
230
- let sqlPut = `insert into ${table.name} (${table.fields.map((field) => escapeColumnName(field.name)).join(", ")}) VALUES (${table.fields.map((_x) => "?").join(", ")}) RETURNING ${table.primary};`;
231
- // insert without replace when the caller already knows the id is fresh
232
- let sqlInsertKnownId = `insert into ${table.name} (${table.fields.map((field) => escapeColumnName(field.name)).join(", ")}) VALUES (${table.fields.map((_x) => "?").join(", ")});`;
233
- // insert or replace with id already defined
234
- let sqlReplace = `insert or replace into ${table.name} (${table.fields.map((field) => escapeColumnName(field.name)).join(", ")}) VALUES (${table.fields.map((_x) => "?").join(", ")});`;
235
- await this.properties.db.prepare(sqlPut, putStatementKey(table));
236
- await this.properties.db.prepare(sqlInsertKnownId, insertKnownIdStatementKey(table));
237
- await this.properties.db.prepare(sqlReplace, replaceStatementKey(table));
238
- if (table.parent) {
239
- await this.properties.db.prepare(selectChildren(table), resolveChildrenStatement(table));
310
+ }
311
+ if (startupTableStatements.size > 0) {
312
+ const existingTables = await this.getExistingSQLiteObjects("table", [
313
+ ...startupTableStatements.keys(),
314
+ ]);
315
+ const missingTableStatements = [...startupTableStatements.entries()]
316
+ .filter(([tableName]) => !existingTables.has(tableName))
317
+ .map(([, sql]) => sql);
318
+ if (missingTableStatements.length > 0) {
319
+ await this.properties.db.exec(missingTableStatements.join(";"));
320
+ }
321
+ }
322
+ if (this.properties.db.prepareMany) {
323
+ await this.properties.db.prepareMany(startupStatements);
324
+ }
325
+ else {
326
+ for (const statement of startupStatements) {
327
+ await this.properties.db.prepare(statement.sql, statement.id);
240
328
  }
241
329
  }
242
330
  this.cursorPruner = setInterval(() => {
@@ -247,7 +335,22 @@ export class SQLiteIndex {
247
335
  }
248
336
  }
249
337
  }, this.iteratorTimeout);
250
- this.closed = false;
338
+ this.setOpen();
339
+ }
340
+ async getExistingSQLiteObjects(type, names) {
341
+ if (names.length === 0) {
342
+ return new Set();
343
+ }
344
+ const sql = `select name from sqlite_master where type = ? and name in (${names
345
+ .map(() => "?")
346
+ .join(", ")})`;
347
+ const statement = this.properties.db.statements.get(sql) ||
348
+ (await this.properties.db.prepare(sql, sql));
349
+ const rows = await statement.all([type, ...names]);
350
+ await statement.reset?.();
351
+ return new Set(rows
352
+ .map((row) => row.name)
353
+ .filter((name) => typeof name === "string"));
251
354
  }
252
355
  async clearStatements() {
253
356
  if ((await this.properties.db.status()) === "closed") {
@@ -256,54 +359,86 @@ export class SQLiteIndex {
256
359
  }
257
360
  }
258
361
  async stop() {
259
- if (this.closed) {
362
+ if (this.state === "closed") {
260
363
  return;
261
364
  }
262
- this.closed = true;
263
- clearInterval(this.cursorPruner);
264
- await this.clearStatements();
265
- this._tables.clear();
266
- for (const [k, _v] of this._cursor) {
267
- await this.clearupIterator(k);
268
- }
269
- await this.planner.stop();
270
- }
271
- async drop() {
272
- if (!this.closed) {
273
- this.closed = true;
365
+ if (this.state === "closing") {
366
+ await this._writeBarrier.catch(() => undefined);
367
+ return;
274
368
  }
275
- if (this.cursorPruner != null) {
369
+ this.setClosing();
370
+ try {
276
371
  clearInterval(this.cursorPruner);
277
- this.cursorPruner = undefined;
372
+ await this._writeBarrier.catch(() => undefined);
373
+ await this.clearStatements();
374
+ this._tables?.clear();
375
+ if (this._cursor) {
376
+ for (const [k, _v] of this._cursor) {
377
+ await this.clearupIterator(k);
378
+ }
379
+ }
380
+ await this.planner.stop();
278
381
  }
279
- const status = await this.properties.db.status?.();
280
- if (status === "closed") {
281
- this._tables.clear();
282
- return;
382
+ finally {
383
+ this.setClosed();
283
384
  }
284
- await this.clearStatements();
285
- // drop root table and cascade
286
- // drop table faster by dropping constraints first
287
- for (const table of this._rootTables) {
288
- await this.properties.db.exec(`drop table if exists ${table.name}`);
385
+ }
386
+ async drop() {
387
+ const wasOpen = this.state === "open";
388
+ if (wasOpen) {
389
+ this.setClosing();
289
390
  }
290
- this._tables.clear();
291
- for (const [k, _v] of this._cursor) {
292
- await this.clearupIterator(k);
391
+ try {
392
+ if (this.cursorPruner != null) {
393
+ clearInterval(this.cursorPruner);
394
+ this.cursorPruner = undefined;
395
+ }
396
+ if (wasOpen) {
397
+ await this._writeBarrier.catch(() => undefined);
398
+ }
399
+ const status = await this.properties.db.status?.();
400
+ if (status === "closed") {
401
+ this._tables?.clear();
402
+ return;
403
+ }
404
+ await this.clearStatements();
405
+ // drop root table and cascade
406
+ // drop table faster by dropping constraints first
407
+ if (this._rootTables) {
408
+ for (const table of this._rootTables) {
409
+ await this.properties.db.exec(`drop table if exists ${table.name}`);
410
+ }
411
+ }
412
+ this._tables?.clear();
413
+ if (this._cursor) {
414
+ for (const [k, _v] of this._cursor) {
415
+ await this.clearupIterator(k);
416
+ }
417
+ }
418
+ await this.planner.stop();
419
+ }
420
+ finally {
421
+ this.setClosed();
293
422
  }
294
- await this.planner.stop();
295
423
  }
296
424
  async resolveDependencies(parentId, table) {
297
- const stmt = this.properties.db.statements.get(resolveChildrenStatement(table));
425
+ const stmt = await this.getOrPrepareStatement(resolveChildrenStatement(table), selectChildren(table));
298
426
  const results = await stmt.all([parentId]);
299
427
  await stmt.reset?.();
300
428
  return results;
301
429
  }
430
+ async getOrPrepareStatement(key, sql) {
431
+ const existing = this.properties.db.statements.get(key);
432
+ if (existing) {
433
+ return existing;
434
+ }
435
+ return this.properties.db.prepare(sql, key);
436
+ }
302
437
  async get(id, options) {
303
- for (const table of this._rootTables) {
304
- const { join: joinMap, selects } = selectAllFieldsFromTable(table, options?.shape);
305
- const sql = `${generateSelectQuery(table, selects)} ${buildJoin(joinMap).join} where ${table.name}.${this.primaryKeyString} = ? limit 1`;
306
- try {
438
+ return this.ifOpen(undefined, async () => {
439
+ for (const table of this._rootTables) {
440
+ const { join: joinMap, selects } = selectAllFieldsFromTable(table, options?.shape);
441
+ const sql = `${generateSelectQuery(table, selects)} ${buildJoin(joinMap).join} where ${table.name}.${this.primaryKeyString} = ? limit 1`;
307
442
  const stmt = await this.properties.db.prepare(sql, sql);
308
443
  const rows = await stmt.get([
309
444
  table.primaryField?.from?.type
@@ -318,17 +453,11 @@ export class SQLiteIndex {
318
453
  id,
319
454
  };
320
455
  }
321
- catch (error) {
322
- if (this.closed) {
323
- throw new types.NotStartedError();
324
- }
325
- throw error;
326
- }
327
- }
328
- return undefined;
456
+ return undefined;
457
+ });
329
458
  }
330
459
  async put(value, _id, options) {
331
- return this.withWriteBarrier(async () => {
460
+ return this.withWriteIfOpen(undefined, async () => {
332
461
  const classOfValue = value.constructor;
333
462
  return insert(async (values, table) => {
334
463
  let preId = values[table.primaryIndex];
@@ -337,7 +466,7 @@ export class SQLiteIndex {
337
466
  if (preId != null) {
338
467
  const shouldReplace = options?.replace ?? true;
339
468
  if (!shouldReplace) {
340
- statement = this.properties.db.statements.get(insertKnownIdStatementKey(table));
469
+ statement = await this.getOrPrepareStatement(insertKnownIdStatementKey(table), createInsertKnownIdSQL(table));
341
470
  try {
342
471
  this.fkMode === "race-tolerant"
343
472
  ? await runIgnoreFK(statement, values)
@@ -348,14 +477,14 @@ export class SQLiteIndex {
348
477
  throw error;
349
478
  }
350
479
  await statement.reset?.();
351
- statement = this.properties.db.statements.get(replaceStatementKey(table));
480
+ statement = await this.getOrPrepareStatement(replaceStatementKey(table), createReplaceSQL(table));
352
481
  this.fkMode === "race-tolerant"
353
482
  ? await runIgnoreFK(statement, values)
354
483
  : await statement.run(values);
355
484
  }
356
485
  }
357
486
  else {
358
- statement = this.properties.db.statements.get(replaceStatementKey(table));
487
+ statement = await this.getOrPrepareStatement(replaceStatementKey(table), createReplaceSQL(table));
359
488
  this.fkMode === "race-tolerant"
360
489
  ? await runIgnoreFK(statement, values)
361
490
  : await statement.run(values);
@@ -363,7 +492,7 @@ export class SQLiteIndex {
363
492
  return preId;
364
493
  }
365
494
  else {
366
- statement = this.properties.db.statements.get(putStatementKey(table));
495
+ statement = await this.getOrPrepareStatement(putStatementKey(table), createInsertReturningSQL(table));
367
496
  const out = this.fkMode === "race-tolerant"
368
497
  ? await getIgnoreFK(statement, values)
369
498
  : await statement.get(values);
@@ -397,6 +526,10 @@ export class SQLiteIndex {
397
526
  });
398
527
  }
399
528
  iterate(request, options) {
529
+ if (this.isClosing()) {
530
+ return SQLiteIndex.closedIterator();
531
+ }
532
+ this.assertOpen();
400
533
  // create a sql statement where the offset and the limit id dynamic and can be updated
401
534
  // TODO don't use offset but sort and limit 'next' calls by the last value of the sort
402
535
  /* const totalCountKey = "count"; */
@@ -417,54 +550,72 @@ export class SQLiteIndex {
417
550
  let planningScope;
418
551
  /* let totalCount: undefined | number = undefined; */
419
552
  const fetch = async (amount) => {
420
- kept = undefined;
421
- if (!once) {
422
- planningScope = this.planner.scope(normalizedQuery);
423
- let { sql, bindable: toBind } = convertSearchRequestToQuery(normalizedQuery, this.tables, this._rootTables, {
424
- planner: planningScope,
425
- shape: options?.shape,
426
- fetchAll: amount === "all", // if we are to fetch all, we dont need stable sorting
427
- });
428
- sqlFetch = sql;
429
- bindable = toBind;
430
- await planningScope.beforePrepare();
431
- stmt = await this.properties.db.prepare(sqlFetch, sqlFetch);
432
- // Bump timeout timer
433
- iterator.expire = Date.now() + this.iteratorTimeout;
553
+ const closeAsDone = () => {
554
+ once = true;
555
+ hasMore = false;
556
+ kept = 0;
557
+ return [];
558
+ };
559
+ if (this.isClosing()) {
560
+ return closeAsDone();
434
561
  }
435
- once = true;
436
- const allResults = await planningScope.perform(async () => {
437
- const allResults = await stmt.all([
562
+ this.assertOpen();
563
+ try {
564
+ kept = undefined;
565
+ if (!once) {
566
+ planningScope = this.planner.scope(normalizedQuery);
567
+ let { sql, bindable: toBind } = convertSearchRequestToQuery(normalizedQuery, this.tables, this._rootTables, {
568
+ planner: planningScope,
569
+ shape: options?.shape,
570
+ fetchAll: amount === "all", // if we are to fetch all, we dont need stable sorting
571
+ });
572
+ sqlFetch = sql;
573
+ bindable = toBind;
574
+ await planningScope.beforePrepare();
575
+ stmt = await this.properties.db.prepare(sqlFetch, sqlFetch);
576
+ // Bump timeout timer
577
+ iterator.expire = Date.now() + this.iteratorTimeout;
578
+ }
579
+ once = true;
580
+ const allResults = await planningScope.perform(async () => {
581
+ const allResults = await stmt.all([
582
+ ...bindable,
583
+ ...(amount !== "all" ? [amount, offset] : []),
584
+ ]);
585
+ return allResults;
586
+ });
587
+ /* const allResults: Record<string, any>[] = await stmt.all([
438
588
  ...bindable,
439
- ...(amount !== "all" ? [amount, offset] : []),
589
+ ...(amount !== "all" ? [amount,
590
+ offset] : [])
440
591
  ]);
441
- return allResults;
442
- });
443
- /* const allResults: Record<string, any>[] = await stmt.all([
444
- ...bindable,
445
- ...(amount !== "all" ? [amount,
446
- offset] : [])
447
- ]);
448
- */
449
- let results = await Promise.all(allResults.map(async (row) => {
450
- let selectedTable = this._rootTables.find((table) => row[getTablePrefixedField(table, this.primaryKeyString)] !=
451
- null);
452
- const value = await resolveInstanceFromValue(row, this.tables, selectedTable, this.resolveDependencies.bind(this), true, options?.shape);
453
- return {
454
- value,
455
- id: types.toId(convertFromSQLType(row[getTablePrefixedField(selectedTable, this.primaryKeyString)], selectedTable.primaryField.from.type)),
456
- };
457
- }));
458
- offset += results.length;
459
- /* const uniqueIds = new Set(results.map((x) => x.id.primitive));
460
- if (uniqueIds.size !== results.length) {
461
- throw new Error("Duplicate ids in result set");
462
- } */
463
- if (amount === "all" || results.length < amount) {
464
- hasMore = false;
465
- await this.clearupIterator(requestId);
592
+ */
593
+ let results = await Promise.all(allResults.map(async (row) => {
594
+ let selectedTable = this._rootTables.find((table) => row[getTablePrefixedField(table, this.primaryKeyString)] !=
595
+ null);
596
+ const value = await resolveInstanceFromValue(row, this.tables, selectedTable, this.resolveDependencies.bind(this), true, options?.shape);
597
+ return {
598
+ value,
599
+ id: types.toId(convertFromSQLType(row[getTablePrefixedField(selectedTable, this.primaryKeyString)], selectedTable.primaryField.from.type)),
600
+ };
601
+ }));
602
+ offset += results.length;
603
+ /* const uniqueIds = new Set(results.map((x) => x.id.primitive));
604
+ if (uniqueIds.size !== results.length) {
605
+ throw new Error("Duplicate ids in result set");
606
+ } */
607
+ if (amount === "all" || results.length < amount) {
608
+ hasMore = false;
609
+ await this.clearupIterator(requestId);
610
+ }
611
+ return results;
612
+ }
613
+ catch (error) {
614
+ if (this.isClosing()) {
615
+ return closeAsDone();
616
+ }
617
+ throw error;
466
618
  }
467
- return results;
468
619
  };
469
620
  const iterator = {
470
621
  fetch,
@@ -487,12 +638,20 @@ export class SQLiteIndex {
487
638
  return results;
488
639
  },
489
640
  close: () => {
641
+ once = true;
490
642
  hasMore = false;
491
643
  kept = 0;
492
644
  this.clearupIterator(requestId);
493
645
  },
494
646
  next: (amount) => fetch(amount),
495
647
  pending: async () => {
648
+ if (this.isClosing()) {
649
+ once = true;
650
+ hasMore = false;
651
+ kept = 0;
652
+ return 0;
653
+ }
654
+ this.assertOpen();
496
655
  if (!hasMore) {
497
656
  return 0;
498
657
  }
@@ -504,7 +663,12 @@ export class SQLiteIndex {
504
663
  hasMore = kept > 0;
505
664
  return kept;
506
665
  },
507
- done: () => (once ? !hasMore : undefined),
666
+ done: () => {
667
+ if (this.isClosing()) {
668
+ return true;
669
+ }
670
+ return once ? !hasMore : undefined;
671
+ },
508
672
  };
509
673
  }
510
674
  async clearupIterator(id) {
@@ -517,25 +681,33 @@ export class SQLiteIndex {
517
681
  this._cursor.delete(id);
518
682
  }
519
683
  async getSize() {
520
- if (this.tables.size === 0) {
521
- return 0;
522
- }
523
- /* const stmt = await this.properties.db.prepare(`select count(*) as total from ${this.rootTableName}`);
524
- const result = await stmt.get()
525
- stmt.finalize?.();
526
- return result.total as number */
527
- return this.count();
684
+ return this.ifOpen(0, async () => {
685
+ if (this.tables.size === 0) {
686
+ return 0;
687
+ }
688
+ /* const stmt = await this.properties.db.prepare(`select count(*) as total from ${this.rootTableName}`);
689
+ const result = await stmt.get()
690
+ stmt.finalize?.();
691
+ return result.total as number */
692
+ return this.count();
693
+ });
528
694
  }
529
695
  async del(query) {
530
- return this.withWriteBarrier(async () => {
696
+ return this.withWriteIfOpen([], async () => {
531
697
  let ret = [];
532
698
  let once = false;
533
699
  let lastError = undefined;
534
700
  for (const table of this._rootTables) {
535
701
  try {
536
- const { sql, bindable } = convertDeleteRequestToQuery(query, this.tables, table);
702
+ const planningScope = this.planner.scope(new PlannableQuery({
703
+ query: coerceLocalQueries(query.query),
704
+ }));
705
+ const { sql, bindable } = convertDeleteRequestToQuery(query, this.tables, table, {
706
+ planner: planningScope,
707
+ });
708
+ await planningScope.beforePrepare();
537
709
  const stmt = await this.properties.db.prepare(sql, sql);
538
- const results = await stmt.all(bindable);
710
+ const results = await planningScope.perform(async () => stmt.all(bindable));
539
711
  // TODO types
540
712
  for (const result of results) {
541
713
  ret.push(types.toId(convertFromSQLType(result[table.primary], table.primaryField.from.type)));
@@ -557,73 +729,83 @@ export class SQLiteIndex {
557
729
  });
558
730
  }
559
731
  async sum(query) {
560
- let ret = undefined;
561
- let once = false;
562
- let lastError = undefined;
563
- let inlinedName = getInlineTableFieldName(query.key);
564
- for (const table of this._rootTables) {
565
- try {
566
- if (table.fields.find((x) => x.name === inlinedName) == null) {
567
- lastError = new MissingFieldError("Missing field: " +
568
- (Array.isArray(query.key) ? query.key : [query.key]).join("."));
569
- continue;
570
- }
571
- const { sql, bindable } = convertSumRequestToQuery(query, this.tables, table);
572
- const stmt = await this.properties.db.prepare(sql, sql);
573
- const result = await stmt.get(bindable);
574
- if (result != null) {
575
- const value = result.sum;
576
- if (ret == null) {
577
- ret = value;
732
+ return this.ifOpen(0, async () => {
733
+ let ret = undefined;
734
+ let once = false;
735
+ let lastError = undefined;
736
+ let inlinedName = getInlineTableFieldName(query.key);
737
+ for (const table of this._rootTables) {
738
+ try {
739
+ if (table.fields.find((x) => x.name === inlinedName) == null) {
740
+ lastError = new MissingFieldError("Missing field: " +
741
+ (Array.isArray(query.key) ? query.key : [query.key]).join("."));
742
+ continue;
578
743
  }
579
- else {
580
- ret += value;
744
+ const planningScope = this.planner.scope(new PlannableQuery({
745
+ query: coerceLocalQueries(query.query),
746
+ }));
747
+ const { sql, bindable } = convertSumRequestToQuery(query, this.tables, table, {
748
+ planner: planningScope,
749
+ });
750
+ await planningScope.beforePrepare();
751
+ const stmt = await this.properties.db.prepare(sql, sql);
752
+ const result = await planningScope.perform(async () => stmt.get(bindable));
753
+ if (result != null) {
754
+ const value = result.sum;
755
+ if (ret == null) {
756
+ ret = value;
757
+ }
758
+ else {
759
+ ret += value;
760
+ }
761
+ once = true;
581
762
  }
582
- once = true;
583
763
  }
584
- }
585
- catch (error) {
586
- if (error instanceof MissingFieldError) {
587
- lastError = error;
588
- continue;
764
+ catch (error) {
765
+ if (error instanceof MissingFieldError) {
766
+ lastError = error;
767
+ continue;
768
+ }
769
+ throw error;
589
770
  }
590
- throw error;
591
771
  }
592
- }
593
- if (!once) {
594
- throw lastError;
595
- }
596
- return ret != null ? ret : 0;
772
+ if (!once) {
773
+ throw lastError;
774
+ }
775
+ return ret != null ? ret : 0;
776
+ });
597
777
  }
598
778
  async count(request) {
599
- let ret = 0;
600
- let once = false;
601
- let lastError = undefined;
602
- for (const table of this._rootTables) {
603
- try {
604
- const { sql, bindable } = convertCountRequestToQuery(request, this.tables, table);
605
- const stmt = await this.properties.db.prepare(sql, sql);
606
- const result = await stmt.get(bindable);
607
- if (result != null) {
608
- ret += Number(result.count);
609
- once = true;
779
+ return this.ifOpen(0, async () => {
780
+ let ret = 0;
781
+ let once = false;
782
+ let lastError = undefined;
783
+ for (const table of this._rootTables) {
784
+ try {
785
+ const { sql, bindable } = convertCountRequestToQuery(request, this.tables, table);
786
+ const stmt = await this.properties.db.prepare(sql, sql);
787
+ const result = await stmt.get(bindable);
788
+ if (result != null) {
789
+ ret += Number(result.count);
790
+ once = true;
791
+ }
610
792
  }
611
- }
612
- catch (error) {
613
- if (error instanceof MissingFieldError) {
614
- lastError = error;
615
- continue;
793
+ catch (error) {
794
+ if (error instanceof MissingFieldError) {
795
+ lastError = error;
796
+ continue;
797
+ }
798
+ throw error;
616
799
  }
617
- throw error;
618
800
  }
619
- }
620
- if (!once) {
621
- throw lastError;
622
- }
623
- return ret;
801
+ if (!once) {
802
+ throw lastError;
803
+ }
804
+ return ret;
805
+ });
624
806
  }
625
807
  get cursorCount() {
626
- return this.cursor.size;
808
+ return this.closed ? 0 : this._cursor.size;
627
809
  }
628
810
  }
629
811
  export class SQLiteIndices {
@@ -683,7 +865,10 @@ export class SQLiteIndices {
683
865
  async start() {
684
866
  this.closed = false;
685
867
  if (!this.properties.parent) {
686
- await this.properties.db.open();
868
+ const status = await this.properties.db.status();
869
+ if (status !== "open") {
870
+ await this.properties.db.open();
871
+ }
687
872
  }
688
873
  for (const scope of this.scopes.values()) {
689
874
  await scope.start();