@peerbit/indexer-sqlite3 1.0.7 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/engine.ts CHANGED
@@ -1,12 +1,8 @@
1
1
  import { type AbstractType, type Constructor, getSchema } from "@dao-xyz/borsh";
2
2
  import type {
3
- CloseIteratorRequest,
4
- CollectNextRequest,
5
3
  Index,
6
4
  IndexEngineInitProperties,
7
5
  IndexedResult,
8
- IndexedResults,
9
- SearchRequest,
10
6
  Shape,
11
7
  } from "@peerbit/indexer-interface";
12
8
  import * as types from "@peerbit/indexer-interface";
@@ -37,25 +33,23 @@ const escapePathToSQLName = (path: string[]) => {
37
33
  return path.map((x) => x.replace(/[^a-zA-Z0-9]/g, "_"));
38
34
  };
39
35
 
36
+ const putStatementKey = (table: Table) => table.name + "_put";
37
+ const replaceStatementKey = (table: Table) => table.name + "_replicate";
38
+ const resolveChildrenStatement = (table: Table) =>
39
+ table.name + "_resolve_children";
40
+
40
41
  export class SQLLiteIndex<T extends Record<string, any>>
41
42
  implements Index<T, any>
42
43
  {
43
- primaryKeyArr: string[];
44
- primaryKeyString: string;
45
- putStatement: Map<string, Statement>;
46
- replaceStatement: Map<string, Statement>;
47
- resolveChildrenStatement: Map<string, Statement>;
44
+ primaryKeyArr!: string[];
45
+ primaryKeyString!: string;
48
46
  private scopeString?: string;
49
- private _rootTables: Table[];
50
- private _tables: Map<string, Table>;
51
- private _cursor: Map<
47
+ private _rootTables!: Table[];
48
+ private _tables!: Map<string, Table>;
49
+ private _cursor!: Map<
52
50
  string,
53
51
  {
54
- kept: number;
55
- fetch: (
56
- amount: number,
57
- ) => Promise<{ results: IndexedResult[]; kept: number }>;
58
- fetchStatement: Statement;
52
+ fetch: (amount: number) => Promise<IndexedResult[]>;
59
53
  /* countStatement: Statement; */
60
54
  timeout: ReturnType<typeof setTimeout>;
61
55
  }
@@ -142,9 +136,6 @@ export class SQLLiteIndex<T extends Record<string, any>>
142
136
 
143
137
  await this.properties.start?.();
144
138
 
145
- this.putStatement = new Map();
146
- this.replaceStatement = new Map();
147
- this.resolveChildrenStatement = new Map();
148
139
  this._tables = new Map();
149
140
  this._cursor = new Map();
150
141
 
@@ -187,19 +178,12 @@ export class SQLLiteIndex<T extends Record<string, any>>
187
178
  // insert or replace with id already defined
188
179
  let sqlReplace = `insert or replace into ${table.name} (${table.fields.map((field) => escapeColumnName(field.name)).join(", ")}) VALUES (${table.fields.map((_x) => "?").join(", ")});`;
189
180
 
190
- this.putStatement.set(
191
- table.name,
192
- await this.properties.db.prepare(sqlPut),
193
- );
194
- this.replaceStatement.set(
195
- table.name,
196
- await this.properties.db.prepare(sqlReplace),
197
- );
198
-
181
+ await this.properties.db.prepare(sqlPut, putStatementKey(table));
182
+ await this.properties.db.prepare(sqlReplace, replaceStatementKey(table));
199
183
  if (table.parent) {
200
- this.resolveChildrenStatement.set(
201
- table.name,
202
- await this.properties.db.prepare(selectChildren(table)),
184
+ await this.properties.db.prepare(
185
+ selectChildren(table),
186
+ resolveChildrenStatement(table),
203
187
  );
204
188
  }
205
189
  }
@@ -212,21 +196,6 @@ export class SQLLiteIndex<T extends Record<string, any>>
212
196
  // TODO this should never be true, but if we remove this statement the tests faiL for browser tests?
213
197
  return;
214
198
  }
215
-
216
- for (const [_k, v] of this.putStatement) {
217
- await v.finalize?.();
218
- }
219
-
220
- for (const [_k, v] of this.replaceStatement) {
221
- await v.finalize?.();
222
- }
223
-
224
- for (const [_k, v] of this.resolveChildrenStatement) {
225
- await v.finalize?.();
226
- }
227
- this.putStatement.clear();
228
- this.replaceStatement.clear();
229
- this.resolveChildrenStatement.clear();
230
199
  }
231
200
 
232
201
  async stop(): Promise<void> {
@@ -266,7 +235,9 @@ export class SQLLiteIndex<T extends Record<string, any>>
266
235
  parentId: any,
267
236
  table: Table,
268
237
  ): Promise<any[]> {
269
- const stmt = this.resolveChildrenStatement.get(table.name)!;
238
+ const stmt = this.properties.db.statements.get(
239
+ resolveChildrenStatement(table),
240
+ )!;
270
241
  const results = await stmt.all([parentId]);
271
242
  await stmt.reset?.();
272
243
  return results;
@@ -281,9 +252,8 @@ export class SQLLiteIndex<T extends Record<string, any>>
281
252
  options?.shape,
282
253
  );
283
254
  const sql = `${generateSelectQuery(table, selects)} ${buildJoin(joinMap, true)} where ${this.primaryKeyString} = ? `;
284
- const stmt = await this.properties.db.prepare(sql);
255
+ const stmt = await this.properties.db.prepare(sql, sql);
285
256
  const rows = await stmt.get([id.key]);
286
- await stmt.finalize?.();
287
257
  if (!rows) {
288
258
  continue;
289
259
  }
@@ -309,20 +279,27 @@ export class SQLLiteIndex<T extends Record<string, any>>
309
279
  const preId = values[table.primaryIndex];
310
280
 
311
281
  if (preId != null) {
312
- const statement = this.replaceStatement.get(table.name)!;
282
+ const statement = this.properties.db.statements.get(
283
+ replaceStatementKey(table),
284
+ )!;
313
285
  await statement.run(
314
286
  values.map((x) => (typeof x === "boolean" ? (x ? 1 : 0) : x)),
315
287
  );
316
288
  await statement.reset?.();
317
289
  return preId;
318
290
  } else {
319
- const statement = this.putStatement.get(table.name)!;
291
+ const statement = this.properties.db.statements.get(
292
+ putStatementKey(table),
293
+ )!;
320
294
  const out = await statement.get(
321
295
  values.map((x) => (typeof x === "boolean" ? (x ? 1 : 0) : x)),
322
296
  );
323
297
  await statement.reset?.();
324
298
 
325
299
  // TODO types
300
+ if (out == null) {
301
+ return undefined;
302
+ }
326
303
  return out[table.primary as string];
327
304
  }
328
305
  },
@@ -341,117 +318,151 @@ export class SQLLiteIndex<T extends Record<string, any>>
341
318
  );
342
319
  }
343
320
 
344
- async query(
345
- request: SearchRequest,
346
- options?: { shape: Shape },
347
- ): Promise<IndexedResults<T>> {
321
+ iterate<S extends Shape | undefined>(
322
+ request?: types.IterateOptions,
323
+ options?: { shape?: S; reference?: boolean },
324
+ ): types.IndexIterator<T, S> {
348
325
  // create a sql statement where the offset and the limit id dynamic and can be updated
349
326
  // TODO don't use offset but sort and limit 'next' calls by the last value of the sort
350
- let sqlFetch = convertSearchRequestToQuery(
327
+ let { sql: sqlFetch, bindable } = convertSearchRequestToQuery(
351
328
  request,
352
329
  this.tables,
353
330
  this._rootTables,
354
331
  options?.shape,
355
332
  );
356
333
 
357
- const stmt = await this.properties.db.prepare(sqlFetch);
358
334
  /* const totalCountKey = "count"; */
359
335
  /* const sqlTotalCount = convertCountRequestToQuery(new types.CountRequest({ query: request.query }), this.tables, this.tables.get(this.rootTableName)!)
360
336
  const countStmt = await this.properties.db.prepare(sqlTotalCount); */
361
337
 
362
338
  let offset = 0;
363
- let first = false;
339
+ let once = false;
340
+ let requestId = uuid();
341
+ let hasMore = true;
342
+
343
+ let stmt: Statement;
344
+ let kept: number | undefined = undefined;
364
345
 
346
+ /* let totalCount: undefined | number = undefined; */
365
347
  const fetch = async (amount: number) => {
366
- if (!first) {
367
- stmt.reset?.();
348
+ kept = undefined;
349
+ if (!once) {
350
+ stmt = await this.properties.db.prepare(sqlFetch, sqlFetch);
351
+ // stmt.reset?.(); // TODO dont invoke reset if not needed
368
352
  /* countStmt.reset?.(); */
369
353
 
370
354
  // Bump timeout timer
371
355
  clearTimeout(iterator.timeout);
372
356
  iterator.timeout = setTimeout(
373
- () => this.clearupIterator(request.idString),
357
+ () => this.clearupIterator(requestId),
374
358
  this.iteratorTimeout,
375
359
  );
376
360
  }
377
361
 
378
- first = true;
362
+ once = true;
379
363
  const offsetStart = offset;
364
+
380
365
  const allResults: Record<string, any>[] = await stmt.all([
366
+ ...bindable,
381
367
  amount,
382
368
  offsetStart,
383
369
  ]);
384
370
 
385
- let results: IndexedResult<T>[] = await Promise.all(
386
- allResults.map(async (row: any) => {
387
- let selectedTable = this._rootTables.find(
388
- (table /* row["table_name"] === table.name, */) =>
389
- row[getTablePrefixedField(table, this.primaryKeyString)] != null,
390
- )!;
391
-
392
- const value = await resolveInstanceFromValue<T>(
393
- row,
394
- this.tables,
395
- selectedTable,
396
- this.resolveDependencies.bind(this),
397
- true,
398
- options?.shape,
399
- );
400
-
401
- return {
402
- value,
403
- id: types.toId(
404
- row[getTablePrefixedField(selectedTable, this.primaryKeyString)],
405
- ),
406
- };
407
- }),
408
- );
371
+ let results: IndexedResult<types.ReturnTypeFromShape<T, S>>[] =
372
+ await Promise.all(
373
+ allResults.map(async (row: any) => {
374
+ let selectedTable = this._rootTables.find(
375
+ (table /* row["table_name"] === table.name, */) =>
376
+ row[getTablePrefixedField(table, this.primaryKeyString)] !=
377
+ null,
378
+ )!;
379
+
380
+ const value = await resolveInstanceFromValue<T, S>(
381
+ row,
382
+ this.tables,
383
+ selectedTable,
384
+ this.resolveDependencies.bind(this),
385
+ true,
386
+ options?.shape,
387
+ );
388
+
389
+ return {
390
+ value,
391
+ id: types.toId(
392
+ row[
393
+ getTablePrefixedField(selectedTable, this.primaryKeyString)
394
+ ],
395
+ ),
396
+ };
397
+ }),
398
+ );
409
399
 
410
- offset += amount;
400
+ offset += results.length;
411
401
 
412
- if (results.length > 0) {
413
- const totalCount = await this.count(
414
- new types.CountRequest({ query: request.query }),
415
- ); /* (await countStmt.get())[totalCountKey] as number; */
402
+ /* if (results.length > 0) {
403
+ totalCount =
404
+ totalCount ??
405
+ (await this.count(
406
+ request,
407
+ ));
416
408
  iterator.kept = totalCount - results.length - offsetStart;
417
409
  } else {
418
410
  iterator.kept = 0;
419
- }
411
+ } */
420
412
 
421
- if (iterator.kept === 0) {
422
- await this.clearupIterator(request.idString);
413
+ if (results.length < amount) {
414
+ hasMore = false;
415
+ await this.clearupIterator(requestId);
423
416
  clearTimeout(iterator.timeout);
424
417
  }
425
- return { results, kept: iterator.kept };
418
+ return results;
426
419
  };
427
420
 
428
421
  const iterator = {
429
- kept: 0,
430
422
  fetch,
431
- fetchStatement: stmt,
432
423
  /* countStatement: countStmt, */
433
424
  timeout: setTimeout(
434
- () => this.clearupIterator(request.idString),
425
+ () => this.clearupIterator(requestId),
435
426
  this.iteratorTimeout,
436
427
  ),
437
428
  };
438
429
 
439
- this.cursor.set(request.idString, iterator);
440
- return fetch(request.fetch);
441
- }
442
-
443
- next(query: CollectNextRequest): Promise<IndexedResults<T>> {
444
- const cache = this.cursor.get(query.idString);
445
- if (!cache) {
446
- throw new Error("No cursor found with id: " + query.idString);
447
- }
448
-
449
- // reuse statement
450
- return cache.fetch(query.amount) as Promise<IndexedResults<T>>;
451
- }
430
+ this.cursor.set(requestId, iterator);
431
+ let totalCount: number | undefined = undefined;
432
+ /* return fetch(request.fetch); */
433
+ return {
434
+ all: async () => {
435
+ const results: IndexedResult<types.ReturnTypeFromShape<T, S>>[] = [];
436
+ while (true) {
437
+ const res = await fetch(100);
438
+ results.push(...res);
439
+ if (res.length === 0) {
440
+ break;
441
+ }
442
+ }
443
+ return results;
444
+ },
445
+ close: () => {
446
+ hasMore = false;
447
+ kept = 0;
448
+ this.clearupIterator(requestId);
449
+ },
450
+ next: (amount: number) => fetch(amount),
451
+ pending: async () => {
452
+ if (!hasMore) {
453
+ return 0;
454
+ }
455
+ if (kept != null) {
456
+ return kept;
457
+ }
458
+ totalCount = totalCount ?? (await this.count(request));
452
459
 
453
- close(query: CloseIteratorRequest): void | Promise<void> {
454
- return this.clearupIterator(query.idString);
460
+ kept = totalCount - offset;
461
+ hasMore = kept > 0;
462
+ return kept;
463
+ },
464
+ done: () => (once ? !hasMore : undefined),
465
+ };
455
466
  }
456
467
 
457
468
  private async clearupIterator(id: string) {
@@ -459,10 +470,9 @@ export class SQLLiteIndex<T extends Record<string, any>>
459
470
  if (!cache) {
460
471
  return; // already cleared
461
472
  }
462
-
463
473
  clearTimeout(cache.timeout);
464
474
  /* cache.countStatement.finalize?.(); */
465
- await cache.fetchStatement.finalize?.();
475
+ // await cache.fetchStatement.finalize?.();
466
476
  this._cursor.delete(id);
467
477
  }
468
478
 
@@ -475,20 +485,23 @@ export class SQLLiteIndex<T extends Record<string, any>>
475
485
  const result = await stmt.get()
476
486
  stmt.finalize?.();
477
487
  return result.total as number */
478
- return this.count(new types.CountRequest({ query: {} }));
488
+ return this.count();
479
489
  }
480
490
 
481
- async del(query: types.DeleteRequest): Promise<types.IdKey[]> {
491
+ async del(query: types.DeleteOptions): Promise<types.IdKey[]> {
482
492
  let ret: types.IdKey[] = [];
483
493
  let once = false;
484
494
  let lastError: Error | undefined = undefined;
485
495
  for (const table of this._rootTables) {
486
496
  try {
487
- const stmt = await this.properties.db.prepare(
488
- convertDeleteRequestToQuery(query, this.tables, table),
497
+ const { sql, bindable } = convertDeleteRequestToQuery(
498
+ query,
499
+ this.tables,
500
+ table,
489
501
  );
490
- const results: any[] = await stmt.all([]);
491
- await stmt.finalize?.();
502
+ const stmt = await this.properties.db.prepare(sql, sql);
503
+ const results: any[] = await stmt.all(bindable);
504
+
492
505
  // TODO types
493
506
  for (const result of results) {
494
507
  ret.push(types.toId(result[table.primary as string]));
@@ -505,13 +518,13 @@ export class SQLLiteIndex<T extends Record<string, any>>
505
518
  }
506
519
 
507
520
  if (!once) {
508
- throw lastError;
521
+ throw lastError!;
509
522
  }
510
523
 
511
524
  return ret;
512
525
  }
513
526
 
514
- async sum(query: types.SumRequest): Promise<number | bigint> {
527
+ async sum(query: types.SumOptions): Promise<number | bigint> {
515
528
  let ret: number | bigint | undefined = undefined;
516
529
  let once = false;
517
530
  let lastError: Error | undefined = undefined;
@@ -521,22 +534,27 @@ export class SQLLiteIndex<T extends Record<string, any>>
521
534
  try {
522
535
  if (table.fields.find((x) => x.name === inlinedName) == null) {
523
536
  lastError = new MissingFieldError(
524
- "Missing field: " + query.key.join("."),
537
+ "Missing field: " +
538
+ (Array.isArray(query.key) ? query.key : [query.key]).join("."),
525
539
  );
526
540
  continue;
527
541
  }
528
542
 
529
- const stmt = await this.properties.db.prepare(
530
- convertSumRequestToQuery(query, this.tables, table),
543
+ const { sql, bindable } = convertSumRequestToQuery(
544
+ query,
545
+ this.tables,
546
+ table,
531
547
  );
532
- const result = await stmt.get();
533
- await stmt.finalize?.();
534
- if (ret == null) {
535
- (ret as any) = result.sum as number;
536
- } else {
537
- (ret as any) += result.sum as number;
548
+ const stmt = await this.properties.db.prepare(sql, sql);
549
+ const result = await stmt.get(bindable);
550
+ if (result != null) {
551
+ if (ret == null) {
552
+ (ret as any) = result.sum as number;
553
+ } else {
554
+ (ret as any) += result.sum as number;
555
+ }
556
+ once = true;
538
557
  }
539
- once = true;
540
558
  } catch (error) {
541
559
  if (error instanceof MissingFieldError) {
542
560
  lastError = error;
@@ -547,25 +565,29 @@ export class SQLLiteIndex<T extends Record<string, any>>
547
565
  }
548
566
 
549
567
  if (!once) {
550
- throw lastError;
568
+ throw lastError!;
551
569
  }
552
570
 
553
571
  return ret != null ? ret : 0;
554
572
  }
555
573
 
556
- async count(request: types.CountRequest): Promise<number> {
574
+ async count(request?: types.CountOptions): Promise<number> {
557
575
  let ret: number = 0;
558
576
  let once = false;
559
577
  let lastError: Error | undefined = undefined;
560
578
  for (const table of this._rootTables) {
561
579
  try {
562
- const stmt = await this.properties.db.prepare(
563
- convertCountRequestToQuery(request, this.tables, table),
580
+ const { sql, bindable } = convertCountRequestToQuery(
581
+ request,
582
+ this.tables,
583
+ table,
564
584
  );
565
- const result = await stmt.get();
566
- await stmt.finalize?.();
567
- ret += Number(result.count);
568
- once = true;
585
+ const stmt = await this.properties.db.prepare(sql, sql);
586
+ const result = await stmt.get(bindable);
587
+ if (result != null) {
588
+ ret += Number(result.count);
589
+ once = true;
590
+ }
569
591
  } catch (error) {
570
592
  if (error instanceof MissingFieldError) {
571
593
  lastError = error;
@@ -577,19 +599,11 @@ export class SQLLiteIndex<T extends Record<string, any>>
577
599
  }
578
600
 
579
601
  if (!once) {
580
- throw lastError;
602
+ throw lastError!;
581
603
  }
582
604
  return ret;
583
605
  }
584
606
 
585
- getPending(cursorId: string): number | undefined {
586
- const cursor = this.cursor.get(cursorId);
587
- if (!cursor) {
588
- return;
589
- }
590
- return cursor.kept;
591
- }
592
-
593
607
  get cursorCount(): number {
594
608
  return this.cursor.size;
595
609
  }