@minnowdb/core 0.10.1 → 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.
@@ -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]);
@@ -6055,7 +6055,7 @@ class MinnowDatabase {
6055
6055
  const direct = await this.#scopeRowsByKey(transaction, table, keyColumn, keys, projection);
6056
6056
  if (direct !== void 0)
6057
6057
  return direct;
6058
- const quote = (name) => `"${name.replaceAll('"', '""')}"`;
6058
+ const quote = quoteSqlIdentifier;
6059
6059
  const selected = projection === "*" ? "*" : projection.map(quote).join(", ");
6060
6060
  const rows = [];
6061
6061
  for (let start = 0; start < keys.length; start += SCOPE_KEY_LOOKUP_WINDOW) {
@@ -7803,7 +7803,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
7803
7803
  }
7804
7804
  return {
7805
7805
  kind: "rows",
7806
- result: await this.query(`SELECT $1 AS ${quoteIdentifier(name)}`, { params: [value] })
7806
+ result: await this.query(`SELECT $1 AS ${quoteSqlIdentifier(name)}`, { params: [value] })
7807
7807
  };
7808
7808
  }
7809
7809
  if (statement.kind === "create-enum") {
@@ -8170,7 +8170,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
8170
8170
  }
8171
8171
  if (statement.kind === "transaction") {
8172
8172
  const action = statement.action === "rollback-to" ? "ROLLBACK TO" : statement.action;
8173
- return this.execute(`${action}${statement.name === void 0 ? "" : ` ${quoteIdentifier(statement.name)}`}`);
8173
+ return this.execute(`${action}${statement.name === void 0 ? "" : ` ${quoteSqlIdentifier(statement.name)}`}`);
8174
8174
  }
8175
8175
  if (options.writer === void 0) {
8176
8176
  return this.#coordinateWrite(async () => {
@@ -8727,7 +8727,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
8727
8727
  return true;
8728
8728
  return keyPredicates.every((predicate) => zoneMapCanMatch(description, predicate));
8729
8729
  });
8730
- if (canAffect)
8730
+ if (segment.kind === "delete" || canAffect)
8731
8731
  prunedSegments.push(segment);
8732
8732
  continue;
8733
8733
  }
@@ -9936,13 +9936,21 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
9936
9936
  async compactTable(tableName, options = {}) {
9937
9937
  return this.#foreground(async () => {
9938
9938
  const maxBlocks = boundedMaintenanceBatchItems(options.maxBlocksPerStep ?? 16, "Compaction blocks per step");
9939
- let progress = await this.compactTableStep(tableName, { ...options, maxBlocks });
9940
- while (progress.result === null) {
9941
- if (progress.jobId === null)
9942
- throw new Error("Compaction progress lost its job ID");
9943
- 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
+ }
9944
9953
  }
9945
- return progress.result;
9946
9954
  });
9947
9955
  }
9948
9956
  async compactTableStep(tableName, options = {}) {
@@ -10046,13 +10054,21 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
10046
10054
  do {
10047
10055
  reconciliationCursor = (await this.#reconcileCompactionPage(reconciliationCursor)).nextCursor;
10048
10056
  } while (reconciliationCursor !== null);
10049
- let progress = await this.collectGarbageStep({
10050
- maxItems,
10051
- ...options.maxPlanningItems === void 0 ? {} : { maxPlanningItems: options.maxPlanningItems },
10052
- ...options.retainRecentVersions === void 0 ? {} : { retainRecentVersions: options.retainRecentVersions }
10053
- });
10054
- while (progress.result === null) {
10055
- 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
+ }
10056
10072
  }
10057
10073
  await this.#pruneFinishedJobRecords();
10058
10074
  this.#autoCollectionDebtCommits = 0;
@@ -10138,6 +10154,12 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
10138
10154
  if (reclaimed || !moreWork)
10139
10155
  this.#autoCollectionDebtCommits = 0;
10140
10156
  }).catch((error) => {
10157
+ if (this.#closed)
10158
+ return;
10159
+ if (error instanceof GarbageCollectionJobConflictError) {
10160
+ this.#autoCollectionRequested = true;
10161
+ return;
10162
+ }
10141
10163
  this.#autoCollectionConsecutiveFailures += 1;
10142
10164
  const at = dateMilliseconds(this.#now());
10143
10165
  this.#autoCollectionLastError = {
@@ -10431,15 +10453,19 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
10431
10453
  }
10432
10454
  };
10433
10455
  let discovered;
10434
- try {
10435
- discovered = await this.store.createGarbageCollectionJob(input);
10436
- } catch (error) {
10437
- if (!(error instanceof GarbageCollectionJobConflictError))
10438
- throw error;
10439
- const raced = await this.#findActiveGarbageCollectionJob();
10440
- if (raced === void 0)
10441
- throw error;
10442
- 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
+ }
10443
10469
  }
10444
10470
  if (continuation !== void 0) {
10445
10471
  await this.store.removeGarbageCollectionJob(continuation.jobId);
@@ -11421,7 +11447,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
11421
11447
  columns: columns2
11422
11448
  });
11423
11449
  }
11424
- assertCanonicalMergeSourceOrder(describedSegments);
11450
+ assertCanonicalMergeSourceOrder(sourceSegments.map((segment) => sourceOrderTuple(segment, transactions, "Compaction source")));
11425
11451
  const resolved = await this.#resolveMergeOutput(table, describedSegments, keyColumn, memoryBudgetBytes, snapshot);
11426
11452
  const { columns, rowIdSpans, totalRows } = resolved;
11427
11453
  const rowIdEnvelope = rowIdSpanEnvelope(rowIdSpans);
@@ -11900,6 +11926,12 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
11900
11926
  this.#afterCompactionCommit(manifest2, table.id);
11901
11927
  return manifest2;
11902
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
+ }
11903
11935
  if (!(error instanceof WriteConflictError))
11904
11936
  throw error;
11905
11937
  publicationConflict = error;
@@ -12704,25 +12736,42 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
12704
12736
  const indexId = this.#createId();
12705
12737
  const storageColumnId = `secondary-index:${indexId}`;
12706
12738
  const buildId = this.#createId();
12707
- const marked = await this.store.updateTable(table.id, table.revision, {
12708
- secondaryIndexes: {
12709
- ...table.secondaryIndexes,
12710
- [indexId]: {
12711
- name: indexName,
12712
- columnId: columns[0]?.id ?? "",
12713
- columnIds: columns.map((column) => column.id),
12714
- directions: indexColumns.map((column) => column.direction),
12715
- ...options.unique === true ? { unique: true } : {},
12716
- termEncoding: "tuple-v1",
12717
- storage: "postings-v1",
12718
- storageColumnId,
12719
- locator: table.uniqueKeyColumnId === void 0 ? "row-id" : "key-hash-v1",
12720
- state: "building",
12721
- buildId,
12722
- 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 });
12723
12771
  }
12772
+ current = fresh;
12724
12773
  }
12725
- });
12774
+ }
12726
12775
  try {
12727
12776
  await this.#buildSecondaryIndex(marked, indexId);
12728
12777
  } catch (error) {
@@ -12970,8 +13019,6 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
12970
13019
  const byTerm = /* @__PURE__ */ new Map();
12971
13020
  for (let row = start; row < end; row += 1) {
12972
13021
  const values = valueVectors.map((vector, position) => vector === void 0 ? null : storedSqlValueFromExecution(columns[position], vectorValue(vector, row)));
12973
- if (values.some((value) => value === null))
12974
- continue;
12975
13022
  const locator = secondaryKeyLocator(keyColumn.type, storedSqlValueFromExecution(keyColumn, vectorValue(keyVector, row)));
12976
13023
  addSecondaryPosting(byTerm, index, columns, values, locator, uniqueTerms);
12977
13024
  }
@@ -13022,8 +13069,6 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
13022
13069
  const byTerm = /* @__PURE__ */ new Map();
13023
13070
  for (let row = start; row < end; row += 1) {
13024
13071
  const values = valueVectors.map((vector, position) => vector === void 0 ? null : storedSqlValueFromExecution(columns[position], vectorValue(vector, row)));
13025
- if (values.some((value) => value === null))
13026
- continue;
13027
13072
  const locator = keyColumn === void 0 ? rowIdAt?.(row) ?? 0n : secondaryKeyLocator(keyColumn.type, keyVector === void 0 ? null : storedSqlValueFromExecution(keyColumn, vectorValue(keyVector, row)));
13028
13073
  addSecondaryPosting(byTerm, index, columns, values, locator, uniqueTerms);
13029
13074
  }
@@ -13112,8 +13157,6 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
13112
13157
  const vector = vectors[position];
13113
13158
  return vector === void 0 ? column.backfill ?? null : vectorValue(vector, row);
13114
13159
  });
13115
- if (values.some((value) => value === null))
13116
- continue;
13117
13160
  const locator = keyColumn === void 0 ? rowIdAt?.(segmentRow + row) ?? 0n : secondaryKeyLocator(keyColumn.type, keys === void 0 ? null : vectorValue(keys, row));
13118
13161
  addSecondaryPosting(byTerm, index, columns, values, locator);
13119
13162
  }
@@ -13523,6 +13566,10 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
13523
13566
  let selectedRowStart = 0;
13524
13567
  const selected = [];
13525
13568
  for (const segment of segments) {
13569
+ if (segment.kind === "delete") {
13570
+ selected.push(segment);
13571
+ continue;
13572
+ }
13526
13573
  const anchorColumn2 = keyColumn ?? table.columns[0];
13527
13574
  if (anchorColumn2 === void 0)
13528
13575
  return { segments, pruned: false };
@@ -13606,7 +13653,10 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
13606
13653
  ]))
13607
13654
  });
13608
13655
  }
13609
- 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 };
13610
13660
  }
13611
13661
  #scheduleSecondaryIndexBuild(table, indexId) {
13612
13662
  if (this.#closed)
@@ -13785,7 +13835,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
13785
13835
  const kind = segment.kind;
13786
13836
  return kind === "insert" || kind === "base" || kind === "delete" || kind === "update";
13787
13837
  })) {
13788
- 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);
13789
13839
  if (overlay !== void 0)
13790
13840
  return overlay;
13791
13841
  }
@@ -15429,35 +15479,6 @@ function sortedRowIdsIntersectRange(sorted, start, endExclusive) {
15429
15479
  }
15430
15480
  return low < sorted.length && (sorted[low] ?? 0n) < endExclusive;
15431
15481
  }
15432
- function addFtsDocument(byTerm, value, rowId) {
15433
- const rendered = renderDocumentValue(value);
15434
- if (rendered === void 0)
15435
- return 0;
15436
- const tokens = ftsTokenize(rendered);
15437
- const counts = /* @__PURE__ */ new Map();
15438
- for (const token of tokens)
15439
- counts.set(token, (counts.get(token) ?? 0) + 1);
15440
- for (const [term, tf] of counts) {
15441
- const posting = byTerm.get(term) ?? { rowIds: [], tf: [] };
15442
- posting.rowIds.push(rowId);
15443
- posting.tf.push(tf);
15444
- byTerm.set(term, posting);
15445
- }
15446
- return tokens.length;
15447
- }
15448
- function sortedFtsPostings(byTerm) {
15449
- return [...byTerm.entries()].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([term, posting]) => ({ term, rowIds: posting.rowIds, tf: posting.tf }));
15450
- }
15451
- function postingFrequencyTotal(postings) {
15452
- let total = 0;
15453
- for (const posting of postings) {
15454
- for (const frequency of posting.tf) {
15455
- total = safeWholeNumberSum([total, frequency], "Posting term-frequency total");
15456
- }
15457
- }
15458
- return total;
15459
- }
15460
- const secondaryNumberBits = new DataView(new ArrayBuffer(8));
15461
15482
  function storedSqlValueFromExecution(column, value) {
15462
15483
  if (column?.type === "string" && column.sqlDomain === void 0 && typeof value === "string") {
15463
15484
  return externalSqlTextValue(value);
@@ -15487,661 +15508,11 @@ function executionSqlValueFromInput(column, value) {
15487
15508
  const stored = column?.sqlDomain === void 0 ? value : normalizeSqlDomainValue(column.sqlDomain, value);
15488
15509
  return executionSqlValueFromStorage(column, stored);
15489
15510
  }
15490
- function secondaryIndexTerm(type, value) {
15491
- if (value === null)
15492
- throw new TypeError("A NULL has no secondary-index comparison term");
15493
- if (type === "string") {
15494
- if (typeof value !== "string")
15495
- throw new TypeError("Invalid string index value");
15496
- assertIndexedStringLength(value);
15497
- return value;
15498
- }
15499
- if (type === "boolean") {
15500
- if (typeof value !== "boolean")
15501
- throw new TypeError("Invalid boolean index value");
15502
- return value ? "1" : "0";
15503
- }
15504
- const numeric = type === "datetime" && value instanceof Date ? dateMilliseconds(value) : value;
15505
- if (typeof numeric !== "number" || !Number.isFinite(numeric)) {
15506
- throw new TypeError(`Invalid ${type} index value`);
15507
- }
15508
- secondaryNumberBits.setFloat64(0, numeric === 0 ? 0 : numeric, false);
15509
- const bits = secondaryNumberBits.getBigUint64(0, false);
15510
- const sortable = (bits & 0x8000000000000000n) === 0n ? bits ^ 0x8000000000000000n : ~bits & 0xffffffffffffffffn;
15511
- return sortable.toString(16).padStart(16, "0");
15512
- }
15513
- function secondaryTupleComponent(type, value) {
15514
- if (value === null)
15515
- throw new TypeError("A NULL has no secondary-index comparison term");
15516
- if (type === "string") {
15517
- if (typeof value !== "string")
15518
- throw new TypeError("Invalid string index value");
15519
- assertIndexedStringLength(value);
15520
- if (value.length * 5 + 5 > MAX_FTS_POSTING_TERM_CHARACTERS) {
15521
- throw new RangeError("Composite index term exceeds the persisted term limit");
15522
- }
15523
- let encoded = "";
15524
- for (let index = 0; index < value.length; index += 1) {
15525
- encoded += (value.charCodeAt(index) + 1).toString(16).padStart(5, "0");
15526
- }
15527
- return `${encoded}00000`;
15528
- }
15529
- if (type === "boolean") {
15530
- if (typeof value !== "boolean")
15531
- throw new TypeError("Invalid boolean index value");
15532
- return value ? "1" : "0";
15533
- }
15534
- return secondaryIndexTerm(type, value);
15535
- }
15536
- const reversedHex = new Map(Array.from("0123456789abcdef", (character, index) => [
15537
- character,
15538
- "fedcba9876543210"[index] ?? ""
15539
- ]));
15540
- function reverseSecondaryHex(input) {
15541
- let output = "";
15542
- for (let index = 0; index < input.length; index += 1) {
15543
- const character = input.charAt(index);
15544
- const reversed = reversedHex.get(character);
15545
- if (reversed === void 0)
15546
- throw new Error("Secondary index has a non-hexadecimal term");
15547
- output += reversed;
15548
- }
15549
- return output;
15550
- }
15551
- function secondaryTupleIndexTerm(index, columns, values) {
15552
- if (columns.length !== values.length)
15553
- throw new TypeError("Index key has the wrong arity");
15554
- const directions = secondaryIndexDirections(index);
15555
- let term = "";
15556
- for (const [position, column] of columns.entries()) {
15557
- const component = secondaryTupleComponent(column.type, values[position] ?? null);
15558
- if (term.length + component.length > MAX_FTS_POSTING_TERM_CHARACTERS) {
15559
- throw new RangeError("Composite index term exceeds the persisted term limit");
15560
- }
15561
- term += directions[position] === "desc" ? reverseSecondaryHex(component) : component;
15562
- }
15563
- return term;
15564
- }
15565
- function secondaryIndexComponentTerm(index, column, position, value) {
15566
- const component = secondaryTupleComponent(column.type, value);
15567
- if (secondaryIndexDirections(index)[position] !== "desc")
15568
- return component;
15569
- return reverseSecondaryHex(component);
15570
- }
15571
- function decodeSecondaryTupleTerm(index, columns, term) {
15572
- const directions = secondaryIndexDirections(index);
15573
- let offset = 0;
15574
- const values = columns.map((column, position) => {
15575
- const descending = directions[position] === "desc";
15576
- const restore = (encoded2) => descending ? reverseSecondaryHex(encoded2) : encoded2;
15577
- if (column.type === "string") {
15578
- let value2 = "";
15579
- for (; ; ) {
15580
- const group = restore(term.slice(offset, offset + 5));
15581
- if (group.length !== 5)
15582
- throw new Error(`Secondary index ${index.name} has a bad term`);
15583
- offset += 5;
15584
- if (group === "00000")
15585
- return value2;
15586
- const code = Number.parseInt(group, 16) - 1;
15587
- if (!Number.isInteger(code) || code < 0 || code > 65535) {
15588
- throw new Error(`Secondary index ${index.name} has a bad string term`);
15589
- }
15590
- value2 += String.fromCharCode(code);
15591
- }
15592
- }
15593
- if (column.type === "boolean") {
15594
- const encoded2 = restore(term.slice(offset, offset + 1));
15595
- offset += 1;
15596
- if (encoded2 !== "0" && encoded2 !== "1") {
15597
- throw new Error(`Secondary index ${index.name} has a bad boolean term`);
15598
- }
15599
- return encoded2 === "1";
15600
- }
15601
- const encoded = restore(term.slice(offset, offset + 16));
15602
- if (encoded.length !== 16)
15603
- throw new Error(`Secondary index ${index.name} has a bad term`);
15604
- offset += 16;
15605
- const sortable = BigInt(`0x${encoded}`);
15606
- const bits = (sortable & 0x8000000000000000n) === 0n ? ~sortable & 0xffffffffffffffffn : sortable ^ 0x8000000000000000n;
15607
- secondaryNumberBits.setBigUint64(0, bits, false);
15608
- const value = secondaryNumberBits.getFloat64(0, false);
15609
- return column.type === "datetime" ? new Date(value) : value;
15610
- });
15611
- if (offset !== term.length)
15612
- throw new Error(`Secondary index ${index.name} has a bad term`);
15613
- return values;
15614
- }
15615
- function secondaryKeyLocator(type, value) {
15616
- const token = keyToken(type, value);
15617
- let hash = 2166136261;
15618
- for (let index = 0; index < token.length; index += 1) {
15619
- const code = token.charCodeAt(index);
15620
- hash = Math.imul(hash ^ code & 255, 16777619) >>> 0;
15621
- hash = Math.imul(hash ^ code >>> 8, 16777619) >>> 0;
15622
- }
15623
- return BigInt(hash >>> 0);
15624
- }
15625
- function addSecondaryPosting(byTerm, index, columns, values, locator, uniqueTerms) {
15626
- if (values.some((value) => value === null))
15627
- return;
15628
- const first = columns[0];
15629
- if (first === void 0)
15630
- return;
15631
- const term = secondaryTupleIndexTerm(index, columns, values);
15632
- if (uniqueTerms?.has(term) === true) {
15633
- throw new TypeError(`UNIQUE index ${index.name} has a duplicate key`);
15634
- }
15635
- uniqueTerms?.add(term);
15636
- const posting = byTerm.get(term) ?? { rowIds: [], tf: [] };
15637
- posting.rowIds.push(locator);
15638
- posting.tf.push(1);
15639
- byTerm.set(term, posting);
15640
- }
15641
- function sortedSecondaryPostings(byTerm) {
15642
- return [...byTerm.entries()].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([term, posting]) => {
15643
- const rowIds = [...new Set(posting.rowIds)].sort((left, right) => left < right ? -1 : left > right ? 1 : 0);
15644
- return { term, rowIds, tf: rowIds.map(() => 1) };
15645
- });
15646
- }
15647
- function appendRowIdLocator(segments, expectedRows) {
15648
- const spans = appendRowIdSpans(segments, expectedRows);
15649
- let spanIndex = 0;
15650
- return (row) => {
15651
- while (spanIndex < spans.length && row >= (spans[spanIndex]?.rowStart ?? 0) + (spans[spanIndex]?.rowCount ?? 0)) {
15652
- spanIndex += 1;
15653
- }
15654
- const span = spans[spanIndex];
15655
- if (span === void 0 || row < span.rowStart) {
15656
- throw new Error(`Secondary-index row ID is missing: ${String(row)}`);
15657
- }
15658
- return span.rowIdStart + BigInt(row - span.rowStart);
15659
- };
15660
- }
15661
- function appendRowIdSpans(segments, expectedRows) {
15662
- const spans = [];
15663
- let outputStart = 0;
15664
- for (const segment of segments) {
15665
- const kind = segment.kind;
15666
- if (kind !== "insert" && kind !== "base")
15667
- continue;
15668
- for (const span of mergeSourceRowIdSpans(segment, kind)) {
15669
- spans.push({ ...span, rowStart: outputStart + span.rowStart });
15670
- }
15671
- outputStart += segment.rowCount;
15672
- }
15673
- const rows = spans.reduce((total, span) => total + span.rowCount, 0);
15674
- if (rows !== expectedRows)
15675
- throw new Error("Secondary-index row IDs differ from table rows");
15676
- return spans;
15677
- }
15678
- function appendRowForLocator(segments, expectedRows) {
15679
- const spans = appendRowIdSpans(segments, expectedRows).sort((left, right) => left.rowIdStart < right.rowIdStart ? -1 : left.rowIdStart > right.rowIdStart ? 1 : 0);
15680
- return (locator) => {
15681
- let low = 0;
15682
- let high = spans.length;
15683
- while (low < high) {
15684
- const middle = low + high >>> 1;
15685
- if ((spans[middle]?.rowIdStart ?? 0n) <= locator)
15686
- low = middle + 1;
15687
- else
15688
- high = middle;
15689
- }
15690
- const span = spans[low - 1];
15691
- if (span === void 0)
15692
- return void 0;
15693
- const offset = locator - span.rowIdStart;
15694
- if (offset < 0n || offset >= BigInt(span.rowCount))
15695
- return void 0;
15696
- return span.rowStart + Number(offset);
15697
- };
15698
- }
15699
- function chunkFtsPostings(postings, size = 128) {
15700
- const chunks = [];
15701
- let chunk = [];
15702
- let rowIds = 0;
15703
- const flush = () => {
15704
- if (chunk.length === 0)
15705
- return;
15706
- chunks.push(chunk);
15707
- chunk = [];
15708
- rowIds = 0;
15709
- };
15710
- for (const posting of postings) {
15711
- if (posting.rowIds.length > MAX_FTS_POSTING_ROW_IDS_PER_CHUNK) {
15712
- throw new RangeError("One posting exceeds the persisted row-id chunk limit");
15713
- }
15714
- if (chunk.length >= Math.min(size, MAX_FTS_POSTINGS_PER_CHUNK) || rowIds + posting.rowIds.length > MAX_FTS_POSTING_ROW_IDS_PER_CHUNK) {
15715
- flush();
15716
- }
15717
- chunk.push(posting);
15718
- rowIds += posting.rowIds.length;
15719
- }
15720
- flush();
15721
- return chunks;
15722
- }
15723
- function buildFtsColumnDeltas(table, input, rowIdStart) {
15724
- const active = Object.entries(table.ftsColumns ?? {}).filter(([, record]) => record.state !== "invalid");
15725
- if (active.length === 0)
15726
- return [];
15727
- const columnsById = new Map(table.columns.map((column) => [column.id, column]));
15728
- return active.flatMap(([columnId]) => {
15729
- const column = columnsById.get(columnId);
15730
- if (column === void 0)
15731
- return [];
15732
- const byTerm = /* @__PURE__ */ new Map();
15733
- let totalTokens = 0;
15734
- (input.columns[column.name] ?? []).forEach((value, index) => {
15735
- const documentValue = column.type === "string" && column.sqlDomain === void 0 && typeof value === "string" ? protectedSqlTextValue(value) : value;
15736
- totalTokens += addFtsDocument(byTerm, documentValue, rowIdStart + BigInt(index));
15737
- });
15738
- return [{ columnId, postings: sortedFtsPostings(byTerm), totalTokens }];
15739
- });
15740
- }
15741
- function buildSecondaryInsertDeltas(table, input, rowIdStart) {
15742
- const active = Object.values(table.secondaryIndexes ?? {}).filter((index) => index.state !== "invalid");
15743
- if (active.length === 0)
15744
- return [];
15745
- const columnsById = new Map(table.columns.map((column) => [column.id, column]));
15746
- const keyColumn = getUniqueKeyColumn(table);
15747
- return active.flatMap((index) => {
15748
- const columns = secondaryIndexColumnIds(index).map((columnId) => columnsById.get(columnId));
15749
- if (columns.some((column) => column === void 0))
15750
- return [];
15751
- const indexedColumns = columns;
15752
- const byTerm = /* @__PURE__ */ new Map();
15753
- const keys = keyColumn === void 0 ? void 0 : input.columns[keyColumn.name] ?? [];
15754
- const rowCount = input.rowCount ?? Object.values(input.columns)[0]?.length ?? 0;
15755
- try {
15756
- for (let row = 0; row < rowCount; row += 1) {
15757
- const values = indexedColumns.map((column) => input.columns[column.name]?.[row] ?? null);
15758
- if (values.some((value) => value === null))
15759
- continue;
15760
- const locator = keyColumn === void 0 ? rowIdStart + BigInt(row) : secondaryKeyLocator(keyColumn.type, keys?.[row] ?? null);
15761
- addSecondaryPosting(byTerm, index, indexedColumns, values, locator);
15762
- }
15763
- } catch (error) {
15764
- if (error instanceof RangeError && index.unique !== true)
15765
- return [];
15766
- throw error;
15767
- }
15768
- const postings = sortedSecondaryPostings(byTerm);
15769
- return [
15770
- {
15771
- columnId: index.storageColumnId,
15772
- postings,
15773
- totalTokens: postingFrequencyTotal(postings)
15774
- }
15775
- ];
15776
- });
15777
- }
15778
- function buildSecondaryUpdateDeltas(table, input, preImages = []) {
15779
- const active = Object.values(table.secondaryIndexes ?? {}).filter((index) => index.state !== "invalid");
15780
- if (active.length === 0)
15781
- return [];
15782
- const keyColumn = getUniqueKeyColumn(table);
15783
- if (keyColumn === void 0)
15784
- return [];
15785
- const columnsById = new Map(table.columns.map((column) => [column.id, column]));
15786
- return active.flatMap((index) => {
15787
- const columns = secondaryIndexColumnIds(index).map((columnId) => columnsById.get(columnId));
15788
- if (columns.some((column) => column === void 0))
15789
- return [];
15790
- const indexedColumns = columns;
15791
- const byTerm = /* @__PURE__ */ new Map();
15792
- const affected = indexedColumns.some((column) => input.changes[column.name] !== void 0);
15793
- if (affected) {
15794
- try {
15795
- for (let row = 0; row < input.keys.length; row += 1) {
15796
- const values = indexedColumns.map((column) => input.changes[column.name] === void 0 ? preImages[row]?.[column.name] ?? null : input.changes[column.name]?.[row] ?? null);
15797
- if (values.some((value) => value === null))
15798
- continue;
15799
- addSecondaryPosting(byTerm, index, indexedColumns, values, secondaryKeyLocator(keyColumn.type, input.keys[row] ?? null));
15800
- }
15801
- } catch (error) {
15802
- if (error instanceof RangeError && index.unique !== true)
15803
- return [];
15804
- throw error;
15805
- }
15806
- }
15807
- const postings = sortedSecondaryPostings(byTerm);
15808
- return [
15809
- {
15810
- columnId: index.storageColumnId,
15811
- postings,
15812
- totalTokens: postingFrequencyTotal(postings)
15813
- }
15814
- ];
15815
- });
15816
- }
15817
- function secondaryIndexUpdateNeedsPreImages(table, input) {
15818
- const changed = new Set(Object.keys(input.changes));
15819
- const columnsById = new Map(table.columns.map((column) => [column.id, column.name]));
15820
- return Object.values(table.secondaryIndexes ?? {}).some((index) => {
15821
- if (index.state === "invalid")
15822
- return false;
15823
- const names = secondaryIndexColumnIds(index).map((columnId) => columnsById.get(columnId));
15824
- return names.some((name) => name !== void 0 && changed.has(name)) && names.some((name) => name === void 0 || !changed.has(name));
15825
- });
15826
- }
15827
- function buildSecondaryDeleteCoverage(table) {
15828
- return Object.values(table.secondaryIndexes ?? {}).flatMap((index) => index.state === "invalid" ? [] : [
15829
- {
15830
- columnId: index.storageColumnId,
15831
- postings: [],
15832
- totalTokens: 0
15833
- }
15834
- ]);
15835
- }
15836
- function readyUniqueSecondaryIndexes(table) {
15837
- const columnsById = new Map(table.columns.map((column) => [column.id, column]));
15838
- return Object.entries(table.secondaryIndexes ?? {}).flatMap(([indexId, index]) => {
15839
- if (index.unique !== true || index.uniqueEnforced !== true)
15840
- return [];
15841
- const columns = secondaryIndexColumnIds(index).map((columnId) => columnsById.get(columnId));
15842
- return columns.some((column) => column === void 0) ? [] : [{ indexId, index, columns }];
15843
- });
15844
- }
15845
- function secondaryUniqueTerm(index, columns, values) {
15846
- if (values.some((value) => value === null))
15847
- return void 0;
15848
- return secondaryTupleIndexTerm(index, columns, values);
15849
- }
15850
- function assertNoDuplicateUniqueTerms(index, terms) {
15851
- const seen = /* @__PURE__ */ new Set();
15852
- for (const term of terms) {
15853
- if (seen.has(term))
15854
- throw new TypeError(`UNIQUE index ${index.name} has a duplicate key`);
15855
- seen.add(term);
15856
- }
15857
- }
15858
- function assertBatchSecondaryTermsDistinct(table, input) {
15859
- const rowCount = input.rowCount ?? Object.values(input.columns)[0]?.length ?? 0;
15860
- for (const { index, columns } of readyUniqueSecondaryIndexes(table)) {
15861
- const terms = [];
15862
- for (let row = 0; row < rowCount; row += 1) {
15863
- const term = secondaryUniqueTerm(index, columns, columns.map((column) => input.columns[column.name]?.[row] ?? null));
15864
- if (term !== void 0)
15865
- terms.push(term);
15866
- }
15867
- assertNoDuplicateUniqueTerms(index, terms);
15868
- }
15869
- }
15870
- function stageSecondaryUniqueInsertChanges(transaction, table, input, oldImages) {
15871
- const rowCount = input.rowCount ?? Object.values(input.columns)[0]?.length ?? 0;
15872
- for (const { indexId, index, columns } of readyUniqueSecondaryIndexes(table)) {
15873
- const namespaceId = secondaryUniqueKeyNamespace(table.id, indexId);
15874
- const removed = (oldImages ?? []).flatMap((old) => {
15875
- if (old === void 0)
15876
- return [];
15877
- const term = secondaryUniqueTerm(index, columns, columns.map((column) => old[column.name] ?? null));
15878
- return term === void 0 ? [] : [term];
15879
- });
15880
- if (removed.length > 0) {
15881
- transaction.setUniqueKeyChanges({
15882
- tableId: namespaceId,
15883
- keyTokens: removed,
15884
- requireAbsent: false,
15885
- remove: true
15886
- });
15887
- }
15888
- const added = [];
15889
- for (let row = 0; row < rowCount; row += 1) {
15890
- const term = secondaryUniqueTerm(index, columns, columns.map((column) => input.columns[column.name]?.[row] ?? null));
15891
- if (term !== void 0)
15892
- added.push(term);
15893
- }
15894
- assertNoDuplicateUniqueTerms(index, added);
15895
- transaction.setUniqueKeyChanges({
15896
- tableId: namespaceId,
15897
- keyTokens: added,
15898
- requireAbsent: true
15899
- });
15900
- }
15901
- }
15902
- function stageSecondaryUniqueMutationChanges(transaction, table, input, oldImages) {
15903
- for (const { indexId, index, columns } of readyUniqueSecondaryIndexes(table)) {
15904
- const namespaceId = secondaryUniqueKeyNamespace(table.id, indexId);
15905
- const removed = oldImages.flatMap((old) => {
15906
- if (old === void 0)
15907
- return [];
15908
- const term = secondaryUniqueTerm(index, columns, columns.map((column) => old[column.name] ?? null));
15909
- return term === void 0 ? [] : [term];
15910
- });
15911
- transaction.setUniqueKeyChanges({
15912
- tableId: namespaceId,
15913
- keyTokens: removed,
15914
- requireAbsent: false,
15915
- remove: true
15916
- });
15917
- if (input === void 0)
15918
- continue;
15919
- const added = oldImages.flatMap((old, row) => {
15920
- if (old === void 0)
15921
- return [];
15922
- const term = secondaryUniqueTerm(index, columns, columns.map((column) => {
15923
- const assigned = input.changes[column.name];
15924
- return assigned === void 0 ? old[column.name] ?? null : assigned[row] ?? null;
15925
- }));
15926
- return term === void 0 ? [] : [term];
15927
- });
15928
- assertNoDuplicateUniqueTerms(index, added);
15929
- transaction.setUniqueKeyChanges({
15930
- tableId: namespaceId,
15931
- keyTokens: added,
15932
- requireAbsent: true
15933
- });
15934
- }
15935
- }
15936
15511
  function searchableFtsColumns(table) {
15937
15512
  if (table === void 0)
15938
15513
  return void 0;
15939
15514
  return visibleTableColumns(table).filter((column) => column.type !== "boolean").map((column) => column.name);
15940
15515
  }
15941
- const LIVE_HIDDEN_PREFIX = "__minnow_live_";
15942
- const LIVE_KEY_ALIAS = `${LIVE_HIDDEN_PREFIX}key`;
15943
- const LIVE_ORDER_ALIAS = `${LIVE_HIDDEN_PREFIX}order_`;
15944
- const LIVE_MAINTENANCE_MAX_DELTA_ROWS = 2048;
15945
- const LIVE_WINDOW_MARGIN_MIN = 16;
15946
- const LIVE_WINDOW_MARGIN_MAX = 64;
15947
- function liveKeyToken(value) {
15948
- if (typeof value === "number")
15949
- return `n:${String(value)}`;
15950
- if (typeof value === "string")
15951
- return `s:${value}`;
15952
- if (typeof value === "boolean")
15953
- return value ? "b:1" : "b:0";
15954
- if (value instanceof Date)
15955
- return `d:${String(dateMilliseconds(value))}`;
15956
- return "z";
15957
- }
15958
- function splitLiveHiddenColumns(executed, state) {
15959
- const publicCount = state.publicColumns.length;
15960
- const hidden = executed.columns.slice(publicCount);
15961
- const count = executed.rows.length;
15962
- const keys = new Array(count);
15963
- const order = state.orderTerms.map(() => new Array(count));
15964
- for (let index = 0; index < count; index += 1) {
15965
- const row = executed.rows[index] ?? {};
15966
- keys[index] = liveKeyToken(row[LIVE_KEY_ALIAS] ?? null);
15967
- for (const [term, { alias }] of state.orderTerms.entries()) {
15968
- const values = order[term];
15969
- if (values !== void 0)
15970
- values[index] = row[alias] ?? null;
15971
- }
15972
- for (const column of hidden)
15973
- Reflect.deleteProperty(row, column);
15974
- }
15975
- return {
15976
- result: externalizeQueryResult({
15977
- columns: executed.columns.slice(0, publicCount),
15978
- columnDomains: executed.columnDomains.slice(0, publicCount),
15979
- rows: executed.rows
15980
- }),
15981
- keys,
15982
- order
15983
- };
15984
- }
15985
- function liveMaintainedOutcome(state, previous, previousIndex) {
15986
- const positions = /* @__PURE__ */ new Map();
15987
- let retainedBytes = state.retainedBytes ?? 128 + planMemoKey(state.fullPlan).length * 2 + planMemoKey(state.deltaPlan).length * 2;
15988
- for (const [index, key] of state.keys.entries()) {
15989
- if (positions.has(key))
15990
- throw new TypeError("Duplicate live input key");
15991
- positions.set(key, index);
15992
- if (state.retainedBytes === void 0)
15993
- retainedBytes += liveRowStateBytes(state, index);
15994
- }
15995
- state = { ...state, positions, retainedBytes };
15996
- const visibleCount = state.limit === void 0 ? state.rows.length : Math.min(state.rows.length, state.limit);
15997
- const visible = state.rows.slice(0, visibleCount);
15998
- if (previous === void 0) {
15999
- return {
16000
- result: {
16001
- columns: [...state.publicColumns],
16002
- columnDomains: [...state.columnDomains],
16003
- rows: visible
16004
- },
16005
- state,
16006
- retainedBytes,
16007
- changed: true
16008
- };
16009
- }
16010
- const previousCount = previous.rows.length;
16011
- const retained = new Int32Array(visibleCount);
16012
- let same = visibleCount === previousCount;
16013
- for (let index = 0; index < visibleCount; index += 1) {
16014
- const was = previousIndex?.[index] ?? -1;
16015
- if (was >= 0 && was < previousCount) {
16016
- retained[index] = was;
16017
- if (was !== index)
16018
- same = false;
16019
- continue;
16020
- }
16021
- retained[index] = -1;
16022
- const before = previous.rows[index];
16023
- const now = visible[index];
16024
- if (before !== void 0 && now !== void 0 && sameLiveRow(before, now, previous.columns)) {
16025
- visible[index] = before;
16026
- retained[index] = index;
16027
- } else
16028
- same = false;
16029
- }
16030
- if (same)
16031
- return { result: previous, state, retainedBytes, changed: false };
16032
- return {
16033
- result: {
16034
- columns: [...state.publicColumns],
16035
- columnDomains: [...state.columnDomains],
16036
- rows: visible
16037
- },
16038
- state,
16039
- retainedBytes,
16040
- changed: true,
16041
- retained
16042
- };
16043
- }
16044
- function liveRowStateBytes(state, index) {
16045
- let bytes = 112 + (state.keys[index]?.length ?? 0) * 2 + estimateValuesBytes(Object.values(state.rows[index] ?? {})) * 2;
16046
- for (const values of state.order)
16047
- bytes += 8 + estimateValuesBytes([values[index]]) * 2;
16048
- return bytes;
16049
- }
16050
- function filterLiveRows(state, keep) {
16051
- const rows = [];
16052
- const keys = [];
16053
- const order = state.order.map(() => new Array());
16054
- const previous = [];
16055
- for (let index = 0; index < state.rows.length; index += 1) {
16056
- const row = state.rows[index];
16057
- if (row === void 0 || !keep(index))
16058
- continue;
16059
- rows.push(row);
16060
- keys.push(state.keys[index] ?? "z");
16061
- for (const [term, values] of state.order.entries()) {
16062
- order[term]?.push(values[index] ?? null);
16063
- }
16064
- previous.push(index);
16065
- }
16066
- return { rows, keys, order, previousIndex: Int32Array.from(previous) };
16067
- }
16068
- function trimLiveRows(rows, count) {
16069
- return {
16070
- rows: rows.rows.slice(0, count),
16071
- keys: rows.keys.slice(0, count),
16072
- order: rows.order.map((values) => values.slice(0, count)),
16073
- previousIndex: rows.previousIndex.slice(0, count)
16074
- };
16075
- }
16076
- function liveOrderComparator(terms) {
16077
- return (leftOrder, leftIndex, rightOrder, rightIndex) => {
16078
- for (const [term, { descending, nulls }] of terms.entries()) {
16079
- const a = leftOrder[term]?.[leftIndex] ?? null;
16080
- const b = rightOrder[term]?.[rightIndex] ?? null;
16081
- if (a === null || b === null) {
16082
- if (a === null && b === null)
16083
- continue;
16084
- const nullsFirst = nulls === "first" || nulls === void 0 && descending;
16085
- return a === null ? nullsFirst ? -1 : 1 : nullsFirst ? 1 : -1;
16086
- }
16087
- let comparison = compareSqlValues(a, b);
16088
- if (descending)
16089
- comparison = -comparison;
16090
- if (comparison !== 0)
16091
- return comparison;
16092
- }
16093
- return 0;
16094
- };
16095
- }
16096
- function mergeLiveRows(kept, added, compare, ordered) {
16097
- if (added.rows.length === 0)
16098
- return kept;
16099
- const terms = kept.order.length;
16100
- const total = kept.rows.length + added.rows.length;
16101
- const rows = new Array(total);
16102
- const keys = new Array(total);
16103
- const order = Array.from({ length: terms }, () => new Array(total));
16104
- const previousIndex = new Int32Array(total);
16105
- const take = (source, from, to) => {
16106
- rows[to] = source.rows[from] ?? {};
16107
- keys[to] = source.keys[from] ?? "z";
16108
- for (let term = 0; term < terms; term += 1) {
16109
- const values = order[term];
16110
- if (values !== void 0)
16111
- values[to] = source.order[term]?.[from] ?? null;
16112
- }
16113
- previousIndex[to] = source.previousIndex[from] ?? -1;
16114
- };
16115
- if (!ordered) {
16116
- for (let index = 0; index < kept.rows.length; index += 1)
16117
- take(kept, index, index);
16118
- for (let index = 0; index < added.rows.length; index += 1) {
16119
- take(added, index, kept.rows.length + index);
16120
- }
16121
- return { rows, keys, order, previousIndex };
16122
- }
16123
- const addedIndexes = added.rows.map((_, index) => index);
16124
- addedIndexes.sort((left, right) => compare(added.order, left, added.order, right));
16125
- let keptIndex = 0;
16126
- let addedPosition = 0;
16127
- for (let to = 0; to < total; to += 1) {
16128
- const addedIndex = addedIndexes[addedPosition];
16129
- const takeAdded = addedIndex !== void 0 && (keptIndex >= kept.rows.length || compare(added.order, addedIndex, kept.order, keptIndex) < 0);
16130
- if (takeAdded) {
16131
- take(added, addedIndex, to);
16132
- addedPosition += 1;
16133
- } else {
16134
- take(kept, keptIndex, to);
16135
- keptIndex += 1;
16136
- }
16137
- }
16138
- return { rows, keys, order, previousIndex };
16139
- }
16140
- function getUniqueKeyColumn(table) {
16141
- if (table.uniqueKeyColumnId === void 0)
16142
- return void 0;
16143
- return table.columns.find((column) => column.id === table.uniqueKeyColumnId);
16144
- }
16145
15516
  function visibleTableColumns(table) {
16146
15517
  return table.columns.filter((column) => column.hidden !== true);
16147
15518
  }
@@ -16198,9 +15569,6 @@ function foldAssignmentColumns(table, assignments) {
16198
15569
  });
16199
15570
  return folded.some((assignment, index) => assignment !== assignments[index]) ? folded : assignments;
16200
15571
  }
16201
- function quoteIdentifier(name) {
16202
- return `"${name.replace(/"/g, '""')}"`;
16203
- }
16204
15572
  function normalizeDomainBatch(table, input) {
16205
15573
  for (const column of table.columns) {
16206
15574
  const values = input.columns[column.name];
@@ -16606,36 +15974,6 @@ function valueMatchesColumnType(column, value) {
16606
15974
  return value instanceof Date;
16607
15975
  }
16608
15976
  }
16609
- function keyToken(type, value) {
16610
- if (value === null)
16611
- throw new TypeError("Unique key cannot be null");
16612
- switch (type) {
16613
- case "boolean":
16614
- if (typeof value !== "boolean")
16615
- throw new TypeError("Invalid boolean unique key");
16616
- return value ? "boolean:true" : "boolean:false";
16617
- case "number":
16618
- if (typeof value !== "number" || !Number.isFinite(value)) {
16619
- throw new TypeError("Invalid number unique key");
16620
- }
16621
- return `number:${String(value)}`;
16622
- case "string":
16623
- if (typeof value !== "string")
16624
- throw new TypeError("Invalid string unique key");
16625
- assertIndexedStringLength(value);
16626
- return `string:${value}`;
16627
- case "datetime":
16628
- if (!(value instanceof Date) || !Number.isFinite(dateMilliseconds(value))) {
16629
- throw new TypeError("Invalid datetime unique key");
16630
- }
16631
- return `datetime:${String(dateMilliseconds(value))}`;
16632
- }
16633
- }
16634
- function assertIndexedStringLength(value) {
16635
- if (value.length > MAX_INDEXED_STRING_CHARACTERS) {
16636
- throw new RangeError(`Indexed strings cannot exceed ${String(MAX_INDEXED_STRING_CHARACTERS)} characters`);
16637
- }
16638
- }
16639
15977
  function formatValue(value) {
16640
15978
  return value instanceof Date ? dateIsoString(value) : String(value);
16641
15979
  }
@@ -17619,13 +16957,14 @@ function secondaryIndexPredicates(plan, table) {
17619
16957
  break;
17620
16958
  if (constraint.lower === void 0 && constraint.upper === void 0)
17621
16959
  break;
17622
- const descending = secondaryIndexDirections(index)[position] === "desc";
16960
+ const direction = secondaryIndexDirections(index)[position] ?? "asc";
16961
+ const descending = direction === "desc";
17623
16962
  queries = prefixes.map((prefix) => {
17624
16963
  const query = {};
17625
16964
  const logicalLower = descending ? constraint.upper : constraint.lower;
17626
16965
  const logicalUpper = descending ? constraint.lower : constraint.upper;
17627
16966
  if (logicalLower === void 0) {
17628
- query.lower = prefix;
16967
+ query.lower = descending ? prefix + LOWEST_SECONDARY_COMPONENT : prefix;
17629
16968
  query.lowerInclusive = true;
17630
16969
  } else {
17631
16970
  const term = prefix + secondaryIndexComponentTerm(index, column, position, logicalLower.value);
@@ -17633,7 +16972,7 @@ function secondaryIndexPredicates(plan, table) {
17633
16972
  query.lowerInclusive = logicalLower.inclusive;
17634
16973
  }
17635
16974
  if (logicalUpper === void 0) {
17636
- query.upper = `${prefix}\uFFFF`;
16975
+ query.upper = descending ? `${prefix}\uFFFF` : prefix + ASCENDING_NULL_COMPONENT;
17637
16976
  query.upperInclusive = false;
17638
16977
  } else {
17639
16978
  const term = prefix + secondaryIndexComponentTerm(index, column, position, logicalUpper.value);
@@ -17647,6 +16986,9 @@ function secondaryIndexPredicates(plan, table) {
17647
16986
  }
17648
16987
  if (matchedColumns === 0)
17649
16988
  continue;
16989
+ if (matchedColumns < indexedColumns.length && !secondaryIndexTermsCoverNulls(index) && indexedColumns.slice(matchedColumns).some((column) => column.nullable)) {
16990
+ continue;
16991
+ }
17650
16992
  if (queries === void 0) {
17651
16993
  const complete = matchedColumns === indexedColumns.length;
17652
16994
  queries = prefixes.map((term) => ({ term, prefix: !complete }));
@@ -17972,41 +17314,30 @@ function validateMergeSegmentShape(table, segmentId, kind, columns, keyColumnId)
17972
17314
  throw new Error(`Update compaction source has invalid columns: ${segmentId}`);
17973
17315
  }
17974
17316
  }
17975
- function mergeSourceRowIdSpans(segment, kind) {
17976
- if (kind === "update" || kind === "delete") {
17977
- if (segment.rowIdStart !== 0n || segment.rowIdEndExclusive !== 0n || segment.rowIdSpans.length !== 0) {
17978
- throw new Error(`Mutation marker unexpectedly owns row IDs: ${segment.id}`);
17979
- }
17980
- return [];
17981
- }
17982
- const spans = segment.rowIdSpans.length === 0 ? [{ rowStart: 0, rowCount: segment.rowCount, rowIdStart: segment.rowIdStart }] : segment.rowIdSpans.map((span) => ({ ...span }));
17983
- const envelope = rowIdSpanEnvelope(spans);
17984
- let rowStart = 0;
17985
- for (const [index, span] of spans.entries()) {
17986
- if (span.rowStart !== rowStart || span.rowCount <= 0) {
17987
- throw new Error(`Segment row ID spans are not contiguous: ${segment.id}`);
17988
- }
17989
- const previous = spans[index - 1];
17990
- if (previous !== void 0 && previous.rowIdStart + BigInt(previous.rowCount) === span.rowIdStart) {
17991
- throw new Error(`Segment row ID spans are not coalesced: ${segment.id}`);
17992
- }
17993
- 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;
17994
17320
  }
17995
- if (rowStart !== segment.rowCount || envelope.start !== segment.rowIdStart || envelope.endExclusive !== segment.rowIdEndExclusive) {
17996
- 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;
17997
17325
  }
17998
- const intervals = spans.map((span) => ({
17999
- start: span.rowIdStart,
18000
- end: span.rowIdStart + BigInt(span.rowCount)
18001
- })).sort((left, right) => left.start < right.start ? -1 : left.start > right.start ? 1 : 0);
18002
- for (let index = 1; index < intervals.length; index += 1) {
18003
- const previous = intervals[index - 1];
18004
- const current = intervals[index];
18005
- if (previous !== void 0 && current !== void 0 && current.start < previous.end) {
18006
- 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);
18007
17338
  }
18008
17339
  }
18009
- return spans;
17340
+ return result;
18010
17341
  }
18011
17342
  function assertCanonicalMergeSourceOrder(segments) {
18012
17343
  for (let index = 1; index < segments.length; index += 1) {
@@ -18018,7 +17349,7 @@ function assertCanonicalMergeSourceOrder(segments) {
18018
17349
  }
18019
17350
  }
18020
17351
  function compareMergeSourceOrder(left, right) {
18021
- 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);
18022
17353
  }
18023
17354
  function sourceOrderTuple(segment, transactions, label) {
18024
17355
  if (segment === void 0)
@@ -18030,6 +17361,7 @@ function sourceOrderTuple(segment, transactions, label) {
18030
17361
  return {
18031
17362
  logicalOrder: segment.logicalOrder,
18032
17363
  committedVersion: owner.committedVersion,
17364
+ commitOrdinal: segment.commitOrdinal,
18033
17365
  segmentId: segment.id
18034
17366
  };
18035
17367
  }
@@ -18352,20 +17684,6 @@ function rowRangeAt(ranges, rowIndex) {
18352
17684
  }
18353
17685
  return void 0;
18354
17686
  }
18355
- function rowIdSpanEnvelope(spans) {
18356
- if (spans.length === 0)
18357
- return { start: 0n, endExclusive: 0n };
18358
- let start = spans[0]?.rowIdStart ?? 0n;
18359
- let endExclusive = start + BigInt(spans[0]?.rowCount ?? 0);
18360
- for (const span of spans.slice(1)) {
18361
- if (span.rowIdStart < start)
18362
- start = span.rowIdStart;
18363
- const spanEnd = span.rowIdStart + BigInt(span.rowCount);
18364
- if (spanEnd > endExclusive)
18365
- endExclusive = spanEnd;
18366
- }
18367
- return { start, endExclusive };
18368
- }
18369
17687
  function validatePhysicalTablePlan(table, plan) {
18370
17688
  if (table.columns.length !== plan.columns.length || table.columns.some((column, index) => {
18371
17689
  const planned = plan.columns[index];