@peerbit/indexer-sqlite3 1.1.4 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/peerbit/sqlite3-bundler-friendly.mjs +7 -7
  2. package/dist/peerbit/sqlite3-node.mjs +7 -7
  3. package/dist/peerbit/sqlite3.js +7 -7
  4. package/dist/peerbit/sqlite3.min.js +688 -168
  5. package/dist/peerbit/sqlite3.mjs +7 -7
  6. package/dist/peerbit/sqlite3.wasm +0 -0
  7. package/dist/peerbit/sqlite3.worker.min.js +19 -5
  8. package/dist/src/engine.d.ts +4 -1
  9. package/dist/src/engine.d.ts.map +1 -1
  10. package/dist/src/engine.js +125 -48
  11. package/dist/src/engine.js.map +1 -1
  12. package/dist/src/query-planner.d.ts +47 -0
  13. package/dist/src/query-planner.d.ts.map +1 -0
  14. package/dist/src/query-planner.js +290 -0
  15. package/dist/src/query-planner.js.map +1 -0
  16. package/dist/src/schema.d.ts +31 -7
  17. package/dist/src/schema.d.ts.map +1 -1
  18. package/dist/src/schema.js +370 -123
  19. package/dist/src/schema.js.map +1 -1
  20. package/dist/src/sqlite3-messages.worker.d.ts +4 -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 +7 -0
  25. package/dist/src/sqlite3.browser.js.map +1 -1
  26. package/dist/src/sqlite3.d.ts.map +1 -1
  27. package/dist/src/sqlite3.js +24 -14
  28. package/dist/src/sqlite3.js.map +1 -1
  29. package/dist/src/sqlite3.wasm.d.ts +1 -0
  30. package/dist/src/sqlite3.wasm.d.ts.map +1 -1
  31. package/dist/src/sqlite3.wasm.js +9 -1
  32. package/dist/src/sqlite3.wasm.js.map +1 -1
  33. package/dist/src/sqlite3.worker.js +7 -0
  34. package/dist/src/sqlite3.worker.js.map +1 -1
  35. package/dist/src/types.d.ts +1 -0
  36. package/dist/src/types.d.ts.map +1 -1
  37. package/package.json +4 -4
  38. package/src/engine.ts +143 -68
  39. package/src/query-planner.ts +334 -0
  40. package/src/schema.ts +519 -164
  41. package/src/sqlite3-messages.worker.ts +5 -0
  42. package/src/sqlite3.browser.ts +8 -0
  43. package/src/sqlite3.ts +24 -13
  44. package/src/sqlite3.wasm.ts +11 -1
  45. package/src/sqlite3.worker.ts +6 -1
  46. package/src/types.ts +1 -0
package/src/engine.ts CHANGED
@@ -7,6 +7,7 @@ import type {
7
7
  } from "@peerbit/indexer-interface";
8
8
  import * as types from "@peerbit/indexer-interface";
9
9
  import { v4 as uuid } from "uuid";
10
+ import { PlannableQuery, QueryPlanner } from "./query-planner.js";
10
11
  import {
11
12
  MissingFieldError,
12
13
  type Table,
@@ -14,6 +15,9 @@ import {
14
15
  convertCountRequestToQuery,
15
16
  convertDeleteRequestToQuery,
16
17
  convertFromSQLType,
18
+ /* convertFromSQLType, */
19
+
20
+ /* convertFromSQLType, */
17
21
  convertSearchRequestToQuery,
18
22
  /* getTableName, */
19
23
  convertSumRequestToQuery,
@@ -45,6 +49,7 @@ export class SQLLiteIndex<T extends Record<string, any>>
45
49
  {
46
50
  primaryKeyArr!: string[];
47
51
  primaryKeyString!: string;
52
+ planner: QueryPlanner;
48
53
  private scopeString?: string;
49
54
  private _rootTables!: Table[];
50
55
  private _tables!: Map<string, Table>;
@@ -53,9 +58,10 @@ export class SQLLiteIndex<T extends Record<string, any>>
53
58
  {
54
59
  fetch: (amount: number) => Promise<IndexedResult[]>;
55
60
  /* countStatement: Statement; */
56
- timeout: ReturnType<typeof setTimeout>;
61
+ expire: number;
57
62
  }
58
63
  >; // TODO choose limit better
64
+ private cursorPruner: ReturnType<typeof setInterval> | undefined;
59
65
 
60
66
  iteratorTimeout: number;
61
67
  closed: boolean = true;
@@ -78,25 +84,28 @@ export class SQLLiteIndex<T extends Record<string, any>>
78
84
  ? "_" + escapePathToSQLName(properties.scope).join("_")
79
85
  : undefined;
80
86
  this.iteratorTimeout = options?.iteratorTimeout || 60e3;
87
+ this.planner = new QueryPlanner({
88
+ exec: this.properties.db.exec.bind(this.properties.db),
89
+ });
81
90
  }
82
91
 
83
92
  get tables() {
84
93
  if (this.closed) {
85
- throw new Error("Not started");
94
+ throw new types.NotStartedError();
86
95
  }
87
96
  return this._tables;
88
97
  }
89
98
 
90
99
  get rootTables() {
91
100
  if (this.closed) {
92
- throw new Error("Not started");
101
+ throw new types.NotStartedError();
93
102
  }
94
103
  return this._rootTables;
95
104
  }
96
105
 
97
106
  get cursor() {
98
107
  if (this.closed) {
99
- throw new Error("Not started");
108
+ throw new types.NotStartedError();
100
109
  }
101
110
  return this._cursor;
102
111
  }
@@ -169,10 +178,40 @@ export class SQLLiteIndex<T extends Record<string, any>>
169
178
  }
170
179
 
171
180
  const sqlCreateTable = `create table if not exists ${table.name} (${[...table.fields, ...table.constraints].map((s) => s.definition).join(", ")}) strict`;
172
- const sqlCreateIndex = `create index if not exists ${table.name}_index on ${table.name} (${table.fields.map((field) => escapeColumnName(field.name)).join(", ")})`;
173
-
174
181
  this.properties.db.exec(sqlCreateTable);
175
- this.properties.db.exec(sqlCreateIndex);
182
+
183
+ /* const fieldsToIndex = table.fields.filter(
184
+ (field) =>
185
+ field.key !== ARRAY_INDEX_COLUMN && field.key !== table.primary,
186
+ );
187
+ if (fieldsToIndex.length > 0) {
188
+ let arr = fieldsToIndex.map((field) => escapeColumnName(field.name));
189
+
190
+ const createIndex = async (columns: string[]) => {
191
+ const key = createIndexKey(table.name, columns)
192
+ const command = `create index if not exists ${key} on ${table.name} (${columns.map((n) => escapeColumnName(n)).join(", ")})`;
193
+ await this.properties.db.exec(command);
194
+ table.indices.add(key);
195
+
196
+
197
+
198
+ const rev = columns.reverse()
199
+ const key2 = createIndexKey(table.name, rev)
200
+ const command2 = `create index if not exists ${key2} on ${table.name} (${rev.join(", ")})`;
201
+ await this.properties.db.exec(command2);
202
+ table.indices.add(key2);
203
+ }
204
+ await createIndex(fieldsToIndex.map(x => x.name));
205
+ await createIndex([table.primary as string, ...fieldsToIndex.map(x => x.name)]);
206
+
207
+ if (arr.length > 1) {
208
+ for (const field of fieldsToIndex) {
209
+ await createIndex([field.name]);
210
+ await createIndex([table.primary as string, field.name]);
211
+
212
+ }
213
+ }
214
+ } */
176
215
 
177
216
  // put and return the id
178
217
  let sqlPut = `insert into ${table.name} (${table.fields.map((field) => escapeColumnName(field.name)).join(", ")}) VALUES (${table.fields.map((_x) => "?").join(", ")}) RETURNING ${table.primary};`;
@@ -182,6 +221,7 @@ export class SQLLiteIndex<T extends Record<string, any>>
182
221
 
183
222
  await this.properties.db.prepare(sqlPut, putStatementKey(table));
184
223
  await this.properties.db.prepare(sqlReplace, replaceStatementKey(table));
224
+
185
225
  if (table.parent) {
186
226
  await this.properties.db.prepare(
187
227
  selectChildren(table),
@@ -190,6 +230,15 @@ export class SQLLiteIndex<T extends Record<string, any>>
190
230
  }
191
231
  }
192
232
 
233
+ this.cursorPruner = setInterval(() => {
234
+ const now = Date.now();
235
+ for (const [k, v] of this._cursor) {
236
+ if (v.expire < now) {
237
+ this.clearupIterator(k);
238
+ }
239
+ }
240
+ }, this.iteratorTimeout);
241
+
193
242
  this.closed = false;
194
243
  }
195
244
 
@@ -205,6 +254,7 @@ export class SQLLiteIndex<T extends Record<string, any>>
205
254
  return;
206
255
  }
207
256
  this.closed = true;
257
+ clearInterval(this.cursorPruner!);
208
258
 
209
259
  await this.clearStatements();
210
260
 
@@ -213,15 +263,23 @@ export class SQLLiteIndex<T extends Record<string, any>>
213
263
  for (const [k, _v] of this._cursor) {
214
264
  await this.clearupIterator(k);
215
265
  }
266
+
267
+ await this.planner.stop();
216
268
  }
217
269
 
218
270
  async drop(): Promise<void> {
271
+ if (this.closed) {
272
+ throw new Error(`Already closed index ${this.id}, can not drop`);
273
+ }
274
+
219
275
  this.closed = true;
276
+ clearInterval(this.cursorPruner!);
220
277
 
221
278
  await this.clearStatements();
222
279
 
223
280
  // drop root table and cascade
224
281
  // drop table faster by dropping constraints first
282
+
225
283
  for (const table of this._rootTables) {
226
284
  await this.properties.db.exec(`drop table if exists ${table.name}`);
227
285
  }
@@ -231,6 +289,7 @@ export class SQLLiteIndex<T extends Record<string, any>>
231
289
  for (const [k, _v] of this._cursor) {
232
290
  await this.clearupIterator(k);
233
291
  }
292
+ await this.planner.stop();
234
293
  }
235
294
 
236
295
  private async resolveDependencies(
@@ -253,27 +312,36 @@ export class SQLLiteIndex<T extends Record<string, any>>
253
312
  table,
254
313
  options?.shape,
255
314
  );
256
- const sql = `${generateSelectQuery(table, selects)} ${buildJoin(joinMap, true)} where ${this.primaryKeyString} = ? limit 1`;
257
- const stmt = await this.properties.db.prepare(sql, sql);
258
- const rows = await stmt.get([
259
- table.primaryField?.from?.type
260
- ? convertToSQLType(id.key, table.primaryField.from.type)
261
- : id.key,
262
- ]);
263
- if (!rows) {
264
- continue;
315
+ const sql = `${generateSelectQuery(table, selects)} ${buildJoin(joinMap).join} where ${this.primaryKeyString} = ? limit 1`;
316
+ try {
317
+ const stmt = await this.properties.db.prepare(sql, sql);
318
+ const rows = await stmt.get([
319
+ table.primaryField?.from?.type
320
+ ? convertToSQLType(id.key, table.primaryField.from.type)
321
+ : id.key,
322
+ ]);
323
+ if (
324
+ rows?.[getTablePrefixedField(table, table.primary as string)] == null
325
+ ) {
326
+ continue;
327
+ }
328
+ return {
329
+ value: (await resolveInstanceFromValue(
330
+ rows,
331
+ this.tables,
332
+ table,
333
+ this.resolveDependencies.bind(this),
334
+ true,
335
+ options?.shape,
336
+ )) as unknown as T,
337
+ id,
338
+ };
339
+ } catch (error) {
340
+ if (this.closed) {
341
+ throw new types.NotStartedError();
342
+ }
343
+ throw error;
265
344
  }
266
- return {
267
- value: (await resolveInstanceFromValue(
268
- rows,
269
- this.tables,
270
- table,
271
- this.resolveDependencies.bind(this),
272
- true,
273
- options?.shape,
274
- )) as unknown as T,
275
- id,
276
- };
277
345
  }
278
346
  return undefined;
279
347
  }
@@ -282,24 +350,20 @@ export class SQLLiteIndex<T extends Record<string, any>>
282
350
  const classOfValue = value.constructor as Constructor<T>;
283
351
  return insert(
284
352
  async (values, table) => {
285
- const preId = values[table.primaryIndex];
353
+ let preId = values[table.primaryIndex];
286
354
 
287
355
  if (preId != null) {
288
356
  const statement = this.properties.db.statements.get(
289
357
  replaceStatementKey(table),
290
358
  )!;
291
- await statement.run(
292
- values.map((x) => (typeof x === "boolean" ? (x ? 1 : 0) : x)),
293
- );
359
+ await statement.run(values);
294
360
  await statement.reset?.();
295
361
  return preId;
296
362
  } else {
297
363
  const statement = this.properties.db.statements.get(
298
364
  putStatementKey(table),
299
365
  )!;
300
- const out = await statement.get(
301
- values.map((x) => (typeof x === "boolean" ? (x ? 1 : 0) : x)),
302
- );
366
+ const out = await statement.get(values);
303
367
  await statement.reset?.();
304
368
 
305
369
  // TODO types
@@ -345,47 +409,63 @@ export class SQLLiteIndex<T extends Record<string, any>>
345
409
  let bindable: any[] = [];
346
410
  let sqlFetch: string | undefined = undefined;
347
411
 
412
+ const normalizedQuery = new PlannableQuery({
413
+ query: types.toQuery(request?.query),
414
+ sort: request?.sort,
415
+ });
416
+ let planningScope: ReturnType<QueryPlanner["scope"]>;
417
+
348
418
  /* let totalCount: undefined | number = undefined; */
349
419
  const fetch = async (amount: number | "all") => {
350
420
  kept = undefined;
351
421
  if (!once) {
422
+ planningScope = this.planner.scope(normalizedQuery);
423
+
352
424
  let { sql, bindable: toBind } = convertSearchRequestToQuery(
353
- request,
425
+ normalizedQuery,
354
426
  this.tables,
355
427
  this._rootTables,
356
428
  {
429
+ planner: planningScope,
357
430
  shape: options?.shape,
358
- stable: typeof amount === "number", // if we are to fetch all, we dont need stable sorting
431
+ fetchAll: amount === "all", // if we are to fetch all, we dont need stable sorting
359
432
  },
360
433
  );
434
+
361
435
  sqlFetch = sql;
362
436
  bindable = toBind;
363
437
 
438
+ await planningScope.beforePrepare();
439
+
364
440
  stmt = await this.properties.db.prepare(sqlFetch, sqlFetch);
365
- // stmt.reset?.(); // TODO dont invoke reset if not needed
366
- /* countStmt.reset?.(); */
367
441
 
368
442
  // Bump timeout timer
369
- clearTimeout(iterator.timeout);
370
- iterator.timeout = setTimeout(
371
- () => this.clearupIterator(requestId),
372
- this.iteratorTimeout,
373
- );
443
+ iterator.expire = Date.now() + this.iteratorTimeout;
374
444
  }
375
445
 
376
446
  once = true;
377
447
 
378
- const allResults: Record<string, any>[] = await stmt.all([
448
+ /* console.log("----------------------")
449
+ console.log(sqlFetch); */
450
+ const allResults = await planningScope.perform(async () => {
451
+ const allResults: Record<string, any>[] = await stmt.all([
452
+ ...bindable,
453
+ ...(amount !== "all" ? [amount, offset] : []),
454
+ ]);
455
+ return allResults;
456
+ });
457
+
458
+ /* const allResults: Record<string, any>[] = await stmt.all([
379
459
  ...bindable,
380
- amount === "all" ? Number.MAX_SAFE_INTEGER : amount,
381
- offset,
460
+ ...(amount !== "all" ? [amount,
461
+ offset] : [])
382
462
  ]);
383
-
463
+ */
384
464
  let results: IndexedResult<types.ReturnTypeFromShape<T, S>>[] =
385
465
  await Promise.all(
386
466
  allResults.map(async (row: any) => {
387
467
  let selectedTable = this._rootTables.find(
388
- (table /* row["table_name"] === table.name, */) =>
468
+ (table) =>
389
469
  row[getTablePrefixedField(table, this.primaryKeyString)] !=
390
470
  null,
391
471
  )!;
@@ -415,21 +495,14 @@ export class SQLLiteIndex<T extends Record<string, any>>
415
495
 
416
496
  offset += results.length;
417
497
 
418
- /* if (results.length > 0) {
419
- totalCount =
420
- totalCount ??
421
- (await this.count(
422
- request,
423
- ));
424
- iterator.kept = totalCount - results.length - offsetStart;
425
- } else {
426
- iterator.kept = 0;
498
+ /* const uniqueIds = new Set(results.map((x) => x.id.primitive));
499
+ if (uniqueIds.size !== results.length) {
500
+ throw new Error("Duplicate ids in result set");
427
501
  } */
428
502
 
429
503
  if (amount === "all" || results.length < amount) {
430
504
  hasMore = false;
431
505
  await this.clearupIterator(requestId);
432
- clearTimeout(iterator.timeout);
433
506
  }
434
507
  return results;
435
508
  };
@@ -437,10 +510,7 @@ export class SQLLiteIndex<T extends Record<string, any>>
437
510
  const iterator = {
438
511
  fetch,
439
512
  /* countStatement: countStmt, */
440
- timeout: setTimeout(
441
- () => this.clearupIterator(requestId),
442
- this.iteratorTimeout,
443
- ),
513
+ expire: Date.now() + this.iteratorTimeout,
444
514
  };
445
515
 
446
516
  this.cursor.set(requestId, iterator);
@@ -450,9 +520,9 @@ export class SQLLiteIndex<T extends Record<string, any>>
450
520
  all: async () => {
451
521
  const results: IndexedResult<types.ReturnTypeFromShape<T, S>>[] = [];
452
522
  while (true) {
453
- const res = await fetch(100);
523
+ const res = await fetch("all");
454
524
  results.push(...res);
455
- if (res.length === 0) {
525
+ if (hasMore === false) {
456
526
  break;
457
527
  }
458
528
  }
@@ -486,7 +556,6 @@ export class SQLLiteIndex<T extends Record<string, any>>
486
556
  if (!cache) {
487
557
  return; // already cleared
488
558
  }
489
- clearTimeout(cache.timeout);
490
559
  /* cache.countStatement.finalize?.(); */
491
560
  // await cache.fetchStatement.finalize?.();
492
561
  this._cursor.delete(id);
@@ -717,10 +786,16 @@ export class SQLiteIndices implements types.Indices {
717
786
  await scope.drop();
718
787
  }
719
788
 
720
- for (const index of this.indices) {
721
- await index.index.drop();
789
+ if (!this.properties.parent) {
790
+ for (const index of this.indices) {
791
+ await index.index.stop();
792
+ }
793
+ await this.properties.db.drop();
794
+ } else {
795
+ for (const index of this.indices) {
796
+ await index.index.drop();
797
+ }
722
798
  }
723
-
724
799
  this.scopes.clear();
725
800
  }
726
801
  }