@minnowdb/core 0.7.1 → 0.7.5

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";
@@ -15,13 +16,13 @@ import { cachedQueryTerms, FTS_TOKENIZER_VERSION, renderDocumentValue, tokenize
15
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_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,9 @@ 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_RETRY_MIN_MS = 250;
82
+ const AUTO_COMPACT_RETRY_MAX_MS = 6e4;
83
+ const MAX_COMPACTION_PUBLICATION_CONFLICTS = 64;
80
84
  const ZONE_DESCRIPTION_CACHE_BYTES = 160;
81
85
  const STAGED_OVERLAY_ORDER_BASE = 2 ** 52;
82
86
  class StaleTriggerDerivationsError extends Error {
@@ -202,6 +206,7 @@ function projectReturningItems(table, alias, items, rows, facts) {
202
206
  having: [],
203
207
  orderBy: []
204
208
  }, facts.domains, facts.types);
209
+ annotatePlanIntegerDivision(plan, (name) => facts.integers.get(name));
205
210
  const visible = visibleTableColumns(table);
206
211
  const columns = new Map(visible.map((column) => [
207
212
  column.name,
@@ -338,6 +343,18 @@ function resolveMutationStatementDatetimes(statement, now) {
338
343
  }
339
344
  };
340
345
  }
346
+ const sources = (from) => from === void 0 ? {} : {
347
+ from: {
348
+ ...from,
349
+ predicates: predicates(from.predicates),
350
+ joins: from.joins.map((join) => ({
351
+ ...join,
352
+ left: expression(join.left),
353
+ right: expression(join.right),
354
+ ...join.on === void 0 ? {} : { on: expression(join.on) }
355
+ }))
356
+ }
357
+ };
341
358
  if (statement.kind === "update") {
342
359
  return {
343
360
  ...statement,
@@ -345,11 +362,16 @@ function resolveMutationStatementDatetimes(statement, now) {
345
362
  ...assignment,
346
363
  expression: expression(assignment.expression)
347
364
  })),
348
- predicates: predicates(statement.predicates)
365
+ predicates: predicates(statement.predicates),
366
+ ...sources(statement.from)
349
367
  };
350
368
  }
351
369
  if (statement.kind === "delete") {
352
- return { ...statement, predicates: predicates(statement.predicates) };
370
+ return {
371
+ ...statement,
372
+ predicates: predicates(statement.predicates),
373
+ ...sources(statement.from)
374
+ };
353
375
  }
354
376
  if (statement.kind === "merge") {
355
377
  return {
@@ -580,6 +602,9 @@ class MinnowDatabase {
580
602
  #commitsSinceCompactionCheck = /* @__PURE__ */ new Map();
581
603
  #compactionSteps = /* @__PURE__ */ new Map();
582
604
  #writeChain = Promise.resolve();
605
+ #pendingCompactionPublications = /* @__PURE__ */ new Set();
606
+ #commitSignal;
607
+ #pendingEventLoopTurn;
583
608
  #pendingWriteReservations = 0;
584
609
  #activeReadReservations = 0;
585
610
  #autoCollectionTask;
@@ -695,6 +720,10 @@ class MinnowDatabase {
695
720
  this.#gzipVerdicts.clear();
696
721
  this.#autoCompactionsRequested.clear();
697
722
  this.#idleCompactionTableIds.clear();
723
+ for (const backoff of this.#autoCompactionBackoff.values()) {
724
+ if (backoff.retryTimer !== void 0)
725
+ clearTimeout(backoff.retryTimer);
726
+ }
698
727
  this.#autoCompactionBackoff.clear();
699
728
  this.#autoCompactionHints.clear();
700
729
  this.#commitsSinceCompactionCheck.clear();
@@ -980,6 +1009,7 @@ class MinnowDatabase {
980
1009
  }
981
1010
  async #runWrite(run) {
982
1011
  const restarting = async () => {
1012
+ await this.#publishPendingCompactions();
983
1013
  await this.#assistAutomaticCollection();
984
1014
  for (let attempt = 0; ; attempt += 1) {
985
1015
  try {
@@ -1012,6 +1042,57 @@ class MinnowDatabase {
1012
1042
  this.#writeChain = Promise.resolve();
1013
1043
  }
1014
1044
  }
1045
+ async #withCompactionPublicationSlot(attempt) {
1046
+ return new Promise((resolve, reject) => {
1047
+ let settled;
1048
+ const claim = () => {
1049
+ settled ??= (async () => {
1050
+ try {
1051
+ resolve(await attempt());
1052
+ } catch (error) {
1053
+ reject(error instanceof Error ? error : new Error(String(error)));
1054
+ } finally {
1055
+ this.#pendingCompactionPublications.delete(claim);
1056
+ }
1057
+ })();
1058
+ return settled;
1059
+ };
1060
+ this.#pendingCompactionPublications.add(claim);
1061
+ void this.#eventLoopTurn().then(claim);
1062
+ });
1063
+ }
1064
+ async #publishPendingCompactions() {
1065
+ while (this.#pendingCompactionPublications.size > 0) {
1066
+ const [claim] = this.#pendingCompactionPublications;
1067
+ if (claim === void 0)
1068
+ return;
1069
+ await claim();
1070
+ }
1071
+ }
1072
+ #eventLoopTurn() {
1073
+ const pending = this.#pendingEventLoopTurn;
1074
+ if (pending !== void 0)
1075
+ return pending;
1076
+ const turn = yieldToEventLoop().then(() => {
1077
+ if (this.#pendingEventLoopTurn === turn)
1078
+ this.#pendingEventLoopTurn = void 0;
1079
+ });
1080
+ this.#pendingEventLoopTurn = turn;
1081
+ return turn;
1082
+ }
1083
+ #nextLocalCommit() {
1084
+ if (this.#commitSignal === void 0) {
1085
+ let resolve = () => void 0;
1086
+ const promise = new Promise((settle) => {
1087
+ resolve = settle;
1088
+ });
1089
+ this.#commitSignal = { promise, resolve };
1090
+ }
1091
+ return this.#commitSignal.promise;
1092
+ }
1093
+ async #yieldMaintenance() {
1094
+ await Promise.race([this.#eventLoopTurn(), this.#nextLocalCommit()]);
1095
+ }
1015
1096
  async #withWriteReservation(run) {
1016
1097
  if (this.#pendingWriteReservations >= MAX_DATABASE_PENDING_WRITES) {
1017
1098
  throw new RangeError(`A database cannot queue more than ${String(MAX_DATABASE_PENDING_WRITES)} writes; await a write for backpressure`);
@@ -2492,7 +2573,7 @@ class MinnowDatabase {
2492
2573
  catalogEpoch: after.catalogEpoch
2493
2574
  };
2494
2575
  }
2495
- await yieldToEventLoop();
2576
+ await this.#yieldMaintenance();
2496
2577
  }
2497
2578
  }
2498
2579
  async #acquireSharedLease(version) {
@@ -2667,7 +2748,15 @@ class MinnowDatabase {
2667
2748
  }
2668
2749
  memberSchema.forEach((column, index) => {
2669
2750
  const expected = schema2?.[index];
2670
- if (expected !== void 0 && (column.type !== expected.type || JSON.stringify(column.sqlDomain ?? null) !== JSON.stringify(expected.sqlDomain ?? null))) {
2751
+ if (expected === void 0)
2752
+ return;
2753
+ if (column.type === expected.type && column.sqlDomain?.kind === "numeric" && expected.sqlDomain?.kind === "numeric") {
2754
+ if (JSON.stringify(column.sqlDomain) !== JSON.stringify(expected.sqlDomain)) {
2755
+ schema2 = schema2?.map((entry, position) => position === index ? { ...entry, sqlDomain: { kind: "numeric" } } : entry);
2756
+ }
2757
+ return;
2758
+ }
2759
+ if (column.type !== expected.type || JSON.stringify(column.sqlDomain ?? null) !== JSON.stringify(expected.sqlDomain ?? null)) {
2671
2760
  throw new TypeError(`UNION member column types must match: ${expected.name}`);
2672
2761
  }
2673
2762
  });
@@ -2782,7 +2871,13 @@ class MinnowDatabase {
2782
2871
  if (template !== null) {
2783
2872
  const shape = resolvePointReadShape(template, options.params ?? []);
2784
2873
  if (shape !== void 0) {
2785
- const point = await this.#pointReadResult(shape, options);
2874
+ let point;
2875
+ try {
2876
+ point = await this.#pointReadResult(shape, options);
2877
+ } catch (error) {
2878
+ if (!(error instanceof UnknownTableError))
2879
+ throw error;
2880
+ }
2786
2881
  throwIfAborted(options.signal);
2787
2882
  if (point !== void 0)
2788
2883
  return externalizeQueryResult(point);
@@ -2905,9 +3000,11 @@ class MinnowDatabase {
2905
3000
  async #applyCatalogRewrites(plan, probe) {
2906
3001
  const aliased = planHasSourceColumnAliases(plan);
2907
3002
  const natural = planHasNaturalJoins(plan);
2908
- const { views, domains, types, columns: catalogColumns } = await this.#catalogFacts(probe);
3003
+ const { views, domains, types, integers, keys, columns: catalogColumns } = await this.#catalogFacts(probe);
2909
3004
  let rewritten = plan.usesSequenceCalls === true ? await this.#resolveSequenceCalls(plan) : plan;
2910
- if (views.size > 0 && planReadsViews(plan, (name) => views.has(name))) {
3005
+ rewritten = foldIdentifierCase(rewritten, catalogColumns);
3006
+ rewritten = expandRowReferences(rewritten, (name) => catalogColumns.get(name));
3007
+ if (views.size > 0 && planReadsViews(rewritten, (name) => views.has(name))) {
2911
3008
  const bodies = /* @__PURE__ */ new Map();
2912
3009
  rewritten = expandViewSources(rewritten, (name) => {
2913
3010
  const sql = views.get(name);
@@ -2924,6 +3021,13 @@ class MinnowDatabase {
2924
3021
  if (planReadsTable(rewritten, (name) => domains.has(name) || types.has(name))) {
2925
3022
  rewritten = normalizePlanDomainLiterals(rewritten, domains, types);
2926
3023
  }
3024
+ if (integers.size > 0 && planMayHaveIntegerDivision(rewritten)) {
3025
+ rewritten = clonePlanTree(rewritten);
3026
+ annotatePlanIntegerDivision(rewritten, (name) => integers.get(name));
3027
+ }
3028
+ if (keys.size > 0) {
3029
+ rewritten = extendGroupByWithKeyDependents(rewritten, (name) => keys.get(name), (name) => catalogColumns.get(name));
3030
+ }
2927
3031
  const columnsOf = (name) => {
2928
3032
  const columns = catalogColumns.get(name);
2929
3033
  if (columns === void 0)
@@ -3034,6 +3138,14 @@ class MinnowDatabase {
3034
3138
  item.expression = await rewrite(item.expression);
3035
3139
  return resolved;
3036
3140
  }
3141
+ async #foldTableName(name) {
3142
+ const { columns } = await this.#catalogFacts();
3143
+ if (columns.has(name))
3144
+ return name;
3145
+ const lowered = name.toLowerCase();
3146
+ const matches = [...columns.keys()].filter((candidate) => candidate.toLowerCase() === lowered);
3147
+ return matches.length === 1 ? matches[0] ?? name : name;
3148
+ }
3037
3149
  async #catalogFacts(probe) {
3038
3150
  probe ??= await this.store.getCatalogProbe();
3039
3151
  const epoch = probe.catalogEpoch;
@@ -3045,10 +3157,23 @@ class MinnowDatabase {
3045
3157
  const domains = /* @__PURE__ */ new Map();
3046
3158
  const columns = /* @__PURE__ */ new Map();
3047
3159
  const types = /* @__PURE__ */ new Map();
3160
+ const integers = /* @__PURE__ */ new Map();
3161
+ const keys = /* @__PURE__ */ new Map();
3048
3162
  for (const table of await this.store.listTables()) {
3049
3163
  const visible = table.columns.filter(({ hidden }) => hidden !== true);
3164
+ const keyColumn = getUniqueKeyColumn(table);
3165
+ const keyNames = keyColumn !== void 0 && !keyColumn.hidden ? [keyColumn.name] : (table.primaryKeyColumnIds ?? []).flatMap((columnId) => {
3166
+ const column = table.columns.find((candidate) => candidate.id === columnId);
3167
+ return column === void 0 ? [] : [column.name];
3168
+ });
3169
+ if (keyNames.length > 0)
3170
+ keys.set(table.name, keyNames);
3050
3171
  columns.set(table.name, visible.map(({ name }) => name));
3051
3172
  types.set(table.name, new Map(visible.map((column) => [column.name, column.type])));
3173
+ const integerColumns = visible.filter((column) => column.integer === true);
3174
+ if (integerColumns.length > 0) {
3175
+ integers.set(table.name, new Set(integerColumns.map(({ name }) => name)));
3176
+ }
3052
3177
  const tableDomains = new Map(table.columns.flatMap((column) => column.sqlDomain === void 0 ? [] : [[column.name, column.sqlDomain]]));
3053
3178
  if (tableDomains.size > 0)
3054
3179
  domains.set(table.name, tableDomains);
@@ -3062,7 +3187,7 @@ class MinnowDatabase {
3062
3187
  existing.push({ table, key });
3063
3188
  }
3064
3189
  }
3065
- const facts = { views, childKeys, domains, columns, types };
3190
+ const facts = { views, childKeys, domains, columns, types, integers, keys };
3066
3191
  this.#catalogCache = { epoch, facts };
3067
3192
  return facts;
3068
3193
  }
@@ -3558,7 +3683,7 @@ class MinnowDatabase {
3558
3683
  if (page.nextCursor === null)
3559
3684
  break;
3560
3685
  cursor = page.nextCursor;
3561
- await yieldToEventLoop();
3686
+ await this.#yieldMaintenance();
3562
3687
  }
3563
3688
  return result;
3564
3689
  }
@@ -3583,7 +3708,7 @@ class MinnowDatabase {
3583
3708
  if (page.nextCursor === null)
3584
3709
  break;
3585
3710
  cursor = page.nextCursor;
3586
- await yieldToEventLoop();
3711
+ await this.#yieldMaintenance();
3587
3712
  }
3588
3713
  return result;
3589
3714
  }
@@ -3793,8 +3918,11 @@ class MinnowDatabase {
3793
3918
  return;
3794
3919
  if (!autoCompactionDueHint(hint))
3795
3920
  return;
3796
- if (hint.visible < (this.#autoCompactionBackoff.get(table.id) ?? 0))
3797
- return;
3921
+ const backoff = this.#autoCompactionBackoff.get(table.id);
3922
+ if (backoff !== void 0) {
3923
+ if (backoff.retryTimer !== void 0 || hint.visible < backoff.minimumSegments)
3924
+ return;
3925
+ }
3798
3926
  if (this.#autoCompactionsInFlight.has(table.id)) {
3799
3927
  this.#autoCompactionsRequested.add(table.id);
3800
3928
  return;
@@ -3802,22 +3930,43 @@ class MinnowDatabase {
3802
3930
  const run = this.#runAutoCompaction(table).then((folded) => {
3803
3931
  if (folded)
3804
3932
  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
- }
3933
+ else
3934
+ this.#backOffAutoCompaction(table.id, hint.visible);
3808
3935
  }).catch(() => {
3809
- this.#autoCompactionBackoff.set(table.id, Math.min(AUTO_COMPACT_MAX_LEVEL_ZERO_SEGMENTS, Math.max(2, hint.visible * 2)));
3936
+ this.#backOffAutoCompaction(table.id, hint.visible);
3810
3937
  }).finally(() => {
3811
3938
  if (this.#autoCompactionsInFlight.get(table.id) === run) {
3812
3939
  this.#autoCompactionsInFlight.delete(table.id);
3813
3940
  }
3814
3941
  if (this.#autoCompactionsRequested.delete(table.id)) {
3815
- void yieldToEventLoop().then(() => this.#checkAutoCompaction(table.id));
3942
+ void this.#yieldMaintenance().then(() => this.#checkAutoCompaction(table.id));
3816
3943
  }
3817
3944
  });
3818
3945
  this.#autoCompactionsInFlight.set(table.id, run);
3819
3946
  void run;
3820
3947
  }
3948
+ #backOffAutoCompaction(tableId, visible) {
3949
+ if (this.#closed)
3950
+ return;
3951
+ const previous = this.#autoCompactionBackoff.get(tableId);
3952
+ if (previous?.retryTimer !== void 0)
3953
+ clearTimeout(previous.retryTimer);
3954
+ const failures = (previous?.failures ?? 0) + 1;
3955
+ const delay = Math.min(AUTO_COMPACT_RETRY_MAX_MS, AUTO_COMPACT_RETRY_MIN_MS * 2 ** Math.min(16, failures - 1));
3956
+ const timer = setTimeout(() => {
3957
+ const current = this.#autoCompactionBackoff.get(tableId);
3958
+ if (current?.retryTimer === timer) {
3959
+ this.#autoCompactionBackoff.set(tableId, { ...current, retryTimer: void 0 });
3960
+ }
3961
+ void this.#checkAutoCompaction(tableId);
3962
+ }, delay);
3963
+ timer.unref?.();
3964
+ this.#autoCompactionBackoff.set(tableId, {
3965
+ minimumSegments: Math.min(AUTO_COMPACT_MAX_LEVEL_ZERO_SEGMENTS, Math.max(2, visible * 2)),
3966
+ failures,
3967
+ retryTimer: timer
3968
+ });
3969
+ }
3821
3970
  async #assertCompactionCapacity(table, transaction) {
3822
3971
  transaction?.limitLevelZeroSegments(table.id, MAX_LEVEL_ZERO_SEGMENTS);
3823
3972
  if (!this.#autoCompact)
@@ -3834,6 +3983,7 @@ class MinnowDatabase {
3834
3983
  }
3835
3984
  if (levelZero < MAX_LEVEL_ZERO_SEGMENTS)
3836
3985
  return;
3986
+ await this.#publishPendingCompactions();
3837
3987
  await this.compactTableStep(table.name, {
3838
3988
  maxBlocks: AUTO_COMPACT_STEP_BLOCKS,
3839
3989
  maxLevel0Segments: AUTO_COMPACT_MAX_LEVEL_ZERO_SEGMENTS
@@ -3859,14 +4009,14 @@ class MinnowDatabase {
3859
4009
  while (progress.result === null) {
3860
4010
  if (progress.jobId === null)
3861
4011
  throw new Error("Compaction progress lost its job ID");
3862
- await yieldToEventLoop();
4012
+ await this.#yieldMaintenance();
3863
4013
  progress = await this.resumeCompactionJob(progress.jobId, options);
3864
4014
  }
3865
4015
  if (!progress.result.compacted)
3866
4016
  return folded;
3867
4017
  folded = true;
3868
4018
  this.#maybeScheduleAutoCollection();
3869
- await yieldToEventLoop();
4019
+ await this.#yieldMaintenance();
3870
4020
  const current = await this.store.getTable(table.id);
3871
4021
  if (current === void 0 || !autoCompactionDue(await this.#currentVisibleSegments(current))) {
3872
4022
  return folded;
@@ -3887,6 +4037,9 @@ class MinnowDatabase {
3887
4037
  return segments;
3888
4038
  }
3889
4039
  #afterCommit(manifest, transaction) {
4040
+ const signal = this.#commitSignal;
4041
+ this.#commitSignal = void 0;
4042
+ signal?.resolve();
3890
4043
  if (this.#closed)
3891
4044
  return;
3892
4045
  const contribution = transaction?.committedCatalogContribution;
@@ -4265,6 +4418,7 @@ class MinnowDatabase {
4265
4418
  const statementNow = this.#now();
4266
4419
  const materialized = await this.#materializeInsertExpressions({ ...compiled, rows: derivedRows }, statementNow);
4267
4420
  const input = insertStatementBatch(materialized);
4421
+ coerceSqlWriteBatch(target, input);
4268
4422
  const filled = await fillColumnDefaults(target, input, (sql) => this.#evaluateDefaultExpression(sql, statementNow), derivedRows.length);
4269
4423
  normalizeDomainBatch(target, filled.batch);
4270
4424
  fillStoredGeneratedColumns(target, filled.batch, derivedRows.length, filled.generated);
@@ -4293,6 +4447,9 @@ class MinnowDatabase {
4293
4447
  const bound = bindStatementParameters(compiled, params);
4294
4448
  if (bound.kind !== "update" && bound.kind !== "delete")
4295
4449
  continue;
4450
+ if (bound.from !== void 0) {
4451
+ throw new TypeError(`Trigger body ${bound.kind.toUpperCase()}s cannot join other sources`);
4452
+ }
4296
4453
  const referenced = /* @__PURE__ */ new Set([keyColumn.name]);
4297
4454
  if (bound.kind === "update") {
4298
4455
  for (const assignment of bound.assignments) {
@@ -4417,6 +4574,7 @@ class MinnowDatabase {
4417
4574
  return this.#openWriteScope((session) => action(session));
4418
4575
  }
4419
4576
  async #openWriteScope(action, options = {}) {
4577
+ await this.#publishPendingCompactions();
4420
4578
  await this.#assistAutomaticCollection();
4421
4579
  const transaction = await this.#transactions.beginDeferred({
4422
4580
  durableSnapshot: options.durableSnapshot ?? true
@@ -4458,6 +4616,9 @@ class MinnowDatabase {
4458
4616
  }));
4459
4617
  }
4460
4618
  const statement = bindStatementParameters(compiled, params);
4619
+ if (statement.kind === "set" || statement.kind === "show") {
4620
+ return externalizeExecuteResult(await this.runStatement(statement));
4621
+ }
4461
4622
  if (!isTransactionalStatement(statement)) {
4462
4623
  throw new TypeError(`${statement.kind.toUpperCase().replace("-", " ")} is not allowed inside a write scope`);
4463
4624
  }
@@ -5371,15 +5532,19 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
5371
5532
  })
5372
5533
  };
5373
5534
  }
5535
+ if (statement.kind === "set" || statement.kind === "show") {
5536
+ return externalizeExecuteResult(await this.runStatement(statement));
5537
+ }
5374
5538
  const open = this.#openTransaction;
5375
5539
  if (open !== void 0) {
5376
- const boundStatement = bindStatementParameters(statement, params);
5377
- if (!isTransactionalStatement(boundStatement)) {
5378
- throw new TypeError(`${boundStatement.kind.toUpperCase().replace("-", " ")} is not allowed inside a transaction`);
5540
+ const boundStatement2 = bindStatementParameters(statement, params);
5541
+ if (!isTransactionalStatement(boundStatement2)) {
5542
+ throw new TypeError(`${boundStatement2.kind.toUpperCase().replace("-", " ")} is not allowed inside a transaction`);
5379
5543
  }
5380
- return externalizeExecuteResult(await this.#projectReturningItems(boundStatement, await this.#duringTransaction(open, () => open.session.executeStatement(boundStatement))));
5544
+ return externalizeExecuteResult(await this.#projectReturningItems(boundStatement2, await this.#duringTransaction(open, () => open.session.executeStatement(boundStatement2))));
5381
5545
  }
5382
- return externalizeExecuteResult(await this.#projectReturningItems(statement, await this.runStatement(bindStatementParameters(statement, params))));
5546
+ const boundStatement = bindStatementParameters(statement, params);
5547
+ return externalizeExecuteResult(await this.#projectReturningItems(boundStatement, await this.runStatement(boundStatement)));
5383
5548
  }
5384
5549
  async #assertForeignKeysPresent(table, rowsByColumn, read, transaction) {
5385
5550
  for (const key of table.foreignKeys ?? []) {
@@ -5691,12 +5856,14 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
5691
5856
  if (keyColumn === void 0 || conflictColumns.length !== addressColumns.length || conflictColumns.some((name, index) => name !== addressColumns[index])) {
5692
5857
  throw new TypeError(`ON CONFLICT targets the table's primary or unique key columns: ${addressColumns.join(", ") || "(none)"}`);
5693
5858
  }
5859
+ statement = foldInsertColumns(table, statement);
5694
5860
  for (const name of statement.columns) {
5695
5861
  if (!table.columns.some((column) => column.name === name)) {
5696
5862
  throw new TypeError(`INSERT column does not exist: ${name}`);
5697
5863
  }
5698
5864
  }
5699
5865
  const proposedBatch = insertStatementBatch(statement);
5866
+ coerceSqlWriteBatch(table, proposedBatch);
5700
5867
  const statementNow = this.#now();
5701
5868
  const filledProposed = await fillColumnDefaults(table, proposedBatch, (sql) => this.#evaluateDefaultExpression(sql, statementNow), statement.rows.length);
5702
5869
  normalizeDomainBatch(table, filledProposed.batch);
@@ -5892,6 +6059,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
5892
6059
  throw new TypeError(`ON CONFLICT targets the table's primary or unique key columns: ${addressColumns.join(", ") || "(none)"}`);
5893
6060
  }
5894
6061
  const proposed = insertStatementBatch(statement);
6062
+ coerceSqlWriteBatch(table, proposed);
5895
6063
  const statementNow = this.#now();
5896
6064
  const filled = await fillColumnDefaults(table, proposed, (sql) => this.#evaluateDefaultExpression(sql, statementNow), statement.rows.length);
5897
6065
  normalizeDomainBatch(table, filled.batch);
@@ -6064,6 +6232,38 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
6064
6232
  if (statement.kind === "select") {
6065
6233
  return { kind: "rows", result: await this.query(statement.sql) };
6066
6234
  }
6235
+ if (statement.kind === "set") {
6236
+ return { kind: "set", action: statement.action, name: statement.name };
6237
+ }
6238
+ if (statement.kind === "show") {
6239
+ const settings = {
6240
+ search_path: "public",
6241
+ server_version: "16.0",
6242
+ server_version_num: "160000",
6243
+ timezone: "UTC",
6244
+ time_zone: "UTC",
6245
+ transaction_isolation: "repeatable read",
6246
+ default_transaction_isolation: "repeatable read",
6247
+ client_encoding: "UTF8",
6248
+ server_encoding: "UTF8",
6249
+ standard_conforming_strings: "on",
6250
+ integer_datetimes: "on",
6251
+ datestyle: "ISO, MDY",
6252
+ intervalstyle: "postgres",
6253
+ application_name: "",
6254
+ is_superuser: "on",
6255
+ max_identifier_length: "63"
6256
+ };
6257
+ const name = statement.name.toLowerCase();
6258
+ const value = settings[name];
6259
+ if (value === void 0) {
6260
+ throw new TypeError(`unrecognized configuration parameter "${statement.name}"`);
6261
+ }
6262
+ return {
6263
+ kind: "rows",
6264
+ result: await this.query(`SELECT $1 AS ${quoteIdentifier(name)}`, { params: [value] })
6265
+ };
6266
+ }
6067
6267
  if (statement.kind === "create-enum") {
6068
6268
  const values = validateEnumValues(statement.values, statement.name);
6069
6269
  const storageName = `${ENUM_TYPE_PREFIX}${statement.name}`;
@@ -6495,12 +6695,14 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
6495
6695
  throw new TypeError(`ON CONFLICT targets the table's primary or unique key columns: ${addressColumns.join(", ") || "(none)"}`);
6496
6696
  }
6497
6697
  }
6498
- for (const name of statement.columns) {
6698
+ const spelledColumns = foldInsertColumns(table2, statement).columns;
6699
+ for (const name of spelledColumns) {
6499
6700
  if (!table2.columns.some((column) => column.name === name)) {
6500
6701
  throw new TypeError(`INSERT column does not exist: ${name}`);
6501
6702
  }
6502
6703
  }
6503
- const input = insertStatementBatch(statement);
6704
+ const input = insertStatementBatch({ ...statement, columns: spelledColumns });
6705
+ coerceSqlWriteBatch(table2, input);
6504
6706
  normalizeDomainBatch(table2, input);
6505
6707
  let returningColumns2;
6506
6708
  if (options.returning !== void 0) {
@@ -6514,7 +6716,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
6514
6716
  const writer = options.writer;
6515
6717
  const stagedKeyColumn = writer === void 0 ? void 0 : getUniqueKeyColumn(table2);
6516
6718
  if (writer !== void 0 && !viaUpsert && stagedKeyColumn !== void 0) {
6517
- const assignedGenerated = table2.columns.find((column) => column.generatedValue !== void 0 && statement.columns.includes(column.name));
6719
+ const assignedGenerated = table2.columns.find((column) => column.generatedValue !== void 0 && spelledColumns.includes(column.name));
6518
6720
  if (assignedGenerated !== void 0) {
6519
6721
  throw new TypeError(`Generated column cannot be assigned: ${assignedGenerated.name}`);
6520
6722
  }
@@ -6530,7 +6732,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
6530
6732
  ...returningColumns2 === void 0 ? {} : {
6531
6733
  ...returningExecuteFields(table2, returningColumns2, statement.rows.map((row, rowIndex) => Object.fromEntries(returningColumns2.map((name) => {
6532
6734
  const column = table2.columns.find((candidate) => candidate.name === name);
6533
- const position = statement.columns.indexOf(name);
6735
+ const position = spelledColumns.indexOf(name);
6534
6736
  const inserted = position < 0 ? void 0 : row[position];
6535
6737
  const value = generated[name]?.[rowIndex] ?? input.columns[name]?.[rowIndex] ?? (inserted === void 0 || isDefaultInsertValue(inserted) ? null : boundInsertValue(inserted));
6536
6738
  return [name, executionSqlValueFromInput(column, value)];
@@ -6545,8 +6747,9 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
6545
6747
  }
6546
6748
  const returningColumns = options.returning === void 0 ? void 0 : options.returning === "*" ? visibleTableColumns(table).map(({ name }) => name) : [...options.returning];
6547
6749
  const updateAssignments = statement.kind !== "update" ? [] : await (async () => {
6750
+ const spelled = foldAssignmentColumns(table, statement.assignments);
6548
6751
  if (!table.columns.some((column) => column.sqlDomain !== void 0)) {
6549
- return statement.assignments;
6752
+ return spelled;
6550
6753
  }
6551
6754
  const { domains } = await this.#catalogFacts();
6552
6755
  if (!domains.has(table.name))
@@ -6555,7 +6758,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
6555
6758
  sql: `UPDATE ${table.name}`,
6556
6759
  base: { table: table.name, alias: table.name },
6557
6760
  joins: [],
6558
- select: statement.assignments.map((assignment) => ({
6761
+ select: spelled.map((assignment) => ({
6559
6762
  expression: assignment.expression,
6560
6763
  alias: assignment.column
6561
6764
  })),
@@ -6564,20 +6767,25 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
6564
6767
  having: [],
6565
6768
  orderBy: []
6566
6769
  }, domains);
6567
- return statement.assignments.map((assignment, index) => ({
6770
+ return spelled.map((assignment, index) => ({
6568
6771
  ...assignment,
6569
6772
  expression: normalized.select[index]?.expression ?? assignment.expression
6570
6773
  }));
6571
6774
  })();
6572
6775
  const referenced = /* @__PURE__ */ new Set([keyColumn.name, ...returningColumns ?? []]);
6573
6776
  const assignmentAlias = (column) => `\0set:${column}`;
6777
+ const targetAlias = statement.alias ?? table.name;
6778
+ const from = statement.from;
6574
6779
  const plan = optimizePlan({
6575
6780
  sql: `(${statement.kind})`,
6576
- base: { table: table.name, alias: statement.alias ?? table.name },
6577
- joins: [],
6781
+ base: { table: table.name, alias: targetAlias },
6782
+ joins: from === void 0 ? [] : [crossJoinPlan(from.base), ...from.joins],
6578
6783
  select: [
6579
6784
  ...[...referenced].map((name) => ({
6580
- expression: { kind: "column", reference: name },
6785
+ expression: {
6786
+ kind: "column",
6787
+ reference: from === void 0 ? name : `${targetAlias}.${name}`
6788
+ },
6581
6789
  alias: name
6582
6790
  })),
6583
6791
  ...statement.kind === "update" ? updateAssignments.map((assignment) => ({
@@ -6585,12 +6793,12 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
6585
6793
  alias: assignmentAlias(assignment.column)
6586
6794
  })) : []
6587
6795
  ],
6588
- predicates: statement.predicates,
6796
+ predicates: from === void 0 ? statement.predicates : [...statement.predicates, ...from.predicates],
6589
6797
  groupBy: [],
6590
6798
  having: [],
6591
6799
  orderBy: []
6592
6800
  });
6593
- if (statement.kind === "delete" && returningColumns === void 0) {
6801
+ if (statement.kind === "delete" && returningColumns === void 0 && from === void 0) {
6594
6802
  const internalWriter = options.writer;
6595
6803
  if (internalWriter.queryFirstColumn !== void 0) {
6596
6804
  const selectedKeys = await internalWriter.queryFirstColumn(plan);
@@ -6622,6 +6830,16 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
6622
6830
  } else {
6623
6831
  rows = (await this.#queryCompiled(plan)).rows;
6624
6832
  }
6833
+ if (from !== void 0) {
6834
+ const seen = /* @__PURE__ */ new Set();
6835
+ rows = rows.filter((row) => {
6836
+ const key = JSON.stringify(row[keyColumn.name] ?? null);
6837
+ if (seen.has(key))
6838
+ return false;
6839
+ seen.add(key);
6840
+ return true;
6841
+ });
6842
+ }
6625
6843
  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);
6626
6844
  if (keys.some((key) => key === null)) {
6627
6845
  throw new TypeError(`Unique key values must not be null: ${keyColumn.name}`);
@@ -6665,8 +6883,9 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
6665
6883
  if (typeof value === "number" && !Number.isFinite(value)) {
6666
6884
  throw new TypeError(`UPDATE assignment produced a non-finite number: ${assignment.column}`);
6667
6885
  }
6668
- executionValues?.push(value);
6669
- return column.type === "string" && column.sqlDomain === void 0 ? storedSqlValueFromExecution(column, value) : value;
6886
+ const stored = storedSqlValueFromExecution(column, value);
6887
+ executionValues?.push(column.type === "string" && column.sqlDomain === void 0 ? value : stored);
6888
+ return stored;
6670
6889
  });
6671
6890
  if (executionValues !== void 0 && returnedChanges !== void 0) {
6672
6891
  returnedChanges[assignment.column] = executionValues;
@@ -8050,7 +8269,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
8050
8269
  return active;
8051
8270
  cursor = page.nextCursor;
8052
8271
  if (cursor !== null)
8053
- await yieldToEventLoop();
8272
+ await this.#yieldMaintenance();
8054
8273
  } while (cursor !== null);
8055
8274
  return void 0;
8056
8275
  }
@@ -8088,11 +8307,15 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
8088
8307
  }
8089
8308
  cursor = page.nextCursor;
8090
8309
  if (cursor !== null)
8091
- await yieldToEventLoop();
8310
+ await this.#yieldMaintenance();
8092
8311
  } while (cursor !== null);
8093
8312
  }
8094
8313
  async collectGarbage(options = {}) {
8095
8314
  const maxItems = boundedMaintenanceBatchItems(options.maxItemsPerStep ?? 64, "Garbage collection items per step");
8315
+ let reconciliationCursor = null;
8316
+ do {
8317
+ reconciliationCursor = (await this.#reconcileCompactionPage(reconciliationCursor)).nextCursor;
8318
+ } while (reconciliationCursor !== null);
8096
8319
  let progress = await this.collectGarbageStep({
8097
8320
  maxItems,
8098
8321
  ...options.maxPlanningItems === void 0 ? {} : { maxPlanningItems: options.maxPlanningItems },
@@ -8129,7 +8352,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
8129
8352
  return active;
8130
8353
  cursor = page.nextCursor;
8131
8354
  if (cursor !== null)
8132
- await yieldToEventLoop();
8355
+ await this.#yieldMaintenance();
8133
8356
  } while (cursor !== null);
8134
8357
  return void 0;
8135
8358
  }
@@ -8170,14 +8393,14 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
8170
8393
  this.#autoCollectionRequested = false;
8171
8394
  this.#commitsSinceCollection = 0;
8172
8395
  this.#lastCollectionAt = dateMilliseconds(this.#now());
8173
- const run = this.#runAutoCollection().then((moreWork) => {
8396
+ const run = this.#runAutoCollection().then(({ moreWork, reclaimed }) => {
8174
8397
  this.#autoCollectionConsecutiveFailures = 0;
8175
8398
  this.#autoCollectionLastError = void 0;
8176
8399
  this.#lastCollectionCompletedAt = dateMilliseconds(this.#now());
8177
8400
  this.#autoCollectionRetryAt = void 0;
8178
8401
  if (moreWork)
8179
8402
  this.#autoCollectionRequested = true;
8180
- else
8403
+ if (reclaimed || !moreWork)
8181
8404
  this.#autoCollectionDebtCommits = 0;
8182
8405
  }).catch((error) => {
8183
8406
  this.#autoCollectionConsecutiveFailures += 1;
@@ -8193,7 +8416,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
8193
8416
  this.#autoCollectionTask = void 0;
8194
8417
  this.#autoCollectionInFlight = false;
8195
8418
  if (this.#autoCollectionRequested) {
8196
- void yieldToEventLoop().then(() => {
8419
+ void this.#yieldMaintenance().then(() => {
8197
8420
  if (this.#closed) {
8198
8421
  this.#autoCollectionRequested = false;
8199
8422
  return;
@@ -8238,6 +8461,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
8238
8461
  async #runAutoCollection() {
8239
8462
  const reconciliationMoreWork = await this.#reconcileCompactionMaintenance();
8240
8463
  let moreWork = false;
8464
+ let reclaimed = false;
8241
8465
  for (let pass = 0; pass < AUTO_COLLECT_MAX_PASSES; pass += 1) {
8242
8466
  if (this.#closed)
8243
8467
  break;
@@ -8248,7 +8472,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
8248
8472
  retainRecentVersions: AUTO_COLLECT_RETAINED_VERSIONS
8249
8473
  }, AUTO_COLLECT_RETAINED_VERSION_MS);
8250
8474
  while (progress.result === null) {
8251
- await yieldToEventLoop();
8475
+ await this.#yieldMaintenance();
8252
8476
  progress = await this.resumeGarbageCollectionJob(progress.jobId, {
8253
8477
  maxItems: AUTO_COLLECT_STEP_ITEMS
8254
8478
  });
@@ -8258,14 +8482,19 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
8258
8482
  if (!moreWork) {
8259
8483
  break;
8260
8484
  }
8261
- await yieldToEventLoop();
8485
+ reclaimed = true;
8486
+ await this.#yieldMaintenance();
8262
8487
  }
8263
8488
  await this.#pruneFinishedJobRecords();
8264
- return moreWork || reconciliationMoreWork;
8489
+ return { moreWork: moreWork || reconciliationMoreWork, reclaimed };
8265
8490
  }
8266
8491
  async #reconcileCompactionMaintenance() {
8267
- const page = await this.store.listCompactionJobPage(this.#compactionReconciliationCursor, AUTO_COMPACTION_RECONCILE_RECORDS);
8268
- this.#compactionReconciliationCursor = page.nextCursor;
8492
+ const { nextCursor, moreWork } = await this.#reconcileCompactionPage(this.#compactionReconciliationCursor);
8493
+ this.#compactionReconciliationCursor = nextCursor;
8494
+ return moreWork;
8495
+ }
8496
+ async #reconcileCompactionPage(cursor) {
8497
+ const page = await this.store.listCompactionJobPage(cursor, AUTO_COMPACTION_RECONCILE_RECORDS);
8269
8498
  let moreWork = page.nextCursor !== null;
8270
8499
  const cutoff = dateIsoString(this.#now());
8271
8500
  for (const listed of page.records) {
@@ -8309,14 +8538,22 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
8309
8538
  this.#maybeScheduleAutoCompaction(table, await this.#currentVisibleSegments(table));
8310
8539
  continue;
8311
8540
  }
8312
- const progress = await this.resumeCompactionJob(job.id, { maxBlocks: 1 });
8313
- if (progress.result !== null) {
8314
- job = await this.store.getCompactionJob(job.id) ?? job;
8315
- if (!isActiveCompactionState(job.state))
8541
+ try {
8542
+ const progress = await this.resumeCompactionJob(job.id, { maxBlocks: 1 });
8543
+ if (progress.result !== null) {
8544
+ job = await this.store.getCompactionJob(job.id) ?? job;
8545
+ if (!isActiveCompactionState(job.state))
8546
+ continue;
8547
+ }
8548
+ } catch (error) {
8549
+ if (error instanceof CompactionJobCancelledError)
8316
8550
  continue;
8551
+ if (this.#closed)
8552
+ throw error;
8553
+ this.#maybeScheduleAutoCompaction(table, await this.#currentVisibleSegments(table));
8317
8554
  }
8318
8555
  }
8319
- return moreWork;
8556
+ return { nextCursor: page.nextCursor, moreWork };
8320
8557
  }
8321
8558
  async #pruneFinishedJobRecords() {
8322
8559
  const newestFirst = (left, right) => right.updatedAt.localeCompare(left.updatedAt) || right.id.localeCompare(left.id);
@@ -8337,7 +8574,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
8337
8574
  }
8338
8575
  cursor = page.nextCursor;
8339
8576
  if (cursor !== null)
8340
- await yieldToEventLoop();
8577
+ await this.#yieldMaintenance();
8341
8578
  } while (cursor !== null);
8342
8579
  const retainedCompactionIds = new Set(newestCompactions.map((job) => job.id));
8343
8580
  cursor = null;
@@ -8387,7 +8624,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
8387
8624
  }
8388
8625
  cursor = page.nextCursor;
8389
8626
  if (cursor !== null)
8390
- await yieldToEventLoop();
8627
+ await this.#yieldMaintenance();
8391
8628
  } while (cursor !== null);
8392
8629
  const newestCollections = [];
8393
8630
  cursor = null;
@@ -8399,7 +8636,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
8399
8636
  }
8400
8637
  cursor = page.nextCursor;
8401
8638
  if (cursor !== null)
8402
- await yieldToEventLoop();
8639
+ await this.#yieldMaintenance();
8403
8640
  } while (cursor !== null);
8404
8641
  const retainedCollectionIds = new Set(newestCollections.map((job) => job.id));
8405
8642
  cursor = null;
@@ -8412,7 +8649,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
8412
8649
  }
8413
8650
  cursor = page.nextCursor;
8414
8651
  if (cursor !== null)
8415
- await yieldToEventLoop();
8652
+ await this.#yieldMaintenance();
8416
8653
  } while (cursor !== null);
8417
8654
  }
8418
8655
  async listGarbageCollectionJobs() {
@@ -8479,7 +8716,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
8479
8716
  }
8480
8717
  cursor = page.nextCursor;
8481
8718
  if (cursor !== null)
8482
- await yieldToEventLoop();
8719
+ await this.#yieldMaintenance();
8483
8720
  } while (cursor !== null);
8484
8721
  return newest?.discovery === void 0 ? void 0 : { jobId: newest.id, discovery: newest.discovery };
8485
8722
  }
@@ -9137,7 +9374,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
9137
9374
  }
9138
9375
  cursor = page.nextCursor;
9139
9376
  if (cursor !== null)
9140
- await yieldToEventLoop();
9377
+ await this.#yieldMaintenance();
9141
9378
  } while (cursor !== null);
9142
9379
  return largest;
9143
9380
  }
@@ -9716,7 +9953,17 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
9716
9953
  if (job.state === "published") {
9717
9954
  return this.#publishedCompactionProgress(table, job);
9718
9955
  }
9719
- const linkedTransaction = job.transactionId === null ? void 0 : await this.store.getTransaction(job.transactionId);
9956
+ let linkedTransaction = job.transactionId === null ? void 0 : await this.store.getTransaction(job.transactionId);
9957
+ if (job.transactionId !== null && linkedTransaction?.status === "active" && Date.parse(linkedTransaction.expiresAt) <= dateMilliseconds(this.#now())) {
9958
+ const cutoff = dateIsoString(this.#now());
9959
+ await this.store.abortTransactionIfExpired({
9960
+ transactionId: linkedTransaction.id,
9961
+ expectedOwnerId: linkedTransaction.ownerId,
9962
+ expiresAtCutoff: cutoff,
9963
+ updatedAt: cutoff
9964
+ });
9965
+ linkedTransaction = await this.store.getTransaction(job.transactionId);
9966
+ }
9720
9967
  if (linkedTransaction?.status === "committed" && linkedTransaction.committedVersion !== null) {
9721
9968
  job = await this.#markCompactionPublished(job, linkedTransaction.committedVersion);
9722
9969
  const recoveredManifest = await this.store.getManifest(linkedTransaction.committedVersion);
@@ -9881,18 +10128,20 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
9881
10128
  }
9882
10129
  transaction.setCompactionIntent(job.id, job.sourceBlockIds);
9883
10130
  transaction.markLogicallyUnchanged();
9884
- let manifest;
9885
- for (; ; ) {
9886
- let publicationConflict;
10131
+ let publicationConflict;
10132
+ const publish = async () => {
9887
10133
  try {
9888
- manifest = await transaction.commit();
9889
- this.#afterCompactionCommit(manifest, table.id);
9890
- break;
10134
+ const manifest2 = await transaction.commit();
10135
+ this.#afterCompactionCommit(manifest2, table.id);
10136
+ return manifest2;
9891
10137
  } catch (error) {
9892
10138
  if (!(error instanceof WriteConflictError))
9893
10139
  throw error;
9894
10140
  publicationConflict = error;
10141
+ return void 0;
9895
10142
  }
10143
+ };
10144
+ const rebaseOverConcurrentCommits = async () => {
9896
10145
  const currentVersion = await this.store.getCurrentManifestVersion();
9897
10146
  const sourcePresence = await this.#manifestBlockPresence(currentVersion, job.sourceBlockIds);
9898
10147
  if (sourcePresence.some((present) => !present)) {
@@ -9919,7 +10168,24 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
9919
10168
  }
9920
10169
  transaction.setCompactionIntent(job.id, job.sourceBlockIds);
9921
10170
  transaction.markLogicallyUnchanged();
9922
- await yieldToEventLoop();
10171
+ };
10172
+ let manifest = await publish();
10173
+ if (manifest === void 0) {
10174
+ manifest = await this.#withCompactionPublicationSlot(async () => {
10175
+ for (let conflicts = 1; conflicts <= MAX_COMPACTION_PUBLICATION_CONFLICTS; conflicts += 1) {
10176
+ await rebaseOverConcurrentCommits();
10177
+ const published = await publish();
10178
+ if (published !== void 0)
10179
+ return published;
10180
+ }
10181
+ return void 0;
10182
+ });
10183
+ if (manifest === void 0) {
10184
+ if (transaction.status === "active")
10185
+ await transaction.abort();
10186
+ job = await this.#abortCompactionJob(job, `Compaction publication lost ${String(MAX_COMPACTION_PUBLICATION_CONFLICTS + 1)} manifest races`);
10187
+ throw new Error(job.error, { cause: publicationConflict });
10188
+ }
9923
10189
  }
9924
10190
  job = await this.#markCompactionPublished(job, manifest.version);
9925
10191
  return compactionProgress(table.name, job, this.#compactionResult(table, job, manifest.version));
@@ -10079,7 +10345,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
10079
10345
  }
10080
10346
  segmentCursor = page.nextCursor;
10081
10347
  if (segmentCursor !== null)
10082
- await yieldToEventLoop();
10348
+ await this.#yieldMaintenance();
10083
10349
  } while (segmentCursor !== null);
10084
10350
  if (job.outputPartitionOrdinal === void 0 && job.targetLevel === 1 && plan.kind !== "copy-v1" && plan.partitions !== void 0) {
10085
10351
  const table = await this.store.getTable(job.tableId);
@@ -12633,7 +12899,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
12633
12899
  }
12634
12900
  async #findTable(name) {
12635
12901
  validateName(name, "Table");
12636
- const table = await this.store.getTableByName(name);
12902
+ const table = await this.store.getTableByName(name) ?? await this.store.getTableByName(await this.#foldTableName(name));
12637
12903
  if (table === void 0)
12638
12904
  throw new UnknownTableError(name);
12639
12905
  if (table.view !== void 0)
@@ -13328,7 +13594,26 @@ function postingFrequencyTotal(postings) {
13328
13594
  }
13329
13595
  const secondaryNumberBits = new DataView(new ArrayBuffer(8));
13330
13596
  function storedSqlValueFromExecution(column, value) {
13331
- return column?.type === "string" && column.sqlDomain === void 0 && typeof value === "string" ? externalSqlTextValue(value) : value;
13597
+ if (column?.type === "string" && column.sqlDomain === void 0 && typeof value === "string") {
13598
+ return externalSqlTextValue(value);
13599
+ }
13600
+ return coerceSqlWriteValue(column, value);
13601
+ }
13602
+ function coerceSqlWriteValue(column, value) {
13603
+ if (column === void 0 || column.sqlDomain !== void 0 || column.type === "string" || typeof value !== "string" || isSqlDomainValue(value)) {
13604
+ return value;
13605
+ }
13606
+ return readUntypedText(column.type, value);
13607
+ }
13608
+ function coerceSqlWriteBatch(table, input) {
13609
+ for (const column of table.columns) {
13610
+ if (column.type === "string" || column.sqlDomain !== void 0)
13611
+ continue;
13612
+ const values = input.columns[column.name];
13613
+ if (!values?.some((value) => typeof value === "string"))
13614
+ continue;
13615
+ input.columns[column.name] = values.map((value) => coerceSqlWriteValue(column, value));
13616
+ }
13332
13617
  }
13333
13618
  function executionSqlValueFromStorage(column, value) {
13334
13619
  return column?.type === "string" && column.sqlDomain === void 0 && typeof value === "string" ? protectedSqlTextValue(value) : value;
@@ -13815,6 +14100,28 @@ function compositeKeyValue(columns, values) {
13815
14100
  }
13816
14101
  return columns.map((column, index) => secondaryTupleComponent(column.type, values[index] ?? null)).join("");
13817
14102
  }
14103
+ function foldColumnName(table, name) {
14104
+ const columns = visibleTableColumns(table);
14105
+ if (columns.some((column) => column.name === name))
14106
+ return name;
14107
+ const lowered = name.toLowerCase();
14108
+ const matches = columns.filter((column) => column.name.toLowerCase() === lowered);
14109
+ return matches.length === 1 ? matches[0]?.name ?? name : name;
14110
+ }
14111
+ function foldInsertColumns(table, statement) {
14112
+ const columns = statement.columns.map((name) => foldColumnName(table, name));
14113
+ return columns.some((name, index) => name !== statement.columns[index]) ? { ...statement, columns } : statement;
14114
+ }
14115
+ function foldAssignmentColumns(table, assignments) {
14116
+ const folded = assignments.map((assignment) => {
14117
+ const column = foldColumnName(table, assignment.column);
14118
+ return column === assignment.column ? assignment : { ...assignment, column };
14119
+ });
14120
+ return folded.some((assignment, index) => assignment !== assignments[index]) ? folded : assignments;
14121
+ }
14122
+ function quoteIdentifier(name) {
14123
+ return `"${name.replace(/"/g, '""')}"`;
14124
+ }
13818
14125
  function normalizeDomainBatch(table, input) {
13819
14126
  for (const column of table.columns) {
13820
14127
  const values = input.columns[column.name];
@@ -13899,23 +14206,8 @@ function normalizeDomainUpdate(table, input) {
13899
14206
  function normalizePlanDomainLiterals(input, domains, types = /* @__PURE__ */ new Map()) {
13900
14207
  const pass = { probing: true, needed: false };
13901
14208
  const coerceText = (type, text) => {
13902
- if (type === "datetime")
13903
- return parseSqlTimestampText(text);
13904
- if (type === "number") {
13905
- const trimmed = text.trim();
13906
- if (trimmed === "")
13907
- return void 0;
13908
- const parsed = Number(trimmed);
13909
- return Number.isFinite(parsed) ? parsed : void 0;
13910
- }
13911
- if (type === "boolean") {
13912
- const lowered = text.trim().toLowerCase();
13913
- if (lowered === "t" || lowered === "true" || lowered === "1")
13914
- return true;
13915
- if (lowered === "f" || lowered === "false" || lowered === "0")
13916
- return false;
13917
- }
13918
- return void 0;
14209
+ const read = readUntypedText(type, text);
14210
+ return read === text ? void 0 : read;
13919
14211
  };
13920
14212
  const rewriteBlock = (block) => {
13921
14213
  if (pass.probing && pass.needed)
@@ -14493,9 +14785,15 @@ function installStreamedWindow(vector, resident, windowStart, windowRows, memory
14493
14785
  mutable.window = { start: windowStart, length: windowRows };
14494
14786
  }
14495
14787
  function derivedColumnarTable(name, result, schema) {
14496
- const columns = new Map(schema.map(({ name: columnName, type }) => [
14788
+ const columns = new Map(schema.map(({ name: columnName, type, sqlDomain }) => [
14497
14789
  columnName,
14498
- { type, values: result.rows.map((row) => row[columnName] ?? null) }
14790
+ {
14791
+ type,
14792
+ values: sqlDomain?.kind === "numeric" ? result.rows.map((row) => {
14793
+ const value = row[columnName] ?? null;
14794
+ return typeof value === "number" ? exactNumericValue(value) : value;
14795
+ }) : result.rows.map((row) => row[columnName] ?? null)
14796
+ }
14499
14797
  ]));
14500
14798
  return createColumnarTable(name, columns);
14501
14799
  }