@peerbit/indexer-sqlite3 3.0.6 → 3.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/sqlite3/sqlite3.worker.min.js +6 -1
- package/dist/benchmark/query-planner.d.ts +2 -0
- package/dist/benchmark/query-planner.d.ts.map +1 -0
- package/dist/benchmark/query-planner.js +97 -0
- package/dist/benchmark/query-planner.js.map +1 -0
- package/dist/index.min.js +535 -183
- package/dist/index.min.js.map +3 -3
- package/dist/src/engine.d.ts +15 -4
- package/dist/src/engine.d.ts.map +1 -1
- package/dist/src/engine.js +364 -179
- package/dist/src/engine.js.map +1 -1
- package/dist/src/query-planner.d.ts +20 -0
- package/dist/src/query-planner.d.ts.map +1 -1
- package/dist/src/query-planner.js +191 -21
- package/dist/src/query-planner.js.map +1 -1
- package/dist/src/schema.d.ts +6 -2
- package/dist/src/schema.d.ts.map +1 -1
- package/dist/src/schema.js +11 -8
- package/dist/src/schema.js.map +1 -1
- package/dist/src/sqlite3-messages.worker.d.ts +8 -1
- package/dist/src/sqlite3-messages.worker.d.ts.map +1 -1
- package/dist/src/sqlite3-messages.worker.js.map +1 -1
- package/dist/src/sqlite3.browser.d.ts.map +1 -1
- package/dist/src/sqlite3.browser.js +21 -0
- package/dist/src/sqlite3.browser.js.map +1 -1
- package/dist/src/sqlite3.wasm.d.ts.map +1 -1
- package/dist/src/sqlite3.wasm.js +4 -1
- package/dist/src/sqlite3.wasm.js.map +1 -1
- package/dist/src/sqlite3.worker.js +6 -0
- package/dist/src/sqlite3.worker.js.map +1 -1
- package/dist/src/types.d.ts +4 -0
- package/dist/src/types.d.ts.map +1 -1
- package/package.json +6 -5
- package/src/engine.ts +464 -235
- package/src/query-planner.ts +247 -22
- package/src/schema.ts +21 -4
- package/src/sqlite3-messages.worker.ts +6 -0
- package/src/sqlite3.browser.ts +33 -0
- package/src/sqlite3.wasm.ts +4 -1
- package/src/sqlite3.worker.ts +6 -0
- package/src/types.ts +3 -0
package/src/query-planner.ts
CHANGED
|
@@ -16,12 +16,18 @@ import {
|
|
|
16
16
|
Query,
|
|
17
17
|
Sort,
|
|
18
18
|
StringMatch,
|
|
19
|
+
StringMatchMethod,
|
|
19
20
|
UnsignedIntegerValue,
|
|
20
21
|
} from "@peerbit/indexer-interface";
|
|
21
22
|
import { hrtime } from "@peerbit/time";
|
|
22
23
|
import pDefer, { type DeferredPromise } from "p-defer";
|
|
23
24
|
import { escapeColumnName } from "./schema.js";
|
|
24
25
|
|
|
26
|
+
type IndexColumn = {
|
|
27
|
+
name: string;
|
|
28
|
+
collation?: "NOCASE";
|
|
29
|
+
};
|
|
30
|
+
|
|
25
31
|
export interface QueryIndexPlanner {
|
|
26
32
|
// assumes withing a query, each index can be picked independently. For example if we are to join two tables, we can pick the best index for each table
|
|
27
33
|
// sorted column names key to execution time for each index that was tried
|
|
@@ -33,6 +39,7 @@ export interface QueryIndexPlanner {
|
|
|
33
39
|
avg: number;
|
|
34
40
|
times: number[];
|
|
35
41
|
indexKey: string;
|
|
42
|
+
columns: IndexColumn[];
|
|
36
43
|
created: () => boolean;
|
|
37
44
|
creationPromiseDeferred: DeferredPromise<void>;
|
|
38
45
|
}[];
|
|
@@ -43,12 +50,18 @@ export interface QueryIndexPlanner {
|
|
|
43
50
|
type StmtStats = Map<string, QueryIndexPlanner>;
|
|
44
51
|
|
|
45
52
|
const getSortedNameKey = (tableName: string, names: string[]) =>
|
|
46
|
-
[tableName, ...names.sort()].join(",");
|
|
47
|
-
const
|
|
48
|
-
`${
|
|
53
|
+
[tableName, ...[...names].sort()].join(",");
|
|
54
|
+
const getIndexColumnKey = (field: IndexColumn) =>
|
|
55
|
+
`${field.name}${field.collation ? `_collate_${field.collation.toLowerCase()}` : ""}`;
|
|
56
|
+
const createIndexKey = (tableName: string, fields: IndexColumn[]) =>
|
|
57
|
+
`${tableName}_index_${fields.map((x) => getIndexColumnKey(x).replace(/[^a-zA-Z0-9_]/g, "_")).join("_")}`;
|
|
58
|
+
const createIndexColumnSQL = (field: IndexColumn) =>
|
|
59
|
+
`${escapeColumnName(field.name)}${field.collation ? ` COLLATE ${field.collation}` : ""}`;
|
|
49
60
|
|
|
50
61
|
const HALF_MAX_U32 = 2147483647; // rounded down
|
|
51
62
|
const HALF_MAX_U64 = 9223372036854775807n; // rounded down
|
|
63
|
+
const PARENT_TABLE_ID = "__parent_id";
|
|
64
|
+
const AMBIGUOUS_CHILD_FORCE_AFTER_USES = 6_000;
|
|
52
65
|
|
|
53
66
|
export const flattenQuery = function* (props?: {
|
|
54
67
|
query: Query[];
|
|
@@ -194,7 +207,16 @@ export class QueryPlanner {
|
|
|
194
207
|
pendingIndexCreation: Map<string, Promise<void>> = new Map();
|
|
195
208
|
|
|
196
209
|
constructor(
|
|
197
|
-
readonly props: {
|
|
210
|
+
readonly props: {
|
|
211
|
+
exec: (query: string) => Promise<any> | any;
|
|
212
|
+
/**
|
|
213
|
+
* INDEXED BY is a hard SQLite requirement, not a hint. Keep the legacy
|
|
214
|
+
* forced-index behavior by default and allow callers to disable it once
|
|
215
|
+
* their query shapes have been verified against SQLite's own planner.
|
|
216
|
+
*/
|
|
217
|
+
forceIndexes?: boolean;
|
|
218
|
+
optimizeAfterCreate?: boolean;
|
|
219
|
+
},
|
|
198
220
|
) {}
|
|
199
221
|
|
|
200
222
|
async stop() {
|
|
@@ -219,24 +241,48 @@ export class QueryPlanner {
|
|
|
219
241
|
| undefined = undefined;
|
|
220
242
|
let pickedIndexKeys: Map<string, string> = new Map(); // index key to column names key
|
|
221
243
|
let indexCreationPromiseToAwait: Promise<void>[] = [];
|
|
244
|
+
let forceIndex = this.props.forceIndexes !== false;
|
|
222
245
|
return {
|
|
246
|
+
get forceIndex() {
|
|
247
|
+
return forceIndex;
|
|
248
|
+
},
|
|
223
249
|
beforePrepare: async () => {
|
|
224
250
|
// create missing indices
|
|
225
251
|
if (indexCreateCommands != null) {
|
|
226
|
-
|
|
252
|
+
const commandsToCreate: typeof indexCreateCommands = [];
|
|
253
|
+
for (const command of indexCreateCommands) {
|
|
254
|
+
if (this.pendingIndexCreation.has(command.key)) {
|
|
255
|
+
// TODO is this kind of debouncing needed? how do we end up here?
|
|
256
|
+
await this.pendingIndexCreation.get(command.key);
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
commandsToCreate.push(command);
|
|
260
|
+
}
|
|
261
|
+
if (commandsToCreate.length > 0) {
|
|
262
|
+
const creationPromise = Promise.resolve(
|
|
263
|
+
this.props.exec(
|
|
264
|
+
[
|
|
265
|
+
...commandsToCreate.map((command) => command.cmd),
|
|
266
|
+
...(this.props.optimizeAfterCreate === false
|
|
267
|
+
? []
|
|
268
|
+
: ["PRAGMA optimize"]),
|
|
269
|
+
].join(";"),
|
|
270
|
+
),
|
|
271
|
+
);
|
|
272
|
+
for (const { key } of commandsToCreate) {
|
|
273
|
+
this.pendingIndexCreation.set(key, creationPromise);
|
|
274
|
+
}
|
|
227
275
|
try {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
276
|
+
await creationPromise;
|
|
277
|
+
for (const { key, deferred } of commandsToCreate) {
|
|
278
|
+
this.pendingIndexCreation.delete(key);
|
|
279
|
+
deferred.resolve();
|
|
231
280
|
}
|
|
232
|
-
const promise = this.props.exec(cmd);
|
|
233
|
-
this.pendingIndexCreation.set(key, promise);
|
|
234
|
-
await promise;
|
|
235
|
-
|
|
236
|
-
this.pendingIndexCreation.delete(key);
|
|
237
|
-
deferred.resolve();
|
|
238
281
|
} catch (error) {
|
|
239
|
-
deferred
|
|
282
|
+
for (const { key, deferred } of commandsToCreate) {
|
|
283
|
+
this.pendingIndexCreation.delete(key);
|
|
284
|
+
deferred.reject(error);
|
|
285
|
+
}
|
|
240
286
|
}
|
|
241
287
|
}
|
|
242
288
|
}
|
|
@@ -250,6 +296,8 @@ export class QueryPlanner {
|
|
|
250
296
|
await Promise.all(indexCreationPromiseToAwait);
|
|
251
297
|
},
|
|
252
298
|
resolveIndex: (tableName: string, columns: string[]): string => {
|
|
299
|
+
forceIndex = this.props.forceIndexes !== false;
|
|
300
|
+
|
|
253
301
|
// first we figure out whether we want to reuse the fastest index or try a new one
|
|
254
302
|
// only assume we either do forward or backward column order for now (not all n! permutations)
|
|
255
303
|
const sortedNameKey = getSortedNameKey(tableName, columns);
|
|
@@ -262,11 +310,10 @@ export class QueryPlanner {
|
|
|
262
310
|
}
|
|
263
311
|
|
|
264
312
|
if (indexStats.results.length === 0) {
|
|
265
|
-
|
|
266
|
-
const
|
|
267
|
-
for (const columns of permutations) {
|
|
313
|
+
const candidates = generateIndexCandidates(query, columns);
|
|
314
|
+
for (const columns of candidates) {
|
|
268
315
|
const indexKey = createIndexKey(tableName, columns);
|
|
269
|
-
const command = `create index if not exists ${indexKey} on ${tableName} (${columns.map((n) =>
|
|
316
|
+
const command = `create index if not exists ${indexKey} on ${tableName} (${columns.map((n) => createIndexColumnSQL(n)).join(", ")})`;
|
|
270
317
|
|
|
271
318
|
let deferred = pDefer<void>();
|
|
272
319
|
(indexCreateCommands || (indexCreateCommands = [])).push({
|
|
@@ -284,12 +331,27 @@ export class QueryPlanner {
|
|
|
284
331
|
times: [],
|
|
285
332
|
avg: -1, // setting -1 will force the first time to be the fastest (i.e. new indices are always tested once)
|
|
286
333
|
indexKey,
|
|
334
|
+
columns,
|
|
287
335
|
created: () => created,
|
|
288
336
|
creationPromiseDeferred: deferred,
|
|
289
337
|
});
|
|
290
338
|
}
|
|
291
339
|
}
|
|
292
340
|
|
|
341
|
+
const isAmbiguousChildPredicate =
|
|
342
|
+
query.sort.length === 0 &&
|
|
343
|
+
columns.includes(PARENT_TABLE_ID) &&
|
|
344
|
+
columns.length > 1;
|
|
345
|
+
if (isAmbiguousChildPredicate) {
|
|
346
|
+
const totalUses = indexStats.results.reduce(
|
|
347
|
+
(sum, result) => sum + result.used,
|
|
348
|
+
0,
|
|
349
|
+
);
|
|
350
|
+
forceIndex =
|
|
351
|
+
this.props.forceIndexes !== false &&
|
|
352
|
+
totalUses >= AMBIGUOUS_CHILD_FORCE_AFTER_USES;
|
|
353
|
+
}
|
|
354
|
+
|
|
293
355
|
// find the fastest index
|
|
294
356
|
let fastestIndex = indexStats.results[0];
|
|
295
357
|
fastestIndex.used++;
|
|
@@ -337,9 +399,172 @@ export class QueryPlanner {
|
|
|
337
399
|
}
|
|
338
400
|
}
|
|
339
401
|
|
|
340
|
-
const
|
|
341
|
-
if (
|
|
342
|
-
|
|
402
|
+
const queryKeyToColumnName = (key: string[]) => {
|
|
403
|
+
if (key.length > 2) {
|
|
404
|
+
return `${key.slice(0, -1).join("_")}__${key[key.length - 1]}`;
|
|
405
|
+
}
|
|
406
|
+
return key.join("__");
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
const pushUniqueColumn = (list: IndexColumn[], column: IndexColumn) => {
|
|
410
|
+
const key = getIndexColumnKey(column);
|
|
411
|
+
if (!list.some((x) => getIndexColumnKey(x) === key)) {
|
|
412
|
+
list.push(column);
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
const pushColumns = (target: IndexColumn[], columns: IndexColumn[]) => {
|
|
417
|
+
for (const column of columns) {
|
|
418
|
+
pushUniqueColumn(target, column);
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
const getIndexableQueryColumns = (
|
|
423
|
+
query: Query[],
|
|
424
|
+
availableColumns: Set<string>,
|
|
425
|
+
) => {
|
|
426
|
+
const equality: IndexColumn[] = [];
|
|
427
|
+
const range: IndexColumn[] = [];
|
|
428
|
+
|
|
429
|
+
const visit = (item: Query, path: string[] = []) => {
|
|
430
|
+
if (item instanceof And) {
|
|
431
|
+
for (const condition of item.and) {
|
|
432
|
+
visit(condition, path);
|
|
433
|
+
}
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
if (item instanceof Or) {
|
|
437
|
+
for (const condition of item.or) {
|
|
438
|
+
visit(condition, path);
|
|
439
|
+
}
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
if (item instanceof Not) {
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
if (item instanceof Nested) {
|
|
446
|
+
for (const condition of item.query) {
|
|
447
|
+
visit(condition, [...path, ...item.path]);
|
|
448
|
+
}
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
let key: string[] | undefined;
|
|
453
|
+
let target: IndexColumn[] | undefined;
|
|
454
|
+
let collation: IndexColumn["collation"] | undefined;
|
|
455
|
+
if (item instanceof IntegerCompare) {
|
|
456
|
+
key = item.key;
|
|
457
|
+
target = item.compare === Compare.Equal ? equality : range;
|
|
458
|
+
} else if (item instanceof StringMatch) {
|
|
459
|
+
key = item.key;
|
|
460
|
+
if (item.method === StringMatchMethod.contains) {
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
target = item.method === StringMatchMethod.exact ? equality : range;
|
|
464
|
+
collation = item.caseInsensitive ? "NOCASE" : undefined;
|
|
465
|
+
} else if (
|
|
466
|
+
item instanceof ByteMatchQuery ||
|
|
467
|
+
item instanceof BoolQuery ||
|
|
468
|
+
item instanceof IsNull
|
|
469
|
+
) {
|
|
470
|
+
key = item.key;
|
|
471
|
+
target = equality;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (!key || !target) {
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
const columnName = queryKeyToColumnName([...path, ...key]);
|
|
478
|
+
if (availableColumns.has(columnName)) {
|
|
479
|
+
pushUniqueColumn(target, { name: columnName, collation });
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
for (const item of query) {
|
|
484
|
+
visit(item);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
return { equality, range };
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
const getSortableColumns = (sort: Sort[], availableColumns: Set<string>) => {
|
|
491
|
+
const out: IndexColumn[] = [];
|
|
492
|
+
for (const item of sort) {
|
|
493
|
+
const columnName = queryKeyToColumnName(item.key);
|
|
494
|
+
if (availableColumns.has(columnName)) {
|
|
495
|
+
pushUniqueColumn(out, { name: columnName });
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return out;
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
const normalizeCandidate = (columns: IndexColumn[]) => {
|
|
502
|
+
const out: IndexColumn[] = [];
|
|
503
|
+
pushColumns(out, columns);
|
|
504
|
+
return out;
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
const generateIndexCandidates = (query: PlannableQuery, columns: string[]) => {
|
|
508
|
+
if (columns.length === 0) {
|
|
509
|
+
return [];
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const availableColumns = new Set(columns);
|
|
513
|
+
const { equality, range } = getIndexableQueryColumns(
|
|
514
|
+
query.query,
|
|
515
|
+
availableColumns,
|
|
516
|
+
);
|
|
517
|
+
const sort = getSortableColumns(query.sort, availableColumns);
|
|
518
|
+
const join = availableColumns.has(PARENT_TABLE_ID)
|
|
519
|
+
? [{ name: PARENT_TABLE_ID }]
|
|
520
|
+
: [];
|
|
521
|
+
const knownColumnNames = new Set(
|
|
522
|
+
[...join, ...equality, ...range, ...sort].map((x) => x.name),
|
|
523
|
+
);
|
|
524
|
+
const remaining = columns
|
|
525
|
+
.filter((column) => !knownColumnNames.has(column))
|
|
526
|
+
.map((name) => ({ name }));
|
|
527
|
+
|
|
528
|
+
const candidates: IndexColumn[][] = [];
|
|
529
|
+
const pushCandidate = (...parts: IndexColumn[][]) => {
|
|
530
|
+
const candidate = normalizeCandidate(parts.flat());
|
|
531
|
+
if (
|
|
532
|
+
candidate.length > 0 &&
|
|
533
|
+
!candidates.some(
|
|
534
|
+
(existing) =>
|
|
535
|
+
existing.map(getIndexColumnKey).join(",") ===
|
|
536
|
+
candidate.map(getIndexColumnKey).join(","),
|
|
537
|
+
)
|
|
538
|
+
) {
|
|
539
|
+
candidates.push(candidate);
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
if (sort.length > 0 && range.length > 0) {
|
|
544
|
+
pushCandidate(join, equality, sort, range, remaining);
|
|
545
|
+
pushCandidate(join, equality, range, sort, remaining);
|
|
546
|
+
} else if (sort.length > 0) {
|
|
547
|
+
pushCandidate(join, equality, sort, range, remaining);
|
|
548
|
+
} else {
|
|
549
|
+
if (join.length > 0 && (equality.length > 0 || range.length > 0)) {
|
|
550
|
+
pushCandidate(equality, range, join, remaining);
|
|
551
|
+
}
|
|
552
|
+
pushCandidate(join, equality, range, remaining);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
if (join.length > 0 && (equality.length > 0 || range.length > 0)) {
|
|
556
|
+
if (sort.length > 0 && range.length > 0) {
|
|
557
|
+
pushCandidate(equality, sort, range, join, remaining);
|
|
558
|
+
pushCandidate(equality, range, sort, join, remaining);
|
|
559
|
+
} else if (sort.length > 0) {
|
|
560
|
+
pushCandidate(equality, range, sort, join, remaining);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
pushCandidate(columns.map((name) => ({ name })));
|
|
565
|
+
pushCandidate([...columns].reverse().map((name) => ({ name })));
|
|
566
|
+
|
|
567
|
+
return candidates;
|
|
343
568
|
};
|
|
344
569
|
/* const generatePermutations = (list: string[]) => {
|
|
345
570
|
const results: string[][] = [];
|
package/src/schema.ts
CHANGED
|
@@ -1565,12 +1565,18 @@ export const convertDeleteRequestToQuery = (
|
|
|
1565
1565
|
request: types.DeleteOptions,
|
|
1566
1566
|
tables: Map<string, Table>,
|
|
1567
1567
|
table: Table,
|
|
1568
|
+
options?: {
|
|
1569
|
+
planner?: PlanningSession;
|
|
1570
|
+
},
|
|
1568
1571
|
): { sql: string; bindable: any[] } => {
|
|
1569
1572
|
const { query, bindable } = convertRequestToQuery(
|
|
1570
1573
|
"delete",
|
|
1571
1574
|
{ query: coerceLocalQueries(request.query) },
|
|
1572
1575
|
tables,
|
|
1573
1576
|
table,
|
|
1577
|
+
undefined,
|
|
1578
|
+
[],
|
|
1579
|
+
options,
|
|
1574
1580
|
);
|
|
1575
1581
|
return {
|
|
1576
1582
|
sql: `DELETE FROM ${table.name} WHERE ${table.name}.${table.primary} IN (SELECT ${table.primary} from ${table.name} ${query}) returning ${table.primary}`,
|
|
@@ -1582,12 +1588,18 @@ export const convertSumRequestToQuery = (
|
|
|
1582
1588
|
request: types.SumOptions,
|
|
1583
1589
|
tables: Map<string, Table>,
|
|
1584
1590
|
table: Table,
|
|
1591
|
+
options?: {
|
|
1592
|
+
planner?: PlanningSession;
|
|
1593
|
+
},
|
|
1585
1594
|
): { sql: string; bindable: any[] } => {
|
|
1586
1595
|
const { query, bindable } = convertRequestToQuery(
|
|
1587
1596
|
"sum",
|
|
1588
1597
|
{ query: coerceLocalQueries(request.query), key: request.key },
|
|
1589
1598
|
tables,
|
|
1590
1599
|
table,
|
|
1600
|
+
undefined,
|
|
1601
|
+
[],
|
|
1602
|
+
options,
|
|
1591
1603
|
);
|
|
1592
1604
|
|
|
1593
1605
|
const inlineName = getInlineTableFieldName(request.key);
|
|
@@ -1983,10 +1995,15 @@ const _buildJoin = (
|
|
|
1983
1995
|
table.table.primary !== false &&
|
|
1984
1996
|
usedColumns.length === 1 &&
|
|
1985
1997
|
usedColumns[0] === table.table.primary;
|
|
1986
|
-
|
|
1987
|
-
options
|
|
1988
|
-
|
|
1989
|
-
|
|
1998
|
+
if (options?.planner && !usesImplicitPrimaryKeyIndex) {
|
|
1999
|
+
const indexKey = options.planner.resolveIndex(
|
|
2000
|
+
table.table.name,
|
|
2001
|
+
usedColumns,
|
|
2002
|
+
);
|
|
2003
|
+
indexedBy = options.planner.forceIndex ? ` INDEXED BY ${indexKey} ` : "";
|
|
2004
|
+
} else {
|
|
2005
|
+
indexedBy = "";
|
|
2006
|
+
}
|
|
1990
2007
|
}
|
|
1991
2008
|
|
|
1992
2009
|
if (table.type !== "root") {
|
|
@@ -50,6 +50,11 @@ interface Prepare extends Message {
|
|
|
50
50
|
sql: string;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
interface PrepareMany extends Message {
|
|
54
|
+
type: "prepare-many";
|
|
55
|
+
statements: { id: string; sql: string }[];
|
|
56
|
+
}
|
|
57
|
+
|
|
53
58
|
type Uint8ArrayBase64Type = {
|
|
54
59
|
type: "uint8array";
|
|
55
60
|
encoding: "base64";
|
|
@@ -204,6 +209,7 @@ export type DatabaseMessages =
|
|
|
204
209
|
| CreateDatabase
|
|
205
210
|
| Exec
|
|
206
211
|
| Prepare
|
|
212
|
+
| PrepareMany
|
|
207
213
|
| Close
|
|
208
214
|
| Drop
|
|
209
215
|
| Open
|
package/src/sqlite3.browser.ts
CHANGED
|
@@ -311,6 +311,39 @@ class ProxyDatabase implements IDatabase {
|
|
|
311
311
|
return statement;
|
|
312
312
|
}
|
|
313
313
|
|
|
314
|
+
async prepareMany(statements: { sql: string; id: string }[]) {
|
|
315
|
+
if (statements.length === 0) {
|
|
316
|
+
return [];
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const missing = statements.filter(
|
|
320
|
+
(statement) => !this.statements.get(statement.id),
|
|
321
|
+
);
|
|
322
|
+
if (missing.length > 0) {
|
|
323
|
+
const statementIds = await this.send<string[]>({
|
|
324
|
+
type: "prepare-many",
|
|
325
|
+
statements: missing,
|
|
326
|
+
id: uuid(),
|
|
327
|
+
databaseId: this.databaseId,
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
for (const [index, statementId] of statementIds.entries()) {
|
|
331
|
+
const definition = missing[index];
|
|
332
|
+
const statement = new ProxyStatement(
|
|
333
|
+
this.send.bind(this),
|
|
334
|
+
this.databaseId,
|
|
335
|
+
statementId,
|
|
336
|
+
definition.sql,
|
|
337
|
+
this.options,
|
|
338
|
+
);
|
|
339
|
+
this.statements.set(statementId, statement);
|
|
340
|
+
this.statements.set(definition.id, statement);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return statements.map((statement) => this.statements.get(statement.id)!);
|
|
345
|
+
}
|
|
346
|
+
|
|
314
347
|
async open() {
|
|
315
348
|
return this.send({ type: "open", id: uuid(), databaseId: this.databaseId });
|
|
316
349
|
}
|
package/src/sqlite3.wasm.ts
CHANGED
|
@@ -313,7 +313,10 @@ const create = async (
|
|
|
313
313
|
}));
|
|
314
314
|
poolUtil = activePoolUtil;
|
|
315
315
|
|
|
316
|
-
|
|
316
|
+
// SAH pool capacity persists across sessions, and the default pool is
|
|
317
|
+
// already sized for a small number of databases plus temp files.
|
|
318
|
+
// Avoid preallocating a much larger pool on every cold start because it
|
|
319
|
+
// dominates the initial browser open latency.
|
|
317
320
|
sqliteDb = new activePoolUtil.OpfsSAHPoolDb(dbFileName);
|
|
318
321
|
} else {
|
|
319
322
|
sqliteDb = new sqlite3.oo1.DB(":memory:");
|
package/src/sqlite3.worker.ts
CHANGED
|
@@ -103,6 +103,12 @@ class SqliteWorkerHandler {
|
|
|
103
103
|
await db.prepare(message.sql, message.id);
|
|
104
104
|
return statementId;
|
|
105
105
|
}
|
|
106
|
+
if (message.type === "prepare-many") {
|
|
107
|
+
for (const statement of message.statements) {
|
|
108
|
+
await db.prepare(statement.sql, statement.id);
|
|
109
|
+
}
|
|
110
|
+
return message.statements.map((statement) => statement.id);
|
|
111
|
+
}
|
|
106
112
|
if (message.type === "close") {
|
|
107
113
|
await db.close();
|
|
108
114
|
this.databases.delete(message.databaseId);
|
package/src/types.ts
CHANGED
|
@@ -7,6 +7,9 @@ export type SQLite = {
|
|
|
7
7
|
export type Database = {
|
|
8
8
|
exec: (sql: string) => Promise<any> | any;
|
|
9
9
|
prepare: (sql: string, id?: string) => Promise<Statement> | Statement;
|
|
10
|
+
prepareMany?: (
|
|
11
|
+
statements: { sql: string; id: string }[],
|
|
12
|
+
) => Promise<Statement[]> | Statement[];
|
|
10
13
|
close: () => Promise<any> | any;
|
|
11
14
|
drop: () => Promise<any> | any;
|
|
12
15
|
open(): Promise<any> | any;
|