@minnowdb/core 0.7.2 → 0.7.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,4 @@
1
+ import { crossJoinPlan } from "../plan/model.js";
1
2
  import { toColumnarBatch } from "./batch.js";
2
3
  import { ArtifactCache } from "./artifact-cache.js";
3
4
  import { estimateBatchBytes, estimateRowBytes, estimateValuesBytes } from "./byte-estimates.js";
@@ -12,16 +13,16 @@ import { estimateCompactionRowsPerOutput, planAlignedWriteBlockRanges as writeBl
12
13
  import { MAX_CACHEABLE_TEXT_CHARACTERS, MAX_SQL_PARAMETERS } from "./cache-limits.js";
13
14
  import { fillColumnDefaults, patchAutoIncrementValues } from "./defaults.js";
14
15
  import { cachedQueryTerms, FTS_TOKENIZER_VERSION, renderDocumentValue, tokenize as ftsTokenize } from "./fts.js";
15
- 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_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";
16
+ 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";
16
17
  import { decodeSnapshotFrameStream, encodeSnapshotFrameStreamFooter, encodeSnapshotFrameStreamHeader, extendSnapshotFrameStreamChecksum, snapshotFrameEnvelopeParts, snapshotFrameStreamHeaderIdentity } from "../storage/snapshot.js";
17
18
  import { Snapshot, TransactionManager } from "../transactions/index.js";
18
- import { applyWindowFunctions, bindPendingSelectShapes, bindPlanParameters, bindStatementParameters, DUAL_TABLE, dualTableRows, blockHasSubqueries, combineUnionResults, compileCheckExpression, compileQuery, hasAggregate, createRecursiveCteState, annotateAvgArgumentScales, compileStatement, comparisonHolds, createPreparedColumnarQuery, evaluateJoinedRowExpression, evaluateRowExpression, externalizeQueryResult, expressionColumnNames, inferBlockSchema, inferResultColumnDomains, isDeferredInsertExpression, isDefaultInsertValue, referencedColumns, childExpressions, expandFtsColumns, expandNaturalJoins, expandSourceColumnAliases, expandViewSources, forEachBlockExpression, forEachNestedBlock, planContainsFts, planHasNaturalJoins, planHasSourceColumnAliases, planReadsTable, planReadsViews, planReadsBeyondSingleScan, projectResultColumns, queryResultNeedsExternalization, resolveStatementDatetimes, subqueryResolutionSteps, topLevelFtsMatchConjuncts, transparentProjectionSource, unknownColumnDomains, validateDefaultExpression, windowOutputDomain, windowOutputType, volatileScalarFunctionNames } from "./query.js";
19
+ import { applyWindowFunctions, bindPendingSelectShapes, bindPlanParameters, bindStatementParameters, DUAL_TABLE, dualTableRows, blockHasSubqueries, combineUnionResults, compileCheckExpression, compileQuery, hasAggregate, createRecursiveCteState, annotateAvgArgumentScales, compileStatement, comparisonHolds, createPreparedColumnarQuery, evaluateJoinedRowExpression, evaluateRowExpression, externalizeQueryResult, expressionColumnNames, inferBlockSchema, inferResultColumnDomains, isDeferredInsertExpression, isDefaultInsertValue, referencedColumns, childExpressions, 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";
19
20
  import { copyQueryResult, planMemoKey, queryResultMemoKey, queryResultRetainedBytes, RESULT_MEMO_MAX_BYTES } from "./query-cache.js";
20
21
  import { QueryMemoryBudgetError, QueryMemoryContext, DEFAULT_QUERY_MEMORY_BUDGET_BYTES } from "./memory.js";
21
22
  import { LiveQueryLimitError, LiveQuerySet, MAX_LIVE_QUERY_SETS_PER_DATABASE } from "./live.js";
22
23
  import { chooseJoinOrder, optimizePlan, qualifyCorrelatedReferences, renderPlan } from "./optimizer.js";
23
- import { encodeSqlEqualityValue, parseSqlTimestampText } from "./sql-semantics.js";
24
- import { exactNumericAsNumber, externalSqlDomainValue, externalSqlTextValue, isDateDomainValue, isExactNumeric, isSqlDomainValue, normalizeSqlDomainValue, protectedSqlTextValue } from "./sql-domains.js";
24
+ import { encodeSqlEqualityValue, readUntypedText } from "./sql-semantics.js";
25
+ import { exactNumericAsNumber, exactNumericValue, externalSqlDomainValue, externalSqlTextValue, isDateDomainValue, isExactNumeric, isSqlDomainValue, normalizeSqlDomainValue, protectedSqlTextValue } from "./sql-domains.js";
25
26
  import { toCatalog } from "./catalog.js";
26
27
  import { applyColumnSteps, assertColumnDroppable, compileGeneratedColumnExpression, declaredForeignKeys, isDestructiveStep, planMigration } from "./schema.js";
27
28
  import { columnarTableFromRows, createColumnarTable, vectorValue } from "./vector.js";
@@ -77,6 +78,10 @@ const AUTO_COLLECT_RETRY_MIN_MS = 1e3;
77
78
  const AUTO_COLLECT_RETRY_MAX_MS = 6e4;
78
79
  const AUTO_COMPACT_STEP_BLOCKS = 4;
79
80
  const AUTO_COMPACT_MAX_LEVEL_ZERO_SEGMENTS = 256;
81
+ const AUTO_COMPACT_BACKPRESSURE_LEVEL_ZERO_SEGMENTS = 2 * AUTO_COMPACT_MAX_LEVEL_ZERO_SEGMENTS;
82
+ const AUTO_COMPACT_RETRY_MIN_MS = 250;
83
+ const AUTO_COMPACT_RETRY_MAX_MS = 6e4;
84
+ const MAX_COMPACTION_PUBLICATION_CONFLICTS = 64;
80
85
  const ZONE_DESCRIPTION_CACHE_BYTES = 160;
81
86
  const STAGED_OVERLAY_ORDER_BASE = 2 ** 52;
82
87
  class StaleTriggerDerivationsError extends Error {
@@ -202,6 +207,7 @@ function projectReturningItems(table, alias, items, rows, facts) {
202
207
  having: [],
203
208
  orderBy: []
204
209
  }, facts.domains, facts.types);
210
+ annotatePlanIntegerDivision(plan, (name) => facts.integers.get(name));
205
211
  const visible = visibleTableColumns(table);
206
212
  const columns = new Map(visible.map((column) => [
207
213
  column.name,
@@ -338,6 +344,18 @@ function resolveMutationStatementDatetimes(statement, now) {
338
344
  }
339
345
  };
340
346
  }
347
+ const sources = (from) => from === void 0 ? {} : {
348
+ from: {
349
+ ...from,
350
+ predicates: predicates(from.predicates),
351
+ joins: from.joins.map((join) => ({
352
+ ...join,
353
+ left: expression(join.left),
354
+ right: expression(join.right),
355
+ ...join.on === void 0 ? {} : { on: expression(join.on) }
356
+ }))
357
+ }
358
+ };
341
359
  if (statement.kind === "update") {
342
360
  return {
343
361
  ...statement,
@@ -345,11 +363,16 @@ function resolveMutationStatementDatetimes(statement, now) {
345
363
  ...assignment,
346
364
  expression: expression(assignment.expression)
347
365
  })),
348
- predicates: predicates(statement.predicates)
366
+ predicates: predicates(statement.predicates),
367
+ ...sources(statement.from)
349
368
  };
350
369
  }
351
370
  if (statement.kind === "delete") {
352
- return { ...statement, predicates: predicates(statement.predicates) };
371
+ return {
372
+ ...statement,
373
+ predicates: predicates(statement.predicates),
374
+ ...sources(statement.from)
375
+ };
353
376
  }
354
377
  if (statement.kind === "merge") {
355
378
  return {
@@ -580,6 +603,9 @@ class MinnowDatabase {
580
603
  #commitsSinceCompactionCheck = /* @__PURE__ */ new Map();
581
604
  #compactionSteps = /* @__PURE__ */ new Map();
582
605
  #writeChain = Promise.resolve();
606
+ #pendingCompactionPublications = /* @__PURE__ */ new Set();
607
+ #commitSignal;
608
+ #pendingEventLoopTurn;
583
609
  #pendingWriteReservations = 0;
584
610
  #activeReadReservations = 0;
585
611
  #autoCollectionTask;
@@ -695,6 +721,10 @@ class MinnowDatabase {
695
721
  this.#gzipVerdicts.clear();
696
722
  this.#autoCompactionsRequested.clear();
697
723
  this.#idleCompactionTableIds.clear();
724
+ for (const backoff of this.#autoCompactionBackoff.values()) {
725
+ if (backoff.retryTimer !== void 0)
726
+ clearTimeout(backoff.retryTimer);
727
+ }
698
728
  this.#autoCompactionBackoff.clear();
699
729
  this.#autoCompactionHints.clear();
700
730
  this.#commitsSinceCompactionCheck.clear();
@@ -980,6 +1010,7 @@ class MinnowDatabase {
980
1010
  }
981
1011
  async #runWrite(run) {
982
1012
  const restarting = async () => {
1013
+ await this.#publishPendingCompactions();
983
1014
  await this.#assistAutomaticCollection();
984
1015
  for (let attempt = 0; ; attempt += 1) {
985
1016
  try {
@@ -1012,6 +1043,57 @@ class MinnowDatabase {
1012
1043
  this.#writeChain = Promise.resolve();
1013
1044
  }
1014
1045
  }
1046
+ async #withCompactionPublicationSlot(attempt) {
1047
+ return new Promise((resolve, reject) => {
1048
+ let settled;
1049
+ const claim = () => {
1050
+ settled ??= (async () => {
1051
+ try {
1052
+ resolve(await attempt());
1053
+ } catch (error) {
1054
+ reject(error instanceof Error ? error : new Error(String(error)));
1055
+ } finally {
1056
+ this.#pendingCompactionPublications.delete(claim);
1057
+ }
1058
+ })();
1059
+ return settled;
1060
+ };
1061
+ this.#pendingCompactionPublications.add(claim);
1062
+ void this.#eventLoopTurn().then(claim);
1063
+ });
1064
+ }
1065
+ async #publishPendingCompactions() {
1066
+ while (this.#pendingCompactionPublications.size > 0) {
1067
+ const [claim] = this.#pendingCompactionPublications;
1068
+ if (claim === void 0)
1069
+ return;
1070
+ await claim();
1071
+ }
1072
+ }
1073
+ #eventLoopTurn() {
1074
+ const pending = this.#pendingEventLoopTurn;
1075
+ if (pending !== void 0)
1076
+ return pending;
1077
+ const turn = yieldToEventLoop().then(() => {
1078
+ if (this.#pendingEventLoopTurn === turn)
1079
+ this.#pendingEventLoopTurn = void 0;
1080
+ });
1081
+ this.#pendingEventLoopTurn = turn;
1082
+ return turn;
1083
+ }
1084
+ #nextLocalCommit() {
1085
+ if (this.#commitSignal === void 0) {
1086
+ let resolve = () => void 0;
1087
+ const promise = new Promise((settle) => {
1088
+ resolve = settle;
1089
+ });
1090
+ this.#commitSignal = { promise, resolve };
1091
+ }
1092
+ return this.#commitSignal.promise;
1093
+ }
1094
+ async #yieldMaintenance() {
1095
+ await Promise.race([this.#eventLoopTurn(), this.#nextLocalCommit()]);
1096
+ }
1015
1097
  async #withWriteReservation(run) {
1016
1098
  if (this.#pendingWriteReservations >= MAX_DATABASE_PENDING_WRITES) {
1017
1099
  throw new RangeError(`A database cannot queue more than ${String(MAX_DATABASE_PENDING_WRITES)} writes; await a write for backpressure`);
@@ -1531,9 +1613,7 @@ class MinnowDatabase {
1531
1613
  const { result, batch, generated, autoIncrement } = completed;
1532
1614
  collectAutoIncrementGenerated(batch, generated, autoIncrement);
1533
1615
  if (result.acceptedRowIndexes.length !== result.requestedRowCount) {
1534
- for (const [name, values] of generated) {
1535
- generated.set(name, result.acceptedRowIndexes.map((index) => values[index] ?? null));
1536
- }
1616
+ remapGeneratedColumns(generated, result.acceptedRowIndexes);
1537
1617
  }
1538
1618
  return {
1539
1619
  tableName: result.tableName,
@@ -2011,30 +2091,21 @@ class MinnowDatabase {
2011
2091
  const normalizedConflictWhere = kind === "upsert" && conflictWhere !== void 0 ? normalizeUpsertConflictWhere(table, conflictWhere) : void 0;
2012
2092
  const upsertKeyColumn = kind === "upsert" ? getUniqueKeyColumn(table) : void 0;
2013
2093
  if (upsertKeyColumn !== void 0) {
2014
- upsertFirings = await this.#upsertTriggerFirings(table, upsertKeyColumn, input, rowCount, (sql, params) => this.#sessionQuery(transaction, sql, { params }), normalizedConflictWhere, normalizedConflictWhere !== void 0);
2094
+ upsertFirings = await this.#upsertTriggerFirings(table, upsertKeyColumn, input, rowCount, (sql, params) => this.#sessionQuery(transaction, sql, { params }), normalizedConflictWhere);
2015
2095
  }
2016
2096
  if (normalizedConflictWhere !== void 0) {
2017
2097
  if (upsertFirings === void 0) {
2018
2098
  throw new Error("Upsert conflict classification is missing");
2019
2099
  }
2020
- const classifiedFirings = upsertFirings;
2021
- acceptedRowIndexes = [...classifiedFirings.inserts, ...classifiedFirings.updates].sort((left, right) => left - right);
2022
- skippedRowCount = classifiedFirings.skipped.length;
2023
- input = selectBatchRows(input, acceptedRowIndexes);
2024
- rowCount = acceptedRowIndexes.length;
2100
+ counts = { inserted: upsertFirings.inserts.length, updated: upsertFirings.updates.length };
2101
+ const filtered = applyUpsertConflictFilter(input, rowCount, upsertFirings);
2102
+ acceptedRowIndexes = filtered.acceptedRowIndexes;
2103
+ skippedRowCount = filtered.skippedRowCount;
2104
+ input = filtered.batch;
2105
+ rowCount = filtered.rowCount;
2106
+ upsertFirings = filtered.firings;
2025
2107
  logicalBytes = estimateBatchBytes(input);
2026
2108
  resolvedKeys = batchKeys(table, input);
2027
- counts = {
2028
- inserted: classifiedFirings.inserts.length,
2029
- updated: classifiedFirings.updates.length
2030
- };
2031
- const acceptedPosition = new Map(acceptedRowIndexes.map((original, position) => [original, position]));
2032
- upsertFirings = {
2033
- inserts: classifiedFirings.inserts.map((index) => acceptedPosition.get(index) ?? -1),
2034
- updates: classifiedFirings.updates.map((index) => acceptedPosition.get(index) ?? -1),
2035
- skipped: [],
2036
- oldImages: acceptedRowIndexes.map((index) => classifiedFirings.oldImages[index])
2037
- };
2038
2109
  if (rowCount === 0) {
2039
2110
  await transaction.abort();
2040
2111
  const version = transaction.snapshotVersion;
@@ -2492,7 +2563,7 @@ class MinnowDatabase {
2492
2563
  catalogEpoch: after.catalogEpoch
2493
2564
  };
2494
2565
  }
2495
- await yieldToEventLoop();
2566
+ await this.#yieldMaintenance();
2496
2567
  }
2497
2568
  }
2498
2569
  async #acquireSharedLease(version) {
@@ -2667,7 +2738,15 @@ class MinnowDatabase {
2667
2738
  }
2668
2739
  memberSchema.forEach((column, index) => {
2669
2740
  const expected = schema2?.[index];
2670
- if (expected !== void 0 && (column.type !== expected.type || JSON.stringify(column.sqlDomain ?? null) !== JSON.stringify(expected.sqlDomain ?? null))) {
2741
+ if (expected === void 0)
2742
+ return;
2743
+ if (column.type === expected.type && column.sqlDomain?.kind === "numeric" && expected.sqlDomain?.kind === "numeric") {
2744
+ if (JSON.stringify(column.sqlDomain) !== JSON.stringify(expected.sqlDomain)) {
2745
+ schema2 = schema2?.map((entry, position) => position === index ? { ...entry, sqlDomain: { kind: "numeric" } } : entry);
2746
+ }
2747
+ return;
2748
+ }
2749
+ if (column.type !== expected.type || JSON.stringify(column.sqlDomain ?? null) !== JSON.stringify(expected.sqlDomain ?? null)) {
2671
2750
  throw new TypeError(`UNION member column types must match: ${expected.name}`);
2672
2751
  }
2673
2752
  });
@@ -2782,7 +2861,13 @@ class MinnowDatabase {
2782
2861
  if (template !== null) {
2783
2862
  const shape = resolvePointReadShape(template, options.params ?? []);
2784
2863
  if (shape !== void 0) {
2785
- const point = await this.#pointReadResult(shape, options);
2864
+ let point;
2865
+ try {
2866
+ point = await this.#pointReadResult(shape, options);
2867
+ } catch (error) {
2868
+ if (!(error instanceof UnknownTableError))
2869
+ throw error;
2870
+ }
2786
2871
  throwIfAborted(options.signal);
2787
2872
  if (point !== void 0)
2788
2873
  return externalizeQueryResult(point);
@@ -2905,9 +2990,11 @@ class MinnowDatabase {
2905
2990
  async #applyCatalogRewrites(plan, probe) {
2906
2991
  const aliased = planHasSourceColumnAliases(plan);
2907
2992
  const natural = planHasNaturalJoins(plan);
2908
- const { views, domains, types, columns: catalogColumns } = await this.#catalogFacts(probe);
2993
+ const { views, domains, types, integers, keys, columns: catalogColumns } = await this.#catalogFacts(probe);
2909
2994
  let rewritten = plan.usesSequenceCalls === true ? await this.#resolveSequenceCalls(plan) : plan;
2910
- if (views.size > 0 && planReadsViews(plan, (name) => views.has(name))) {
2995
+ rewritten = foldIdentifierCase(rewritten, catalogColumns);
2996
+ rewritten = expandRowReferences(rewritten, (name) => catalogColumns.get(name));
2997
+ if (views.size > 0 && planReadsViews(rewritten, (name) => views.has(name))) {
2911
2998
  const bodies = /* @__PURE__ */ new Map();
2912
2999
  rewritten = expandViewSources(rewritten, (name) => {
2913
3000
  const sql = views.get(name);
@@ -2924,6 +3011,13 @@ class MinnowDatabase {
2924
3011
  if (planReadsTable(rewritten, (name) => domains.has(name) || types.has(name))) {
2925
3012
  rewritten = normalizePlanDomainLiterals(rewritten, domains, types);
2926
3013
  }
3014
+ if (integers.size > 0 && planMayHaveIntegerDivision(rewritten)) {
3015
+ rewritten = clonePlanTree(rewritten);
3016
+ annotatePlanIntegerDivision(rewritten, (name) => integers.get(name));
3017
+ }
3018
+ if (keys.size > 0) {
3019
+ rewritten = extendGroupByWithKeyDependents(rewritten, (name) => keys.get(name), (name) => catalogColumns.get(name));
3020
+ }
2927
3021
  const columnsOf = (name) => {
2928
3022
  const columns = catalogColumns.get(name);
2929
3023
  if (columns === void 0)
@@ -3034,6 +3128,14 @@ class MinnowDatabase {
3034
3128
  item.expression = await rewrite(item.expression);
3035
3129
  return resolved;
3036
3130
  }
3131
+ async #foldTableName(name) {
3132
+ const { columns } = await this.#catalogFacts();
3133
+ if (columns.has(name))
3134
+ return name;
3135
+ const lowered = name.toLowerCase();
3136
+ const matches = [...columns.keys()].filter((candidate) => candidate.toLowerCase() === lowered);
3137
+ return matches.length === 1 ? matches[0] ?? name : name;
3138
+ }
3037
3139
  async #catalogFacts(probe) {
3038
3140
  probe ??= await this.store.getCatalogProbe();
3039
3141
  const epoch = probe.catalogEpoch;
@@ -3045,10 +3147,23 @@ class MinnowDatabase {
3045
3147
  const domains = /* @__PURE__ */ new Map();
3046
3148
  const columns = /* @__PURE__ */ new Map();
3047
3149
  const types = /* @__PURE__ */ new Map();
3150
+ const integers = /* @__PURE__ */ new Map();
3151
+ const keys = /* @__PURE__ */ new Map();
3048
3152
  for (const table of await this.store.listTables()) {
3049
3153
  const visible = table.columns.filter(({ hidden }) => hidden !== true);
3154
+ const keyColumn = getUniqueKeyColumn(table);
3155
+ const keyNames = keyColumn !== void 0 && !keyColumn.hidden ? [keyColumn.name] : (table.primaryKeyColumnIds ?? []).flatMap((columnId) => {
3156
+ const column = table.columns.find((candidate) => candidate.id === columnId);
3157
+ return column === void 0 ? [] : [column.name];
3158
+ });
3159
+ if (keyNames.length > 0)
3160
+ keys.set(table.name, keyNames);
3050
3161
  columns.set(table.name, visible.map(({ name }) => name));
3051
3162
  types.set(table.name, new Map(visible.map((column) => [column.name, column.type])));
3163
+ const integerColumns = visible.filter((column) => column.integer === true);
3164
+ if (integerColumns.length > 0) {
3165
+ integers.set(table.name, new Set(integerColumns.map(({ name }) => name)));
3166
+ }
3052
3167
  const tableDomains = new Map(table.columns.flatMap((column) => column.sqlDomain === void 0 ? [] : [[column.name, column.sqlDomain]]));
3053
3168
  if (tableDomains.size > 0)
3054
3169
  domains.set(table.name, tableDomains);
@@ -3062,7 +3177,7 @@ class MinnowDatabase {
3062
3177
  existing.push({ table, key });
3063
3178
  }
3064
3179
  }
3065
- const facts = { views, childKeys, domains, columns, types };
3180
+ const facts = { views, childKeys, domains, columns, types, integers, keys };
3066
3181
  this.#catalogCache = { epoch, facts };
3067
3182
  return facts;
3068
3183
  }
@@ -3558,7 +3673,7 @@ class MinnowDatabase {
3558
3673
  if (page.nextCursor === null)
3559
3674
  break;
3560
3675
  cursor = page.nextCursor;
3561
- await yieldToEventLoop();
3676
+ await this.#yieldMaintenance();
3562
3677
  }
3563
3678
  return result;
3564
3679
  }
@@ -3583,7 +3698,7 @@ class MinnowDatabase {
3583
3698
  if (page.nextCursor === null)
3584
3699
  break;
3585
3700
  cursor = page.nextCursor;
3586
- await yieldToEventLoop();
3701
+ await this.#yieldMaintenance();
3587
3702
  }
3588
3703
  return result;
3589
3704
  }
@@ -3793,8 +3908,11 @@ class MinnowDatabase {
3793
3908
  return;
3794
3909
  if (!autoCompactionDueHint(hint))
3795
3910
  return;
3796
- if (hint.visible < (this.#autoCompactionBackoff.get(table.id) ?? 0))
3797
- return;
3911
+ const backoff = this.#autoCompactionBackoff.get(table.id);
3912
+ if (backoff !== void 0) {
3913
+ if (backoff.retryTimer !== void 0 || hint.visible < backoff.minimumSegments)
3914
+ return;
3915
+ }
3798
3916
  if (this.#autoCompactionsInFlight.has(table.id)) {
3799
3917
  this.#autoCompactionsRequested.add(table.id);
3800
3918
  return;
@@ -3802,44 +3920,81 @@ class MinnowDatabase {
3802
3920
  const run = this.#runAutoCompaction(table).then((folded) => {
3803
3921
  if (folded)
3804
3922
  this.#autoCompactionBackoff.delete(table.id);
3805
- else {
3806
- this.#autoCompactionBackoff.set(table.id, Math.min(AUTO_COMPACT_MAX_LEVEL_ZERO_SEGMENTS, Math.max(2, hint.visible * 2)));
3807
- }
3923
+ else
3924
+ this.#backOffAutoCompaction(table.id, hint.visible);
3808
3925
  }).catch(() => {
3809
- this.#autoCompactionBackoff.set(table.id, Math.min(AUTO_COMPACT_MAX_LEVEL_ZERO_SEGMENTS, Math.max(2, hint.visible * 2)));
3926
+ this.#backOffAutoCompaction(table.id, hint.visible);
3810
3927
  }).finally(() => {
3811
3928
  if (this.#autoCompactionsInFlight.get(table.id) === run) {
3812
3929
  this.#autoCompactionsInFlight.delete(table.id);
3813
3930
  }
3814
3931
  if (this.#autoCompactionsRequested.delete(table.id)) {
3815
- void yieldToEventLoop().then(() => this.#checkAutoCompaction(table.id));
3932
+ void this.#yieldMaintenance().then(() => this.#checkAutoCompaction(table.id));
3816
3933
  }
3817
3934
  });
3818
3935
  this.#autoCompactionsInFlight.set(table.id, run);
3819
3936
  void run;
3820
3937
  }
3938
+ #backOffAutoCompaction(tableId, visible) {
3939
+ if (this.#closed)
3940
+ return;
3941
+ const previous = this.#autoCompactionBackoff.get(tableId);
3942
+ if (previous?.retryTimer !== void 0)
3943
+ clearTimeout(previous.retryTimer);
3944
+ const failures = (previous?.failures ?? 0) + 1;
3945
+ const delay = Math.min(AUTO_COMPACT_RETRY_MAX_MS, AUTO_COMPACT_RETRY_MIN_MS * 2 ** Math.min(16, failures - 1));
3946
+ const timer = setTimeout(() => {
3947
+ const current = this.#autoCompactionBackoff.get(tableId);
3948
+ if (current?.retryTimer === timer) {
3949
+ this.#autoCompactionBackoff.set(tableId, { ...current, retryTimer: void 0 });
3950
+ }
3951
+ void this.#checkAutoCompaction(tableId);
3952
+ }, delay);
3953
+ timer.unref?.();
3954
+ this.#autoCompactionBackoff.set(tableId, {
3955
+ minimumSegments: Math.min(AUTO_COMPACT_MAX_LEVEL_ZERO_SEGMENTS, Math.max(2, visible * 2)),
3956
+ failures,
3957
+ retryTimer: timer
3958
+ });
3959
+ }
3821
3960
  async #assertCompactionCapacity(table, transaction) {
3822
3961
  transaction?.limitLevelZeroSegments(table.id, MAX_LEVEL_ZERO_SEGMENTS);
3823
3962
  if (!this.#autoCompact)
3824
3963
  return;
3825
3964
  const hintVersion = transaction?.snapshotVersion ?? await this.store.getCurrentManifestVersion();
3826
3965
  const hint = this.#autoCompactionHints.get(table.id);
3827
- if (hint?.version === hintVersion && hint.levelZero < MAX_LEVEL_ZERO_SEGMENTS)
3966
+ let visible;
3967
+ let levelZero;
3968
+ if (hint?.version === hintVersion) {
3969
+ ({ visible, levelZero } = hint);
3970
+ } else {
3971
+ const segments = await this.#currentVisibleSegments(table);
3972
+ visible = segments.length;
3973
+ levelZero = countLevelZeroSegments(segments);
3974
+ }
3975
+ if (levelZero < AUTO_COMPACT_BACKPRESSURE_LEVEL_ZERO_SEGMENTS)
3976
+ return;
3977
+ const atCeiling = levelZero >= MAX_LEVEL_ZERO_SEGMENTS;
3978
+ if (!atCeiling && this.#autoCompactionBackoff.get(table.id)?.retryTimer !== void 0)
3979
+ return;
3980
+ await this.#publishPendingCompactions();
3981
+ try {
3982
+ const progress = await this.compactTableStep(table.name, {
3983
+ maxBlocks: AUTO_COMPACT_STEP_BLOCKS,
3984
+ maxLevel0Segments: AUTO_COMPACT_MAX_LEVEL_ZERO_SEGMENTS
3985
+ });
3986
+ if (!atCeiling && progress.result !== null && !progress.result.compacted) {
3987
+ this.#backOffAutoCompaction(table.id, visible);
3988
+ }
3989
+ } catch (error) {
3990
+ if (atCeiling)
3991
+ throw error;
3992
+ this.#backOffAutoCompaction(table.id, visible);
3828
3993
  return;
3829
- let segments = await this.#currentVisibleSegments(table);
3830
- let levelZero = 0;
3831
- for (const segment of segments) {
3832
- if (segment.level === 0)
3833
- levelZero += 1;
3834
3994
  }
3835
- if (levelZero < MAX_LEVEL_ZERO_SEGMENTS)
3995
+ if (!atCeiling)
3836
3996
  return;
3837
- await this.compactTableStep(table.name, {
3838
- maxBlocks: AUTO_COMPACT_STEP_BLOCKS,
3839
- maxLevel0Segments: AUTO_COMPACT_MAX_LEVEL_ZERO_SEGMENTS
3840
- });
3841
- segments = await this.#currentVisibleSegments(table);
3842
- levelZero = segments.reduce((count, segment) => count + (segment.level === 0 ? 1 : 0), 0);
3997
+ levelZero = countLevelZeroSegments(await this.#currentVisibleSegments(table));
3843
3998
  if (levelZero >= MAX_LEVEL_ZERO_SEGMENTS) {
3844
3999
  throw new CompactionBacklogError(table.name, levelZero, MAX_LEVEL_ZERO_SEGMENTS);
3845
4000
  }
@@ -3859,14 +4014,14 @@ class MinnowDatabase {
3859
4014
  while (progress.result === null) {
3860
4015
  if (progress.jobId === null)
3861
4016
  throw new Error("Compaction progress lost its job ID");
3862
- await yieldToEventLoop();
4017
+ await this.#yieldMaintenance();
3863
4018
  progress = await this.resumeCompactionJob(progress.jobId, options);
3864
4019
  }
3865
4020
  if (!progress.result.compacted)
3866
4021
  return folded;
3867
4022
  folded = true;
3868
4023
  this.#maybeScheduleAutoCollection();
3869
- await yieldToEventLoop();
4024
+ await this.#yieldMaintenance();
3870
4025
  const current = await this.store.getTable(table.id);
3871
4026
  if (current === void 0 || !autoCompactionDue(await this.#currentVisibleSegments(current))) {
3872
4027
  return folded;
@@ -3887,6 +4042,9 @@ class MinnowDatabase {
3887
4042
  return segments;
3888
4043
  }
3889
4044
  #afterCommit(manifest, transaction) {
4045
+ const signal = this.#commitSignal;
4046
+ this.#commitSignal = void 0;
4047
+ signal?.resolve();
3890
4048
  if (this.#closed)
3891
4049
  return;
3892
4050
  const contribution = transaction?.committedCatalogContribution;
@@ -4146,9 +4304,9 @@ class MinnowDatabase {
4146
4304
  }
4147
4305
  }
4148
4306
  }
4149
- async #upsertTriggerFirings(table, keyColumn, batch, rowCount, readRows, conflictWhere, force = false) {
4307
+ async #upsertTriggerFirings(table, keyColumn, batch, rowCount, readRows, conflictWhere) {
4150
4308
  const fires = (table.triggers ?? []).some((trigger) => trigger.event === "insert" || trigger.event === "update");
4151
- if (!fires && !force || rowCount === 0)
4309
+ if (!fires && conflictWhere === void 0 || rowCount === 0)
4152
4310
  return void 0;
4153
4311
  const quote = (name) => `"${name.replaceAll('"', '""')}"`;
4154
4312
  const keyValues = batch.columns[keyColumn.name] ?? [];
@@ -4262,9 +4420,17 @@ class MinnowDatabase {
4262
4420
  if (target.uniqueKeyColumnId !== void 0) {
4263
4421
  throw new TypeError(`Trigger bodies insert into keyless tables only: ${target.name}`);
4264
4422
  }
4423
+ let columns = compiled.columns;
4424
+ if (columns.length === 0 && compiled.defaultValues !== true) {
4425
+ columns = visibleTableColumns(target).map((column) => column.name);
4426
+ if (derivedRows.some((row) => row.length !== columns.length)) {
4427
+ throw new TypeError(`Trigger body INSERT must match the ${String(columns.length)} visible columns of ${target.name}`);
4428
+ }
4429
+ }
4265
4430
  const statementNow = this.#now();
4266
- const materialized = await this.#materializeInsertExpressions({ ...compiled, rows: derivedRows }, statementNow);
4431
+ const materialized = await this.#materializeInsertExpressions({ ...compiled, columns, rows: derivedRows }, statementNow);
4267
4432
  const input = insertStatementBatch(materialized);
4433
+ coerceSqlWriteBatch(target, input);
4268
4434
  const filled = await fillColumnDefaults(target, input, (sql) => this.#evaluateDefaultExpression(sql, statementNow), derivedRows.length);
4269
4435
  normalizeDomainBatch(target, filled.batch);
4270
4436
  fillStoredGeneratedColumns(target, filled.batch, derivedRows.length, filled.generated);
@@ -4293,6 +4459,9 @@ class MinnowDatabase {
4293
4459
  const bound = bindStatementParameters(compiled, params);
4294
4460
  if (bound.kind !== "update" && bound.kind !== "delete")
4295
4461
  continue;
4462
+ if (bound.from !== void 0) {
4463
+ throw new TypeError(`Trigger body ${bound.kind.toUpperCase()}s cannot join other sources`);
4464
+ }
4296
4465
  const referenced = /* @__PURE__ */ new Set([keyColumn.name]);
4297
4466
  if (bound.kind === "update") {
4298
4467
  for (const assignment of bound.assignments) {
@@ -4417,6 +4586,7 @@ class MinnowDatabase {
4417
4586
  return this.#openWriteScope((session) => action(session));
4418
4587
  }
4419
4588
  async #openWriteScope(action, options = {}) {
4589
+ await this.#publishPendingCompactions();
4420
4590
  await this.#assistAutomaticCollection();
4421
4591
  const transaction = await this.#transactions.beginDeferred({
4422
4592
  durableSnapshot: options.durableSnapshot ?? true
@@ -4458,6 +4628,9 @@ class MinnowDatabase {
4458
4628
  }));
4459
4629
  }
4460
4630
  const statement = bindStatementParameters(compiled, params);
4631
+ if (statement.kind === "set" || statement.kind === "show") {
4632
+ return externalizeExecuteResult(await this.runStatement(statement));
4633
+ }
4461
4634
  if (!isTransactionalStatement(statement)) {
4462
4635
  throw new TypeError(`${statement.kind.toUpperCase().replace("-", " ")} is not allowed inside a write scope`);
4463
4636
  }
@@ -4468,10 +4641,10 @@ class MinnowDatabase {
4468
4641
  staged += 1;
4469
4642
  return guarded(() => this.#sessionInsert(transaction, tableName, input, "insert"));
4470
4643
  },
4471
- upsertBatch: async (tableName, input) => {
4644
+ upsertBatch: async (tableName, input, options2) => {
4472
4645
  open();
4473
4646
  staged += 1;
4474
- return guarded(() => this.#sessionInsert(transaction, tableName, input, "upsert"));
4647
+ return guarded(() => this.#sessionInsert(transaction, tableName, input, "upsert", options2));
4475
4648
  },
4476
4649
  updateBatch: async (tableName, input) => {
4477
4650
  open();
@@ -4705,10 +4878,12 @@ class MinnowDatabase {
4705
4878
  }
4706
4879
  return { added, removed };
4707
4880
  }
4708
- async #sessionInsert(transaction, tableName, input, kind, cascadeBudget = 1) {
4881
+ async #sessionInsert(transaction, tableName, input, kind, options, cascadeBudget = 1) {
4709
4882
  const table = await this.#findTable(tableName);
4710
- await this.#assertCompactionCapacity(table, transaction);
4711
- const { batch, generated, autoIncrement, rowCount } = await this.#fillDefaults(table, input);
4883
+ const filled = await this.#fillDefaults(table, input);
4884
+ const { generated, autoIncrement } = filled;
4885
+ let batch = filled.batch;
4886
+ let rowCount = filled.rowCount;
4712
4887
  if (autoIncrement !== void 0 && autoIncrement.missingIndexes.length > 0) {
4713
4888
  const values = await this.store.reserveAutoIncrement(table.id, autoIncrement.column.id, autoIncrement.missingIndexes.length, autoIncrement.atLeast);
4714
4889
  patchAutoIncrementValues(batch, autoIncrement, values);
@@ -4717,6 +4892,36 @@ class MinnowDatabase {
4717
4892
  validateValue(autoIncrement.column, patched[rowIndex] ?? null, rowIndex);
4718
4893
  }
4719
4894
  }
4895
+ const sessionUpsertKeyColumn = kind === "upsert" ? getUniqueKeyColumn(table) : void 0;
4896
+ const normalizedConflictWhere = kind === "upsert" && options?.conflictWhere !== void 0 ? normalizeUpsertConflictWhere(table, options.conflictWhere) : void 0;
4897
+ if (normalizedConflictWhere !== void 0 && sessionUpsertKeyColumn === void 0) {
4898
+ throw new TypeError(`Table needs a unique key before it can be upserted: ${table.name}`);
4899
+ }
4900
+ let sessionUpsertFirings = sessionUpsertKeyColumn === void 0 ? void 0 : await this.#upsertTriggerFirings(table, sessionUpsertKeyColumn, batch, rowCount, (sql, params) => this.#sessionQuery(transaction, sql, { params }), normalizedConflictWhere);
4901
+ let skippedRowCount = 0;
4902
+ if (normalizedConflictWhere !== void 0) {
4903
+ if (sessionUpsertFirings === void 0) {
4904
+ throw new Error("Upsert conflict classification is missing");
4905
+ }
4906
+ const filtered = applyUpsertConflictFilter(batch, rowCount, sessionUpsertFirings);
4907
+ skippedRowCount = filtered.skippedRowCount;
4908
+ if (filtered.rowCount !== rowCount) {
4909
+ remapGeneratedColumns(generated, filtered.acceptedRowIndexes);
4910
+ }
4911
+ batch = filtered.batch;
4912
+ rowCount = filtered.rowCount;
4913
+ sessionUpsertFirings = filtered.firings;
4914
+ if (rowCount === 0) {
4915
+ return {
4916
+ tableName: table.name,
4917
+ segmentId: null,
4918
+ rowCount: 0,
4919
+ skippedRowCount,
4920
+ ...generated.size === 0 ? {} : { generatedColumns: Object.fromEntries(generated) }
4921
+ };
4922
+ }
4923
+ }
4924
+ await this.#assertCompactionCapacity(table, transaction);
4720
4925
  const keys = batchKeys(table, batch);
4721
4926
  if (keys !== void 0) {
4722
4927
  transaction.setUniqueKeyChanges({
@@ -4728,8 +4933,6 @@ class MinnowDatabase {
4728
4933
  await this.#assertForeignKeysPresent(table, (column) => batch.columns[column] ?? [], (sql, params) => this.#sessionQuery(transaction, sql, { params }), transaction);
4729
4934
  const rowIds = await this.store.reserveRowIds(table.id, rowCount);
4730
4935
  const insertValueAt = (source, column, rowIndex) => source === "new" ? batch.columns[column]?.[rowIndex] ?? null : null;
4731
- const sessionUpsertKeyColumn = kind === "upsert" ? getUniqueKeyColumn(table) : void 0;
4732
- const sessionUpsertFirings = sessionUpsertKeyColumn === void 0 ? void 0 : await this.#upsertTriggerFirings(table, sessionUpsertKeyColumn, batch, rowCount, (sql, params) => this.#sessionQuery(transaction, sql, { params }));
4733
4936
  stageSecondaryUniqueInsertChanges(transaction, table, batch, kind === "upsert" ? sessionUpsertFirings?.oldImages : void 0);
4734
4937
  if (kind === "insert") {
4735
4938
  await this.#stageTriggerDerivedInserts(transaction, table, "insert", rowCount, insertValueAt, "before", cascadeBudget);
@@ -4747,6 +4950,7 @@ class MinnowDatabase {
4747
4950
  tableName: table.name,
4748
4951
  segmentId,
4749
4952
  rowCount,
4953
+ skippedRowCount,
4750
4954
  ...generated.size === 0 ? {} : { generatedColumns: Object.fromEntries(generated) }
4751
4955
  };
4752
4956
  }
@@ -5371,6 +5575,9 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
5371
5575
  })
5372
5576
  };
5373
5577
  }
5578
+ if (statement.kind === "set" || statement.kind === "show") {
5579
+ return externalizeExecuteResult(await this.runStatement(statement));
5580
+ }
5374
5581
  const open = this.#openTransaction;
5375
5582
  if (open !== void 0) {
5376
5583
  const boundStatement2 = bindStatementParameters(statement, params);
@@ -5692,12 +5899,14 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
5692
5899
  if (keyColumn === void 0 || conflictColumns.length !== addressColumns.length || conflictColumns.some((name, index) => name !== addressColumns[index])) {
5693
5900
  throw new TypeError(`ON CONFLICT targets the table's primary or unique key columns: ${addressColumns.join(", ") || "(none)"}`);
5694
5901
  }
5902
+ statement = foldInsertColumns(table, statement);
5695
5903
  for (const name of statement.columns) {
5696
5904
  if (!table.columns.some((column) => column.name === name)) {
5697
5905
  throw new TypeError(`INSERT column does not exist: ${name}`);
5698
5906
  }
5699
5907
  }
5700
5908
  const proposedBatch = insertStatementBatch(statement);
5909
+ coerceSqlWriteBatch(table, proposedBatch);
5701
5910
  const statementNow = this.#now();
5702
5911
  const filledProposed = await fillColumnDefaults(table, proposedBatch, (sql) => this.#evaluateDefaultExpression(sql, statementNow), statement.rows.length);
5703
5912
  normalizeDomainBatch(table, filledProposed.batch);
@@ -5893,6 +6102,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
5893
6102
  throw new TypeError(`ON CONFLICT targets the table's primary or unique key columns: ${addressColumns.join(", ") || "(none)"}`);
5894
6103
  }
5895
6104
  const proposed = insertStatementBatch(statement);
6105
+ coerceSqlWriteBatch(table, proposed);
5896
6106
  const statementNow = this.#now();
5897
6107
  const filled = await fillColumnDefaults(table, proposed, (sql) => this.#evaluateDefaultExpression(sql, statementNow), statement.rows.length);
5898
6108
  normalizeDomainBatch(table, filled.batch);
@@ -6065,6 +6275,38 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
6065
6275
  if (statement.kind === "select") {
6066
6276
  return { kind: "rows", result: await this.query(statement.sql) };
6067
6277
  }
6278
+ if (statement.kind === "set") {
6279
+ return { kind: "set", action: statement.action, name: statement.name };
6280
+ }
6281
+ if (statement.kind === "show") {
6282
+ const settings = {
6283
+ search_path: "public",
6284
+ server_version: "16.0",
6285
+ server_version_num: "160000",
6286
+ timezone: "UTC",
6287
+ time_zone: "UTC",
6288
+ transaction_isolation: "repeatable read",
6289
+ default_transaction_isolation: "repeatable read",
6290
+ client_encoding: "UTF8",
6291
+ server_encoding: "UTF8",
6292
+ standard_conforming_strings: "on",
6293
+ integer_datetimes: "on",
6294
+ datestyle: "ISO, MDY",
6295
+ intervalstyle: "postgres",
6296
+ application_name: "",
6297
+ is_superuser: "on",
6298
+ max_identifier_length: "63"
6299
+ };
6300
+ const name = statement.name.toLowerCase();
6301
+ const value = settings[name];
6302
+ if (value === void 0) {
6303
+ throw new TypeError(`unrecognized configuration parameter "${statement.name}"`);
6304
+ }
6305
+ return {
6306
+ kind: "rows",
6307
+ result: await this.query(`SELECT $1 AS ${quoteIdentifier(name)}`, { params: [value] })
6308
+ };
6309
+ }
6068
6310
  if (statement.kind === "create-enum") {
6069
6311
  const values = validateEnumValues(statement.values, statement.name);
6070
6312
  const storageName = `${ENUM_TYPE_PREFIX}${statement.name}`;
@@ -6496,12 +6738,14 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
6496
6738
  throw new TypeError(`ON CONFLICT targets the table's primary or unique key columns: ${addressColumns.join(", ") || "(none)"}`);
6497
6739
  }
6498
6740
  }
6499
- for (const name of statement.columns) {
6741
+ const spelledColumns = foldInsertColumns(table2, statement).columns;
6742
+ for (const name of spelledColumns) {
6500
6743
  if (!table2.columns.some((column) => column.name === name)) {
6501
6744
  throw new TypeError(`INSERT column does not exist: ${name}`);
6502
6745
  }
6503
6746
  }
6504
- const input = insertStatementBatch(statement);
6747
+ const input = insertStatementBatch({ ...statement, columns: spelledColumns });
6748
+ coerceSqlWriteBatch(table2, input);
6505
6749
  normalizeDomainBatch(table2, input);
6506
6750
  let returningColumns2;
6507
6751
  if (options.returning !== void 0) {
@@ -6515,7 +6759,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
6515
6759
  const writer = options.writer;
6516
6760
  const stagedKeyColumn = writer === void 0 ? void 0 : getUniqueKeyColumn(table2);
6517
6761
  if (writer !== void 0 && !viaUpsert && stagedKeyColumn !== void 0) {
6518
- const assignedGenerated = table2.columns.find((column) => column.generatedValue !== void 0 && statement.columns.includes(column.name));
6762
+ const assignedGenerated = table2.columns.find((column) => column.generatedValue !== void 0 && spelledColumns.includes(column.name));
6519
6763
  if (assignedGenerated !== void 0) {
6520
6764
  throw new TypeError(`Generated column cannot be assigned: ${assignedGenerated.name}`);
6521
6765
  }
@@ -6531,7 +6775,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
6531
6775
  ...returningColumns2 === void 0 ? {} : {
6532
6776
  ...returningExecuteFields(table2, returningColumns2, statement.rows.map((row, rowIndex) => Object.fromEntries(returningColumns2.map((name) => {
6533
6777
  const column = table2.columns.find((candidate) => candidate.name === name);
6534
- const position = statement.columns.indexOf(name);
6778
+ const position = spelledColumns.indexOf(name);
6535
6779
  const inserted = position < 0 ? void 0 : row[position];
6536
6780
  const value = generated[name]?.[rowIndex] ?? input.columns[name]?.[rowIndex] ?? (inserted === void 0 || isDefaultInsertValue(inserted) ? null : boundInsertValue(inserted));
6537
6781
  return [name, executionSqlValueFromInput(column, value)];
@@ -6546,8 +6790,9 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
6546
6790
  }
6547
6791
  const returningColumns = options.returning === void 0 ? void 0 : options.returning === "*" ? visibleTableColumns(table).map(({ name }) => name) : [...options.returning];
6548
6792
  const updateAssignments = statement.kind !== "update" ? [] : await (async () => {
6793
+ const spelled = foldAssignmentColumns(table, statement.assignments);
6549
6794
  if (!table.columns.some((column) => column.sqlDomain !== void 0)) {
6550
- return statement.assignments;
6795
+ return spelled;
6551
6796
  }
6552
6797
  const { domains } = await this.#catalogFacts();
6553
6798
  if (!domains.has(table.name))
@@ -6556,7 +6801,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
6556
6801
  sql: `UPDATE ${table.name}`,
6557
6802
  base: { table: table.name, alias: table.name },
6558
6803
  joins: [],
6559
- select: statement.assignments.map((assignment) => ({
6804
+ select: spelled.map((assignment) => ({
6560
6805
  expression: assignment.expression,
6561
6806
  alias: assignment.column
6562
6807
  })),
@@ -6565,20 +6810,25 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
6565
6810
  having: [],
6566
6811
  orderBy: []
6567
6812
  }, domains);
6568
- return statement.assignments.map((assignment, index) => ({
6813
+ return spelled.map((assignment, index) => ({
6569
6814
  ...assignment,
6570
6815
  expression: normalized.select[index]?.expression ?? assignment.expression
6571
6816
  }));
6572
6817
  })();
6573
6818
  const referenced = /* @__PURE__ */ new Set([keyColumn.name, ...returningColumns ?? []]);
6574
6819
  const assignmentAlias = (column) => `\0set:${column}`;
6820
+ const targetAlias = statement.alias ?? table.name;
6821
+ const from = statement.from;
6575
6822
  const plan = optimizePlan({
6576
6823
  sql: `(${statement.kind})`,
6577
- base: { table: table.name, alias: statement.alias ?? table.name },
6578
- joins: [],
6824
+ base: { table: table.name, alias: targetAlias },
6825
+ joins: from === void 0 ? [] : [crossJoinPlan(from.base), ...from.joins],
6579
6826
  select: [
6580
6827
  ...[...referenced].map((name) => ({
6581
- expression: { kind: "column", reference: name },
6828
+ expression: {
6829
+ kind: "column",
6830
+ reference: from === void 0 ? name : `${targetAlias}.${name}`
6831
+ },
6582
6832
  alias: name
6583
6833
  })),
6584
6834
  ...statement.kind === "update" ? updateAssignments.map((assignment) => ({
@@ -6586,12 +6836,12 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
6586
6836
  alias: assignmentAlias(assignment.column)
6587
6837
  })) : []
6588
6838
  ],
6589
- predicates: statement.predicates,
6839
+ predicates: from === void 0 ? statement.predicates : [...statement.predicates, ...from.predicates],
6590
6840
  groupBy: [],
6591
6841
  having: [],
6592
6842
  orderBy: []
6593
6843
  });
6594
- if (statement.kind === "delete" && returningColumns === void 0) {
6844
+ if (statement.kind === "delete" && returningColumns === void 0 && from === void 0) {
6595
6845
  const internalWriter = options.writer;
6596
6846
  if (internalWriter.queryFirstColumn !== void 0) {
6597
6847
  const selectedKeys = await internalWriter.queryFirstColumn(plan);
@@ -6623,6 +6873,16 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
6623
6873
  } else {
6624
6874
  rows = (await this.#queryCompiled(plan)).rows;
6625
6875
  }
6876
+ if (from !== void 0) {
6877
+ const seen = /* @__PURE__ */ new Set();
6878
+ rows = rows.filter((row) => {
6879
+ const key = JSON.stringify(row[keyColumn.name] ?? null);
6880
+ if (seen.has(key))
6881
+ return false;
6882
+ seen.add(key);
6883
+ return true;
6884
+ });
6885
+ }
6626
6886
  const keys = keyColumn.type === "string" && keyColumn.sqlDomain === void 0 ? rows.map((row) => storedSqlValueFromExecution(keyColumn, row[keyColumn.name] ?? null)) : rows.map((row) => row[keyColumn.name] ?? null);
6627
6887
  if (keys.some((key) => key === null)) {
6628
6888
  throw new TypeError(`Unique key values must not be null: ${keyColumn.name}`);
@@ -6666,8 +6926,9 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
6666
6926
  if (typeof value === "number" && !Number.isFinite(value)) {
6667
6927
  throw new TypeError(`UPDATE assignment produced a non-finite number: ${assignment.column}`);
6668
6928
  }
6669
- executionValues?.push(value);
6670
- return column.type === "string" && column.sqlDomain === void 0 ? storedSqlValueFromExecution(column, value) : value;
6929
+ const stored = storedSqlValueFromExecution(column, value);
6930
+ executionValues?.push(column.type === "string" && column.sqlDomain === void 0 ? value : stored);
6931
+ return stored;
6671
6932
  });
6672
6933
  if (executionValues !== void 0 && returnedChanges !== void 0) {
6673
6934
  returnedChanges[assignment.column] = executionValues;
@@ -8051,7 +8312,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
8051
8312
  return active;
8052
8313
  cursor = page.nextCursor;
8053
8314
  if (cursor !== null)
8054
- await yieldToEventLoop();
8315
+ await this.#yieldMaintenance();
8055
8316
  } while (cursor !== null);
8056
8317
  return void 0;
8057
8318
  }
@@ -8089,11 +8350,15 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
8089
8350
  }
8090
8351
  cursor = page.nextCursor;
8091
8352
  if (cursor !== null)
8092
- await yieldToEventLoop();
8353
+ await this.#yieldMaintenance();
8093
8354
  } while (cursor !== null);
8094
8355
  }
8095
8356
  async collectGarbage(options = {}) {
8096
8357
  const maxItems = boundedMaintenanceBatchItems(options.maxItemsPerStep ?? 64, "Garbage collection items per step");
8358
+ let reconciliationCursor = null;
8359
+ do {
8360
+ reconciliationCursor = (await this.#reconcileCompactionPage(reconciliationCursor)).nextCursor;
8361
+ } while (reconciliationCursor !== null);
8097
8362
  let progress = await this.collectGarbageStep({
8098
8363
  maxItems,
8099
8364
  ...options.maxPlanningItems === void 0 ? {} : { maxPlanningItems: options.maxPlanningItems },
@@ -8130,7 +8395,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
8130
8395
  return active;
8131
8396
  cursor = page.nextCursor;
8132
8397
  if (cursor !== null)
8133
- await yieldToEventLoop();
8398
+ await this.#yieldMaintenance();
8134
8399
  } while (cursor !== null);
8135
8400
  return void 0;
8136
8401
  }
@@ -8171,14 +8436,14 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
8171
8436
  this.#autoCollectionRequested = false;
8172
8437
  this.#commitsSinceCollection = 0;
8173
8438
  this.#lastCollectionAt = dateMilliseconds(this.#now());
8174
- const run = this.#runAutoCollection().then((moreWork) => {
8439
+ const run = this.#runAutoCollection().then(({ moreWork, reclaimed }) => {
8175
8440
  this.#autoCollectionConsecutiveFailures = 0;
8176
8441
  this.#autoCollectionLastError = void 0;
8177
8442
  this.#lastCollectionCompletedAt = dateMilliseconds(this.#now());
8178
8443
  this.#autoCollectionRetryAt = void 0;
8179
8444
  if (moreWork)
8180
8445
  this.#autoCollectionRequested = true;
8181
- else
8446
+ if (reclaimed || !moreWork)
8182
8447
  this.#autoCollectionDebtCommits = 0;
8183
8448
  }).catch((error) => {
8184
8449
  this.#autoCollectionConsecutiveFailures += 1;
@@ -8194,7 +8459,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
8194
8459
  this.#autoCollectionTask = void 0;
8195
8460
  this.#autoCollectionInFlight = false;
8196
8461
  if (this.#autoCollectionRequested) {
8197
- void yieldToEventLoop().then(() => {
8462
+ void this.#yieldMaintenance().then(() => {
8198
8463
  if (this.#closed) {
8199
8464
  this.#autoCollectionRequested = false;
8200
8465
  return;
@@ -8239,6 +8504,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
8239
8504
  async #runAutoCollection() {
8240
8505
  const reconciliationMoreWork = await this.#reconcileCompactionMaintenance();
8241
8506
  let moreWork = false;
8507
+ let reclaimed = false;
8242
8508
  for (let pass = 0; pass < AUTO_COLLECT_MAX_PASSES; pass += 1) {
8243
8509
  if (this.#closed)
8244
8510
  break;
@@ -8249,7 +8515,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
8249
8515
  retainRecentVersions: AUTO_COLLECT_RETAINED_VERSIONS
8250
8516
  }, AUTO_COLLECT_RETAINED_VERSION_MS);
8251
8517
  while (progress.result === null) {
8252
- await yieldToEventLoop();
8518
+ await this.#yieldMaintenance();
8253
8519
  progress = await this.resumeGarbageCollectionJob(progress.jobId, {
8254
8520
  maxItems: AUTO_COLLECT_STEP_ITEMS
8255
8521
  });
@@ -8259,14 +8525,19 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
8259
8525
  if (!moreWork) {
8260
8526
  break;
8261
8527
  }
8262
- await yieldToEventLoop();
8528
+ reclaimed = true;
8529
+ await this.#yieldMaintenance();
8263
8530
  }
8264
8531
  await this.#pruneFinishedJobRecords();
8265
- return moreWork || reconciliationMoreWork;
8532
+ return { moreWork: moreWork || reconciliationMoreWork, reclaimed };
8266
8533
  }
8267
8534
  async #reconcileCompactionMaintenance() {
8268
- const page = await this.store.listCompactionJobPage(this.#compactionReconciliationCursor, AUTO_COMPACTION_RECONCILE_RECORDS);
8269
- this.#compactionReconciliationCursor = page.nextCursor;
8535
+ const { nextCursor, moreWork } = await this.#reconcileCompactionPage(this.#compactionReconciliationCursor);
8536
+ this.#compactionReconciliationCursor = nextCursor;
8537
+ return moreWork;
8538
+ }
8539
+ async #reconcileCompactionPage(cursor) {
8540
+ const page = await this.store.listCompactionJobPage(cursor, AUTO_COMPACTION_RECONCILE_RECORDS);
8270
8541
  let moreWork = page.nextCursor !== null;
8271
8542
  const cutoff = dateIsoString(this.#now());
8272
8543
  for (const listed of page.records) {
@@ -8310,14 +8581,22 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
8310
8581
  this.#maybeScheduleAutoCompaction(table, await this.#currentVisibleSegments(table));
8311
8582
  continue;
8312
8583
  }
8313
- const progress = await this.resumeCompactionJob(job.id, { maxBlocks: 1 });
8314
- if (progress.result !== null) {
8315
- job = await this.store.getCompactionJob(job.id) ?? job;
8316
- if (!isActiveCompactionState(job.state))
8584
+ try {
8585
+ const progress = await this.resumeCompactionJob(job.id, { maxBlocks: 1 });
8586
+ if (progress.result !== null) {
8587
+ job = await this.store.getCompactionJob(job.id) ?? job;
8588
+ if (!isActiveCompactionState(job.state))
8589
+ continue;
8590
+ }
8591
+ } catch (error) {
8592
+ if (error instanceof CompactionJobCancelledError)
8317
8593
  continue;
8594
+ if (this.#closed)
8595
+ throw error;
8596
+ this.#maybeScheduleAutoCompaction(table, await this.#currentVisibleSegments(table));
8318
8597
  }
8319
8598
  }
8320
- return moreWork;
8599
+ return { nextCursor: page.nextCursor, moreWork };
8321
8600
  }
8322
8601
  async #pruneFinishedJobRecords() {
8323
8602
  const newestFirst = (left, right) => right.updatedAt.localeCompare(left.updatedAt) || right.id.localeCompare(left.id);
@@ -8338,7 +8617,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
8338
8617
  }
8339
8618
  cursor = page.nextCursor;
8340
8619
  if (cursor !== null)
8341
- await yieldToEventLoop();
8620
+ await this.#yieldMaintenance();
8342
8621
  } while (cursor !== null);
8343
8622
  const retainedCompactionIds = new Set(newestCompactions.map((job) => job.id));
8344
8623
  cursor = null;
@@ -8388,7 +8667,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
8388
8667
  }
8389
8668
  cursor = page.nextCursor;
8390
8669
  if (cursor !== null)
8391
- await yieldToEventLoop();
8670
+ await this.#yieldMaintenance();
8392
8671
  } while (cursor !== null);
8393
8672
  const newestCollections = [];
8394
8673
  cursor = null;
@@ -8400,7 +8679,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
8400
8679
  }
8401
8680
  cursor = page.nextCursor;
8402
8681
  if (cursor !== null)
8403
- await yieldToEventLoop();
8682
+ await this.#yieldMaintenance();
8404
8683
  } while (cursor !== null);
8405
8684
  const retainedCollectionIds = new Set(newestCollections.map((job) => job.id));
8406
8685
  cursor = null;
@@ -8413,7 +8692,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
8413
8692
  }
8414
8693
  cursor = page.nextCursor;
8415
8694
  if (cursor !== null)
8416
- await yieldToEventLoop();
8695
+ await this.#yieldMaintenance();
8417
8696
  } while (cursor !== null);
8418
8697
  }
8419
8698
  async listGarbageCollectionJobs() {
@@ -8480,7 +8759,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
8480
8759
  }
8481
8760
  cursor = page.nextCursor;
8482
8761
  if (cursor !== null)
8483
- await yieldToEventLoop();
8762
+ await this.#yieldMaintenance();
8484
8763
  } while (cursor !== null);
8485
8764
  return newest?.discovery === void 0 ? void 0 : { jobId: newest.id, discovery: newest.discovery };
8486
8765
  }
@@ -9039,7 +9318,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
9039
9318
  }
9040
9319
  const priorAttemptOutputStoredBytes = await this.#priorCompactionAttemptOutputStoredBytes(table.id, baseJobId);
9041
9320
  const maximumOutputStoredBytes = Math.max(0, floorWholeNumberProduct(selection.level0SourceStoredBytes, maxWriteAmplification, "Compaction maximum output stored bytes") - priorAttemptOutputStoredBytes);
9042
- const plannedOutputStoredBytesUpperBound = await this.#plannedPhysicalOutputStoredBytesUpperBound(rewritePlan, snapshot);
9321
+ const plannedOutputStoredBytesUpperBound = await this.#plannedPhysicalOutputStoredBytesUpperBound(rewritePlan, memoryBudgetBytes, snapshot);
9043
9322
  levelTwoBudget = {
9044
9323
  outputPartitionOrdinal,
9045
9324
  maxWriteAmplification,
@@ -9138,7 +9417,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
9138
9417
  }
9139
9418
  cursor = page.nextCursor;
9140
9419
  if (cursor !== null)
9141
- await yieldToEventLoop();
9420
+ await this.#yieldMaintenance();
9142
9421
  } while (cursor !== null);
9143
9422
  return largest;
9144
9423
  }
@@ -9495,17 +9774,15 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
9495
9774
  const columnIndexById = new Map(table.columns.map((column, index) => [column.id, index]));
9496
9775
  const touched = /* @__PURE__ */ new Set();
9497
9776
  const deltaKeys = /* @__PURE__ */ new Map();
9498
- for (const segment of segments) {
9499
- if (!mergeSourceReferencesKeys(segment.kind))
9500
- continue;
9501
- const keys = [];
9502
- await this.#forEachMergeSourceKey(segment, keyColumn, snapshot, (value) => {
9503
- const key = overlayKeyOf(keyColumn.type, value);
9504
- keys.push(key);
9505
- touched.add(key);
9506
- });
9507
- deltaKeys.set(segment.segmentId, keys);
9508
- }
9777
+ const deltas = segments.filter((segment) => mergeSourceReferencesKeys(segment.kind));
9778
+ for (const segment of deltas)
9779
+ deltaKeys.set(segment.segmentId, []);
9780
+ const unusedBudgetBytes = memoryBudgetBytes - plannerMemoryBytes;
9781
+ await this.#forEachMergeSourceKey(deltas, keyColumn, unusedBudgetBytes, snapshot, (segment, value) => {
9782
+ const key = overlayKeyOf(keyColumn.type, value);
9783
+ deltaKeys.get(segment.segmentId)?.push(key);
9784
+ touched.add(key);
9785
+ });
9509
9786
  let slotCount = 0;
9510
9787
  for (const segment of segments) {
9511
9788
  if (mergeSourceBearsRows(segment.kind))
@@ -9577,7 +9854,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
9577
9854
  if (keys !== void 0) {
9578
9855
  keys.forEach(visit);
9579
9856
  } else if (touched.size > 0) {
9580
- await this.#forEachMergeSourceKey(segment, keyColumn, snapshot, (value, rowIndex) => {
9857
+ await this.#forEachMergeSourceKey([segment], keyColumn, unusedBudgetBytes, snapshot, (_, value, rowIndex) => {
9581
9858
  visit(overlayKeyOf(keyColumn.type, value), rowIndex);
9582
9859
  });
9583
9860
  }
@@ -9614,28 +9891,48 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
9614
9891
  }
9615
9892
  return { ...output.finish(), sourceOutputRowStarts };
9616
9893
  }
9617
- async #forEachMergeSourceKey(segment, column, snapshot, action) {
9618
- const planned = segment.columns.find((candidate) => candidate.columnId === column.id);
9619
- if (planned === void 0) {
9620
- throw new Error(`Mutation segment has no key column: ${segment.segmentId}`);
9894
+ async #forEachMergeSourceKey(segments, column, unusedBudgetBytes, snapshot, action) {
9895
+ const blocks = [];
9896
+ let largestEncodedBytes = 0;
9897
+ for (const segment of segments) {
9898
+ const planned = segment.columns.find((candidate) => candidate.columnId === column.id);
9899
+ if (planned === void 0) {
9900
+ throw new Error(`Mutation segment has no key column: ${segment.segmentId}`);
9901
+ }
9902
+ for (const source of planned.sourceBlocks) {
9903
+ blocks.push({
9904
+ blockId: source.blockId,
9905
+ storedBytes: source.storedBytes,
9906
+ encodedBytes: source.encodedBytes,
9907
+ segment,
9908
+ source
9909
+ });
9910
+ largestEncodedBytes = Math.max(largestEncodedBytes, source.encodedBytes);
9911
+ }
9621
9912
  }
9622
- let rowIndex = 0;
9623
- for (const source of planned.sourceBlocks) {
9624
- await this.#renewInternalLeaseIfNeeded(snapshot);
9625
- const bytes = await this.store.getBlock(source.blockId);
9626
- if (bytes === void 0)
9627
- throw new Error(`Compaction source block is missing: ${source.blockId}`);
9913
+ const limitBytes = safeWholeNumberSum([
9914
+ Math.max(0, unusedBudgetBytes),
9915
+ safeWholeNumberProduct(largestEncodedBytes, MERGE_PLANNER_DECODED_KEY_BLOCK_FACTOR, "Mutation compaction key decode memory")
9916
+ ], "Mutation compaction key decode memory");
9917
+ const rowIndexes = /* @__PURE__ */ new Map();
9918
+ await this.#decodeCompactionBlocks(blocks, { limitBytes, retainsDecoded: false }, snapshot, async (bytes, { source }) => {
9628
9919
  const description = inspectBlock(bytes);
9629
9920
  if (bytes.byteLength !== source.storedBytes || description.encodedLength !== source.encodedBytes || description.checksum !== source.checksum || description.rowCount !== source.rowCount || description.type !== column.type) {
9630
9921
  throw new Error(`Compaction source block differs from its plan: ${source.blockId}`);
9631
9922
  }
9632
- for (const value of (await decodeBlock(bytes)).column.values) {
9633
- action(value, rowIndex);
9923
+ return (await decodeBlock(bytes)).column.values;
9924
+ }, (values, { segment }) => {
9925
+ let rowIndex = rowIndexes.get(segment.segmentId) ?? 0;
9926
+ for (const value of values) {
9927
+ action(segment, value, rowIndex);
9634
9928
  rowIndex += 1;
9635
9929
  }
9636
- }
9637
- if (rowIndex !== segment.rowCount) {
9638
- throw new Error(`Mutation segment key rows differ: ${segment.segmentId}`);
9930
+ rowIndexes.set(segment.segmentId, rowIndex);
9931
+ });
9932
+ for (const segment of segments) {
9933
+ if ((rowIndexes.get(segment.segmentId) ?? 0) !== segment.rowCount) {
9934
+ throw new Error(`Mutation segment key rows differ: ${segment.segmentId}`);
9935
+ }
9639
9936
  }
9640
9937
  }
9641
9938
  async #refinePhysicalOutputWindows(columns, estimatedOutputs, targetBlockBytes, outputCompression, memoryBudgetBytes, snapshot) {
@@ -9694,12 +9991,12 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
9694
9991
  const loaded = await this.#loadPhysicalCompactionRanges(column, output, outputCompression, memoryBudgetBytes, snapshot);
9695
9992
  return measurePhysicalColumnRanges(column.type, loaded.ranges);
9696
9993
  }
9697
- async #plannedPhysicalOutputStoredBytesUpperBound(plan, snapshot) {
9994
+ async #plannedPhysicalOutputStoredBytesUpperBound(plan, memoryBudgetBytes, snapshot) {
9698
9995
  const layout = physicalRewriteLayout(plan);
9699
9996
  let total = 0;
9700
9997
  for (const output of layout.outputs) {
9701
9998
  for (const column of layout.columns) {
9702
- const measurement = await this.#measurePhysicalCompactionOutput(column, output, plan.outputCompression, Number.MAX_SAFE_INTEGER, snapshot);
9999
+ const measurement = await this.#measurePhysicalCompactionOutput(column, output, plan.outputCompression, memoryBudgetBytes, snapshot);
9703
10000
  total = safeWholeNumberSum([
9704
10001
  total,
9705
10002
  maximumPhysicalBlockByteLength(measurement.encodedByteLength, measurement.metadata, plan.outputCompression)
@@ -9717,7 +10014,17 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
9717
10014
  if (job.state === "published") {
9718
10015
  return this.#publishedCompactionProgress(table, job);
9719
10016
  }
9720
- const linkedTransaction = job.transactionId === null ? void 0 : await this.store.getTransaction(job.transactionId);
10017
+ let linkedTransaction = job.transactionId === null ? void 0 : await this.store.getTransaction(job.transactionId);
10018
+ if (job.transactionId !== null && linkedTransaction?.status === "active" && Date.parse(linkedTransaction.expiresAt) <= dateMilliseconds(this.#now())) {
10019
+ const cutoff = dateIsoString(this.#now());
10020
+ await this.store.abortTransactionIfExpired({
10021
+ transactionId: linkedTransaction.id,
10022
+ expectedOwnerId: linkedTransaction.ownerId,
10023
+ expiresAtCutoff: cutoff,
10024
+ updatedAt: cutoff
10025
+ });
10026
+ linkedTransaction = await this.store.getTransaction(job.transactionId);
10027
+ }
9721
10028
  if (linkedTransaction?.status === "committed" && linkedTransaction.committedVersion !== null) {
9722
10029
  job = await this.#markCompactionPublished(job, linkedTransaction.committedVersion);
9723
10030
  const recoveredManifest = await this.store.getManifest(linkedTransaction.committedVersion);
@@ -9882,18 +10189,20 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
9882
10189
  }
9883
10190
  transaction.setCompactionIntent(job.id, job.sourceBlockIds);
9884
10191
  transaction.markLogicallyUnchanged();
9885
- let manifest;
9886
- for (; ; ) {
9887
- let publicationConflict;
10192
+ let publicationConflict;
10193
+ const publish = async () => {
9888
10194
  try {
9889
- manifest = await transaction.commit();
9890
- this.#afterCompactionCommit(manifest, table.id);
9891
- break;
10195
+ const manifest2 = await transaction.commit();
10196
+ this.#afterCompactionCommit(manifest2, table.id);
10197
+ return manifest2;
9892
10198
  } catch (error) {
9893
10199
  if (!(error instanceof WriteConflictError))
9894
10200
  throw error;
9895
10201
  publicationConflict = error;
10202
+ return void 0;
9896
10203
  }
10204
+ };
10205
+ const rebaseOverConcurrentCommits = async () => {
9897
10206
  const currentVersion = await this.store.getCurrentManifestVersion();
9898
10207
  const sourcePresence = await this.#manifestBlockPresence(currentVersion, job.sourceBlockIds);
9899
10208
  if (sourcePresence.some((present) => !present)) {
@@ -9920,7 +10229,24 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
9920
10229
  }
9921
10230
  transaction.setCompactionIntent(job.id, job.sourceBlockIds);
9922
10231
  transaction.markLogicallyUnchanged();
9923
- await yieldToEventLoop();
10232
+ };
10233
+ let manifest = await publish();
10234
+ if (manifest === void 0) {
10235
+ manifest = await this.#withCompactionPublicationSlot(async () => {
10236
+ for (let conflicts = 1; conflicts <= MAX_COMPACTION_PUBLICATION_CONFLICTS; conflicts += 1) {
10237
+ await rebaseOverConcurrentCommits();
10238
+ const published = await publish();
10239
+ if (published !== void 0)
10240
+ return published;
10241
+ }
10242
+ return void 0;
10243
+ });
10244
+ if (manifest === void 0) {
10245
+ if (transaction.status === "active")
10246
+ await transaction.abort();
10247
+ job = await this.#abortCompactionJob(job, `Compaction publication lost ${String(MAX_COMPACTION_PUBLICATION_CONFLICTS + 1)} manifest races`);
10248
+ throw new Error(job.error, { cause: publicationConflict });
10249
+ }
9924
10250
  }
9925
10251
  job = await this.#markCompactionPublished(job, manifest.version);
9926
10252
  return compactionProgress(table.name, job, this.#compactionResult(table, job, manifest.version));
@@ -10031,26 +10357,84 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
10031
10357
  throw new CompactionMemoryBudgetError(memoryBudgetBytes, memoryBound);
10032
10358
  }
10033
10359
  const outputEnd = safeWholeNumberSum([output.rowStart, output.rowCount], "Compaction output row range");
10034
- const ranges = [];
10035
- for (const sourceBlock of overlappingPhysicalSourceRanges(column, output)) {
10036
- if (snapshot !== void 0)
10037
- await this.#renewInternalLeaseIfNeeded(snapshot);
10038
- const bytes = await this.store.getBlock(sourceBlock.blockId);
10039
- if (bytes === void 0) {
10040
- throw new Error(`A compaction source block is missing: ${sourceBlock.blockId}`);
10041
- }
10360
+ const sourceBlocks = overlappingPhysicalSourceRanges(column, output);
10361
+ const distinct = [];
10362
+ const seen = /* @__PURE__ */ new Set();
10363
+ for (const sourceBlock of sourceBlocks) {
10364
+ if (seen.has(sourceBlock.blockId))
10365
+ continue;
10366
+ seen.add(sourceBlock.blockId);
10367
+ distinct.push(sourceBlock);
10368
+ }
10369
+ const decoded = /* @__PURE__ */ new Map();
10370
+ const peakWorkingBytes = await this.#decodeCompactionBlocks(distinct, { limitBytes: memoryBudgetBytes, retainsDecoded: true }, snapshot, async (bytes, sourceBlock) => {
10042
10371
  const description = inspectBlock(bytes);
10043
10372
  if (bytes.byteLength !== sourceBlock.storedBytes || description.encodedLength !== sourceBlock.encodedBytes || description.checksum !== sourceBlock.checksum || description.rowCount !== sourceBlock.sourceBlockRowCount || description.type !== column.type) {
10044
10373
  throw new Error(`A compaction source block differs from its plan: ${sourceBlock.blockId}`);
10045
10374
  }
10046
- const decoded = await decodePhysicalBlock(bytes);
10375
+ return decodePhysicalBlock(bytes);
10376
+ }, (block, sourceBlock) => {
10377
+ decoded.set(sourceBlock.blockId, block);
10378
+ });
10379
+ const ranges = [];
10380
+ for (const sourceBlock of sourceBlocks) {
10381
+ const block = decoded.get(sourceBlock.blockId);
10382
+ if (block === void 0) {
10383
+ throw new Error(`A compaction source block is missing: ${sourceBlock.blockId}`);
10384
+ }
10047
10385
  const sourceEnd = safeWholeNumberSum([sourceBlock.outputRowStart, sourceBlock.rowCount], "Compaction source row range");
10048
10386
  const start = sourceBlock.sourceRowStart + Math.max(output.rowStart, sourceBlock.outputRowStart) - sourceBlock.outputRowStart;
10049
10387
  const end = sourceBlock.sourceRowStart + Math.min(outputEnd, sourceEnd) - sourceBlock.outputRowStart;
10050
- const slice = slicePhysicalColumn(decoded.column, start, end);
10388
+ const slice = slicePhysicalColumn(block.column, start, end);
10051
10389
  ranges.push({ column: slice, start: 0, end: slice.rowCount });
10052
10390
  }
10053
- return { ranges, peakWorkingBytes: memoryBound };
10391
+ return { ranges, peakWorkingBytes: Math.max(memoryBound, peakWorkingBytes) };
10392
+ }
10393
+ async #decodeCompactionBlocks(blocks, budget, snapshot, decode, consume) {
10394
+ let retainedBytes = 0;
10395
+ let peakBytes = 0;
10396
+ let start = 0;
10397
+ while (start < blocks.length) {
10398
+ let end = start;
10399
+ let transientBytes = 0;
10400
+ let storedBytes = 0;
10401
+ for (; end < blocks.length && end - start < MAX_STORAGE_BULK_READ_ITEMS; end += 1) {
10402
+ const block = blocks[end];
10403
+ if (block === void 0)
10404
+ break;
10405
+ const blockTransientBytes = safeWholeNumberSum([
10406
+ safeWholeNumberProduct(block.storedBytes, 2, "Compaction decode memory"),
10407
+ safeWholeNumberProduct(block.encodedBytes, 2, "Compaction decode memory")
10408
+ ], "Compaction decode memory");
10409
+ if (end > start && (transientBytes + blockTransientBytes > budget.limitBytes - retainedBytes || storedBytes + block.storedBytes > MAX_BLOCK_READ_BATCH_BYTES)) {
10410
+ break;
10411
+ }
10412
+ transientBytes += blockTransientBytes;
10413
+ storedBytes += block.storedBytes;
10414
+ }
10415
+ const group = blocks.slice(start, end);
10416
+ if (snapshot !== void 0)
10417
+ await this.#renewInternalLeaseIfNeeded(snapshot);
10418
+ const bytes = await this.#readBlockWindow(group.map((block) => block.blockId));
10419
+ const decoded = await Promise.all(group.map((block, index) => {
10420
+ const stored = bytes[index];
10421
+ if (stored === void 0) {
10422
+ throw new Error(`A compaction source block is missing: ${block.blockId}`);
10423
+ }
10424
+ return decode(stored, block);
10425
+ }));
10426
+ peakBytes = Math.max(peakBytes, retainedBytes + transientBytes);
10427
+ for (const [index, block] of group.entries()) {
10428
+ const value = decoded[index];
10429
+ if (value === void 0)
10430
+ throw new Error("Compaction decode lost a block");
10431
+ consume(value, block);
10432
+ if (budget.retainsDecoded)
10433
+ retainedBytes += block.encodedBytes;
10434
+ }
10435
+ start = end;
10436
+ }
10437
+ return peakBytes;
10054
10438
  }
10055
10439
  async #loadCompactionSources(job) {
10056
10440
  const segments = await Promise.all(job.sourceSegmentIds.map((id) => this.store.getSegment(id)));
@@ -10080,7 +10464,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
10080
10464
  }
10081
10465
  segmentCursor = page.nextCursor;
10082
10466
  if (segmentCursor !== null)
10083
- await yieldToEventLoop();
10467
+ await this.#yieldMaintenance();
10084
10468
  } while (segmentCursor !== null);
10085
10469
  if (job.outputPartitionOrdinal === void 0 && job.targetLevel === 1 && plan.kind !== "copy-v1" && plan.partitions !== void 0) {
10086
10470
  const table = await this.store.getTable(job.tableId);
@@ -12634,7 +13018,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
12634
13018
  }
12635
13019
  async #findTable(name) {
12636
13020
  validateName(name, "Table");
12637
- const table = await this.store.getTableByName(name);
13021
+ const table = await this.store.getTableByName(name) ?? await this.store.getTableByName(await this.#foldTableName(name));
12638
13022
  if (table === void 0)
12639
13023
  throw new UnknownTableError(name);
12640
13024
  if (table.view !== void 0)
@@ -13329,7 +13713,26 @@ function postingFrequencyTotal(postings) {
13329
13713
  }
13330
13714
  const secondaryNumberBits = new DataView(new ArrayBuffer(8));
13331
13715
  function storedSqlValueFromExecution(column, value) {
13332
- return column?.type === "string" && column.sqlDomain === void 0 && typeof value === "string" ? externalSqlTextValue(value) : value;
13716
+ if (column?.type === "string" && column.sqlDomain === void 0 && typeof value === "string") {
13717
+ return externalSqlTextValue(value);
13718
+ }
13719
+ return coerceSqlWriteValue(column, value);
13720
+ }
13721
+ function coerceSqlWriteValue(column, value) {
13722
+ if (column === void 0 || column.sqlDomain !== void 0 || column.type === "string" || typeof value !== "string" || isSqlDomainValue(value)) {
13723
+ return value;
13724
+ }
13725
+ return readUntypedText(column.type, value);
13726
+ }
13727
+ function coerceSqlWriteBatch(table, input) {
13728
+ for (const column of table.columns) {
13729
+ if (column.type === "string" || column.sqlDomain !== void 0)
13730
+ continue;
13731
+ const values = input.columns[column.name];
13732
+ if (!values?.some((value) => typeof value === "string"))
13733
+ continue;
13734
+ input.columns[column.name] = values.map((value) => coerceSqlWriteValue(column, value));
13735
+ }
13333
13736
  }
13334
13737
  function executionSqlValueFromStorage(column, value) {
13335
13738
  return column?.type === "string" && column.sqlDomain === void 0 && typeof value === "string" ? protectedSqlTextValue(value) : value;
@@ -13816,6 +14219,28 @@ function compositeKeyValue(columns, values) {
13816
14219
  }
13817
14220
  return columns.map((column, index) => secondaryTupleComponent(column.type, values[index] ?? null)).join("");
13818
14221
  }
14222
+ function foldColumnName(table, name) {
14223
+ const columns = visibleTableColumns(table);
14224
+ if (columns.some((column) => column.name === name))
14225
+ return name;
14226
+ const lowered = name.toLowerCase();
14227
+ const matches = columns.filter((column) => column.name.toLowerCase() === lowered);
14228
+ return matches.length === 1 ? matches[0]?.name ?? name : name;
14229
+ }
14230
+ function foldInsertColumns(table, statement) {
14231
+ const columns = statement.columns.map((name) => foldColumnName(table, name));
14232
+ return columns.some((name, index) => name !== statement.columns[index]) ? { ...statement, columns } : statement;
14233
+ }
14234
+ function foldAssignmentColumns(table, assignments) {
14235
+ const folded = assignments.map((assignment) => {
14236
+ const column = foldColumnName(table, assignment.column);
14237
+ return column === assignment.column ? assignment : { ...assignment, column };
14238
+ });
14239
+ return folded.some((assignment, index) => assignment !== assignments[index]) ? folded : assignments;
14240
+ }
14241
+ function quoteIdentifier(name) {
14242
+ return `"${name.replace(/"/g, '""')}"`;
14243
+ }
13819
14244
  function normalizeDomainBatch(table, input) {
13820
14245
  for (const column of table.columns) {
13821
14246
  const values = input.columns[column.name];
@@ -13846,6 +14271,49 @@ function normalizeColumnLogicalValue(column, value) {
13846
14271
  }
13847
14272
  return value;
13848
14273
  }
14274
+ function mergeAscendingIndexes(left, right) {
14275
+ const merged = [];
14276
+ let leftIndex = 0;
14277
+ let rightIndex = 0;
14278
+ while (leftIndex < left.length || rightIndex < right.length) {
14279
+ const a = left[leftIndex];
14280
+ const b = right[rightIndex];
14281
+ if (b === void 0 || a !== void 0 && a <= b) {
14282
+ if (a !== void 0)
14283
+ merged.push(a);
14284
+ leftIndex += 1;
14285
+ } else {
14286
+ merged.push(b);
14287
+ rightIndex += 1;
14288
+ }
14289
+ }
14290
+ return merged;
14291
+ }
14292
+ function applyUpsertConflictFilter(batch, rowCount, classified) {
14293
+ const acceptedRowIndexes = mergeAscendingIndexes(classified.inserts, classified.updates);
14294
+ const skippedRowCount = classified.skipped.length;
14295
+ if (acceptedRowIndexes.length === rowCount) {
14296
+ return { batch, rowCount, skippedRowCount, acceptedRowIndexes, firings: classified };
14297
+ }
14298
+ const acceptedPosition = new Map(acceptedRowIndexes.map((original, position) => [original, position]));
14299
+ return {
14300
+ batch: selectBatchRows(batch, acceptedRowIndexes),
14301
+ rowCount: acceptedRowIndexes.length,
14302
+ skippedRowCount,
14303
+ acceptedRowIndexes,
14304
+ firings: {
14305
+ inserts: classified.inserts.map((index) => acceptedPosition.get(index) ?? -1),
14306
+ updates: classified.updates.map((index) => acceptedPosition.get(index) ?? -1),
14307
+ skipped: [],
14308
+ oldImages: acceptedRowIndexes.map((index) => classified.oldImages[index])
14309
+ }
14310
+ };
14311
+ }
14312
+ function remapGeneratedColumns(generated, acceptedRowIndexes) {
14313
+ for (const [name, values] of generated) {
14314
+ generated.set(name, acceptedRowIndexes.map((index) => values[index] ?? null));
14315
+ }
14316
+ }
13849
14317
  function selectBatchRows(input, indexes) {
13850
14318
  return {
13851
14319
  columns: Object.fromEntries(Object.entries(input.columns).map(([name, values]) => [
@@ -13900,23 +14368,8 @@ function normalizeDomainUpdate(table, input) {
13900
14368
  function normalizePlanDomainLiterals(input, domains, types = /* @__PURE__ */ new Map()) {
13901
14369
  const pass = { probing: true, needed: false };
13902
14370
  const coerceText = (type, text) => {
13903
- if (type === "datetime")
13904
- return parseSqlTimestampText(text);
13905
- if (type === "number") {
13906
- const trimmed = text.trim();
13907
- if (trimmed === "")
13908
- return void 0;
13909
- const parsed = Number(trimmed);
13910
- return Number.isFinite(parsed) ? parsed : void 0;
13911
- }
13912
- if (type === "boolean") {
13913
- const lowered = text.trim().toLowerCase();
13914
- if (lowered === "t" || lowered === "true" || lowered === "1")
13915
- return true;
13916
- if (lowered === "f" || lowered === "false" || lowered === "0")
13917
- return false;
13918
- }
13919
- return void 0;
14371
+ const read = readUntypedText(type, text);
14372
+ return read === text ? void 0 : read;
13920
14373
  };
13921
14374
  const rewriteBlock = (block) => {
13922
14375
  if (pass.probing && pass.needed)
@@ -14140,6 +14593,14 @@ function autoCompactionHint(version, segments) {
14140
14593
  deltas
14141
14594
  };
14142
14595
  }
14596
+ function countLevelZeroSegments(segments) {
14597
+ let levelZero = 0;
14598
+ for (const segment of segments) {
14599
+ if (segment.level === 0)
14600
+ levelZero += 1;
14601
+ }
14602
+ return levelZero;
14603
+ }
14143
14604
  function autoCompactionDueHint(hint) {
14144
14605
  return hint.levelZero >= AUTO_COMPACT_SCAN_SEGMENTS || hint.deltas >= AUTO_COMPACT_DELTA_SEGMENTS;
14145
14606
  }
@@ -14494,9 +14955,15 @@ function installStreamedWindow(vector, resident, windowStart, windowRows, memory
14494
14955
  mutable.window = { start: windowStart, length: windowRows };
14495
14956
  }
14496
14957
  function derivedColumnarTable(name, result, schema) {
14497
- const columns = new Map(schema.map(({ name: columnName, type }) => [
14958
+ const columns = new Map(schema.map(({ name: columnName, type, sqlDomain }) => [
14498
14959
  columnName,
14499
- { type, values: result.rows.map((row) => row[columnName] ?? null) }
14960
+ {
14961
+ type,
14962
+ values: sqlDomain?.kind === "numeric" ? result.rows.map((row) => {
14963
+ const value = row[columnName] ?? null;
14964
+ return typeof value === "number" ? exactNumericValue(value) : value;
14965
+ }) : result.rows.map((row) => row[columnName] ?? null)
14966
+ }
14500
14967
  ]));
14501
14968
  return createColumnarTable(name, columns);
14502
14969
  }
@@ -15584,6 +16051,7 @@ function sourceOrderTuple(segment, transactions, label) {
15584
16051
  };
15585
16052
  }
15586
16053
  const MERGE_PLANNER_KEY_BYTES = 96;
16054
+ const MERGE_PLANNER_DECODED_KEY_BLOCK_FACTOR = 4;
15587
16055
  function planLinearOutputPartitions(totalRows, partitionRows, firstOrder, nextOrder) {
15588
16056
  const count = Math.max(1, Math.ceil(totalRows / partitionRows));
15589
16057
  const orders = fractionalLogicalOrders(firstOrder, nextOrder, count);
@@ -15699,7 +16167,6 @@ function mergePlannerMemoryBound(table, segments, keyColumnId) {
15699
16167
  const PATCH_ROW_BYTES = 64;
15700
16168
  const PATCH_CELL_BYTES = 48;
15701
16169
  const RANGE_BYTES = 80;
15702
- const DECODED_KEY_BLOCK_FACTOR = 4;
15703
16170
  let slotRows = 0;
15704
16171
  let deltaKeys = 0;
15705
16172
  let patchRows = 0;
@@ -15730,7 +16197,7 @@ function mergePlannerMemoryBound(table, segments, keyColumnId) {
15730
16197
  safeWholeNumberProduct(columns, PATCH_CELL_BYTES, "Mutation patch cells")
15731
16198
  ], "Mutation compaction patch row"), "Mutation compaction patches"),
15732
16199
  safeWholeNumberProduct(safeWholeNumberSum([sourceBlocks, safeWholeNumberProduct(patchRows, columns, "Mutation patched cells")], "Mutation compaction ranges"), RANGE_BYTES, "Mutation compaction range bytes"),
15733
- safeWholeNumberProduct(largestKeyBlockBytes, DECODED_KEY_BLOCK_FACTOR, "Mutation compaction decoded key block")
16200
+ safeWholeNumberProduct(largestKeyBlockBytes, MERGE_PLANNER_DECODED_KEY_BLOCK_FACTOR, "Mutation compaction decoded key block")
15734
16201
  ], "Mutation compaction planner memory");
15735
16202
  }
15736
16203
  function mergeSourceBearsRows(kind) {