@minnowdb/core 0.8.0 → 0.9.1
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/artifact-cache.js +3 -2
- package/dist/engine/buffered-writer.js +12 -2
- package/dist/engine/client.d.ts +8 -2
- package/dist/engine/client.js +106 -22
- package/dist/engine/database.d.ts +5 -3
- package/dist/engine/database.js +2044 -1674
- package/dist/engine/errors.d.ts +19 -0
- package/dist/engine/errors.js +29 -0
- package/dist/engine/index.d.ts +1 -0
- package/dist/engine/index.js +4 -0
- package/dist/engine/live-accept.js +13 -0
- package/dist/engine/live-aggregate.js +54 -16
- package/dist/engine/live.d.ts +2 -0
- package/dist/engine/live.js +109 -108
- package/dist/engine/query-cache.js +17 -2
- package/dist/engine/query.d.ts +1 -2
- package/dist/engine/query.js +4 -7
- package/dist/engine/result-state.d.ts +7 -0
- package/dist/engine/result-state.js +15 -0
- package/dist/engine/vector.d.ts +4 -0
- package/dist/engine/vector.js +14 -14
- package/dist/engine/windows.js +15 -14
- package/dist/engine/worker-server.d.ts +1 -1
- package/dist/engine/worker-server.js +28 -15
- package/dist/engine/write-coordinator.js +54 -0
- package/dist/storage/indexeddb.js +134 -89
- package/dist/storage/opfs/index.d.ts +1 -1
- package/dist/storage/opfs/index.js +3 -1
- package/dist/storage/opfs/leader.js +20 -9
- package/dist/storage/opfs/rpc.js +3 -1
- package/dist/storage/opfs/store.d.ts +2 -0
- package/dist/storage/opfs/store.js +128 -40
- package/dist/storage/toolkit/record-core.js +2 -1
- package/dist/storage/types.d.ts +14 -0
- package/dist/storage/types.js +21 -0
- package/dist/transactions/index.d.ts +3 -0
- package/dist/transactions/index.js +4 -1
- package/dist/worker-protocol/index.d.ts +1 -1
- package/dist/worker-protocol/index.js +1 -1
- package/package.json +2 -2
- package/dist/date-value.d.ts +0 -20
- package/dist/engine/artifact-cache.d.ts +0 -29
- package/dist/engine/byte-estimates.d.ts +0 -11
- package/dist/engine/cancellation.d.ts +0 -2
- package/dist/engine/defaults.d.ts +0 -29
- package/dist/engine/group-index.d.ts +0 -33
- package/dist/engine/join-index.d.ts +0 -10
- package/dist/engine/live-aggregate.d.ts +0 -12
- package/dist/engine/live-equal.d.ts +0 -7
- package/dist/engine/point-read.d.ts +0 -59
- package/dist/engine/query-cache.d.ts +0 -21
- package/dist/engine/query-generations.d.ts +0 -8
- package/dist/engine/query-identity.d.ts +0 -3
- package/dist/engine/result-wire.d.ts +0 -70
- package/dist/engine/sort-keys.d.ts +0 -73
- package/dist/engine/sql-domains.d.ts +0 -97
- package/dist/engine/sql-functions.d.ts +0 -11
- package/dist/engine/sql-json.d.ts +0 -40
- package/dist/engine/sql-semantics.d.ts +0 -66
- package/dist/engine/worker-store-indexeddb.d.ts +0 -2
- package/dist/engine/worker-store-memory.d.ts +0 -2
- package/dist/engine/worker-store-opfs.d.ts +0 -2
- package/dist/engine/write-block-planner.d.ts +0 -19
- package/dist/storage/opfs/leader.d.ts +0 -460
- package/dist/storage/opfs/rpc.d.ts +0 -82
- package/dist/storage/opfs/snapshot-ledger.d.ts +0 -41
package/dist/engine/database.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { coordinateWrite } from "./write-coordinator.js";
|
|
1
2
|
import { LiveAggregate } from "./live-aggregate.js";
|
|
3
|
+
import { stageLiveExecution } from "./live-accept.js";
|
|
2
4
|
import { QueryGenerations } from "./query-generations.js";
|
|
3
5
|
import { applyWindowFunctionsAsync } from "./windows.js";
|
|
4
6
|
import { crossJoinPlan } from "../plan/model.js";
|
|
@@ -8,7 +10,7 @@ import { estimateBatchBytes, estimateRowBytes, estimateValuesBytes } from "./byt
|
|
|
8
10
|
import { throwIfAborted } from "./cancellation.js";
|
|
9
11
|
import { BufferedTableWriter } from "./buffered-writer.js";
|
|
10
12
|
import { attachLifecycleFlush, BufferedTableWriter as BufferedTableWriter2, MAX_BUFFERED_WRITER_PENDING_ADDS } from "./buffered-writer.js";
|
|
11
|
-
import { CompactionJobCancelledError, CompactionMemoryBudgetError, CompactionWriteAmplificationError, DatabaseReadBacklogError, MaintenanceBacklogError, MissingKeyError, SqlCompileError, UnknownTableError, UniqueConstraintError, VisibleSegmentCursorStaleError } from "./errors.js";
|
|
13
|
+
import { CompactionJobCancelledError, CompactionMemoryBudgetError, CompactionWriteAmplificationError, DatabaseReadBacklogError, TransactionExpiredError, MaintenanceBacklogError, MissingKeyError, SqlCompileError, UnknownTableError, UniqueConstraintError, VisibleSegmentCursorStaleError } from "./errors.js";
|
|
12
14
|
import { DatabaseReadBacklogError as DatabaseReadBacklogError2 } from "./errors.js";
|
|
13
15
|
import { BLOCK_HEADER_LENGTH, buildPhysicalColumnFromRanges, decodeBlock, decodePhysicalBlock, encodeBlock, encodePhysicalBlock, getCompressionMemoryBound, inspectBlock, maximumPhysicalBlockByteLength, MAX_BLOCK_METADATA_BYTE_LENGTH, MAX_BLOCK_ROW_COUNT, MAX_PHYSICAL_COLUMN_BYTE_LENGTH, MAX_STORED_BLOCK_BYTE_LENGTH, measurePhysicalColumnRanges, physicalColumnByteLength, slicePhysicalColumn, StoredBlockPayloadTooLargeError, wellFormedUtf8ByteLength } from "../block-format/index.js";
|
|
14
16
|
import { dateIsoString, dateMilliseconds } from "../date-value.js";
|
|
@@ -19,8 +21,8 @@ import { cachedQueryTerms, FTS_TOKENIZER_VERSION, renderDocumentValue, tokenize
|
|
|
19
21
|
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";
|
|
20
22
|
import { decodeSnapshotFrameStream, encodeSnapshotFrameStreamFooter, encodeSnapshotFrameStreamHeader, extendSnapshotFrameStreamChecksum, snapshotFrameEnvelopeParts, snapshotFrameStreamHeaderIdentity } from "../storage/snapshot.js";
|
|
21
23
|
import { Snapshot, TransactionManager } from "../transactions/index.js";
|
|
22
|
-
import { 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, mapChildExpressions, expandFtsColumns, expandNaturalJoins, expandSourceColumnAliases, expandViewSources, forEachBlockExpression, forEachNestedBlock, planContainsFts, planHasNaturalJoins, planHasSourceColumnAliases, planReadsTable, planReadsViews, planReadsBeyondSingleScan, projectResultColumns, queryResultNeedsExternalization, resolveStatementDatetimes, subqueryResolutionSteps, topLevelFtsMatchConjuncts, transparentProjectionSource, unknownColumnDomains, validateDefaultExpression, windowOutputDomain, windowOutputType, volatileScalarFunctionNames, annotatePlanIntegerDivision, clonePlanTree, planMayHaveIntegerDivision, foldIdentifierCase, extendGroupByWithKeyDependents, expandRowReferences } from "./query.js";
|
|
23
|
-
import { copyQueryResult, planMemoKey, queryResultMemoKey, queryResultRetainedBytes, RESULT_MEMO_MAX_BYTES } from "./query-cache.js";
|
|
24
|
+
import { bindPendingSelectShapes, bindPlanParameters, bindStatementParameters, DUAL_TABLE, dualTableRows, blockHasSubqueries, combineUnionResults, compileCheckExpression, compileQuery, hasAggregate, createRecursiveCteState, annotateAvgArgumentScales, compileStatement, comparisonHolds, createPreparedColumnarQuery, evaluateJoinedRowExpression, evaluateRowExpression, externalizeQueryResult, markQueryResultExternal, expressionColumnNames, inferBlockSchema, inferResultColumnDomains, isDeferredInsertExpression, isDefaultInsertValue, referencedColumns, childExpressions, mapChildExpressions, expandFtsColumns, expandNaturalJoins, expandSourceColumnAliases, expandViewSources, forEachBlockExpression, forEachNestedBlock, planContainsFts, planHasNaturalJoins, planHasSourceColumnAliases, planReadsTable, planReadsViews, planReadsBeyondSingleScan, projectResultColumns, queryResultNeedsExternalization, resolveStatementDatetimes, subqueryResolutionSteps, topLevelFtsMatchConjuncts, transparentProjectionSource, unknownColumnDomains, validateDefaultExpression, windowOutputDomain, windowOutputType, volatileScalarFunctionNames, annotatePlanIntegerDivision, clonePlanTree, planMayHaveIntegerDivision, foldIdentifierCase, extendGroupByWithKeyDependents, expandRowReferences } from "./query.js";
|
|
25
|
+
import { copyQueryResult, planMemoKey, queryResultMemoKey, queryResultRetainedBytes, RESULT_MEMO_MAX_BYTES, sameQueryRow as sameLiveRow } from "./query-cache.js";
|
|
24
26
|
import { QueryMemoryBudgetError, QueryMemoryContext, DEFAULT_QUERY_MEMORY_BUDGET_BYTES } from "./memory.js";
|
|
25
27
|
import { LiveQueryLimitError, LiveQuerySet, MAX_LIVE_QUERY_SETS_PER_DATABASE } from "./live.js";
|
|
26
28
|
import { chooseJoinOrder, optimizePlan, qualifyCorrelatedReferences, renderPlan } from "./optimizer.js";
|
|
@@ -28,7 +30,7 @@ import { encodeSqlEqualityValue, readUntypedText, compareSqlValues } from "./sql
|
|
|
28
30
|
import { exactNumericAsNumber, exactNumericValue, externalSqlDomainValue, externalSqlTextValue, isDateDomainValue, isExactNumeric, isSqlDomainValue, normalizeSqlDomainValue, protectedSqlTextValue } from "./sql-domains.js";
|
|
29
31
|
import { toCatalog } from "./catalog.js";
|
|
30
32
|
import { applyColumnSteps, assertColumnDroppable, compileGeneratedColumnExpression, declaredForeignKeys, isDestructiveStep, planMigration } from "./schema.js";
|
|
31
|
-
import { columnarTableFromRows, createColumnarTable, vectorValue } from "./vector.js";
|
|
33
|
+
import { columnarTableFromRows, createColumnarTable, createColumnVector, vectorValue } from "./vector.js";
|
|
32
34
|
import { cachedPointReadTemplate, equalRunRange, pointReadTestHooks, resolvePointReadShape, valuesAreAscending } from "./point-read.js";
|
|
33
35
|
const PLATFORM_LITTLE_ENDIAN = new Uint8Array(new Uint32Array([1]).buffer)[0] === 1;
|
|
34
36
|
const vectorTextDecoder = new TextDecoder("utf-8", { fatal: true });
|
|
@@ -75,6 +77,7 @@ const CATALOG_SEGMENT_READ_CONCURRENCY = 8;
|
|
|
75
77
|
const MAX_TRANSACTION_SAVEPOINTS = 64;
|
|
76
78
|
const MAX_TRANSACTION_SAVEPOINT_BYTES = 8 * 1024 * 1024;
|
|
77
79
|
const MAX_DATABASE_PENDING_WRITES = 64;
|
|
80
|
+
const MAX_DATABASE_ACTIVE_SCOPES = 256;
|
|
78
81
|
const MAX_DATABASE_ACTIVE_READS = 256;
|
|
79
82
|
const AUTO_COLLECT_MAX_DEBT_COMMITS = 4096;
|
|
80
83
|
const AUTO_COLLECT_RETRY_MIN_MS = 1e3;
|
|
@@ -566,11 +569,19 @@ class MinnowDatabase {
|
|
|
566
569
|
#sequenceCurrValues = /* @__PURE__ */ new Map();
|
|
567
570
|
#openTransaction;
|
|
568
571
|
#statementTransactionCleanup;
|
|
572
|
+
#statementTransactionExpired = false;
|
|
573
|
+
#executeChain = Promise.resolve();
|
|
574
|
+
#pendingExecutes = 0;
|
|
575
|
+
#scopeTasks = /* @__PURE__ */ new Set();
|
|
576
|
+
#scopeControllers = /* @__PURE__ */ new Set();
|
|
569
577
|
#compression;
|
|
570
578
|
#gzipVerdicts = /* @__PURE__ */ new Map();
|
|
571
579
|
#rowsPerBlock;
|
|
572
580
|
#targetBlockBytes;
|
|
573
581
|
#maxCommitRetries;
|
|
582
|
+
#coordinateWrites;
|
|
583
|
+
#shutdown = new AbortController();
|
|
584
|
+
#exports = /* @__PURE__ */ new Set();
|
|
574
585
|
#now;
|
|
575
586
|
#spillOwnerLeaseMs;
|
|
576
587
|
#transactionOwnerLeaseMs;
|
|
@@ -646,6 +657,7 @@ class MinnowDatabase {
|
|
|
646
657
|
if (!Number.isSafeInteger(this.#targetBlockBytes) || this.#targetBlockBytes <= 0 || this.#targetBlockBytes > MAX_PHYSICAL_COLUMN_BYTE_LENGTH) {
|
|
647
658
|
throw new RangeError(`Target block bytes must be a positive whole number no larger than ${String(MAX_PHYSICAL_COLUMN_BYTE_LENGTH)}`);
|
|
648
659
|
}
|
|
660
|
+
this.#coordinateWrites = options.coordinateWrites ?? true;
|
|
649
661
|
this.#maxCommitRetries = options.maxCommitRetries ?? 8;
|
|
650
662
|
if (!Number.isSafeInteger(this.#maxCommitRetries) || this.#maxCommitRetries < 0) {
|
|
651
663
|
throw new RangeError("Commit retries must be a non-negative whole number");
|
|
@@ -685,11 +697,15 @@ class MinnowDatabase {
|
|
|
685
697
|
if (this.#closePromise !== void 0)
|
|
686
698
|
return this.#closePromise;
|
|
687
699
|
this.#closed = true;
|
|
700
|
+
this.#shutdown.abort(new Error("Database is closed"));
|
|
688
701
|
const closing = this.#closeResources();
|
|
689
702
|
this.#closePromise = closing;
|
|
690
703
|
return closing;
|
|
691
704
|
}
|
|
692
705
|
async #closeResources() {
|
|
706
|
+
for (const controller of this.#scopeControllers) {
|
|
707
|
+
controller.abort(new Error("Database is closed"));
|
|
708
|
+
}
|
|
693
709
|
if (this.#idleCompactionTimer !== void 0) {
|
|
694
710
|
clearTimeout(this.#idleCompactionTimer);
|
|
695
711
|
this.#idleCompactionTimer = void 0;
|
|
@@ -708,6 +724,11 @@ class MinnowDatabase {
|
|
|
708
724
|
set.close();
|
|
709
725
|
this.#liveSets.clear();
|
|
710
726
|
await Promise.allSettled([
|
|
727
|
+
...[...this.#exports].map((iterator) => iterator.return(void 0)),
|
|
728
|
+
this.#writeChain,
|
|
729
|
+
this.#executeChain,
|
|
730
|
+
...this.#scopeTasks,
|
|
731
|
+
...this.#foregroundTasks,
|
|
711
732
|
...this.#autoCompactionsInFlight.values(),
|
|
712
733
|
...this.#ftsBuildsInFlight.values(),
|
|
713
734
|
...this.#autoCollectionTask === void 0 ? [] : [this.#autoCollectionTask],
|
|
@@ -761,264 +782,270 @@ class MinnowDatabase {
|
|
|
761
782
|
};
|
|
762
783
|
}
|
|
763
784
|
async createTable(input) {
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
const names = /* @__PURE__ */ new Set();
|
|
782
|
-
const columns = input.columns.map((column) => {
|
|
783
|
-
const columnName = validateName(column.name, "Column");
|
|
784
|
-
if (names.has(columnName))
|
|
785
|
-
throw new TypeError(`Duplicate column: ${columnName}`);
|
|
786
|
-
names.add(columnName);
|
|
787
|
-
if (!simpleDataTypes.includes(column.type)) {
|
|
788
|
-
throw new TypeError(`Unsupported data type: ${column.type}`);
|
|
789
|
-
}
|
|
790
|
-
if (column.enumValues !== void 0 && column.type !== "string") {
|
|
791
|
-
throw new TypeError(`Enum values require a string column: ${columnName}`);
|
|
792
|
-
}
|
|
793
|
-
const sqlDomain = enumDomains.get(column.name) ?? column.sqlDomain;
|
|
794
|
-
return {
|
|
795
|
-
id: this.#createId(),
|
|
796
|
-
name: columnName,
|
|
797
|
-
type: column.type,
|
|
798
|
-
...column.integer === true ? { integer: true } : {},
|
|
799
|
-
...sqlDomain === void 0 ? {} : { sqlDomain: structuredClone(sqlDomain) },
|
|
800
|
-
nullable: column.nullable ?? false,
|
|
801
|
-
...column.defaultValue === void 0 ? {} : { defaultValue: column.defaultValue },
|
|
802
|
-
...column.generatedValue === void 0 ? {} : { generatedValue: structuredClone(column.generatedValue) },
|
|
803
|
-
...column.enumValues === void 0 ? {} : { enumValues: validateEnumValues(column.enumValues, columnName) },
|
|
804
|
-
...column.backfill === void 0 ? {} : {
|
|
805
|
-
backfill: normalizedColumnBackfill({
|
|
806
|
-
name: columnName,
|
|
807
|
-
type: column.type,
|
|
808
|
-
...column.integer === true ? { integer: true } : {},
|
|
809
|
-
...sqlDomain === void 0 ? {} : { sqlDomain },
|
|
810
|
-
...column.enumValues === void 0 ? {} : { enumValues: column.enumValues }
|
|
811
|
-
}, column.backfill)
|
|
812
|
-
}
|
|
813
|
-
};
|
|
814
|
-
});
|
|
815
|
-
if (input.uniqueKey !== void 0 && input.compositePrimaryKey !== void 0) {
|
|
816
|
-
throw new TypeError("A table cannot declare both scalar and composite primary keys");
|
|
817
|
-
}
|
|
818
|
-
const compositePrimaryColumns = (input.compositePrimaryKey ?? []).map((columnName) => {
|
|
819
|
-
const column = columns.find((candidate) => candidate.name === columnName);
|
|
820
|
-
if (column === void 0) {
|
|
821
|
-
throw new TypeError(`PRIMARY KEY column not found: ${columnName}`);
|
|
822
|
-
}
|
|
823
|
-
return column;
|
|
824
|
-
});
|
|
825
|
-
if (input.compositePrimaryKey !== void 0 && (compositePrimaryColumns.length < 2 || new Set(compositePrimaryColumns.map((column) => column.id)).size !== compositePrimaryColumns.length)) {
|
|
826
|
-
throw new TypeError("A composite PRIMARY KEY needs at least two distinct columns");
|
|
827
|
-
}
|
|
828
|
-
for (const column of compositePrimaryColumns) {
|
|
829
|
-
if (column.nullable)
|
|
830
|
-
throw new TypeError(`PRIMARY KEY cannot be nullable: ${column.name}`);
|
|
831
|
-
}
|
|
832
|
-
const hiddenCompositeKey = compositePrimaryColumns.length === 0 ? void 0 : {
|
|
833
|
-
id: this.#createId(),
|
|
834
|
-
name: COMPOSITE_KEY_COLUMN_NAME,
|
|
835
|
-
type: "string",
|
|
836
|
-
nullable: false,
|
|
837
|
-
hidden: true
|
|
838
|
-
};
|
|
839
|
-
if (hiddenCompositeKey !== void 0)
|
|
840
|
-
columns.push(hiddenCompositeKey);
|
|
841
|
-
const uniqueKeyColumn = hiddenCompositeKey ?? (input.uniqueKey === void 0 ? void 0 : columns.find((column) => column.name === input.uniqueKey));
|
|
842
|
-
if (input.uniqueKey !== void 0 && uniqueKeyColumn === void 0) {
|
|
843
|
-
throw new TypeError(`Unique key column not found: ${input.uniqueKey}`);
|
|
844
|
-
}
|
|
845
|
-
if (uniqueKeyColumn?.nullable === true) {
|
|
846
|
-
throw new TypeError(`Unique key cannot be nullable: ${uniqueKeyColumn.name}`);
|
|
847
|
-
}
|
|
848
|
-
for (const column of columns) {
|
|
849
|
-
if (column.generatedValue !== void 0) {
|
|
850
|
-
if (column.defaultValue !== void 0 || column.backfill !== void 0) {
|
|
851
|
-
throw new TypeError(`A generated column cannot also have a default or backfill: ${name}.${column.name}`);
|
|
852
|
-
}
|
|
853
|
-
compileGeneratedColumnExpression(name, column.name, column.generatedValue.sql, columns);
|
|
854
|
-
if (column === uniqueKeyColumn || compositePrimaryColumns.includes(column)) {
|
|
855
|
-
throw new TypeError(`Generated columns cannot be row-addressing keys: ${name}.${column.name}`);
|
|
856
|
-
}
|
|
785
|
+
return this.#foreground(async () => {
|
|
786
|
+
const name = validateName(input.name, "Table");
|
|
787
|
+
if (input.columns.length === 0)
|
|
788
|
+
throw new TypeError("A table needs at least one column");
|
|
789
|
+
const enumDomains = /* @__PURE__ */ new Map();
|
|
790
|
+
for (const column of input.columns) {
|
|
791
|
+
if (column.sqlDomain?.kind !== "enum" || column.sqlDomain.values.length > 0)
|
|
792
|
+
continue;
|
|
793
|
+
const record = await this.store.getTableByName(`${ENUM_TYPE_PREFIX}${column.sqlDomain.name}`);
|
|
794
|
+
if (record?.enumType === void 0) {
|
|
795
|
+
throw new TypeError(`Unsupported column type: ${column.sqlDomain.name}`);
|
|
796
|
+
}
|
|
797
|
+
enumDomains.set(column.name, {
|
|
798
|
+
kind: "enum",
|
|
799
|
+
name: record.enumType.name,
|
|
800
|
+
values: [...record.enumType.values]
|
|
801
|
+
});
|
|
857
802
|
}
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
803
|
+
const names = /* @__PURE__ */ new Set();
|
|
804
|
+
const columns = input.columns.map((column) => {
|
|
805
|
+
const columnName = validateName(column.name, "Column");
|
|
806
|
+
if (names.has(columnName))
|
|
807
|
+
throw new TypeError(`Duplicate column: ${columnName}`);
|
|
808
|
+
names.add(columnName);
|
|
809
|
+
if (!simpleDataTypes.includes(column.type)) {
|
|
810
|
+
throw new TypeError(`Unsupported data type: ${column.type}`);
|
|
862
811
|
}
|
|
863
|
-
if (column.
|
|
864
|
-
|
|
865
|
-
name: `${name}.${column.name}`,
|
|
866
|
-
type: column.type,
|
|
867
|
-
...column.sqlDomain === void 0 ? {} : { sqlDomain: column.sqlDomain }
|
|
868
|
-
});
|
|
812
|
+
if (column.enumValues !== void 0 && column.type !== "string") {
|
|
813
|
+
throw new TypeError(`Enum values require a string column: ${columnName}`);
|
|
869
814
|
}
|
|
815
|
+
const sqlDomain = enumDomains.get(column.name) ?? column.sqlDomain;
|
|
816
|
+
return {
|
|
817
|
+
id: this.#createId(),
|
|
818
|
+
name: columnName,
|
|
819
|
+
type: column.type,
|
|
820
|
+
...column.integer === true ? { integer: true } : {},
|
|
821
|
+
...sqlDomain === void 0 ? {} : { sqlDomain: structuredClone(sqlDomain) },
|
|
822
|
+
nullable: column.nullable ?? false,
|
|
823
|
+
...column.defaultValue === void 0 ? {} : { defaultValue: column.defaultValue },
|
|
824
|
+
...column.generatedValue === void 0 ? {} : { generatedValue: structuredClone(column.generatedValue) },
|
|
825
|
+
...column.enumValues === void 0 ? {} : { enumValues: validateEnumValues(column.enumValues, columnName) },
|
|
826
|
+
...column.backfill === void 0 ? {} : {
|
|
827
|
+
backfill: normalizedColumnBackfill({
|
|
828
|
+
name: columnName,
|
|
829
|
+
type: column.type,
|
|
830
|
+
...column.integer === true ? { integer: true } : {},
|
|
831
|
+
...sqlDomain === void 0 ? {} : { sqlDomain },
|
|
832
|
+
...column.enumValues === void 0 ? {} : { enumValues: column.enumValues }
|
|
833
|
+
}, column.backfill)
|
|
834
|
+
}
|
|
835
|
+
};
|
|
836
|
+
});
|
|
837
|
+
if (input.uniqueKey !== void 0 && input.compositePrimaryKey !== void 0) {
|
|
838
|
+
throw new TypeError("A table cannot declare both scalar and composite primary keys");
|
|
870
839
|
}
|
|
871
|
-
|
|
872
|
-
const constraintNames = /* @__PURE__ */ new Set();
|
|
873
|
-
const claimConstraintName = (rawName) => {
|
|
874
|
-
const constraintName = validateName(rawName, "Constraint");
|
|
875
|
-
if (constraintNames.has(constraintName)) {
|
|
876
|
-
throw new TypeError(`Constraint already exists: ${constraintName}`);
|
|
877
|
-
}
|
|
878
|
-
constraintNames.add(constraintName);
|
|
879
|
-
return constraintName;
|
|
880
|
-
};
|
|
881
|
-
const foreignKeyNames = (input.foreignKeys ?? []).map((key) => claimConstraintName(key.name));
|
|
882
|
-
const checkNames = (input.checks ?? []).map((check) => claimConstraintName(check.name));
|
|
883
|
-
const uniqueConstraintNames = (input.uniqueConstraints ?? []).map((constraint) => claimConstraintName(constraint.name));
|
|
884
|
-
const foreignKeys = await Promise.all((input.foreignKeys ?? []).map(async (key, keyIndex) => {
|
|
885
|
-
if (key.enforced === false && key.onDelete !== "restrict") {
|
|
886
|
-
throw new TypeError(`Informational FOREIGN KEY ${key.name} cannot declare ON DELETE actions`);
|
|
887
|
-
}
|
|
888
|
-
const childNames = key.columns ?? [key.column];
|
|
889
|
-
const children = childNames.map((columnName) => {
|
|
840
|
+
const compositePrimaryColumns = (input.compositePrimaryKey ?? []).map((columnName) => {
|
|
890
841
|
const column = columns.find((candidate) => candidate.name === columnName);
|
|
891
842
|
if (column === void 0) {
|
|
892
|
-
throw new TypeError(`
|
|
843
|
+
throw new TypeError(`PRIMARY KEY column not found: ${columnName}`);
|
|
893
844
|
}
|
|
894
845
|
return column;
|
|
895
846
|
});
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
throw new TypeError(`FOREIGN KEY ${key.name} references a table that does not exist: ${key.parentTable}`);
|
|
847
|
+
if (input.compositePrimaryKey !== void 0 && (compositePrimaryColumns.length < 2 || new Set(compositePrimaryColumns.map((column) => column.id)).size !== compositePrimaryColumns.length)) {
|
|
848
|
+
throw new TypeError("A composite PRIMARY KEY needs at least two distinct columns");
|
|
899
849
|
}
|
|
900
|
-
const
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
if (parentPrimary.length === 0) {
|
|
904
|
-
throw new TypeError(`FOREIGN KEY ${key.name} references a table with no unique key: ${key.parentTable}`);
|
|
850
|
+
for (const column of compositePrimaryColumns) {
|
|
851
|
+
if (column.nullable)
|
|
852
|
+
throw new TypeError(`PRIMARY KEY cannot be nullable: ${column.name}`);
|
|
905
853
|
}
|
|
906
|
-
const
|
|
907
|
-
|
|
908
|
-
|
|
854
|
+
const hiddenCompositeKey = compositePrimaryColumns.length === 0 ? void 0 : {
|
|
855
|
+
id: this.#createId(),
|
|
856
|
+
name: COMPOSITE_KEY_COLUMN_NAME,
|
|
857
|
+
type: "string",
|
|
858
|
+
nullable: false,
|
|
859
|
+
hidden: true
|
|
860
|
+
};
|
|
861
|
+
if (hiddenCompositeKey !== void 0)
|
|
862
|
+
columns.push(hiddenCompositeKey);
|
|
863
|
+
const uniqueKeyColumn = hiddenCompositeKey ?? (input.uniqueKey === void 0 ? void 0 : columns.find((column) => column.name === input.uniqueKey));
|
|
864
|
+
if (input.uniqueKey !== void 0 && uniqueKeyColumn === void 0) {
|
|
865
|
+
throw new TypeError(`Unique key column not found: ${input.uniqueKey}`);
|
|
909
866
|
}
|
|
910
|
-
if (
|
|
911
|
-
throw new TypeError(`
|
|
867
|
+
if (uniqueKeyColumn?.nullable === true) {
|
|
868
|
+
throw new TypeError(`Unique key cannot be nullable: ${uniqueKeyColumn.name}`);
|
|
912
869
|
}
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
throw new TypeError(`FOREIGN KEY ${key.name} compares an integer domain with an approximate number domain`);
|
|
870
|
+
for (const column of columns) {
|
|
871
|
+
if (column.generatedValue !== void 0) {
|
|
872
|
+
if (column.defaultValue !== void 0 || column.backfill !== void 0) {
|
|
873
|
+
throw new TypeError(`A generated column cannot also have a default or backfill: ${name}.${column.name}`);
|
|
874
|
+
}
|
|
875
|
+
compileGeneratedColumnExpression(name, column.name, column.generatedValue.sql, columns);
|
|
876
|
+
if (column === uniqueKeyColumn || compositePrimaryColumns.includes(column)) {
|
|
877
|
+
throw new TypeError(`Generated columns cannot be row-addressing keys: ${name}.${column.name}`);
|
|
878
|
+
}
|
|
923
879
|
}
|
|
924
|
-
if (
|
|
925
|
-
|
|
880
|
+
if (column.defaultValue !== void 0) {
|
|
881
|
+
validateColumnDefault({ ...column, isUniqueKey: column === uniqueKeyColumn }, column.defaultValue);
|
|
882
|
+
if (column.defaultValue.kind === "literal" && column.sqlDomain !== void 0) {
|
|
883
|
+
normalizeSqlDomainValue(column.sqlDomain, column.defaultValue.value);
|
|
884
|
+
}
|
|
885
|
+
if (column.defaultValue.kind === "expression") {
|
|
886
|
+
validateDefaultExpression(column.defaultValue.sql, {
|
|
887
|
+
name: `${name}.${column.name}`,
|
|
888
|
+
type: column.type,
|
|
889
|
+
...column.sqlDomain === void 0 ? {} : { sqlDomain: column.sqlDomain }
|
|
890
|
+
});
|
|
891
|
+
}
|
|
926
892
|
}
|
|
927
|
-
|
|
928
|
-
|
|
893
|
+
}
|
|
894
|
+
const constraintNames = /* @__PURE__ */ new Set();
|
|
895
|
+
const claimConstraintName = (rawName) => {
|
|
896
|
+
const constraintName = validateName(rawName, "Constraint");
|
|
897
|
+
if (constraintNames.has(constraintName)) {
|
|
898
|
+
throw new TypeError(`Constraint already exists: ${constraintName}`);
|
|
929
899
|
}
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
return {
|
|
933
|
-
name: foreignKeyNames[keyIndex] ?? key.name,
|
|
934
|
-
columns: childNames,
|
|
935
|
-
parentTable: key.parentTable,
|
|
936
|
-
parentColumns: parentNames,
|
|
937
|
-
onDelete: key.onDelete,
|
|
938
|
-
...key.enforced === false ? { enforced: false } : {}
|
|
900
|
+
constraintNames.add(constraintName);
|
|
901
|
+
return constraintName;
|
|
939
902
|
};
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
const
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
903
|
+
const foreignKeyNames = (input.foreignKeys ?? []).map((key) => claimConstraintName(key.name));
|
|
904
|
+
const checkNames = (input.checks ?? []).map((check) => claimConstraintName(check.name));
|
|
905
|
+
const uniqueConstraintNames = (input.uniqueConstraints ?? []).map((constraint) => claimConstraintName(constraint.name));
|
|
906
|
+
const foreignKeys = await Promise.all((input.foreignKeys ?? []).map(async (key, keyIndex) => {
|
|
907
|
+
if (key.enforced === false && key.onDelete !== "restrict") {
|
|
908
|
+
throw new TypeError(`Informational FOREIGN KEY ${key.name} cannot declare ON DELETE actions`);
|
|
909
|
+
}
|
|
910
|
+
const childNames = key.columns ?? [key.column];
|
|
911
|
+
const children = childNames.map((columnName) => {
|
|
912
|
+
const column = columns.find((candidate) => candidate.name === columnName);
|
|
913
|
+
if (column === void 0) {
|
|
914
|
+
throw new TypeError(`FOREIGN KEY ${key.name} names a column this table has no: ${columnName}`);
|
|
915
|
+
}
|
|
916
|
+
return column;
|
|
917
|
+
});
|
|
918
|
+
const parent = key.parentTable === name ? void 0 : await this.store.getTableByName(key.parentTable);
|
|
919
|
+
if (key.parentTable !== name && parent === void 0) {
|
|
920
|
+
throw new TypeError(`FOREIGN KEY ${key.name} references a table that does not exist: ${key.parentTable}`);
|
|
921
|
+
}
|
|
922
|
+
const parentColumns = parent === void 0 ? columns : parent.columns;
|
|
923
|
+
const parentPrimaryIds = parent === void 0 ? compositePrimaryColumns.length > 0 ? compositePrimaryColumns.map((column) => column.id) : uniqueKeyColumn === void 0 ? [] : [uniqueKeyColumn.id] : parent.primaryKeyColumnIds ?? (parent.uniqueKeyColumnId === void 0 ? [] : [parent.uniqueKeyColumnId]);
|
|
924
|
+
const parentPrimary = parentPrimaryIds.map((columnId) => parentColumns.find((column) => column.id === columnId)).filter((column) => column !== void 0 && !column.hidden);
|
|
925
|
+
if (parentPrimary.length === 0) {
|
|
926
|
+
throw new TypeError(`FOREIGN KEY ${key.name} references a table with no unique key: ${key.parentTable}`);
|
|
927
|
+
}
|
|
928
|
+
const requestedParentNames = key.parentColumns ?? (key.parentColumn === void 0 ? parentPrimary.map((column) => column.name) : [key.parentColumn]);
|
|
929
|
+
if (requestedParentNames.length !== parentPrimary.length || requestedParentNames.some((columnName, index) => columnName !== parentPrimary[index]?.name)) {
|
|
930
|
+
throw new TypeError(`FOREIGN KEY ${key.name} must reference ${key.parentTable}'s primary key (${parentPrimary.map((column) => column.name).join(", ")})`);
|
|
931
|
+
}
|
|
932
|
+
if (children.length !== parentPrimary.length) {
|
|
933
|
+
throw new TypeError(`FOREIGN KEY ${key.name} has ${String(children.length)} child columns for ${String(parentPrimary.length)} parent columns`);
|
|
934
|
+
}
|
|
935
|
+
children.forEach((child, index) => {
|
|
936
|
+
const parentKey = parentPrimary[index];
|
|
937
|
+
if (parentKey === void 0) {
|
|
938
|
+
throw new TypeError(`FOREIGN KEY ${key.name} is missing a parent key column`);
|
|
939
|
+
}
|
|
940
|
+
if (child.type !== parentKey.type) {
|
|
941
|
+
throw new TypeError(`FOREIGN KEY ${key.name} compares ${child.type} with ${parentKey.type}`);
|
|
942
|
+
}
|
|
943
|
+
if (child.integer === true !== (parentKey.integer === true)) {
|
|
944
|
+
throw new TypeError(`FOREIGN KEY ${key.name} compares an integer domain with an approximate number domain`);
|
|
945
|
+
}
|
|
946
|
+
if (JSON.stringify(child.sqlDomain ?? null) !== JSON.stringify(parentKey.sqlDomain ?? null)) {
|
|
947
|
+
throw new TypeError(`FOREIGN KEY ${key.name} compares different SQL value domains`);
|
|
948
|
+
}
|
|
949
|
+
if (key.onDelete === "set null" && !child.nullable) {
|
|
950
|
+
throw new TypeError(`FOREIGN KEY ${key.name} cannot SET NULL a NOT NULL column`);
|
|
951
|
+
}
|
|
952
|
+
});
|
|
953
|
+
const parentNames = parentPrimary.map((column) => column.name);
|
|
954
|
+
return {
|
|
955
|
+
name: foreignKeyNames[keyIndex] ?? key.name,
|
|
956
|
+
columns: childNames,
|
|
957
|
+
parentTable: key.parentTable,
|
|
958
|
+
parentColumns: parentNames,
|
|
959
|
+
onDelete: key.onDelete,
|
|
960
|
+
...key.enforced === false ? { enforced: false } : {}
|
|
961
|
+
};
|
|
962
|
+
}));
|
|
963
|
+
const checks = (input.checks ?? []).map((check, checkIndex) => {
|
|
964
|
+
const referencedColumns2 = expressionColumnNames(compileCheckExpression(check.sql, check.name));
|
|
965
|
+
for (const reference of referencedColumns2) {
|
|
966
|
+
const pieces = reference.split(".");
|
|
967
|
+
const columnName = pieces.at(-1) ?? reference;
|
|
968
|
+
const qualifier = pieces.length > 1 ? pieces.slice(0, -1).join(".") : void 0;
|
|
969
|
+
if (qualifier !== void 0 && qualifier !== name) {
|
|
970
|
+
throw new TypeError(`CHECK ${check.name} references another table: ${reference}`);
|
|
971
|
+
}
|
|
972
|
+
if (!columns.some((column) => column.name === columnName && !column.hidden)) {
|
|
973
|
+
throw new TypeError(`CHECK ${check.name} names an unknown column: ${columnName}`);
|
|
974
|
+
}
|
|
949
975
|
}
|
|
950
|
-
|
|
951
|
-
|
|
976
|
+
return { name: checkNames[checkIndex] ?? check.name, sql: check.sql };
|
|
977
|
+
});
|
|
978
|
+
const tableId = this.#createId();
|
|
979
|
+
const currentVersion = (await this.store.getCurrentManifest())?.version ?? -1;
|
|
980
|
+
const secondaryIndexes = {};
|
|
981
|
+
for (const [constraintIndex, constraint] of (input.uniqueConstraints ?? []).entries()) {
|
|
982
|
+
const indexName = uniqueConstraintNames[constraintIndex] ?? constraint.name;
|
|
983
|
+
const indexedColumns = constraint.columns.map((columnName) => {
|
|
984
|
+
const column = columns.find((candidate) => candidate.name === columnName && !candidate.hidden);
|
|
985
|
+
if (column === void 0) {
|
|
986
|
+
throw new TypeError(`UNIQUE ${indexName} names an unknown column: ${columnName}`);
|
|
987
|
+
}
|
|
988
|
+
return column;
|
|
989
|
+
});
|
|
990
|
+
if (indexedColumns.length === 0 || new Set(indexedColumns.map((column) => column.id)).size !== indexedColumns.length) {
|
|
991
|
+
throw new TypeError(`UNIQUE ${indexName} needs distinct columns`);
|
|
952
992
|
}
|
|
993
|
+
const indexId = this.#createId();
|
|
994
|
+
secondaryIndexes[indexId] = {
|
|
995
|
+
name: indexName,
|
|
996
|
+
columnId: indexedColumns[0]?.id ?? "",
|
|
997
|
+
columnIds: indexedColumns.map((column) => column.id),
|
|
998
|
+
directions: indexedColumns.map(() => "asc"),
|
|
999
|
+
unique: true,
|
|
1000
|
+
uniqueEnforced: true,
|
|
1001
|
+
termEncoding: "tuple-v1",
|
|
1002
|
+
storage: "postings-v1",
|
|
1003
|
+
storageColumnId: this.#createId(),
|
|
1004
|
+
locator: uniqueKeyColumn === void 0 ? "row-id" : "key-hash-v1",
|
|
1005
|
+
state: "ready",
|
|
1006
|
+
buildFromVersion: currentVersion
|
|
1007
|
+
};
|
|
953
1008
|
}
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
}
|
|
966
|
-
|
|
1009
|
+
await this.store.addTable({
|
|
1010
|
+
id: tableId,
|
|
1011
|
+
name,
|
|
1012
|
+
columns,
|
|
1013
|
+
revision: 0,
|
|
1014
|
+
managed: input.managed === true,
|
|
1015
|
+
...foreignKeys.length === 0 ? {} : { foreignKeys },
|
|
1016
|
+
...checks.length === 0 ? {} : { checks },
|
|
1017
|
+
...uniqueKeyColumn === void 0 ? {} : { uniqueKeyColumnId: uniqueKeyColumn.id },
|
|
1018
|
+
...uniqueKeyColumn === void 0 ? {} : { uniqueKeyLookupReady: true },
|
|
1019
|
+
...compositePrimaryColumns.length === 0 ? {} : { primaryKeyColumnIds: compositePrimaryColumns.map((column) => column.id) },
|
|
1020
|
+
...Object.keys(secondaryIndexes).length === 0 ? {} : { secondaryIndexes },
|
|
1021
|
+
createdAt: dateIsoString(this.#now())
|
|
967
1022
|
});
|
|
968
|
-
if (indexedColumns.length === 0 || new Set(indexedColumns.map((column) => column.id)).size !== indexedColumns.length) {
|
|
969
|
-
throw new TypeError(`UNIQUE ${indexName} needs distinct columns`);
|
|
970
|
-
}
|
|
971
|
-
const indexId = this.#createId();
|
|
972
|
-
secondaryIndexes[indexId] = {
|
|
973
|
-
name: indexName,
|
|
974
|
-
columnId: indexedColumns[0]?.id ?? "",
|
|
975
|
-
columnIds: indexedColumns.map((column) => column.id),
|
|
976
|
-
directions: indexedColumns.map(() => "asc"),
|
|
977
|
-
unique: true,
|
|
978
|
-
uniqueEnforced: true,
|
|
979
|
-
termEncoding: "tuple-v1",
|
|
980
|
-
storage: "postings-v1",
|
|
981
|
-
storageColumnId: this.#createId(),
|
|
982
|
-
locator: uniqueKeyColumn === void 0 ? "row-id" : "key-hash-v1",
|
|
983
|
-
state: "ready",
|
|
984
|
-
buildFromVersion: currentVersion
|
|
985
|
-
};
|
|
986
|
-
}
|
|
987
|
-
await this.store.addTable({
|
|
988
|
-
id: tableId,
|
|
989
|
-
name,
|
|
990
|
-
columns,
|
|
991
|
-
revision: 0,
|
|
992
|
-
managed: input.managed === true,
|
|
993
|
-
...foreignKeys.length === 0 ? {} : { foreignKeys },
|
|
994
|
-
...checks.length === 0 ? {} : { checks },
|
|
995
|
-
...uniqueKeyColumn === void 0 ? {} : { uniqueKeyColumnId: uniqueKeyColumn.id },
|
|
996
|
-
...uniqueKeyColumn === void 0 ? {} : { uniqueKeyLookupReady: true },
|
|
997
|
-
...compositePrimaryColumns.length === 0 ? {} : { primaryKeyColumnIds: compositePrimaryColumns.map((column) => column.id) },
|
|
998
|
-
...Object.keys(secondaryIndexes).length === 0 ? {} : { secondaryIndexes },
|
|
999
|
-
createdAt: dateIsoString(this.#now())
|
|
1000
1023
|
});
|
|
1001
1024
|
}
|
|
1002
1025
|
async introspect() {
|
|
1003
|
-
return
|
|
1026
|
+
return this.#foreground(async () => {
|
|
1027
|
+
return toCatalog(await this.store.listTables());
|
|
1028
|
+
});
|
|
1004
1029
|
}
|
|
1005
1030
|
async listTables() {
|
|
1006
|
-
return
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1031
|
+
return this.#foreground(async () => {
|
|
1032
|
+
return (await this.store.listTables()).filter((table) => table.enumType === void 0 && table.sequence === void 0).map((table) => {
|
|
1033
|
+
const uniqueKey = table.columns.find((column) => column.id === table.uniqueKeyColumnId)?.name;
|
|
1034
|
+
return {
|
|
1035
|
+
name: table.name,
|
|
1036
|
+
columns: visibleTableColumns(table).map((column) => ({
|
|
1037
|
+
name: column.name,
|
|
1038
|
+
type: column.type,
|
|
1039
|
+
...column.integer === true ? { integer: true } : {},
|
|
1040
|
+
...column.sqlDomain === void 0 ? {} : { sqlDomain: structuredClone(column.sqlDomain) },
|
|
1041
|
+
nullable: column.nullable,
|
|
1042
|
+
...column.defaultValue === void 0 ? {} : { defaultValue: column.defaultValue },
|
|
1043
|
+
...column.generatedValue === void 0 ? {} : { generatedValue: column.generatedValue },
|
|
1044
|
+
...column.enumValues === void 0 ? {} : { enumValues: [...column.enumValues] }
|
|
1045
|
+
})),
|
|
1046
|
+
...uniqueKey === void 0 ? {} : { uniqueKey }
|
|
1047
|
+
};
|
|
1048
|
+
});
|
|
1022
1049
|
});
|
|
1023
1050
|
}
|
|
1024
1051
|
async #runWrite(run) {
|
|
@@ -1047,7 +1074,8 @@ class MinnowDatabase {
|
|
|
1047
1074
|
}
|
|
1048
1075
|
};
|
|
1049
1076
|
const previous = this.#writeChain;
|
|
1050
|
-
const
|
|
1077
|
+
const coordinated = () => this.#coordinateWrite(restarting);
|
|
1078
|
+
const current = previous.then(coordinated, coordinated);
|
|
1051
1079
|
this.#writeChain = current;
|
|
1052
1080
|
try {
|
|
1053
1081
|
return await current;
|
|
@@ -1108,6 +1136,10 @@ class MinnowDatabase {
|
|
|
1108
1136
|
await Promise.race([this.#eventLoopTurn(), this.#nextLocalCommit()]);
|
|
1109
1137
|
}
|
|
1110
1138
|
async #withWriteReservation(run) {
|
|
1139
|
+
if (this.#closed)
|
|
1140
|
+
throw new Error("Database is closed");
|
|
1141
|
+
if (this.#statementTransactionExpired)
|
|
1142
|
+
throw new TransactionExpiredError();
|
|
1111
1143
|
if (this.#pendingWriteReservations >= MAX_DATABASE_PENDING_WRITES) {
|
|
1112
1144
|
throw new RangeError(`A database cannot queue more than ${String(MAX_DATABASE_PENDING_WRITES)} writes; await a write for backpressure`);
|
|
1113
1145
|
}
|
|
@@ -1118,7 +1150,21 @@ class MinnowDatabase {
|
|
|
1118
1150
|
this.#pendingWriteReservations -= 1;
|
|
1119
1151
|
}
|
|
1120
1152
|
}
|
|
1153
|
+
#foregroundTasks = /* @__PURE__ */ new Set();
|
|
1154
|
+
async #foreground(run) {
|
|
1155
|
+
if (this.#closed)
|
|
1156
|
+
throw new Error("Database is closed");
|
|
1157
|
+
const task = run();
|
|
1158
|
+
this.#foregroundTasks.add(task);
|
|
1159
|
+
try {
|
|
1160
|
+
return await task;
|
|
1161
|
+
} finally {
|
|
1162
|
+
this.#foregroundTasks.delete(task);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1121
1165
|
async #withReadReservation(run) {
|
|
1166
|
+
if (this.#closed)
|
|
1167
|
+
throw new Error("Database is closed");
|
|
1122
1168
|
if (this.#activeReadReservations >= MAX_DATABASE_ACTIVE_READS) {
|
|
1123
1169
|
throw new DatabaseReadBacklogError(MAX_DATABASE_ACTIVE_READS);
|
|
1124
1170
|
}
|
|
@@ -1130,8 +1176,12 @@ class MinnowDatabase {
|
|
|
1130
1176
|
}
|
|
1131
1177
|
}
|
|
1132
1178
|
async #queryWithReadReservation(sql, options, probe, storeMemo = true) {
|
|
1179
|
+
if (this.#closed)
|
|
1180
|
+
throw new Error("Database is closed");
|
|
1133
1181
|
throwIfAborted(options.signal);
|
|
1134
1182
|
await this.#settleExpiredStatementTransaction();
|
|
1183
|
+
if (this.#statementTransactionExpired)
|
|
1184
|
+
throw new TransactionExpiredError();
|
|
1135
1185
|
throwIfAborted(options.signal);
|
|
1136
1186
|
return this.#openTransaction === void 0 ? this.#withReadReservation(() => this.#queryUnreserved(sql, options, probe, storeMemo)) : this.#queryUnreserved(sql, options, probe, storeMemo);
|
|
1137
1187
|
}
|
|
@@ -1190,116 +1240,120 @@ class MinnowDatabase {
|
|
|
1190
1240
|
return error;
|
|
1191
1241
|
}
|
|
1192
1242
|
async createView(name, sql, options = {}) {
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
pinnedExistingId
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
if (existing.view === void 0)
|
|
1207
|
-
throw new TypeError(`Table already exists: ${viewName}`);
|
|
1208
|
-
if (options.orReplace !== true)
|
|
1209
|
-
throw new TypeError(`View already exists: ${viewName}`);
|
|
1210
|
-
}
|
|
1211
|
-
assertViewDefinitionAcyclic(proof.records, viewName, plan);
|
|
1212
|
-
const schemas = catalogQuerySchemas(proof.records);
|
|
1213
|
-
const inferred = inferBlockSchema(plan, schemas);
|
|
1214
|
-
if (inferred.length === 0) {
|
|
1215
|
-
throw new TypeError(`A view needs at least one column: ${viewName}`);
|
|
1216
|
-
}
|
|
1217
|
-
if (existing !== void 0) {
|
|
1218
|
-
schemas.set(viewName, inferred);
|
|
1219
|
-
assertDependentViewsKeepSchema(proof.records, existing, schemas);
|
|
1220
|
-
}
|
|
1221
|
-
const columns = inferred.map((column, index) => {
|
|
1222
|
-
const previous = existing?.columns[index];
|
|
1223
|
-
return {
|
|
1224
|
-
id: previous !== void 0 && querySchemasEqual([column], [previous]) ? previous.id : this.#createId(),
|
|
1225
|
-
name: column.name,
|
|
1226
|
-
type: column.type,
|
|
1227
|
-
...column.integer === true ? { integer: true } : {},
|
|
1228
|
-
...column.sqlDomain === void 0 ? {} : { sqlDomain: column.sqlDomain },
|
|
1229
|
-
nullable: true
|
|
1230
|
-
};
|
|
1231
|
-
});
|
|
1232
|
-
const view = { sql, managed: options.managed === true };
|
|
1233
|
-
try {
|
|
1243
|
+
return this.#foreground(async () => {
|
|
1244
|
+
const viewName = validateName(name, "View");
|
|
1245
|
+
const plan = compileQuery(sql);
|
|
1246
|
+
let pinnedExistingId;
|
|
1247
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
1248
|
+
const proof = await this.#stableCatalogProof();
|
|
1249
|
+
const existing = proof.records.find((record) => record.name === viewName);
|
|
1250
|
+
const existingId = existing?.id ?? null;
|
|
1251
|
+
if (pinnedExistingId === void 0)
|
|
1252
|
+
pinnedExistingId = existingId;
|
|
1253
|
+
else if (pinnedExistingId !== existingId) {
|
|
1254
|
+
throw new TableRecordConflictError(viewName, 0, existing?.revision ?? null);
|
|
1255
|
+
}
|
|
1234
1256
|
if (existing !== void 0) {
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
triggers: null,
|
|
1240
|
-
view,
|
|
1241
|
-
expectedCatalogEpoch: proof.catalogEpoch
|
|
1242
|
-
});
|
|
1243
|
-
} else {
|
|
1244
|
-
await this.store.addTable({
|
|
1245
|
-
id: this.#createId(),
|
|
1246
|
-
name: viewName,
|
|
1247
|
-
columns,
|
|
1248
|
-
view,
|
|
1249
|
-
managed: false,
|
|
1250
|
-
revision: 0,
|
|
1251
|
-
createdAt: dateIsoString(this.#now())
|
|
1252
|
-
}, { expectedCatalogEpoch: proof.catalogEpoch });
|
|
1257
|
+
if (existing.view === void 0)
|
|
1258
|
+
throw new TypeError(`Table already exists: ${viewName}`);
|
|
1259
|
+
if (options.orReplace !== true)
|
|
1260
|
+
throw new TypeError(`View already exists: ${viewName}`);
|
|
1253
1261
|
}
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
if (
|
|
1258
|
-
throw
|
|
1262
|
+
assertViewDefinitionAcyclic(proof.records, viewName, plan);
|
|
1263
|
+
const schemas = catalogQuerySchemas(proof.records);
|
|
1264
|
+
const inferred = inferBlockSchema(plan, schemas);
|
|
1265
|
+
if (inferred.length === 0) {
|
|
1266
|
+
throw new TypeError(`A view needs at least one column: ${viewName}`);
|
|
1259
1267
|
}
|
|
1260
1268
|
if (existing !== void 0) {
|
|
1261
|
-
|
|
1262
|
-
|
|
1269
|
+
schemas.set(viewName, inferred);
|
|
1270
|
+
assertDependentViewsKeepSchema(proof.records, existing, schemas);
|
|
1271
|
+
}
|
|
1272
|
+
const columns = inferred.map((column, index) => {
|
|
1273
|
+
const previous = existing?.columns[index];
|
|
1274
|
+
return {
|
|
1275
|
+
id: previous !== void 0 && querySchemasEqual([column], [previous]) ? previous.id : this.#createId(),
|
|
1276
|
+
name: column.name,
|
|
1277
|
+
type: column.type,
|
|
1278
|
+
...column.integer === true ? { integer: true } : {},
|
|
1279
|
+
...column.sqlDomain === void 0 ? {} : { sqlDomain: column.sqlDomain },
|
|
1280
|
+
nullable: true
|
|
1281
|
+
};
|
|
1282
|
+
});
|
|
1283
|
+
const view = { sql, managed: options.managed === true };
|
|
1284
|
+
try {
|
|
1285
|
+
if (existing !== void 0) {
|
|
1286
|
+
await this.store.updateTable(existing.id, existing.revision, {
|
|
1287
|
+
columns,
|
|
1288
|
+
ftsColumns: null,
|
|
1289
|
+
secondaryIndexes: null,
|
|
1290
|
+
triggers: null,
|
|
1291
|
+
view,
|
|
1292
|
+
expectedCatalogEpoch: proof.catalogEpoch
|
|
1293
|
+
});
|
|
1294
|
+
} else {
|
|
1295
|
+
await this.store.addTable({
|
|
1296
|
+
id: this.#createId(),
|
|
1297
|
+
name: viewName,
|
|
1298
|
+
columns,
|
|
1299
|
+
view,
|
|
1300
|
+
managed: false,
|
|
1301
|
+
revision: 0,
|
|
1302
|
+
createdAt: dateIsoString(this.#now())
|
|
1303
|
+
}, { expectedCatalogEpoch: proof.catalogEpoch });
|
|
1304
|
+
}
|
|
1305
|
+
this.#planCache.clear();
|
|
1306
|
+
return;
|
|
1307
|
+
} catch (error) {
|
|
1308
|
+
if (!(error instanceof TableRecordConflictError) || attempt >= this.#maxCommitRetries) {
|
|
1263
1309
|
throw error;
|
|
1310
|
+
}
|
|
1311
|
+
if (existing !== void 0) {
|
|
1312
|
+
const current = await this.store.getTable(existing.id);
|
|
1313
|
+
if (current?.revision !== existing.revision)
|
|
1314
|
+
throw error;
|
|
1315
|
+
}
|
|
1264
1316
|
}
|
|
1265
1317
|
}
|
|
1266
|
-
}
|
|
1318
|
+
});
|
|
1267
1319
|
}
|
|
1268
1320
|
async dropView(name, options = {}) {
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
if (
|
|
1275
|
-
|
|
1321
|
+
return this.#foreground(async () => {
|
|
1322
|
+
let pinnedId;
|
|
1323
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
1324
|
+
const proof = await this.#stableCatalogProof();
|
|
1325
|
+
const record = proof.records.find((candidate) => candidate.name === name);
|
|
1326
|
+
if (record?.view === void 0) {
|
|
1327
|
+
if (pinnedId !== void 0) {
|
|
1328
|
+
throw new TableRecordConflictError(pinnedId, 0, record?.revision ?? null);
|
|
1329
|
+
}
|
|
1330
|
+
if (record !== void 0)
|
|
1331
|
+
throw new TypeError(`Not a view: ${name}`);
|
|
1332
|
+
if (options.ifExists === true)
|
|
1333
|
+
return false;
|
|
1334
|
+
throw new Error(`View not found: ${name}`);
|
|
1276
1335
|
}
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
this.#planCache.clear();
|
|
1296
|
-
return true;
|
|
1297
|
-
} catch (error) {
|
|
1298
|
-
if (!(error instanceof TableRecordConflictError) || attempt >= this.#maxCommitRetries) {
|
|
1299
|
-
throw error;
|
|
1336
|
+
pinnedId ??= record.id;
|
|
1337
|
+
if (record.id !== pinnedId) {
|
|
1338
|
+
throw new TableRecordConflictError(pinnedId, 0, record.revision);
|
|
1339
|
+
}
|
|
1340
|
+
const dependent = proof.records.find((candidate) => candidate.id !== record.id && candidate.view !== void 0 && viewReadsTable(candidate.view.sql, record.name));
|
|
1341
|
+
if (dependent !== void 0) {
|
|
1342
|
+
throw new TypeError(`Cannot drop ${record.name}: view ${dependent.name} reads it`);
|
|
1343
|
+
}
|
|
1344
|
+
try {
|
|
1345
|
+
await this.store.removeTable(record.id, record.revision, {
|
|
1346
|
+
expectedCatalogEpoch: proof.catalogEpoch
|
|
1347
|
+
});
|
|
1348
|
+
this.#planCache.clear();
|
|
1349
|
+
return true;
|
|
1350
|
+
} catch (error) {
|
|
1351
|
+
if (!(error instanceof TableRecordConflictError) || attempt >= this.#maxCommitRetries) {
|
|
1352
|
+
throw error;
|
|
1353
|
+
}
|
|
1300
1354
|
}
|
|
1301
1355
|
}
|
|
1302
|
-
}
|
|
1356
|
+
});
|
|
1303
1357
|
}
|
|
1304
1358
|
async #stableCatalogProof() {
|
|
1305
1359
|
for (let attempt = 0; ; attempt += 1) {
|
|
@@ -1319,71 +1373,73 @@ class MinnowDatabase {
|
|
|
1319
1373
|
}
|
|
1320
1374
|
}
|
|
1321
1375
|
async dropColumn(tableName, columnName, options = {}) {
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
if (
|
|
1329
|
-
|
|
1376
|
+
return this.#foreground(async () => {
|
|
1377
|
+
let pinnedTableId;
|
|
1378
|
+
let pinnedColumnId;
|
|
1379
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
1380
|
+
const catalogProof = await this.store.getCatalogProbe();
|
|
1381
|
+
const records = await this.store.listTables();
|
|
1382
|
+
if ((await this.store.getCatalogProbe()).catalogEpoch !== catalogProof.catalogEpoch) {
|
|
1383
|
+
if (attempt >= this.#maxCommitRetries) {
|
|
1384
|
+
throw new TableRecordConflictError(tableName, 0, null);
|
|
1385
|
+
}
|
|
1386
|
+
continue;
|
|
1330
1387
|
}
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1388
|
+
const table = records.find((record) => record.name === tableName);
|
|
1389
|
+
if (table === void 0 || table.view !== void 0) {
|
|
1390
|
+
if (pinnedTableId !== void 0) {
|
|
1391
|
+
throw new TableRecordConflictError(pinnedTableId, 0, table?.revision ?? null);
|
|
1392
|
+
}
|
|
1393
|
+
if (table?.view !== void 0)
|
|
1394
|
+
throw new TypeError(`${tableName} is a view, not a table`);
|
|
1395
|
+
if (options.ifExists === true)
|
|
1396
|
+
return false;
|
|
1397
|
+
throw new UnknownTableError(tableName);
|
|
1337
1398
|
}
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1399
|
+
pinnedTableId ??= table.id;
|
|
1400
|
+
if (table.id !== pinnedTableId) {
|
|
1401
|
+
throw new TableRecordConflictError(pinnedTableId, 0, table.revision);
|
|
1402
|
+
}
|
|
1403
|
+
const column = table.columns.find((candidate) => candidate.name === columnName);
|
|
1404
|
+
if (column === void 0) {
|
|
1405
|
+
if (pinnedColumnId !== void 0) {
|
|
1406
|
+
throw new TableRecordConflictError(pinnedTableId, table.revision, table.revision);
|
|
1407
|
+
}
|
|
1408
|
+
if (options.ifExists === true)
|
|
1409
|
+
return false;
|
|
1410
|
+
throw new TypeError(`Column not found: ${tableName}.${columnName}`);
|
|
1411
|
+
}
|
|
1412
|
+
pinnedColumnId ??= column.id;
|
|
1413
|
+
if (column.id !== pinnedColumnId) {
|
|
1351
1414
|
throw new TableRecordConflictError(pinnedTableId, table.revision, table.revision);
|
|
1352
1415
|
}
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
this.#planCache.clear();
|
|
1378
|
-
return true;
|
|
1379
|
-
} catch (error) {
|
|
1380
|
-
if (!(error instanceof TableRecordConflictError) && !(error instanceof TableInUseError) && !(error instanceof WriteConflictError) || attempt >= this.#maxCommitRetries) {
|
|
1381
|
-
throw error;
|
|
1416
|
+
this.#assertColumnDropSafe(records, table, column);
|
|
1417
|
+
const buildKey = `${table.id}/${column.id}`;
|
|
1418
|
+
this.#droppingFtsColumns.add(buildKey);
|
|
1419
|
+
try {
|
|
1420
|
+
await this.#cancelTableCompactions(table.id);
|
|
1421
|
+
const manifest = await this.store.dropTableColumn({
|
|
1422
|
+
tableId: table.id,
|
|
1423
|
+
columnId: column.id,
|
|
1424
|
+
expectedTableRevision: table.revision,
|
|
1425
|
+
expectedManifestVersion: await this.store.getCurrentManifestVersion(),
|
|
1426
|
+
expectedCatalogEpoch: catalogProof.catalogEpoch,
|
|
1427
|
+
committedAt: dateIsoString(this.#now())
|
|
1428
|
+
});
|
|
1429
|
+
this.#afterCommit(manifest);
|
|
1430
|
+
this.#gzipVerdicts.delete(column.id);
|
|
1431
|
+
this.#postingDeltaTailCounts.delete(buildKey);
|
|
1432
|
+
this.#planCache.clear();
|
|
1433
|
+
return true;
|
|
1434
|
+
} catch (error) {
|
|
1435
|
+
if (!(error instanceof TableRecordConflictError) && !(error instanceof TableInUseError) && !(error instanceof WriteConflictError) || attempt >= this.#maxCommitRetries) {
|
|
1436
|
+
throw error;
|
|
1437
|
+
}
|
|
1438
|
+
} finally {
|
|
1439
|
+
this.#droppingFtsColumns.delete(buildKey);
|
|
1382
1440
|
}
|
|
1383
|
-
} finally {
|
|
1384
|
-
this.#droppingFtsColumns.delete(buildKey);
|
|
1385
1441
|
}
|
|
1386
|
-
}
|
|
1442
|
+
});
|
|
1387
1443
|
}
|
|
1388
1444
|
#assertColumnDropSafe(records, table, column) {
|
|
1389
1445
|
if (table.columns.length === 1) {
|
|
@@ -1433,84 +1489,86 @@ class MinnowDatabase {
|
|
|
1433
1489
|
}
|
|
1434
1490
|
}
|
|
1435
1491
|
async dropTable(tableName, options = {}) {
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
if (
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
const table = records.find((record) => record.name === tableName);
|
|
1447
|
-
if (table === void 0) {
|
|
1448
|
-
if (pinnedTableId !== void 0) {
|
|
1449
|
-
throw new TableRecordConflictError(pinnedTableId, 0, null);
|
|
1492
|
+
return this.#foreground(async () => {
|
|
1493
|
+
let pinnedTableId;
|
|
1494
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
1495
|
+
const catalogProof = await this.store.getCatalogProbe();
|
|
1496
|
+
const records = await this.store.listTables();
|
|
1497
|
+
if ((await this.store.getCatalogProbe()).catalogEpoch !== catalogProof.catalogEpoch) {
|
|
1498
|
+
if (attempt >= this.#maxCommitRetries) {
|
|
1499
|
+
throw new TableRecordConflictError(tableName, 0, null);
|
|
1500
|
+
}
|
|
1501
|
+
continue;
|
|
1450
1502
|
}
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
if (table.view !== void 0)
|
|
1460
|
-
throw new TypeError(`${tableName} is a view; use DROP VIEW`);
|
|
1461
|
-
for (const owner of records) {
|
|
1462
|
-
if (owner.id === table.id)
|
|
1463
|
-
continue;
|
|
1464
|
-
if (owner.view !== void 0 && viewReadsTable(owner.view.sql, table.name)) {
|
|
1465
|
-
throw new TypeError(`Cannot drop ${table.name}: view ${owner.name} reads it`);
|
|
1503
|
+
const table = records.find((record) => record.name === tableName);
|
|
1504
|
+
if (table === void 0) {
|
|
1505
|
+
if (pinnedTableId !== void 0) {
|
|
1506
|
+
throw new TableRecordConflictError(pinnedTableId, 0, null);
|
|
1507
|
+
}
|
|
1508
|
+
if (options.ifExists === true)
|
|
1509
|
+
return false;
|
|
1510
|
+
throw new UnknownTableError(tableName);
|
|
1466
1511
|
}
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1512
|
+
pinnedTableId ??= table.id;
|
|
1513
|
+
if (table.id !== pinnedTableId) {
|
|
1514
|
+
throw new TableRecordConflictError(pinnedTableId, 0, table.revision);
|
|
1515
|
+
}
|
|
1516
|
+
if (table.view !== void 0)
|
|
1517
|
+
throw new TypeError(`${tableName} is a view; use DROP VIEW`);
|
|
1518
|
+
for (const owner of records) {
|
|
1519
|
+
if (owner.id === table.id)
|
|
1520
|
+
continue;
|
|
1521
|
+
if (owner.view !== void 0 && viewReadsTable(owner.view.sql, table.name)) {
|
|
1522
|
+
throw new TypeError(`Cannot drop ${table.name}: view ${owner.name} reads it`);
|
|
1523
|
+
}
|
|
1524
|
+
for (const key of owner.foreignKeys ?? []) {
|
|
1525
|
+
if (key.parentTable === table.name) {
|
|
1526
|
+
throw new TypeError(`Cannot drop ${table.name}: foreign key ${key.name} on ${owner.name} references it`);
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
for (const trigger of owner.triggers ?? []) {
|
|
1530
|
+
const writesHere = trigger.statements.some((triggerStatement) => {
|
|
1531
|
+
const compiled = compileStatement(triggerStatement.sql);
|
|
1532
|
+
return (compiled.kind === "insert" || compiled.kind === "update" || compiled.kind === "delete") && compiled.table === table.name;
|
|
1533
|
+
});
|
|
1534
|
+
if (writesHere) {
|
|
1535
|
+
throw new TypeError(`Cannot drop ${table.name}: trigger ${trigger.name} on ${owner.name} writes to it`);
|
|
1536
|
+
}
|
|
1470
1537
|
}
|
|
1471
1538
|
}
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1539
|
+
this.#droppingTables.add(table.id);
|
|
1540
|
+
try {
|
|
1541
|
+
await this.#cancelTableCompactions(table.id);
|
|
1542
|
+
const manifest = await this.store.dropTable({
|
|
1543
|
+
tableId: table.id,
|
|
1544
|
+
expectedTableRevision: table.revision,
|
|
1545
|
+
expectedManifestVersion: await this.store.getCurrentManifestVersion(),
|
|
1546
|
+
expectedCatalogEpoch: catalogProof.catalogEpoch,
|
|
1547
|
+
committedAt: dateIsoString(this.#now())
|
|
1476
1548
|
});
|
|
1477
|
-
|
|
1478
|
-
|
|
1549
|
+
this.#afterCommit(manifest);
|
|
1550
|
+
for (const column of table.columns)
|
|
1551
|
+
this.#gzipVerdicts.delete(column.id);
|
|
1552
|
+
this.#autoCompactionBackoff.delete(table.id);
|
|
1553
|
+
this.#autoCompactionHints.delete(table.id);
|
|
1554
|
+
this.#commitsSinceCompactionCheck.delete(table.id);
|
|
1555
|
+
this.#idleCompactionTableIds.delete(table.id);
|
|
1556
|
+
const postingPrefix = `${table.id}/`;
|
|
1557
|
+
for (const key of this.#postingDeltaTailCounts.keys()) {
|
|
1558
|
+
if (key.startsWith(postingPrefix))
|
|
1559
|
+
this.#postingDeltaTailCounts.delete(key);
|
|
1479
1560
|
}
|
|
1561
|
+
this.#planCache.clear();
|
|
1562
|
+
return true;
|
|
1563
|
+
} catch (error) {
|
|
1564
|
+
if (!(error instanceof TableRecordConflictError) && !(error instanceof TableInUseError) && !(error instanceof WriteConflictError) || attempt >= this.#maxCommitRetries) {
|
|
1565
|
+
throw error;
|
|
1566
|
+
}
|
|
1567
|
+
} finally {
|
|
1568
|
+
this.#droppingTables.delete(table.id);
|
|
1480
1569
|
}
|
|
1481
1570
|
}
|
|
1482
|
-
|
|
1483
|
-
try {
|
|
1484
|
-
await this.#cancelTableCompactions(table.id);
|
|
1485
|
-
const manifest = await this.store.dropTable({
|
|
1486
|
-
tableId: table.id,
|
|
1487
|
-
expectedTableRevision: table.revision,
|
|
1488
|
-
expectedManifestVersion: await this.store.getCurrentManifestVersion(),
|
|
1489
|
-
expectedCatalogEpoch: catalogProof.catalogEpoch,
|
|
1490
|
-
committedAt: dateIsoString(this.#now())
|
|
1491
|
-
});
|
|
1492
|
-
this.#afterCommit(manifest);
|
|
1493
|
-
for (const column of table.columns)
|
|
1494
|
-
this.#gzipVerdicts.delete(column.id);
|
|
1495
|
-
this.#autoCompactionBackoff.delete(table.id);
|
|
1496
|
-
this.#autoCompactionHints.delete(table.id);
|
|
1497
|
-
this.#commitsSinceCompactionCheck.delete(table.id);
|
|
1498
|
-
this.#idleCompactionTableIds.delete(table.id);
|
|
1499
|
-
const postingPrefix = `${table.id}/`;
|
|
1500
|
-
for (const key of this.#postingDeltaTailCounts.keys()) {
|
|
1501
|
-
if (key.startsWith(postingPrefix))
|
|
1502
|
-
this.#postingDeltaTailCounts.delete(key);
|
|
1503
|
-
}
|
|
1504
|
-
this.#planCache.clear();
|
|
1505
|
-
return true;
|
|
1506
|
-
} catch (error) {
|
|
1507
|
-
if (!(error instanceof TableRecordConflictError) && !(error instanceof TableInUseError) && !(error instanceof WriteConflictError) || attempt >= this.#maxCommitRetries) {
|
|
1508
|
-
throw error;
|
|
1509
|
-
}
|
|
1510
|
-
} finally {
|
|
1511
|
-
this.#droppingTables.delete(table.id);
|
|
1512
|
-
}
|
|
1513
|
-
}
|
|
1571
|
+
});
|
|
1514
1572
|
}
|
|
1515
1573
|
insertBatch(tableName, input) {
|
|
1516
1574
|
return this.#withWriteReservation(() => this.#insertBatchReserved(tableName, input));
|
|
@@ -1569,7 +1627,7 @@ class MinnowDatabase {
|
|
|
1569
1627
|
}
|
|
1570
1628
|
throw new TypeError(`DEFAULT produced an unsupported value: ${sql}`);
|
|
1571
1629
|
}
|
|
1572
|
-
async #materializeInsertExpressions(statement, statementNow) {
|
|
1630
|
+
async #materializeInsertExpressions(statement, statementNow, read) {
|
|
1573
1631
|
if (!statement.rows.some((row) => row.some(isDeferredInsertExpression)))
|
|
1574
1632
|
return statement;
|
|
1575
1633
|
const rows = [];
|
|
@@ -1594,7 +1652,7 @@ class MinnowDatabase {
|
|
|
1594
1652
|
usesVolatileFunctions: true
|
|
1595
1653
|
};
|
|
1596
1654
|
plan = resolveStatementDatetimes(plan, statementNow);
|
|
1597
|
-
const result = await
|
|
1655
|
+
const result = await read(plan);
|
|
1598
1656
|
materialized.push(result.rows[0]?.value ?? null);
|
|
1599
1657
|
}
|
|
1600
1658
|
rows.push(materialized);
|
|
@@ -1602,7 +1660,9 @@ class MinnowDatabase {
|
|
|
1602
1660
|
return { ...statement, rows };
|
|
1603
1661
|
}
|
|
1604
1662
|
async insert(tableName, row) {
|
|
1605
|
-
return this.#
|
|
1663
|
+
return this.#foreground(async () => {
|
|
1664
|
+
return this.#erased.insertBatch(tableName, [row]);
|
|
1665
|
+
});
|
|
1606
1666
|
}
|
|
1607
1667
|
upsertBatch(tableName, input, options = {}) {
|
|
1608
1668
|
return this.#withWriteReservation(() => this.#upsertBatchReserved(tableName, input, options));
|
|
@@ -1644,7 +1704,9 @@ class MinnowDatabase {
|
|
|
1644
1704
|
};
|
|
1645
1705
|
}
|
|
1646
1706
|
async upsert(tableName, row, options = {}) {
|
|
1647
|
-
return this.#
|
|
1707
|
+
return this.#foreground(async () => {
|
|
1708
|
+
return this.#erased.upsertBatch(tableName, [row], options);
|
|
1709
|
+
});
|
|
1648
1710
|
}
|
|
1649
1711
|
updateBatch(tableName, input) {
|
|
1650
1712
|
return this.#withWriteReservation(() => this.#updateBatchReserved(tableName, compactUpdateBatchInput(input)));
|
|
@@ -1670,13 +1732,17 @@ class MinnowDatabase {
|
|
|
1670
1732
|
});
|
|
1671
1733
|
}
|
|
1672
1734
|
async update(tableName, key, changes) {
|
|
1673
|
-
return this.#
|
|
1674
|
-
|
|
1675
|
-
|
|
1735
|
+
return this.#foreground(async () => {
|
|
1736
|
+
return this.#erased.updateBatch(tableName, {
|
|
1737
|
+
keys: [key],
|
|
1738
|
+
changes: Object.fromEntries(Object.entries(changes).flatMap(([name, value]) => value === void 0 ? [] : [[name, [value]]]))
|
|
1739
|
+
});
|
|
1676
1740
|
});
|
|
1677
1741
|
}
|
|
1678
1742
|
async delete(tableName, key) {
|
|
1679
|
-
return this.#
|
|
1743
|
+
return this.#foreground(async () => {
|
|
1744
|
+
return this.#erased.deleteBatch(tableName, { keys: [key] });
|
|
1745
|
+
});
|
|
1680
1746
|
}
|
|
1681
1747
|
deleteBatch(tableName, input) {
|
|
1682
1748
|
return this.#withWriteReservation(() => this.#deleteBatchReserved(tableName, input));
|
|
@@ -2326,7 +2392,9 @@ class MinnowDatabase {
|
|
|
2326
2392
|
}
|
|
2327
2393
|
}
|
|
2328
2394
|
async readTable(tableName, versionOrOptions) {
|
|
2329
|
-
return this.#
|
|
2395
|
+
return this.#foreground(async () => {
|
|
2396
|
+
return this.#withReadReservation(() => this.#readTableUnreserved(tableName, versionOrOptions));
|
|
2397
|
+
});
|
|
2330
2398
|
}
|
|
2331
2399
|
async #readTableUnreserved(tableName, versionOrOptions) {
|
|
2332
2400
|
const table = await this.#findTable(tableName);
|
|
@@ -2352,21 +2420,50 @@ class MinnowDatabase {
|
|
|
2352
2420
|
}).rows;
|
|
2353
2421
|
}
|
|
2354
2422
|
async snapshot(action) {
|
|
2423
|
+
return this.#ownScope((signal) => this.#snapshot(action, signal));
|
|
2424
|
+
}
|
|
2425
|
+
async #snapshot(action, signal) {
|
|
2355
2426
|
for (; ; ) {
|
|
2356
2427
|
const version = await this.store.getCurrentManifestVersion();
|
|
2357
2428
|
const entry = await this.#acquireSharedLease(version);
|
|
2358
2429
|
if (entry === void 0)
|
|
2359
2430
|
continue;
|
|
2431
|
+
let active = true;
|
|
2432
|
+
let renewalError;
|
|
2433
|
+
let renewal;
|
|
2434
|
+
const renew = () => {
|
|
2435
|
+
renewal ??= this.#renewInternalLeaseIfNeeded(entry.lease).catch((error) => {
|
|
2436
|
+
renewalError = error instanceof Error ? error : new Error("Snapshot renewal failed", { cause: error });
|
|
2437
|
+
}).finally(() => {
|
|
2438
|
+
renewal = void 0;
|
|
2439
|
+
});
|
|
2440
|
+
return renewal;
|
|
2441
|
+
};
|
|
2442
|
+
const timer = setInterval(() => {
|
|
2443
|
+
void renew();
|
|
2444
|
+
}, INTERNAL_READ_LEASE_TTL_MS / 3);
|
|
2445
|
+
timer.unref?.();
|
|
2360
2446
|
try {
|
|
2361
2447
|
const session = {
|
|
2362
2448
|
version,
|
|
2363
|
-
query: (sql, options = {}) =>
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2449
|
+
query: async (sql, options = {}) => {
|
|
2450
|
+
signal.throwIfAborted();
|
|
2451
|
+
if (!active)
|
|
2452
|
+
throw new Error("The snapshot scope has ended");
|
|
2453
|
+
await renew();
|
|
2454
|
+
if (renewalError !== void 0)
|
|
2455
|
+
throw renewalError;
|
|
2456
|
+
return this.#queryWithReadReservation(sql, { ...options, version });
|
|
2457
|
+
}
|
|
2367
2458
|
};
|
|
2368
|
-
|
|
2459
|
+
const result = await untilAborted(() => action(session), signal);
|
|
2460
|
+
if (renewalError !== void 0)
|
|
2461
|
+
throw renewalError;
|
|
2462
|
+
return result;
|
|
2369
2463
|
} finally {
|
|
2464
|
+
active = false;
|
|
2465
|
+
clearInterval(timer);
|
|
2466
|
+
await renewal;
|
|
2370
2467
|
this.#releaseSharedLease(entry);
|
|
2371
2468
|
}
|
|
2372
2469
|
}
|
|
@@ -2873,7 +2970,7 @@ class MinnowDatabase {
|
|
|
2873
2970
|
async #queryUnreserved(sql, options, probe, storeMemo = true) {
|
|
2874
2971
|
throwIfAborted(options.signal);
|
|
2875
2972
|
const open = this.#openTransaction;
|
|
2876
|
-
if (open !== void 0) {
|
|
2973
|
+
if (open !== void 0 && options.version === void 0) {
|
|
2877
2974
|
return externalizeQueryResult(await this.#duringTransaction(open, () => open.session.query(sql, options)));
|
|
2878
2975
|
}
|
|
2879
2976
|
const compiled = this.#compileCached(sql);
|
|
@@ -2903,6 +3000,8 @@ class MinnowDatabase {
|
|
|
2903
3000
|
return externalizeQueryResult(result);
|
|
2904
3001
|
}
|
|
2905
3002
|
queryCursor(sql, options = {}) {
|
|
3003
|
+
if (this.#closed)
|
|
3004
|
+
throw new Error("Database is closed");
|
|
2906
3005
|
const batchRows = positiveWholeNumber(options.batchRows ?? 2048, "Query batch rows");
|
|
2907
3006
|
const controller = new AbortController();
|
|
2908
3007
|
const channel = new QueryBatchChannel();
|
|
@@ -2910,18 +3009,23 @@ class MinnowDatabase {
|
|
|
2910
3009
|
void _batchRows;
|
|
2911
3010
|
let producer;
|
|
2912
3011
|
const abort = () => {
|
|
2913
|
-
const reason = queryCursorError(signal?.reason, "Query cursor is closed");
|
|
3012
|
+
const reason = queryCursorError(signal?.reason ?? this.#shutdown.signal.reason, "Query cursor is closed");
|
|
2914
3013
|
controller.abort(reason);
|
|
2915
3014
|
channel.fail(reason);
|
|
2916
3015
|
};
|
|
2917
|
-
signal?.addEventListener("abort", abort, { once: true });
|
|
2918
|
-
if (signal?.aborted === true)
|
|
2919
|
-
abort();
|
|
2920
3016
|
const start = () => {
|
|
2921
3017
|
if (producer !== void 0)
|
|
2922
3018
|
return;
|
|
3019
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
3020
|
+
if (signal?.aborted === true)
|
|
3021
|
+
abort();
|
|
3022
|
+
this.#shutdown.signal.addEventListener("abort", abort, { once: true });
|
|
3023
|
+
if (this.#closed)
|
|
3024
|
+
abort();
|
|
2923
3025
|
if (controller.signal.aborted) {
|
|
2924
3026
|
producer = Promise.resolve();
|
|
3027
|
+
this.#shutdown.signal.removeEventListener("abort", abort);
|
|
3028
|
+
signal?.removeEventListener("abort", abort);
|
|
2925
3029
|
return;
|
|
2926
3030
|
}
|
|
2927
3031
|
producer = this.#withReadReservation(() => this.#queryBatches(sql, queryOptions, {
|
|
@@ -2929,6 +3033,13 @@ class MinnowDatabase {
|
|
|
2929
3033
|
signal: controller.signal,
|
|
2930
3034
|
consume: (batch) => channel.push(batch)
|
|
2931
3035
|
})).then(() => channel.finish(), (error) => channel.fail(error));
|
|
3036
|
+
const task = producer;
|
|
3037
|
+
this.#foregroundTasks.add(task);
|
|
3038
|
+
void task.finally(() => {
|
|
3039
|
+
this.#foregroundTasks.delete(task);
|
|
3040
|
+
this.#shutdown.signal.removeEventListener("abort", abort);
|
|
3041
|
+
signal?.removeEventListener("abort", abort);
|
|
3042
|
+
});
|
|
2932
3043
|
};
|
|
2933
3044
|
const close = async () => {
|
|
2934
3045
|
if (!controller.signal.aborted)
|
|
@@ -2991,15 +3102,9 @@ class MinnowDatabase {
|
|
|
2991
3102
|
throwIfAborted(options.signal);
|
|
2992
3103
|
before ??= await probe();
|
|
2993
3104
|
throwIfAborted(options.signal);
|
|
2994
|
-
const
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
const rewritten = await this.#applyCatalogRewrites(plan, before);
|
|
2998
|
-
dependencies = [...(await this.#findRealBlockTables(rewritten)).values()].map((table) => table.id).sort();
|
|
2999
|
-
this.#cachePut(dependencyKey, dependencies, 64 + dependencies.reduce((bytes2, id) => bytes2 + id.length * 2 + 16, 0));
|
|
3000
|
-
}
|
|
3001
|
-
const generation = await this.#queryGenerations.key(dependencies, before.manifestVersion);
|
|
3002
|
-
const resultKey = `${key}${String(before.schemaEpoch)}${generation}`;
|
|
3105
|
+
const resultKey = await this.#resultMemoIdentity(plan, key, before);
|
|
3106
|
+
if (resultKey === void 0)
|
|
3107
|
+
return this.#queryCompiled(plan, options, before);
|
|
3003
3108
|
const cached = this.#cacheGet(resultKey);
|
|
3004
3109
|
if (cached !== void 0) {
|
|
3005
3110
|
options.onStats?.({ peakMemoryBytes: 0 });
|
|
@@ -3019,6 +3124,50 @@ class MinnowDatabase {
|
|
|
3019
3124
|
}
|
|
3020
3125
|
return result;
|
|
3021
3126
|
}
|
|
3127
|
+
async #resultMemoIdentity(plan, key, before) {
|
|
3128
|
+
const dependencyKey = `deps ${String(before.schemaEpoch)} ${key}`;
|
|
3129
|
+
let proof = this.#cacheGet(dependencyKey);
|
|
3130
|
+
if (proof === void 0) {
|
|
3131
|
+
const rewritten = await this.#applyCatalogRewrites(plan, before);
|
|
3132
|
+
const dependencies = [...(await this.#findRealBlockTables(rewritten)).values()].map((table) => table.id).sort();
|
|
3133
|
+
proof = {
|
|
3134
|
+
dependencies,
|
|
3135
|
+
deterministic: !blockCallsFunctions(rewritten, nonDeterministicFunctionNames)
|
|
3136
|
+
};
|
|
3137
|
+
this.#cachePut(dependencyKey, proof, 64 + dependencies.reduce((bytes, id) => bytes + id.length * 2 + 16, 0));
|
|
3138
|
+
}
|
|
3139
|
+
if (!proof.deterministic)
|
|
3140
|
+
return void 0;
|
|
3141
|
+
const generation = await this.#queryGenerations.key(proof.dependencies, before.manifestVersion);
|
|
3142
|
+
return `${key}${String(before.schemaEpoch)}${generation}`;
|
|
3143
|
+
}
|
|
3144
|
+
async #memoizeLiveResult(query, result, before) {
|
|
3145
|
+
if (!this.#artifactCache.enabled)
|
|
3146
|
+
return;
|
|
3147
|
+
const bytes = queryResultRetainedBytes(result);
|
|
3148
|
+
if (bytes > RESULT_MEMO_MAX_BYTES)
|
|
3149
|
+
return;
|
|
3150
|
+
const plan = this.#compileLiveQuery(query);
|
|
3151
|
+
let key;
|
|
3152
|
+
if (typeof query !== "string" && query.kind === "typed-query") {
|
|
3153
|
+
key = `typed ${planMemoKey(plan)}`;
|
|
3154
|
+
} else {
|
|
3155
|
+
const sql = typeof query === "string" ? query : query.sql;
|
|
3156
|
+
const params = typeof query === "string" ? [] : query.params;
|
|
3157
|
+
if (!cacheableQueryInput(sql, params))
|
|
3158
|
+
return;
|
|
3159
|
+
key = `res ${queryResultMemoKey(sql, params)}`;
|
|
3160
|
+
}
|
|
3161
|
+
if (plan.usesStatementDatetime || plan.usesVolatileFunctions || plan.usesSequenceCalls)
|
|
3162
|
+
return;
|
|
3163
|
+
const resultKey = await this.#resultMemoIdentity(plan, key, before);
|
|
3164
|
+
if (resultKey === void 0)
|
|
3165
|
+
return;
|
|
3166
|
+
const after = await this.store.getCatalogProbe();
|
|
3167
|
+
if (after.catalogEpoch !== before.catalogEpoch)
|
|
3168
|
+
return;
|
|
3169
|
+
this.#cachePut(resultKey, markQueryResultExternal(copyQueryResult(result)), bytes);
|
|
3170
|
+
}
|
|
3022
3171
|
async #applyCatalogRewrites(plan, probe) {
|
|
3023
3172
|
const aliased = planHasSourceColumnAliases(plan);
|
|
3024
3173
|
const natural = planHasNaturalJoins(plan);
|
|
@@ -3316,11 +3465,12 @@ class MinnowDatabase {
|
|
|
3316
3465
|
const table = realTables.get(shape.table);
|
|
3317
3466
|
if (table === void 0 || table.view !== void 0)
|
|
3318
3467
|
return void 0;
|
|
3319
|
-
const
|
|
3468
|
+
const byName = shape.select === "*" || shape.select.length + shape.equalities.length > 8 ? new Map(table.columns.map((column) => [column.name, column])) : void 0;
|
|
3469
|
+
const findColumn2 = (name) => byName === void 0 ? table.columns.find((column) => column.name === name) : byName.get(name);
|
|
3320
3470
|
const matchesStorageType = (column, value) => column.sqlDomain === void 0 && column.hidden !== true && (column.type === "number" && typeof value === "number" || column.type === "string" && typeof value === "string" || column.type === "boolean" && typeof value === "boolean" || column.type === "datetime" && value instanceof Date);
|
|
3321
3471
|
const equalityColumns = /* @__PURE__ */ new Map();
|
|
3322
3472
|
for (const equality of shape.equalities) {
|
|
3323
|
-
const column =
|
|
3473
|
+
const column = findColumn2(equality.column);
|
|
3324
3474
|
if (column === void 0 || !matchesStorageType(column, equality.value))
|
|
3325
3475
|
return void 0;
|
|
3326
3476
|
equalityColumns.set(column.name, column);
|
|
@@ -3337,7 +3487,7 @@ class MinnowDatabase {
|
|
|
3337
3487
|
const projected = [];
|
|
3338
3488
|
const selected = shape.select === "*" ? visibleTableColumns(table).map((column) => ({ column: column.name, alias: column.name })) : shape.select;
|
|
3339
3489
|
for (const item of selected) {
|
|
3340
|
-
const column =
|
|
3490
|
+
const column = findColumn2(item.column);
|
|
3341
3491
|
if (column === void 0 || column.hidden === true)
|
|
3342
3492
|
return void 0;
|
|
3343
3493
|
projected.push({ column, alias: item.alias });
|
|
@@ -3389,47 +3539,18 @@ class MinnowDatabase {
|
|
|
3389
3539
|
}
|
|
3390
3540
|
if (blockIndexes.length === 0)
|
|
3391
3541
|
continue;
|
|
3392
|
-
const searchBlocks = await this.#
|
|
3542
|
+
const searchBlocks = await this.#pointReadVectors(blockIndexes.map((blockIndex) => searchBlockIds[blockIndex] ?? ""), snapshot);
|
|
3393
3543
|
for (const [position, blockIndex] of blockIndexes.entries()) {
|
|
3394
3544
|
throwIfAborted(options.signal);
|
|
3395
|
-
const
|
|
3396
|
-
if (
|
|
3545
|
+
const vector = searchBlocks[position];
|
|
3546
|
+
if (vector === void 0)
|
|
3397
3547
|
return void 0;
|
|
3398
3548
|
const blockId = searchBlockIds[blockIndex] ?? "";
|
|
3399
|
-
const vector = this.#blockColumnVector(blockId, decoded);
|
|
3400
3549
|
if (vector.kind !== searchColumn.type)
|
|
3401
3550
|
return void 0;
|
|
3402
|
-
const candidates =
|
|
3403
|
-
if (
|
|
3404
|
-
if (decoded.description.nullCount === 0 && valuesAreAscending(vector.values)) {
|
|
3405
|
-
const run = equalRunRange(vector.values, searchTarget);
|
|
3406
|
-
for (let slot = run.begin; slot < run.end; slot += 1)
|
|
3407
|
-
candidates.push(slot);
|
|
3408
|
-
} else {
|
|
3409
|
-
for (let slot = 0; slot < vector.length; slot += 1) {
|
|
3410
|
-
if (vector.values[slot] === searchTarget) {
|
|
3411
|
-
const value = vectorValue(vector, slot);
|
|
3412
|
-
if (value !== null)
|
|
3413
|
-
candidates.push(slot);
|
|
3414
|
-
}
|
|
3415
|
-
}
|
|
3416
|
-
}
|
|
3417
|
-
} else if (vector.kind === "string" && typeof searchTarget === "string") {
|
|
3418
|
-
const code = this.#dictionaryCode(blockId, vector, searchTarget);
|
|
3419
|
-
if (code === void 0)
|
|
3420
|
-
continue;
|
|
3421
|
-
for (let slot = 0; slot < vector.length; slot += 1) {
|
|
3422
|
-
if (vector.codes[slot] === code)
|
|
3423
|
-
candidates.push(slot);
|
|
3424
|
-
}
|
|
3425
|
-
} else if (vector.kind === "boolean" && typeof searchTarget === "boolean") {
|
|
3426
|
-
for (let slot = 0; slot < vector.length; slot += 1) {
|
|
3427
|
-
if (vectorValue(vector, slot) === searchTarget)
|
|
3428
|
-
candidates.push(slot);
|
|
3429
|
-
}
|
|
3430
|
-
} else {
|
|
3551
|
+
const candidates = this.#keySlotsInBlock(blockId, vector, searchTarget);
|
|
3552
|
+
if (candidates === void 0)
|
|
3431
3553
|
return void 0;
|
|
3432
|
-
}
|
|
3433
3554
|
if (candidates.length === 0)
|
|
3434
3555
|
continue;
|
|
3435
3556
|
candidateBudget -= candidates.length;
|
|
@@ -3437,13 +3558,12 @@ class MinnowDatabase {
|
|
|
3437
3558
|
return void 0;
|
|
3438
3559
|
const remainingColumns = neededColumns.filter((column) => column.id !== searchColumn.id);
|
|
3439
3560
|
const rowBlockIds = remainingColumns.map((column) => segment.columnBlockIds[column.id]?.[blockIndex] ?? "");
|
|
3440
|
-
const rowBlocks = await this.#
|
|
3561
|
+
const rowBlocks = await this.#pointReadVectors(rowBlockIds, snapshot);
|
|
3441
3562
|
const vectors = /* @__PURE__ */ new Map([[searchColumn.name, vector]]);
|
|
3442
3563
|
for (const [index, column] of remainingColumns.entries()) {
|
|
3443
|
-
const
|
|
3444
|
-
if (
|
|
3564
|
+
const columnVector = rowBlocks[index];
|
|
3565
|
+
if (columnVector === void 0)
|
|
3445
3566
|
return void 0;
|
|
3446
|
-
const columnVector = this.#blockColumnVector(rowBlockIds[index] ?? "", block);
|
|
3447
3567
|
if (columnVector.kind !== column.type)
|
|
3448
3568
|
return void 0;
|
|
3449
3569
|
vectors.set(column.name, columnVector);
|
|
@@ -3487,13 +3607,10 @@ class MinnowDatabase {
|
|
|
3487
3607
|
rows
|
|
3488
3608
|
};
|
|
3489
3609
|
}
|
|
3490
|
-
#keySlotsInBlock(blockId,
|
|
3491
|
-
const vector = this.#blockColumnVector(blockId, decoded);
|
|
3492
|
-
if (vector.kind !== column.type)
|
|
3493
|
-
return void 0;
|
|
3610
|
+
#keySlotsInBlock(blockId, vector, target) {
|
|
3494
3611
|
const slots = [];
|
|
3495
3612
|
if ((vector.kind === "number" || vector.kind === "datetime") && typeof target === "number") {
|
|
3496
|
-
if (
|
|
3613
|
+
if (vector.nullCount === 0 && valuesAreAscending(vector.values)) {
|
|
3497
3614
|
const run = equalRunRange(vector.values, target);
|
|
3498
3615
|
for (let slot = run.begin; slot < run.end; slot += 1)
|
|
3499
3616
|
slots.push(slot);
|
|
@@ -3555,13 +3672,15 @@ class MinnowDatabase {
|
|
|
3555
3672
|
decodedBlocks += blockIndexes.length;
|
|
3556
3673
|
if (decodedBlocks > MAX_POINT_READ_DELTA_BLOCKS)
|
|
3557
3674
|
return void 0;
|
|
3558
|
-
const keyBlocks = await this.#
|
|
3675
|
+
const keyBlocks = await this.#pointReadVectors(blockIndexes.map((blockIndex) => keyBlockIds[blockIndex] ?? ""), snapshot);
|
|
3559
3676
|
for (const [position, blockIndex] of blockIndexes.entries()) {
|
|
3560
|
-
const
|
|
3561
|
-
if (
|
|
3677
|
+
const keyVector = keyBlocks[position];
|
|
3678
|
+
if (keyVector === void 0)
|
|
3562
3679
|
return void 0;
|
|
3563
3680
|
const blockId = keyBlockIds[blockIndex] ?? "";
|
|
3564
|
-
|
|
3681
|
+
if (keyVector.kind !== keyColumn.type)
|
|
3682
|
+
return void 0;
|
|
3683
|
+
const slots = this.#keySlotsInBlock(blockId, keyVector, target);
|
|
3565
3684
|
if (slots === void 0)
|
|
3566
3685
|
return void 0;
|
|
3567
3686
|
if (slots.length === 0)
|
|
@@ -3582,21 +3701,17 @@ class MinnowDatabase {
|
|
|
3582
3701
|
decodedBlocks += blockIds.length;
|
|
3583
3702
|
if (decodedBlocks > MAX_POINT_READ_DELTA_BLOCKS)
|
|
3584
3703
|
return void 0;
|
|
3585
|
-
const blocks = await this.#
|
|
3704
|
+
const blocks = await this.#pointReadVectors(blockIds, snapshot);
|
|
3586
3705
|
const values = segment.kind === "update" && current !== void 0 ? current : /* @__PURE__ */ new Map();
|
|
3587
3706
|
if (segment.kind !== "update") {
|
|
3588
3707
|
if (current !== void 0)
|
|
3589
3708
|
return void 0;
|
|
3590
|
-
values.set(keyColumn.name, vectorValue(
|
|
3709
|
+
values.set(keyColumn.name, vectorValue(keyVector, slot));
|
|
3591
3710
|
}
|
|
3592
3711
|
for (const [index, column] of carried.entries()) {
|
|
3593
|
-
const
|
|
3594
|
-
|
|
3595
|
-
if (block === void 0 || columnBlockId === void 0)
|
|
3596
|
-
return void 0;
|
|
3597
|
-
if (block.column.rowCount !== decoded.column.rowCount)
|
|
3712
|
+
const vector = blocks[index];
|
|
3713
|
+
if (vector?.length !== keyVector.length)
|
|
3598
3714
|
return void 0;
|
|
3599
|
-
const vector = this.#blockColumnVector(columnBlockId, block);
|
|
3600
3715
|
if (vector.kind !== column.type)
|
|
3601
3716
|
return void 0;
|
|
3602
3717
|
values.set(column.name, vectorValue(vector, slot));
|
|
@@ -3719,30 +3834,32 @@ class MinnowDatabase {
|
|
|
3719
3834
|
};
|
|
3720
3835
|
}
|
|
3721
3836
|
async cleanupQuerySpill(options = {}) {
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3837
|
+
return this.#foreground(async () => {
|
|
3838
|
+
const maxOwners = options.maxOwners === void 0 ? Number.MAX_SAFE_INTEGER : positiveWholeNumber(options.maxOwners, "Spill cleanup owner limit");
|
|
3839
|
+
const cutoff = dateIsoString(this.#now());
|
|
3840
|
+
const result = {
|
|
3841
|
+
ownersExamined: 0,
|
|
3842
|
+
ownersReclaimed: 0,
|
|
3843
|
+
ownersRetained: 0
|
|
3844
|
+
};
|
|
3845
|
+
let cursor = null;
|
|
3846
|
+
while (result.ownersExamined < maxOwners) {
|
|
3847
|
+
const page = await this.store.listTempOwnerIdsPage(cursor, Math.min(64, maxOwners - result.ownersExamined));
|
|
3848
|
+
for (const ownerId of page.records) {
|
|
3849
|
+
result.ownersExamined += 1;
|
|
3850
|
+
if (await this.store.removeTempOwnerIfExpired(ownerId, cutoff)) {
|
|
3851
|
+
result.ownersReclaimed += 1;
|
|
3852
|
+
} else {
|
|
3853
|
+
result.ownersRetained += 1;
|
|
3854
|
+
}
|
|
3738
3855
|
}
|
|
3856
|
+
if (page.nextCursor === null)
|
|
3857
|
+
break;
|
|
3858
|
+
cursor = page.nextCursor;
|
|
3859
|
+
await this.#yieldMaintenance();
|
|
3739
3860
|
}
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
cursor = page.nextCursor;
|
|
3743
|
-
await this.#yieldMaintenance();
|
|
3744
|
-
}
|
|
3745
|
-
return result;
|
|
3861
|
+
return result;
|
|
3862
|
+
});
|
|
3746
3863
|
}
|
|
3747
3864
|
async #cleanupExpiredQuerySpill(maxOwners) {
|
|
3748
3865
|
const cutoff = dateIsoString(this.#now());
|
|
@@ -3776,26 +3893,18 @@ class MinnowDatabase {
|
|
|
3776
3893
|
throw new LiveQueryLimitError("set", MAX_LIVE_QUERY_SETS_PER_DATABASE);
|
|
3777
3894
|
}
|
|
3778
3895
|
const channelName = options.channel === void 0 && options.channelName === void 0 ? this.store.liveQueryChannelName : options.channelName;
|
|
3779
|
-
const compileLiveQuery = (query) => {
|
|
3780
|
-
if (typeof query === "string")
|
|
3781
|
-
return this.#compileCached(query);
|
|
3782
|
-
if (query.kind === "typed-query")
|
|
3783
|
-
return query.plan;
|
|
3784
|
-
return bindPlanParameters(this.#compileCached(query.sql), query.params);
|
|
3785
|
-
};
|
|
3786
3896
|
const set = new LiveQuerySet({
|
|
3787
|
-
currentProbe:
|
|
3788
|
-
return this.store.getCatalogProbe();
|
|
3789
|
-
},
|
|
3897
|
+
currentProbe: () => this.store.getCatalogProbe(),
|
|
3790
3898
|
manifestPage: (afterVersion, limit) => this.store.listManifestPage(afterVersion, limit),
|
|
3791
3899
|
dependencyTableIds: async (query, probe) => {
|
|
3792
|
-
const compiled = compileLiveQuery(query);
|
|
3900
|
+
const compiled = this.#compileLiveQuery(query);
|
|
3793
3901
|
const plan = await this.#applyCatalogRewrites(compiled, probe);
|
|
3794
3902
|
const tables = await this.#findRealBlockTables(plan);
|
|
3795
3903
|
return new Set([...tables.values()].map((record) => record.id));
|
|
3796
3904
|
},
|
|
3797
3905
|
execute: (query, context) => this.#liveExecute(query, context),
|
|
3798
|
-
|
|
3906
|
+
memoize: (query, result, probe) => this.#memoizeLiveResult(query, result, probe),
|
|
3907
|
+
executeMaintainable: (query, context) => this.#liveExecuteMaintainable(this.#compileLiveQuery(query), context),
|
|
3799
3908
|
maintain: (_query, result, state, tableIds, after, until, probe) => this.#liveMaintain(result, state, tableIds, after, until, probe),
|
|
3800
3909
|
changeCanAffect: (query, tableIds, after, until) => this.#liveChangeCanAffect(query, tableIds, after, until)
|
|
3801
3910
|
}, {
|
|
@@ -3809,6 +3918,13 @@ class MinnowDatabase {
|
|
|
3809
3918
|
this.#liveSets.add(set);
|
|
3810
3919
|
return set;
|
|
3811
3920
|
}
|
|
3921
|
+
#compileLiveQuery(query) {
|
|
3922
|
+
if (typeof query === "string")
|
|
3923
|
+
return this.#compileCached(query);
|
|
3924
|
+
if (query.kind === "typed-query")
|
|
3925
|
+
return query.plan;
|
|
3926
|
+
return bindPlanParameters(this.#compileCached(query.sql), query.params);
|
|
3927
|
+
}
|
|
3812
3928
|
async #liveExecute(query, context) {
|
|
3813
3929
|
if (context === void 0) {
|
|
3814
3930
|
if (typeof query === "string")
|
|
@@ -3832,6 +3948,8 @@ class MinnowDatabase {
|
|
|
3832
3948
|
return void 0;
|
|
3833
3949
|
}
|
|
3834
3950
|
const plan = await this.#applyCatalogRewrites(compiled, probe);
|
|
3951
|
+
if (blockCallsFunctions(plan, nonDeterministicFunctionNames))
|
|
3952
|
+
return void 0;
|
|
3835
3953
|
const base = plan.base;
|
|
3836
3954
|
if (base.table === DUAL_TABLE || base.derived !== void 0 || base.union !== void 0 || base.recursive !== void 0 || base.windowed !== void 0 || plan.pendingSelectShape !== void 0 || plan.distinctWildcard === true || plan.limitParameter !== void 0 || plan.offsetParameter !== void 0 || planReadsBeyondSingleScan(plan) || planContainsFts(plan)) {
|
|
3837
3955
|
return void 0;
|
|
@@ -3953,18 +4071,19 @@ class MinnowDatabase {
|
|
|
3953
4071
|
try {
|
|
3954
4072
|
const input = await this.#withReadReservation(() => this.#queryCompiled(state.fullPlan, {}, probe));
|
|
3955
4073
|
const aggregate = state.aggregate.patch(input, /* @__PURE__ */ new Set(), liveKeyToken);
|
|
3956
|
-
|
|
3957
|
-
|
|
4074
|
+
const result = aggregate.result();
|
|
4075
|
+
return stageLiveExecution({
|
|
4076
|
+
result,
|
|
3958
4077
|
state: { ...state, aggregate },
|
|
3959
|
-
retainedBytes: aggregate.retainedBytes
|
|
3960
|
-
};
|
|
4078
|
+
retainedBytes: aggregate.retainedBytes + queryResultRetainedBytes(result) + result.rows.length * 48
|
|
4079
|
+
}, () => aggregate.accept());
|
|
3961
4080
|
} catch {
|
|
3962
4081
|
return void 0;
|
|
3963
4082
|
}
|
|
3964
4083
|
}
|
|
3965
4084
|
let executed;
|
|
3966
4085
|
try {
|
|
3967
|
-
executed =
|
|
4086
|
+
executed = await this.#withReadReservation(() => this.#queryCompiled(state.fullPlan, {}, probe));
|
|
3968
4087
|
} catch {
|
|
3969
4088
|
return void 0;
|
|
3970
4089
|
}
|
|
@@ -4017,12 +4136,12 @@ class MinnowDatabase {
|
|
|
4017
4136
|
const previous = retained.rows[index];
|
|
4018
4137
|
return previous === void 0 || !sameLiveRow(previous, row, result.columns);
|
|
4019
4138
|
});
|
|
4020
|
-
return {
|
|
4139
|
+
return stageLiveExecution({
|
|
4021
4140
|
result,
|
|
4022
4141
|
state: { ...state, aggregate },
|
|
4023
|
-
retainedBytes: aggregate.retainedBytes,
|
|
4142
|
+
retainedBytes: aggregate.retainedBytes + queryResultRetainedBytes(result) + result.rows.length * 48,
|
|
4024
4143
|
changed: changed2
|
|
4025
|
-
};
|
|
4144
|
+
}, () => aggregate.accept());
|
|
4026
4145
|
}
|
|
4027
4146
|
const affected = /* @__PURE__ */ new Set();
|
|
4028
4147
|
const retainedTokens = state.positions;
|
|
@@ -4054,7 +4173,9 @@ class MinnowDatabase {
|
|
|
4054
4173
|
}
|
|
4055
4174
|
]
|
|
4056
4175
|
};
|
|
4057
|
-
const delta = splitLiveHiddenColumns(
|
|
4176
|
+
const delta = splitLiveHiddenColumns(await this.#withReadReservation(() => this.#queryCompiled(deltaPlan, {}, probe)), state);
|
|
4177
|
+
if (affected.size === 0 && delta.result.rows.length === 0)
|
|
4178
|
+
return { result: retained, state, changed: false };
|
|
4058
4179
|
const compare = liveOrderComparator(state.orderTerms);
|
|
4059
4180
|
const added = {
|
|
4060
4181
|
rows: delta.result.rows,
|
|
@@ -4074,6 +4195,18 @@ class MinnowDatabase {
|
|
|
4074
4195
|
} catch {
|
|
4075
4196
|
return void 0;
|
|
4076
4197
|
}
|
|
4198
|
+
let retainedBytes = state.retainedBytes;
|
|
4199
|
+
if (retainedBytes !== void 0) {
|
|
4200
|
+
for (const index of affected)
|
|
4201
|
+
retainedBytes -= liveRowStateBytes(state, index);
|
|
4202
|
+
for (let index = 0; index < added.rows.length; index += 1)
|
|
4203
|
+
retainedBytes += liveRowStateBytes(added, index);
|
|
4204
|
+
if (fullLimit !== void 0) {
|
|
4205
|
+
for (let index = fullLimit; index < merged.rows.length; index += 1)
|
|
4206
|
+
retainedBytes -= liveRowStateBytes(merged, index);
|
|
4207
|
+
}
|
|
4208
|
+
}
|
|
4209
|
+
const complete = state.complete && (fullLimit === void 0 || merged.rows.length <= fullLimit);
|
|
4077
4210
|
if (fullLimit !== void 0 && merged.rows.length > fullLimit)
|
|
4078
4211
|
merged = trimLiveRows(merged, fullLimit);
|
|
4079
4212
|
if (truncated && affected.size > 0 && state.limit !== void 0) {
|
|
@@ -4083,10 +4216,17 @@ class MinnowDatabase {
|
|
|
4083
4216
|
return void 0;
|
|
4084
4217
|
}
|
|
4085
4218
|
}
|
|
4086
|
-
return liveMaintainedOutcome({
|
|
4219
|
+
return liveMaintainedOutcome({
|
|
4220
|
+
...state,
|
|
4221
|
+
complete,
|
|
4222
|
+
rows: merged.rows,
|
|
4223
|
+
keys: merged.keys,
|
|
4224
|
+
order: merged.order,
|
|
4225
|
+
...retainedBytes === void 0 ? {} : { retainedBytes }
|
|
4226
|
+
}, retained, merged.previousIndex);
|
|
4087
4227
|
}
|
|
4088
4228
|
async #liveChangeCanAffect(query, tableIds, after, until) {
|
|
4089
|
-
const plan =
|
|
4229
|
+
const plan = this.#compileLiveQuery(query);
|
|
4090
4230
|
if (planContainsFts(plan))
|
|
4091
4231
|
return true;
|
|
4092
4232
|
if (planReadsBeyondSingleScan(plan))
|
|
@@ -4812,7 +4952,7 @@ class MinnowDatabase {
|
|
|
4812
4952
|
}
|
|
4813
4953
|
}
|
|
4814
4954
|
const statementNow = this.#now();
|
|
4815
|
-
const materialized = await this.#materializeInsertExpressions({ ...compiled, columns, rows: derivedRows }, statementNow);
|
|
4955
|
+
const materialized = await this.#materializeInsertExpressions({ ...compiled, columns, rows: derivedRows }, statementNow, async (plan) => this.#sessionQueryPlan(transaction, await this.#applyCatalogRewrites(plan)));
|
|
4816
4956
|
const input = insertStatementBatch(materialized);
|
|
4817
4957
|
coerceSqlWriteBatch(target, input);
|
|
4818
4958
|
const filled = await fillColumnDefaults(target, input, (sql) => this.#evaluateDefaultExpression(sql, statementNow), derivedRows.length);
|
|
@@ -4969,16 +5109,47 @@ class MinnowDatabase {
|
|
|
4969
5109
|
async write(action) {
|
|
4970
5110
|
return this.#openWriteScope((session) => action(session));
|
|
4971
5111
|
}
|
|
5112
|
+
#coordinateWrite(run) {
|
|
5113
|
+
return this.#coordinateWrites ? coordinateWrite(this.store, run, this.#shutdown.signal) : run();
|
|
5114
|
+
}
|
|
4972
5115
|
async #openWriteScope(action, options = {}) {
|
|
5116
|
+
return this.#ownScope((signal) => this.#runWriteScope(action, options, signal));
|
|
5117
|
+
}
|
|
5118
|
+
async #ownScope(run) {
|
|
5119
|
+
if (this.#closed)
|
|
5120
|
+
throw new Error("Database is closed");
|
|
5121
|
+
if (this.#scopeTasks.size >= MAX_DATABASE_ACTIVE_SCOPES) {
|
|
5122
|
+
throw new RangeError("Too many active database scopes; await a scope before opening another");
|
|
5123
|
+
}
|
|
5124
|
+
const controller = new AbortController();
|
|
5125
|
+
this.#scopeControllers.add(controller);
|
|
5126
|
+
const task = run(controller.signal);
|
|
5127
|
+
this.#scopeTasks.add(task);
|
|
5128
|
+
try {
|
|
5129
|
+
return await task;
|
|
5130
|
+
} finally {
|
|
5131
|
+
this.#scopeControllers.delete(controller);
|
|
5132
|
+
this.#scopeTasks.delete(task);
|
|
5133
|
+
}
|
|
5134
|
+
}
|
|
5135
|
+
async #runWriteScope(action, options, signal) {
|
|
5136
|
+
signal.throwIfAborted();
|
|
4973
5137
|
await this.#publishPendingCompactions();
|
|
4974
5138
|
await this.#assistAutomaticCollection();
|
|
4975
5139
|
const transaction = await this.#transactions.beginDeferred({
|
|
4976
|
-
durableSnapshot: options.durableSnapshot ?? true
|
|
5140
|
+
durableSnapshot: options.durableSnapshot ?? true,
|
|
5141
|
+
coalesceArtifacts: true
|
|
4977
5142
|
});
|
|
4978
5143
|
let closed = false;
|
|
5144
|
+
let accepting = true;
|
|
5145
|
+
let tail = Promise.resolve();
|
|
5146
|
+
let pendingReads = 0;
|
|
5147
|
+
let pendingWrites = 0;
|
|
5148
|
+
let writeBarrier = tail;
|
|
4979
5149
|
let staged = 0;
|
|
4980
5150
|
let poisoned;
|
|
4981
5151
|
const open = () => {
|
|
5152
|
+
signal.throwIfAborted();
|
|
4982
5153
|
if (closed)
|
|
4983
5154
|
throw new Error("The write scope has ended");
|
|
4984
5155
|
if (poisoned !== void 0) {
|
|
@@ -4997,7 +5168,7 @@ class MinnowDatabase {
|
|
|
4997
5168
|
throw error;
|
|
4998
5169
|
}
|
|
4999
5170
|
};
|
|
5000
|
-
const
|
|
5171
|
+
const operations = {
|
|
5001
5172
|
query: async (sql, options2) => {
|
|
5002
5173
|
open();
|
|
5003
5174
|
return this.#withReadReservation(() => this.#sessionQuery(transaction, sql, options2));
|
|
@@ -5042,7 +5213,7 @@ class MinnowDatabase {
|
|
|
5042
5213
|
}
|
|
5043
5214
|
};
|
|
5044
5215
|
const writer = {
|
|
5045
|
-
...
|
|
5216
|
+
...operations,
|
|
5046
5217
|
queryPlan: (plan) => this.#withReadReservation(async () => {
|
|
5047
5218
|
const probe = transaction.initialCatalogProbe;
|
|
5048
5219
|
return transaction.stagedWorkCount === 0 && probe !== void 0 ? this.#queryCompiled(plan, {}, probe) : this.#sessionQueryPlan(transaction, await this.#applyCatalogRewrites(plan));
|
|
@@ -5071,8 +5242,61 @@ class MinnowDatabase {
|
|
|
5071
5242
|
staged = transaction.stagedWorkCount === 0 ? 0 : 1;
|
|
5072
5243
|
}
|
|
5073
5244
|
};
|
|
5245
|
+
const enqueue = (run, kind = "write") => {
|
|
5246
|
+
if (!accepting)
|
|
5247
|
+
return Promise.reject(new Error("The write scope has ended"));
|
|
5248
|
+
const read = kind === "read";
|
|
5249
|
+
if (read && pendingReads >= MAX_DATABASE_ACTIVE_READS) {
|
|
5250
|
+
return Promise.reject(new DatabaseReadBacklogError(MAX_DATABASE_ACTIVE_READS));
|
|
5251
|
+
}
|
|
5252
|
+
if (!read && pendingWrites >= MAX_DATABASE_PENDING_WRITES) {
|
|
5253
|
+
return Promise.reject(new RangeError("Too many pending transaction operations; await a statement"));
|
|
5254
|
+
}
|
|
5255
|
+
if (read)
|
|
5256
|
+
pendingReads += 1;
|
|
5257
|
+
else
|
|
5258
|
+
pendingWrites += 1;
|
|
5259
|
+
const operation = (read ? writeBarrier : tail).then(() => {
|
|
5260
|
+
if (kind === "rollback") {
|
|
5261
|
+
signal.throwIfAborted();
|
|
5262
|
+
if (closed)
|
|
5263
|
+
throw new Error("The write scope has ended");
|
|
5264
|
+
} else
|
|
5265
|
+
open();
|
|
5266
|
+
return run();
|
|
5267
|
+
});
|
|
5268
|
+
const settled = operation.then(() => void 0, () => void 0);
|
|
5269
|
+
tail = Promise.all([tail, settled]).then(() => void 0);
|
|
5270
|
+
if (!read)
|
|
5271
|
+
writeBarrier = tail;
|
|
5272
|
+
return operation.finally(() => {
|
|
5273
|
+
if (read)
|
|
5274
|
+
pendingReads -= 1;
|
|
5275
|
+
else
|
|
5276
|
+
pendingWrites -= 1;
|
|
5277
|
+
});
|
|
5278
|
+
};
|
|
5279
|
+
const session = {
|
|
5280
|
+
query: (sql, queryOptions) => enqueue(() => operations.query(sql, queryOptions), "read"),
|
|
5281
|
+
execute: (sql, params) => enqueue(() => operations.execute(sql, params)),
|
|
5282
|
+
insertBatch: (name, input) => enqueue(() => operations.insertBatch(name, input)),
|
|
5283
|
+
upsertBatch: (name, input, upsertOptions) => enqueue(() => operations.upsertBatch(name, input, upsertOptions)),
|
|
5284
|
+
updateBatch: (name, input) => enqueue(() => operations.updateBatch(name, input)),
|
|
5285
|
+
deleteBatch: (name, input) => enqueue(() => operations.deleteBatch(name, input))
|
|
5286
|
+
};
|
|
5287
|
+
const queuedWriter = {
|
|
5288
|
+
...writer,
|
|
5289
|
+
...session,
|
|
5290
|
+
queryPlan: (plan) => enqueue(() => writer.queryPlan(plan), "read"),
|
|
5291
|
+
queryFirstColumn: (plan) => enqueue(() => writer.queryFirstColumn(plan), "read"),
|
|
5292
|
+
executeStatement: (statement) => enqueue(() => writer.executeStatement(statement)),
|
|
5293
|
+
rollbackTo: (checkpoint) => enqueue(() => writer.rollbackTo(checkpoint), "rollback")
|
|
5294
|
+
};
|
|
5074
5295
|
try {
|
|
5075
|
-
const result = await action(session, transaction,
|
|
5296
|
+
const result = await untilAborted(() => action(session, transaction, queuedWriter), signal);
|
|
5297
|
+
accepting = false;
|
|
5298
|
+
await tail;
|
|
5299
|
+
signal.throwIfAborted();
|
|
5076
5300
|
closed = true;
|
|
5077
5301
|
if (poisoned !== void 0) {
|
|
5078
5302
|
throw new Error(`The write scope failed mid-stage and was rolled back: ${poisoned instanceof Error ? poisoned.message : "staging failed"}`, { cause: poisoned });
|
|
@@ -5098,7 +5322,9 @@ class MinnowDatabase {
|
|
|
5098
5322
|
}
|
|
5099
5323
|
throw new Error("Commit retry limit was exceeded");
|
|
5100
5324
|
} catch (error) {
|
|
5325
|
+
accepting = false;
|
|
5101
5326
|
closed = true;
|
|
5327
|
+
await tail;
|
|
5102
5328
|
await transaction.abort().catch(() => void 0);
|
|
5103
5329
|
throw await this.#translateUniqueConflict(error);
|
|
5104
5330
|
}
|
|
@@ -5557,33 +5783,39 @@ class MinnowDatabase {
|
|
|
5557
5783
|
set.notifyLocalCommit();
|
|
5558
5784
|
}
|
|
5559
5785
|
async run(query) {
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
const
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5786
|
+
return this.#foreground(async () => {
|
|
5787
|
+
await this.#settleExpiredStatementTransaction();
|
|
5788
|
+
if (this.#statementTransactionExpired)
|
|
5789
|
+
throw new TransactionExpiredError();
|
|
5790
|
+
const open = this.#openTransaction;
|
|
5791
|
+
const plan = query.plan;
|
|
5792
|
+
const execute = async () => {
|
|
5793
|
+
const probe = this.store.getCatalogProbe.bind(this.store);
|
|
5794
|
+
return plan.usesStatementDatetime === true || plan.usesVolatileFunctions === true || plan.usesSequenceCalls === true ? this.#queryCompiled(plan) : this.#memoizedQuery(plan, `typed ${planMemoKey(plan)}`, {}, probe);
|
|
5795
|
+
};
|
|
5796
|
+
const result = open === void 0 ? await this.#withReadReservation(execute) : await this.#duringTransaction(open, () => open.session.queryPlan(plan));
|
|
5797
|
+
return externalizeQueryResult(result).rows;
|
|
5798
|
+
});
|
|
5569
5799
|
}
|
|
5570
5800
|
async migrate(definition = this.#schema, options = {}) {
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5801
|
+
return this.#foreground(async () => {
|
|
5802
|
+
if (definition === void 0) {
|
|
5803
|
+
throw new TypeError("migrate() needs a schema: pass a definition, or construct the database with { schema }");
|
|
5804
|
+
}
|
|
5805
|
+
let originalSteps;
|
|
5806
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
5807
|
+
try {
|
|
5808
|
+
const result = await this.#migrateOnce(definition, options, (steps) => {
|
|
5809
|
+
originalSteps ??= [...steps];
|
|
5810
|
+
});
|
|
5811
|
+
return originalSteps === void 0 ? result : migrationResult(originalSteps);
|
|
5812
|
+
} catch (error) {
|
|
5813
|
+
if (!(error instanceof TableRecordConflictError) && !(error instanceof WriteConflictError) || attempt >= this.#maxCommitRetries) {
|
|
5814
|
+
throw error;
|
|
5815
|
+
}
|
|
5584
5816
|
}
|
|
5585
5817
|
}
|
|
5586
|
-
}
|
|
5818
|
+
});
|
|
5587
5819
|
}
|
|
5588
5820
|
#assertViewMatchesDeclaration(record, step) {
|
|
5589
5821
|
if (record === void 0)
|
|
@@ -5884,77 +6116,105 @@ class MinnowDatabase {
|
|
|
5884
6116
|
};
|
|
5885
6117
|
}
|
|
5886
6118
|
async explain(sql) {
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
const reported = transparentProjectionSource(plan)?.inner ?? plan;
|
|
5893
|
-
const notes = [];
|
|
5894
|
-
if (this.#canStreamPlanShape(reported, {})) {
|
|
5895
|
-
notes.push("streams the base scan through resident block windows");
|
|
5896
|
-
} else {
|
|
5897
|
-
notes.push("materializes inputs at preparation");
|
|
5898
|
-
}
|
|
5899
|
-
const table = reported.base.derived ?? reported.base.union ?? reported.base.windowed ?? reported.base.recursive;
|
|
5900
|
-
if (table === void 0 && reported.joins.length === 0 && reported.base.table !== DUAL_TABLE) {
|
|
5901
|
-
const record = await this.#findTable(reported.base.table);
|
|
5902
|
-
const pointTemplate = record.view === void 0 ? cachedPointReadTemplate(reported) : null;
|
|
5903
|
-
if (pointTemplate !== null) {
|
|
5904
|
-
const keyColumn = getUniqueKeyColumn(record);
|
|
5905
|
-
const keyComponents = keyColumn === void 0 ? [] : keyColumn.hidden === true ? primaryKeyColumns(record) : [keyColumn];
|
|
5906
|
-
const covered = new Set(pointTemplate.equalities.map((equality) => equality.column));
|
|
5907
|
-
if (keyComponents.length > 0 && keyComponents.every((component) => covered.has(component.name))) {
|
|
5908
|
-
notes.push("key-covering equality answers as a point read when the visible history allows");
|
|
5909
|
-
}
|
|
5910
|
-
}
|
|
5911
|
-
if (zonePredicates(reported, record).length > 0) {
|
|
5912
|
-
notes.push("zone-map pruning applies to the unbudgeted scan");
|
|
5913
|
-
}
|
|
5914
|
-
if (secondaryIndexPredicates(reported, record).length > 0) {
|
|
5915
|
-
notes.push("a ready secondary index prunes the scan to candidate row groups");
|
|
6119
|
+
return this.#foreground(async () => {
|
|
6120
|
+
let plan = this.#compileCached(sql);
|
|
6121
|
+
if (planContainsFts(plan)) {
|
|
6122
|
+
const realTables = await this.#findRealBlockTables(plan);
|
|
6123
|
+
plan = expandFtsColumns(plan, (tableName) => searchableFtsColumns(realTables.get(tableName)));
|
|
5916
6124
|
}
|
|
5917
|
-
const
|
|
5918
|
-
const
|
|
5919
|
-
if (
|
|
5920
|
-
notes.push("
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
}
|
|
6125
|
+
const reported = transparentProjectionSource(plan)?.inner ?? plan;
|
|
6126
|
+
const notes = [];
|
|
6127
|
+
if (this.#canStreamPlanShape(reported, {})) {
|
|
6128
|
+
notes.push("streams the base scan through resident block windows");
|
|
6129
|
+
} else {
|
|
6130
|
+
notes.push("materializes inputs at preparation");
|
|
5924
6131
|
}
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
notes.push("resolves uncorrelated subqueries at one snapshot");
|
|
5928
|
-
if (planContainsFts(reported)) {
|
|
5929
|
-
notes.push("full-text MATCH evaluates via per-dictionary term tables on the scan");
|
|
5930
|
-
if (table === void 0 && reported.joins.length === 0) {
|
|
6132
|
+
const table = reported.base.derived ?? reported.base.union ?? reported.base.windowed ?? reported.base.recursive;
|
|
6133
|
+
if (table === void 0 && reported.joins.length === 0 && reported.base.table !== DUAL_TABLE) {
|
|
5931
6134
|
const record = await this.#findTable(reported.base.table);
|
|
5932
|
-
const
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
if (
|
|
5942
|
-
notes.push("
|
|
6135
|
+
const pointTemplate = record.view === void 0 ? cachedPointReadTemplate(reported) : null;
|
|
6136
|
+
if (pointTemplate !== null) {
|
|
6137
|
+
const keyColumn = getUniqueKeyColumn(record);
|
|
6138
|
+
const keyComponents = keyColumn === void 0 ? [] : keyColumn.hidden === true ? primaryKeyColumns(record) : [keyColumn];
|
|
6139
|
+
const covered = new Set(pointTemplate.equalities.map((equality) => equality.column));
|
|
6140
|
+
if (keyComponents.length > 0 && keyComponents.every((component) => covered.has(component.name))) {
|
|
6141
|
+
notes.push("key-covering equality answers as a point read when the visible history allows");
|
|
6142
|
+
}
|
|
6143
|
+
}
|
|
6144
|
+
if (zonePredicates(reported, record).length > 0) {
|
|
6145
|
+
notes.push("zone-map pruning applies to the unbudgeted scan");
|
|
5943
6146
|
}
|
|
5944
|
-
if (
|
|
5945
|
-
notes.push(
|
|
6147
|
+
if (secondaryIndexPredicates(reported, record).length > 0) {
|
|
6148
|
+
notes.push("a ready secondary index prunes the scan to candidate row groups");
|
|
6149
|
+
}
|
|
6150
|
+
const referenced = referencedColumns(reported, /* @__PURE__ */ new Map([[record.name, record.columns.map((column) => column.name)]])).get(record.name);
|
|
6151
|
+
const projected = referenced === void 0 || referenced.length === 0 ? [] : resolveReadColumns(record, referenced);
|
|
6152
|
+
if (secondaryIndexOrderForPlan(reported, record, projected, false) !== void 0) {
|
|
6153
|
+
notes.push("a ready secondary index supplies ORDER BY without a sort");
|
|
6154
|
+
if (secondaryIndexOrderForPlan(reported, record, projected) !== void 0) {
|
|
6155
|
+
notes.push("a covering secondary-index scan avoids table blocks");
|
|
6156
|
+
}
|
|
5946
6157
|
}
|
|
5947
|
-
} else if (planContainsFts(reported, "bm25")) {
|
|
5948
|
-
notes.push("BM25 scoring reads the full scan for corpus statistics; index pruning does not apply");
|
|
5949
6158
|
}
|
|
5950
|
-
|
|
5951
|
-
|
|
6159
|
+
if (blockHasSubqueries(reported))
|
|
6160
|
+
notes.push("resolves uncorrelated subqueries at one snapshot");
|
|
6161
|
+
if (planContainsFts(reported)) {
|
|
6162
|
+
notes.push("full-text MATCH evaluates via per-dictionary term tables on the scan");
|
|
6163
|
+
if (table === void 0 && reported.joins.length === 0) {
|
|
6164
|
+
const record = await this.#findTable(reported.base.table);
|
|
6165
|
+
const readyColumnIds = new Set(Object.entries(record.ftsColumns ?? {}).filter(([, state]) => state.state === "ready" && state.tokenizerVersion === FTS_TOKENIZER_VERSION).map(([columnId]) => columnId));
|
|
6166
|
+
const columnsByName = new Map(record.columns.map((column) => [column.name, column]));
|
|
6167
|
+
const scoringServed = [...this.#ftsBm25Nodes(reported).values()].every((node) => node.columns.every((expression) => {
|
|
6168
|
+
if (expression.kind !== "column")
|
|
6169
|
+
return false;
|
|
6170
|
+
const column = columnsByName.get(expression.reference.split(".").at(-1) ?? expression.reference);
|
|
6171
|
+
return column !== void 0 && readyColumnIds.has(column.id);
|
|
6172
|
+
}));
|
|
6173
|
+
const scoring = planContainsFts(reported, "bm25");
|
|
6174
|
+
if (readyColumnIds.size > 0 && (!scoring || scoringServed)) {
|
|
6175
|
+
notes.push("a ready full-text index prunes the base scan to candidate segments");
|
|
6176
|
+
}
|
|
6177
|
+
if (scoring) {
|
|
6178
|
+
notes.push(scoringServed ? "BM25 statistics come from the full-text index" : "BM25 scoring reads the full scan for corpus statistics; index pruning does not apply");
|
|
6179
|
+
}
|
|
6180
|
+
} else if (planContainsFts(reported, "bm25")) {
|
|
6181
|
+
notes.push("BM25 scoring reads the full scan for corpus statistics; index pruning does not apply");
|
|
6182
|
+
}
|
|
6183
|
+
}
|
|
6184
|
+
return `${renderPlan(plan)}
|
|
5952
6185
|
${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
6186
|
+
});
|
|
5953
6187
|
}
|
|
5954
6188
|
async execute(sql, params, options = {}) {
|
|
6189
|
+
if (this.#closed)
|
|
6190
|
+
throw new Error("Database is closed");
|
|
6191
|
+
if (this.#pendingExecutes >= MAX_DATABASE_PENDING_WRITES) {
|
|
6192
|
+
throw new RangeError("Too many pending SQL statements; await a statement");
|
|
6193
|
+
}
|
|
6194
|
+
this.#pendingExecutes += 1;
|
|
6195
|
+
const run = () => this.#executeSerial(sql, params, options);
|
|
6196
|
+
const operation = this.#executeChain.then(run, run);
|
|
6197
|
+
this.#executeChain = operation.then(() => void 0, () => void 0);
|
|
6198
|
+
try {
|
|
6199
|
+
return await operation;
|
|
6200
|
+
} finally {
|
|
6201
|
+
this.#pendingExecutes -= 1;
|
|
6202
|
+
}
|
|
6203
|
+
}
|
|
6204
|
+
async #executeSerial(sql, params, options) {
|
|
6205
|
+
if (this.#closed)
|
|
6206
|
+
throw new Error("Database is closed");
|
|
5955
6207
|
throwIfAborted(options.signal);
|
|
5956
6208
|
await this.#settleExpiredStatementTransaction();
|
|
5957
6209
|
const statement = this.#compileStatementCached(sql);
|
|
6210
|
+
if (this.#statementTransactionExpired) {
|
|
6211
|
+
if (statement.kind !== "transaction" || statement.action !== "begin" && statement.action !== "rollback") {
|
|
6212
|
+
throw new TransactionExpiredError();
|
|
6213
|
+
}
|
|
6214
|
+
this.#statementTransactionExpired = false;
|
|
6215
|
+
if (statement.action === "rollback")
|
|
6216
|
+
return { kind: "transaction", action: "rollback" };
|
|
6217
|
+
}
|
|
5958
6218
|
if (statement.kind === "transaction") {
|
|
5959
6219
|
return this.#runTransactionStatement(statement.action, statement.name);
|
|
5960
6220
|
}
|
|
@@ -6176,6 +6436,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
6176
6436
|
if (open === void 0 || open.busy > 0 || dateMilliseconds(this.#now()) - open.activeAt < this.#transactionIdleTimeoutMs) {
|
|
6177
6437
|
return;
|
|
6178
6438
|
}
|
|
6439
|
+
this.#statementTransactionExpired = true;
|
|
6179
6440
|
await this.#rollbackStatementTransaction(open);
|
|
6180
6441
|
}
|
|
6181
6442
|
async #runTransactionStatement(action, name) {
|
|
@@ -6219,6 +6480,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
6219
6480
|
return;
|
|
6220
6481
|
if (dateMilliseconds(this.#now()) - state.activeAt < this.#transactionIdleTimeoutMs)
|
|
6221
6482
|
return;
|
|
6483
|
+
this.#statementTransactionExpired = true;
|
|
6222
6484
|
void this.#rollbackStatementTransaction(state).catch(() => void 0);
|
|
6223
6485
|
}, this.#transactionIdleTimeoutMs)
|
|
6224
6486
|
};
|
|
@@ -6662,240 +6924,263 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
6662
6924
|
};
|
|
6663
6925
|
}
|
|
6664
6926
|
async runStatement(statement, options = {}) {
|
|
6665
|
-
|
|
6666
|
-
|
|
6667
|
-
|
|
6668
|
-
|
|
6669
|
-
|
|
6670
|
-
|
|
6671
|
-
|
|
6672
|
-
|
|
6673
|
-
|
|
6674
|
-
|
|
6675
|
-
|
|
6676
|
-
|
|
6677
|
-
|
|
6678
|
-
|
|
6679
|
-
|
|
6680
|
-
|
|
6681
|
-
|
|
6682
|
-
|
|
6683
|
-
|
|
6684
|
-
|
|
6685
|
-
|
|
6686
|
-
|
|
6687
|
-
|
|
6688
|
-
|
|
6689
|
-
|
|
6690
|
-
|
|
6691
|
-
|
|
6692
|
-
|
|
6693
|
-
|
|
6694
|
-
|
|
6695
|
-
|
|
6927
|
+
return this.#foreground(async () => {
|
|
6928
|
+
if (this.#closed)
|
|
6929
|
+
throw new Error("Database is closed");
|
|
6930
|
+
if (this.#statementTransactionExpired && statement.kind !== "transaction")
|
|
6931
|
+
throw new TransactionExpiredError();
|
|
6932
|
+
const statementNow = this.#now();
|
|
6933
|
+
statement = resolveMutationStatementDatetimes(statement, statementNow);
|
|
6934
|
+
if (statement.kind === "select") {
|
|
6935
|
+
return { kind: "rows", result: await this.query(statement.sql) };
|
|
6936
|
+
}
|
|
6937
|
+
if (statement.kind === "set") {
|
|
6938
|
+
return { kind: "set", action: statement.action, name: statement.name };
|
|
6939
|
+
}
|
|
6940
|
+
if (statement.kind === "show") {
|
|
6941
|
+
const settings = {
|
|
6942
|
+
search_path: "public",
|
|
6943
|
+
server_version: "16.0",
|
|
6944
|
+
server_version_num: "160000",
|
|
6945
|
+
timezone: "UTC",
|
|
6946
|
+
time_zone: "UTC",
|
|
6947
|
+
transaction_isolation: "repeatable read",
|
|
6948
|
+
default_transaction_isolation: "repeatable read",
|
|
6949
|
+
client_encoding: "UTF8",
|
|
6950
|
+
server_encoding: "UTF8",
|
|
6951
|
+
standard_conforming_strings: "on",
|
|
6952
|
+
integer_datetimes: "on",
|
|
6953
|
+
datestyle: "ISO, MDY",
|
|
6954
|
+
intervalstyle: "postgres",
|
|
6955
|
+
application_name: "",
|
|
6956
|
+
is_superuser: "on",
|
|
6957
|
+
max_identifier_length: "63"
|
|
6958
|
+
};
|
|
6959
|
+
const name = statement.name.toLowerCase();
|
|
6960
|
+
const value = settings[name];
|
|
6961
|
+
if (value === void 0) {
|
|
6962
|
+
throw new TypeError(`unrecognized configuration parameter "${statement.name}"`);
|
|
6963
|
+
}
|
|
6964
|
+
return {
|
|
6965
|
+
kind: "rows",
|
|
6966
|
+
result: await this.query(`SELECT $1 AS ${quoteIdentifier(name)}`, { params: [value] })
|
|
6967
|
+
};
|
|
6696
6968
|
}
|
|
6697
|
-
|
|
6698
|
-
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
|
|
6703
|
-
|
|
6704
|
-
|
|
6705
|
-
|
|
6706
|
-
|
|
6969
|
+
if (statement.kind === "create-enum") {
|
|
6970
|
+
const values = validateEnumValues(statement.values, statement.name);
|
|
6971
|
+
const storageName = `${ENUM_TYPE_PREFIX}${statement.name}`;
|
|
6972
|
+
if (await this.store.getTableByName(storageName)) {
|
|
6973
|
+
throw new TypeError(`Type already exists: ${statement.name}`);
|
|
6974
|
+
}
|
|
6975
|
+
await this.store.addTable({
|
|
6976
|
+
id: this.#createId(),
|
|
6977
|
+
name: storageName,
|
|
6978
|
+
columns: [
|
|
6979
|
+
{
|
|
6980
|
+
id: this.#createId(),
|
|
6981
|
+
name: "value",
|
|
6982
|
+
type: "string",
|
|
6983
|
+
nullable: false,
|
|
6984
|
+
hidden: true
|
|
6985
|
+
}
|
|
6986
|
+
],
|
|
6987
|
+
managed: false,
|
|
6988
|
+
revision: 0,
|
|
6989
|
+
enumType: { name: statement.name, values },
|
|
6990
|
+
createdAt: dateIsoString(this.#now())
|
|
6991
|
+
});
|
|
6992
|
+
this.#planCache.clear();
|
|
6993
|
+
return { kind: "create-type", name: statement.name };
|
|
6707
6994
|
}
|
|
6708
|
-
|
|
6709
|
-
|
|
6710
|
-
|
|
6711
|
-
|
|
6712
|
-
{
|
|
6713
|
-
id: this.#createId(),
|
|
6714
|
-
name: "value",
|
|
6715
|
-
type: "string",
|
|
6716
|
-
nullable: false,
|
|
6717
|
-
hidden: true
|
|
6718
|
-
}
|
|
6719
|
-
],
|
|
6720
|
-
managed: false,
|
|
6721
|
-
revision: 0,
|
|
6722
|
-
enumType: { name: statement.name, values },
|
|
6723
|
-
createdAt: dateIsoString(this.#now())
|
|
6724
|
-
});
|
|
6725
|
-
this.#planCache.clear();
|
|
6726
|
-
return { kind: "create-type", name: statement.name };
|
|
6727
|
-
}
|
|
6728
|
-
if (statement.kind === "create-sequence") {
|
|
6729
|
-
const storageName = `${SEQUENCE_PREFIX}${statement.name}`;
|
|
6730
|
-
if (await this.store.getTableByName(storageName)) {
|
|
6731
|
-
throw new TypeError(`Sequence already exists: ${statement.name}`);
|
|
6732
|
-
}
|
|
6733
|
-
const columnId = this.#createId();
|
|
6734
|
-
await this.store.addTable({
|
|
6735
|
-
id: this.#createId(),
|
|
6736
|
-
name: storageName,
|
|
6737
|
-
columns: [
|
|
6738
|
-
{
|
|
6739
|
-
id: columnId,
|
|
6740
|
-
name: "value",
|
|
6741
|
-
type: "number",
|
|
6742
|
-
integer: true,
|
|
6743
|
-
nullable: false,
|
|
6744
|
-
hidden: true,
|
|
6745
|
-
defaultValue: { kind: "autoincrement" }
|
|
6746
|
-
}
|
|
6747
|
-
],
|
|
6748
|
-
managed: false,
|
|
6749
|
-
revision: 0,
|
|
6750
|
-
sequence: { name: statement.name, start: 1, columnId },
|
|
6751
|
-
createdAt: dateIsoString(this.#now())
|
|
6752
|
-
});
|
|
6753
|
-
return { kind: "create-sequence", name: statement.name };
|
|
6754
|
-
}
|
|
6755
|
-
if (statement.kind === "create-table") {
|
|
6756
|
-
if (statement.ifNotExists === true) {
|
|
6757
|
-
const existing = await this.store.getTableByName(statement.table);
|
|
6758
|
-
if (existing !== void 0)
|
|
6759
|
-
return { kind: "create-table", table: statement.table };
|
|
6760
|
-
}
|
|
6761
|
-
try {
|
|
6762
|
-
await this.createTable({
|
|
6763
|
-
name: statement.table,
|
|
6764
|
-
columns: statement.columns,
|
|
6765
|
-
...statement.checks === void 0 ? {} : { checks: statement.checks },
|
|
6766
|
-
...statement.foreignKeys === void 0 ? {} : { foreignKeys: statement.foreignKeys },
|
|
6767
|
-
...statement.uniqueKey === void 0 ? {} : { uniqueKey: statement.uniqueKey },
|
|
6768
|
-
...statement.compositePrimaryKey === void 0 ? {} : { compositePrimaryKey: statement.compositePrimaryKey },
|
|
6769
|
-
...statement.uniqueConstraints === void 0 ? {} : { uniqueConstraints: statement.uniqueConstraints },
|
|
6770
|
-
...statement.managed === void 0 ? {} : { managed: statement.managed }
|
|
6771
|
-
});
|
|
6772
|
-
} catch (error) {
|
|
6773
|
-
if (statement.ifNotExists !== true || await this.store.getTableByName(statement.table) === void 0) {
|
|
6774
|
-
throw error;
|
|
6995
|
+
if (statement.kind === "create-sequence") {
|
|
6996
|
+
const storageName = `${SEQUENCE_PREFIX}${statement.name}`;
|
|
6997
|
+
if (await this.store.getTableByName(storageName)) {
|
|
6998
|
+
throw new TypeError(`Sequence already exists: ${statement.name}`);
|
|
6775
6999
|
}
|
|
7000
|
+
const columnId = this.#createId();
|
|
7001
|
+
await this.store.addTable({
|
|
7002
|
+
id: this.#createId(),
|
|
7003
|
+
name: storageName,
|
|
7004
|
+
columns: [
|
|
7005
|
+
{
|
|
7006
|
+
id: columnId,
|
|
7007
|
+
name: "value",
|
|
7008
|
+
type: "number",
|
|
7009
|
+
integer: true,
|
|
7010
|
+
nullable: false,
|
|
7011
|
+
hidden: true,
|
|
7012
|
+
defaultValue: { kind: "autoincrement" }
|
|
7013
|
+
}
|
|
7014
|
+
],
|
|
7015
|
+
managed: false,
|
|
7016
|
+
revision: 0,
|
|
7017
|
+
sequence: { name: statement.name, start: 1, columnId },
|
|
7018
|
+
createdAt: dateIsoString(this.#now())
|
|
7019
|
+
});
|
|
7020
|
+
return { kind: "create-sequence", name: statement.name };
|
|
6776
7021
|
}
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
const before = await this.store.getCatalogProbe();
|
|
6782
|
-
const existing = await this.store.getTableByName(statement.table);
|
|
6783
|
-
if (existing !== void 0) {
|
|
6784
|
-
if (statement.ifNotExists === true) {
|
|
7022
|
+
if (statement.kind === "create-table") {
|
|
7023
|
+
if (statement.ifNotExists === true) {
|
|
7024
|
+
const existing = await this.store.getTableByName(statement.table);
|
|
7025
|
+
if (existing !== void 0)
|
|
6785
7026
|
return { kind: "create-table", table: statement.table };
|
|
6786
|
-
}
|
|
6787
|
-
throw new TypeError(`Table already exists: ${statement.table}`);
|
|
6788
7027
|
}
|
|
6789
|
-
|
|
6790
|
-
|
|
6791
|
-
|
|
6792
|
-
|
|
6793
|
-
|
|
7028
|
+
try {
|
|
7029
|
+
await this.createTable({
|
|
7030
|
+
name: statement.table,
|
|
7031
|
+
columns: statement.columns,
|
|
7032
|
+
...statement.checks === void 0 ? {} : { checks: statement.checks },
|
|
7033
|
+
...statement.foreignKeys === void 0 ? {} : { foreignKeys: statement.foreignKeys },
|
|
7034
|
+
...statement.uniqueKey === void 0 ? {} : { uniqueKey: statement.uniqueKey },
|
|
7035
|
+
...statement.compositePrimaryKey === void 0 ? {} : { compositePrimaryKey: statement.compositePrimaryKey },
|
|
7036
|
+
...statement.uniqueConstraints === void 0 ? {} : { uniqueConstraints: statement.uniqueConstraints },
|
|
7037
|
+
...statement.managed === void 0 ? {} : { managed: statement.managed }
|
|
7038
|
+
});
|
|
7039
|
+
} catch (error) {
|
|
7040
|
+
if (statement.ifNotExists !== true || await this.store.getTableByName(statement.table) === void 0) {
|
|
7041
|
+
throw error;
|
|
6794
7042
|
}
|
|
6795
|
-
continue;
|
|
6796
7043
|
}
|
|
6797
|
-
|
|
6798
|
-
|
|
6799
|
-
|
|
6800
|
-
|
|
7044
|
+
return { kind: "create-table", table: statement.table };
|
|
7045
|
+
}
|
|
7046
|
+
if (statement.kind === "create-table-as") {
|
|
7047
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
7048
|
+
const before = await this.store.getCatalogProbe();
|
|
7049
|
+
const existing = await this.store.getTableByName(statement.table);
|
|
7050
|
+
if (existing !== void 0) {
|
|
7051
|
+
if (statement.ifNotExists === true) {
|
|
7052
|
+
return { kind: "create-table", table: statement.table };
|
|
7053
|
+
}
|
|
7054
|
+
throw new TypeError(`Table already exists: ${statement.table}`);
|
|
7055
|
+
}
|
|
7056
|
+
const records = await this.store.listTables();
|
|
7057
|
+
const after = await this.store.getCatalogProbe();
|
|
7058
|
+
if (after.catalogEpoch !== before.catalogEpoch) {
|
|
7059
|
+
if (attempt >= this.#maxCommitRetries) {
|
|
7060
|
+
throw new TableRecordConflictError(statement.table, 0, null);
|
|
7061
|
+
}
|
|
7062
|
+
continue;
|
|
7063
|
+
}
|
|
7064
|
+
const schemas = new Map(records.map((record) => [
|
|
7065
|
+
record.name,
|
|
7066
|
+
record.columns.map(({ name, type, integer, sqlDomain }) => ({
|
|
7067
|
+
name,
|
|
7068
|
+
type,
|
|
7069
|
+
...integer === true ? { integer: true } : {},
|
|
7070
|
+
...sqlDomain === void 0 ? {} : { sqlDomain }
|
|
7071
|
+
}))
|
|
7072
|
+
]));
|
|
7073
|
+
const plan2 = await this.#applyCatalogRewrites(statement.query, before);
|
|
7074
|
+
const tableColumns = inferBlockSchema(plan2, schemas).map(({ name, type, integer, sqlDomain }) => ({
|
|
7075
|
+
id: this.#createId(),
|
|
7076
|
+
name: validateName(name, "Column"),
|
|
6801
7077
|
type,
|
|
6802
7078
|
...integer === true ? { integer: true } : {},
|
|
6803
|
-
...sqlDomain === void 0 ? {} : { sqlDomain }
|
|
6804
|
-
|
|
6805
|
-
|
|
6806
|
-
|
|
6807
|
-
|
|
6808
|
-
|
|
6809
|
-
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
|
|
6818
|
-
|
|
6819
|
-
|
|
6820
|
-
|
|
6821
|
-
|
|
6822
|
-
|
|
6823
|
-
|
|
6824
|
-
|
|
6825
|
-
|
|
6826
|
-
|
|
6827
|
-
|
|
6828
|
-
|
|
6829
|
-
|
|
6830
|
-
|
|
6831
|
-
|
|
6832
|
-
|
|
6833
|
-
|
|
6834
|
-
|
|
6835
|
-
|
|
6836
|
-
|
|
7079
|
+
...sqlDomain === void 0 ? {} : { sqlDomain: structuredClone(sqlDomain) },
|
|
7080
|
+
nullable: true
|
|
7081
|
+
}));
|
|
7082
|
+
const pendingTable = {
|
|
7083
|
+
id: this.#createId(),
|
|
7084
|
+
name: validateName(statement.table, "Table"),
|
|
7085
|
+
columns: tableColumns,
|
|
7086
|
+
revision: 0,
|
|
7087
|
+
managed: false,
|
|
7088
|
+
createdAt: dateIsoString(this.#now())
|
|
7089
|
+
};
|
|
7090
|
+
const transaction = await this.#transactions.beginWithPendingTable(pendingTable, before.catalogEpoch);
|
|
7091
|
+
try {
|
|
7092
|
+
const executionMemoryBudgetBytes = Math.min(this.#queryExecutionMemoryBudgetBytes, DEFAULT_QUERY_MEMORY_BUDGET_BYTES);
|
|
7093
|
+
const stageBatchRows = 16384;
|
|
7094
|
+
let nextRowId = 1n;
|
|
7095
|
+
let stagedRows = 0;
|
|
7096
|
+
transaction.limitLevelZeroSegments(pendingTable.id, MAX_LEVEL_ZERO_SEGMENTS);
|
|
7097
|
+
const stageRows = async (rows2) => {
|
|
7098
|
+
if (rows2.length === 0)
|
|
7099
|
+
return;
|
|
7100
|
+
const batchResult = {
|
|
7101
|
+
columns: tableColumns.map(({ name }) => name),
|
|
7102
|
+
columnDomains: tableColumns.map(({ sqlDomain }) => sqlDomain ?? null),
|
|
7103
|
+
rows: [...rows2]
|
|
7104
|
+
};
|
|
7105
|
+
if (queryResultRetainedBytes(batchResult) > executionMemoryBudgetBytes) {
|
|
7106
|
+
throw new QueryMemoryBudgetError("CREATE TABLE AS result batch", queryResultRetainedBytes(batchResult), 0, executionMemoryBudgetBytes);
|
|
7107
|
+
}
|
|
7108
|
+
const batch = {
|
|
7109
|
+
columns: Object.fromEntries(tableColumns.map((column) => [
|
|
7110
|
+
column.name,
|
|
7111
|
+
rows2.map((row) => storedSqlValueFromExecution(column, row[column.name] ?? null))
|
|
7112
|
+
])),
|
|
7113
|
+
rowCount: rows2.length
|
|
7114
|
+
};
|
|
7115
|
+
normalizeDomainBatch(pendingTable, batch);
|
|
7116
|
+
validateBatch(pendingTable, batch);
|
|
7117
|
+
const endExclusive = nextRowId + BigInt(rows2.length);
|
|
7118
|
+
await this.#stageInsertSegment(transaction, pendingTable, batch, rows2.length, "insert", { start: nextRowId, endExclusive });
|
|
7119
|
+
nextRowId = endExclusive;
|
|
7120
|
+
stagedRows += rows2.length;
|
|
6837
7121
|
};
|
|
6838
|
-
|
|
6839
|
-
|
|
6840
|
-
|
|
6841
|
-
|
|
6842
|
-
|
|
6843
|
-
column.name,
|
|
6844
|
-
rows2.map((row) => storedSqlValueFromExecution(column, row[column.name] ?? null))
|
|
6845
|
-
])),
|
|
6846
|
-
rowCount: rows2.length
|
|
7122
|
+
const queryOptions = {
|
|
7123
|
+
...transaction.snapshotVersion === null ? {} : { version: transaction.snapshotVersion },
|
|
7124
|
+
executionMemoryBudgetBytes,
|
|
7125
|
+
spillToStorage: true,
|
|
7126
|
+
memoize: false
|
|
6847
7127
|
};
|
|
6848
|
-
|
|
6849
|
-
|
|
6850
|
-
|
|
6851
|
-
|
|
6852
|
-
|
|
6853
|
-
|
|
6854
|
-
|
|
6855
|
-
const queryOptions = {
|
|
6856
|
-
...transaction.snapshotVersion === null ? {} : { version: transaction.snapshotVersion },
|
|
6857
|
-
executionMemoryBudgetBytes,
|
|
6858
|
-
spillToStorage: true,
|
|
6859
|
-
memoize: false
|
|
6860
|
-
};
|
|
6861
|
-
let streamed;
|
|
6862
|
-
if (!compiledPlanIsGrouped(plan2) && plan2.orderBy.length === 0 && plan2.joins.length === 0 && this.#canStreamPlanShape(plan2, queryOptions)) {
|
|
6863
|
-
streamed = await this.#queryStreamed(plan2, queryOptions, void 0, void 0, {
|
|
6864
|
-
batchRows: stageBatchRows,
|
|
6865
|
-
signal: new AbortController().signal,
|
|
6866
|
-
consume: async (batch) => stageRows(batch.rows)
|
|
6867
|
-
});
|
|
6868
|
-
}
|
|
6869
|
-
if (streamed === void 0) {
|
|
6870
|
-
if (stagedRows !== 0) {
|
|
6871
|
-
throw new Error("CREATE TABLE AS streaming stopped after staging rows");
|
|
7128
|
+
let streamed;
|
|
7129
|
+
if (!compiledPlanIsGrouped(plan2) && plan2.orderBy.length === 0 && plan2.joins.length === 0 && this.#canStreamPlanShape(plan2, queryOptions)) {
|
|
7130
|
+
streamed = await this.#queryStreamed(plan2, queryOptions, void 0, void 0, {
|
|
7131
|
+
batchRows: stageBatchRows,
|
|
7132
|
+
signal: new AbortController().signal,
|
|
7133
|
+
consume: async (batch) => stageRows(batch.rows)
|
|
7134
|
+
});
|
|
6872
7135
|
}
|
|
6873
|
-
|
|
6874
|
-
|
|
6875
|
-
|
|
6876
|
-
|
|
7136
|
+
if (streamed === void 0) {
|
|
7137
|
+
if (stagedRows !== 0) {
|
|
7138
|
+
throw new Error("CREATE TABLE AS streaming stopped after staging rows");
|
|
7139
|
+
}
|
|
7140
|
+
const result = await this.#sessionQueryPlan(transaction, plan2, queryOptions);
|
|
7141
|
+
const retainedBytes = queryResultRetainedBytes(result);
|
|
7142
|
+
if (retainedBytes > executionMemoryBudgetBytes) {
|
|
7143
|
+
throw new QueryMemoryBudgetError("CREATE TABLE AS result", retainedBytes, 0, executionMemoryBudgetBytes);
|
|
7144
|
+
}
|
|
7145
|
+
for (let start = 0; start < result.rows.length; start += stageBatchRows) {
|
|
7146
|
+
await stageRows(result.rows.slice(start, start + stageBatchRows));
|
|
7147
|
+
}
|
|
6877
7148
|
}
|
|
6878
|
-
|
|
6879
|
-
|
|
7149
|
+
transaction.markTableChanged(pendingTable.id);
|
|
7150
|
+
const manifest = await transaction.commit();
|
|
7151
|
+
this.#afterCommit(manifest);
|
|
7152
|
+
return { kind: "create-table", table: statement.table };
|
|
7153
|
+
} catch (error) {
|
|
7154
|
+
await transaction.abort().catch(() => void 0);
|
|
7155
|
+
if ((error instanceof WriteConflictError || error instanceof SchemaConflictError || error instanceof TableRecordConflictError) && attempt < this.#maxCommitRetries) {
|
|
7156
|
+
continue;
|
|
6880
7157
|
}
|
|
7158
|
+
throw error;
|
|
6881
7159
|
}
|
|
6882
|
-
transaction.markTableChanged(pendingTable.id);
|
|
6883
|
-
const manifest = await transaction.commit();
|
|
6884
|
-
this.#afterCommit(manifest);
|
|
6885
|
-
return { kind: "create-table", table: statement.table };
|
|
6886
|
-
} catch (error) {
|
|
6887
|
-
await transaction.abort().catch(() => void 0);
|
|
6888
|
-
if ((error instanceof WriteConflictError || error instanceof SchemaConflictError || error instanceof TableRecordConflictError) && attempt < this.#maxCommitRetries) {
|
|
6889
|
-
continue;
|
|
6890
|
-
}
|
|
6891
|
-
throw error;
|
|
6892
7160
|
}
|
|
6893
7161
|
}
|
|
6894
|
-
|
|
6895
|
-
|
|
6896
|
-
|
|
6897
|
-
|
|
6898
|
-
|
|
7162
|
+
if (statement.kind === "create-index") {
|
|
7163
|
+
const indexStatement = statement;
|
|
7164
|
+
const existing = (await this.store.listTables()).some((table2) => Object.values(table2.secondaryIndexes ?? {}).some((index) => index.name === indexStatement.index));
|
|
7165
|
+
if (existing && statement.ifNotExists === true) {
|
|
7166
|
+
return {
|
|
7167
|
+
kind: "create-index",
|
|
7168
|
+
index: statement.index,
|
|
7169
|
+
table: statement.table,
|
|
7170
|
+
column: statement.columns[0]?.name ?? "",
|
|
7171
|
+
columns: statement.columns.map((column) => column.name),
|
|
7172
|
+
unique: statement.unique === true
|
|
7173
|
+
};
|
|
7174
|
+
}
|
|
7175
|
+
try {
|
|
7176
|
+
await this.createIndex(statement.index, statement.table, statement.columns, {
|
|
7177
|
+
unique: statement.unique === true
|
|
7178
|
+
});
|
|
7179
|
+
} catch (error) {
|
|
7180
|
+
const admitted = statement.ifNotExists === true && (await this.store.listTables()).some((table2) => Object.values(table2.secondaryIndexes ?? {}).some((index) => index.name === indexStatement.index));
|
|
7181
|
+
if (!admitted)
|
|
7182
|
+
throw error;
|
|
7183
|
+
}
|
|
6899
7184
|
return {
|
|
6900
7185
|
kind: "create-index",
|
|
6901
7186
|
index: statement.index,
|
|
@@ -6905,439 +7190,423 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
6905
7190
|
unique: statement.unique === true
|
|
6906
7191
|
};
|
|
6907
7192
|
}
|
|
6908
|
-
|
|
6909
|
-
await this.
|
|
6910
|
-
|
|
7193
|
+
if (statement.kind === "drop-index") {
|
|
7194
|
+
const dropped = await this.dropIndex(statement.index, {
|
|
7195
|
+
...statement.ifExists === true ? { ifExists: true } : {}
|
|
6911
7196
|
});
|
|
6912
|
-
|
|
6913
|
-
const admitted = statement.ifNotExists === true && (await this.store.listTables()).some((table2) => Object.values(table2.secondaryIndexes ?? {}).some((index) => index.name === indexStatement.index));
|
|
6914
|
-
if (!admitted)
|
|
6915
|
-
throw error;
|
|
7197
|
+
return { kind: "drop-index", index: statement.index, dropped };
|
|
6916
7198
|
}
|
|
6917
|
-
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
6929
|
-
});
|
|
6930
|
-
return { kind: "drop-index", index: statement.index, dropped };
|
|
6931
|
-
}
|
|
6932
|
-
if (statement.kind === "add-column") {
|
|
6933
|
-
let pinnedTableId;
|
|
6934
|
-
const addColumnStatement = statement;
|
|
6935
|
-
for (let attempt = 0; ; attempt += 1) {
|
|
6936
|
-
const proof = await this.#stableCatalogProof();
|
|
6937
|
-
try {
|
|
6938
|
-
let added = addColumnStatement.column;
|
|
6939
|
-
if (added.sqlDomain?.kind === "enum" && added.sqlDomain.values.length === 0) {
|
|
6940
|
-
const enumName = added.sqlDomain.name;
|
|
6941
|
-
const enumRecord = proof.records.find((record2) => record2.name === `${ENUM_TYPE_PREFIX}${enumName}`);
|
|
6942
|
-
if (enumRecord?.enumType === void 0) {
|
|
6943
|
-
throw new TypeError(`Unsupported column type: ${enumName}`);
|
|
6944
|
-
}
|
|
6945
|
-
added = {
|
|
6946
|
-
...added,
|
|
6947
|
-
sqlDomain: {
|
|
6948
|
-
kind: "enum",
|
|
6949
|
-
name: enumRecord.enumType.name,
|
|
6950
|
-
values: [...enumRecord.enumType.values]
|
|
7199
|
+
if (statement.kind === "add-column") {
|
|
7200
|
+
let pinnedTableId;
|
|
7201
|
+
const addColumnStatement = statement;
|
|
7202
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
7203
|
+
const proof = await this.#stableCatalogProof();
|
|
7204
|
+
try {
|
|
7205
|
+
let added = addColumnStatement.column;
|
|
7206
|
+
if (added.sqlDomain?.kind === "enum" && added.sqlDomain.values.length === 0) {
|
|
7207
|
+
const enumName = added.sqlDomain.name;
|
|
7208
|
+
const enumRecord = proof.records.find((record2) => record2.name === `${ENUM_TYPE_PREFIX}${enumName}`);
|
|
7209
|
+
if (enumRecord?.enumType === void 0) {
|
|
7210
|
+
throw new TypeError(`Unsupported column type: ${enumName}`);
|
|
6951
7211
|
}
|
|
6952
|
-
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
|
|
6959
|
-
|
|
6960
|
-
|
|
6961
|
-
|
|
6962
|
-
|
|
6963
|
-
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
if (added.generatedValue !== void 0) {
|
|
6970
|
-
throw new TypeError("ALTER TABLE cannot add a generated column without rewriting existing rows");
|
|
6971
|
-
}
|
|
6972
|
-
if (added.defaultValue !== void 0) {
|
|
6973
|
-
validateColumnDefault({
|
|
6974
|
-
name: added.name,
|
|
6975
|
-
type: added.type,
|
|
6976
|
-
...added.integer === true ? { integer: true } : {},
|
|
6977
|
-
...added.sqlDomain === void 0 ? {} : { sqlDomain: added.sqlDomain },
|
|
6978
|
-
nullable: added.nullable ?? false,
|
|
6979
|
-
isUniqueKey: false,
|
|
6980
|
-
...added.enumValues === void 0 ? {} : { enumValues: added.enumValues }
|
|
6981
|
-
}, added.defaultValue);
|
|
6982
|
-
if (added.defaultValue.kind === "literal" && added.sqlDomain !== void 0) {
|
|
6983
|
-
normalizeSqlDomainValue(added.sqlDomain, added.defaultValue.value);
|
|
7212
|
+
added = {
|
|
7213
|
+
...added,
|
|
7214
|
+
sqlDomain: {
|
|
7215
|
+
kind: "enum",
|
|
7216
|
+
name: enumRecord.enumType.name,
|
|
7217
|
+
values: [...enumRecord.enumType.values]
|
|
7218
|
+
}
|
|
7219
|
+
};
|
|
7220
|
+
}
|
|
7221
|
+
const record = proof.records.find((candidate) => candidate.name === addColumnStatement.table);
|
|
7222
|
+
if (record === void 0)
|
|
7223
|
+
throw new UnknownTableError(addColumnStatement.table);
|
|
7224
|
+
if (record.view !== void 0)
|
|
7225
|
+
throw new TypeError(`${addColumnStatement.table} is a view, not a table`);
|
|
7226
|
+
pinnedTableId ??= record.id;
|
|
7227
|
+
if (record.id !== pinnedTableId) {
|
|
7228
|
+
throw new TableRecordConflictError(pinnedTableId, 0, record.revision);
|
|
6984
7229
|
}
|
|
6985
|
-
if (added.
|
|
6986
|
-
|
|
6987
|
-
|
|
7230
|
+
if (added.nullable !== true && !(addColumnStatement.allowNonNullableWithBackfill === true && added.backfill !== void 0)) {
|
|
7231
|
+
throw new TypeError("ALTER TABLE ADD COLUMN adds a nullable column; existing rows have no value for it");
|
|
7232
|
+
}
|
|
7233
|
+
if (record.columns.some(({ name }) => name === added.name)) {
|
|
7234
|
+
throw new TypeError(`Column already exists: ${addColumnStatement.table}.${added.name}`);
|
|
7235
|
+
}
|
|
7236
|
+
if (added.generatedValue !== void 0) {
|
|
7237
|
+
throw new TypeError("ALTER TABLE cannot add a generated column without rewriting existing rows");
|
|
7238
|
+
}
|
|
7239
|
+
if (added.defaultValue !== void 0) {
|
|
7240
|
+
validateColumnDefault({
|
|
7241
|
+
name: added.name,
|
|
6988
7242
|
type: added.type,
|
|
6989
|
-
...added.
|
|
6990
|
-
|
|
7243
|
+
...added.integer === true ? { integer: true } : {},
|
|
7244
|
+
...added.sqlDomain === void 0 ? {} : { sqlDomain: added.sqlDomain },
|
|
7245
|
+
nullable: added.nullable ?? false,
|
|
7246
|
+
isUniqueKey: false,
|
|
7247
|
+
...added.enumValues === void 0 ? {} : { enumValues: added.enumValues }
|
|
7248
|
+
}, added.defaultValue);
|
|
7249
|
+
if (added.defaultValue.kind === "literal" && added.sqlDomain !== void 0) {
|
|
7250
|
+
normalizeSqlDomainValue(added.sqlDomain, added.defaultValue.value);
|
|
7251
|
+
}
|
|
7252
|
+
if (added.defaultValue.kind === "expression") {
|
|
7253
|
+
validateDefaultExpression(added.defaultValue.sql, {
|
|
7254
|
+
name: `${addColumnStatement.table}.${added.name}`,
|
|
7255
|
+
type: added.type,
|
|
7256
|
+
...added.sqlDomain === void 0 ? {} : { sqlDomain: added.sqlDomain }
|
|
7257
|
+
});
|
|
7258
|
+
}
|
|
6991
7259
|
}
|
|
6992
|
-
|
|
6993
|
-
|
|
6994
|
-
|
|
6995
|
-
|
|
6996
|
-
|
|
6997
|
-
|
|
6998
|
-
|
|
6999
|
-
|
|
7000
|
-
|
|
7001
|
-
|
|
7002
|
-
|
|
7003
|
-
|
|
7004
|
-
|
|
7260
|
+
const columns = [
|
|
7261
|
+
...structuredClone(record.columns),
|
|
7262
|
+
{
|
|
7263
|
+
id: this.#createId(),
|
|
7264
|
+
name: added.name,
|
|
7265
|
+
type: added.type,
|
|
7266
|
+
...added.integer === true ? { integer: true } : {},
|
|
7267
|
+
...added.sqlDomain === void 0 ? {} : { sqlDomain: structuredClone(added.sqlDomain) },
|
|
7268
|
+
nullable: added.nullable ?? false,
|
|
7269
|
+
...added.defaultValue === void 0 ? {} : { defaultValue: added.defaultValue },
|
|
7270
|
+
...added.enumValues === void 0 ? {} : { enumValues: [...added.enumValues] },
|
|
7271
|
+
...added.backfill === void 0 ? {} : { backfill: normalizedColumnBackfill(added, added.backfill) }
|
|
7272
|
+
}
|
|
7273
|
+
];
|
|
7274
|
+
assertTriggerBodiesAcceptTableSchema(proof.records, record, columns);
|
|
7275
|
+
const candidateSchemas = catalogQuerySchemas(proof.records);
|
|
7276
|
+
candidateSchemas.set(record.name, columns);
|
|
7277
|
+
assertDependentViewsKeepSchema(proof.records, record, candidateSchemas);
|
|
7278
|
+
await this.store.updateTable(record.id, record.revision, {
|
|
7279
|
+
columns,
|
|
7280
|
+
expectedCatalogEpoch: proof.catalogEpoch
|
|
7281
|
+
});
|
|
7282
|
+
return { kind: "add-column", table: addColumnStatement.table, column: added.name };
|
|
7283
|
+
} catch (error) {
|
|
7284
|
+
if (!(error instanceof TableRecordConflictError) || attempt >= this.#maxCommitRetries) {
|
|
7285
|
+
throw error;
|
|
7005
7286
|
}
|
|
7006
|
-
];
|
|
7007
|
-
assertTriggerBodiesAcceptTableSchema(proof.records, record, columns);
|
|
7008
|
-
const candidateSchemas = catalogQuerySchemas(proof.records);
|
|
7009
|
-
candidateSchemas.set(record.name, columns);
|
|
7010
|
-
assertDependentViewsKeepSchema(proof.records, record, candidateSchemas);
|
|
7011
|
-
await this.store.updateTable(record.id, record.revision, {
|
|
7012
|
-
columns,
|
|
7013
|
-
expectedCatalogEpoch: proof.catalogEpoch
|
|
7014
|
-
});
|
|
7015
|
-
return { kind: "add-column", table: addColumnStatement.table, column: added.name };
|
|
7016
|
-
} catch (error) {
|
|
7017
|
-
if (!(error instanceof TableRecordConflictError) || attempt >= this.#maxCommitRetries) {
|
|
7018
|
-
throw error;
|
|
7019
7287
|
}
|
|
7020
7288
|
}
|
|
7021
7289
|
}
|
|
7022
|
-
|
|
7023
|
-
|
|
7024
|
-
|
|
7025
|
-
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
|
|
7029
|
-
|
|
7030
|
-
|
|
7031
|
-
|
|
7032
|
-
}
|
|
7033
|
-
|
|
7034
|
-
|
|
7035
|
-
|
|
7036
|
-
|
|
7037
|
-
|
|
7038
|
-
|
|
7039
|
-
|
|
7040
|
-
|
|
7041
|
-
|
|
7042
|
-
|
|
7043
|
-
|
|
7044
|
-
|
|
7045
|
-
|
|
7046
|
-
|
|
7047
|
-
|
|
7048
|
-
|
|
7049
|
-
|
|
7050
|
-
|
|
7051
|
-
|
|
7052
|
-
|
|
7053
|
-
|
|
7054
|
-
|
|
7055
|
-
|
|
7056
|
-
|
|
7057
|
-
|
|
7058
|
-
|
|
7059
|
-
|
|
7060
|
-
|
|
7061
|
-
|
|
7062
|
-
|
|
7063
|
-
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
|
|
7067
|
-
|
|
7068
|
-
|
|
7069
|
-
|
|
7070
|
-
|
|
7071
|
-
|
|
7072
|
-
|
|
7073
|
-
|
|
7074
|
-
|
|
7075
|
-
|
|
7076
|
-
|
|
7077
|
-
|
|
7078
|
-
|
|
7079
|
-
|
|
7080
|
-
|
|
7290
|
+
if (statement.kind === "drop-column") {
|
|
7291
|
+
const dropped = await this.dropColumn(statement.table, statement.column, {
|
|
7292
|
+
...statement.ifExists === true ? { ifExists: true } : {}
|
|
7293
|
+
});
|
|
7294
|
+
return {
|
|
7295
|
+
kind: "drop-column",
|
|
7296
|
+
table: statement.table,
|
|
7297
|
+
column: statement.column,
|
|
7298
|
+
dropped
|
|
7299
|
+
};
|
|
7300
|
+
}
|
|
7301
|
+
if (statement.kind === "merge")
|
|
7302
|
+
return this.#runMerge(statement);
|
|
7303
|
+
if (statement.kind === "create-view") {
|
|
7304
|
+
await this.createView(statement.view, statement.sql, {
|
|
7305
|
+
...statement.orReplace === true ? { orReplace: true } : {},
|
|
7306
|
+
...statement.managed === true ? { managed: true } : {}
|
|
7307
|
+
});
|
|
7308
|
+
return { kind: "create-view", view: statement.view };
|
|
7309
|
+
}
|
|
7310
|
+
if (statement.kind === "drop-view") {
|
|
7311
|
+
const dropped = await this.dropView(statement.view, {
|
|
7312
|
+
...statement.ifExists === true ? { ifExists: true } : {}
|
|
7313
|
+
});
|
|
7314
|
+
return { kind: "drop-view", view: statement.view, dropped };
|
|
7315
|
+
}
|
|
7316
|
+
if (statement.kind === "drop-table") {
|
|
7317
|
+
const dropped = await this.dropTable(statement.table, {
|
|
7318
|
+
...statement.ifExists === true ? { ifExists: true } : {}
|
|
7319
|
+
});
|
|
7320
|
+
return { kind: "drop-table", table: statement.table, dropped };
|
|
7321
|
+
}
|
|
7322
|
+
if (statement.kind === "create-trigger") {
|
|
7323
|
+
await this.#createTrigger(statement.table, statement.trigger);
|
|
7324
|
+
return { kind: "create-trigger", table: statement.table, name: statement.trigger.name };
|
|
7325
|
+
}
|
|
7326
|
+
if (statement.kind === "drop-trigger") {
|
|
7327
|
+
await this.#dropTrigger(statement.name);
|
|
7328
|
+
return { kind: "drop-trigger", name: statement.name };
|
|
7329
|
+
}
|
|
7330
|
+
if (statement.kind === "transaction") {
|
|
7331
|
+
const action = statement.action === "rollback-to" ? "ROLLBACK TO" : statement.action;
|
|
7332
|
+
return this.execute(`${action}${statement.name === void 0 ? "" : ` ${quoteIdentifier(statement.name)}`}`);
|
|
7333
|
+
}
|
|
7334
|
+
if (options.writer === void 0) {
|
|
7335
|
+
return this.#coordinateWrite(async () => {
|
|
7336
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
7337
|
+
try {
|
|
7338
|
+
const completed = await this.#openWriteScope((_session, _transaction, writer) => this.runStatement(statement, { ...options, writer }), { durableSnapshot: false });
|
|
7339
|
+
const result = completed.result;
|
|
7340
|
+
if (result.kind !== "insert" && result.kind !== "update" && result.kind !== "delete") {
|
|
7341
|
+
throw new Error(`Mutation statement returned an unexpected result: ${result.kind}`);
|
|
7342
|
+
}
|
|
7343
|
+
return result.rowCount === 0 || completed.version === null ? result : { ...result, version: completed.version };
|
|
7344
|
+
} catch (error) {
|
|
7345
|
+
if (!(error instanceof WriteConflictError) && !(error instanceof SchemaConflictError) || attempt >= this.#maxCommitRetries) {
|
|
7346
|
+
throw error;
|
|
7347
|
+
}
|
|
7348
|
+
}
|
|
7081
7349
|
}
|
|
7350
|
+
});
|
|
7351
|
+
}
|
|
7352
|
+
if (statement.kind === "insert" && statement.query !== void 0) {
|
|
7353
|
+
statement = await this.#materializeInsertSelect(statement, options.writer);
|
|
7354
|
+
}
|
|
7355
|
+
if (statement.kind === "insert" && statement.columns.length === 0) {
|
|
7356
|
+
const table2 = await this.#findTable(statement.table);
|
|
7357
|
+
const columns = visibleTableColumns(table2).map((column) => column.name);
|
|
7358
|
+
if (statement.defaultValues === true) {
|
|
7359
|
+
} else if (statement.rows.some((row) => row.length !== columns.length)) {
|
|
7360
|
+
throw new TypeError("Each INSERT row must match the table column count");
|
|
7361
|
+
} else {
|
|
7362
|
+
statement = { ...statement, columns };
|
|
7082
7363
|
}
|
|
7083
7364
|
}
|
|
7084
|
-
|
|
7085
|
-
|
|
7086
|
-
|
|
7087
|
-
}
|
|
7088
|
-
if (statement.kind === "insert" && statement.columns.length === 0) {
|
|
7089
|
-
const table2 = await this.#findTable(statement.table);
|
|
7090
|
-
const columns = visibleTableColumns(table2).map((column) => column.name);
|
|
7091
|
-
if (statement.defaultValues === true) {
|
|
7092
|
-
} else if (statement.rows.some((row) => row.length !== columns.length)) {
|
|
7093
|
-
throw new TypeError("Each INSERT row must match the table column count");
|
|
7094
|
-
} else {
|
|
7095
|
-
statement = { ...statement, columns };
|
|
7365
|
+
if (statement.kind === "insert") {
|
|
7366
|
+
const reader = options.writer;
|
|
7367
|
+
statement = await this.#materializeInsertExpressions(statement, statementNow, (plan2) => reader.queryPlan(plan2));
|
|
7096
7368
|
}
|
|
7097
|
-
|
|
7098
|
-
|
|
7099
|
-
|
|
7100
|
-
|
|
7101
|
-
|
|
7102
|
-
|
|
7103
|
-
|
|
7104
|
-
|
|
7105
|
-
|
|
7106
|
-
|
|
7107
|
-
|
|
7108
|
-
|
|
7109
|
-
|
|
7110
|
-
|
|
7111
|
-
|
|
7112
|
-
|
|
7113
|
-
|
|
7114
|
-
|
|
7115
|
-
|
|
7116
|
-
|
|
7117
|
-
|
|
7118
|
-
|
|
7119
|
-
|
|
7120
|
-
|
|
7121
|
-
|
|
7122
|
-
|
|
7123
|
-
|
|
7124
|
-
|
|
7125
|
-
|
|
7126
|
-
|
|
7127
|
-
|
|
7128
|
-
|
|
7129
|
-
|
|
7130
|
-
|
|
7131
|
-
}
|
|
7132
|
-
}
|
|
7133
|
-
const spelledColumns = foldInsertColumns(table2, statement).columns;
|
|
7134
|
-
for (const name of spelledColumns) {
|
|
7135
|
-
if (!table2.columns.some((column) => column.name === name)) {
|
|
7136
|
-
throw new TypeError(`INSERT column does not exist: ${name}`);
|
|
7137
|
-
}
|
|
7138
|
-
}
|
|
7139
|
-
const input = insertStatementBatch({ ...statement, columns: spelledColumns });
|
|
7140
|
-
coerceSqlWriteBatch(table2, input);
|
|
7141
|
-
normalizeDomainBatch(table2, input);
|
|
7142
|
-
let returningColumns2;
|
|
7143
|
-
if (options.returning !== void 0) {
|
|
7144
|
-
returningColumns2 = options.returning === "*" ? visibleTableColumns(table2).map((column) => column.name) : options.returning;
|
|
7145
|
-
for (const name of returningColumns2) {
|
|
7369
|
+
if (statement.returning !== void 0 && options.returning === void 0) {
|
|
7370
|
+
options = { ...options, returning: statement.returning };
|
|
7371
|
+
} else if (statement.returningItems !== void 0 && options.returning === void 0) {
|
|
7372
|
+
options = { ...options, returning: "*" };
|
|
7373
|
+
}
|
|
7374
|
+
if (statement.kind === "insert" && statement.onConflict?.action === "nothing") {
|
|
7375
|
+
statement = await this.#filterConflictingInsertRows(statement, options.writer);
|
|
7376
|
+
}
|
|
7377
|
+
if (statement.kind === "insert" && statement.onConflict?.action === "update") {
|
|
7378
|
+
return this.#mergeConflictingInsertRows(statement, options);
|
|
7379
|
+
}
|
|
7380
|
+
if (statement.kind === "insert" && statement.rows.length === 0) {
|
|
7381
|
+
const table2 = await this.#findTable(statement.table);
|
|
7382
|
+
const returningColumns2 = options.returning === void 0 ? void 0 : options.returning === "*" ? visibleTableColumns(table2).map(({ name }) => name) : [...options.returning];
|
|
7383
|
+
return {
|
|
7384
|
+
kind: "insert",
|
|
7385
|
+
table: statement.table,
|
|
7386
|
+
rowCount: 0,
|
|
7387
|
+
...returningColumns2 === void 0 ? {} : returningExecuteFields(table2, returningColumns2, [])
|
|
7388
|
+
};
|
|
7389
|
+
}
|
|
7390
|
+
if (statement.kind === "insert") {
|
|
7391
|
+
const table2 = await this.#findTable(statement.table);
|
|
7392
|
+
const viaUpsert = statement.onConflict?.action === "replace";
|
|
7393
|
+
if (viaUpsert) {
|
|
7394
|
+
const keyColumn2 = getUniqueKeyColumn(table2);
|
|
7395
|
+
const conflictColumns = statement.onConflict?.columns ?? (statement.onConflict === void 0 ? [] : [statement.onConflict.column]);
|
|
7396
|
+
const addressColumns = keyColumn2?.hidden === true ? primaryKeyColumns(table2).map(({ name }) => name) : keyColumn2 === void 0 ? [] : [keyColumn2.name];
|
|
7397
|
+
if (keyColumn2 === void 0 || conflictColumns.length !== addressColumns.length || conflictColumns.some((name, index) => name !== addressColumns[index])) {
|
|
7398
|
+
throw new TypeError(`ON CONFLICT targets the table's primary or unique key columns: ${addressColumns.join(", ") || "(none)"}`);
|
|
7399
|
+
}
|
|
7400
|
+
}
|
|
7401
|
+
const spelledColumns = foldInsertColumns(table2, statement).columns;
|
|
7402
|
+
for (const name of spelledColumns) {
|
|
7146
7403
|
if (!table2.columns.some((column) => column.name === name)) {
|
|
7147
|
-
throw new TypeError(`
|
|
7404
|
+
throw new TypeError(`INSERT column does not exist: ${name}`);
|
|
7148
7405
|
}
|
|
7149
7406
|
}
|
|
7150
|
-
|
|
7151
|
-
|
|
7152
|
-
|
|
7153
|
-
|
|
7154
|
-
|
|
7155
|
-
|
|
7156
|
-
|
|
7407
|
+
const input = insertStatementBatch({ ...statement, columns: spelledColumns });
|
|
7408
|
+
coerceSqlWriteBatch(table2, input);
|
|
7409
|
+
normalizeDomainBatch(table2, input);
|
|
7410
|
+
let returningColumns2;
|
|
7411
|
+
if (options.returning !== void 0) {
|
|
7412
|
+
returningColumns2 = options.returning === "*" ? visibleTableColumns(table2).map((column) => column.name) : options.returning;
|
|
7413
|
+
for (const name of returningColumns2) {
|
|
7414
|
+
if (!table2.columns.some((column) => column.name === name)) {
|
|
7415
|
+
throw new TypeError(`RETURNING column does not exist: ${name}`);
|
|
7416
|
+
}
|
|
7417
|
+
}
|
|
7157
7418
|
}
|
|
7158
|
-
|
|
7159
|
-
|
|
7160
|
-
|
|
7161
|
-
|
|
7162
|
-
|
|
7163
|
-
|
|
7164
|
-
|
|
7165
|
-
|
|
7166
|
-
...result.version === void 0 ? {} : { version: result.version },
|
|
7167
|
-
...returningColumns2 === void 0 ? {} : {
|
|
7168
|
-
...returningExecuteFields(table2, returningColumns2, statement.rows.map((row, rowIndex) => Object.fromEntries(returningColumns2.map((name) => {
|
|
7169
|
-
const column = table2.columns.find((candidate) => candidate.name === name);
|
|
7170
|
-
const position = spelledColumns.indexOf(name);
|
|
7171
|
-
const inserted = position < 0 ? void 0 : row[position];
|
|
7172
|
-
const value = generated[name]?.[rowIndex] ?? input.columns[name]?.[rowIndex] ?? (inserted === void 0 || isDefaultInsertValue(inserted) ? null : boundInsertValue(inserted));
|
|
7173
|
-
return [name, executionSqlValueFromInput(column, value)];
|
|
7174
|
-
}))))
|
|
7419
|
+
const writer = options.writer;
|
|
7420
|
+
const stagedKeyColumn = writer === void 0 ? void 0 : getUniqueKeyColumn(table2);
|
|
7421
|
+
if (writer !== void 0 && !viaUpsert && stagedKeyColumn !== void 0) {
|
|
7422
|
+
const assignedGenerated = table2.columns.find((column) => column.generatedValue !== void 0 && spelledColumns.includes(column.name));
|
|
7423
|
+
if (assignedGenerated !== void 0) {
|
|
7424
|
+
throw new TypeError(`Generated column cannot be assigned: ${assignedGenerated.name}`);
|
|
7425
|
+
}
|
|
7426
|
+
await this.#assertStagedInsertKeysFree(table2, stagedKeyColumn, input, writer);
|
|
7175
7427
|
}
|
|
7176
|
-
|
|
7177
|
-
|
|
7178
|
-
|
|
7179
|
-
|
|
7180
|
-
|
|
7181
|
-
|
|
7182
|
-
|
|
7183
|
-
|
|
7184
|
-
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
|
|
7196
|
-
|
|
7197
|
-
|
|
7198
|
-
|
|
7199
|
-
|
|
7200
|
-
|
|
7428
|
+
const result = await (viaUpsert ? writer?.upsertBatch(statement.table, input) ?? this.#erased.upsertBatch(statement.table, input) : writer?.insertBatch(statement.table, input) ?? this.#erased.insertBatch(statement.table, input));
|
|
7429
|
+
const generated = "generatedColumns" in result ? result.generatedColumns ?? {} : {};
|
|
7430
|
+
return {
|
|
7431
|
+
kind: "insert",
|
|
7432
|
+
table: statement.table,
|
|
7433
|
+
rowCount: result.rowCount,
|
|
7434
|
+
...result.version === void 0 ? {} : { version: result.version },
|
|
7435
|
+
...returningColumns2 === void 0 ? {} : {
|
|
7436
|
+
...returningExecuteFields(table2, returningColumns2, statement.rows.map((row, rowIndex) => Object.fromEntries(returningColumns2.map((name) => {
|
|
7437
|
+
const column = table2.columns.find((candidate) => candidate.name === name);
|
|
7438
|
+
const position = spelledColumns.indexOf(name);
|
|
7439
|
+
const inserted = position < 0 ? void 0 : row[position];
|
|
7440
|
+
const value = generated[name]?.[rowIndex] ?? input.columns[name]?.[rowIndex] ?? (inserted === void 0 || isDefaultInsertValue(inserted) ? null : boundInsertValue(inserted));
|
|
7441
|
+
return [name, executionSqlValueFromInput(column, value)];
|
|
7442
|
+
}))))
|
|
7443
|
+
}
|
|
7444
|
+
};
|
|
7445
|
+
}
|
|
7446
|
+
const table = await this.#findTable(statement.table);
|
|
7447
|
+
const keyColumn = getUniqueKeyColumn(table);
|
|
7448
|
+
if (keyColumn === void 0) {
|
|
7449
|
+
throw new TypeError(`${statement.kind === "update" ? "UPDATE" : "DELETE"} requires a table with a unique key: ${statement.table}`);
|
|
7450
|
+
}
|
|
7451
|
+
const returningColumns = options.returning === void 0 ? void 0 : options.returning === "*" ? visibleTableColumns(table).map(({ name }) => name) : [...options.returning];
|
|
7452
|
+
const updateAssignments = statement.kind !== "update" ? [] : await (async () => {
|
|
7453
|
+
const spelled = foldAssignmentColumns(table, statement.assignments);
|
|
7454
|
+
if (!table.columns.some((column) => column.sqlDomain !== void 0)) {
|
|
7455
|
+
return spelled;
|
|
7456
|
+
}
|
|
7457
|
+
const { domains } = await this.#catalogFacts();
|
|
7458
|
+
if (!domains.has(table.name))
|
|
7459
|
+
return statement.assignments;
|
|
7460
|
+
const normalized = normalizePlanDomainLiterals({
|
|
7461
|
+
sql: `UPDATE ${table.name}`,
|
|
7462
|
+
base: { table: table.name, alias: table.name },
|
|
7463
|
+
joins: [],
|
|
7464
|
+
select: spelled.map((assignment) => ({
|
|
7465
|
+
expression: assignment.expression,
|
|
7466
|
+
alias: assignment.column
|
|
7467
|
+
})),
|
|
7468
|
+
predicates: [],
|
|
7469
|
+
groupBy: [],
|
|
7470
|
+
having: [],
|
|
7471
|
+
orderBy: []
|
|
7472
|
+
}, domains);
|
|
7473
|
+
return spelled.map((assignment, index) => ({
|
|
7474
|
+
...assignment,
|
|
7475
|
+
expression: normalized.select[index]?.expression ?? assignment.expression
|
|
7476
|
+
}));
|
|
7477
|
+
})();
|
|
7478
|
+
const referenced = /* @__PURE__ */ new Set([keyColumn.name, ...returningColumns ?? []]);
|
|
7479
|
+
const assignmentAlias = (column) => `\0set:${column}`;
|
|
7480
|
+
const targetAlias = statement.alias ?? table.name;
|
|
7481
|
+
const from = statement.from;
|
|
7482
|
+
const plan = optimizePlan({
|
|
7483
|
+
sql: `(${statement.kind})`,
|
|
7484
|
+
base: { table: table.name, alias: targetAlias },
|
|
7485
|
+
joins: from === void 0 ? [] : [crossJoinPlan(from.base), ...from.joins],
|
|
7486
|
+
select: [
|
|
7487
|
+
...[...referenced].map((name) => ({
|
|
7488
|
+
expression: {
|
|
7489
|
+
kind: "column",
|
|
7490
|
+
reference: from === void 0 ? name : `${targetAlias}.${name}`
|
|
7491
|
+
},
|
|
7492
|
+
alias: name
|
|
7493
|
+
})),
|
|
7494
|
+
...statement.kind === "update" ? updateAssignments.map((assignment) => ({
|
|
7495
|
+
expression: assignment.expression,
|
|
7496
|
+
alias: assignmentAlias(assignment.column)
|
|
7497
|
+
})) : []
|
|
7498
|
+
],
|
|
7499
|
+
predicates: from === void 0 ? statement.predicates : [...statement.predicates, ...from.predicates],
|
|
7201
7500
|
groupBy: [],
|
|
7202
7501
|
having: [],
|
|
7203
7502
|
orderBy: []
|
|
7204
|
-
}
|
|
7205
|
-
|
|
7206
|
-
|
|
7207
|
-
|
|
7208
|
-
|
|
7209
|
-
|
|
7210
|
-
|
|
7211
|
-
|
|
7212
|
-
|
|
7213
|
-
|
|
7214
|
-
|
|
7215
|
-
sql: `(${statement.kind})`,
|
|
7216
|
-
base: { table: table.name, alias: targetAlias },
|
|
7217
|
-
joins: from === void 0 ? [] : [crossJoinPlan(from.base), ...from.joins],
|
|
7218
|
-
select: [
|
|
7219
|
-
...[...referenced].map((name) => ({
|
|
7220
|
-
expression: {
|
|
7221
|
-
kind: "column",
|
|
7222
|
-
reference: from === void 0 ? name : `${targetAlias}.${name}`
|
|
7223
|
-
},
|
|
7224
|
-
alias: name
|
|
7225
|
-
})),
|
|
7226
|
-
...statement.kind === "update" ? updateAssignments.map((assignment) => ({
|
|
7227
|
-
expression: assignment.expression,
|
|
7228
|
-
alias: assignmentAlias(assignment.column)
|
|
7229
|
-
})) : []
|
|
7230
|
-
],
|
|
7231
|
-
predicates: from === void 0 ? statement.predicates : [...statement.predicates, ...from.predicates],
|
|
7232
|
-
groupBy: [],
|
|
7233
|
-
having: [],
|
|
7234
|
-
orderBy: []
|
|
7235
|
-
});
|
|
7236
|
-
if (statement.kind === "delete" && returningColumns === void 0 && from === void 0) {
|
|
7237
|
-
const internalWriter = options.writer;
|
|
7238
|
-
if (internalWriter.queryFirstColumn !== void 0) {
|
|
7239
|
-
const selectedKeys = await internalWriter.queryFirstColumn(plan);
|
|
7240
|
-
if (selectedKeys !== void 0) {
|
|
7241
|
-
const keys2 = selectedKeys;
|
|
7242
|
-
if (keyColumn.type === "string" && keyColumn.sqlDomain === void 0) {
|
|
7243
|
-
for (let index = 0; index < keys2.length; index += 1) {
|
|
7244
|
-
keys2[index] = storedSqlValueFromExecution(keyColumn, keys2[index] ?? null);
|
|
7503
|
+
});
|
|
7504
|
+
if (statement.kind === "delete" && returningColumns === void 0 && from === void 0) {
|
|
7505
|
+
const internalWriter = options.writer;
|
|
7506
|
+
if (internalWriter.queryFirstColumn !== void 0) {
|
|
7507
|
+
const selectedKeys = await internalWriter.queryFirstColumn(plan);
|
|
7508
|
+
if (selectedKeys !== void 0) {
|
|
7509
|
+
const keys2 = selectedKeys;
|
|
7510
|
+
if (keyColumn.type === "string" && keyColumn.sqlDomain === void 0) {
|
|
7511
|
+
for (let index = 0; index < keys2.length; index += 1) {
|
|
7512
|
+
keys2[index] = storedSqlValueFromExecution(keyColumn, keys2[index] ?? null);
|
|
7513
|
+
}
|
|
7245
7514
|
}
|
|
7515
|
+
if (keys2.some((key) => key === null)) {
|
|
7516
|
+
throw new TypeError(`Unique key values must not be null: ${keyColumn.name}`);
|
|
7517
|
+
}
|
|
7518
|
+
if (keys2.length === 0) {
|
|
7519
|
+
return { kind: "delete", table: table.name, rowCount: 0 };
|
|
7520
|
+
}
|
|
7521
|
+
const deleted = await internalWriter.deleteBatch(table.name, { keys: keys2 });
|
|
7522
|
+
return {
|
|
7523
|
+
kind: "delete",
|
|
7524
|
+
table: table.name,
|
|
7525
|
+
rowCount: deleted.rowCount
|
|
7526
|
+
};
|
|
7246
7527
|
}
|
|
7247
|
-
|
|
7248
|
-
|
|
7249
|
-
|
|
7250
|
-
|
|
7251
|
-
|
|
7252
|
-
|
|
7253
|
-
|
|
7528
|
+
}
|
|
7529
|
+
}
|
|
7530
|
+
let rows;
|
|
7531
|
+
if (options.writer !== void 0) {
|
|
7532
|
+
rows = (await options.writer.queryPlan(plan)).rows;
|
|
7533
|
+
} else {
|
|
7534
|
+
rows = (await this.#queryCompiled(plan)).rows;
|
|
7535
|
+
}
|
|
7536
|
+
if (from !== void 0) {
|
|
7537
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7538
|
+
rows = rows.filter((row) => {
|
|
7539
|
+
const key = JSON.stringify(row[keyColumn.name] ?? null);
|
|
7540
|
+
if (seen.has(key))
|
|
7541
|
+
return false;
|
|
7542
|
+
seen.add(key);
|
|
7543
|
+
return true;
|
|
7544
|
+
});
|
|
7545
|
+
}
|
|
7546
|
+
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);
|
|
7547
|
+
if (keys.some((key) => key === null)) {
|
|
7548
|
+
throw new TypeError(`Unique key values must not be null: ${keyColumn.name}`);
|
|
7549
|
+
}
|
|
7550
|
+
if (statement.kind === "delete") {
|
|
7551
|
+
const returnedRows2 = returningColumns === void 0 ? void 0 : rows.map((row) => Object.fromEntries(returningColumns.map((name) => [name, row[name] ?? null])));
|
|
7552
|
+
if (keys.length === 0) {
|
|
7254
7553
|
return {
|
|
7255
7554
|
kind: "delete",
|
|
7256
7555
|
table: table.name,
|
|
7257
|
-
rowCount:
|
|
7556
|
+
rowCount: 0,
|
|
7557
|
+
...returnedRows2 === void 0 || returningColumns === void 0 ? {} : returningExecuteFields(table, returningColumns, returnedRows2)
|
|
7258
7558
|
};
|
|
7259
7559
|
}
|
|
7560
|
+
const deleted = options.writer === void 0 ? await this.#erased.deleteBatch(table.name, { keys }) : await options.writer.deleteBatch(table.name, { keys });
|
|
7561
|
+
return {
|
|
7562
|
+
kind: "delete",
|
|
7563
|
+
table: table.name,
|
|
7564
|
+
rowCount: deleted.deletedRowCount ?? deleted.rowCount ?? 0,
|
|
7565
|
+
...deleted.version === void 0 || deleted.version === null ? {} : { version: deleted.version },
|
|
7566
|
+
...returnedRows2 === void 0 || returningColumns === void 0 ? {} : returningExecuteFields(table, returningColumns, returnedRows2)
|
|
7567
|
+
};
|
|
7260
7568
|
}
|
|
7261
|
-
}
|
|
7262
|
-
let rows;
|
|
7263
|
-
if (options.writer !== void 0) {
|
|
7264
|
-
rows = (await options.writer.queryPlan(plan)).rows;
|
|
7265
|
-
} else {
|
|
7266
|
-
rows = (await this.#queryCompiled(plan)).rows;
|
|
7267
|
-
}
|
|
7268
|
-
if (from !== void 0) {
|
|
7269
|
-
const seen = /* @__PURE__ */ new Set();
|
|
7270
|
-
rows = rows.filter((row) => {
|
|
7271
|
-
const key = JSON.stringify(row[keyColumn.name] ?? null);
|
|
7272
|
-
if (seen.has(key))
|
|
7273
|
-
return false;
|
|
7274
|
-
seen.add(key);
|
|
7275
|
-
return true;
|
|
7276
|
-
});
|
|
7277
|
-
}
|
|
7278
|
-
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);
|
|
7279
|
-
if (keys.some((key) => key === null)) {
|
|
7280
|
-
throw new TypeError(`Unique key values must not be null: ${keyColumn.name}`);
|
|
7281
|
-
}
|
|
7282
|
-
if (statement.kind === "delete") {
|
|
7283
|
-
const returnedRows2 = returningColumns === void 0 ? void 0 : rows.map((row) => Object.fromEntries(returningColumns.map((name) => [name, row[name] ?? null])));
|
|
7284
7569
|
if (keys.length === 0) {
|
|
7285
7570
|
return {
|
|
7286
|
-
kind: "
|
|
7571
|
+
kind: "update",
|
|
7287
7572
|
table: table.name,
|
|
7288
7573
|
rowCount: 0,
|
|
7289
|
-
...
|
|
7574
|
+
...returningColumns === void 0 ? {} : returningExecuteFields(table, returningColumns, [])
|
|
7290
7575
|
};
|
|
7291
7576
|
}
|
|
7292
|
-
const
|
|
7293
|
-
|
|
7294
|
-
|
|
7295
|
-
|
|
7296
|
-
|
|
7297
|
-
|
|
7298
|
-
|
|
7299
|
-
|
|
7300
|
-
|
|
7301
|
-
|
|
7577
|
+
const changes = {};
|
|
7578
|
+
const returnedChanges = returningColumns === void 0 ? void 0 : {};
|
|
7579
|
+
for (const assignment of updateAssignments) {
|
|
7580
|
+
const column = table.columns.find((candidate) => candidate.name === assignment.column);
|
|
7581
|
+
if (column === void 0)
|
|
7582
|
+
throw new TypeError(`Unknown column: ${assignment.column}`);
|
|
7583
|
+
const executionValues = returnedChanges === void 0 ? void 0 : [];
|
|
7584
|
+
changes[assignment.column] = rows.map((row) => {
|
|
7585
|
+
const value = row[assignmentAlias(assignment.column)] ?? null;
|
|
7586
|
+
if (typeof value === "number" && !Number.isFinite(value)) {
|
|
7587
|
+
throw new TypeError(`UPDATE assignment produced a non-finite number: ${assignment.column}`);
|
|
7588
|
+
}
|
|
7589
|
+
const stored = storedSqlValueFromExecution(column, value);
|
|
7590
|
+
executionValues?.push(column.type === "string" && column.sqlDomain === void 0 ? value : stored);
|
|
7591
|
+
return stored;
|
|
7592
|
+
});
|
|
7593
|
+
if (executionValues !== void 0 && returnedChanges !== void 0) {
|
|
7594
|
+
returnedChanges[assignment.column] = executionValues;
|
|
7595
|
+
}
|
|
7596
|
+
}
|
|
7597
|
+
const updated = options.writer === void 0 ? await this.#erased.updateBatch(table.name, { keys, changes }) : await options.writer.updateBatch(table.name, { keys, changes });
|
|
7598
|
+
const returnedRows = returningColumns === void 0 ? void 0 : rows.map((row, index) => Object.fromEntries(returningColumns.map((name) => [
|
|
7599
|
+
name,
|
|
7600
|
+
returnedChanges !== void 0 && name in returnedChanges ? returnedChanges[name]?.[index] ?? null : updated.generatedColumns?.[name] !== void 0 ? updated.generatedColumns[name][index] ?? null : row[name] ?? null
|
|
7601
|
+
])));
|
|
7302
7602
|
return {
|
|
7303
7603
|
kind: "update",
|
|
7304
7604
|
table: table.name,
|
|
7305
|
-
rowCount: 0,
|
|
7306
|
-
...
|
|
7605
|
+
rowCount: updated.updatedRowCount ?? updated.rowCount ?? 0,
|
|
7606
|
+
...updated.version === void 0 || updated.version === null ? {} : { version: updated.version },
|
|
7607
|
+
...returnedRows === void 0 || returningColumns === void 0 ? {} : returningExecuteFields(table, returningColumns, returnedRows)
|
|
7307
7608
|
};
|
|
7308
|
-
}
|
|
7309
|
-
const changes = {};
|
|
7310
|
-
const returnedChanges = returningColumns === void 0 ? void 0 : {};
|
|
7311
|
-
for (const assignment of updateAssignments) {
|
|
7312
|
-
const column = table.columns.find((candidate) => candidate.name === assignment.column);
|
|
7313
|
-
if (column === void 0)
|
|
7314
|
-
throw new TypeError(`Unknown column: ${assignment.column}`);
|
|
7315
|
-
const executionValues = returnedChanges === void 0 ? void 0 : [];
|
|
7316
|
-
changes[assignment.column] = rows.map((row) => {
|
|
7317
|
-
const value = row[assignmentAlias(assignment.column)] ?? null;
|
|
7318
|
-
if (typeof value === "number" && !Number.isFinite(value)) {
|
|
7319
|
-
throw new TypeError(`UPDATE assignment produced a non-finite number: ${assignment.column}`);
|
|
7320
|
-
}
|
|
7321
|
-
const stored = storedSqlValueFromExecution(column, value);
|
|
7322
|
-
executionValues?.push(column.type === "string" && column.sqlDomain === void 0 ? value : stored);
|
|
7323
|
-
return stored;
|
|
7324
|
-
});
|
|
7325
|
-
if (executionValues !== void 0 && returnedChanges !== void 0) {
|
|
7326
|
-
returnedChanges[assignment.column] = executionValues;
|
|
7327
|
-
}
|
|
7328
|
-
}
|
|
7329
|
-
const updated = options.writer === void 0 ? await this.#erased.updateBatch(table.name, { keys, changes }) : await options.writer.updateBatch(table.name, { keys, changes });
|
|
7330
|
-
const returnedRows = returningColumns === void 0 ? void 0 : rows.map((row, index) => Object.fromEntries(returningColumns.map((name) => [
|
|
7331
|
-
name,
|
|
7332
|
-
returnedChanges !== void 0 && name in returnedChanges ? returnedChanges[name]?.[index] ?? null : updated.generatedColumns?.[name] !== void 0 ? updated.generatedColumns[name][index] ?? null : row[name] ?? null
|
|
7333
|
-
])));
|
|
7334
|
-
return {
|
|
7335
|
-
kind: "update",
|
|
7336
|
-
table: table.name,
|
|
7337
|
-
rowCount: updated.updatedRowCount ?? updated.rowCount ?? 0,
|
|
7338
|
-
...updated.version === void 0 || updated.version === null ? {} : { version: updated.version },
|
|
7339
|
-
...returnedRows === void 0 || returningColumns === void 0 ? {} : returningExecuteFields(table, returningColumns, returnedRows)
|
|
7340
|
-
};
|
|
7609
|
+
});
|
|
7341
7610
|
}
|
|
7342
7611
|
#canStreamPlanShape(plan, options) {
|
|
7343
7612
|
return (options.executionMemoryBudgetBytes === void 0 || options.spillToStorage !== false) && plan.limitWithTies !== true && plan.base.table !== DUAL_TABLE && plan.base.derived === void 0 && plan.base.union === void 0 && plan.base.windowed === void 0 && plan.base.recursive === void 0 && plan.joins.every((join) => join.derived === void 0 && join.recursive === void 0) && !plan.joins.some((join) => join.table === plan.base.table) && !blockHasSubqueries(plan);
|
|
@@ -7809,6 +8078,24 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
7809
8078
|
this.#cachePut(`dkl ${blockId}`, locators, 64 + locators.size * 48);
|
|
7810
8079
|
return locators;
|
|
7811
8080
|
}
|
|
8081
|
+
async #pointReadVectors(ids, snapshot) {
|
|
8082
|
+
const vectors = ids.map((id) => this.#cacheGet(`dbv ${id}`));
|
|
8083
|
+
const missing = [];
|
|
8084
|
+
const positions = [];
|
|
8085
|
+
for (let index = 0; index < ids.length; index += 1) {
|
|
8086
|
+
if (vectors[index] !== void 0)
|
|
8087
|
+
continue;
|
|
8088
|
+
missing.push(ids[index] ?? "");
|
|
8089
|
+
positions.push(index);
|
|
8090
|
+
}
|
|
8091
|
+
if (missing.length > 0) {
|
|
8092
|
+
const decoded = await this.#decodedBlocksThroughCache(missing, snapshot);
|
|
8093
|
+
for (const [index, block] of decoded.entries()) {
|
|
8094
|
+
vectors[positions[index] ?? 0] = this.#blockColumnVector(missing[index] ?? "", block);
|
|
8095
|
+
}
|
|
8096
|
+
}
|
|
8097
|
+
return vectors;
|
|
8098
|
+
}
|
|
7812
8099
|
#blockColumnVector(blockId, decoded) {
|
|
7813
8100
|
const cached = this.#cacheGet(`dbv ${blockId}`);
|
|
7814
8101
|
if (cached !== void 0)
|
|
@@ -7821,7 +8108,10 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
7821
8108
|
codes?.fill(NULL_STRING_VECTOR_CODE);
|
|
7822
8109
|
const builder = new StringDictionaryBuilder();
|
|
7823
8110
|
appendPhysicalColumnToVector(column, 0, validity, values, codes, builder);
|
|
7824
|
-
const vector =
|
|
8111
|
+
const vector = {
|
|
8112
|
+
nullCount: decoded.description.nullCount,
|
|
8113
|
+
...codes !== void 0 ? { kind: "string", length: rows, validity, codes, dictionary: builder.dictionary } : { kind: column.type, length: rows, validity, values }
|
|
8114
|
+
};
|
|
7825
8115
|
this.#cachePut(`dbv ${blockId}`, vector, blockVectorRetainedBytes(vector));
|
|
7826
8116
|
return vector;
|
|
7827
8117
|
}
|
|
@@ -8359,87 +8649,119 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
8359
8649
|
}
|
|
8360
8650
|
}
|
|
8361
8651
|
async listVisibleSegmentPage(tableName, options = {}) {
|
|
8362
|
-
|
|
8363
|
-
|
|
8364
|
-
|
|
8365
|
-
|
|
8366
|
-
|
|
8367
|
-
|
|
8368
|
-
|
|
8369
|
-
|
|
8370
|
-
|
|
8371
|
-
|
|
8372
|
-
|
|
8373
|
-
|
|
8374
|
-
|
|
8375
|
-
|
|
8376
|
-
|
|
8377
|
-
|
|
8378
|
-
|
|
8379
|
-
|
|
8380
|
-
|
|
8381
|
-
|
|
8382
|
-
|
|
8383
|
-
|
|
8384
|
-
|
|
8385
|
-
|
|
8386
|
-
|
|
8387
|
-
|
|
8388
|
-
|
|
8389
|
-
|
|
8390
|
-
|
|
8391
|
-
|
|
8392
|
-
|
|
8393
|
-
|
|
8394
|
-
|
|
8395
|
-
|
|
8396
|
-
|
|
8397
|
-
record
|
|
8398
|
-
|
|
8399
|
-
|
|
8400
|
-
|
|
8401
|
-
const
|
|
8402
|
-
|
|
8403
|
-
|
|
8404
|
-
|
|
8405
|
-
|
|
8406
|
-
|
|
8407
|
-
|
|
8408
|
-
|
|
8409
|
-
|
|
8410
|
-
|
|
8411
|
-
|
|
8412
|
-
|
|
8413
|
-
|
|
8414
|
-
|
|
8415
|
-
nextCursor: page.nextCursor === null ? null : {
|
|
8416
|
-
tableName: normalizedTableName,
|
|
8417
|
-
tableId,
|
|
8652
|
+
return this.#foreground(async () => {
|
|
8653
|
+
const normalizedTableName = validateName(tableName, "Table");
|
|
8654
|
+
const limit = positiveWholeNumber(options.limit ?? MAX_VISIBLE_SEGMENT_PAGE_ITEMS, "Visible segment page limit");
|
|
8655
|
+
if (limit > MAX_VISIBLE_SEGMENT_PAGE_ITEMS) {
|
|
8656
|
+
throw new RangeError(`Visible segment page limit cannot exceed ${String(MAX_VISIBLE_SEGMENT_PAGE_ITEMS)}`);
|
|
8657
|
+
}
|
|
8658
|
+
const rawCursor = options.cursor;
|
|
8659
|
+
if (rawCursor !== void 0 && (typeof rawCursor !== "object" || rawCursor === null)) {
|
|
8660
|
+
throw new TypeError("A visible segment cursor must be an object");
|
|
8661
|
+
}
|
|
8662
|
+
const cursor = rawCursor;
|
|
8663
|
+
if (cursor !== void 0 && options.version !== void 0) {
|
|
8664
|
+
throw new TypeError("A visible segment cursor cannot be combined with a version");
|
|
8665
|
+
}
|
|
8666
|
+
let tableId;
|
|
8667
|
+
let version;
|
|
8668
|
+
let afterId;
|
|
8669
|
+
if (cursor === void 0) {
|
|
8670
|
+
tableId = (await this.#findTable(normalizedTableName)).id;
|
|
8671
|
+
version = visibleSegmentManifestVersion(options.version, "Visible segment version");
|
|
8672
|
+
afterId = null;
|
|
8673
|
+
} else {
|
|
8674
|
+
if (cursor.tableName !== normalizedTableName) {
|
|
8675
|
+
throw new TypeError("A visible segment cursor belongs to a different table name");
|
|
8676
|
+
}
|
|
8677
|
+
tableId = validateStorageId(cursor.tableId, "Visible segment cursor table ID");
|
|
8678
|
+
version = visibleSegmentManifestVersion(cursor.version, "Visible segment cursor version");
|
|
8679
|
+
if (version === void 0)
|
|
8680
|
+
throw new TypeError("A visible segment cursor needs a version");
|
|
8681
|
+
afterId = validateStorageId(cursor.afterId, "Visible segment cursor ID");
|
|
8682
|
+
}
|
|
8683
|
+
return this.#withLeasedSnapshot(version, async (snapshot) => {
|
|
8684
|
+
await this.#assertVisibleSegmentCursorTable(normalizedTableName, tableId);
|
|
8685
|
+
const page = await this.store.listTableSegmentPage(tableId, afterId, limit);
|
|
8686
|
+
await this.#assertVisibleSegmentCursorTable(normalizedTableName, tableId);
|
|
8687
|
+
const transactions = new Map((await this.#transactionRecordsForSegments(page.records)).map((record) => [
|
|
8688
|
+
record.id,
|
|
8689
|
+
record
|
|
8690
|
+
]));
|
|
8691
|
+
const versionEligible = page.records.filter((segment) => {
|
|
8692
|
+
const committedVersion = transactions.get(segment.transactionId)?.committedVersion;
|
|
8693
|
+
return snapshot.version !== null && committedVersion !== null && committedVersion !== void 0 && committedVersion <= snapshot.version;
|
|
8694
|
+
});
|
|
8695
|
+
const visible = await filterSnapshotSegments(snapshot, versionEligible);
|
|
8696
|
+
return {
|
|
8697
|
+
records: visible.map((segment) => ({
|
|
8698
|
+
id: segment.id,
|
|
8699
|
+
rowCount: segment.rowCount,
|
|
8700
|
+
columnBlockIds: structuredClone(segment.columnBlockIds),
|
|
8701
|
+
logicalOrder: segment.logicalOrder,
|
|
8702
|
+
committedVersion: transactions.get(segment.transactionId)?.committedVersion ?? -1,
|
|
8703
|
+
commitOrdinal: segment.commitOrdinal
|
|
8704
|
+
})),
|
|
8418
8705
|
version: snapshot.version,
|
|
8419
|
-
|
|
8420
|
-
|
|
8421
|
-
|
|
8706
|
+
nextCursor: page.nextCursor === null ? null : {
|
|
8707
|
+
tableName: normalizedTableName,
|
|
8708
|
+
tableId,
|
|
8709
|
+
version: snapshot.version,
|
|
8710
|
+
afterId: page.nextCursor
|
|
8711
|
+
}
|
|
8712
|
+
};
|
|
8713
|
+
});
|
|
8422
8714
|
});
|
|
8423
8715
|
}
|
|
8424
8716
|
async exportSnapshot(options = {}) {
|
|
8425
|
-
|
|
8426
|
-
|
|
8427
|
-
|
|
8428
|
-
|
|
8429
|
-
|
|
8430
|
-
|
|
8431
|
-
|
|
8432
|
-
|
|
8433
|
-
|
|
8434
|
-
|
|
8435
|
-
|
|
8436
|
-
|
|
8437
|
-
|
|
8438
|
-
|
|
8439
|
-
|
|
8440
|
-
|
|
8717
|
+
return this.#foreground(async () => {
|
|
8718
|
+
const chunks = [];
|
|
8719
|
+
let byteLength = 0;
|
|
8720
|
+
for await (const chunk of this.exportSnapshotStream(options)) {
|
|
8721
|
+
byteLength = safeWholeNumberSum([byteLength, chunk.byteLength], "Snapshot byte-array length");
|
|
8722
|
+
if (byteLength > MAX_SNAPSHOT_BYTE_ARRAY_LENGTH) {
|
|
8723
|
+
throw new RangeError("Snapshot exceeds the byte-array API's 4 GiB limit");
|
|
8724
|
+
}
|
|
8725
|
+
chunks.push(chunk);
|
|
8726
|
+
}
|
|
8727
|
+
const bytes = new Uint8Array(byteLength);
|
|
8728
|
+
let offset = 0;
|
|
8729
|
+
for (const chunk of chunks) {
|
|
8730
|
+
bytes.set(chunk, offset);
|
|
8731
|
+
offset += chunk.byteLength;
|
|
8732
|
+
}
|
|
8733
|
+
return bytes;
|
|
8734
|
+
});
|
|
8735
|
+
}
|
|
8736
|
+
exportSnapshotStream(options = {}) {
|
|
8737
|
+
if (this.#closed)
|
|
8738
|
+
throw new Error("Database is closed");
|
|
8739
|
+
if (this.#exports.size >= MAX_DATABASE_ACTIVE_SCOPES)
|
|
8740
|
+
throw new RangeError("Too many open snapshot exports");
|
|
8741
|
+
const iterator = this.#exportSnapshotChunks(options);
|
|
8742
|
+
this.#exports.add(iterator);
|
|
8743
|
+
const next = iterator.next.bind(iterator);
|
|
8744
|
+
const end = iterator.return.bind(iterator);
|
|
8745
|
+
const fail = iterator.throw.bind(iterator);
|
|
8746
|
+
iterator.next = (...args) => this.#foreground(() => next(...args)).then((result) => {
|
|
8747
|
+
if (result.done)
|
|
8748
|
+
this.#exports.delete(iterator);
|
|
8749
|
+
return result;
|
|
8750
|
+
}, (error) => {
|
|
8751
|
+
this.#exports.delete(iterator);
|
|
8752
|
+
throw error;
|
|
8753
|
+
});
|
|
8754
|
+
iterator.return = (value) => end(value).finally(() => {
|
|
8755
|
+
this.#exports.delete(iterator);
|
|
8756
|
+
});
|
|
8757
|
+
iterator.throw = (error) => fail(error).finally(() => {
|
|
8758
|
+
this.#exports.delete(iterator);
|
|
8759
|
+
});
|
|
8760
|
+
return iterator;
|
|
8441
8761
|
}
|
|
8442
|
-
async
|
|
8762
|
+
async *#exportSnapshotChunks(options) {
|
|
8763
|
+
if (this.#closed)
|
|
8764
|
+
throw new Error("Database is closed");
|
|
8443
8765
|
const store = streamingSnapshotExportStore(this.store);
|
|
8444
8766
|
throwIfSnapshotAborted(options.signal);
|
|
8445
8767
|
options.onProgress?.({ phase: "reading", transferredBytes: 0, totalBytes: 0 });
|
|
@@ -8531,160 +8853,178 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
8531
8853
|
}
|
|
8532
8854
|
}
|
|
8533
8855
|
async importSnapshot(bytes, options = {}) {
|
|
8534
|
-
|
|
8856
|
+
return this.#foreground(async () => {
|
|
8857
|
+
await this.importSnapshotStream(singleSnapshotChunk(bytes), options);
|
|
8858
|
+
});
|
|
8535
8859
|
}
|
|
8536
8860
|
async importSnapshotStream(source, options = {}) {
|
|
8537
|
-
|
|
8538
|
-
|
|
8539
|
-
|
|
8540
|
-
|
|
8541
|
-
|
|
8542
|
-
|
|
8543
|
-
|
|
8544
|
-
|
|
8545
|
-
|
|
8546
|
-
|
|
8547
|
-
|
|
8548
|
-
|
|
8549
|
-
|
|
8550
|
-
|
|
8551
|
-
|
|
8552
|
-
|
|
8553
|
-
|
|
8554
|
-
|
|
8555
|
-
|
|
8556
|
-
|
|
8557
|
-
|
|
8558
|
-
|
|
8559
|
-
|
|
8560
|
-
|
|
8561
|
-
|
|
8562
|
-
|
|
8563
|
-
|
|
8564
|
-
|
|
8565
|
-
|
|
8566
|
-
|
|
8567
|
-
for await (const entry of decodeSnapshotFrameStream(abortableSnapshotSource(source, options.signal))) {
|
|
8861
|
+
return this.#foreground(async () => {
|
|
8862
|
+
const store = streamingSnapshotImportStore(this.store);
|
|
8863
|
+
const ownerId = `snapshot-import/${crypto.randomUUID()}`;
|
|
8864
|
+
let identity;
|
|
8865
|
+
let acquired = false;
|
|
8866
|
+
let totalBytes = 0;
|
|
8867
|
+
let writtenBytes = 0;
|
|
8868
|
+
let batchBytes = 0;
|
|
8869
|
+
let metadataBatchBytes = 0;
|
|
8870
|
+
let batchBlockCount = 0;
|
|
8871
|
+
const batch = [];
|
|
8872
|
+
const flush = async () => {
|
|
8873
|
+
if (batch.length === 0 || identity === void 0)
|
|
8874
|
+
return;
|
|
8875
|
+
throwIfSnapshotAborted(options.signal);
|
|
8876
|
+
const times = snapshotSessionTimes(this.#now());
|
|
8877
|
+
const session = await store.appendSnapshotImportFrames({
|
|
8878
|
+
identity,
|
|
8879
|
+
ownerId,
|
|
8880
|
+
expiresAtCutoff: times.createdAt,
|
|
8881
|
+
expiresAt: times.expiresAt,
|
|
8882
|
+
frames: batch.splice(0)
|
|
8883
|
+
});
|
|
8884
|
+
batchBytes = 0;
|
|
8885
|
+
metadataBatchBytes = 0;
|
|
8886
|
+
batchBlockCount = 0;
|
|
8887
|
+
writtenBytes = session.stagedBytes;
|
|
8888
|
+
options.onProgress?.({ phase: "blocks", writtenBytes, totalBytes });
|
|
8889
|
+
};
|
|
8890
|
+
try {
|
|
8568
8891
|
throwIfSnapshotAborted(options.signal);
|
|
8569
|
-
|
|
8570
|
-
|
|
8571
|
-
|
|
8572
|
-
|
|
8573
|
-
|
|
8574
|
-
|
|
8575
|
-
|
|
8892
|
+
for await (const entry of decodeSnapshotFrameStream(abortableSnapshotSource(source, options.signal))) {
|
|
8893
|
+
throwIfSnapshotAborted(options.signal);
|
|
8894
|
+
if (entry.type === "header") {
|
|
8895
|
+
if (identity !== void 0)
|
|
8896
|
+
throw new Error("Snapshot stream repeats its header");
|
|
8897
|
+
identity = snapshotFrameStreamHeaderIdentity(entry.header);
|
|
8898
|
+
totalBytes = Object.values(entry.header.kinds).reduce((total, kind) => safeWholeNumberSum([total, kind.storedBytes], "Snapshot streamed payload length"), 0);
|
|
8899
|
+
const times = snapshotSessionTimes(this.#now());
|
|
8900
|
+
await store.beginSnapshotFrameImport({
|
|
8901
|
+
identity,
|
|
8902
|
+
ownerId,
|
|
8903
|
+
...times,
|
|
8904
|
+
header: entry.header
|
|
8905
|
+
});
|
|
8906
|
+
acquired = true;
|
|
8907
|
+
options.onProgress?.({ phase: "blocks", writtenBytes: 0, totalBytes });
|
|
8908
|
+
continue;
|
|
8909
|
+
}
|
|
8910
|
+
if (entry.type === "frame") {
|
|
8911
|
+
if (identity === void 0)
|
|
8912
|
+
throw new Error("Snapshot frame precedes its header");
|
|
8913
|
+
if (batch.length > 0 && (batch.length >= MAX_SNAPSHOT_FRAME_BATCH_ITEMS || batchBytes + entry.frame.payload.byteLength > MAX_SNAPSHOT_FRAME_BATCH_BYTES || entry.frame.kind === "block" && batchBlockCount > 0 || entry.frame.kind !== "block" && metadataBatchBytes + entry.frame.payload.byteLength > MAX_SNAPSHOT_METADATA_BATCH_BYTES)) {
|
|
8914
|
+
await flush();
|
|
8915
|
+
}
|
|
8916
|
+
batch.push(entry.frame);
|
|
8917
|
+
batchBytes = safeWholeNumberSum([batchBytes, entry.frame.payload.byteLength], "Snapshot import batch bytes");
|
|
8918
|
+
if (entry.frame.kind !== "block") {
|
|
8919
|
+
metadataBatchBytes = safeWholeNumberSum([metadataBatchBytes, entry.frame.payload.byteLength], "Snapshot import metadata batch bytes");
|
|
8920
|
+
} else {
|
|
8921
|
+
batchBlockCount += 1;
|
|
8922
|
+
}
|
|
8923
|
+
continue;
|
|
8924
|
+
}
|
|
8925
|
+
if (identity === void 0)
|
|
8926
|
+
throw new Error("Snapshot footer precedes its header");
|
|
8927
|
+
await flush();
|
|
8928
|
+
throwIfSnapshotAborted(options.signal);
|
|
8929
|
+
const cutoff = dateIsoString(this.#now());
|
|
8930
|
+
options.onProgress?.({ phase: "catalog", writtenBytes, totalBytes });
|
|
8931
|
+
await store.finishSnapshotFrameImport({
|
|
8576
8932
|
identity,
|
|
8577
8933
|
ownerId,
|
|
8578
|
-
|
|
8579
|
-
|
|
8934
|
+
expiresAtCutoff: cutoff,
|
|
8935
|
+
footer: entry.footer
|
|
8580
8936
|
});
|
|
8581
|
-
acquired =
|
|
8582
|
-
options.onProgress?.({ phase: "blocks", writtenBytes: 0, totalBytes });
|
|
8583
|
-
continue;
|
|
8584
|
-
}
|
|
8585
|
-
if (entry.type === "frame") {
|
|
8586
|
-
if (identity === void 0)
|
|
8587
|
-
throw new Error("Snapshot frame precedes its header");
|
|
8588
|
-
if (batch.length > 0 && (batch.length >= MAX_SNAPSHOT_FRAME_BATCH_ITEMS || batchBytes + entry.frame.payload.byteLength > MAX_SNAPSHOT_FRAME_BATCH_BYTES || entry.frame.kind === "block" && batchBlockCount > 0 || entry.frame.kind !== "block" && metadataBatchBytes + entry.frame.payload.byteLength > MAX_SNAPSHOT_METADATA_BATCH_BYTES)) {
|
|
8589
|
-
await flush();
|
|
8590
|
-
}
|
|
8591
|
-
batch.push(entry.frame);
|
|
8592
|
-
batchBytes = safeWholeNumberSum([batchBytes, entry.frame.payload.byteLength], "Snapshot import batch bytes");
|
|
8593
|
-
if (entry.frame.kind !== "block") {
|
|
8594
|
-
metadataBatchBytes = safeWholeNumberSum([metadataBatchBytes, entry.frame.payload.byteLength], "Snapshot import metadata batch bytes");
|
|
8595
|
-
} else {
|
|
8596
|
-
batchBlockCount += 1;
|
|
8597
|
-
}
|
|
8598
|
-
continue;
|
|
8937
|
+
acquired = false;
|
|
8599
8938
|
}
|
|
8600
8939
|
if (identity === void 0)
|
|
8601
|
-
throw new Error("Snapshot
|
|
8602
|
-
|
|
8603
|
-
|
|
8604
|
-
|
|
8605
|
-
options.
|
|
8606
|
-
|
|
8607
|
-
|
|
8608
|
-
|
|
8609
|
-
expiresAtCutoff: cutoff,
|
|
8610
|
-
footer: entry.footer
|
|
8611
|
-
});
|
|
8612
|
-
acquired = false;
|
|
8613
|
-
}
|
|
8614
|
-
if (identity === void 0)
|
|
8615
|
-
throw new Error("Snapshot stream is empty");
|
|
8616
|
-
if (acquired)
|
|
8617
|
-
throw new Error("Snapshot stream ended without its footer");
|
|
8618
|
-
} catch (error) {
|
|
8619
|
-
if (options.signal?.aborted === true && acquired && identity !== void 0) {
|
|
8620
|
-
await store.cancelSnapshotFrameImport({ identity, ownerId });
|
|
8940
|
+
throw new Error("Snapshot stream is empty");
|
|
8941
|
+
if (acquired)
|
|
8942
|
+
throw new Error("Snapshot stream ended without its footer");
|
|
8943
|
+
} catch (error) {
|
|
8944
|
+
if (options.signal?.aborted === true && acquired && identity !== void 0) {
|
|
8945
|
+
await store.cancelSnapshotFrameImport({ identity, ownerId });
|
|
8946
|
+
}
|
|
8947
|
+
throw error;
|
|
8621
8948
|
}
|
|
8622
|
-
|
|
8623
|
-
|
|
8624
|
-
|
|
8625
|
-
|
|
8626
|
-
|
|
8627
|
-
options.onProgress?.({ phase: "done", writtenBytes, totalBytes });
|
|
8949
|
+
this.#planCache.clear();
|
|
8950
|
+
this.#catalogStateCache.clear();
|
|
8951
|
+
this.#catalogStateEpoch = void 0;
|
|
8952
|
+
options.onProgress?.({ phase: "done", writtenBytes, totalBytes });
|
|
8953
|
+
});
|
|
8628
8954
|
}
|
|
8629
8955
|
async checkIntegrity(options = {}) {
|
|
8630
|
-
|
|
8631
|
-
|
|
8632
|
-
|
|
8633
|
-
|
|
8956
|
+
return this.#foreground(async () => {
|
|
8957
|
+
const check = this.store.checkIntegrity?.bind(this.store);
|
|
8958
|
+
if (check === void 0)
|
|
8959
|
+
throw new Error("This database's block store cannot check integrity");
|
|
8960
|
+
return check(options);
|
|
8961
|
+
});
|
|
8634
8962
|
}
|
|
8635
8963
|
async storageStats() {
|
|
8636
|
-
|
|
8637
|
-
|
|
8638
|
-
|
|
8639
|
-
|
|
8964
|
+
return this.#foreground(async () => {
|
|
8965
|
+
const inspect = this.store.getStorageStats?.bind(this.store);
|
|
8966
|
+
if (inspect === void 0)
|
|
8967
|
+
throw new Error("This database's block store cannot report storage stats");
|
|
8968
|
+
return inspect();
|
|
8969
|
+
});
|
|
8640
8970
|
}
|
|
8641
8971
|
async inspectInterruptedImport() {
|
|
8642
|
-
|
|
8643
|
-
|
|
8644
|
-
|
|
8645
|
-
|
|
8972
|
+
return this.#foreground(async () => {
|
|
8973
|
+
const inspect = this.store.inspectInterruptedImport?.bind(this.store);
|
|
8974
|
+
if (inspect === void 0)
|
|
8975
|
+
return null;
|
|
8976
|
+
return inspect();
|
|
8977
|
+
});
|
|
8646
8978
|
}
|
|
8647
8979
|
async abortInterruptedImport(identity) {
|
|
8648
|
-
|
|
8649
|
-
|
|
8650
|
-
|
|
8651
|
-
|
|
8652
|
-
|
|
8980
|
+
return this.#foreground(async () => {
|
|
8981
|
+
const abort = this.store.abortInterruptedImport?.bind(this.store);
|
|
8982
|
+
if (abort === void 0) {
|
|
8983
|
+
throw new Error("This database's block store cannot abort interrupted imports");
|
|
8984
|
+
}
|
|
8985
|
+
return abort(identity);
|
|
8986
|
+
});
|
|
8653
8987
|
}
|
|
8654
8988
|
async compactTable(tableName, options = {}) {
|
|
8655
|
-
|
|
8656
|
-
|
|
8657
|
-
|
|
8658
|
-
|
|
8659
|
-
|
|
8660
|
-
|
|
8661
|
-
|
|
8662
|
-
|
|
8989
|
+
return this.#foreground(async () => {
|
|
8990
|
+
const maxBlocks = boundedMaintenanceBatchItems(options.maxBlocksPerStep ?? 16, "Compaction blocks per step");
|
|
8991
|
+
let progress = await this.compactTableStep(tableName, { ...options, maxBlocks });
|
|
8992
|
+
while (progress.result === null) {
|
|
8993
|
+
if (progress.jobId === null)
|
|
8994
|
+
throw new Error("Compaction progress lost its job ID");
|
|
8995
|
+
progress = await this.resumeCompactionJob(progress.jobId, { maxBlocks });
|
|
8996
|
+
}
|
|
8997
|
+
return progress.result;
|
|
8998
|
+
});
|
|
8663
8999
|
}
|
|
8664
9000
|
async compactTableStep(tableName, options = {}) {
|
|
8665
|
-
|
|
8666
|
-
|
|
8667
|
-
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
|
|
8674
|
-
|
|
8675
|
-
|
|
8676
|
-
|
|
9001
|
+
return this.#foreground(async () => {
|
|
9002
|
+
const maxBlocks = boundedMaintenanceBatchItems(options.maxBlocks ?? options.maxBlocksPerStep ?? 1, "Compaction step block limit");
|
|
9003
|
+
const table = await this.#findTable(tableName);
|
|
9004
|
+
return this.#serializedCompactionStep(table.id, async () => {
|
|
9005
|
+
const active = await this.#findActiveCompactionJob(table.id);
|
|
9006
|
+
let job = active;
|
|
9007
|
+
if (job === void 0) {
|
|
9008
|
+
const planned = await this.#planCompaction(table, options);
|
|
9009
|
+
if ("compacted" in planned)
|
|
9010
|
+
return compactionSkippedProgress(planned);
|
|
9011
|
+
job = planned;
|
|
9012
|
+
}
|
|
9013
|
+
return this.#runCompactionJob(table, job, maxBlocks);
|
|
9014
|
+
});
|
|
8677
9015
|
});
|
|
8678
9016
|
}
|
|
8679
9017
|
async resumeCompactionJob(jobId, options = {}) {
|
|
8680
|
-
|
|
8681
|
-
|
|
8682
|
-
|
|
8683
|
-
|
|
8684
|
-
|
|
8685
|
-
|
|
8686
|
-
|
|
8687
|
-
|
|
9018
|
+
return this.#foreground(async () => {
|
|
9019
|
+
const maxBlocks = boundedMaintenanceBatchItems(options.maxBlocks ?? 1, "Compaction step block limit");
|
|
9020
|
+
const job = await this.store.getCompactionJob(jobId);
|
|
9021
|
+
if (job === void 0)
|
|
9022
|
+
throw new Error(`Compaction job not found: ${jobId}`);
|
|
9023
|
+
const table = await this.store.getTable(job.tableId);
|
|
9024
|
+
if (table === void 0)
|
|
9025
|
+
throw new Error(`Compaction table not found: ${job.tableId}`);
|
|
9026
|
+
return this.#serializedCompactionStep(table.id, () => this.#runCompactionJob(table, job, maxBlocks));
|
|
9027
|
+
});
|
|
8688
9028
|
}
|
|
8689
9029
|
async #serializedCompactionStep(tableId, step) {
|
|
8690
9030
|
const previous = this.#compactionSteps.get(tableId) ?? Promise.resolve();
|
|
@@ -8711,27 +9051,31 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
8711
9051
|
return void 0;
|
|
8712
9052
|
}
|
|
8713
9053
|
async listCompactionJobs(tableName) {
|
|
8714
|
-
|
|
8715
|
-
|
|
8716
|
-
|
|
8717
|
-
|
|
9054
|
+
return this.#foreground(async () => {
|
|
9055
|
+
if (tableName === void 0)
|
|
9056
|
+
return this.store.listCompactionJobs();
|
|
9057
|
+
const table = await this.#findTable(tableName);
|
|
9058
|
+
return this.store.listCompactionJobs(table.id);
|
|
9059
|
+
});
|
|
8718
9060
|
}
|
|
8719
9061
|
async cancelCompactionJob(jobId) {
|
|
8720
|
-
|
|
8721
|
-
|
|
8722
|
-
|
|
8723
|
-
|
|
8724
|
-
|
|
8725
|
-
|
|
8726
|
-
|
|
8727
|
-
|
|
8728
|
-
|
|
8729
|
-
|
|
8730
|
-
|
|
8731
|
-
|
|
8732
|
-
|
|
9062
|
+
return this.#foreground(async () => {
|
|
9063
|
+
for (; ; ) {
|
|
9064
|
+
const job = await this.store.getCompactionJob(jobId);
|
|
9065
|
+
if (job === void 0)
|
|
9066
|
+
throw new Error(`Compaction job not found: ${jobId}`);
|
|
9067
|
+
if (job.state === "cancelled" || job.state === "published" || job.state === "aborted") {
|
|
9068
|
+
return compactionCancellationResult(job);
|
|
9069
|
+
}
|
|
9070
|
+
try {
|
|
9071
|
+
return compactionCancellationResult(await this.store.cancelCompactionJob(job.id, job.revision, dateIsoString(this.#now())));
|
|
9072
|
+
} catch (error) {
|
|
9073
|
+
if (error instanceof CompactionJobConflictError)
|
|
9074
|
+
continue;
|
|
9075
|
+
throw error;
|
|
9076
|
+
}
|
|
8733
9077
|
}
|
|
8734
|
-
}
|
|
9078
|
+
});
|
|
8735
9079
|
}
|
|
8736
9080
|
async #cancelTableCompactions(tableId) {
|
|
8737
9081
|
let cursor = null;
|
|
@@ -8748,28 +9092,32 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
8748
9092
|
} while (cursor !== null);
|
|
8749
9093
|
}
|
|
8750
9094
|
async collectGarbage(options = {}) {
|
|
8751
|
-
|
|
8752
|
-
|
|
8753
|
-
|
|
8754
|
-
|
|
8755
|
-
|
|
8756
|
-
|
|
8757
|
-
|
|
8758
|
-
|
|
8759
|
-
|
|
9095
|
+
return this.#foreground(async () => {
|
|
9096
|
+
const maxItems = boundedMaintenanceBatchItems(options.maxItemsPerStep ?? 64, "Garbage collection items per step");
|
|
9097
|
+
let reconciliationCursor = null;
|
|
9098
|
+
do {
|
|
9099
|
+
reconciliationCursor = (await this.#reconcileCompactionPage(reconciliationCursor)).nextCursor;
|
|
9100
|
+
} while (reconciliationCursor !== null);
|
|
9101
|
+
let progress = await this.collectGarbageStep({
|
|
9102
|
+
maxItems,
|
|
9103
|
+
...options.maxPlanningItems === void 0 ? {} : { maxPlanningItems: options.maxPlanningItems },
|
|
9104
|
+
...options.retainRecentVersions === void 0 ? {} : { retainRecentVersions: options.retainRecentVersions }
|
|
9105
|
+
});
|
|
9106
|
+
while (progress.result === null) {
|
|
9107
|
+
progress = await this.resumeGarbageCollectionJob(progress.jobId, { maxItems });
|
|
9108
|
+
}
|
|
9109
|
+
await this.#pruneFinishedJobRecords();
|
|
9110
|
+
this.#autoCollectionDebtCommits = 0;
|
|
9111
|
+
this.#manualCollectionDebtInitialized = true;
|
|
9112
|
+
this.#commitsSinceCollection = 0;
|
|
9113
|
+
this.#lastCollectionCompletedAt = dateMilliseconds(this.#now());
|
|
9114
|
+
return progress.result;
|
|
8760
9115
|
});
|
|
8761
|
-
while (progress.result === null) {
|
|
8762
|
-
progress = await this.resumeGarbageCollectionJob(progress.jobId, { maxItems });
|
|
8763
|
-
}
|
|
8764
|
-
await this.#pruneFinishedJobRecords();
|
|
8765
|
-
this.#autoCollectionDebtCommits = 0;
|
|
8766
|
-
this.#manualCollectionDebtInitialized = true;
|
|
8767
|
-
this.#commitsSinceCollection = 0;
|
|
8768
|
-
this.#lastCollectionCompletedAt = dateMilliseconds(this.#now());
|
|
8769
|
-
return progress.result;
|
|
8770
9116
|
}
|
|
8771
9117
|
async collectGarbageStep(options = {}) {
|
|
8772
|
-
return this.#
|
|
9118
|
+
return this.#foreground(async () => {
|
|
9119
|
+
return this.#collectGarbageStep(options);
|
|
9120
|
+
});
|
|
8773
9121
|
}
|
|
8774
9122
|
async #collectGarbageStep(options, retainedVersionMaxAgeMs = Number.POSITIVE_INFINITY) {
|
|
8775
9123
|
const maxPlanningItems = boundedMaintenanceBatchItems(options.maxPlanningItems ?? 1024, "Garbage collection planning limit");
|
|
@@ -8794,10 +9142,12 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
8794
9142
|
return void 0;
|
|
8795
9143
|
}
|
|
8796
9144
|
async resumeGarbageCollectionJob(jobId, options = {}) {
|
|
8797
|
-
|
|
8798
|
-
|
|
8799
|
-
|
|
8800
|
-
|
|
9145
|
+
return this.#foreground(async () => {
|
|
9146
|
+
const job = await this.store.getGarbageCollectionJob(jobId);
|
|
9147
|
+
if (job === void 0)
|
|
9148
|
+
throw new Error(`Garbage collection job not found: ${jobId}`);
|
|
9149
|
+
return this.#serializedCollectionStep(() => this.#runGarbageCollectionJob(job, boundedMaintenanceBatchItems(options.maxItems ?? 1, "Garbage collection item limit")));
|
|
9150
|
+
});
|
|
8801
9151
|
}
|
|
8802
9152
|
async #serializedCollectionStep(step) {
|
|
8803
9153
|
const previous = this.#collectionSteps;
|
|
@@ -8915,11 +9265,12 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
8915
9265
|
});
|
|
8916
9266
|
}
|
|
8917
9267
|
const result = progress.result;
|
|
8918
|
-
|
|
9268
|
+
const madeProgress = !(result.prunedManifestCount === 0 && result.reclaimedBlockCount === 0 && result.reclaimedSegmentCount === 0 && result.reclaimedTransactionCount === 0);
|
|
9269
|
+
reclaimed ||= madeProgress;
|
|
9270
|
+
moreWork = (await this.store.getGarbageCollectionJob(result.jobId))?.discovery?.resumePhase != null;
|
|
8919
9271
|
if (!moreWork) {
|
|
8920
9272
|
break;
|
|
8921
9273
|
}
|
|
8922
|
-
reclaimed = true;
|
|
8923
9274
|
await this.#yieldMaintenance();
|
|
8924
9275
|
}
|
|
8925
9276
|
await this.#pruneFinishedJobRecords();
|
|
@@ -9090,7 +9441,9 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
9090
9441
|
} while (cursor !== null);
|
|
9091
9442
|
}
|
|
9092
9443
|
async listGarbageCollectionJobs() {
|
|
9093
|
-
return this
|
|
9444
|
+
return this.#foreground(async () => {
|
|
9445
|
+
return this.store.listGarbageCollectionJobs();
|
|
9446
|
+
});
|
|
9094
9447
|
}
|
|
9095
9448
|
async #planGarbageCollection(maxPlanningItems, retainRecentVersions, retainedVersionMaxAgeMs = Number.POSITIVE_INFINITY) {
|
|
9096
9449
|
await this.#transactions.removeExpiredLeases(this.#now());
|
|
@@ -9248,9 +9601,9 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
9248
9601
|
const complete = reachedPlanningCurrent || page.nextCursor === null || remaining <= 0;
|
|
9249
9602
|
next = {
|
|
9250
9603
|
...next,
|
|
9251
|
-
phase: complete ? remaining <= 0 ? "complete" : discovery.postManifestPhase ?? "
|
|
9604
|
+
phase: complete ? remaining <= 0 ? "complete" : discovery.postManifestPhase ?? "segments" : "manifests",
|
|
9252
9605
|
resumePhase: remaining <= 0 ? "manifests" : null,
|
|
9253
|
-
postManifestPhase: remaining <= 0 ? discovery.postManifestPhase ?? null : null,
|
|
9606
|
+
postManifestPhase: !complete || remaining <= 0 ? discovery.postManifestPhase ?? null : null,
|
|
9254
9607
|
manifestCursor: remaining <= 0 ? lastVisited : complete ? null : page.nextCursor,
|
|
9255
9608
|
visitedRecords: safeWholeNumberSum([discovery.visitedRecords, page.records.length], "Garbage collection discovery records")
|
|
9256
9609
|
};
|
|
@@ -9270,7 +9623,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
9270
9623
|
const lastExaminedBlockId = scanned.nextIndex === 0 ? afterBlockId : pageIds[scanned.nextIndex - 1] ?? afterBlockId;
|
|
9271
9624
|
next = {
|
|
9272
9625
|
...next,
|
|
9273
|
-
phase: remaining <= 0 ? "complete" : reachedEnd ? "
|
|
9626
|
+
phase: remaining <= 0 ? "complete" : reachedEnd ? "transactions" : "manifest-blocks",
|
|
9274
9627
|
resumePhase: remaining <= 0 ? "manifest-blocks" : null,
|
|
9275
9628
|
manifestCursor: null,
|
|
9276
9629
|
artifactCursor: reachedEnd && remaining > 0 ? null : {
|
|
@@ -9283,9 +9636,11 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
9283
9636
|
visitedRecords: safeWholeNumberSum([discovery.visitedRecords, page.records.length], "Garbage collection discovery records")
|
|
9284
9637
|
};
|
|
9285
9638
|
} else if (discovery.phase === "segments") {
|
|
9286
|
-
const page = await this.store.listSegmentPage(discovery.segmentCursor, 1);
|
|
9639
|
+
const page = await this.store.listSegmentPage(discovery.segmentCursor, Math.max(1, Math.min(MAINTENANCE_JOB_PAGE_SIZE, remaining)));
|
|
9640
|
+
const ownerIds = [...new Set(page.records.map((segment) => segment.transactionId))];
|
|
9641
|
+
const owners = new Map((await this.store.getTransactions(ownerIds)).filter((owner) => owner !== void 0).map((owner) => [owner.id, owner]));
|
|
9287
9642
|
for (const segment of page.records) {
|
|
9288
|
-
const listedOwner =
|
|
9643
|
+
const listedOwner = owners.get(segment.transactionId);
|
|
9289
9644
|
if (listedOwner?.status === "active" && Date.parse(listedOwner.expiresAt) <= Date.parse(job.leaseCutoff)) {
|
|
9290
9645
|
await this.store.abortTransactionIfExpired({
|
|
9291
9646
|
transactionId: listedOwner.id,
|
|
@@ -9301,11 +9656,11 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
9301
9656
|
if (remaining <= 0)
|
|
9302
9657
|
break;
|
|
9303
9658
|
}
|
|
9304
|
-
const record = page.records
|
|
9659
|
+
const record = page.records.at(-1);
|
|
9305
9660
|
const filled = remaining <= 0;
|
|
9306
9661
|
next = {
|
|
9307
9662
|
...next,
|
|
9308
|
-
phase: record === void 0 ? "
|
|
9663
|
+
phase: record === void 0 ? "manifest-blocks" : filled ? "complete" : "segments",
|
|
9309
9664
|
resumePhase: filled ? "segments" : null,
|
|
9310
9665
|
segmentCursor: record?.id ?? null,
|
|
9311
9666
|
artifactCursor: null,
|
|
@@ -11369,57 +11724,59 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
11369
11724
|
this.#artifactCache.put(key, payload, bytes);
|
|
11370
11725
|
}
|
|
11371
11726
|
async createIndex(indexName, tableName, requestedColumns, options = {}) {
|
|
11372
|
-
|
|
11373
|
-
|
|
11374
|
-
|
|
11375
|
-
|
|
11376
|
-
|
|
11377
|
-
|
|
11378
|
-
|
|
11379
|
-
|
|
11380
|
-
|
|
11381
|
-
|
|
11382
|
-
|
|
11383
|
-
|
|
11384
|
-
|
|
11385
|
-
|
|
11386
|
-
|
|
11387
|
-
|
|
11388
|
-
const
|
|
11389
|
-
|
|
11390
|
-
|
|
11391
|
-
|
|
11392
|
-
|
|
11393
|
-
|
|
11394
|
-
|
|
11395
|
-
|
|
11396
|
-
|
|
11397
|
-
|
|
11398
|
-
|
|
11399
|
-
|
|
11400
|
-
|
|
11401
|
-
|
|
11402
|
-
|
|
11403
|
-
|
|
11404
|
-
|
|
11405
|
-
|
|
11406
|
-
|
|
11407
|
-
|
|
11408
|
-
|
|
11409
|
-
|
|
11410
|
-
|
|
11411
|
-
|
|
11727
|
+
return this.#foreground(async () => {
|
|
11728
|
+
const indexColumns = typeof requestedColumns === "string" ? [{ name: requestedColumns, direction: "asc" }] : requestedColumns;
|
|
11729
|
+
const tables = await this.store.listTables();
|
|
11730
|
+
if (tables.some((table2) => Object.values(table2.secondaryIndexes ?? {}).some((index) => index.name === indexName))) {
|
|
11731
|
+
throw new TypeError(`Index already exists: ${indexName}`);
|
|
11732
|
+
}
|
|
11733
|
+
const table = tables.find((candidate) => candidate.name === tableName);
|
|
11734
|
+
if (table === void 0 || table.view !== void 0) {
|
|
11735
|
+
throw new UnknownTableError(tableName);
|
|
11736
|
+
}
|
|
11737
|
+
if (indexColumns.length === 0)
|
|
11738
|
+
throw new TypeError("An index needs at least one column");
|
|
11739
|
+
const duplicate = indexColumns.find((column, position) => indexColumns.findIndex((candidate) => candidate.name === column.name) !== position);
|
|
11740
|
+
if (duplicate !== void 0) {
|
|
11741
|
+
throw new TypeError(`Index column appears more than once: ${duplicate.name}`);
|
|
11742
|
+
}
|
|
11743
|
+
const columns = indexColumns.map(({ name }) => {
|
|
11744
|
+
const column = table.columns.find((candidate) => candidate.name === name);
|
|
11745
|
+
if (column === void 0)
|
|
11746
|
+
throw new TypeError(`Unknown column: ${name}`);
|
|
11747
|
+
return column;
|
|
11748
|
+
});
|
|
11749
|
+
const indexId = this.#createId();
|
|
11750
|
+
const storageColumnId = `secondary-index:${indexId}`;
|
|
11751
|
+
const buildId = this.#createId();
|
|
11752
|
+
const marked = await this.store.updateTable(table.id, table.revision, {
|
|
11753
|
+
secondaryIndexes: {
|
|
11754
|
+
...table.secondaryIndexes,
|
|
11755
|
+
[indexId]: {
|
|
11756
|
+
name: indexName,
|
|
11757
|
+
columnId: columns[0]?.id ?? "",
|
|
11758
|
+
columnIds: columns.map((column) => column.id),
|
|
11759
|
+
directions: indexColumns.map((column) => column.direction),
|
|
11760
|
+
...options.unique === true ? { unique: true } : {},
|
|
11761
|
+
termEncoding: "tuple-v1",
|
|
11762
|
+
storage: "postings-v1",
|
|
11763
|
+
storageColumnId,
|
|
11764
|
+
locator: table.uniqueKeyColumnId === void 0 ? "row-id" : "key-hash-v1",
|
|
11765
|
+
state: "building",
|
|
11766
|
+
buildId,
|
|
11767
|
+
buildFromVersion: -1
|
|
11768
|
+
}
|
|
11412
11769
|
}
|
|
11770
|
+
});
|
|
11771
|
+
try {
|
|
11772
|
+
await this.#buildSecondaryIndex(marked, indexId);
|
|
11773
|
+
} catch (error) {
|
|
11774
|
+
await this.#removeSecondaryIndexIfOwned(marked.id, indexId, indexName, storageColumnId).catch((cleanupError) => {
|
|
11775
|
+
throw new AggregateError([error, cleanupError], `CREATE INDEX ${indexName} failed and its catalog cleanup also failed`);
|
|
11776
|
+
});
|
|
11777
|
+
throw error;
|
|
11413
11778
|
}
|
|
11414
11779
|
});
|
|
11415
|
-
try {
|
|
11416
|
-
await this.#buildSecondaryIndex(marked, indexId);
|
|
11417
|
-
} catch (error) {
|
|
11418
|
-
await this.#removeSecondaryIndexIfOwned(marked.id, indexId, indexName, storageColumnId).catch((cleanupError) => {
|
|
11419
|
-
throw new AggregateError([error, cleanupError], `CREATE INDEX ${indexName} failed and its catalog cleanup also failed`);
|
|
11420
|
-
});
|
|
11421
|
-
throw error;
|
|
11422
|
-
}
|
|
11423
11780
|
}
|
|
11424
11781
|
async #removeSecondaryIndexIfOwned(tableId, indexId, indexName, storageColumnId) {
|
|
11425
11782
|
for (let attempt = 0; attempt <= this.#maxCommitRetries; attempt += 1) {
|
|
@@ -11444,20 +11801,22 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
11444
11801
|
}
|
|
11445
11802
|
}
|
|
11446
11803
|
async dropIndex(indexName, options = {}) {
|
|
11447
|
-
|
|
11448
|
-
const
|
|
11449
|
-
|
|
11450
|
-
|
|
11451
|
-
|
|
11452
|
-
|
|
11453
|
-
|
|
11454
|
-
|
|
11455
|
-
|
|
11456
|
-
|
|
11457
|
-
|
|
11458
|
-
|
|
11459
|
-
|
|
11460
|
-
|
|
11804
|
+
return this.#foreground(async () => {
|
|
11805
|
+
for (const table of await this.store.listTables()) {
|
|
11806
|
+
const entry = Object.entries(table.secondaryIndexes ?? {}).find(([, index]) => index.name === indexName);
|
|
11807
|
+
if (entry === void 0)
|
|
11808
|
+
continue;
|
|
11809
|
+
const [indexId] = entry;
|
|
11810
|
+
const indexes = Object.fromEntries(Object.entries(table.secondaryIndexes ?? {}).filter(([candidateId]) => candidateId !== indexId));
|
|
11811
|
+
await this.store.updateTable(table.id, table.revision, {
|
|
11812
|
+
secondaryIndexes: Object.keys(indexes).length === 0 ? null : indexes
|
|
11813
|
+
});
|
|
11814
|
+
return true;
|
|
11815
|
+
}
|
|
11816
|
+
if (options.ifExists === true)
|
|
11817
|
+
return false;
|
|
11818
|
+
throw new TypeError(`Unknown index: ${indexName}`);
|
|
11819
|
+
});
|
|
11461
11820
|
}
|
|
11462
11821
|
async #buildSecondaryIndex(table, indexId) {
|
|
11463
11822
|
const index = table.secondaryIndexes?.[indexId];
|
|
@@ -11826,25 +12185,27 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
11826
12185
|
}
|
|
11827
12186
|
}
|
|
11828
12187
|
async buildFtsIndex(tableName, columnName) {
|
|
11829
|
-
|
|
11830
|
-
|
|
11831
|
-
|
|
11832
|
-
|
|
11833
|
-
|
|
11834
|
-
|
|
11835
|
-
|
|
11836
|
-
|
|
11837
|
-
|
|
11838
|
-
|
|
11839
|
-
|
|
11840
|
-
|
|
11841
|
-
|
|
11842
|
-
|
|
11843
|
-
|
|
11844
|
-
|
|
11845
|
-
|
|
11846
|
-
this.#ftsBuildsInFlight.
|
|
11847
|
-
|
|
12188
|
+
return this.#foreground(async () => {
|
|
12189
|
+
const table = await this.#findTable(tableName);
|
|
12190
|
+
const column = table.columns.find((candidate) => candidate.name === columnName);
|
|
12191
|
+
if (column === void 0)
|
|
12192
|
+
throw new TypeError(`Unknown column: ${columnName}`);
|
|
12193
|
+
const key = `${table.id}/${column.id}`;
|
|
12194
|
+
if (this.#droppingFtsColumns.has(key)) {
|
|
12195
|
+
throw new TypeError(`Column is being dropped: ${tableName}.${columnName}`);
|
|
12196
|
+
}
|
|
12197
|
+
const existing = this.#ftsBuildsInFlight.get(key);
|
|
12198
|
+
if (existing !== void 0)
|
|
12199
|
+
return existing;
|
|
12200
|
+
const build = this.#buildFtsIndex(table, column);
|
|
12201
|
+
this.#ftsBuildsInFlight.set(key, build);
|
|
12202
|
+
try {
|
|
12203
|
+
await build;
|
|
12204
|
+
} finally {
|
|
12205
|
+
if (this.#ftsBuildsInFlight.get(key) === build)
|
|
12206
|
+
this.#ftsBuildsInFlight.delete(key);
|
|
12207
|
+
}
|
|
12208
|
+
});
|
|
11848
12209
|
}
|
|
11849
12210
|
async #buildFtsIndex(table, column) {
|
|
11850
12211
|
const tableName = table.name;
|
|
@@ -13359,6 +13720,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
13359
13720
|
}
|
|
13360
13721
|
}
|
|
13361
13722
|
}
|
|
13723
|
+
#retiredLeaseRenewals = /* @__PURE__ */ new WeakMap();
|
|
13362
13724
|
async #renewInternalLeaseIfNeeded(snapshot) {
|
|
13363
13725
|
if (dateMilliseconds(snapshot.expiresAt) - dateMilliseconds(this.#now()) > INTERNAL_READ_LEASE_TTL_MS / 2) {
|
|
13364
13726
|
return;
|
|
@@ -13370,7 +13732,17 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
13370
13732
|
await this.#sharedLeaseRenewal;
|
|
13371
13733
|
return;
|
|
13372
13734
|
}
|
|
13373
|
-
|
|
13735
|
+
let renewal = this.#retiredLeaseRenewals.get(snapshot);
|
|
13736
|
+
if (renewal === void 0) {
|
|
13737
|
+
renewal = snapshot.renew(INTERNAL_READ_LEASE_TTL_MS).then(() => void 0);
|
|
13738
|
+
this.#retiredLeaseRenewals.set(snapshot, renewal);
|
|
13739
|
+
}
|
|
13740
|
+
try {
|
|
13741
|
+
await renewal;
|
|
13742
|
+
} finally {
|
|
13743
|
+
if (this.#retiredLeaseRenewals.get(snapshot) === renewal)
|
|
13744
|
+
this.#retiredLeaseRenewals.delete(snapshot);
|
|
13745
|
+
}
|
|
13374
13746
|
}
|
|
13375
13747
|
async #encodeColumnBlock(columnId, input, preferred = this.#compression) {
|
|
13376
13748
|
return this.#encodePreferredBlock(columnId, preferred, columnInputBytesBelow(input, GZIP_MINIMUM_INPUT_BYTES), (compression) => encodeBlock(input, compression));
|
|
@@ -14577,19 +14949,6 @@ const LIVE_ORDER_ALIAS = `${LIVE_HIDDEN_PREFIX}order_`;
|
|
|
14577
14949
|
const LIVE_MAINTENANCE_MAX_DELTA_ROWS = 2048;
|
|
14578
14950
|
const LIVE_WINDOW_MARGIN_MIN = 16;
|
|
14579
14951
|
const LIVE_WINDOW_MARGIN_MAX = 64;
|
|
14580
|
-
function sameLiveRow(left, right, columns) {
|
|
14581
|
-
for (const column of columns) {
|
|
14582
|
-
const a = left[column] ?? null;
|
|
14583
|
-
const b = right[column] ?? null;
|
|
14584
|
-
if (a instanceof Date || b instanceof Date) {
|
|
14585
|
-
if (!(a instanceof Date && b instanceof Date) || !Object.is(dateMilliseconds(a), dateMilliseconds(b))) {
|
|
14586
|
-
return false;
|
|
14587
|
-
}
|
|
14588
|
-
} else if (!Object.is(a, b))
|
|
14589
|
-
return false;
|
|
14590
|
-
}
|
|
14591
|
-
return true;
|
|
14592
|
-
}
|
|
14593
14952
|
function liveKeyToken(value) {
|
|
14594
14953
|
if (typeof value === "number")
|
|
14595
14954
|
return `n:${String(value)}`;
|
|
@@ -14609,7 +14968,7 @@ function splitLiveHiddenColumns(executed, state) {
|
|
|
14609
14968
|
const order = state.orderTerms.map(() => new Array(count));
|
|
14610
14969
|
for (let index = 0; index < count; index += 1) {
|
|
14611
14970
|
const row = executed.rows[index] ?? {};
|
|
14612
|
-
keys[index] = row[LIVE_KEY_ALIAS] ?? null;
|
|
14971
|
+
keys[index] = liveKeyToken(row[LIVE_KEY_ALIAS] ?? null);
|
|
14613
14972
|
for (const [term, { alias }] of state.orderTerms.entries()) {
|
|
14614
14973
|
const values = order[term];
|
|
14615
14974
|
if (values !== void 0)
|
|
@@ -14619,28 +14978,26 @@ function splitLiveHiddenColumns(executed, state) {
|
|
|
14619
14978
|
Reflect.deleteProperty(row, column);
|
|
14620
14979
|
}
|
|
14621
14980
|
return {
|
|
14622
|
-
result: {
|
|
14981
|
+
result: externalizeQueryResult({
|
|
14623
14982
|
columns: executed.columns.slice(0, publicCount),
|
|
14624
14983
|
columnDomains: executed.columnDomains.slice(0, publicCount),
|
|
14625
14984
|
rows: executed.rows
|
|
14626
|
-
},
|
|
14985
|
+
}),
|
|
14627
14986
|
keys,
|
|
14628
14987
|
order
|
|
14629
14988
|
};
|
|
14630
14989
|
}
|
|
14631
14990
|
function liveMaintainedOutcome(state, previous, previousIndex) {
|
|
14632
14991
|
const positions = /* @__PURE__ */ new Map();
|
|
14633
|
-
let retainedBytes = 128 + planMemoKey(state.fullPlan).length * 2 + planMemoKey(state.deltaPlan).length * 2;
|
|
14992
|
+
let retainedBytes = state.retainedBytes ?? 128 + planMemoKey(state.fullPlan).length * 2 + planMemoKey(state.deltaPlan).length * 2;
|
|
14634
14993
|
for (const [index, key] of state.keys.entries()) {
|
|
14635
|
-
|
|
14636
|
-
|
|
14637
|
-
|
|
14994
|
+
if (positions.has(key))
|
|
14995
|
+
throw new TypeError("Duplicate live input key");
|
|
14996
|
+
positions.set(key, index);
|
|
14997
|
+
if (state.retainedBytes === void 0)
|
|
14998
|
+
retainedBytes += liveRowStateBytes(state, index);
|
|
14638
14999
|
}
|
|
14639
|
-
|
|
14640
|
-
retainedBytes += 48 + estimateValuesBytes(Object.values(row)) * 2;
|
|
14641
|
-
for (const values of state.order)
|
|
14642
|
-
retainedBytes += values.length * 8 + estimateValuesBytes(values) * 2;
|
|
14643
|
-
state = { ...state, positions };
|
|
15000
|
+
state = { ...state, positions, retainedBytes };
|
|
14644
15001
|
const visibleCount = state.limit === void 0 ? state.rows.length : Math.min(state.rows.length, state.limit);
|
|
14645
15002
|
const visible = state.rows.slice(0, visibleCount);
|
|
14646
15003
|
if (previous === void 0) {
|
|
@@ -14658,12 +15015,10 @@ function liveMaintainedOutcome(state, previous, previousIndex) {
|
|
|
14658
15015
|
const previousCount = previous.rows.length;
|
|
14659
15016
|
const retained = new Int32Array(visibleCount);
|
|
14660
15017
|
let same = visibleCount === previousCount;
|
|
14661
|
-
let kept = 0;
|
|
14662
15018
|
for (let index = 0; index < visibleCount; index += 1) {
|
|
14663
15019
|
const was = previousIndex?.[index] ?? -1;
|
|
14664
15020
|
if (was >= 0 && was < previousCount) {
|
|
14665
15021
|
retained[index] = was;
|
|
14666
|
-
kept += 1;
|
|
14667
15022
|
if (was !== index)
|
|
14668
15023
|
same = false;
|
|
14669
15024
|
continue;
|
|
@@ -14674,7 +15029,6 @@ function liveMaintainedOutcome(state, previous, previousIndex) {
|
|
|
14674
15029
|
if (before !== void 0 && now !== void 0 && sameLiveRow(before, now, previous.columns)) {
|
|
14675
15030
|
visible[index] = before;
|
|
14676
15031
|
retained[index] = index;
|
|
14677
|
-
kept += 1;
|
|
14678
15032
|
} else
|
|
14679
15033
|
same = false;
|
|
14680
15034
|
}
|
|
@@ -14689,9 +15043,15 @@ function liveMaintainedOutcome(state, previous, previousIndex) {
|
|
|
14689
15043
|
state,
|
|
14690
15044
|
retainedBytes,
|
|
14691
15045
|
changed: true,
|
|
14692
|
-
|
|
15046
|
+
retained
|
|
14693
15047
|
};
|
|
14694
15048
|
}
|
|
15049
|
+
function liveRowStateBytes(state, index) {
|
|
15050
|
+
let bytes = 112 + (state.keys[index]?.length ?? 0) * 2 + estimateValuesBytes(Object.values(state.rows[index] ?? {})) * 2;
|
|
15051
|
+
for (const values of state.order)
|
|
15052
|
+
bytes += 8 + estimateValuesBytes([values[index]]) * 2;
|
|
15053
|
+
return bytes;
|
|
15054
|
+
}
|
|
14695
15055
|
function filterLiveRows(state, keep) {
|
|
14696
15056
|
const rows = [];
|
|
14697
15057
|
const keys = [];
|
|
@@ -14702,7 +15062,7 @@ function filterLiveRows(state, keep) {
|
|
|
14702
15062
|
if (row === void 0 || !keep(index))
|
|
14703
15063
|
continue;
|
|
14704
15064
|
rows.push(row);
|
|
14705
|
-
keys.push(state.keys[index] ??
|
|
15065
|
+
keys.push(state.keys[index] ?? "z");
|
|
14706
15066
|
for (const [term, values] of state.order.entries()) {
|
|
14707
15067
|
order[term]?.push(values[index] ?? null);
|
|
14708
15068
|
}
|
|
@@ -14749,7 +15109,7 @@ function mergeLiveRows(kept, added, compare, ordered) {
|
|
|
14749
15109
|
const previousIndex = new Int32Array(total);
|
|
14750
15110
|
const take = (source, from, to) => {
|
|
14751
15111
|
rows[to] = source.rows[from] ?? {};
|
|
14752
|
-
keys[to] = source.keys[from] ??
|
|
15112
|
+
keys[to] = source.keys[from] ?? "z";
|
|
14753
15113
|
for (let term = 0; term < terms; term += 1) {
|
|
14754
15114
|
const values = order[term];
|
|
14755
15115
|
if (values !== void 0)
|
|
@@ -15560,17 +15920,14 @@ function installStreamedWindow(vector, resident, windowStart, windowRows, memory
|
|
|
15560
15920
|
mutable.window = { start: windowStart, length: windowRows };
|
|
15561
15921
|
}
|
|
15562
15922
|
function derivedColumnarTable(name, result, schema) {
|
|
15563
|
-
const columns = new Map(
|
|
15564
|
-
|
|
15565
|
-
{
|
|
15566
|
-
|
|
15567
|
-
|
|
15568
|
-
|
|
15569
|
-
|
|
15570
|
-
|
|
15571
|
-
}
|
|
15572
|
-
]));
|
|
15573
|
-
return createColumnarTable(name, columns);
|
|
15923
|
+
const columns = /* @__PURE__ */ new Map();
|
|
15924
|
+
for (const { name: columnName, type, sqlDomain } of schema) {
|
|
15925
|
+
columns.set(columnName, createColumnVector(type, result.rows.length, (index) => {
|
|
15926
|
+
const value = result.rows[index]?.[columnName] ?? null;
|
|
15927
|
+
return sqlDomain?.kind === "numeric" && typeof value === "number" ? exactNumericValue(value) : value;
|
|
15928
|
+
}));
|
|
15929
|
+
}
|
|
15930
|
+
return { name, rowCount: result.rows.length, columns };
|
|
15574
15931
|
}
|
|
15575
15932
|
function fillColumnVectorRange(vector, start, count, value) {
|
|
15576
15933
|
for (let offset = 0; offset < count; offset += 1) {
|
|
@@ -17399,7 +17756,7 @@ function blockVectorRetainedBytes(vector) {
|
|
|
17399
17756
|
const memoized = blockVectorRetainedBytesMemo.get(vector);
|
|
17400
17757
|
if (memoized !== void 0)
|
|
17401
17758
|
return memoized;
|
|
17402
|
-
const bytes = columnVectorRetainedBytes(vector);
|
|
17759
|
+
const bytes = 8 + columnVectorRetainedBytes(vector);
|
|
17403
17760
|
blockVectorRetainedBytesMemo.set(vector, bytes);
|
|
17404
17761
|
return bytes;
|
|
17405
17762
|
}
|
|
@@ -17713,6 +18070,19 @@ function asColumnInput(type, values) {
|
|
|
17713
18070
|
return { type, values };
|
|
17714
18071
|
}
|
|
17715
18072
|
}
|
|
18073
|
+
async function untilAborted(run, signal) {
|
|
18074
|
+
signal.throwIfAborted();
|
|
18075
|
+
let abort = () => void 0;
|
|
18076
|
+
const cancelled = new Promise((_resolve, reject) => {
|
|
18077
|
+
abort = () => reject(signal.reason instanceof Error ? signal.reason : new Error("Database scope cancelled", { cause: signal.reason }));
|
|
18078
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
18079
|
+
});
|
|
18080
|
+
try {
|
|
18081
|
+
return await Promise.race([run(), cancelled]);
|
|
18082
|
+
} finally {
|
|
18083
|
+
signal.removeEventListener("abort", abort);
|
|
18084
|
+
}
|
|
18085
|
+
}
|
|
17716
18086
|
export {
|
|
17717
18087
|
BufferedTableWriter2 as BufferedTableWriter,
|
|
17718
18088
|
CompactionBacklogError,
|