@minnowdb/core 0.7.5 → 0.7.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/engine/client.d.ts +2 -2
- package/dist/engine/client.js +7 -2
- package/dist/engine/database.d.ts +5 -1
- package/dist/engine/database.js +258 -88
- package/dist/engine/worker-server.js +4 -1
- package/dist/storage/indexeddb.js +13 -8
- package/dist/storage/toolkit/record-core.js +11 -6
- package/dist/storage/types.d.ts +11 -0
- package/package.json +1 -1
package/dist/engine/client.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type CompactionJobRecord, type GarbageCollectionJobRecord, type StorageIntegrityMode, type StorageIntegrityReport, type StorageStats, type InterruptedSnapshotImport, type InterruptedSnapshotImportAbortResult } from "../storage/types.js";
|
|
2
2
|
import { type BatchRow, type InsertBatchInput } from "./batch.js";
|
|
3
3
|
import type { Catalog } from "./catalog.js";
|
|
4
|
-
import type { BatchValue, BufferPoolStats, StagedWriteResult, BufferedFlushResult, BufferedWriterOptions, CancelCompactionJobResult, CollectGarbageOptions, CollectGarbageStepOptions, CompactTableOptions, CompactTableResult, CompactTableStepOptions, CompactionJobProgress, CreateTableInput, DatabaseRow, MigrateOptions, DeleteBatchInput, DeleteBatchResult, ExecuteOptions, ExecuteResult, GarbageCollectionProgress, GarbageCollectionResult, MaintenanceStatus, InsertBatchResult, QueryOptions, QueryExecutionStats, QueryCursorOptions, QuerySpillCleanupOptions, QuerySpillCleanupResult, ReadTableOptions, RunStatementOptions, SnapshotExportOptions, SnapshotImportOptions, TableDefinition, UpdateBatchInput, UpdateBatchResult, UpsertBatchResult, UpsertOptions, VisibleSegmentPage, VisibleSegmentPageOptions } from "./database.js";
|
|
4
|
+
import type { BatchValue, BufferPoolStats, StagedWriteResult, StagedUpsertResult, BufferedFlushResult, BufferedWriterOptions, CancelCompactionJobResult, CollectGarbageOptions, CollectGarbageStepOptions, CompactTableOptions, CompactTableResult, CompactTableStepOptions, CompactionJobProgress, CreateTableInput, DatabaseRow, MigrateOptions, DeleteBatchInput, DeleteBatchResult, ExecuteOptions, ExecuteResult, GarbageCollectionProgress, GarbageCollectionResult, MaintenanceStatus, InsertBatchResult, QueryOptions, QueryExecutionStats, QueryCursorOptions, QuerySpillCleanupOptions, QuerySpillCleanupResult, ReadTableOptions, RunStatementOptions, SnapshotExportOptions, SnapshotImportOptions, TableDefinition, UpdateBatchInput, UpdateBatchResult, UpsertBatchResult, UpsertOptions, VisibleSegmentPage, VisibleSegmentPageOptions } from "./database.js";
|
|
5
5
|
import type { LiveQueryInput, LiveQueryInvalidation, LiveQueryObserveOptions, LiveQueryStats, LiveQuerySubscribeOptions } from "./live.js";
|
|
6
6
|
import type { CompiledQuery, CompiledStatement, QueryResult, QueryValue } from "./query.js";
|
|
7
7
|
import type { AnyTable, SchemaDefinition } from "./schema.js";
|
|
@@ -210,7 +210,7 @@ export interface ClientWriteSession {
|
|
|
210
210
|
query(sql: string, options?: QueryOptions): Promise<QueryResult>;
|
|
211
211
|
execute(sql: string, params?: readonly QueryValue[]): Promise<ExecuteResult>;
|
|
212
212
|
insertBatch(tableName: string, input: InsertBatchInput): Promise<StagedWriteResult>;
|
|
213
|
-
upsertBatch(tableName: string, input: InsertBatchInput): Promise<
|
|
213
|
+
upsertBatch(tableName: string, input: InsertBatchInput, options?: UpsertOptions): Promise<StagedUpsertResult>;
|
|
214
214
|
updateBatch(tableName: string, input: UpdateBatchInput): Promise<StagedWriteResult>;
|
|
215
215
|
deleteBatch(tableName: string, input: DeleteBatchInput): Promise<StagedWriteResult>;
|
|
216
216
|
}
|
package/dist/engine/client.js
CHANGED
|
@@ -290,7 +290,12 @@ class MinnowDatabaseClient {
|
|
|
290
290
|
}
|
|
291
291
|
async write(action) {
|
|
292
292
|
const opened = await this.#call("writeOpen", []);
|
|
293
|
-
const stage = (op, tableName, input) => this._invoke(opened.handleId, "stage", [
|
|
293
|
+
const stage = (op, tableName, input, options) => this._invoke(opened.handleId, "stage", [
|
|
294
|
+
op,
|
|
295
|
+
tableName,
|
|
296
|
+
input,
|
|
297
|
+
options
|
|
298
|
+
]);
|
|
294
299
|
const session = {
|
|
295
300
|
query: async (sql, options = {}) => {
|
|
296
301
|
const { signal, onStats, ...wireOptions } = options;
|
|
@@ -298,7 +303,7 @@ class MinnowDatabaseClient {
|
|
|
298
303
|
},
|
|
299
304
|
execute: (sql, params) => this._invoke(opened.handleId, "execute", params === void 0 ? [sql] : [sql, params]),
|
|
300
305
|
insertBatch: (tableName, input) => stage("insertBatch", tableName, input),
|
|
301
|
-
upsertBatch: (tableName, input) => stage("upsertBatch", tableName, input),
|
|
306
|
+
upsertBatch: (tableName, input, options) => stage("upsertBatch", tableName, input, options),
|
|
302
307
|
updateBatch: (tableName, input) => stage("updateBatch", tableName, input),
|
|
303
308
|
deleteBatch: (tableName, input) => stage("deleteBatch", tableName, input)
|
|
304
309
|
};
|
|
@@ -174,6 +174,10 @@ export interface StagedWriteResult {
|
|
|
174
174
|
/** Values filled by defaults or auto-increment while the rows were staged. */
|
|
175
175
|
generatedColumns?: Record<string, QueryValue[]>;
|
|
176
176
|
}
|
|
177
|
+
export interface StagedUpsertResult extends StagedWriteResult {
|
|
178
|
+
/** Input rows rejected by `conflictWhere`; always 0 without it. */
|
|
179
|
+
skippedRowCount: number;
|
|
180
|
+
}
|
|
177
181
|
/**
|
|
178
182
|
* The scope handed to `write()`: every mutation stages into one transaction and publishes
|
|
179
183
|
* as one commit — all of it or none of it, in every tab. Reads observe the pre-scope snapshot
|
|
@@ -194,7 +198,7 @@ export interface WriteSession {
|
|
|
194
198
|
/** Runs a SELECT, INSERT, UPDATE, or DELETE inside this write scope. */
|
|
195
199
|
execute(sql: string, params?: readonly QueryValue[]): Promise<ExecuteResult>;
|
|
196
200
|
insertBatch(tableName: string, input: InsertBatchInput): Promise<StagedWriteResult>;
|
|
197
|
-
upsertBatch(tableName: string, input: InsertBatchInput): Promise<
|
|
201
|
+
upsertBatch(tableName: string, input: InsertBatchInput, options?: UpsertOptions): Promise<StagedUpsertResult>;
|
|
198
202
|
updateBatch(tableName: string, input: UpdateBatchInput): Promise<StagedWriteResult>;
|
|
199
203
|
deleteBatch(tableName: string, input: DeleteBatchInput): Promise<StagedWriteResult>;
|
|
200
204
|
}
|
package/dist/engine/database.js
CHANGED
|
@@ -13,7 +13,7 @@ import { estimateCompactionRowsPerOutput, planAlignedWriteBlockRanges as writeBl
|
|
|
13
13
|
import { MAX_CACHEABLE_TEXT_CHARACTERS, MAX_SQL_PARAMETERS } from "./cache-limits.js";
|
|
14
14
|
import { fillColumnDefaults, patchAutoIncrementValues } from "./defaults.js";
|
|
15
15
|
import { cachedQueryTerms, FTS_TOKENIZER_VERSION, renderDocumentValue, tokenize as ftsTokenize } from "./fts.js";
|
|
16
|
-
import { boundedMaintenanceBatchItems, simpleDataTypes, floorWholeNumberProduct, BlockReadBatchTooLargeError, validateColumnDefault, validateEnumValues, secondaryIndexColumnIds, secondaryIndexDirections, secondaryUniqueKeyNamespace, CompactionJobConflictError, CompactionBacklogError, GarbageCollectionJobConflictError, MAX_LEVEL_ZERO_SEGMENTS, MAX_FTS_CANDIDATE_ROW_IDS, MAX_FTS_POSTING_TERM_CHARACTERS, MAX_FTS_POSTINGS_PER_CHUNK, MAX_FTS_POSTING_ROW_IDS_PER_CHUNK, MAX_INDEXED_STRING_CHARACTERS, MAX_CATALOG_NAME_CHARACTERS, MAX_MANIFEST_BLOCK_PRESENCE_IDS, MAX_MAINTENANCE_BATCH_ITEMS, MAX_STORAGE_BULK_READ_ITEMS, MAX_TRANSACTION_STAGE_BLOCKS, MAX_TRANSACTION_STAGE_BYTES, MAX_SNAPSHOT_SESSION_TTL_MS, MAX_POSTING_BUILD_TTL_MS, MAX_TEMP_OWNER_TTL_MS, MAX_SNAPSHOT_FRAME_BATCH_BYTES, MAX_SNAPSHOT_FRAME_BATCH_ITEMS, MAX_SNAPSHOT_METADATA_BATCH_BYTES, SNAPSHOT_FRAME_KINDS, validateStorageId, SnapshotManifestMissingError, SchemaConflictError, TableInUseError, TableRecordConflictError, TransactionRecordConflictError, UniqueKeyConflictError, UniqueIndexCoverageError, WriteConflictError } from "../storage/types.js";
|
|
16
|
+
import { boundedMaintenanceBatchItems, simpleDataTypes, floorWholeNumberProduct, BlockReadBatchTooLargeError, validateColumnDefault, validateEnumValues, secondaryIndexColumnIds, secondaryIndexDirections, secondaryUniqueKeyNamespace, CompactionJobConflictError, CompactionBacklogError, GarbageCollectionJobConflictError, MAX_LEVEL_ZERO_SEGMENTS, MAX_FTS_CANDIDATE_ROW_IDS, MAX_FTS_POSTING_TERM_CHARACTERS, MAX_FTS_POSTINGS_PER_CHUNK, MAX_FTS_POSTING_ROW_IDS_PER_CHUNK, MAX_INDEXED_STRING_CHARACTERS, MAX_CATALOG_NAME_CHARACTERS, MAX_MANIFEST_BLOCK_PRESENCE_IDS, MAX_MAINTENANCE_BATCH_ITEMS, MAX_STORAGE_BULK_READ_ITEMS, MAX_BLOCK_READ_BATCH_BYTES, MAX_TRANSACTION_STAGE_BLOCKS, MAX_TRANSACTION_STAGE_BYTES, MAX_SNAPSHOT_SESSION_TTL_MS, MAX_POSTING_BUILD_TTL_MS, MAX_TEMP_OWNER_TTL_MS, MAX_SNAPSHOT_FRAME_BATCH_BYTES, MAX_SNAPSHOT_FRAME_BATCH_ITEMS, MAX_SNAPSHOT_METADATA_BATCH_BYTES, SNAPSHOT_FRAME_KINDS, validateStorageId, SnapshotManifestMissingError, SchemaConflictError, TableInUseError, TableRecordConflictError, TransactionRecordConflictError, UniqueKeyConflictError, UniqueIndexCoverageError, WriteConflictError } from "../storage/types.js";
|
|
17
17
|
import { decodeSnapshotFrameStream, encodeSnapshotFrameStreamFooter, encodeSnapshotFrameStreamHeader, extendSnapshotFrameStreamChecksum, snapshotFrameEnvelopeParts, snapshotFrameStreamHeaderIdentity } from "../storage/snapshot.js";
|
|
18
18
|
import { Snapshot, TransactionManager } from "../transactions/index.js";
|
|
19
19
|
import { applyWindowFunctions, bindPendingSelectShapes, bindPlanParameters, bindStatementParameters, DUAL_TABLE, dualTableRows, blockHasSubqueries, combineUnionResults, compileCheckExpression, compileQuery, hasAggregate, createRecursiveCteState, annotateAvgArgumentScales, compileStatement, comparisonHolds, createPreparedColumnarQuery, evaluateJoinedRowExpression, evaluateRowExpression, externalizeQueryResult, expressionColumnNames, inferBlockSchema, inferResultColumnDomains, isDeferredInsertExpression, isDefaultInsertValue, referencedColumns, childExpressions, expandFtsColumns, expandNaturalJoins, expandSourceColumnAliases, expandViewSources, forEachBlockExpression, forEachNestedBlock, planContainsFts, planHasNaturalJoins, planHasSourceColumnAliases, planReadsTable, planReadsViews, planReadsBeyondSingleScan, projectResultColumns, queryResultNeedsExternalization, resolveStatementDatetimes, subqueryResolutionSteps, topLevelFtsMatchConjuncts, transparentProjectionSource, unknownColumnDomains, validateDefaultExpression, windowOutputDomain, windowOutputType, volatileScalarFunctionNames, annotatePlanIntegerDivision, clonePlanTree, planMayHaveIntegerDivision, foldIdentifierCase, extendGroupByWithKeyDependents, expandRowReferences } from "./query.js";
|
|
@@ -78,6 +78,7 @@ const AUTO_COLLECT_RETRY_MIN_MS = 1e3;
|
|
|
78
78
|
const AUTO_COLLECT_RETRY_MAX_MS = 6e4;
|
|
79
79
|
const AUTO_COMPACT_STEP_BLOCKS = 4;
|
|
80
80
|
const AUTO_COMPACT_MAX_LEVEL_ZERO_SEGMENTS = 256;
|
|
81
|
+
const AUTO_COMPACT_BACKPRESSURE_LEVEL_ZERO_SEGMENTS = 2 * AUTO_COMPACT_MAX_LEVEL_ZERO_SEGMENTS;
|
|
81
82
|
const AUTO_COMPACT_RETRY_MIN_MS = 250;
|
|
82
83
|
const AUTO_COMPACT_RETRY_MAX_MS = 6e4;
|
|
83
84
|
const MAX_COMPACTION_PUBLICATION_CONFLICTS = 64;
|
|
@@ -1612,9 +1613,7 @@ class MinnowDatabase {
|
|
|
1612
1613
|
const { result, batch, generated, autoIncrement } = completed;
|
|
1613
1614
|
collectAutoIncrementGenerated(batch, generated, autoIncrement);
|
|
1614
1615
|
if (result.acceptedRowIndexes.length !== result.requestedRowCount) {
|
|
1615
|
-
|
|
1616
|
-
generated.set(name, result.acceptedRowIndexes.map((index) => values[index] ?? null));
|
|
1617
|
-
}
|
|
1616
|
+
remapGeneratedColumns(generated, result.acceptedRowIndexes);
|
|
1618
1617
|
}
|
|
1619
1618
|
return {
|
|
1620
1619
|
tableName: result.tableName,
|
|
@@ -2092,30 +2091,21 @@ class MinnowDatabase {
|
|
|
2092
2091
|
const normalizedConflictWhere = kind === "upsert" && conflictWhere !== void 0 ? normalizeUpsertConflictWhere(table, conflictWhere) : void 0;
|
|
2093
2092
|
const upsertKeyColumn = kind === "upsert" ? getUniqueKeyColumn(table) : void 0;
|
|
2094
2093
|
if (upsertKeyColumn !== void 0) {
|
|
2095
|
-
upsertFirings = await this.#upsertTriggerFirings(table, upsertKeyColumn, input, rowCount, (sql, params) => this.#sessionQuery(transaction, sql, { params }), normalizedConflictWhere
|
|
2094
|
+
upsertFirings = await this.#upsertTriggerFirings(table, upsertKeyColumn, input, rowCount, (sql, params) => this.#sessionQuery(transaction, sql, { params }), normalizedConflictWhere);
|
|
2096
2095
|
}
|
|
2097
2096
|
if (normalizedConflictWhere !== void 0) {
|
|
2098
2097
|
if (upsertFirings === void 0) {
|
|
2099
2098
|
throw new Error("Upsert conflict classification is missing");
|
|
2100
2099
|
}
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2100
|
+
counts = { inserted: upsertFirings.inserts.length, updated: upsertFirings.updates.length };
|
|
2101
|
+
const filtered = applyUpsertConflictFilter(input, rowCount, upsertFirings);
|
|
2102
|
+
acceptedRowIndexes = filtered.acceptedRowIndexes;
|
|
2103
|
+
skippedRowCount = filtered.skippedRowCount;
|
|
2104
|
+
input = filtered.batch;
|
|
2105
|
+
rowCount = filtered.rowCount;
|
|
2106
|
+
upsertFirings = filtered.firings;
|
|
2106
2107
|
logicalBytes = estimateBatchBytes(input);
|
|
2107
2108
|
resolvedKeys = batchKeys(table, input);
|
|
2108
|
-
counts = {
|
|
2109
|
-
inserted: classifiedFirings.inserts.length,
|
|
2110
|
-
updated: classifiedFirings.updates.length
|
|
2111
|
-
};
|
|
2112
|
-
const acceptedPosition = new Map(acceptedRowIndexes.map((original, position) => [original, position]));
|
|
2113
|
-
upsertFirings = {
|
|
2114
|
-
inserts: classifiedFirings.inserts.map((index) => acceptedPosition.get(index) ?? -1),
|
|
2115
|
-
updates: classifiedFirings.updates.map((index) => acceptedPosition.get(index) ?? -1),
|
|
2116
|
-
skipped: [],
|
|
2117
|
-
oldImages: acceptedRowIndexes.map((index) => classifiedFirings.oldImages[index])
|
|
2118
|
-
};
|
|
2119
2109
|
if (rowCount === 0) {
|
|
2120
2110
|
await transaction.abort();
|
|
2121
2111
|
const version = transaction.snapshotVersion;
|
|
@@ -3973,23 +3963,38 @@ class MinnowDatabase {
|
|
|
3973
3963
|
return;
|
|
3974
3964
|
const hintVersion = transaction?.snapshotVersion ?? await this.store.getCurrentManifestVersion();
|
|
3975
3965
|
const hint = this.#autoCompactionHints.get(table.id);
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3966
|
+
let visible;
|
|
3967
|
+
let levelZero;
|
|
3968
|
+
if (hint?.version === hintVersion) {
|
|
3969
|
+
({ visible, levelZero } = hint);
|
|
3970
|
+
} else {
|
|
3971
|
+
const segments = await this.#currentVisibleSegments(table);
|
|
3972
|
+
visible = segments.length;
|
|
3973
|
+
levelZero = countLevelZeroSegments(segments);
|
|
3983
3974
|
}
|
|
3984
|
-
if (levelZero <
|
|
3975
|
+
if (levelZero < AUTO_COMPACT_BACKPRESSURE_LEVEL_ZERO_SEGMENTS)
|
|
3976
|
+
return;
|
|
3977
|
+
const atCeiling = levelZero >= MAX_LEVEL_ZERO_SEGMENTS;
|
|
3978
|
+
if (!atCeiling && this.#autoCompactionBackoff.get(table.id)?.retryTimer !== void 0)
|
|
3985
3979
|
return;
|
|
3986
3980
|
await this.#publishPendingCompactions();
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3981
|
+
try {
|
|
3982
|
+
const progress = await this.compactTableStep(table.name, {
|
|
3983
|
+
maxBlocks: AUTO_COMPACT_STEP_BLOCKS,
|
|
3984
|
+
maxLevel0Segments: AUTO_COMPACT_MAX_LEVEL_ZERO_SEGMENTS
|
|
3985
|
+
});
|
|
3986
|
+
if (!atCeiling && progress.result !== null && !progress.result.compacted) {
|
|
3987
|
+
this.#backOffAutoCompaction(table.id, visible);
|
|
3988
|
+
}
|
|
3989
|
+
} catch (error) {
|
|
3990
|
+
if (atCeiling)
|
|
3991
|
+
throw error;
|
|
3992
|
+
this.#backOffAutoCompaction(table.id, visible);
|
|
3993
|
+
return;
|
|
3994
|
+
}
|
|
3995
|
+
if (!atCeiling)
|
|
3996
|
+
return;
|
|
3997
|
+
levelZero = countLevelZeroSegments(await this.#currentVisibleSegments(table));
|
|
3993
3998
|
if (levelZero >= MAX_LEVEL_ZERO_SEGMENTS) {
|
|
3994
3999
|
throw new CompactionBacklogError(table.name, levelZero, MAX_LEVEL_ZERO_SEGMENTS);
|
|
3995
4000
|
}
|
|
@@ -4299,9 +4304,9 @@ class MinnowDatabase {
|
|
|
4299
4304
|
}
|
|
4300
4305
|
}
|
|
4301
4306
|
}
|
|
4302
|
-
async #upsertTriggerFirings(table, keyColumn, batch, rowCount, readRows, conflictWhere
|
|
4307
|
+
async #upsertTriggerFirings(table, keyColumn, batch, rowCount, readRows, conflictWhere) {
|
|
4303
4308
|
const fires = (table.triggers ?? []).some((trigger) => trigger.event === "insert" || trigger.event === "update");
|
|
4304
|
-
if (!fires &&
|
|
4309
|
+
if (!fires && conflictWhere === void 0 || rowCount === 0)
|
|
4305
4310
|
return void 0;
|
|
4306
4311
|
const quote = (name) => `"${name.replaceAll('"', '""')}"`;
|
|
4307
4312
|
const keyValues = batch.columns[keyColumn.name] ?? [];
|
|
@@ -4415,8 +4420,15 @@ class MinnowDatabase {
|
|
|
4415
4420
|
if (target.uniqueKeyColumnId !== void 0) {
|
|
4416
4421
|
throw new TypeError(`Trigger bodies insert into keyless tables only: ${target.name}`);
|
|
4417
4422
|
}
|
|
4423
|
+
let columns = compiled.columns;
|
|
4424
|
+
if (columns.length === 0 && compiled.defaultValues !== true) {
|
|
4425
|
+
columns = visibleTableColumns(target).map((column) => column.name);
|
|
4426
|
+
if (derivedRows.some((row) => row.length !== columns.length)) {
|
|
4427
|
+
throw new TypeError(`Trigger body INSERT must match the ${String(columns.length)} visible columns of ${target.name}`);
|
|
4428
|
+
}
|
|
4429
|
+
}
|
|
4418
4430
|
const statementNow = this.#now();
|
|
4419
|
-
const materialized = await this.#materializeInsertExpressions({ ...compiled, rows: derivedRows }, statementNow);
|
|
4431
|
+
const materialized = await this.#materializeInsertExpressions({ ...compiled, columns, rows: derivedRows }, statementNow);
|
|
4420
4432
|
const input = insertStatementBatch(materialized);
|
|
4421
4433
|
coerceSqlWriteBatch(target, input);
|
|
4422
4434
|
const filled = await fillColumnDefaults(target, input, (sql) => this.#evaluateDefaultExpression(sql, statementNow), derivedRows.length);
|
|
@@ -4629,10 +4641,10 @@ class MinnowDatabase {
|
|
|
4629
4641
|
staged += 1;
|
|
4630
4642
|
return guarded(() => this.#sessionInsert(transaction, tableName, input, "insert"));
|
|
4631
4643
|
},
|
|
4632
|
-
upsertBatch: async (tableName, input) => {
|
|
4644
|
+
upsertBatch: async (tableName, input, options2) => {
|
|
4633
4645
|
open();
|
|
4634
4646
|
staged += 1;
|
|
4635
|
-
return guarded(() => this.#sessionInsert(transaction, tableName, input, "upsert"));
|
|
4647
|
+
return guarded(() => this.#sessionInsert(transaction, tableName, input, "upsert", options2));
|
|
4636
4648
|
},
|
|
4637
4649
|
updateBatch: async (tableName, input) => {
|
|
4638
4650
|
open();
|
|
@@ -4866,10 +4878,12 @@ class MinnowDatabase {
|
|
|
4866
4878
|
}
|
|
4867
4879
|
return { added, removed };
|
|
4868
4880
|
}
|
|
4869
|
-
async #sessionInsert(transaction, tableName, input, kind, cascadeBudget = 1) {
|
|
4881
|
+
async #sessionInsert(transaction, tableName, input, kind, options, cascadeBudget = 1) {
|
|
4870
4882
|
const table = await this.#findTable(tableName);
|
|
4871
|
-
await this.#
|
|
4872
|
-
const {
|
|
4883
|
+
const filled = await this.#fillDefaults(table, input);
|
|
4884
|
+
const { generated, autoIncrement } = filled;
|
|
4885
|
+
let batch = filled.batch;
|
|
4886
|
+
let rowCount = filled.rowCount;
|
|
4873
4887
|
if (autoIncrement !== void 0 && autoIncrement.missingIndexes.length > 0) {
|
|
4874
4888
|
const values = await this.store.reserveAutoIncrement(table.id, autoIncrement.column.id, autoIncrement.missingIndexes.length, autoIncrement.atLeast);
|
|
4875
4889
|
patchAutoIncrementValues(batch, autoIncrement, values);
|
|
@@ -4878,6 +4892,36 @@ class MinnowDatabase {
|
|
|
4878
4892
|
validateValue(autoIncrement.column, patched[rowIndex] ?? null, rowIndex);
|
|
4879
4893
|
}
|
|
4880
4894
|
}
|
|
4895
|
+
const sessionUpsertKeyColumn = kind === "upsert" ? getUniqueKeyColumn(table) : void 0;
|
|
4896
|
+
const normalizedConflictWhere = kind === "upsert" && options?.conflictWhere !== void 0 ? normalizeUpsertConflictWhere(table, options.conflictWhere) : void 0;
|
|
4897
|
+
if (normalizedConflictWhere !== void 0 && sessionUpsertKeyColumn === void 0) {
|
|
4898
|
+
throw new TypeError(`Table needs a unique key before it can be upserted: ${table.name}`);
|
|
4899
|
+
}
|
|
4900
|
+
let sessionUpsertFirings = sessionUpsertKeyColumn === void 0 ? void 0 : await this.#upsertTriggerFirings(table, sessionUpsertKeyColumn, batch, rowCount, (sql, params) => this.#sessionQuery(transaction, sql, { params }), normalizedConflictWhere);
|
|
4901
|
+
let skippedRowCount = 0;
|
|
4902
|
+
if (normalizedConflictWhere !== void 0) {
|
|
4903
|
+
if (sessionUpsertFirings === void 0) {
|
|
4904
|
+
throw new Error("Upsert conflict classification is missing");
|
|
4905
|
+
}
|
|
4906
|
+
const filtered = applyUpsertConflictFilter(batch, rowCount, sessionUpsertFirings);
|
|
4907
|
+
skippedRowCount = filtered.skippedRowCount;
|
|
4908
|
+
if (filtered.rowCount !== rowCount) {
|
|
4909
|
+
remapGeneratedColumns(generated, filtered.acceptedRowIndexes);
|
|
4910
|
+
}
|
|
4911
|
+
batch = filtered.batch;
|
|
4912
|
+
rowCount = filtered.rowCount;
|
|
4913
|
+
sessionUpsertFirings = filtered.firings;
|
|
4914
|
+
if (rowCount === 0) {
|
|
4915
|
+
return {
|
|
4916
|
+
tableName: table.name,
|
|
4917
|
+
segmentId: null,
|
|
4918
|
+
rowCount: 0,
|
|
4919
|
+
skippedRowCount,
|
|
4920
|
+
...generated.size === 0 ? {} : { generatedColumns: Object.fromEntries(generated) }
|
|
4921
|
+
};
|
|
4922
|
+
}
|
|
4923
|
+
}
|
|
4924
|
+
await this.#assertCompactionCapacity(table, transaction);
|
|
4881
4925
|
const keys = batchKeys(table, batch);
|
|
4882
4926
|
if (keys !== void 0) {
|
|
4883
4927
|
transaction.setUniqueKeyChanges({
|
|
@@ -4889,8 +4933,6 @@ class MinnowDatabase {
|
|
|
4889
4933
|
await this.#assertForeignKeysPresent(table, (column) => batch.columns[column] ?? [], (sql, params) => this.#sessionQuery(transaction, sql, { params }), transaction);
|
|
4890
4934
|
const rowIds = await this.store.reserveRowIds(table.id, rowCount);
|
|
4891
4935
|
const insertValueAt = (source, column, rowIndex) => source === "new" ? batch.columns[column]?.[rowIndex] ?? null : null;
|
|
4892
|
-
const sessionUpsertKeyColumn = kind === "upsert" ? getUniqueKeyColumn(table) : void 0;
|
|
4893
|
-
const sessionUpsertFirings = sessionUpsertKeyColumn === void 0 ? void 0 : await this.#upsertTriggerFirings(table, sessionUpsertKeyColumn, batch, rowCount, (sql, params) => this.#sessionQuery(transaction, sql, { params }));
|
|
4894
4936
|
stageSecondaryUniqueInsertChanges(transaction, table, batch, kind === "upsert" ? sessionUpsertFirings?.oldImages : void 0);
|
|
4895
4937
|
if (kind === "insert") {
|
|
4896
4938
|
await this.#stageTriggerDerivedInserts(transaction, table, "insert", rowCount, insertValueAt, "before", cascadeBudget);
|
|
@@ -4908,6 +4950,7 @@ class MinnowDatabase {
|
|
|
4908
4950
|
tableName: table.name,
|
|
4909
4951
|
segmentId,
|
|
4910
4952
|
rowCount,
|
|
4953
|
+
skippedRowCount,
|
|
4911
4954
|
...generated.size === 0 ? {} : { generatedColumns: Object.fromEntries(generated) }
|
|
4912
4955
|
};
|
|
4913
4956
|
}
|
|
@@ -9275,7 +9318,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
9275
9318
|
}
|
|
9276
9319
|
const priorAttemptOutputStoredBytes = await this.#priorCompactionAttemptOutputStoredBytes(table.id, baseJobId);
|
|
9277
9320
|
const maximumOutputStoredBytes = Math.max(0, floorWholeNumberProduct(selection.level0SourceStoredBytes, maxWriteAmplification, "Compaction maximum output stored bytes") - priorAttemptOutputStoredBytes);
|
|
9278
|
-
const plannedOutputStoredBytesUpperBound = await this.#plannedPhysicalOutputStoredBytesUpperBound(rewritePlan, snapshot);
|
|
9321
|
+
const plannedOutputStoredBytesUpperBound = await this.#plannedPhysicalOutputStoredBytesUpperBound(rewritePlan, memoryBudgetBytes, snapshot);
|
|
9279
9322
|
levelTwoBudget = {
|
|
9280
9323
|
outputPartitionOrdinal,
|
|
9281
9324
|
maxWriteAmplification,
|
|
@@ -9731,17 +9774,15 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
9731
9774
|
const columnIndexById = new Map(table.columns.map((column, index) => [column.id, index]));
|
|
9732
9775
|
const touched = /* @__PURE__ */ new Set();
|
|
9733
9776
|
const deltaKeys = /* @__PURE__ */ new Map();
|
|
9734
|
-
|
|
9735
|
-
|
|
9736
|
-
|
|
9737
|
-
|
|
9738
|
-
|
|
9739
|
-
|
|
9740
|
-
|
|
9741
|
-
|
|
9742
|
-
|
|
9743
|
-
deltaKeys.set(segment.segmentId, keys);
|
|
9744
|
-
}
|
|
9777
|
+
const deltas = segments.filter((segment) => mergeSourceReferencesKeys(segment.kind));
|
|
9778
|
+
for (const segment of deltas)
|
|
9779
|
+
deltaKeys.set(segment.segmentId, []);
|
|
9780
|
+
const unusedBudgetBytes = memoryBudgetBytes - plannerMemoryBytes;
|
|
9781
|
+
await this.#forEachMergeSourceKey(deltas, keyColumn, unusedBudgetBytes, snapshot, (segment, value) => {
|
|
9782
|
+
const key = overlayKeyOf(keyColumn.type, value);
|
|
9783
|
+
deltaKeys.get(segment.segmentId)?.push(key);
|
|
9784
|
+
touched.add(key);
|
|
9785
|
+
});
|
|
9745
9786
|
let slotCount = 0;
|
|
9746
9787
|
for (const segment of segments) {
|
|
9747
9788
|
if (mergeSourceBearsRows(segment.kind))
|
|
@@ -9813,7 +9854,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
9813
9854
|
if (keys !== void 0) {
|
|
9814
9855
|
keys.forEach(visit);
|
|
9815
9856
|
} else if (touched.size > 0) {
|
|
9816
|
-
await this.#forEachMergeSourceKey(segment, keyColumn, snapshot, (value, rowIndex) => {
|
|
9857
|
+
await this.#forEachMergeSourceKey([segment], keyColumn, unusedBudgetBytes, snapshot, (_, value, rowIndex) => {
|
|
9817
9858
|
visit(overlayKeyOf(keyColumn.type, value), rowIndex);
|
|
9818
9859
|
});
|
|
9819
9860
|
}
|
|
@@ -9850,28 +9891,48 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
9850
9891
|
}
|
|
9851
9892
|
return { ...output.finish(), sourceOutputRowStarts };
|
|
9852
9893
|
}
|
|
9853
|
-
async #forEachMergeSourceKey(
|
|
9854
|
-
const
|
|
9855
|
-
|
|
9856
|
-
|
|
9894
|
+
async #forEachMergeSourceKey(segments, column, unusedBudgetBytes, snapshot, action) {
|
|
9895
|
+
const blocks = [];
|
|
9896
|
+
let largestEncodedBytes = 0;
|
|
9897
|
+
for (const segment of segments) {
|
|
9898
|
+
const planned = segment.columns.find((candidate) => candidate.columnId === column.id);
|
|
9899
|
+
if (planned === void 0) {
|
|
9900
|
+
throw new Error(`Mutation segment has no key column: ${segment.segmentId}`);
|
|
9901
|
+
}
|
|
9902
|
+
for (const source of planned.sourceBlocks) {
|
|
9903
|
+
blocks.push({
|
|
9904
|
+
blockId: source.blockId,
|
|
9905
|
+
storedBytes: source.storedBytes,
|
|
9906
|
+
encodedBytes: source.encodedBytes,
|
|
9907
|
+
segment,
|
|
9908
|
+
source
|
|
9909
|
+
});
|
|
9910
|
+
largestEncodedBytes = Math.max(largestEncodedBytes, source.encodedBytes);
|
|
9911
|
+
}
|
|
9857
9912
|
}
|
|
9858
|
-
|
|
9859
|
-
|
|
9860
|
-
|
|
9861
|
-
|
|
9862
|
-
|
|
9863
|
-
|
|
9913
|
+
const limitBytes = safeWholeNumberSum([
|
|
9914
|
+
Math.max(0, unusedBudgetBytes),
|
|
9915
|
+
safeWholeNumberProduct(largestEncodedBytes, MERGE_PLANNER_DECODED_KEY_BLOCK_FACTOR, "Mutation compaction key decode memory")
|
|
9916
|
+
], "Mutation compaction key decode memory");
|
|
9917
|
+
const rowIndexes = /* @__PURE__ */ new Map();
|
|
9918
|
+
await this.#decodeCompactionBlocks(blocks, { limitBytes, retainsDecoded: false }, snapshot, async (bytes, { source }) => {
|
|
9864
9919
|
const description = inspectBlock(bytes);
|
|
9865
9920
|
if (bytes.byteLength !== source.storedBytes || description.encodedLength !== source.encodedBytes || description.checksum !== source.checksum || description.rowCount !== source.rowCount || description.type !== column.type) {
|
|
9866
9921
|
throw new Error(`Compaction source block differs from its plan: ${source.blockId}`);
|
|
9867
9922
|
}
|
|
9868
|
-
|
|
9869
|
-
|
|
9923
|
+
return (await decodeBlock(bytes)).column.values;
|
|
9924
|
+
}, (values, { segment }) => {
|
|
9925
|
+
let rowIndex = rowIndexes.get(segment.segmentId) ?? 0;
|
|
9926
|
+
for (const value of values) {
|
|
9927
|
+
action(segment, value, rowIndex);
|
|
9870
9928
|
rowIndex += 1;
|
|
9871
9929
|
}
|
|
9872
|
-
|
|
9873
|
-
|
|
9874
|
-
|
|
9930
|
+
rowIndexes.set(segment.segmentId, rowIndex);
|
|
9931
|
+
});
|
|
9932
|
+
for (const segment of segments) {
|
|
9933
|
+
if ((rowIndexes.get(segment.segmentId) ?? 0) !== segment.rowCount) {
|
|
9934
|
+
throw new Error(`Mutation segment key rows differ: ${segment.segmentId}`);
|
|
9935
|
+
}
|
|
9875
9936
|
}
|
|
9876
9937
|
}
|
|
9877
9938
|
async #refinePhysicalOutputWindows(columns, estimatedOutputs, targetBlockBytes, outputCompression, memoryBudgetBytes, snapshot) {
|
|
@@ -9930,12 +9991,12 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
9930
9991
|
const loaded = await this.#loadPhysicalCompactionRanges(column, output, outputCompression, memoryBudgetBytes, snapshot);
|
|
9931
9992
|
return measurePhysicalColumnRanges(column.type, loaded.ranges);
|
|
9932
9993
|
}
|
|
9933
|
-
async #plannedPhysicalOutputStoredBytesUpperBound(plan, snapshot) {
|
|
9994
|
+
async #plannedPhysicalOutputStoredBytesUpperBound(plan, memoryBudgetBytes, snapshot) {
|
|
9934
9995
|
const layout = physicalRewriteLayout(plan);
|
|
9935
9996
|
let total = 0;
|
|
9936
9997
|
for (const output of layout.outputs) {
|
|
9937
9998
|
for (const column of layout.columns) {
|
|
9938
|
-
const measurement = await this.#measurePhysicalCompactionOutput(column, output, plan.outputCompression,
|
|
9999
|
+
const measurement = await this.#measurePhysicalCompactionOutput(column, output, plan.outputCompression, memoryBudgetBytes, snapshot);
|
|
9939
10000
|
total = safeWholeNumberSum([
|
|
9940
10001
|
total,
|
|
9941
10002
|
maximumPhysicalBlockByteLength(measurement.encodedByteLength, measurement.metadata, plan.outputCompression)
|
|
@@ -10296,26 +10357,84 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
10296
10357
|
throw new CompactionMemoryBudgetError(memoryBudgetBytes, memoryBound);
|
|
10297
10358
|
}
|
|
10298
10359
|
const outputEnd = safeWholeNumberSum([output.rowStart, output.rowCount], "Compaction output row range");
|
|
10299
|
-
const
|
|
10300
|
-
|
|
10301
|
-
|
|
10302
|
-
|
|
10303
|
-
|
|
10304
|
-
|
|
10305
|
-
|
|
10306
|
-
|
|
10360
|
+
const sourceBlocks = overlappingPhysicalSourceRanges(column, output);
|
|
10361
|
+
const distinct = [];
|
|
10362
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10363
|
+
for (const sourceBlock of sourceBlocks) {
|
|
10364
|
+
if (seen.has(sourceBlock.blockId))
|
|
10365
|
+
continue;
|
|
10366
|
+
seen.add(sourceBlock.blockId);
|
|
10367
|
+
distinct.push(sourceBlock);
|
|
10368
|
+
}
|
|
10369
|
+
const decoded = /* @__PURE__ */ new Map();
|
|
10370
|
+
const peakWorkingBytes = await this.#decodeCompactionBlocks(distinct, { limitBytes: memoryBudgetBytes, retainsDecoded: true }, snapshot, async (bytes, sourceBlock) => {
|
|
10307
10371
|
const description = inspectBlock(bytes);
|
|
10308
10372
|
if (bytes.byteLength !== sourceBlock.storedBytes || description.encodedLength !== sourceBlock.encodedBytes || description.checksum !== sourceBlock.checksum || description.rowCount !== sourceBlock.sourceBlockRowCount || description.type !== column.type) {
|
|
10309
10373
|
throw new Error(`A compaction source block differs from its plan: ${sourceBlock.blockId}`);
|
|
10310
10374
|
}
|
|
10311
|
-
|
|
10375
|
+
return decodePhysicalBlock(bytes);
|
|
10376
|
+
}, (block, sourceBlock) => {
|
|
10377
|
+
decoded.set(sourceBlock.blockId, block);
|
|
10378
|
+
});
|
|
10379
|
+
const ranges = [];
|
|
10380
|
+
for (const sourceBlock of sourceBlocks) {
|
|
10381
|
+
const block = decoded.get(sourceBlock.blockId);
|
|
10382
|
+
if (block === void 0) {
|
|
10383
|
+
throw new Error(`A compaction source block is missing: ${sourceBlock.blockId}`);
|
|
10384
|
+
}
|
|
10312
10385
|
const sourceEnd = safeWholeNumberSum([sourceBlock.outputRowStart, sourceBlock.rowCount], "Compaction source row range");
|
|
10313
10386
|
const start = sourceBlock.sourceRowStart + Math.max(output.rowStart, sourceBlock.outputRowStart) - sourceBlock.outputRowStart;
|
|
10314
10387
|
const end = sourceBlock.sourceRowStart + Math.min(outputEnd, sourceEnd) - sourceBlock.outputRowStart;
|
|
10315
|
-
const slice = slicePhysicalColumn(
|
|
10388
|
+
const slice = slicePhysicalColumn(block.column, start, end);
|
|
10316
10389
|
ranges.push({ column: slice, start: 0, end: slice.rowCount });
|
|
10317
10390
|
}
|
|
10318
|
-
return { ranges, peakWorkingBytes: memoryBound };
|
|
10391
|
+
return { ranges, peakWorkingBytes: Math.max(memoryBound, peakWorkingBytes) };
|
|
10392
|
+
}
|
|
10393
|
+
async #decodeCompactionBlocks(blocks, budget, snapshot, decode, consume) {
|
|
10394
|
+
let retainedBytes = 0;
|
|
10395
|
+
let peakBytes = 0;
|
|
10396
|
+
let start = 0;
|
|
10397
|
+
while (start < blocks.length) {
|
|
10398
|
+
let end = start;
|
|
10399
|
+
let transientBytes = 0;
|
|
10400
|
+
let storedBytes = 0;
|
|
10401
|
+
for (; end < blocks.length && end - start < MAX_STORAGE_BULK_READ_ITEMS; end += 1) {
|
|
10402
|
+
const block = blocks[end];
|
|
10403
|
+
if (block === void 0)
|
|
10404
|
+
break;
|
|
10405
|
+
const blockTransientBytes = safeWholeNumberSum([
|
|
10406
|
+
safeWholeNumberProduct(block.storedBytes, 2, "Compaction decode memory"),
|
|
10407
|
+
safeWholeNumberProduct(block.encodedBytes, 2, "Compaction decode memory")
|
|
10408
|
+
], "Compaction decode memory");
|
|
10409
|
+
if (end > start && (transientBytes + blockTransientBytes > budget.limitBytes - retainedBytes || storedBytes + block.storedBytes > MAX_BLOCK_READ_BATCH_BYTES)) {
|
|
10410
|
+
break;
|
|
10411
|
+
}
|
|
10412
|
+
transientBytes += blockTransientBytes;
|
|
10413
|
+
storedBytes += block.storedBytes;
|
|
10414
|
+
}
|
|
10415
|
+
const group = blocks.slice(start, end);
|
|
10416
|
+
if (snapshot !== void 0)
|
|
10417
|
+
await this.#renewInternalLeaseIfNeeded(snapshot);
|
|
10418
|
+
const bytes = await this.#readBlockWindow(group.map((block) => block.blockId));
|
|
10419
|
+
const decoded = await Promise.all(group.map((block, index) => {
|
|
10420
|
+
const stored = bytes[index];
|
|
10421
|
+
if (stored === void 0) {
|
|
10422
|
+
throw new Error(`A compaction source block is missing: ${block.blockId}`);
|
|
10423
|
+
}
|
|
10424
|
+
return decode(stored, block);
|
|
10425
|
+
}));
|
|
10426
|
+
peakBytes = Math.max(peakBytes, retainedBytes + transientBytes);
|
|
10427
|
+
for (const [index, block] of group.entries()) {
|
|
10428
|
+
const value = decoded[index];
|
|
10429
|
+
if (value === void 0)
|
|
10430
|
+
throw new Error("Compaction decode lost a block");
|
|
10431
|
+
consume(value, block);
|
|
10432
|
+
if (budget.retainsDecoded)
|
|
10433
|
+
retainedBytes += block.encodedBytes;
|
|
10434
|
+
}
|
|
10435
|
+
start = end;
|
|
10436
|
+
}
|
|
10437
|
+
return peakBytes;
|
|
10319
10438
|
}
|
|
10320
10439
|
async #loadCompactionSources(job) {
|
|
10321
10440
|
const segments = await Promise.all(job.sourceSegmentIds.map((id) => this.store.getSegment(id)));
|
|
@@ -14152,6 +14271,49 @@ function normalizeColumnLogicalValue(column, value) {
|
|
|
14152
14271
|
}
|
|
14153
14272
|
return value;
|
|
14154
14273
|
}
|
|
14274
|
+
function mergeAscendingIndexes(left, right) {
|
|
14275
|
+
const merged = [];
|
|
14276
|
+
let leftIndex = 0;
|
|
14277
|
+
let rightIndex = 0;
|
|
14278
|
+
while (leftIndex < left.length || rightIndex < right.length) {
|
|
14279
|
+
const a = left[leftIndex];
|
|
14280
|
+
const b = right[rightIndex];
|
|
14281
|
+
if (b === void 0 || a !== void 0 && a <= b) {
|
|
14282
|
+
if (a !== void 0)
|
|
14283
|
+
merged.push(a);
|
|
14284
|
+
leftIndex += 1;
|
|
14285
|
+
} else {
|
|
14286
|
+
merged.push(b);
|
|
14287
|
+
rightIndex += 1;
|
|
14288
|
+
}
|
|
14289
|
+
}
|
|
14290
|
+
return merged;
|
|
14291
|
+
}
|
|
14292
|
+
function applyUpsertConflictFilter(batch, rowCount, classified) {
|
|
14293
|
+
const acceptedRowIndexes = mergeAscendingIndexes(classified.inserts, classified.updates);
|
|
14294
|
+
const skippedRowCount = classified.skipped.length;
|
|
14295
|
+
if (acceptedRowIndexes.length === rowCount) {
|
|
14296
|
+
return { batch, rowCount, skippedRowCount, acceptedRowIndexes, firings: classified };
|
|
14297
|
+
}
|
|
14298
|
+
const acceptedPosition = new Map(acceptedRowIndexes.map((original, position) => [original, position]));
|
|
14299
|
+
return {
|
|
14300
|
+
batch: selectBatchRows(batch, acceptedRowIndexes),
|
|
14301
|
+
rowCount: acceptedRowIndexes.length,
|
|
14302
|
+
skippedRowCount,
|
|
14303
|
+
acceptedRowIndexes,
|
|
14304
|
+
firings: {
|
|
14305
|
+
inserts: classified.inserts.map((index) => acceptedPosition.get(index) ?? -1),
|
|
14306
|
+
updates: classified.updates.map((index) => acceptedPosition.get(index) ?? -1),
|
|
14307
|
+
skipped: [],
|
|
14308
|
+
oldImages: acceptedRowIndexes.map((index) => classified.oldImages[index])
|
|
14309
|
+
}
|
|
14310
|
+
};
|
|
14311
|
+
}
|
|
14312
|
+
function remapGeneratedColumns(generated, acceptedRowIndexes) {
|
|
14313
|
+
for (const [name, values] of generated) {
|
|
14314
|
+
generated.set(name, acceptedRowIndexes.map((index) => values[index] ?? null));
|
|
14315
|
+
}
|
|
14316
|
+
}
|
|
14155
14317
|
function selectBatchRows(input, indexes) {
|
|
14156
14318
|
return {
|
|
14157
14319
|
columns: Object.fromEntries(Object.entries(input.columns).map(([name, values]) => [
|
|
@@ -14431,6 +14593,14 @@ function autoCompactionHint(version, segments) {
|
|
|
14431
14593
|
deltas
|
|
14432
14594
|
};
|
|
14433
14595
|
}
|
|
14596
|
+
function countLevelZeroSegments(segments) {
|
|
14597
|
+
let levelZero = 0;
|
|
14598
|
+
for (const segment of segments) {
|
|
14599
|
+
if (segment.level === 0)
|
|
14600
|
+
levelZero += 1;
|
|
14601
|
+
}
|
|
14602
|
+
return levelZero;
|
|
14603
|
+
}
|
|
14434
14604
|
function autoCompactionDueHint(hint) {
|
|
14435
14605
|
return hint.levelZero >= AUTO_COMPACT_SCAN_SEGMENTS || hint.deltas >= AUTO_COMPACT_DELTA_SEGMENTS;
|
|
14436
14606
|
}
|
|
@@ -15881,6 +16051,7 @@ function sourceOrderTuple(segment, transactions, label) {
|
|
|
15881
16051
|
};
|
|
15882
16052
|
}
|
|
15883
16053
|
const MERGE_PLANNER_KEY_BYTES = 96;
|
|
16054
|
+
const MERGE_PLANNER_DECODED_KEY_BLOCK_FACTOR = 4;
|
|
15884
16055
|
function planLinearOutputPartitions(totalRows, partitionRows, firstOrder, nextOrder) {
|
|
15885
16056
|
const count = Math.max(1, Math.ceil(totalRows / partitionRows));
|
|
15886
16057
|
const orders = fractionalLogicalOrders(firstOrder, nextOrder, count);
|
|
@@ -15996,7 +16167,6 @@ function mergePlannerMemoryBound(table, segments, keyColumnId) {
|
|
|
15996
16167
|
const PATCH_ROW_BYTES = 64;
|
|
15997
16168
|
const PATCH_CELL_BYTES = 48;
|
|
15998
16169
|
const RANGE_BYTES = 80;
|
|
15999
|
-
const DECODED_KEY_BLOCK_FACTOR = 4;
|
|
16000
16170
|
let slotRows = 0;
|
|
16001
16171
|
let deltaKeys = 0;
|
|
16002
16172
|
let patchRows = 0;
|
|
@@ -16027,7 +16197,7 @@ function mergePlannerMemoryBound(table, segments, keyColumnId) {
|
|
|
16027
16197
|
safeWholeNumberProduct(columns, PATCH_CELL_BYTES, "Mutation patch cells")
|
|
16028
16198
|
], "Mutation compaction patch row"), "Mutation compaction patches"),
|
|
16029
16199
|
safeWholeNumberProduct(safeWholeNumberSum([sourceBlocks, safeWholeNumberProduct(patchRows, columns, "Mutation patched cells")], "Mutation compaction ranges"), RANGE_BYTES, "Mutation compaction range bytes"),
|
|
16030
|
-
safeWholeNumberProduct(largestKeyBlockBytes,
|
|
16200
|
+
safeWholeNumberProduct(largestKeyBlockBytes, MERGE_PLANNER_DECODED_KEY_BLOCK_FACTOR, "Mutation compaction decoded key block")
|
|
16031
16201
|
], "Mutation compaction planner memory");
|
|
16032
16202
|
}
|
|
16033
16203
|
function mergeSourceBearsRows(kind) {
|
|
@@ -554,10 +554,13 @@ class DatabaseRpcServer {
|
|
|
554
554
|
return handle.session.execute(sql, params);
|
|
555
555
|
}
|
|
556
556
|
if (method === "stage") {
|
|
557
|
-
const [op, tableName, input] = args;
|
|
557
|
+
const [op, tableName, input, options] = args;
|
|
558
558
|
if (!isStageOp(op)) {
|
|
559
559
|
throw new Error(`Unsupported write stage operation: ${String(op)}`);
|
|
560
560
|
}
|
|
561
|
+
if (op === "upsertBatch") {
|
|
562
|
+
return handle.session.upsertBatch(tableName, input, options);
|
|
563
|
+
}
|
|
561
564
|
return handle.session[op](tableName, input);
|
|
562
565
|
}
|
|
563
566
|
if (method === "commit")
|
|
@@ -3919,7 +3919,12 @@ class IndexedDbBlockStore {
|
|
|
3919
3919
|
await assertActiveGarbageCollectionMarker(gcStore, current);
|
|
3920
3920
|
}
|
|
3921
3921
|
const updated = updateGarbageCollectionPlanningRecord(current, input);
|
|
3922
|
-
await assertGarbageCollectionCandidateProvenanceInTransaction(transaction,
|
|
3922
|
+
await assertGarbageCollectionCandidateProvenanceInTransaction(transaction, {
|
|
3923
|
+
candidateManifestVersions: input.candidateManifestVersions ?? [],
|
|
3924
|
+
candidateSegmentIds: input.candidateSegmentIds ?? [],
|
|
3925
|
+
candidateBlockIds: input.candidateBlockIds ?? [],
|
|
3926
|
+
candidateTransactionIds: input.candidateTransactionIds ?? []
|
|
3927
|
+
});
|
|
3923
3928
|
gcStore.put(garbageCollectionJobEnvelope(updated), key);
|
|
3924
3929
|
await transactionDone(transaction);
|
|
3925
3930
|
return structuredClone(updated);
|
|
@@ -9717,15 +9722,15 @@ function assertGenericTransactionUpdateAllowed(record, update) {
|
|
|
9717
9722
|
throw new TypeError("Only commitTransaction can set a committed transaction version");
|
|
9718
9723
|
}
|
|
9719
9724
|
}
|
|
9720
|
-
async function assertGarbageCollectionCandidateProvenanceInTransaction(transaction,
|
|
9725
|
+
async function assertGarbageCollectionCandidateProvenanceInTransaction(transaction, candidates) {
|
|
9721
9726
|
const manifestStore = transaction.objectStore("manifests");
|
|
9722
|
-
for (const version of
|
|
9727
|
+
for (const version of candidates.candidateManifestVersions) {
|
|
9723
9728
|
const value = await requestResult(manifestStore.get(version));
|
|
9724
9729
|
if (value === void 0) {
|
|
9725
9730
|
throw new Error(`Garbage collection candidate manifest is missing: ${String(version)}`);
|
|
9726
9731
|
}
|
|
9727
9732
|
}
|
|
9728
|
-
for (const id of
|
|
9733
|
+
for (const id of candidates.candidateTransactionIds) {
|
|
9729
9734
|
const value = await requestResult(transaction.objectStore("transactions").get(id));
|
|
9730
9735
|
const record = value === void 0 ? void 0 : asTransactionRecord(value);
|
|
9731
9736
|
if (record === void 0 || record.status !== "aborted" && (record.status !== "committed" || record.committedVersion === null)) {
|
|
@@ -9733,10 +9738,10 @@ async function assertGarbageCollectionCandidateProvenanceInTransaction(transacti
|
|
|
9733
9738
|
}
|
|
9734
9739
|
}
|
|
9735
9740
|
const manifestProvenBlockIds = /* @__PURE__ */ new Set();
|
|
9736
|
-
const manifestValues = await Promise.all(
|
|
9741
|
+
const manifestValues = await Promise.all(candidates.candidateBlockIds.map((id) => requestResult(transaction.objectStore("catalog").get(manifestBlockKey(id)))));
|
|
9737
9742
|
for (const [index, value] of manifestValues.entries()) {
|
|
9738
9743
|
if (value !== void 0) {
|
|
9739
|
-
const id =
|
|
9744
|
+
const id = candidates.candidateBlockIds[index] ?? "";
|
|
9740
9745
|
asManifestBlockRecord(value, id);
|
|
9741
9746
|
manifestProvenBlockIds.add(id);
|
|
9742
9747
|
}
|
|
@@ -9757,12 +9762,12 @@ async function assertGarbageCollectionCandidateProvenanceInTransaction(transacti
|
|
|
9757
9762
|
return isTerminalCompactionJob(record) && (record.sourceBlockIds.includes(id) || record.outputBlockIds.includes(id));
|
|
9758
9763
|
});
|
|
9759
9764
|
};
|
|
9760
|
-
for (const id of
|
|
9765
|
+
for (const id of candidates.candidateBlockIds) {
|
|
9761
9766
|
if (await blockHasProvenance(id))
|
|
9762
9767
|
continue;
|
|
9763
9768
|
throw new Error(`Garbage collection block candidate has no persisted provenance: ${id}`);
|
|
9764
9769
|
}
|
|
9765
|
-
for (const id of
|
|
9770
|
+
for (const id of candidates.candidateSegmentIds) {
|
|
9766
9771
|
const segmentValue = await requestResult(transaction.objectStore("segments").get(id));
|
|
9767
9772
|
if (segmentValue !== void 0)
|
|
9768
9773
|
continue;
|
|
@@ -3567,7 +3567,12 @@ class RecordCore {
|
|
|
3567
3567
|
throw new GarbageCollectionJobConflictError(input.jobId, input.expectedRevision, current?.revision ?? null);
|
|
3568
3568
|
}
|
|
3569
3569
|
const updated = updateGarbageCollectionPlanningRecord(current, input);
|
|
3570
|
-
assertGarbageCollectionCandidateProvenance(
|
|
3570
|
+
assertGarbageCollectionCandidateProvenance({
|
|
3571
|
+
candidateManifestVersions: input.candidateManifestVersions ?? [],
|
|
3572
|
+
candidateSegmentIds: input.candidateSegmentIds ?? [],
|
|
3573
|
+
candidateBlockIds: input.candidateBlockIds ?? [],
|
|
3574
|
+
candidateTransactionIds: input.candidateTransactionIds ?? []
|
|
3575
|
+
}, this.#manifests, this.#manifestBlocks, this.#segments, this.#transactions, this.#roots);
|
|
3571
3576
|
this.#garbageCollectionJobs.set(updated.id, updated);
|
|
3572
3577
|
return cloneRecord(updated);
|
|
3573
3578
|
}
|
|
@@ -5661,23 +5666,23 @@ function assertPendingArtifactsAvailable(transaction, physical, segments, valida
|
|
|
5661
5666
|
}
|
|
5662
5667
|
}
|
|
5663
5668
|
}
|
|
5664
|
-
function assertGarbageCollectionCandidateProvenance(
|
|
5665
|
-
for (const version of
|
|
5669
|
+
function assertGarbageCollectionCandidateProvenance(candidates, manifests, manifestBlocks, segments, transactions, roots) {
|
|
5670
|
+
for (const version of candidates.candidateManifestVersions) {
|
|
5666
5671
|
if (!manifests.has(version)) {
|
|
5667
5672
|
throw new Error(`Garbage collection candidate manifest is missing: ${String(version)}`);
|
|
5668
5673
|
}
|
|
5669
5674
|
}
|
|
5670
|
-
for (const id of
|
|
5675
|
+
for (const id of candidates.candidateTransactionIds) {
|
|
5671
5676
|
const transaction = transactions.get(id);
|
|
5672
5677
|
if (transaction === void 0 || transaction.status !== "aborted" && (transaction.status !== "committed" || transaction.committedVersion === null)) {
|
|
5673
5678
|
throw new Error(`Garbage collection transaction candidate is not terminal: ${id}`);
|
|
5674
5679
|
}
|
|
5675
5680
|
}
|
|
5676
|
-
const unprovenBlockId =
|
|
5681
|
+
const unprovenBlockId = candidates.candidateBlockIds.find((id) => !manifestBlocks.has(id) && roots.abortedTransactionBlockCount(id) === 0 && roots.terminalJobBlockCount(id) === 0);
|
|
5677
5682
|
if (unprovenBlockId !== void 0) {
|
|
5678
5683
|
throw new Error(`Garbage collection block candidate has no persisted provenance: ${unprovenBlockId}`);
|
|
5679
5684
|
}
|
|
5680
|
-
const unprovenSegmentId =
|
|
5685
|
+
const unprovenSegmentId = candidates.candidateSegmentIds.find((id) => {
|
|
5681
5686
|
return !segments.has(id);
|
|
5682
5687
|
});
|
|
5683
5688
|
if (unprovenSegmentId !== void 0) {
|
package/dist/storage/types.d.ts
CHANGED
|
@@ -815,6 +815,17 @@ export interface UpdateGarbageCollectionPlanningInput {
|
|
|
815
815
|
discovery: GarbageCollectionDiscovery;
|
|
816
816
|
updatedAt: string;
|
|
817
817
|
}
|
|
818
|
+
/**
|
|
819
|
+
* One page's (or one job's full) proposed garbage-collection candidates, checked for persisted
|
|
820
|
+
* provenance before being accepted. Shared by every adapter's provenance assertion so a candidate
|
|
821
|
+
* kind added to one cannot be forgotten in another.
|
|
822
|
+
*/
|
|
823
|
+
export interface GarbageCollectionCandidateSet {
|
|
824
|
+
readonly candidateManifestVersions: readonly number[];
|
|
825
|
+
readonly candidateSegmentIds: readonly string[];
|
|
826
|
+
readonly candidateBlockIds: readonly string[];
|
|
827
|
+
readonly candidateTransactionIds: readonly string[];
|
|
828
|
+
}
|
|
818
829
|
export interface GarbageCollectionJobRecord {
|
|
819
830
|
id: string;
|
|
820
831
|
candidateManifestVersions: number[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@minnowdb/core",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.7",
|
|
4
4
|
"description": "A columnar SQL database for the browser: PostgreSQL-style SQL over durable IndexedDB or OPFS data, with no server or WebAssembly module.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Eric Wilhite",
|