@minnowdb/core 0.10.0 → 0.10.2

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 (37) hide show
  1. package/dist/engine/auto-store.d.ts +15 -3
  2. package/dist/engine/auto-store.js +48 -6
  3. package/dist/engine/client.js +23 -7
  4. package/dist/engine/database.js +347 -862
  5. package/dist/engine/index-terms.js +627 -0
  6. package/dist/engine/index.d.ts +1 -1
  7. package/dist/engine/index.js +2 -1
  8. package/dist/engine/live-maintenance.js +220 -0
  9. package/dist/engine/schema.js +2 -1
  10. package/dist/engine/sql-functions.js +3 -2
  11. package/dist/engine/sql-quote.js +6 -0
  12. package/dist/engine/vector.js +1 -3
  13. package/dist/engine/worker-host.js +2 -0
  14. package/dist/engine/worker-server.js +4 -5
  15. package/dist/engine/worker-store-auto.js +2 -2
  16. package/dist/engine/write-coordinator.js +21 -1
  17. package/dist/storage/indexeddb.d.ts +18 -0
  18. package/dist/storage/indexeddb.js +293 -211
  19. package/dist/storage/opfs/coordination-helpers.js +54 -0
  20. package/dist/storage/opfs/index.d.ts +1 -1
  21. package/dist/storage/opfs/index.js +3 -2
  22. package/dist/storage/opfs/leader.js +44 -5
  23. package/dist/storage/opfs/rpc.js +3 -1
  24. package/dist/storage/opfs/store.d.ts +12 -0
  25. package/dist/storage/opfs/store.js +59 -12
  26. package/dist/storage/toolkit/wal.js +16 -0
  27. package/dist/storage/toolkit/wire.js +3 -3
  28. package/dist/storage/types.d.ts +35 -4
  29. package/dist/storage/types.js +17 -4
  30. package/dist/testing/block-store-conformance.js +40 -1
  31. package/dist/testing/index.d.ts +1 -0
  32. package/dist/testing/index.js +9 -0
  33. package/dist/testing/interaction-simulator.d.ts +319 -0
  34. package/dist/testing/interaction-simulator.js +1631 -0
  35. package/dist/transactions/index.d.ts +7 -0
  36. package/dist/transactions/index.js +17 -3
  37. package/package.json +4 -1
@@ -1,4 +1,5 @@
1
1
  import { coordinateWrite } from "./write-coordinator.js";
2
+ import { quoteSqlIdentifier } from "./sql-quote.js";
2
3
  import { LiveAggregate } from "./live-aggregate.js";
3
4
  import { stageLiveExecution } from "./live-accept.js";
4
5
  import { QueryGenerations } from "./query-generations.js";
@@ -18,8 +19,8 @@ import { dateIsoString, dateMilliseconds } from "../date-value.js";
18
19
  import { estimateCompactionRowsPerOutput, planAlignedWriteBlockRanges as writeBlockRanges } from "./write-block-planner.js";
19
20
  import { MAX_CACHEABLE_TEXT_CHARACTERS, MAX_SQL_PARAMETERS } from "./cache-limits.js";
20
21
  import { fillColumnDefaults, patchAutoIncrementValues } from "./defaults.js";
21
- import { cachedQueryTerms, FTS_TOKENIZER_VERSION, renderDocumentValue, tokenize as ftsTokenize } from "./fts.js";
22
- import { boundedMaintenanceBatchItems, simpleDataTypes, floorWholeNumberProduct, BlockReadBatchTooLargeError, validateColumnDefault, validateEnumValues, secondaryIndexColumnIds, secondaryIndexDirections, secondaryUniqueKeyNamespace, CompactionJobConflictError, CompactionBacklogError, GarbageCollectionJobConflictError, MAX_LEVEL_ZERO_SEGMENTS, MAX_FTS_CANDIDATE_ROW_IDS, MAX_FTS_POSTING_TERM_CHARACTERS, MAX_FTS_POSTINGS_PER_CHUNK, MAX_FTS_POSTING_ROW_IDS_PER_CHUNK, MAX_INDEXED_STRING_CHARACTERS, MAX_CATALOG_NAME_CHARACTERS, MAX_MANIFEST_BLOCK_PRESENCE_IDS, MAX_MAINTENANCE_BATCH_ITEMS, MAX_STORAGE_BULK_READ_ITEMS, MAX_BLOCK_READ_BATCH_BYTES, MAX_TRANSACTION_STAGE_BLOCKS, MAX_TRANSACTION_STAGE_BYTES, MAX_SNAPSHOT_SESSION_TTL_MS, MAX_POSTING_BUILD_TTL_MS, MAX_TEMP_OWNER_TTL_MS, MAX_SNAPSHOT_FRAME_BATCH_BYTES, MAX_SNAPSHOT_FRAME_BATCH_ITEMS, MAX_SNAPSHOT_METADATA_BATCH_BYTES, SNAPSHOT_FRAME_KINDS, validateStorageId, SnapshotManifestMissingError, SchemaConflictError, TableInUseError, TableRecordConflictError, TransactionRecordConflictError, UniqueKeyConflictError, UniqueIndexCoverageError, WriteConflictError } from "../storage/types.js";
22
+ import { cachedQueryTerms, FTS_TOKENIZER_VERSION } from "./fts.js";
23
+ import { boundedMaintenanceBatchItems, simpleDataTypes, floorWholeNumberProduct, BlockReadBatchTooLargeError, validateColumnDefault, validateEnumValues, secondaryIndexColumnIds, secondaryIndexDirections, secondaryUniqueKeyNamespace, CompactionJobConflictError, CompactionBacklogError, GarbageCollectionJobConflictError, MAX_LEVEL_ZERO_SEGMENTS, MAX_FTS_CANDIDATE_ROW_IDS, MAX_CATALOG_NAME_CHARACTERS, MAX_MANIFEST_BLOCK_PRESENCE_IDS, MAX_MAINTENANCE_BATCH_ITEMS, MAX_STORAGE_BULK_READ_ITEMS, MAX_BLOCK_READ_BATCH_BYTES, MAX_TRANSACTION_STAGE_BLOCKS, MAX_TRANSACTION_STAGE_BYTES, MAX_SNAPSHOT_SESSION_TTL_MS, MAX_POSTING_BUILD_TTL_MS, MAX_TEMP_OWNER_TTL_MS, MAX_SNAPSHOT_FRAME_BATCH_BYTES, MAX_SNAPSHOT_FRAME_BATCH_ITEMS, MAX_SNAPSHOT_METADATA_BATCH_BYTES, SNAPSHOT_FRAME_KINDS, validateStorageId, SnapshotManifestMissingError, SchemaConflictError, TableInUseError, TableRecordConflictError, TransactionRecordConflictError, UniqueKeyConflictError, UniqueIndexCoverageError, WriteConflictError } from "../storage/types.js";
23
24
  import { decodeSnapshotFrameStream, encodeSnapshotFrameStreamFooter, encodeSnapshotFrameStreamHeader, extendSnapshotFrameStreamChecksum, snapshotFrameEnvelopeParts, snapshotFrameStreamHeaderIdentity } from "../storage/snapshot.js";
24
25
  import { Snapshot, TransactionManager } from "../transactions/index.js";
25
26
  import { bindPendingSelectShapes, bindPlanParameters, bindStatementParameters, DUAL_TABLE, dualTableRows, blockHasSubqueries, combineUnionResults, compileCheckExpression, compileQuery, hasAggregate, createRecursiveCteState, annotateAvgArgumentScales, compileStatement, comparisonHolds, createPreparedColumnarQuery, evaluateJoinedRowExpression, evaluateRowExpression, externalizeQueryResult, markQueryResultExternal, expressionColumnNames, inferBlockSchema, inferResultColumnDomains, isDeferredInsertExpression, isDefaultInsertValue, referencedColumns, childExpressions, mapChildExpressions, expandFtsColumns, expandNaturalJoins, expandSourceColumnAliases, expandViewSources, forEachBlockExpression, forEachNestedBlock, planContainsFts, planHasNaturalJoins, planHasSourceColumnAliases, planReadsTable, planReadsViews, planReadsBeyondSingleScan, projectResultColumns, queryResultNeedsExternalization, resolveStatementDatetimes, subqueryResolutionSteps, topLevelFtsMatchConjuncts, transparentProjectionSource, unknownColumnDomains, validateDefaultExpression, windowOutputDomain, windowOutputType, volatileScalarFunctionNames, annotatePlanIntegerDivision, clonePlanTree, planMayHaveIntegerDivision, foldIdentifierCase, extendGroupByWithKeyDependents, expandRowReferences } from "./query.js";
@@ -27,11 +28,13 @@ import { copyQueryResult, planMemoKey, queryResultMemoKey, queryResultRetainedBy
27
28
  import { QueryMemoryBudgetError, QueryMemoryContext, DEFAULT_QUERY_MEMORY_BUDGET_BYTES } from "./memory.js";
28
29
  import { LiveQueryLimitError, LiveQuerySet, MAX_LIVE_QUERY_SETS_PER_DATABASE } from "./live.js";
29
30
  import { chooseJoinOrder, optimizePlan, qualifyCorrelatedReferences, renderPlan } from "./optimizer.js";
30
- import { encodeSqlEqualityValue, readUntypedText, compareSqlValues } from "./sql-semantics.js";
31
+ import { encodeSqlEqualityValue, readUntypedText } from "./sql-semantics.js";
31
32
  import { exactNumericAsNumber, exactNumericValue, externalSqlDomainValue, externalSqlTextValue, isDateDomainValue, isExactNumeric, isSqlDomainValue, normalizeSqlDomainValue, protectedSqlTextValue } from "./sql-domains.js";
32
33
  import { toCatalog } from "./catalog.js";
33
34
  import { applyColumnSteps, assertColumnDroppable, compileGeneratedColumnExpression, declaredForeignKeys, isDestructiveStep, planMigration } from "./schema.js";
34
35
  import { columnarTableFromRows, createColumnarTable, createColumnVector, vectorValue } from "./vector.js";
36
+ import { ASCENDING_NULL_COMPONENT, LOWEST_SECONDARY_COMPONENT, addFtsDocument, addSecondaryPosting, appendRowForLocator, appendRowIdLocator, assertBatchSecondaryTermsDistinct, assertNoDuplicateUniqueTerms, buildFtsColumnDeltas, buildSecondaryDeleteCoverage, buildSecondaryInsertDeltas, buildSecondaryUpdateDeltas, chunkFtsPostings, decodeSecondaryTupleTerm, getUniqueKeyColumn, keyToken, mergeSourceRowIdSpans, postingFrequencyTotal, readyUniqueSecondaryIndexes, rowIdSpanEnvelope, secondaryIndexComponentTerm, secondaryIndexTerm, secondaryIndexTermsCoverNulls, secondaryIndexUpdateNeedsPreImages, secondaryKeyLocator, secondaryTupleComponent, secondaryUniqueTerm, sortedFtsPostings, sortedSecondaryPostings, stageSecondaryUniqueInsertChanges, stageSecondaryUniqueMutationChanges } from "./index-terms.js";
37
+ import { LIVE_HIDDEN_PREFIX, LIVE_KEY_ALIAS, LIVE_MAINTENANCE_MAX_DELTA_ROWS, LIVE_ORDER_ALIAS, LIVE_WINDOW_MARGIN_MAX, LIVE_WINDOW_MARGIN_MIN, filterLiveRows, liveKeyToken, liveMaintainedOutcome, liveOrderComparator, liveRowStateBytes, mergeLiveRows, splitLiveHiddenColumns, trimLiveRows } from "./live-maintenance.js";
35
38
  import { cachedPointReadTemplate, equalRunRange, pointReadTestHooks, resolvePointReadShape, valuesAreAscending } from "./point-read.js";
36
39
  const PLATFORM_LITTLE_ENDIAN = new Uint8Array(new Uint32Array([1]).buffer)[0] === 1;
37
40
  const vectorTextDecoder = new TextDecoder("utf-8", { fatal: true });
@@ -418,9 +421,6 @@ function resolveMutationStatementDatetimes(statement, now) {
418
421
  }
419
422
  return statement;
420
423
  }
421
- function quoteSqlIdentifier(identifier) {
422
- return `"${identifier.replaceAll('"', '""')}"`;
423
- }
424
424
  const MAX_VISIBLE_SEGMENT_PAGE_ITEMS = 64;
425
425
  function streamingSnapshotExportStore(store) {
426
426
  const beginSnapshotFrameExport = store.beginSnapshotFrameExport?.bind(store);
@@ -1003,7 +1003,7 @@ class MinnowDatabase {
1003
1003
  directions: indexedColumns.map(() => "asc"),
1004
1004
  unique: true,
1005
1005
  uniqueEnforced: true,
1006
- termEncoding: "tuple-v1",
1006
+ termEncoding: "tuple-v2",
1007
1007
  storage: "postings-v1",
1008
1008
  storageColumnId: this.#createId(),
1009
1009
  locator: uniqueKeyColumn === void 0 ? "row-id" : "key-hash-v1",
@@ -4891,7 +4891,7 @@ class MinnowDatabase {
4891
4891
  if (!fires && conflictWhere === void 0 && !retiresUniqueTerms || rowCount === 0) {
4892
4892
  return void 0;
4893
4893
  }
4894
- const quote = (name) => `"${name.replaceAll('"', '""')}"`;
4894
+ const quote = quoteSqlIdentifier;
4895
4895
  const keyValues = batch.columns[keyColumn.name] ?? [];
4896
4896
  const distinct = /* @__PURE__ */ new Map();
4897
4897
  for (const value of keyValues) {
@@ -4972,7 +4972,7 @@ class MinnowDatabase {
4972
4972
  if (lookup !== void 0) {
4973
4973
  rows = await lookup(keys, "*");
4974
4974
  } else {
4975
- const quote = (name) => `"${name.replaceAll('"', '""')}"`;
4975
+ const quote = quoteSqlIdentifier;
4976
4976
  const placeholders = keys.map(() => "?").join(", ");
4977
4977
  const preImageSql = `SELECT * FROM ${quote(table.name)} WHERE ${quote(keyColumn.name)} IN (${placeholders})`;
4978
4978
  const result = readRows === void 0 ? await this.#readStoredRows(preImageSql, [...keys]) : await readRows(preImageSql, [...keys]);
@@ -5226,6 +5226,7 @@ class MinnowDatabase {
5226
5226
  tables: /* @__PURE__ */ new Map(),
5227
5227
  mirrored: true,
5228
5228
  generation: 0,
5229
+ flushedWork: 0,
5229
5230
  flushing: Promise.resolve()
5230
5231
  });
5231
5232
  let closed = false;
@@ -5247,13 +5248,14 @@ class MinnowDatabase {
5247
5248
  });
5248
5249
  }
5249
5250
  };
5251
+ const ownStagedWork = () => transaction.stagedWorkCount - (this.#scopeWrites.get(transaction)?.flushedWork ?? 0);
5250
5252
  const guarded = async (run) => {
5251
- const before = transaction.stagedWorkCount;
5253
+ const before = ownStagedWork();
5252
5254
  const generation = this.#scopeWriteGeneration(transaction);
5253
5255
  try {
5254
5256
  return await run();
5255
5257
  } catch (error) {
5256
- if (transaction.stagedWorkCount !== before || this.#scopeWriteGeneration(transaction) !== generation) {
5258
+ if (ownStagedWork() !== before || this.#scopeWriteGeneration(transaction) !== generation) {
5257
5259
  poisoned ??= error;
5258
5260
  }
5259
5261
  throw error;
@@ -5262,7 +5264,7 @@ class MinnowDatabase {
5262
5264
  const operations = {
5263
5265
  query: async (sql, options2) => {
5264
5266
  open();
5265
- return this.#withReadReservation(() => this.#sessionQuery(transaction, sql, options2));
5267
+ return externalizeQueryResult(await this.#withReadReservation(() => this.#sessionQuery(transaction, sql, options2)));
5266
5268
  },
5267
5269
  execute: async (sql, params) => {
5268
5270
  open();
@@ -5270,7 +5272,7 @@ class MinnowDatabase {
5270
5272
  if (compiled.kind === "select") {
5271
5273
  return this.#withReadReservation(async () => ({
5272
5274
  kind: "rows",
5273
- result: await this.#sessionQuery(transaction, compiled.sql, params === void 0 ? {} : { params })
5275
+ result: externalizeQueryResult(await this.#sessionQuery(transaction, compiled.sql, params === void 0 ? {} : { params }))
5274
5276
  }));
5275
5277
  }
5276
5278
  const statement = bindStatementParameters(compiled, params);
@@ -5281,14 +5283,14 @@ class MinnowDatabase {
5281
5283
  throw new TypeError(`${statement.kind.toUpperCase().replace("-", " ")} is not allowed inside a write scope`);
5282
5284
  }
5283
5285
  if (statement.kind === "update" || statement.kind === "delete") {
5284
- const keyed = await this.#scopeKeyedMutation(statement);
5286
+ const keyed = await this.#scopeKeyedMutation(transaction, statement);
5285
5287
  if (keyed !== void 0) {
5286
5288
  return guarded(() => this.#runScopeKeyedMutation(transaction, keyed, () => {
5287
5289
  staged += 1;
5288
5290
  }));
5289
5291
  }
5290
5292
  }
5291
- return writer.executeStatement(statement);
5293
+ return externalizeExecuteResult(await writer.executeStatement(statement));
5292
5294
  },
5293
5295
  insertBatch: async (tableName, input) => {
5294
5296
  open();
@@ -5455,7 +5457,7 @@ class MinnowDatabase {
5455
5457
  let point;
5456
5458
  try {
5457
5459
  pointReadTestHooks.attempted += 1;
5458
- const table = await this.#findTable(shape.table);
5460
+ const table = await this.#findTable(shape.table, transaction);
5459
5461
  point = await this.#withSessionVisibility(transaction, [table], options, (snapshot, visibility, realTables) => this.#pointReadAtSnapshot(shape, snapshot, realTables, visibility, options));
5460
5462
  } catch (error) {
5461
5463
  if (!(error instanceof UnknownTableError))
@@ -5478,7 +5480,7 @@ class MinnowDatabase {
5478
5480
  options = this.#effectiveQueryOptions(options);
5479
5481
  throwIfAborted(options.signal);
5480
5482
  const names = collectRealTableNames(plan);
5481
- const tables = await Promise.all(names.map((name) => this.#findTable(name)));
5483
+ const tables = await Promise.all(names.map((name) => this.#findTable(name, transaction)));
5482
5484
  throwIfAborted(options.signal);
5483
5485
  return this.#withSessionVisibility(transaction, tables, options, (snapshot, visibility, realTables) => this.#queryAtVisibility(plan, snapshot, visibility, realTables, true, options));
5484
5486
  }
@@ -5775,6 +5777,7 @@ class MinnowDatabase {
5775
5777
  await this.#settleScopeWriteSet(transaction, set);
5776
5778
  }
5777
5779
  async #settleScopeWriteSet(transaction, set) {
5780
+ await transaction.renewIfDue();
5778
5781
  if (set.pendingRows >= this.#rowsPerBlock) {
5779
5782
  await this.#flushScopeWriteSets(transaction, [set.table.id]);
5780
5783
  return;
@@ -5802,11 +5805,14 @@ class MinnowDatabase {
5802
5805
  const set = state.tables.get(tableId);
5803
5806
  if (set === void 0 || set.pendingRows === 0)
5804
5807
  continue;
5808
+ const before = transaction.stagedWorkCount;
5805
5809
  try {
5806
5810
  await this.#stageScopeWriteSet(transaction, state, set);
5807
5811
  } catch (error) {
5808
5812
  state.failure ??= error;
5809
5813
  throw error;
5814
+ } finally {
5815
+ state.flushedWork += transaction.stagedWorkCount - before;
5810
5816
  }
5811
5817
  }
5812
5818
  if (!state.mirrored)
@@ -5919,6 +5925,7 @@ class MinnowDatabase {
5919
5925
  tables: /* @__PURE__ */ new Map(),
5920
5926
  mirrored: false,
5921
5927
  generation,
5928
+ flushedWork: 0,
5922
5929
  flushing: Promise.resolve()
5923
5930
  });
5924
5931
  this.#stagedKeyOverlays.delete(transaction);
@@ -5960,8 +5967,33 @@ class MinnowDatabase {
5960
5967
  answered.set(token, row);
5961
5968
  }
5962
5969
  if (committed.length > 0 && transaction.snapshotVersion !== null) {
5963
- for (let start = 0; start < committed.length; start += SCOPE_KEY_LOOKUP_WINDOW) {
5964
- const window = committed.slice(start, start + SCOPE_KEY_LOOKUP_WINDOW);
5970
+ const pointRows = await this.#scopeCommittedRowsByKey(transaction, table, keyColumn, committed, names);
5971
+ const windows = [];
5972
+ if (pointRows === void 0) {
5973
+ for (let start = 0; start < committed.length; start += SCOPE_KEY_LOOKUP_WINDOW) {
5974
+ windows.push(committed.slice(start, start + SCOPE_KEY_LOOKUP_WINDOW));
5975
+ }
5976
+ }
5977
+ const patched = (rows) => {
5978
+ for (const row of rows) {
5979
+ const key = row[keyColumn.name] ?? null;
5980
+ if (key === null)
5981
+ continue;
5982
+ const token = keyToken(keyColumn.type, key);
5983
+ const patch = patches.get(token);
5984
+ if (patch !== void 0) {
5985
+ table.columns.forEach((column, position) => {
5986
+ const value = patch.values[position];
5987
+ if (value !== void 0 && wanted.has(column.name))
5988
+ row[column.name] = value;
5989
+ });
5990
+ }
5991
+ answered.set(token, row);
5992
+ }
5993
+ };
5994
+ if (pointRows !== void 0)
5995
+ patched(pointRows);
5996
+ for (const window of windows) {
5965
5997
  const plan = {
5966
5998
  sql: "(scope keyed lookup)",
5967
5999
  base: { table: table.name, alias: table.name },
@@ -5985,31 +6017,45 @@ class MinnowDatabase {
5985
6017
  version: transaction.snapshotVersion,
5986
6018
  memoize: false
5987
6019
  });
5988
- for (const row of result.rows) {
5989
- const key = row[keyColumn.name] ?? null;
5990
- if (key === null)
5991
- continue;
5992
- const token = keyToken(keyColumn.type, key);
5993
- const patch = patches.get(token);
5994
- if (patch !== void 0) {
5995
- table.columns.forEach((column, position) => {
5996
- const value = patch.values[position];
5997
- if (value !== void 0 && wanted.has(column.name))
5998
- row[column.name] = value;
5999
- });
6000
- }
6001
- answered.set(token, row);
6002
- }
6020
+ patched(result.rows);
6003
6021
  }
6004
6022
  }
6005
6023
  return [...answered.values()];
6006
6024
  }
6025
+ async #scopeCommittedRowsByKey(transaction, table, keyColumn, keys, names) {
6026
+ if (pointReadTestHooks.disabled)
6027
+ return void 0;
6028
+ const values = [];
6029
+ for (const key of keys) {
6030
+ if (typeof key !== "number" && typeof key !== "string" && typeof key !== "boolean" && !(key instanceof Date)) {
6031
+ return void 0;
6032
+ }
6033
+ values.push(key);
6034
+ }
6035
+ const { segments, records } = await this.#scopeCommittedSegments(transaction, table);
6036
+ const visibility = {
6037
+ transactions: new Map(records.map((record) => [record.id, record])),
6038
+ segmentsByTable: /* @__PURE__ */ new Map([[table.id, segments]])
6039
+ };
6040
+ const realTables = /* @__PURE__ */ new Map([[table.name, table]]);
6041
+ const select = names.map((name) => ({ column: name, alias: name }));
6042
+ return this.#withLeasedSnapshot(transaction.snapshotVersion, async (snapshot) => {
6043
+ const rows = [];
6044
+ for (const value of values) {
6045
+ const result = await this.#pointReadAtSnapshot({ table: table.name, equalities: [{ column: keyColumn.name, value }], select }, snapshot, realTables, visibility, {});
6046
+ if (result === void 0)
6047
+ return void 0;
6048
+ rows.push(...result.rows);
6049
+ }
6050
+ return rows;
6051
+ });
6052
+ }
6007
6053
  #scopeKeyLookup(transaction, table, keyColumn) {
6008
6054
  return async (keys, projection) => {
6009
6055
  const direct = await this.#scopeRowsByKey(transaction, table, keyColumn, keys, projection);
6010
6056
  if (direct !== void 0)
6011
6057
  return direct;
6012
- const quote = (name) => `"${name.replaceAll('"', '""')}"`;
6058
+ const quote = quoteSqlIdentifier;
6013
6059
  const selected = projection === "*" ? "*" : projection.map(quote).join(", ");
6014
6060
  const rows = [];
6015
6061
  for (let start = 0; start < keys.length; start += SCOPE_KEY_LOOKUP_WINDOW) {
@@ -6021,11 +6067,11 @@ class MinnowDatabase {
6021
6067
  return rows;
6022
6068
  };
6023
6069
  }
6024
- async #scopeKeyedMutation(statement) {
6070
+ async #scopeKeyedMutation(transaction, statement) {
6025
6071
  if (statement.from !== void 0 || statement.returning !== void 0 || statement.returningItems !== void 0 || statement.predicates.length !== 1) {
6026
6072
  return void 0;
6027
6073
  }
6028
- const table = await this.#findTable(statement.table);
6074
+ const table = await this.#findTable(statement.table, transaction);
6029
6075
  const keyColumn = getUniqueKeyColumn(table);
6030
6076
  if (keyColumn === void 0 || keyColumn.hidden === true || keyColumn.sqlDomain !== void 0) {
6031
6077
  return void 0;
@@ -6084,6 +6130,88 @@ class MinnowDatabase {
6084
6130
  return void 0;
6085
6131
  return { kind: "update", table, keyColumn, keys, changes };
6086
6132
  }
6133
+ async #assertInsertKeysAbsent(transaction, table, batch, keys) {
6134
+ const inScope = this.#scopeWrites.has(transaction);
6135
+ const keyColumn = getUniqueKeyColumn(table);
6136
+ if (keys !== void 0 && keyColumn !== void 0) {
6137
+ const present = inScope ? await this.#scopeKeyPresence(transaction, table, keyColumn, [...keys.values()]) : this.#stagedKeyOverlay(transaction, table.id).added;
6138
+ for (const [token, value] of keys) {
6139
+ if (present.has(token)) {
6140
+ throw new UniqueConstraintError(table.name, publicKeyName(table, keyColumn), value);
6141
+ }
6142
+ }
6143
+ }
6144
+ if (!inScope)
6145
+ return;
6146
+ const rowCount = batch.rowCount ?? Object.values(batch.columns)[0]?.length ?? 0;
6147
+ for (const { indexId, index, columns } of readyUniqueSecondaryIndexes(table)) {
6148
+ const namespaceId = secondaryUniqueKeyNamespace(table.id, indexId);
6149
+ const terms = [];
6150
+ for (let row = 0; row < rowCount; row += 1) {
6151
+ const term = secondaryUniqueTerm(index, columns, columns.map((column) => batch.columns[column.name]?.[row] ?? null));
6152
+ if (term !== void 0)
6153
+ terms.push(term);
6154
+ }
6155
+ assertNoDuplicateUniqueTerms(index, terms);
6156
+ const overlay = this.#stagedKeyOverlay(transaction, namespaceId);
6157
+ const unresolved = [];
6158
+ for (const term of terms) {
6159
+ if (overlay.added.has(term)) {
6160
+ throw await this.#translateUniqueConflict(new UniqueKeyConflictError(namespaceId, term));
6161
+ }
6162
+ if (!overlay.removed.has(term))
6163
+ unresolved.push(term);
6164
+ }
6165
+ if (unresolved.length === 0)
6166
+ continue;
6167
+ const [existing] = await this.#existingUniqueKeysWindowed(namespaceId, unresolved);
6168
+ if (existing !== void 0) {
6169
+ throw await this.#translateUniqueConflict(new UniqueKeyConflictError(namespaceId, existing));
6170
+ }
6171
+ }
6172
+ }
6173
+ async #assertUpdateUniqueTermsAbsent(transaction, table, input, preImages) {
6174
+ for (const { indexId, index, columns } of readyUniqueSecondaryIndexes(table)) {
6175
+ if (!columns.some((column) => input.changes[column.name] !== void 0))
6176
+ continue;
6177
+ const namespaceId = secondaryUniqueKeyNamespace(table.id, indexId);
6178
+ const released = /* @__PURE__ */ new Set();
6179
+ const taken = [];
6180
+ preImages.forEach((old, row) => {
6181
+ if (old === void 0)
6182
+ return;
6183
+ const before = secondaryUniqueTerm(index, columns, columns.map((column) => old[column.name] ?? null));
6184
+ const after = secondaryUniqueTerm(index, columns, columns.map((column) => {
6185
+ const assigned = input.changes[column.name];
6186
+ return assigned === void 0 ? old[column.name] ?? null : assigned[row] ?? null;
6187
+ }));
6188
+ if (before === after)
6189
+ return;
6190
+ if (before !== void 0)
6191
+ released.add(before);
6192
+ if (after !== void 0)
6193
+ taken.push(after);
6194
+ });
6195
+ assertNoDuplicateUniqueTerms(index, taken);
6196
+ const overlay = this.#stagedKeyOverlay(transaction, namespaceId);
6197
+ const unresolved = [];
6198
+ for (const term of taken) {
6199
+ if (released.has(term))
6200
+ continue;
6201
+ if (overlay.added.has(term)) {
6202
+ throw await this.#translateUniqueConflict(new UniqueKeyConflictError(namespaceId, term));
6203
+ }
6204
+ if (!overlay.removed.has(term))
6205
+ unresolved.push(term);
6206
+ }
6207
+ if (unresolved.length === 0)
6208
+ continue;
6209
+ const [existing] = await this.#existingUniqueKeysWindowed(namespaceId, unresolved);
6210
+ if (existing !== void 0) {
6211
+ throw await this.#translateUniqueConflict(new UniqueKeyConflictError(namespaceId, existing));
6212
+ }
6213
+ }
6214
+ }
6087
6215
  async #scopeKeyPresence(transaction, table, keyColumn, keys) {
6088
6216
  const overlay = this.#stagedKeyOverlay(transaction, table.id);
6089
6217
  const present = /* @__PURE__ */ new Set();
@@ -6122,7 +6250,7 @@ class MinnowDatabase {
6122
6250
  return { kind: "delete", table: table.name, rowCount: deleted.rowCount };
6123
6251
  }
6124
6252
  const changes = Object.fromEntries(Object.entries(keyed.changes).map(([name, value]) => [name, present.map(() => value)]));
6125
- const updated = await this.#sessionUpdate(transaction, table.name, { keys: present, changes });
6253
+ const updated = await this.#sessionUpdate(transaction, table.name, { keys: present, changes }, 1, true);
6126
6254
  return { kind: "update", table: table.name, rowCount: updated.rowCount };
6127
6255
  }
6128
6256
  async #sessionInsert(transaction, tableName, input, kind, options, cascadeBudget = 1) {
@@ -6170,24 +6298,19 @@ class MinnowDatabase {
6170
6298
  }
6171
6299
  }
6172
6300
  await this.#assertCompactionCapacity(table, transaction);
6301
+ await this.#assertForeignKeysPresent(table, (column) => batch.columns[column] ?? [], (sql, params) => this.#sessionQuery(transaction, sql, { params }), transaction);
6173
6302
  const keys = batchKeys(table, batch);
6303
+ if (kind === "insert")
6304
+ await this.#assertInsertKeysAbsent(transaction, table, batch, keys);
6305
+ else
6306
+ assertBatchSecondaryTermsDistinct(table, batch);
6174
6307
  if (keys !== void 0) {
6175
- if (kind === "insert") {
6176
- const keyColumn = getUniqueKeyColumn(table);
6177
- const overlay = this.#stagedKeyOverlay(transaction, table.id);
6178
- for (const [token, value] of keys) {
6179
- if (overlay.added.has(token) && keyColumn !== void 0) {
6180
- throw new UniqueConstraintError(table.name, keyColumn.name, value);
6181
- }
6182
- }
6183
- }
6184
6308
  transaction.setUniqueKeyChanges({
6185
6309
  tableId: table.id,
6186
6310
  keyTokens: [...keys.keys()],
6187
6311
  requireAbsent: kind === "insert"
6188
6312
  });
6189
6313
  }
6190
- await this.#assertForeignKeysPresent(table, (column) => batch.columns[column] ?? [], (sql, params) => this.#sessionQuery(transaction, sql, { params }), transaction);
6191
6314
  const insertValueAt = (source, column, rowIndex) => source === "new" ? batch.columns[column]?.[rowIndex] ?? null : null;
6192
6315
  stageSecondaryUniqueInsertChanges(transaction, table, batch, kind === "upsert" ? sessionUpsertFirings?.oldImages : void 0);
6193
6316
  const buffered = this.#scopeWrites.has(transaction) && rowCount < SCOPE_DIRECT_STAGE_ROWS && !(table.triggers ?? []).some((trigger) => trigger.event === "insert" || trigger.event === "update");
@@ -6224,8 +6347,8 @@ class MinnowDatabase {
6224
6347
  ...generated.size === 0 ? {} : { generatedColumns: Object.fromEntries(generated) }
6225
6348
  };
6226
6349
  }
6227
- async #sessionUpdate(transaction, tableName, input, cascadeBudget = 1) {
6228
- const table = await this.#findTable(tableName);
6350
+ async #sessionUpdate(transaction, tableName, input, cascadeBudget = 1, keysVerified = false) {
6351
+ const table = await this.#findTable(tableName, transaction);
6229
6352
  await this.#assertCompactionCapacity(table, transaction);
6230
6353
  const keyColumn = getUniqueKeyColumn(table);
6231
6354
  if (keyColumn === void 0) {
@@ -6235,14 +6358,16 @@ class MinnowDatabase {
6235
6358
  rejectGeneratedUpdateAssignments(table, input);
6236
6359
  const keys = validateUpdateBatch(table, keyColumn, input);
6237
6360
  const overlay = this.#stagedKeyOverlay(transaction, table.id);
6238
- for (const [token, value] of keys) {
6239
- if (overlay.removed.has(token)) {
6240
- throw new MissingKeyError(table.name, keyColumn.name, value);
6361
+ if (!keysVerified) {
6362
+ for (const [token, value] of keys) {
6363
+ if (overlay.removed.has(token)) {
6364
+ throw new MissingKeyError(table.name, keyColumn.name, value);
6365
+ }
6366
+ }
6367
+ const committedKeys = new Map([...keys].filter(([token]) => !overlay.added.has(token)));
6368
+ if (committedKeys.size > 0) {
6369
+ await this.#assertKeysExist(table, keyColumn, transaction.snapshotVersion, committedKeys);
6241
6370
  }
6242
- }
6243
- const committedKeys = new Map([...keys].filter(([token]) => !overlay.added.has(token)));
6244
- if (committedKeys.size > 0) {
6245
- await this.#assertKeysExist(table, keyColumn, transaction.snapshotVersion, committedKeys);
6246
6371
  }
6247
6372
  const sessionChecks = table.checks ?? [];
6248
6373
  let changedForeignKey = (table.foreignKeys ?? []).some((key) => key.enforced !== false && foreignKeyColumns(key).some((column) => input.changes[column] !== void 0));
@@ -6250,6 +6375,9 @@ class MinnowDatabase {
6250
6375
  input = applyStoredGeneratedUpdateChanges(table, input, preImages);
6251
6376
  validateUpdateBatch(table, keyColumn, input);
6252
6377
  changedForeignKey = (table.foreignKeys ?? []).some((key) => key.enforced !== false && foreignKeyColumns(key).some((column) => input.changes[column] !== void 0));
6378
+ if (this.#scopeWrites.has(transaction)) {
6379
+ await this.#assertUpdateUniqueTermsAbsent(transaction, table, input, preImages);
6380
+ }
6253
6381
  stageSecondaryUniqueMutationChanges(transaction, table, input, preImages);
6254
6382
  const secondaryDeltas = buildSecondaryUpdateDeltas(table, input, preImages);
6255
6383
  if (secondaryDeltas.length > 0) {
@@ -6298,7 +6426,7 @@ class MinnowDatabase {
6298
6426
  };
6299
6427
  }
6300
6428
  async #sessionDelete(transaction, tableName, input, cascadeBudget = 1, referentialBudget = REFERENTIAL_CASCADES) {
6301
- const table = await this.#findTable(tableName);
6429
+ const table = await this.#findTable(tableName, transaction);
6302
6430
  await this.#assertCompactionCapacity(table, transaction);
6303
6431
  const keyColumn = getUniqueKeyColumn(table);
6304
6432
  if (keyColumn === void 0) {
@@ -7675,7 +7803,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
7675
7803
  }
7676
7804
  return {
7677
7805
  kind: "rows",
7678
- result: await this.query(`SELECT $1 AS ${quoteIdentifier(name)}`, { params: [value] })
7806
+ result: await this.query(`SELECT $1 AS ${quoteSqlIdentifier(name)}`, { params: [value] })
7679
7807
  };
7680
7808
  }
7681
7809
  if (statement.kind === "create-enum") {
@@ -8042,7 +8170,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
8042
8170
  }
8043
8171
  if (statement.kind === "transaction") {
8044
8172
  const action = statement.action === "rollback-to" ? "ROLLBACK TO" : statement.action;
8045
- return this.execute(`${action}${statement.name === void 0 ? "" : ` ${quoteIdentifier(statement.name)}`}`);
8173
+ return this.execute(`${action}${statement.name === void 0 ? "" : ` ${quoteSqlIdentifier(statement.name)}`}`);
8046
8174
  }
8047
8175
  if (options.writer === void 0) {
8048
8176
  return this.#coordinateWrite(async () => {
@@ -8404,9 +8532,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
8404
8532
  return void 0;
8405
8533
  const visibleBaseSegments = visibleByTable.get(freshBaseTable.name) ?? await this.#visibleSegmentRecords(freshBaseTable, snapshot, visibility);
8406
8534
  this.#maybeScheduleAutoCompaction(freshBaseTable, visibleBaseSegments);
8407
- const ftsSegments = await this.#ftsPrunedSegments(freshBaseTable, visibleBaseSegments, plan, snapshot);
8408
- throwIfAborted(options.signal);
8409
- const indexed = await this.#secondaryIndexPrunedSegments(freshBaseTable, ftsSegments, plan, snapshot);
8535
+ const indexed = await this.#indexPrunedSegments(freshBaseTable, visibleBaseSegments, plan, snapshot, visibility);
8410
8536
  throwIfAborted(options.signal);
8411
8537
  const baseSegments = indexed.segments;
8412
8538
  const zonePruned = indexed.rows === void 0 ? await this.#zonePrunedStreamSegments(plan, freshBaseTable, projectedBaseColumns, baseSegments, snapshot) : void 0;
@@ -8601,7 +8727,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
8601
8727
  return true;
8602
8728
  return keyPredicates.every((predicate) => zoneMapCanMatch(description, predicate));
8603
8729
  });
8604
- if (canAffect)
8730
+ if (segment.kind === "delete" || canAffect)
8605
8731
  prunedSegments.push(segment);
8606
8732
  continue;
8607
8733
  }
@@ -9810,13 +9936,21 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
9810
9936
  async compactTable(tableName, options = {}) {
9811
9937
  return this.#foreground(async () => {
9812
9938
  const maxBlocks = boundedMaintenanceBatchItems(options.maxBlocksPerStep ?? 16, "Compaction blocks per step");
9813
- let progress = await this.compactTableStep(tableName, { ...options, maxBlocks });
9814
- while (progress.result === null) {
9815
- if (progress.jobId === null)
9816
- throw new Error("Compaction progress lost its job ID");
9817
- progress = await this.resumeCompactionJob(progress.jobId, { maxBlocks });
9939
+ for (let attempt = 0; ; attempt += 1) {
9940
+ try {
9941
+ let progress = await this.compactTableStep(tableName, { ...options, maxBlocks });
9942
+ while (progress.result === null) {
9943
+ if (progress.jobId === null)
9944
+ throw new Error("Compaction progress lost its job ID");
9945
+ progress = await this.resumeCompactionJob(progress.jobId, { maxBlocks });
9946
+ }
9947
+ return progress.result;
9948
+ } catch (error) {
9949
+ if (!isTransientCompactionConflict(error) || attempt >= this.#maxCommitRetries) {
9950
+ throw error;
9951
+ }
9952
+ }
9818
9953
  }
9819
- return progress.result;
9820
9954
  });
9821
9955
  }
9822
9956
  async compactTableStep(tableName, options = {}) {
@@ -9920,13 +10054,21 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
9920
10054
  do {
9921
10055
  reconciliationCursor = (await this.#reconcileCompactionPage(reconciliationCursor)).nextCursor;
9922
10056
  } while (reconciliationCursor !== null);
9923
- let progress = await this.collectGarbageStep({
9924
- maxItems,
9925
- ...options.maxPlanningItems === void 0 ? {} : { maxPlanningItems: options.maxPlanningItems },
9926
- ...options.retainRecentVersions === void 0 ? {} : { retainRecentVersions: options.retainRecentVersions }
9927
- });
9928
- while (progress.result === null) {
9929
- progress = await this.resumeGarbageCollectionJob(progress.jobId, { maxItems });
10057
+ let progress;
10058
+ for (let attempt = 0; progress?.result == null; attempt += 1) {
10059
+ try {
10060
+ progress = progress === void 0 ? await this.collectGarbageStep({
10061
+ maxItems,
10062
+ ...options.maxPlanningItems === void 0 ? {} : { maxPlanningItems: options.maxPlanningItems },
10063
+ ...options.retainRecentVersions === void 0 ? {} : { retainRecentVersions: options.retainRecentVersions }
10064
+ }) : await this.resumeGarbageCollectionJob(progress.jobId, { maxItems });
10065
+ } catch (error) {
10066
+ if (!(error instanceof GarbageCollectionJobConflictError || error instanceof TransactionRecordConflictError || error instanceof CompactionJobConflictError) || attempt >= 64) {
10067
+ throw error;
10068
+ }
10069
+ progress = void 0;
10070
+ await this.#yieldMaintenance();
10071
+ }
9930
10072
  }
9931
10073
  await this.#pruneFinishedJobRecords();
9932
10074
  this.#autoCollectionDebtCommits = 0;
@@ -10012,6 +10154,12 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
10012
10154
  if (reclaimed || !moreWork)
10013
10155
  this.#autoCollectionDebtCommits = 0;
10014
10156
  }).catch((error) => {
10157
+ if (this.#closed)
10158
+ return;
10159
+ if (error instanceof GarbageCollectionJobConflictError) {
10160
+ this.#autoCollectionRequested = true;
10161
+ return;
10162
+ }
10015
10163
  this.#autoCollectionConsecutiveFailures += 1;
10016
10164
  const at = dateMilliseconds(this.#now());
10017
10165
  this.#autoCollectionLastError = {
@@ -10305,15 +10453,19 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
10305
10453
  }
10306
10454
  };
10307
10455
  let discovered;
10308
- try {
10309
- discovered = await this.store.createGarbageCollectionJob(input);
10310
- } catch (error) {
10311
- if (!(error instanceof GarbageCollectionJobConflictError))
10312
- throw error;
10313
- const raced = await this.#findActiveGarbageCollectionJob();
10314
- if (raced === void 0)
10315
- throw error;
10316
- return raced;
10456
+ for (let attempt = 0; discovered === void 0; attempt += 1) {
10457
+ try {
10458
+ discovered = await this.store.createGarbageCollectionJob(input);
10459
+ } catch (error) {
10460
+ if (!(error instanceof GarbageCollectionJobConflictError))
10461
+ throw error;
10462
+ const raced = await this.#findActiveGarbageCollectionJob();
10463
+ if (raced !== void 0)
10464
+ return raced;
10465
+ if (attempt >= 3)
10466
+ throw error;
10467
+ await this.#yieldMaintenance();
10468
+ }
10317
10469
  }
10318
10470
  if (continuation !== void 0) {
10319
10471
  await this.store.removeGarbageCollectionJob(continuation.jobId);
@@ -11295,7 +11447,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
11295
11447
  columns: columns2
11296
11448
  });
11297
11449
  }
11298
- assertCanonicalMergeSourceOrder(describedSegments);
11450
+ assertCanonicalMergeSourceOrder(sourceSegments.map((segment) => sourceOrderTuple(segment, transactions, "Compaction source")));
11299
11451
  const resolved = await this.#resolveMergeOutput(table, describedSegments, keyColumn, memoryBudgetBytes, snapshot);
11300
11452
  const { columns, rowIdSpans, totalRows } = resolved;
11301
11453
  const rowIdEnvelope = rowIdSpanEnvelope(rowIdSpans);
@@ -11774,6 +11926,12 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
11774
11926
  this.#afterCompactionCommit(manifest2, table.id);
11775
11927
  return manifest2;
11776
11928
  } catch (error) {
11929
+ if (error instanceof SchemaConflictError) {
11930
+ if (transaction.status === "active")
11931
+ await transaction.abort();
11932
+ job = await this.#abortCompactionJob(job, "Schema changed during compaction");
11933
+ throw error;
11934
+ }
11777
11935
  if (!(error instanceof WriteConflictError))
11778
11936
  throw error;
11779
11937
  publicationConflict = error;
@@ -12578,25 +12736,42 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
12578
12736
  const indexId = this.#createId();
12579
12737
  const storageColumnId = `secondary-index:${indexId}`;
12580
12738
  const buildId = this.#createId();
12581
- const marked = await this.store.updateTable(table.id, table.revision, {
12582
- secondaryIndexes: {
12583
- ...table.secondaryIndexes,
12584
- [indexId]: {
12585
- name: indexName,
12586
- columnId: columns[0]?.id ?? "",
12587
- columnIds: columns.map((column) => column.id),
12588
- directions: indexColumns.map((column) => column.direction),
12589
- ...options.unique === true ? { unique: true } : {},
12590
- termEncoding: "tuple-v1",
12591
- storage: "postings-v1",
12592
- storageColumnId,
12593
- locator: table.uniqueKeyColumnId === void 0 ? "row-id" : "key-hash-v1",
12594
- state: "building",
12595
- buildId,
12596
- buildFromVersion: -1
12739
+ let current = table;
12740
+ let marked;
12741
+ for (let attempt = 0; marked === void 0; attempt += 1) {
12742
+ try {
12743
+ marked = await this.store.updateTable(current.id, current.revision, {
12744
+ secondaryIndexes: {
12745
+ ...current.secondaryIndexes,
12746
+ [indexId]: {
12747
+ name: indexName,
12748
+ columnId: columns[0]?.id ?? "",
12749
+ columnIds: columns.map((column) => column.id),
12750
+ directions: indexColumns.map((column) => column.direction),
12751
+ ...options.unique === true ? { unique: true } : {},
12752
+ termEncoding: "tuple-v2",
12753
+ storage: "postings-v1",
12754
+ storageColumnId,
12755
+ locator: current.uniqueKeyColumnId === void 0 ? "row-id" : "key-hash-v1",
12756
+ state: "building",
12757
+ buildId,
12758
+ buildFromVersion: -1
12759
+ }
12760
+ }
12761
+ });
12762
+ } catch (error) {
12763
+ if (!(error instanceof TableRecordConflictError) || attempt >= this.#maxCommitRetries) {
12764
+ throw error;
12765
+ }
12766
+ const fresh = await this.store.getTable(current.id);
12767
+ if (fresh === void 0)
12768
+ throw new UnknownTableError(tableName);
12769
+ if (Object.values(fresh.secondaryIndexes ?? {}).some((index) => index.name === indexName)) {
12770
+ throw new TypeError(`Index already exists: ${indexName}`, { cause: error });
12597
12771
  }
12772
+ current = fresh;
12598
12773
  }
12599
- });
12774
+ }
12600
12775
  try {
12601
12776
  await this.#buildSecondaryIndex(marked, indexId);
12602
12777
  } catch (error) {
@@ -12844,8 +13019,6 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
12844
13019
  const byTerm = /* @__PURE__ */ new Map();
12845
13020
  for (let row = start; row < end; row += 1) {
12846
13021
  const values = valueVectors.map((vector, position) => vector === void 0 ? null : storedSqlValueFromExecution(columns[position], vectorValue(vector, row)));
12847
- if (values.some((value) => value === null))
12848
- continue;
12849
13022
  const locator = secondaryKeyLocator(keyColumn.type, storedSqlValueFromExecution(keyColumn, vectorValue(keyVector, row)));
12850
13023
  addSecondaryPosting(byTerm, index, columns, values, locator, uniqueTerms);
12851
13024
  }
@@ -12896,8 +13069,6 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
12896
13069
  const byTerm = /* @__PURE__ */ new Map();
12897
13070
  for (let row = start; row < end; row += 1) {
12898
13071
  const values = valueVectors.map((vector, position) => vector === void 0 ? null : storedSqlValueFromExecution(columns[position], vectorValue(vector, row)));
12899
- if (values.some((value) => value === null))
12900
- continue;
12901
13072
  const locator = keyColumn === void 0 ? rowIdAt?.(row) ?? 0n : secondaryKeyLocator(keyColumn.type, keyVector === void 0 ? null : storedSqlValueFromExecution(keyColumn, vectorValue(keyVector, row)));
12902
13073
  addSecondaryPosting(byTerm, index, columns, values, locator, uniqueTerms);
12903
13074
  }
@@ -12986,8 +13157,6 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
12986
13157
  const vector = vectors[position];
12987
13158
  return vector === void 0 ? column.backfill ?? null : vectorValue(vector, row);
12988
13159
  });
12989
- if (values.some((value) => value === null))
12990
- continue;
12991
13160
  const locator = keyColumn === void 0 ? rowIdAt?.(segmentRow + row) ?? 0n : secondaryKeyLocator(keyColumn.type, keys === void 0 ? null : vectorValue(keys, row));
12992
13161
  addSecondaryPosting(byTerm, index, columns, values, locator);
12993
13162
  }
@@ -13323,6 +13492,19 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
13323
13492
  }
13324
13493
  return surviving;
13325
13494
  }
13495
+ async #indexPrunedSegments(table, segments, plan, snapshot, visibility) {
13496
+ const own = overlayOwnedSegments(segments, visibility);
13497
+ if (own.length === 0) {
13498
+ const ftsSegments2 = await this.#ftsPrunedSegments(table, segments, plan, snapshot);
13499
+ return this.#secondaryIndexPrunedSegments(table, ftsSegments2, plan, snapshot);
13500
+ }
13501
+ if (own.some((segment) => segment.kind !== "insert"))
13502
+ return { segments, pruned: false };
13503
+ const committed = segments.filter((segment) => segment.transactionId !== visibility?.overlayTransactionId);
13504
+ const ftsSegments = await this.#ftsPrunedSegments(table, committed, plan, snapshot);
13505
+ const indexed = await this.#secondaryIndexPrunedSegments(table, ftsSegments, plan, snapshot);
13506
+ return { segments: [...indexed.segments, ...own], pruned: indexed.pruned };
13507
+ }
13326
13508
  async #secondaryIndexPrunedSegments(table, segments, plan, snapshot) {
13327
13509
  const predicates = secondaryIndexPredicates(plan, table);
13328
13510
  if (predicates.length === 0) {
@@ -13384,6 +13566,10 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
13384
13566
  let selectedRowStart = 0;
13385
13567
  const selected = [];
13386
13568
  for (const segment of segments) {
13569
+ if (segment.kind === "delete") {
13570
+ selected.push(segment);
13571
+ continue;
13572
+ }
13387
13573
  const anchorColumn2 = keyColumn ?? table.columns[0];
13388
13574
  if (anchorColumn2 === void 0)
13389
13575
  return { segments, pruned: false };
@@ -13467,7 +13653,10 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
13467
13653
  ]))
13468
13654
  });
13469
13655
  }
13470
- return rowPositions === void 0 ? { segments: selected, pruned: true } : { segments: selected, pruned: true, rows: rowPositions };
13656
+ if (rowPositions === void 0) {
13657
+ return { segments: withEveryDelta(segments, selected), pruned: true };
13658
+ }
13659
+ return { segments: selected, pruned: true, rows: rowPositions };
13471
13660
  }
13472
13661
  #scheduleSecondaryIndexBuild(table, indexId) {
13473
13662
  if (this.#closed)
@@ -13598,13 +13787,12 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
13598
13787
  }
13599
13788
  async #materializeColumnarTableAtSnapshot(table, snapshot, projectedColumns, visibility, plan) {
13600
13789
  const visibleSegments = await this.#visibleSegmentRecords(table, snapshot, visibility);
13601
- if (plan !== void 0) {
13790
+ if (plan !== void 0 && overlayOwnedSegments(visibleSegments, visibility).length === 0) {
13602
13791
  const covering = await this.#secondaryIndexCoveringTable(table, projectedColumns, visibleSegments, plan, snapshot);
13603
13792
  if (covering !== void 0)
13604
13793
  return covering;
13605
13794
  }
13606
- const ftsSegments = plan === void 0 ? visibleSegments : await this.#ftsPrunedSegments(table, visibleSegments, plan, snapshot);
13607
- const indexed = plan === void 0 ? { segments: ftsSegments, pruned: false } : await this.#secondaryIndexPrunedSegments(table, ftsSegments, plan, snapshot);
13795
+ const indexed = plan === void 0 ? { segments: visibleSegments, pruned: false } : await this.#indexPrunedSegments(table, visibleSegments, plan, snapshot, visibility);
13608
13796
  const segments = indexed.segments;
13609
13797
  const keyColumn = getUniqueKeyColumn(table);
13610
13798
  if (segments.every((segment) => {
@@ -13647,7 +13835,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
13647
13835
  const kind = segment.kind;
13648
13836
  return kind === "insert" || kind === "base" || kind === "delete" || kind === "update";
13649
13837
  })) {
13650
- const overlay = await this.#materializeOverlayTable(table, snapshot, projectedColumns, segments, keyColumn, plan, indexed.pruned);
13838
+ const overlay = await this.#materializeOverlayTable(table, snapshot, projectedColumns, indexed.rows === void 0 ? segments : withEveryDelta(visibleSegments, segments), keyColumn, plan, indexed.pruned);
13651
13839
  if (overlay !== void 0)
13652
13840
  return overlay;
13653
13841
  }
@@ -14614,13 +14802,23 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
14614
14802
  this.#gzipVerdicts.delete(columnId);
14615
14803
  return bytes;
14616
14804
  }
14617
- async #findTable(name) {
14805
+ #scopeTableRecords = /* @__PURE__ */ new WeakMap();
14806
+ async #findTable(name, scope) {
14618
14807
  validateName(name, "Table");
14808
+ const cache = scope === void 0 || !this.#scopeWrites.has(scope) ? void 0 : this.#scopeTableRecords.get(scope) ?? (() => {
14809
+ const created = /* @__PURE__ */ new Map();
14810
+ this.#scopeTableRecords.set(scope, created);
14811
+ return created;
14812
+ })();
14813
+ const cached = cache?.get(name);
14814
+ if (cached !== void 0)
14815
+ return cached;
14619
14816
  const table = await this.store.getTableByName(name) ?? await this.store.getTableByName(await this.#foldTableName(name));
14620
14817
  if (table === void 0)
14621
14818
  throw new UnknownTableError(name);
14622
14819
  if (table.view !== void 0)
14623
14820
  throw new TypeError(`${name} is a view, not a table`);
14821
+ cache?.set(name, table);
14624
14822
  return table;
14625
14823
  }
14626
14824
  async #assertVisibleSegmentCursorTable(tableName, capturedTableId) {
@@ -15281,35 +15479,6 @@ function sortedRowIdsIntersectRange(sorted, start, endExclusive) {
15281
15479
  }
15282
15480
  return low < sorted.length && (sorted[low] ?? 0n) < endExclusive;
15283
15481
  }
15284
- function addFtsDocument(byTerm, value, rowId) {
15285
- const rendered = renderDocumentValue(value);
15286
- if (rendered === void 0)
15287
- return 0;
15288
- const tokens = ftsTokenize(rendered);
15289
- const counts = /* @__PURE__ */ new Map();
15290
- for (const token of tokens)
15291
- counts.set(token, (counts.get(token) ?? 0) + 1);
15292
- for (const [term, tf] of counts) {
15293
- const posting = byTerm.get(term) ?? { rowIds: [], tf: [] };
15294
- posting.rowIds.push(rowId);
15295
- posting.tf.push(tf);
15296
- byTerm.set(term, posting);
15297
- }
15298
- return tokens.length;
15299
- }
15300
- function sortedFtsPostings(byTerm) {
15301
- return [...byTerm.entries()].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([term, posting]) => ({ term, rowIds: posting.rowIds, tf: posting.tf }));
15302
- }
15303
- function postingFrequencyTotal(postings) {
15304
- let total = 0;
15305
- for (const posting of postings) {
15306
- for (const frequency of posting.tf) {
15307
- total = safeWholeNumberSum([total, frequency], "Posting term-frequency total");
15308
- }
15309
- }
15310
- return total;
15311
- }
15312
- const secondaryNumberBits = new DataView(new ArrayBuffer(8));
15313
15482
  function storedSqlValueFromExecution(column, value) {
15314
15483
  if (column?.type === "string" && column.sqlDomain === void 0 && typeof value === "string") {
15315
15484
  return externalSqlTextValue(value);
@@ -15339,646 +15508,11 @@ function executionSqlValueFromInput(column, value) {
15339
15508
  const stored = column?.sqlDomain === void 0 ? value : normalizeSqlDomainValue(column.sqlDomain, value);
15340
15509
  return executionSqlValueFromStorage(column, stored);
15341
15510
  }
15342
- function secondaryIndexTerm(type, value) {
15343
- if (value === null)
15344
- throw new TypeError("A NULL has no secondary-index comparison term");
15345
- if (type === "string") {
15346
- if (typeof value !== "string")
15347
- throw new TypeError("Invalid string index value");
15348
- assertIndexedStringLength(value);
15349
- return value;
15350
- }
15351
- if (type === "boolean") {
15352
- if (typeof value !== "boolean")
15353
- throw new TypeError("Invalid boolean index value");
15354
- return value ? "1" : "0";
15355
- }
15356
- const numeric = type === "datetime" && value instanceof Date ? dateMilliseconds(value) : value;
15357
- if (typeof numeric !== "number" || !Number.isFinite(numeric)) {
15358
- throw new TypeError(`Invalid ${type} index value`);
15359
- }
15360
- secondaryNumberBits.setFloat64(0, numeric === 0 ? 0 : numeric, false);
15361
- const bits = secondaryNumberBits.getBigUint64(0, false);
15362
- const sortable = (bits & 0x8000000000000000n) === 0n ? bits ^ 0x8000000000000000n : ~bits & 0xffffffffffffffffn;
15363
- return sortable.toString(16).padStart(16, "0");
15364
- }
15365
- function secondaryTupleComponent(type, value) {
15366
- if (value === null)
15367
- throw new TypeError("A NULL has no secondary-index comparison term");
15368
- if (type === "string") {
15369
- if (typeof value !== "string")
15370
- throw new TypeError("Invalid string index value");
15371
- assertIndexedStringLength(value);
15372
- if (value.length * 5 + 5 > MAX_FTS_POSTING_TERM_CHARACTERS) {
15373
- throw new RangeError("Composite index term exceeds the persisted term limit");
15374
- }
15375
- let encoded = "";
15376
- for (let index = 0; index < value.length; index += 1) {
15377
- encoded += (value.charCodeAt(index) + 1).toString(16).padStart(5, "0");
15378
- }
15379
- return `${encoded}00000`;
15380
- }
15381
- if (type === "boolean") {
15382
- if (typeof value !== "boolean")
15383
- throw new TypeError("Invalid boolean index value");
15384
- return value ? "1" : "0";
15385
- }
15386
- return secondaryIndexTerm(type, value);
15387
- }
15388
- const reversedHex = new Map(Array.from("0123456789abcdef", (character, index) => [
15389
- character,
15390
- "fedcba9876543210"[index] ?? ""
15391
- ]));
15392
- function reverseSecondaryHex(input) {
15393
- let output = "";
15394
- for (let index = 0; index < input.length; index += 1) {
15395
- const character = input.charAt(index);
15396
- const reversed = reversedHex.get(character);
15397
- if (reversed === void 0)
15398
- throw new Error("Secondary index has a non-hexadecimal term");
15399
- output += reversed;
15400
- }
15401
- return output;
15402
- }
15403
- function secondaryTupleIndexTerm(index, columns, values) {
15404
- if (columns.length !== values.length)
15405
- throw new TypeError("Index key has the wrong arity");
15406
- const directions = secondaryIndexDirections(index);
15407
- let term = "";
15408
- for (const [position, column] of columns.entries()) {
15409
- const component = secondaryTupleComponent(column.type, values[position] ?? null);
15410
- if (term.length + component.length > MAX_FTS_POSTING_TERM_CHARACTERS) {
15411
- throw new RangeError("Composite index term exceeds the persisted term limit");
15412
- }
15413
- term += directions[position] === "desc" ? reverseSecondaryHex(component) : component;
15414
- }
15415
- return term;
15416
- }
15417
- function secondaryIndexComponentTerm(index, column, position, value) {
15418
- const component = secondaryTupleComponent(column.type, value);
15419
- if (secondaryIndexDirections(index)[position] !== "desc")
15420
- return component;
15421
- return reverseSecondaryHex(component);
15422
- }
15423
- function decodeSecondaryTupleTerm(index, columns, term) {
15424
- const directions = secondaryIndexDirections(index);
15425
- let offset = 0;
15426
- const values = columns.map((column, position) => {
15427
- const descending = directions[position] === "desc";
15428
- const restore = (encoded2) => descending ? reverseSecondaryHex(encoded2) : encoded2;
15429
- if (column.type === "string") {
15430
- let value2 = "";
15431
- for (; ; ) {
15432
- const group = restore(term.slice(offset, offset + 5));
15433
- if (group.length !== 5)
15434
- throw new Error(`Secondary index ${index.name} has a bad term`);
15435
- offset += 5;
15436
- if (group === "00000")
15437
- return value2;
15438
- const code = Number.parseInt(group, 16) - 1;
15439
- if (!Number.isInteger(code) || code < 0 || code > 65535) {
15440
- throw new Error(`Secondary index ${index.name} has a bad string term`);
15441
- }
15442
- value2 += String.fromCharCode(code);
15443
- }
15444
- }
15445
- if (column.type === "boolean") {
15446
- const encoded2 = restore(term.slice(offset, offset + 1));
15447
- offset += 1;
15448
- if (encoded2 !== "0" && encoded2 !== "1") {
15449
- throw new Error(`Secondary index ${index.name} has a bad boolean term`);
15450
- }
15451
- return encoded2 === "1";
15452
- }
15453
- const encoded = restore(term.slice(offset, offset + 16));
15454
- if (encoded.length !== 16)
15455
- throw new Error(`Secondary index ${index.name} has a bad term`);
15456
- offset += 16;
15457
- const sortable = BigInt(`0x${encoded}`);
15458
- const bits = (sortable & 0x8000000000000000n) === 0n ? ~sortable & 0xffffffffffffffffn : sortable ^ 0x8000000000000000n;
15459
- secondaryNumberBits.setBigUint64(0, bits, false);
15460
- const value = secondaryNumberBits.getFloat64(0, false);
15461
- return column.type === "datetime" ? new Date(value) : value;
15462
- });
15463
- if (offset !== term.length)
15464
- throw new Error(`Secondary index ${index.name} has a bad term`);
15465
- return values;
15466
- }
15467
- function secondaryKeyLocator(type, value) {
15468
- const token = keyToken(type, value);
15469
- let hash = 2166136261;
15470
- for (let index = 0; index < token.length; index += 1) {
15471
- const code = token.charCodeAt(index);
15472
- hash = Math.imul(hash ^ code & 255, 16777619) >>> 0;
15473
- hash = Math.imul(hash ^ code >>> 8, 16777619) >>> 0;
15474
- }
15475
- return BigInt(hash >>> 0);
15476
- }
15477
- function addSecondaryPosting(byTerm, index, columns, values, locator, uniqueTerms) {
15478
- if (values.some((value) => value === null))
15479
- return;
15480
- const first = columns[0];
15481
- if (first === void 0)
15482
- return;
15483
- const term = secondaryTupleIndexTerm(index, columns, values);
15484
- if (uniqueTerms?.has(term) === true) {
15485
- throw new TypeError(`UNIQUE index ${index.name} has a duplicate key`);
15486
- }
15487
- uniqueTerms?.add(term);
15488
- const posting = byTerm.get(term) ?? { rowIds: [], tf: [] };
15489
- posting.rowIds.push(locator);
15490
- posting.tf.push(1);
15491
- byTerm.set(term, posting);
15492
- }
15493
- function sortedSecondaryPostings(byTerm) {
15494
- return [...byTerm.entries()].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([term, posting]) => {
15495
- const rowIds = [...new Set(posting.rowIds)].sort((left, right) => left < right ? -1 : left > right ? 1 : 0);
15496
- return { term, rowIds, tf: rowIds.map(() => 1) };
15497
- });
15498
- }
15499
- function appendRowIdLocator(segments, expectedRows) {
15500
- const spans = appendRowIdSpans(segments, expectedRows);
15501
- let spanIndex = 0;
15502
- return (row) => {
15503
- while (spanIndex < spans.length && row >= (spans[spanIndex]?.rowStart ?? 0) + (spans[spanIndex]?.rowCount ?? 0)) {
15504
- spanIndex += 1;
15505
- }
15506
- const span = spans[spanIndex];
15507
- if (span === void 0 || row < span.rowStart) {
15508
- throw new Error(`Secondary-index row ID is missing: ${String(row)}`);
15509
- }
15510
- return span.rowIdStart + BigInt(row - span.rowStart);
15511
- };
15512
- }
15513
- function appendRowIdSpans(segments, expectedRows) {
15514
- const spans = [];
15515
- let outputStart = 0;
15516
- for (const segment of segments) {
15517
- const kind = segment.kind;
15518
- if (kind !== "insert" && kind !== "base")
15519
- continue;
15520
- for (const span of mergeSourceRowIdSpans(segment, kind)) {
15521
- spans.push({ ...span, rowStart: outputStart + span.rowStart });
15522
- }
15523
- outputStart += segment.rowCount;
15524
- }
15525
- const rows = spans.reduce((total, span) => total + span.rowCount, 0);
15526
- if (rows !== expectedRows)
15527
- throw new Error("Secondary-index row IDs differ from table rows");
15528
- return spans;
15529
- }
15530
- function appendRowForLocator(segments, expectedRows) {
15531
- const spans = appendRowIdSpans(segments, expectedRows).sort((left, right) => left.rowIdStart < right.rowIdStart ? -1 : left.rowIdStart > right.rowIdStart ? 1 : 0);
15532
- return (locator) => {
15533
- let low = 0;
15534
- let high = spans.length;
15535
- while (low < high) {
15536
- const middle = low + high >>> 1;
15537
- if ((spans[middle]?.rowIdStart ?? 0n) <= locator)
15538
- low = middle + 1;
15539
- else
15540
- high = middle;
15541
- }
15542
- const span = spans[low - 1];
15543
- if (span === void 0)
15544
- return void 0;
15545
- const offset = locator - span.rowIdStart;
15546
- if (offset < 0n || offset >= BigInt(span.rowCount))
15547
- return void 0;
15548
- return span.rowStart + Number(offset);
15549
- };
15550
- }
15551
- function chunkFtsPostings(postings, size = 128) {
15552
- const chunks = [];
15553
- let chunk = [];
15554
- let rowIds = 0;
15555
- const flush = () => {
15556
- if (chunk.length === 0)
15557
- return;
15558
- chunks.push(chunk);
15559
- chunk = [];
15560
- rowIds = 0;
15561
- };
15562
- for (const posting of postings) {
15563
- if (posting.rowIds.length > MAX_FTS_POSTING_ROW_IDS_PER_CHUNK) {
15564
- throw new RangeError("One posting exceeds the persisted row-id chunk limit");
15565
- }
15566
- if (chunk.length >= Math.min(size, MAX_FTS_POSTINGS_PER_CHUNK) || rowIds + posting.rowIds.length > MAX_FTS_POSTING_ROW_IDS_PER_CHUNK) {
15567
- flush();
15568
- }
15569
- chunk.push(posting);
15570
- rowIds += posting.rowIds.length;
15571
- }
15572
- flush();
15573
- return chunks;
15574
- }
15575
- function buildFtsColumnDeltas(table, input, rowIdStart) {
15576
- const active = Object.entries(table.ftsColumns ?? {}).filter(([, record]) => record.state !== "invalid");
15577
- if (active.length === 0)
15578
- return [];
15579
- const columnsById = new Map(table.columns.map((column) => [column.id, column]));
15580
- return active.flatMap(([columnId]) => {
15581
- const column = columnsById.get(columnId);
15582
- if (column === void 0)
15583
- return [];
15584
- const byTerm = /* @__PURE__ */ new Map();
15585
- let totalTokens = 0;
15586
- (input.columns[column.name] ?? []).forEach((value, index) => {
15587
- const documentValue = column.type === "string" && column.sqlDomain === void 0 && typeof value === "string" ? protectedSqlTextValue(value) : value;
15588
- totalTokens += addFtsDocument(byTerm, documentValue, rowIdStart + BigInt(index));
15589
- });
15590
- return [{ columnId, postings: sortedFtsPostings(byTerm), totalTokens }];
15591
- });
15592
- }
15593
- function buildSecondaryInsertDeltas(table, input, rowIdStart) {
15594
- const active = Object.values(table.secondaryIndexes ?? {}).filter((index) => index.state !== "invalid");
15595
- if (active.length === 0)
15596
- return [];
15597
- const columnsById = new Map(table.columns.map((column) => [column.id, column]));
15598
- const keyColumn = getUniqueKeyColumn(table);
15599
- return active.flatMap((index) => {
15600
- const columns = secondaryIndexColumnIds(index).map((columnId) => columnsById.get(columnId));
15601
- if (columns.some((column) => column === void 0))
15602
- return [];
15603
- const indexedColumns = columns;
15604
- const byTerm = /* @__PURE__ */ new Map();
15605
- const keys = keyColumn === void 0 ? void 0 : input.columns[keyColumn.name] ?? [];
15606
- const rowCount = input.rowCount ?? Object.values(input.columns)[0]?.length ?? 0;
15607
- try {
15608
- for (let row = 0; row < rowCount; row += 1) {
15609
- const values = indexedColumns.map((column) => input.columns[column.name]?.[row] ?? null);
15610
- if (values.some((value) => value === null))
15611
- continue;
15612
- const locator = keyColumn === void 0 ? rowIdStart + BigInt(row) : secondaryKeyLocator(keyColumn.type, keys?.[row] ?? null);
15613
- addSecondaryPosting(byTerm, index, indexedColumns, values, locator);
15614
- }
15615
- } catch (error) {
15616
- if (error instanceof RangeError && index.unique !== true)
15617
- return [];
15618
- throw error;
15619
- }
15620
- const postings = sortedSecondaryPostings(byTerm);
15621
- return [
15622
- {
15623
- columnId: index.storageColumnId,
15624
- postings,
15625
- totalTokens: postingFrequencyTotal(postings)
15626
- }
15627
- ];
15628
- });
15629
- }
15630
- function buildSecondaryUpdateDeltas(table, input, preImages = []) {
15631
- const active = Object.values(table.secondaryIndexes ?? {}).filter((index) => index.state !== "invalid");
15632
- if (active.length === 0)
15633
- return [];
15634
- const keyColumn = getUniqueKeyColumn(table);
15635
- if (keyColumn === void 0)
15636
- return [];
15637
- const columnsById = new Map(table.columns.map((column) => [column.id, column]));
15638
- return active.flatMap((index) => {
15639
- const columns = secondaryIndexColumnIds(index).map((columnId) => columnsById.get(columnId));
15640
- if (columns.some((column) => column === void 0))
15641
- return [];
15642
- const indexedColumns = columns;
15643
- const byTerm = /* @__PURE__ */ new Map();
15644
- const affected = indexedColumns.some((column) => input.changes[column.name] !== void 0);
15645
- if (affected) {
15646
- try {
15647
- for (let row = 0; row < input.keys.length; row += 1) {
15648
- const values = indexedColumns.map((column) => input.changes[column.name] === void 0 ? preImages[row]?.[column.name] ?? null : input.changes[column.name]?.[row] ?? null);
15649
- if (values.some((value) => value === null))
15650
- continue;
15651
- addSecondaryPosting(byTerm, index, indexedColumns, values, secondaryKeyLocator(keyColumn.type, input.keys[row] ?? null));
15652
- }
15653
- } catch (error) {
15654
- if (error instanceof RangeError && index.unique !== true)
15655
- return [];
15656
- throw error;
15657
- }
15658
- }
15659
- const postings = sortedSecondaryPostings(byTerm);
15660
- return [
15661
- {
15662
- columnId: index.storageColumnId,
15663
- postings,
15664
- totalTokens: postingFrequencyTotal(postings)
15665
- }
15666
- ];
15667
- });
15668
- }
15669
- function secondaryIndexUpdateNeedsPreImages(table, input) {
15670
- const changed = new Set(Object.keys(input.changes));
15671
- const columnsById = new Map(table.columns.map((column) => [column.id, column.name]));
15672
- return Object.values(table.secondaryIndexes ?? {}).some((index) => {
15673
- if (index.state === "invalid")
15674
- return false;
15675
- const names = secondaryIndexColumnIds(index).map((columnId) => columnsById.get(columnId));
15676
- return names.some((name) => name !== void 0 && changed.has(name)) && names.some((name) => name === void 0 || !changed.has(name));
15677
- });
15678
- }
15679
- function buildSecondaryDeleteCoverage(table) {
15680
- return Object.values(table.secondaryIndexes ?? {}).flatMap((index) => index.state === "invalid" ? [] : [
15681
- {
15682
- columnId: index.storageColumnId,
15683
- postings: [],
15684
- totalTokens: 0
15685
- }
15686
- ]);
15687
- }
15688
- function readyUniqueSecondaryIndexes(table) {
15689
- const columnsById = new Map(table.columns.map((column) => [column.id, column]));
15690
- return Object.entries(table.secondaryIndexes ?? {}).flatMap(([indexId, index]) => {
15691
- if (index.unique !== true || index.uniqueEnforced !== true)
15692
- return [];
15693
- const columns = secondaryIndexColumnIds(index).map((columnId) => columnsById.get(columnId));
15694
- return columns.some((column) => column === void 0) ? [] : [{ indexId, index, columns }];
15695
- });
15696
- }
15697
- function secondaryUniqueTerm(index, columns, values) {
15698
- if (values.some((value) => value === null))
15699
- return void 0;
15700
- return secondaryTupleIndexTerm(index, columns, values);
15701
- }
15702
- function assertNoDuplicateUniqueTerms(index, terms) {
15703
- const seen = /* @__PURE__ */ new Set();
15704
- for (const term of terms) {
15705
- if (seen.has(term))
15706
- throw new TypeError(`UNIQUE index ${index.name} has a duplicate key`);
15707
- seen.add(term);
15708
- }
15709
- }
15710
- function stageSecondaryUniqueInsertChanges(transaction, table, input, oldImages) {
15711
- const rowCount = input.rowCount ?? Object.values(input.columns)[0]?.length ?? 0;
15712
- for (const { indexId, index, columns } of readyUniqueSecondaryIndexes(table)) {
15713
- const namespaceId = secondaryUniqueKeyNamespace(table.id, indexId);
15714
- const removed = (oldImages ?? []).flatMap((old) => {
15715
- if (old === void 0)
15716
- return [];
15717
- const term = secondaryUniqueTerm(index, columns, columns.map((column) => old[column.name] ?? null));
15718
- return term === void 0 ? [] : [term];
15719
- });
15720
- if (removed.length > 0) {
15721
- transaction.setUniqueKeyChanges({
15722
- tableId: namespaceId,
15723
- keyTokens: removed,
15724
- requireAbsent: false,
15725
- remove: true
15726
- });
15727
- }
15728
- const added = [];
15729
- for (let row = 0; row < rowCount; row += 1) {
15730
- const term = secondaryUniqueTerm(index, columns, columns.map((column) => input.columns[column.name]?.[row] ?? null));
15731
- if (term !== void 0)
15732
- added.push(term);
15733
- }
15734
- assertNoDuplicateUniqueTerms(index, added);
15735
- transaction.setUniqueKeyChanges({
15736
- tableId: namespaceId,
15737
- keyTokens: added,
15738
- requireAbsent: true
15739
- });
15740
- }
15741
- }
15742
- function stageSecondaryUniqueMutationChanges(transaction, table, input, oldImages) {
15743
- for (const { indexId, index, columns } of readyUniqueSecondaryIndexes(table)) {
15744
- const namespaceId = secondaryUniqueKeyNamespace(table.id, indexId);
15745
- const removed = oldImages.flatMap((old) => {
15746
- if (old === void 0)
15747
- return [];
15748
- const term = secondaryUniqueTerm(index, columns, columns.map((column) => old[column.name] ?? null));
15749
- return term === void 0 ? [] : [term];
15750
- });
15751
- transaction.setUniqueKeyChanges({
15752
- tableId: namespaceId,
15753
- keyTokens: removed,
15754
- requireAbsent: false,
15755
- remove: true
15756
- });
15757
- if (input === void 0)
15758
- continue;
15759
- const added = oldImages.flatMap((old, row) => {
15760
- if (old === void 0)
15761
- return [];
15762
- const term = secondaryUniqueTerm(index, columns, columns.map((column) => input.changes[column.name]?.[row] ?? old[column.name] ?? null));
15763
- return term === void 0 ? [] : [term];
15764
- });
15765
- assertNoDuplicateUniqueTerms(index, added);
15766
- transaction.setUniqueKeyChanges({
15767
- tableId: namespaceId,
15768
- keyTokens: added,
15769
- requireAbsent: true
15770
- });
15771
- }
15772
- }
15773
15511
  function searchableFtsColumns(table) {
15774
15512
  if (table === void 0)
15775
15513
  return void 0;
15776
15514
  return visibleTableColumns(table).filter((column) => column.type !== "boolean").map((column) => column.name);
15777
15515
  }
15778
- const LIVE_HIDDEN_PREFIX = "__minnow_live_";
15779
- const LIVE_KEY_ALIAS = `${LIVE_HIDDEN_PREFIX}key`;
15780
- const LIVE_ORDER_ALIAS = `${LIVE_HIDDEN_PREFIX}order_`;
15781
- const LIVE_MAINTENANCE_MAX_DELTA_ROWS = 2048;
15782
- const LIVE_WINDOW_MARGIN_MIN = 16;
15783
- const LIVE_WINDOW_MARGIN_MAX = 64;
15784
- function liveKeyToken(value) {
15785
- if (typeof value === "number")
15786
- return `n:${String(value)}`;
15787
- if (typeof value === "string")
15788
- return `s:${value}`;
15789
- if (typeof value === "boolean")
15790
- return value ? "b:1" : "b:0";
15791
- if (value instanceof Date)
15792
- return `d:${String(dateMilliseconds(value))}`;
15793
- return "z";
15794
- }
15795
- function splitLiveHiddenColumns(executed, state) {
15796
- const publicCount = state.publicColumns.length;
15797
- const hidden = executed.columns.slice(publicCount);
15798
- const count = executed.rows.length;
15799
- const keys = new Array(count);
15800
- const order = state.orderTerms.map(() => new Array(count));
15801
- for (let index = 0; index < count; index += 1) {
15802
- const row = executed.rows[index] ?? {};
15803
- keys[index] = liveKeyToken(row[LIVE_KEY_ALIAS] ?? null);
15804
- for (const [term, { alias }] of state.orderTerms.entries()) {
15805
- const values = order[term];
15806
- if (values !== void 0)
15807
- values[index] = row[alias] ?? null;
15808
- }
15809
- for (const column of hidden)
15810
- Reflect.deleteProperty(row, column);
15811
- }
15812
- return {
15813
- result: externalizeQueryResult({
15814
- columns: executed.columns.slice(0, publicCount),
15815
- columnDomains: executed.columnDomains.slice(0, publicCount),
15816
- rows: executed.rows
15817
- }),
15818
- keys,
15819
- order
15820
- };
15821
- }
15822
- function liveMaintainedOutcome(state, previous, previousIndex) {
15823
- const positions = /* @__PURE__ */ new Map();
15824
- let retainedBytes = state.retainedBytes ?? 128 + planMemoKey(state.fullPlan).length * 2 + planMemoKey(state.deltaPlan).length * 2;
15825
- for (const [index, key] of state.keys.entries()) {
15826
- if (positions.has(key))
15827
- throw new TypeError("Duplicate live input key");
15828
- positions.set(key, index);
15829
- if (state.retainedBytes === void 0)
15830
- retainedBytes += liveRowStateBytes(state, index);
15831
- }
15832
- state = { ...state, positions, retainedBytes };
15833
- const visibleCount = state.limit === void 0 ? state.rows.length : Math.min(state.rows.length, state.limit);
15834
- const visible = state.rows.slice(0, visibleCount);
15835
- if (previous === void 0) {
15836
- return {
15837
- result: {
15838
- columns: [...state.publicColumns],
15839
- columnDomains: [...state.columnDomains],
15840
- rows: visible
15841
- },
15842
- state,
15843
- retainedBytes,
15844
- changed: true
15845
- };
15846
- }
15847
- const previousCount = previous.rows.length;
15848
- const retained = new Int32Array(visibleCount);
15849
- let same = visibleCount === previousCount;
15850
- for (let index = 0; index < visibleCount; index += 1) {
15851
- const was = previousIndex?.[index] ?? -1;
15852
- if (was >= 0 && was < previousCount) {
15853
- retained[index] = was;
15854
- if (was !== index)
15855
- same = false;
15856
- continue;
15857
- }
15858
- retained[index] = -1;
15859
- const before = previous.rows[index];
15860
- const now = visible[index];
15861
- if (before !== void 0 && now !== void 0 && sameLiveRow(before, now, previous.columns)) {
15862
- visible[index] = before;
15863
- retained[index] = index;
15864
- } else
15865
- same = false;
15866
- }
15867
- if (same)
15868
- return { result: previous, state, retainedBytes, changed: false };
15869
- return {
15870
- result: {
15871
- columns: [...state.publicColumns],
15872
- columnDomains: [...state.columnDomains],
15873
- rows: visible
15874
- },
15875
- state,
15876
- retainedBytes,
15877
- changed: true,
15878
- retained
15879
- };
15880
- }
15881
- function liveRowStateBytes(state, index) {
15882
- let bytes = 112 + (state.keys[index]?.length ?? 0) * 2 + estimateValuesBytes(Object.values(state.rows[index] ?? {})) * 2;
15883
- for (const values of state.order)
15884
- bytes += 8 + estimateValuesBytes([values[index]]) * 2;
15885
- return bytes;
15886
- }
15887
- function filterLiveRows(state, keep) {
15888
- const rows = [];
15889
- const keys = [];
15890
- const order = state.order.map(() => new Array());
15891
- const previous = [];
15892
- for (let index = 0; index < state.rows.length; index += 1) {
15893
- const row = state.rows[index];
15894
- if (row === void 0 || !keep(index))
15895
- continue;
15896
- rows.push(row);
15897
- keys.push(state.keys[index] ?? "z");
15898
- for (const [term, values] of state.order.entries()) {
15899
- order[term]?.push(values[index] ?? null);
15900
- }
15901
- previous.push(index);
15902
- }
15903
- return { rows, keys, order, previousIndex: Int32Array.from(previous) };
15904
- }
15905
- function trimLiveRows(rows, count) {
15906
- return {
15907
- rows: rows.rows.slice(0, count),
15908
- keys: rows.keys.slice(0, count),
15909
- order: rows.order.map((values) => values.slice(0, count)),
15910
- previousIndex: rows.previousIndex.slice(0, count)
15911
- };
15912
- }
15913
- function liveOrderComparator(terms) {
15914
- return (leftOrder, leftIndex, rightOrder, rightIndex) => {
15915
- for (const [term, { descending, nulls }] of terms.entries()) {
15916
- const a = leftOrder[term]?.[leftIndex] ?? null;
15917
- const b = rightOrder[term]?.[rightIndex] ?? null;
15918
- if (a === null || b === null) {
15919
- if (a === null && b === null)
15920
- continue;
15921
- const nullsFirst = nulls === "first" || nulls === void 0 && descending;
15922
- return a === null ? nullsFirst ? -1 : 1 : nullsFirst ? 1 : -1;
15923
- }
15924
- let comparison = compareSqlValues(a, b);
15925
- if (descending)
15926
- comparison = -comparison;
15927
- if (comparison !== 0)
15928
- return comparison;
15929
- }
15930
- return 0;
15931
- };
15932
- }
15933
- function mergeLiveRows(kept, added, compare, ordered) {
15934
- if (added.rows.length === 0)
15935
- return kept;
15936
- const terms = kept.order.length;
15937
- const total = kept.rows.length + added.rows.length;
15938
- const rows = new Array(total);
15939
- const keys = new Array(total);
15940
- const order = Array.from({ length: terms }, () => new Array(total));
15941
- const previousIndex = new Int32Array(total);
15942
- const take = (source, from, to) => {
15943
- rows[to] = source.rows[from] ?? {};
15944
- keys[to] = source.keys[from] ?? "z";
15945
- for (let term = 0; term < terms; term += 1) {
15946
- const values = order[term];
15947
- if (values !== void 0)
15948
- values[to] = source.order[term]?.[from] ?? null;
15949
- }
15950
- previousIndex[to] = source.previousIndex[from] ?? -1;
15951
- };
15952
- if (!ordered) {
15953
- for (let index = 0; index < kept.rows.length; index += 1)
15954
- take(kept, index, index);
15955
- for (let index = 0; index < added.rows.length; index += 1) {
15956
- take(added, index, kept.rows.length + index);
15957
- }
15958
- return { rows, keys, order, previousIndex };
15959
- }
15960
- const addedIndexes = added.rows.map((_, index) => index);
15961
- addedIndexes.sort((left, right) => compare(added.order, left, added.order, right));
15962
- let keptIndex = 0;
15963
- let addedPosition = 0;
15964
- for (let to = 0; to < total; to += 1) {
15965
- const addedIndex = addedIndexes[addedPosition];
15966
- const takeAdded = addedIndex !== void 0 && (keptIndex >= kept.rows.length || compare(added.order, addedIndex, kept.order, keptIndex) < 0);
15967
- if (takeAdded) {
15968
- take(added, addedIndex, to);
15969
- addedPosition += 1;
15970
- } else {
15971
- take(kept, keptIndex, to);
15972
- keptIndex += 1;
15973
- }
15974
- }
15975
- return { rows, keys, order, previousIndex };
15976
- }
15977
- function getUniqueKeyColumn(table) {
15978
- if (table.uniqueKeyColumnId === void 0)
15979
- return void 0;
15980
- return table.columns.find((column) => column.id === table.uniqueKeyColumnId);
15981
- }
15982
15516
  function visibleTableColumns(table) {
15983
15517
  return table.columns.filter((column) => column.hidden !== true);
15984
15518
  }
@@ -16035,9 +15569,6 @@ function foldAssignmentColumns(table, assignments) {
16035
15569
  });
16036
15570
  return folded.some((assignment, index) => assignment !== assignments[index]) ? folded : assignments;
16037
15571
  }
16038
- function quoteIdentifier(name) {
16039
- return `"${name.replace(/"/g, '""')}"`;
16040
- }
16041
15572
  function normalizeDomainBatch(table, input) {
16042
15573
  for (const column of table.columns) {
16043
15574
  const values = input.columns[column.name];
@@ -16443,36 +15974,6 @@ function valueMatchesColumnType(column, value) {
16443
15974
  return value instanceof Date;
16444
15975
  }
16445
15976
  }
16446
- function keyToken(type, value) {
16447
- if (value === null)
16448
- throw new TypeError("Unique key cannot be null");
16449
- switch (type) {
16450
- case "boolean":
16451
- if (typeof value !== "boolean")
16452
- throw new TypeError("Invalid boolean unique key");
16453
- return value ? "boolean:true" : "boolean:false";
16454
- case "number":
16455
- if (typeof value !== "number" || !Number.isFinite(value)) {
16456
- throw new TypeError("Invalid number unique key");
16457
- }
16458
- return `number:${String(value)}`;
16459
- case "string":
16460
- if (typeof value !== "string")
16461
- throw new TypeError("Invalid string unique key");
16462
- assertIndexedStringLength(value);
16463
- return `string:${value}`;
16464
- case "datetime":
16465
- if (!(value instanceof Date) || !Number.isFinite(dateMilliseconds(value))) {
16466
- throw new TypeError("Invalid datetime unique key");
16467
- }
16468
- return `datetime:${String(dateMilliseconds(value))}`;
16469
- }
16470
- }
16471
- function assertIndexedStringLength(value) {
16472
- if (value.length > MAX_INDEXED_STRING_CHARACTERS) {
16473
- throw new RangeError(`Indexed strings cannot exceed ${String(MAX_INDEXED_STRING_CHARACTERS)} characters`);
16474
- }
16475
- }
16476
15977
  function formatValue(value) {
16477
15978
  return value instanceof Date ? dateIsoString(value) : String(value);
16478
15979
  }
@@ -17456,13 +16957,14 @@ function secondaryIndexPredicates(plan, table) {
17456
16957
  break;
17457
16958
  if (constraint.lower === void 0 && constraint.upper === void 0)
17458
16959
  break;
17459
- const descending = secondaryIndexDirections(index)[position] === "desc";
16960
+ const direction = secondaryIndexDirections(index)[position] ?? "asc";
16961
+ const descending = direction === "desc";
17460
16962
  queries = prefixes.map((prefix) => {
17461
16963
  const query = {};
17462
16964
  const logicalLower = descending ? constraint.upper : constraint.lower;
17463
16965
  const logicalUpper = descending ? constraint.lower : constraint.upper;
17464
16966
  if (logicalLower === void 0) {
17465
- query.lower = prefix;
16967
+ query.lower = descending ? prefix + LOWEST_SECONDARY_COMPONENT : prefix;
17466
16968
  query.lowerInclusive = true;
17467
16969
  } else {
17468
16970
  const term = prefix + secondaryIndexComponentTerm(index, column, position, logicalLower.value);
@@ -17470,7 +16972,7 @@ function secondaryIndexPredicates(plan, table) {
17470
16972
  query.lowerInclusive = logicalLower.inclusive;
17471
16973
  }
17472
16974
  if (logicalUpper === void 0) {
17473
- query.upper = `${prefix}\uFFFF`;
16975
+ query.upper = descending ? `${prefix}\uFFFF` : prefix + ASCENDING_NULL_COMPONENT;
17474
16976
  query.upperInclusive = false;
17475
16977
  } else {
17476
16978
  const term = prefix + secondaryIndexComponentTerm(index, column, position, logicalUpper.value);
@@ -17484,6 +16986,9 @@ function secondaryIndexPredicates(plan, table) {
17484
16986
  }
17485
16987
  if (matchedColumns === 0)
17486
16988
  continue;
16989
+ if (matchedColumns < indexedColumns.length && !secondaryIndexTermsCoverNulls(index) && indexedColumns.slice(matchedColumns).some((column) => column.nullable)) {
16990
+ continue;
16991
+ }
17487
16992
  if (queries === void 0) {
17488
16993
  const complete = matchedColumns === indexedColumns.length;
17489
16994
  queries = prefixes.map((term) => ({ term, prefix: !complete }));
@@ -17809,41 +17314,30 @@ function validateMergeSegmentShape(table, segmentId, kind, columns, keyColumnId)
17809
17314
  throw new Error(`Update compaction source has invalid columns: ${segmentId}`);
17810
17315
  }
17811
17316
  }
17812
- function mergeSourceRowIdSpans(segment, kind) {
17813
- if (kind === "update" || kind === "delete") {
17814
- if (segment.rowIdStart !== 0n || segment.rowIdEndExclusive !== 0n || segment.rowIdSpans.length !== 0) {
17815
- throw new Error(`Mutation marker unexpectedly owns row IDs: ${segment.id}`);
17816
- }
17817
- return [];
17818
- }
17819
- const spans = segment.rowIdSpans.length === 0 ? [{ rowStart: 0, rowCount: segment.rowCount, rowIdStart: segment.rowIdStart }] : segment.rowIdSpans.map((span) => ({ ...span }));
17820
- const envelope = rowIdSpanEnvelope(spans);
17821
- let rowStart = 0;
17822
- for (const [index, span] of spans.entries()) {
17823
- if (span.rowStart !== rowStart || span.rowCount <= 0) {
17824
- throw new Error(`Segment row ID spans are not contiguous: ${segment.id}`);
17825
- }
17826
- const previous = spans[index - 1];
17827
- if (previous !== void 0 && previous.rowIdStart + BigInt(previous.rowCount) === span.rowIdStart) {
17828
- throw new Error(`Segment row ID spans are not coalesced: ${segment.id}`);
17829
- }
17830
- rowStart = safeWholeNumberSum([rowStart, span.rowCount], "Segment row ID span rows");
17317
+ function isTransientCompactionConflict(error) {
17318
+ if (error instanceof SchemaConflictError || error instanceof TransactionRecordConflictError || error instanceof CompactionJobConflictError) {
17319
+ return true;
17831
17320
  }
17832
- if (rowStart !== segment.rowCount || envelope.start !== segment.rowIdStart || envelope.endExclusive !== segment.rowIdEndExclusive) {
17833
- throw new Error(`Segment row ID spans differ from their envelope: ${segment.id}`);
17321
+ if (!(error instanceof Error))
17322
+ return false;
17323
+ if (error.cause instanceof WriteConflictError || error.cause instanceof SchemaConflictError) {
17324
+ return true;
17834
17325
  }
17835
- const intervals = spans.map((span) => ({
17836
- start: span.rowIdStart,
17837
- end: span.rowIdStart + BigInt(span.rowCount)
17838
- })).sort((left, right) => left.start < right.start ? -1 : left.start > right.start ? 1 : 0);
17839
- for (let index = 1; index < intervals.length; index += 1) {
17840
- const previous = intervals[index - 1];
17841
- const current = intervals[index];
17842
- if (previous !== void 0 && current !== void 0 && current.start < previous.end) {
17843
- throw new Error(`Segment row IDs overlap: ${segment.id}`);
17326
+ return /^Compaction (source|sources|publication|planned layout)/u.test(error.message);
17327
+ }
17328
+ function withEveryDelta(visible, pruned) {
17329
+ const keptAppends = new Map(pruned.filter((segment) => segment.kind !== "update" && segment.kind !== "delete").map((segment) => [segment.id, segment]));
17330
+ const result = [];
17331
+ for (const segment of visible) {
17332
+ if (segment.kind === "update" || segment.kind === "delete")
17333
+ result.push(segment);
17334
+ else {
17335
+ const kept = keptAppends.get(segment.id);
17336
+ if (kept !== void 0)
17337
+ result.push(kept);
17844
17338
  }
17845
17339
  }
17846
- return spans;
17340
+ return result;
17847
17341
  }
17848
17342
  function assertCanonicalMergeSourceOrder(segments) {
17849
17343
  for (let index = 1; index < segments.length; index += 1) {
@@ -17855,7 +17349,7 @@ function assertCanonicalMergeSourceOrder(segments) {
17855
17349
  }
17856
17350
  }
17857
17351
  function compareMergeSourceOrder(left, right) {
17858
- return left.logicalOrder - right.logicalOrder || left.committedVersion - right.committedVersion || left.segmentId.localeCompare(right.segmentId);
17352
+ return left.logicalOrder - right.logicalOrder || left.committedVersion - right.committedVersion || (left.commitOrdinal ?? 0) - (right.commitOrdinal ?? 0) || left.segmentId.localeCompare(right.segmentId);
17859
17353
  }
17860
17354
  function sourceOrderTuple(segment, transactions, label) {
17861
17355
  if (segment === void 0)
@@ -17867,6 +17361,7 @@ function sourceOrderTuple(segment, transactions, label) {
17867
17361
  return {
17868
17362
  logicalOrder: segment.logicalOrder,
17869
17363
  committedVersion: owner.committedVersion,
17364
+ commitOrdinal: segment.commitOrdinal,
17870
17365
  segmentId: segment.id
17871
17366
  };
17872
17367
  }
@@ -18189,20 +17684,6 @@ function rowRangeAt(ranges, rowIndex) {
18189
17684
  }
18190
17685
  return void 0;
18191
17686
  }
18192
- function rowIdSpanEnvelope(spans) {
18193
- if (spans.length === 0)
18194
- return { start: 0n, endExclusive: 0n };
18195
- let start = spans[0]?.rowIdStart ?? 0n;
18196
- let endExclusive = start + BigInt(spans[0]?.rowCount ?? 0);
18197
- for (const span of spans.slice(1)) {
18198
- if (span.rowIdStart < start)
18199
- start = span.rowIdStart;
18200
- const spanEnd = span.rowIdStart + BigInt(span.rowCount);
18201
- if (spanEnd > endExclusive)
18202
- endExclusive = spanEnd;
18203
- }
18204
- return { start, endExclusive };
18205
- }
18206
17687
  function validatePhysicalTablePlan(table, plan) {
18207
17688
  if (table.columns.length !== plan.columns.length || table.columns.some((column, index) => {
18208
17689
  const planned = plan.columns[index];
@@ -18525,6 +18006,10 @@ function garbageCollectionProgress(job) {
18525
18006
  function firesAfterTriggers(table, ...events) {
18526
18007
  return (table.triggers ?? []).some((trigger) => trigger.timing === "after" && events.includes(trigger.event));
18527
18008
  }
18009
+ function overlayOwnedSegments(segments, visibility) {
18010
+ const id = visibility?.overlayTransactionId;
18011
+ return id === void 0 ? [] : segments.filter((segment) => segment.transactionId === id);
18012
+ }
18528
18013
  function collectRealTableNames(plan) {
18529
18014
  const names = /* @__PURE__ */ new Set();
18530
18015
  const excluded = /* @__PURE__ */ new Set();