@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/dist/index.min.js CHANGED
@@ -21219,15 +21219,15 @@ var resolveInstanceFromValue = async (fromTablePrefixedValues, tables, table, re
21219
21219
  }
21220
21220
  return Object.assign(Object.create(table.ctor.prototype), obj);
21221
21221
  };
21222
- var convertDeleteRequestToQuery = (request, tables, table) => {
21223
- const { query, bindable } = convertRequestToQuery("delete", { query: coerceLocalQueries(request.query) }, tables, table);
21222
+ var convertDeleteRequestToQuery = (request, tables, table, options) => {
21223
+ const { query, bindable } = convertRequestToQuery("delete", { query: coerceLocalQueries(request.query) }, tables, table, void 0, [], options);
21224
21224
  return {
21225
21225
  sql: `DELETE FROM ${table.name} WHERE ${table.name}.${table.primary} IN (SELECT ${table.primary} from ${table.name} ${query}) returning ${table.primary}`,
21226
21226
  bindable
21227
21227
  };
21228
21228
  };
21229
- var convertSumRequestToQuery = (request, tables, table) => {
21230
- const { query, bindable } = convertRequestToQuery("sum", { query: coerceLocalQueries(request.query), key: request.key }, tables, table);
21229
+ var convertSumRequestToQuery = (request, tables, table, options) => {
21230
+ const { query, bindable } = convertRequestToQuery("sum", { query: coerceLocalQueries(request.query), key: request.key }, tables, table, void 0, [], options);
21231
21231
  const inlineName = getInlineTableFieldName(request.key);
21232
21232
  const field2 = table.fields.find((x) => x.name === inlineName);
21233
21233
  if (unwrapNestedType(field2.from.type) === "u64") {
@@ -21404,7 +21404,12 @@ var _buildJoin = (table, options) => {
21404
21404
  if (table.columns.length > 0) {
21405
21405
  const usedColumns = removeDuplicatesOrdered(table.columns);
21406
21406
  const usesImplicitPrimaryKeyIndex = table.type === "root" && table.table.primary !== false && usedColumns.length === 1 && usedColumns[0] === table.table.primary;
21407
- indexedBy = options?.planner && !usesImplicitPrimaryKeyIndex ? ` INDEXED BY ${options.planner.resolveIndex(table.table.name, usedColumns)} ` : "";
21407
+ if (options?.planner && !usesImplicitPrimaryKeyIndex) {
21408
+ const indexKey = options.planner.resolveIndex(table.table.name, usedColumns);
21409
+ indexedBy = options.planner.forceIndex ? ` INDEXED BY ${indexKey} ` : "";
21410
+ } else {
21411
+ indexedBy = "";
21412
+ }
21408
21413
  }
21409
21414
  if (table.type !== "root") {
21410
21415
  let nonInlinedParent = table.table.parent && getNonInlinedTable(table.table.parent);
@@ -21692,10 +21697,14 @@ var __runInitializers4 = function(thisArg, initializers, value) {
21692
21697
  }
21693
21698
  return useValue ? value : void 0;
21694
21699
  };
21695
- var getSortedNameKey = (tableName, names) => [tableName, ...names.sort()].join(",");
21696
- var createIndexKey = (tableName, fields) => `${tableName}_index_${fields.map((x) => x).join("_")}`;
21700
+ var getSortedNameKey = (tableName, names) => [tableName, ...[...names].sort()].join(",");
21701
+ var getIndexColumnKey = (field2) => `${field2.name}${field2.collation ? `_collate_${field2.collation.toLowerCase()}` : ""}`;
21702
+ var createIndexKey = (tableName, fields) => `${tableName}_index_${fields.map((x) => getIndexColumnKey(x).replace(/[^a-zA-Z0-9_]/g, "_")).join("_")}`;
21703
+ var createIndexColumnSQL = (field2) => `${escapeColumnName(field2.name)}${field2.collation ? ` COLLATE ${field2.collation}` : ""}`;
21697
21704
  var HALF_MAX_U32 = 2147483647;
21698
21705
  var HALF_MAX_U64 = 9223372036854775807n;
21706
+ var PARENT_TABLE_ID2 = "__parent_id";
21707
+ var AMBIGUOUS_CHILD_FORCE_AFTER_USES = 6e3;
21699
21708
  var flattenQuery = function* (props) {
21700
21709
  if (!props) {
21701
21710
  return yield props;
@@ -21850,21 +21859,40 @@ var QueryPlanner = class {
21850
21859
  let indexCreateCommands = void 0;
21851
21860
  let pickedIndexKeys = /* @__PURE__ */ new Map();
21852
21861
  let indexCreationPromiseToAwait = [];
21862
+ let forceIndex = this.props.forceIndexes !== false;
21853
21863
  return {
21864
+ get forceIndex() {
21865
+ return forceIndex;
21866
+ },
21854
21867
  beforePrepare: async () => {
21855
21868
  if (indexCreateCommands != null) {
21856
- for (const { key, cmd, deferred } of indexCreateCommands) {
21869
+ const commandsToCreate = [];
21870
+ for (const command of indexCreateCommands) {
21871
+ if (this.pendingIndexCreation.has(command.key)) {
21872
+ await this.pendingIndexCreation.get(command.key);
21873
+ continue;
21874
+ }
21875
+ commandsToCreate.push(command);
21876
+ }
21877
+ if (commandsToCreate.length > 0) {
21878
+ const creationPromise = Promise.resolve(this.props.exec([
21879
+ ...commandsToCreate.map((command) => command.cmd),
21880
+ ...this.props.optimizeAfterCreate === false ? [] : ["PRAGMA optimize"]
21881
+ ].join(";")));
21882
+ for (const { key } of commandsToCreate) {
21883
+ this.pendingIndexCreation.set(key, creationPromise);
21884
+ }
21857
21885
  try {
21858
- if (this.pendingIndexCreation.has(key)) {
21859
- await this.pendingIndexCreation.get(key);
21886
+ await creationPromise;
21887
+ for (const { key, deferred } of commandsToCreate) {
21888
+ this.pendingIndexCreation.delete(key);
21889
+ deferred.resolve();
21860
21890
  }
21861
- const promise = this.props.exec(cmd);
21862
- this.pendingIndexCreation.set(key, promise);
21863
- await promise;
21864
- this.pendingIndexCreation.delete(key);
21865
- deferred.resolve();
21866
21891
  } catch (error2) {
21867
- deferred.reject(error2);
21892
+ for (const { key, deferred } of commandsToCreate) {
21893
+ this.pendingIndexCreation.delete(key);
21894
+ deferred.reject(error2);
21895
+ }
21868
21896
  }
21869
21897
  }
21870
21898
  }
@@ -21876,6 +21904,7 @@ var QueryPlanner = class {
21876
21904
  await Promise.all(indexCreationPromiseToAwait);
21877
21905
  },
21878
21906
  resolveIndex: (tableName, columns) => {
21907
+ forceIndex = this.props.forceIndexes !== false;
21879
21908
  const sortedNameKey = getSortedNameKey(tableName, columns);
21880
21909
  let indexStats = obj.columnsToIndexes.get(sortedNameKey);
21881
21910
  if (indexStats === void 0) {
@@ -21885,10 +21914,10 @@ var QueryPlanner = class {
21885
21914
  obj.columnsToIndexes.set(sortedNameKey, indexStats);
21886
21915
  }
21887
21916
  if (indexStats.results.length === 0) {
21888
- const permutations = generatePermutations(columns);
21889
- for (const columns2 of permutations) {
21917
+ const candidates = generateIndexCandidates(query, columns);
21918
+ for (const columns2 of candidates) {
21890
21919
  const indexKey = createIndexKey(tableName, columns2);
21891
- const command = `create index if not exists ${indexKey} on ${tableName} (${columns2.map((n) => escapeColumnName(n)).join(", ")})`;
21920
+ const command = `create index if not exists ${indexKey} on ${tableName} (${columns2.map((n) => createIndexColumnSQL(n)).join(", ")})`;
21892
21921
  let deferred = pDefer();
21893
21922
  (indexCreateCommands || (indexCreateCommands = [])).push({
21894
21923
  cmd: command,
@@ -21905,11 +21934,17 @@ var QueryPlanner = class {
21905
21934
  avg: -1,
21906
21935
  // setting -1 will force the first time to be the fastest (i.e. new indices are always tested once)
21907
21936
  indexKey,
21937
+ columns: columns2,
21908
21938
  created: () => created,
21909
21939
  creationPromiseDeferred: deferred
21910
21940
  });
21911
21941
  }
21912
21942
  }
21943
+ const isAmbiguousChildPredicate = query.sort.length === 0 && columns.includes(PARENT_TABLE_ID2) && columns.length > 1;
21944
+ if (isAmbiguousChildPredicate) {
21945
+ const totalUses = indexStats.results.reduce((sum, result) => sum + result.used, 0);
21946
+ forceIndex = this.props.forceIndexes !== false && totalUses >= AMBIGUOUS_CHILD_FORCE_AFTER_USES;
21947
+ }
21913
21948
  let fastestIndex = indexStats.results[0];
21914
21949
  fastestIndex.used++;
21915
21950
  if (!fastestIndex.created()) {
@@ -21944,10 +21979,132 @@ var QueryPlanner = class {
21944
21979
  };
21945
21980
  }
21946
21981
  };
21947
- var generatePermutations = (list) => {
21948
- if (list.length === 1)
21949
- return [list];
21950
- return [list, [...list].reverse()];
21982
+ var queryKeyToColumnName = (key) => {
21983
+ if (key.length > 2) {
21984
+ return `${key.slice(0, -1).join("_")}__${key[key.length - 1]}`;
21985
+ }
21986
+ return key.join("__");
21987
+ };
21988
+ var pushUniqueColumn = (list, column) => {
21989
+ const key = getIndexColumnKey(column);
21990
+ if (!list.some((x) => getIndexColumnKey(x) === key)) {
21991
+ list.push(column);
21992
+ }
21993
+ };
21994
+ var pushColumns = (target, columns) => {
21995
+ for (const column of columns) {
21996
+ pushUniqueColumn(target, column);
21997
+ }
21998
+ };
21999
+ var getIndexableQueryColumns = (query, availableColumns) => {
22000
+ const equality = [];
22001
+ const range = [];
22002
+ const visit = (item, path = []) => {
22003
+ if (item instanceof And) {
22004
+ for (const condition of item.and) {
22005
+ visit(condition, path);
22006
+ }
22007
+ return;
22008
+ }
22009
+ if (item instanceof Or) {
22010
+ for (const condition of item.or) {
22011
+ visit(condition, path);
22012
+ }
22013
+ return;
22014
+ }
22015
+ if (item instanceof Not) {
22016
+ return;
22017
+ }
22018
+ if (item instanceof Nested) {
22019
+ for (const condition of item.query) {
22020
+ visit(condition, [...path, ...item.path]);
22021
+ }
22022
+ return;
22023
+ }
22024
+ let key;
22025
+ let target;
22026
+ let collation;
22027
+ if (item instanceof IntegerCompare) {
22028
+ key = item.key;
22029
+ target = item.compare === Compare.Equal ? equality : range;
22030
+ } else if (item instanceof StringMatch) {
22031
+ key = item.key;
22032
+ if (item.method === StringMatchMethod.contains) {
22033
+ return;
22034
+ }
22035
+ target = item.method === StringMatchMethod.exact ? equality : range;
22036
+ collation = item.caseInsensitive ? "NOCASE" : void 0;
22037
+ } else if (item instanceof ByteMatchQuery || item instanceof BoolQuery || item instanceof IsNull) {
22038
+ key = item.key;
22039
+ target = equality;
22040
+ }
22041
+ if (!key || !target) {
22042
+ return;
22043
+ }
22044
+ const columnName = queryKeyToColumnName([...path, ...key]);
22045
+ if (availableColumns.has(columnName)) {
22046
+ pushUniqueColumn(target, { name: columnName, collation });
22047
+ }
22048
+ };
22049
+ for (const item of query) {
22050
+ visit(item);
22051
+ }
22052
+ return { equality, range };
22053
+ };
22054
+ var getSortableColumns = (sort, availableColumns) => {
22055
+ const out = [];
22056
+ for (const item of sort) {
22057
+ const columnName = queryKeyToColumnName(item.key);
22058
+ if (availableColumns.has(columnName)) {
22059
+ pushUniqueColumn(out, { name: columnName });
22060
+ }
22061
+ }
22062
+ return out;
22063
+ };
22064
+ var normalizeCandidate = (columns) => {
22065
+ const out = [];
22066
+ pushColumns(out, columns);
22067
+ return out;
22068
+ };
22069
+ var generateIndexCandidates = (query, columns) => {
22070
+ if (columns.length === 0) {
22071
+ return [];
22072
+ }
22073
+ const availableColumns = new Set(columns);
22074
+ const { equality, range } = getIndexableQueryColumns(query.query, availableColumns);
22075
+ const sort = getSortableColumns(query.sort, availableColumns);
22076
+ const join = availableColumns.has(PARENT_TABLE_ID2) ? [{ name: PARENT_TABLE_ID2 }] : [];
22077
+ const knownColumnNames = new Set([...join, ...equality, ...range, ...sort].map((x) => x.name));
22078
+ const remaining = columns.filter((column) => !knownColumnNames.has(column)).map((name) => ({ name }));
22079
+ const candidates = [];
22080
+ const pushCandidate = (...parts) => {
22081
+ const candidate = normalizeCandidate(parts.flat());
22082
+ if (candidate.length > 0 && !candidates.some((existing) => existing.map(getIndexColumnKey).join(",") === candidate.map(getIndexColumnKey).join(","))) {
22083
+ candidates.push(candidate);
22084
+ }
22085
+ };
22086
+ if (sort.length > 0 && range.length > 0) {
22087
+ pushCandidate(join, equality, sort, range, remaining);
22088
+ pushCandidate(join, equality, range, sort, remaining);
22089
+ } else if (sort.length > 0) {
22090
+ pushCandidate(join, equality, sort, range, remaining);
22091
+ } else {
22092
+ if (join.length > 0 && (equality.length > 0 || range.length > 0)) {
22093
+ pushCandidate(equality, range, join, remaining);
22094
+ }
22095
+ pushCandidate(join, equality, range, remaining);
22096
+ }
22097
+ if (join.length > 0 && (equality.length > 0 || range.length > 0)) {
22098
+ if (sort.length > 0 && range.length > 0) {
22099
+ pushCandidate(equality, sort, range, join, remaining);
22100
+ pushCandidate(equality, range, sort, join, remaining);
22101
+ } else if (sort.length > 0) {
22102
+ pushCandidate(equality, range, sort, join, remaining);
22103
+ }
22104
+ }
22105
+ pushCandidate(columns.map((name) => ({ name })));
22106
+ pushCandidate([...columns].reverse().map((name) => ({ name })));
22107
+ return candidates;
21951
22108
  };
21952
22109
 
21953
22110
  // dist/src/utils.js
@@ -22013,13 +22170,16 @@ var createBatchInsertSQL = (table, rows) => {
22013
22170
  length: rows
22014
22171
  }).map(() => rowPlaceholder).join(", ")};`;
22015
22172
  };
22173
+ var createInsertReturningSQL = (table) => `insert into ${table.name} (${table.fields.map((field2) => escapeColumnName(field2.name)).join(", ")}) VALUES (${table.fields.map((_x) => "?").join(", ")}) RETURNING ${table.primary};`;
22174
+ var createInsertKnownIdSQL = (table) => `insert into ${table.name} (${table.fields.map((field2) => escapeColumnName(field2.name)).join(", ")}) VALUES (${table.fields.map((_x) => "?").join(", ")});`;
22175
+ var createReplaceSQL = (table) => `insert or replace into ${table.name} (${table.fields.map((field2) => escapeColumnName(field2.name)).join(", ")}) VALUES (${table.fields.map((_x) => "?").join(", ")});`;
22016
22176
  var canUseWithoutRowId = (table) => {
22017
22177
  if (table.inline || table.primary === false || !table.primaryField) {
22018
22178
  return false;
22019
22179
  }
22020
22180
  return !/^INTEGER\b/i.test(table.primaryField.type);
22021
22181
  };
22022
- var SQLiteIndex = class {
22182
+ var SQLiteIndex = class _SQLiteIndex {
22023
22183
  properties;
22024
22184
  // SQLite writes are inherently serialized per connection.
22025
22185
  // We still need an explicit async barrier because our API is async and
@@ -22049,13 +22209,14 @@ var SQLiteIndex = class {
22049
22209
  primaryKeyString;
22050
22210
  planner;
22051
22211
  scopeString;
22052
- _rootTables;
22053
- _tables;
22054
- _cursor;
22212
+ _rootTables = [];
22213
+ _tables = /* @__PURE__ */ new Map();
22214
+ _cursor = /* @__PURE__ */ new Map();
22055
22215
  // TODO choose limit better
22056
22216
  cursorPruner;
22057
22217
  iteratorTimeout;
22058
22218
  closed = true;
22219
+ state = "closed";
22059
22220
  fkMode;
22060
22221
  id;
22061
22222
  constructor(properties, options) {
@@ -22072,21 +22233,74 @@ var SQLiteIndex = class {
22072
22233
  persisted() {
22073
22234
  return this.properties.persisted ?? true;
22074
22235
  }
22236
+ static _emptyTables = /* @__PURE__ */ new Map();
22237
+ static _emptyRootTables = [];
22238
+ static _emptyCursor = /* @__PURE__ */ new Map();
22239
+ static closedIterator() {
22240
+ return {
22241
+ all: async () => [],
22242
+ close: async () => void 0,
22243
+ done: () => true,
22244
+ next: async () => [],
22245
+ pending: async () => 0
22246
+ };
22247
+ }
22248
+ async ifOpen(fallback, fn) {
22249
+ if (this.isClosing()) {
22250
+ return fallback;
22251
+ }
22252
+ this.assertOpen();
22253
+ try {
22254
+ return await fn();
22255
+ } catch (error2) {
22256
+ if (this.isClosing()) {
22257
+ return fallback;
22258
+ }
22259
+ throw error2;
22260
+ }
22261
+ }
22262
+ async withWriteIfOpen(fallback, fn) {
22263
+ if (this.isClosing()) {
22264
+ return fallback;
22265
+ }
22266
+ this.assertOpen();
22267
+ return this.withWriteBarrier(() => this.ifOpen(fallback, fn));
22268
+ }
22269
+ assertOpen() {
22270
+ if (this.state !== "open") {
22271
+ throw new NotStartedError();
22272
+ }
22273
+ }
22274
+ isClosing() {
22275
+ return this.state === "closing";
22276
+ }
22277
+ setClosing() {
22278
+ this.state = "closing";
22279
+ this.closed = true;
22280
+ }
22281
+ setClosed() {
22282
+ this.state = "closed";
22283
+ this.closed = true;
22284
+ }
22285
+ setOpen() {
22286
+ this.state = "open";
22287
+ this.closed = false;
22288
+ }
22075
22289
  get tables() {
22076
22290
  if (this.closed) {
22077
- throw new NotStartedError();
22291
+ return _SQLiteIndex._emptyTables;
22078
22292
  }
22079
22293
  return this._tables;
22080
22294
  }
22081
22295
  get rootTables() {
22082
22296
  if (this.closed) {
22083
- throw new NotStartedError();
22297
+ return _SQLiteIndex._emptyRootTables;
22084
22298
  }
22085
22299
  return this._rootTables;
22086
22300
  }
22087
22301
  get cursor() {
22088
22302
  if (this.closed) {
22089
- throw new NotStartedError();
22303
+ return _SQLiteIndex._emptyCursor;
22090
22304
  }
22091
22305
  return this._cursor;
22092
22306
  }
@@ -22107,9 +22321,12 @@ var SQLiteIndex = class {
22107
22321
  return this;
22108
22322
  }
22109
22323
  async start() {
22110
- if (this.closed === false) {
22324
+ if (this.state === "open") {
22111
22325
  return;
22112
22326
  }
22327
+ if (this.state === "closing") {
22328
+ throw new NotStartedError();
22329
+ }
22113
22330
  if (this.primaryKeyArr == null || this.primaryKeyArr.length === 0) {
22114
22331
  throw new Error("Not initialized");
22115
22332
  }
@@ -22127,6 +22344,8 @@ var SQLiteIndex = class {
22127
22344
  );
22128
22345
  this._rootTables = tables.filter((x) => x.parent == null);
22129
22346
  const allTables = tables;
22347
+ const startupStatements = [];
22348
+ const startupTableStatements = /* @__PURE__ */ new Map();
22130
22349
  for (const table of allTables) {
22131
22350
  this._tables.set(table.name, table);
22132
22351
  for (const child of table.children) {
@@ -22137,15 +22356,38 @@ var SQLiteIndex = class {
22137
22356
  }
22138
22357
  const tableOptions = canUseWithoutRowId(table) ? " strict, without rowid" : " strict";
22139
22358
  const sqlCreateTable = `create table if not exists ${table.name} (${[...table.fields, ...table.constraints].map((s) => s.definition).join(", ")})${tableOptions}`;
22140
- this.properties.db.exec(sqlCreateTable);
22141
- let sqlPut = `insert into ${table.name} (${table.fields.map((field2) => escapeColumnName(field2.name)).join(", ")}) VALUES (${table.fields.map((_x) => "?").join(", ")}) RETURNING ${table.primary};`;
22142
- let sqlInsertKnownId = `insert into ${table.name} (${table.fields.map((field2) => escapeColumnName(field2.name)).join(", ")}) VALUES (${table.fields.map((_x) => "?").join(", ")});`;
22143
- let sqlReplace = `insert or replace into ${table.name} (${table.fields.map((field2) => escapeColumnName(field2.name)).join(", ")}) VALUES (${table.fields.map((_x) => "?").join(", ")});`;
22144
- await this.properties.db.prepare(sqlPut, putStatementKey(table));
22145
- await this.properties.db.prepare(sqlInsertKnownId, insertKnownIdStatementKey(table));
22146
- await this.properties.db.prepare(sqlReplace, replaceStatementKey(table));
22359
+ startupTableStatements.set(table.name, sqlCreateTable);
22360
+ startupStatements.push({
22361
+ id: putStatementKey(table),
22362
+ sql: createInsertReturningSQL(table)
22363
+ }, {
22364
+ id: insertKnownIdStatementKey(table),
22365
+ sql: createInsertKnownIdSQL(table)
22366
+ }, {
22367
+ id: replaceStatementKey(table),
22368
+ sql: createReplaceSQL(table)
22369
+ });
22147
22370
  if (table.parent) {
22148
- await this.properties.db.prepare(selectChildren(table), resolveChildrenStatement(table));
22371
+ startupStatements.push({
22372
+ id: resolveChildrenStatement(table),
22373
+ sql: selectChildren(table)
22374
+ });
22375
+ }
22376
+ }
22377
+ if (startupTableStatements.size > 0) {
22378
+ const existingTables = await this.getExistingSQLiteObjects("table", [
22379
+ ...startupTableStatements.keys()
22380
+ ]);
22381
+ const missingTableStatements = [...startupTableStatements.entries()].filter(([tableName]) => !existingTables.has(tableName)).map(([, sql]) => sql);
22382
+ if (missingTableStatements.length > 0) {
22383
+ await this.properties.db.exec(missingTableStatements.join(";"));
22384
+ }
22385
+ }
22386
+ if (this.properties.db.prepareMany) {
22387
+ await this.properties.db.prepareMany(startupStatements);
22388
+ } else {
22389
+ for (const statement of startupStatements) {
22390
+ await this.properties.db.prepare(statement.sql, statement.id);
22149
22391
  }
22150
22392
  }
22151
22393
  this.cursorPruner = setInterval(() => {
@@ -22156,7 +22398,17 @@ var SQLiteIndex = class {
22156
22398
  }
22157
22399
  }
22158
22400
  }, this.iteratorTimeout);
22159
- this.closed = false;
22401
+ this.setOpen();
22402
+ }
22403
+ async getExistingSQLiteObjects(type, names) {
22404
+ if (names.length === 0) {
22405
+ return /* @__PURE__ */ new Set();
22406
+ }
22407
+ const sql = `select name from sqlite_master where type = ? and name in (${names.map(() => "?").join(", ")})`;
22408
+ const statement = this.properties.db.statements.get(sql) || await this.properties.db.prepare(sql, sql);
22409
+ const rows = await statement.all([type, ...names]);
22410
+ await statement.reset?.();
22411
+ return new Set(rows.map((row) => row.name).filter((name) => typeof name === "string"));
22160
22412
  }
22161
22413
  async clearStatements() {
22162
22414
  if (await this.properties.db.status() === "closed") {
@@ -22164,52 +22416,82 @@ var SQLiteIndex = class {
22164
22416
  }
22165
22417
  }
22166
22418
  async stop() {
22167
- if (this.closed) {
22419
+ if (this.state === "closed") {
22168
22420
  return;
22169
22421
  }
22170
- this.closed = true;
22171
- clearInterval(this.cursorPruner);
22172
- await this.clearStatements();
22173
- this._tables.clear();
22174
- for (const [k, _v] of this._cursor) {
22175
- await this.clearupIterator(k);
22176
- }
22177
- await this.planner.stop();
22178
- }
22179
- async drop() {
22180
- if (!this.closed) {
22181
- this.closed = true;
22422
+ if (this.state === "closing") {
22423
+ await this._writeBarrier.catch(() => void 0);
22424
+ return;
22182
22425
  }
22183
- if (this.cursorPruner != null) {
22426
+ this.setClosing();
22427
+ try {
22184
22428
  clearInterval(this.cursorPruner);
22185
- this.cursorPruner = void 0;
22186
- }
22187
- const status = await this.properties.db.status?.();
22188
- if (status === "closed") {
22189
- this._tables.clear();
22190
- return;
22429
+ await this._writeBarrier.catch(() => void 0);
22430
+ await this.clearStatements();
22431
+ this._tables?.clear();
22432
+ if (this._cursor) {
22433
+ for (const [k, _v] of this._cursor) {
22434
+ await this.clearupIterator(k);
22435
+ }
22436
+ }
22437
+ await this.planner.stop();
22438
+ } finally {
22439
+ this.setClosed();
22191
22440
  }
22192
- await this.clearStatements();
22193
- for (const table of this._rootTables) {
22194
- await this.properties.db.exec(`drop table if exists ${table.name}`);
22441
+ }
22442
+ async drop() {
22443
+ const wasOpen = this.state === "open";
22444
+ if (wasOpen) {
22445
+ this.setClosing();
22195
22446
  }
22196
- this._tables.clear();
22197
- for (const [k, _v] of this._cursor) {
22198
- await this.clearupIterator(k);
22447
+ try {
22448
+ if (this.cursorPruner != null) {
22449
+ clearInterval(this.cursorPruner);
22450
+ this.cursorPruner = void 0;
22451
+ }
22452
+ if (wasOpen) {
22453
+ await this._writeBarrier.catch(() => void 0);
22454
+ }
22455
+ const status = await this.properties.db.status?.();
22456
+ if (status === "closed") {
22457
+ this._tables?.clear();
22458
+ return;
22459
+ }
22460
+ await this.clearStatements();
22461
+ if (this._rootTables) {
22462
+ for (const table of this._rootTables) {
22463
+ await this.properties.db.exec(`drop table if exists ${table.name}`);
22464
+ }
22465
+ }
22466
+ this._tables?.clear();
22467
+ if (this._cursor) {
22468
+ for (const [k, _v] of this._cursor) {
22469
+ await this.clearupIterator(k);
22470
+ }
22471
+ }
22472
+ await this.planner.stop();
22473
+ } finally {
22474
+ this.setClosed();
22199
22475
  }
22200
- await this.planner.stop();
22201
22476
  }
22202
22477
  async resolveDependencies(parentId, table) {
22203
- const stmt = this.properties.db.statements.get(resolveChildrenStatement(table));
22478
+ const stmt = await this.getOrPrepareStatement(resolveChildrenStatement(table), selectChildren(table));
22204
22479
  const results = await stmt.all([parentId]);
22205
22480
  await stmt.reset?.();
22206
22481
  return results;
22207
22482
  }
22483
+ async getOrPrepareStatement(key, sql) {
22484
+ const existing = this.properties.db.statements.get(key);
22485
+ if (existing) {
22486
+ return existing;
22487
+ }
22488
+ return this.properties.db.prepare(sql, key);
22489
+ }
22208
22490
  async get(id, options) {
22209
- for (const table of this._rootTables) {
22210
- const { join: joinMap, selects } = selectAllFieldsFromTable(table, options?.shape);
22211
- const sql = `${generateSelectQuery(table, selects)} ${buildJoin(joinMap).join} where ${table.name}.${this.primaryKeyString} = ? limit 1`;
22212
- try {
22491
+ return this.ifOpen(void 0, async () => {
22492
+ for (const table of this._rootTables) {
22493
+ const { join: joinMap, selects } = selectAllFieldsFromTable(table, options?.shape);
22494
+ const sql = `${generateSelectQuery(table, selects)} ${buildJoin(joinMap).join} where ${table.name}.${this.primaryKeyString} = ? limit 1`;
22213
22495
  const stmt = await this.properties.db.prepare(sql, sql);
22214
22496
  const rows = await stmt.get([
22215
22497
  table.primaryField?.from?.type ? convertToSQLType(id.key, table.primaryField.from.type) : id.key
@@ -22221,17 +22503,12 @@ var SQLiteIndex = class {
22221
22503
  value: await resolveInstanceFromValue(rows, this.tables, table, this.resolveDependencies.bind(this), true, options?.shape),
22222
22504
  id
22223
22505
  };
22224
- } catch (error2) {
22225
- if (this.closed) {
22226
- throw new NotStartedError();
22227
- }
22228
- throw error2;
22229
22506
  }
22230
- }
22231
- return void 0;
22507
+ return void 0;
22508
+ });
22232
22509
  }
22233
22510
  async put(value, _id, options) {
22234
- return this.withWriteBarrier(async () => {
22511
+ return this.withWriteIfOpen(void 0, async () => {
22235
22512
  const classOfValue = value.constructor;
22236
22513
  return insert(async (values, table) => {
22237
22514
  let preId = values[table.primaryIndex];
@@ -22240,7 +22517,7 @@ var SQLiteIndex = class {
22240
22517
  if (preId != null) {
22241
22518
  const shouldReplace = options?.replace ?? true;
22242
22519
  if (!shouldReplace) {
22243
- statement = this.properties.db.statements.get(insertKnownIdStatementKey(table));
22520
+ statement = await this.getOrPrepareStatement(insertKnownIdStatementKey(table), createInsertKnownIdSQL(table));
22244
22521
  try {
22245
22522
  this.fkMode === "race-tolerant" ? await runIgnoreFK(statement, values) : await statement.run(values);
22246
22523
  } catch (error2) {
@@ -22248,16 +22525,16 @@ var SQLiteIndex = class {
22248
22525
  throw error2;
22249
22526
  }
22250
22527
  await statement.reset?.();
22251
- statement = this.properties.db.statements.get(replaceStatementKey(table));
22528
+ statement = await this.getOrPrepareStatement(replaceStatementKey(table), createReplaceSQL(table));
22252
22529
  this.fkMode === "race-tolerant" ? await runIgnoreFK(statement, values) : await statement.run(values);
22253
22530
  }
22254
22531
  } else {
22255
- statement = this.properties.db.statements.get(replaceStatementKey(table));
22532
+ statement = await this.getOrPrepareStatement(replaceStatementKey(table), createReplaceSQL(table));
22256
22533
  this.fkMode === "race-tolerant" ? await runIgnoreFK(statement, values) : await statement.run(values);
22257
22534
  }
22258
22535
  return preId;
22259
22536
  } else {
22260
- statement = this.properties.db.statements.get(putStatementKey(table));
22537
+ statement = await this.getOrPrepareStatement(putStatementKey(table), createInsertReturningSQL(table));
22261
22538
  const out = this.fkMode === "race-tolerant" ? await getIgnoreFK(statement, values) : await statement.get(values);
22262
22539
  if (out == null) {
22263
22540
  return void 0;
@@ -22284,6 +22561,10 @@ var SQLiteIndex = class {
22284
22561
  });
22285
22562
  }
22286
22563
  iterate(request, options) {
22564
+ if (this.isClosing()) {
22565
+ return _SQLiteIndex.closedIterator();
22566
+ }
22567
+ this.assertOpen();
22287
22568
  let offset = 0;
22288
22569
  let once = false;
22289
22570
  let requestId = v4_default();
@@ -22298,43 +22579,60 @@ var SQLiteIndex = class {
22298
22579
  });
22299
22580
  let planningScope;
22300
22581
  const fetch2 = async (amount) => {
22301
- kept = void 0;
22302
- if (!once) {
22303
- planningScope = this.planner.scope(normalizedQuery);
22304
- let { sql, bindable: toBind } = convertSearchRequestToQuery(normalizedQuery, this.tables, this._rootTables, {
22305
- planner: planningScope,
22306
- shape: options?.shape,
22307
- fetchAll: amount === "all"
22308
- // if we are to fetch all, we dont need stable sorting
22309
- });
22310
- sqlFetch = sql;
22311
- bindable = toBind;
22312
- await planningScope.beforePrepare();
22313
- stmt = await this.properties.db.prepare(sqlFetch, sqlFetch);
22314
- iterator.expire = Date.now() + this.iteratorTimeout;
22315
- }
22316
- once = true;
22317
- const allResults = await planningScope.perform(async () => {
22318
- const allResults2 = await stmt.all([
22319
- ...bindable,
22320
- ...amount !== "all" ? [amount, offset] : []
22321
- ]);
22322
- return allResults2;
22323
- });
22324
- let results = await Promise.all(allResults.map(async (row) => {
22325
- let selectedTable = this._rootTables.find((table) => row[getTablePrefixedField(table, this.primaryKeyString)] != null);
22326
- const value = await resolveInstanceFromValue(row, this.tables, selectedTable, this.resolveDependencies.bind(this), true, options?.shape);
22327
- return {
22328
- value,
22329
- id: toId(convertFromSQLType(row[getTablePrefixedField(selectedTable, this.primaryKeyString)], selectedTable.primaryField.from.type))
22330
- };
22331
- }));
22332
- offset += results.length;
22333
- if (amount === "all" || results.length < amount) {
22582
+ const closeAsDone = () => {
22583
+ once = true;
22334
22584
  hasMore = false;
22335
- await this.clearupIterator(requestId);
22585
+ kept = 0;
22586
+ return [];
22587
+ };
22588
+ if (this.isClosing()) {
22589
+ return closeAsDone();
22590
+ }
22591
+ this.assertOpen();
22592
+ try {
22593
+ kept = void 0;
22594
+ if (!once) {
22595
+ planningScope = this.planner.scope(normalizedQuery);
22596
+ let { sql, bindable: toBind } = convertSearchRequestToQuery(normalizedQuery, this.tables, this._rootTables, {
22597
+ planner: planningScope,
22598
+ shape: options?.shape,
22599
+ fetchAll: amount === "all"
22600
+ // if we are to fetch all, we dont need stable sorting
22601
+ });
22602
+ sqlFetch = sql;
22603
+ bindable = toBind;
22604
+ await planningScope.beforePrepare();
22605
+ stmt = await this.properties.db.prepare(sqlFetch, sqlFetch);
22606
+ iterator.expire = Date.now() + this.iteratorTimeout;
22607
+ }
22608
+ once = true;
22609
+ const allResults = await planningScope.perform(async () => {
22610
+ const allResults2 = await stmt.all([
22611
+ ...bindable,
22612
+ ...amount !== "all" ? [amount, offset] : []
22613
+ ]);
22614
+ return allResults2;
22615
+ });
22616
+ let results = await Promise.all(allResults.map(async (row) => {
22617
+ let selectedTable = this._rootTables.find((table) => row[getTablePrefixedField(table, this.primaryKeyString)] != null);
22618
+ const value = await resolveInstanceFromValue(row, this.tables, selectedTable, this.resolveDependencies.bind(this), true, options?.shape);
22619
+ return {
22620
+ value,
22621
+ id: toId(convertFromSQLType(row[getTablePrefixedField(selectedTable, this.primaryKeyString)], selectedTable.primaryField.from.type))
22622
+ };
22623
+ }));
22624
+ offset += results.length;
22625
+ if (amount === "all" || results.length < amount) {
22626
+ hasMore = false;
22627
+ await this.clearupIterator(requestId);
22628
+ }
22629
+ return results;
22630
+ } catch (error2) {
22631
+ if (this.isClosing()) {
22632
+ return closeAsDone();
22633
+ }
22634
+ throw error2;
22336
22635
  }
22337
- return results;
22338
22636
  };
22339
22637
  const iterator = {
22340
22638
  fetch: fetch2,
@@ -22356,12 +22654,20 @@ var SQLiteIndex = class {
22356
22654
  return results;
22357
22655
  },
22358
22656
  close: () => {
22657
+ once = true;
22359
22658
  hasMore = false;
22360
22659
  kept = 0;
22361
22660
  this.clearupIterator(requestId);
22362
22661
  },
22363
22662
  next: (amount) => fetch2(amount),
22364
22663
  pending: async () => {
22664
+ if (this.isClosing()) {
22665
+ once = true;
22666
+ hasMore = false;
22667
+ kept = 0;
22668
+ return 0;
22669
+ }
22670
+ this.assertOpen();
22365
22671
  if (!hasMore) {
22366
22672
  return 0;
22367
22673
  }
@@ -22373,7 +22679,12 @@ var SQLiteIndex = class {
22373
22679
  hasMore = kept > 0;
22374
22680
  return kept;
22375
22681
  },
22376
- done: () => once ? !hasMore : void 0
22682
+ done: () => {
22683
+ if (this.isClosing()) {
22684
+ return true;
22685
+ }
22686
+ return once ? !hasMore : void 0;
22687
+ }
22377
22688
  };
22378
22689
  }
22379
22690
  async clearupIterator(id) {
@@ -22384,21 +22695,29 @@ var SQLiteIndex = class {
22384
22695
  this._cursor.delete(id);
22385
22696
  }
22386
22697
  async getSize() {
22387
- if (this.tables.size === 0) {
22388
- return 0;
22389
- }
22390
- return this.count();
22698
+ return this.ifOpen(0, async () => {
22699
+ if (this.tables.size === 0) {
22700
+ return 0;
22701
+ }
22702
+ return this.count();
22703
+ });
22391
22704
  }
22392
22705
  async del(query) {
22393
- return this.withWriteBarrier(async () => {
22706
+ return this.withWriteIfOpen([], async () => {
22394
22707
  let ret = [];
22395
22708
  let once = false;
22396
22709
  let lastError = void 0;
22397
22710
  for (const table of this._rootTables) {
22398
22711
  try {
22399
- const { sql, bindable } = convertDeleteRequestToQuery(query, this.tables, table);
22712
+ const planningScope = this.planner.scope(new PlannableQuery({
22713
+ query: coerceLocalQueries(query.query)
22714
+ }));
22715
+ const { sql, bindable } = convertDeleteRequestToQuery(query, this.tables, table, {
22716
+ planner: planningScope
22717
+ });
22718
+ await planningScope.beforePrepare();
22400
22719
  const stmt = await this.properties.db.prepare(sql, sql);
22401
- const results = await stmt.all(bindable);
22720
+ const results = await planningScope.perform(async () => stmt.all(bindable));
22402
22721
  for (const result of results) {
22403
22722
  ret.push(toId(convertFromSQLType(result[table.primary], table.primaryField.from.type)));
22404
22723
  }
@@ -22418,69 +22737,79 @@ var SQLiteIndex = class {
22418
22737
  });
22419
22738
  }
22420
22739
  async sum(query) {
22421
- let ret = void 0;
22422
- let once = false;
22423
- let lastError = void 0;
22424
- let inlinedName = getInlineTableFieldName(query.key);
22425
- for (const table of this._rootTables) {
22426
- try {
22427
- if (table.fields.find((x) => x.name === inlinedName) == null) {
22428
- lastError = new MissingFieldError("Missing field: " + (Array.isArray(query.key) ? query.key : [query.key]).join("."));
22429
- continue;
22430
- }
22431
- const { sql, bindable } = convertSumRequestToQuery(query, this.tables, table);
22432
- const stmt = await this.properties.db.prepare(sql, sql);
22433
- const result = await stmt.get(bindable);
22434
- if (result != null) {
22435
- const value = result.sum;
22436
- if (ret == null) {
22437
- ret = value;
22438
- } else {
22439
- ret += value;
22740
+ return this.ifOpen(0, async () => {
22741
+ let ret = void 0;
22742
+ let once = false;
22743
+ let lastError = void 0;
22744
+ let inlinedName = getInlineTableFieldName(query.key);
22745
+ for (const table of this._rootTables) {
22746
+ try {
22747
+ if (table.fields.find((x) => x.name === inlinedName) == null) {
22748
+ lastError = new MissingFieldError("Missing field: " + (Array.isArray(query.key) ? query.key : [query.key]).join("."));
22749
+ continue;
22440
22750
  }
22441
- once = true;
22442
- }
22443
- } catch (error2) {
22444
- if (error2 instanceof MissingFieldError) {
22445
- lastError = error2;
22446
- continue;
22751
+ const planningScope = this.planner.scope(new PlannableQuery({
22752
+ query: coerceLocalQueries(query.query)
22753
+ }));
22754
+ const { sql, bindable } = convertSumRequestToQuery(query, this.tables, table, {
22755
+ planner: planningScope
22756
+ });
22757
+ await planningScope.beforePrepare();
22758
+ const stmt = await this.properties.db.prepare(sql, sql);
22759
+ const result = await planningScope.perform(async () => stmt.get(bindable));
22760
+ if (result != null) {
22761
+ const value = result.sum;
22762
+ if (ret == null) {
22763
+ ret = value;
22764
+ } else {
22765
+ ret += value;
22766
+ }
22767
+ once = true;
22768
+ }
22769
+ } catch (error2) {
22770
+ if (error2 instanceof MissingFieldError) {
22771
+ lastError = error2;
22772
+ continue;
22773
+ }
22774
+ throw error2;
22447
22775
  }
22448
- throw error2;
22449
22776
  }
22450
- }
22451
- if (!once) {
22452
- throw lastError;
22453
- }
22454
- return ret != null ? ret : 0;
22777
+ if (!once) {
22778
+ throw lastError;
22779
+ }
22780
+ return ret != null ? ret : 0;
22781
+ });
22455
22782
  }
22456
22783
  async count(request) {
22457
- let ret = 0;
22458
- let once = false;
22459
- let lastError = void 0;
22460
- for (const table of this._rootTables) {
22461
- try {
22462
- const { sql, bindable } = convertCountRequestToQuery(request, this.tables, table);
22463
- const stmt = await this.properties.db.prepare(sql, sql);
22464
- const result = await stmt.get(bindable);
22465
- if (result != null) {
22466
- ret += Number(result.count);
22467
- once = true;
22468
- }
22469
- } catch (error2) {
22470
- if (error2 instanceof MissingFieldError) {
22471
- lastError = error2;
22472
- continue;
22784
+ return this.ifOpen(0, async () => {
22785
+ let ret = 0;
22786
+ let once = false;
22787
+ let lastError = void 0;
22788
+ for (const table of this._rootTables) {
22789
+ try {
22790
+ const { sql, bindable } = convertCountRequestToQuery(request, this.tables, table);
22791
+ const stmt = await this.properties.db.prepare(sql, sql);
22792
+ const result = await stmt.get(bindable);
22793
+ if (result != null) {
22794
+ ret += Number(result.count);
22795
+ once = true;
22796
+ }
22797
+ } catch (error2) {
22798
+ if (error2 instanceof MissingFieldError) {
22799
+ lastError = error2;
22800
+ continue;
22801
+ }
22802
+ throw error2;
22473
22803
  }
22474
- throw error2;
22475
22804
  }
22476
- }
22477
- if (!once) {
22478
- throw lastError;
22479
- }
22480
- return ret;
22805
+ if (!once) {
22806
+ throw lastError;
22807
+ }
22808
+ return ret;
22809
+ });
22481
22810
  }
22482
22811
  get cursorCount() {
22483
- return this.cursor.size;
22812
+ return this.closed ? 0 : this._cursor.size;
22484
22813
  }
22485
22814
  };
22486
22815
  var SQLiteIndices = class _SQLiteIndices {
@@ -22539,7 +22868,10 @@ var SQLiteIndices = class _SQLiteIndices {
22539
22868
  async start() {
22540
22869
  this.closed = false;
22541
22870
  if (!this.properties.parent) {
22542
- await this.properties.db.open();
22871
+ const status = await this.properties.db.status();
22872
+ if (status !== "open") {
22873
+ await this.properties.db.open();
22874
+ }
22543
22875
  }
22544
22876
  for (const scope of this.scopes.values()) {
22545
22877
  await scope.start();
@@ -22830,7 +23162,6 @@ var create = async (directory, options) => {
22830
23162
  directory: poolDirectory
22831
23163
  });
22832
23164
  poolUtil = activePoolUtil;
22833
- await activePoolUtil.reserveMinimumCapacity(100);
22834
23165
  sqliteDb = new activePoolUtil.OpfsSAHPoolDb(dbFileName);
22835
23166
  } else {
22836
23167
  sqliteDb = new sqlite32.oo1.DB(":memory:");
@@ -23087,6 +23418,27 @@ var ProxyDatabase = class {
23087
23418
  }
23088
23419
  return statement;
23089
23420
  }
23421
+ async prepareMany(statements) {
23422
+ if (statements.length === 0) {
23423
+ return [];
23424
+ }
23425
+ const missing = statements.filter((statement) => !this.statements.get(statement.id));
23426
+ if (missing.length > 0) {
23427
+ const statementIds = await this.send({
23428
+ type: "prepare-many",
23429
+ statements: missing,
23430
+ id: v4_default(),
23431
+ databaseId: this.databaseId
23432
+ });
23433
+ for (const [index, statementId] of statementIds.entries()) {
23434
+ const definition = missing[index];
23435
+ const statement = new ProxyStatement(this.send.bind(this), this.databaseId, statementId, definition.sql, this.options);
23436
+ this.statements.set(statementId, statement);
23437
+ this.statements.set(definition.id, statement);
23438
+ }
23439
+ }
23440
+ return statements.map((statement) => this.statements.get(statement.id));
23441
+ }
23090
23442
  async open() {
23091
23443
  return this.send({ type: "open", id: v4_default(), databaseId: this.databaseId });
23092
23444
  }