@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.
- package/dist/engine/client.d.ts +2 -2
- package/dist/engine/client.js +7 -2
- package/dist/engine/database.d.ts +11 -1
- package/dist/engine/database.js +642 -175
- package/dist/engine/optimizer.js +48 -26
- package/dist/engine/query.d.ts +98 -0
- package/dist/engine/query.js +1452 -165
- package/dist/engine/sql-domains.d.ts +11 -0
- package/dist/engine/sql-domains.js +56 -0
- package/dist/engine/sql-functions.js +129 -3
- package/dist/engine/sql-json.d.ts +2 -0
- package/dist/engine/sql-json.js +4 -0
- package/dist/engine/sql-semantics.d.ts +9 -1
- package/dist/engine/sql-semantics.js +19 -3
- package/dist/engine/vector.js +29 -13
- package/dist/engine/worker-server.js +4 -1
- package/dist/plan/model.d.ts +14 -2
- package/dist/storage/indexeddb.js +13 -8
- package/dist/storage/memory.d.ts +2 -1
- package/dist/storage/memory.js +29 -4
- package/dist/storage/toolkit/record-core.js +11 -6
- package/dist/storage/types.d.ts +11 -0
- package/dist/testing/sqllogictest.js +3 -1
- package/package.json +1 -1
- package/postgres-feature-profile.json +9 -4
- package/sql-feature-matrix.json +541 -4
package/dist/engine/database.js
CHANGED
|
@@ -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,
|
|
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 {
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
3797
|
-
|
|
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.#
|
|
3807
|
-
}
|
|
3923
|
+
else
|
|
3924
|
+
this.#backOffAutoCompaction(table.id, hint.visible);
|
|
3808
3925
|
}).catch(() => {
|
|
3809
|
-
this.#
|
|
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
|
|
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
|
-
|
|
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 (
|
|
3995
|
+
if (!atCeiling)
|
|
3836
3996
|
return;
|
|
3837
|
-
await this
|
|
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
|
|
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
|
|
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
|
|
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 &&
|
|
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.#
|
|
4711
|
-
const {
|
|
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
|
-
|
|
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 &&
|
|
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 =
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
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: {
|
|
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
|
-
|
|
6670
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
8269
|
-
this.#compactionReconciliationCursor =
|
|
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
|
-
|
|
8314
|
-
|
|
8315
|
-
|
|
8316
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
9499
|
-
|
|
9500
|
-
|
|
9501
|
-
|
|
9502
|
-
|
|
9503
|
-
|
|
9504
|
-
|
|
9505
|
-
|
|
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(
|
|
9618
|
-
const
|
|
9619
|
-
|
|
9620
|
-
|
|
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
|
-
|
|
9623
|
-
|
|
9624
|
-
|
|
9625
|
-
|
|
9626
|
-
|
|
9627
|
-
|
|
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
|
-
|
|
9633
|
-
|
|
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
|
-
|
|
9638
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
9886
|
-
|
|
9887
|
-
let publicationConflict;
|
|
10192
|
+
let publicationConflict;
|
|
10193
|
+
const publish = async () => {
|
|
9888
10194
|
try {
|
|
9889
|
-
|
|
9890
|
-
this.#afterCompactionCommit(
|
|
9891
|
-
|
|
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
|
-
|
|
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
|
|
10035
|
-
|
|
10036
|
-
|
|
10037
|
-
|
|
10038
|
-
|
|
10039
|
-
|
|
10040
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
13904
|
-
|
|
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
|
-
{
|
|
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,
|
|
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) {
|