@minnowdb/core 0.7.5 → 0.7.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import { crossJoinPlan } from "../plan/model.js";
2
- import { toColumnarBatch } from "./batch.js";
2
+ import { definedVectors, toColumnarBatch } from "./batch.js";
3
3
  import { ArtifactCache } from "./artifact-cache.js";
4
4
  import { estimateBatchBytes, estimateRowBytes, estimateValuesBytes } from "./byte-estimates.js";
5
5
  import { throwIfAborted } from "./cancellation.js";
@@ -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;
@@ -181,6 +182,9 @@ function migrationResult(steps) {
181
182
  steps: [...steps]
182
183
  };
183
184
  }
185
+ function compactUpdateBatchInput(input) {
186
+ return Object.values(input.changes).includes(void 0) ? { keys: input.keys, changes: definedVectors(input.changes) } : input;
187
+ }
184
188
  const MAX_GARBAGE_COLLECTION_RETAIN_RECENT_VERSIONS = 1024;
185
189
  const REFERENTIAL_CASCADES = 8;
186
190
  class TransactionRollback extends Error {
@@ -547,6 +551,10 @@ async function* singleSnapshotChunk(bytes) {
547
551
  }
548
552
  class MinnowDatabase {
549
553
  store;
554
+ #schema;
555
+ get #erased() {
556
+ return this;
557
+ }
550
558
  #closed = false;
551
559
  #closePromise;
552
560
  #transactions;
@@ -624,6 +632,7 @@ class MinnowDatabase {
624
632
  #preparedCatalogStates = /* @__PURE__ */ new WeakMap();
625
633
  constructor(store, options = {}) {
626
634
  this.store = store;
635
+ this.#schema = options.schema;
627
636
  this.#compression = options.compression ?? "gzip";
628
637
  this.#rowsPerBlock = options.rowsPerBlock ?? 65536;
629
638
  if (!Number.isSafeInteger(this.#rowsPerBlock) || this.#rowsPerBlock <= 0 || this.#rowsPerBlock > MAX_BLOCK_ROW_COUNT) {
@@ -1588,7 +1597,7 @@ class MinnowDatabase {
1588
1597
  return { ...statement, rows };
1589
1598
  }
1590
1599
  async insert(tableName, row) {
1591
- return this.insertBatch(tableName, [row]);
1600
+ return this.#erased.insertBatch(tableName, [row]);
1592
1601
  }
1593
1602
  upsertBatch(tableName, input, options = {}) {
1594
1603
  return this.#withWriteReservation(() => this.#upsertBatchReserved(tableName, input, options));
@@ -1612,9 +1621,7 @@ class MinnowDatabase {
1612
1621
  const { result, batch, generated, autoIncrement } = completed;
1613
1622
  collectAutoIncrementGenerated(batch, generated, autoIncrement);
1614
1623
  if (result.acceptedRowIndexes.length !== result.requestedRowCount) {
1615
- for (const [name, values] of generated) {
1616
- generated.set(name, result.acceptedRowIndexes.map((index) => values[index] ?? null));
1617
- }
1624
+ remapGeneratedColumns(generated, result.acceptedRowIndexes);
1618
1625
  }
1619
1626
  return {
1620
1627
  tableName: result.tableName,
@@ -1632,10 +1639,10 @@ class MinnowDatabase {
1632
1639
  };
1633
1640
  }
1634
1641
  async upsert(tableName, row, options = {}) {
1635
- return this.upsertBatch(tableName, [row], options);
1642
+ return this.#erased.upsertBatch(tableName, [row], options);
1636
1643
  }
1637
1644
  updateBatch(tableName, input) {
1638
- return this.#withWriteReservation(() => this.#updateBatchReserved(tableName, input));
1645
+ return this.#withWriteReservation(() => this.#updateBatchReserved(tableName, compactUpdateBatchInput(input)));
1639
1646
  }
1640
1647
  async #updateBatchReserved(tableName, input) {
1641
1648
  return this.#runWrite(async () => {
@@ -1658,13 +1665,13 @@ class MinnowDatabase {
1658
1665
  });
1659
1666
  }
1660
1667
  async update(tableName, key, changes) {
1661
- return this.updateBatch(tableName, {
1668
+ return this.#erased.updateBatch(tableName, {
1662
1669
  keys: [key],
1663
- changes: Object.fromEntries(Object.entries(changes).map(([name, value]) => [name, [value]]))
1670
+ changes: Object.fromEntries(Object.entries(changes).flatMap(([name, value]) => value === void 0 ? [] : [[name, [value]]]))
1664
1671
  });
1665
1672
  }
1666
1673
  async delete(tableName, key) {
1667
- return this.deleteBatch(tableName, { keys: [key] });
1674
+ return this.#erased.deleteBatch(tableName, { keys: [key] });
1668
1675
  }
1669
1676
  deleteBatch(tableName, input) {
1670
1677
  return this.#withWriteReservation(() => this.#deleteBatchReserved(tableName, input));
@@ -1679,7 +1686,7 @@ class MinnowDatabase {
1679
1686
  if (dependents.length === 0)
1680
1687
  return this.#deleteBatchOnce(tableName, normalizedInput);
1681
1688
  const started = performance.now();
1682
- const { result, version } = await this.write(async (session) => {
1689
+ const { result, version } = await this.#erased.write(async (session) => {
1683
1690
  await this.#applyReferentialActions(table, [...normalizedInput.keys], session, REFERENTIAL_CASCADES);
1684
1691
  return session.deleteBatch(tableName, normalizedInput);
1685
1692
  });
@@ -1883,7 +1890,7 @@ class MinnowDatabase {
1883
1890
  }
1884
1891
  }
1885
1892
  bufferedWriter(tableName, options = {}) {
1886
- return new BufferedTableWriter(this, tableName, options);
1893
+ return new BufferedTableWriter(this.#erased, tableName, options);
1887
1894
  }
1888
1895
  async #writeUpdateBatch(table, keyColumn, input, keys) {
1889
1896
  await this.#assertCompactionCapacity(table);
@@ -2092,30 +2099,21 @@ class MinnowDatabase {
2092
2099
  const normalizedConflictWhere = kind === "upsert" && conflictWhere !== void 0 ? normalizeUpsertConflictWhere(table, conflictWhere) : void 0;
2093
2100
  const upsertKeyColumn = kind === "upsert" ? getUniqueKeyColumn(table) : void 0;
2094
2101
  if (upsertKeyColumn !== void 0) {
2095
- upsertFirings = await this.#upsertTriggerFirings(table, upsertKeyColumn, input, rowCount, (sql, params) => this.#sessionQuery(transaction, sql, { params }), normalizedConflictWhere, normalizedConflictWhere !== void 0);
2102
+ upsertFirings = await this.#upsertTriggerFirings(table, upsertKeyColumn, input, rowCount, (sql, params) => this.#sessionQuery(transaction, sql, { params }), normalizedConflictWhere);
2096
2103
  }
2097
2104
  if (normalizedConflictWhere !== void 0) {
2098
2105
  if (upsertFirings === void 0) {
2099
2106
  throw new Error("Upsert conflict classification is missing");
2100
2107
  }
2101
- const classifiedFirings = upsertFirings;
2102
- acceptedRowIndexes = [...classifiedFirings.inserts, ...classifiedFirings.updates].sort((left, right) => left - right);
2103
- skippedRowCount = classifiedFirings.skipped.length;
2104
- input = selectBatchRows(input, acceptedRowIndexes);
2105
- rowCount = acceptedRowIndexes.length;
2108
+ counts = { inserted: upsertFirings.inserts.length, updated: upsertFirings.updates.length };
2109
+ const filtered = applyUpsertConflictFilter(input, rowCount, upsertFirings);
2110
+ acceptedRowIndexes = filtered.acceptedRowIndexes;
2111
+ skippedRowCount = filtered.skippedRowCount;
2112
+ input = filtered.batch;
2113
+ rowCount = filtered.rowCount;
2114
+ upsertFirings = filtered.firings;
2106
2115
  logicalBytes = estimateBatchBytes(input);
2107
2116
  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
2117
  if (rowCount === 0) {
2120
2118
  await transaction.abort();
2121
2119
  const version = transaction.snapshotVersion;
@@ -3973,23 +3971,38 @@ class MinnowDatabase {
3973
3971
  return;
3974
3972
  const hintVersion = transaction?.snapshotVersion ?? await this.store.getCurrentManifestVersion();
3975
3973
  const hint = this.#autoCompactionHints.get(table.id);
3976
- if (hint?.version === hintVersion && hint.levelZero < MAX_LEVEL_ZERO_SEGMENTS)
3977
- return;
3978
- let segments = await this.#currentVisibleSegments(table);
3979
- let levelZero = 0;
3980
- for (const segment of segments) {
3981
- if (segment.level === 0)
3982
- levelZero += 1;
3974
+ let visible;
3975
+ let levelZero;
3976
+ if (hint?.version === hintVersion) {
3977
+ ({ visible, levelZero } = hint);
3978
+ } else {
3979
+ const segments = await this.#currentVisibleSegments(table);
3980
+ visible = segments.length;
3981
+ levelZero = countLevelZeroSegments(segments);
3983
3982
  }
3984
- if (levelZero < MAX_LEVEL_ZERO_SEGMENTS)
3983
+ if (levelZero < AUTO_COMPACT_BACKPRESSURE_LEVEL_ZERO_SEGMENTS)
3984
+ return;
3985
+ const atCeiling = levelZero >= MAX_LEVEL_ZERO_SEGMENTS;
3986
+ if (!atCeiling && this.#autoCompactionBackoff.get(table.id)?.retryTimer !== void 0)
3985
3987
  return;
3986
3988
  await this.#publishPendingCompactions();
3987
- await this.compactTableStep(table.name, {
3988
- maxBlocks: AUTO_COMPACT_STEP_BLOCKS,
3989
- maxLevel0Segments: AUTO_COMPACT_MAX_LEVEL_ZERO_SEGMENTS
3990
- });
3991
- segments = await this.#currentVisibleSegments(table);
3992
- levelZero = segments.reduce((count, segment) => count + (segment.level === 0 ? 1 : 0), 0);
3989
+ try {
3990
+ const progress = await this.compactTableStep(table.name, {
3991
+ maxBlocks: AUTO_COMPACT_STEP_BLOCKS,
3992
+ maxLevel0Segments: AUTO_COMPACT_MAX_LEVEL_ZERO_SEGMENTS
3993
+ });
3994
+ if (!atCeiling && progress.result !== null && !progress.result.compacted) {
3995
+ this.#backOffAutoCompaction(table.id, visible);
3996
+ }
3997
+ } catch (error) {
3998
+ if (atCeiling)
3999
+ throw error;
4000
+ this.#backOffAutoCompaction(table.id, visible);
4001
+ return;
4002
+ }
4003
+ if (!atCeiling)
4004
+ return;
4005
+ levelZero = countLevelZeroSegments(await this.#currentVisibleSegments(table));
3993
4006
  if (levelZero >= MAX_LEVEL_ZERO_SEGMENTS) {
3994
4007
  throw new CompactionBacklogError(table.name, levelZero, MAX_LEVEL_ZERO_SEGMENTS);
3995
4008
  }
@@ -4299,9 +4312,9 @@ class MinnowDatabase {
4299
4312
  }
4300
4313
  }
4301
4314
  }
4302
- async #upsertTriggerFirings(table, keyColumn, batch, rowCount, readRows, conflictWhere, force = false) {
4315
+ async #upsertTriggerFirings(table, keyColumn, batch, rowCount, readRows, conflictWhere) {
4303
4316
  const fires = (table.triggers ?? []).some((trigger) => trigger.event === "insert" || trigger.event === "update");
4304
- if (!fires && !force || rowCount === 0)
4317
+ if (!fires && conflictWhere === void 0 || rowCount === 0)
4305
4318
  return void 0;
4306
4319
  const quote = (name) => `"${name.replaceAll('"', '""')}"`;
4307
4320
  const keyValues = batch.columns[keyColumn.name] ?? [];
@@ -4415,8 +4428,15 @@ class MinnowDatabase {
4415
4428
  if (target.uniqueKeyColumnId !== void 0) {
4416
4429
  throw new TypeError(`Trigger bodies insert into keyless tables only: ${target.name}`);
4417
4430
  }
4431
+ let columns = compiled.columns;
4432
+ if (columns.length === 0 && compiled.defaultValues !== true) {
4433
+ columns = visibleTableColumns(target).map((column) => column.name);
4434
+ if (derivedRows.some((row) => row.length !== columns.length)) {
4435
+ throw new TypeError(`Trigger body INSERT must match the ${String(columns.length)} visible columns of ${target.name}`);
4436
+ }
4437
+ }
4418
4438
  const statementNow = this.#now();
4419
- const materialized = await this.#materializeInsertExpressions({ ...compiled, rows: derivedRows }, statementNow);
4439
+ const materialized = await this.#materializeInsertExpressions({ ...compiled, columns, rows: derivedRows }, statementNow);
4420
4440
  const input = insertStatementBatch(materialized);
4421
4441
  coerceSqlWriteBatch(target, input);
4422
4442
  const filled = await fillColumnDefaults(target, input, (sql) => this.#evaluateDefaultExpression(sql, statementNow), derivedRows.length);
@@ -4629,10 +4649,10 @@ class MinnowDatabase {
4629
4649
  staged += 1;
4630
4650
  return guarded(() => this.#sessionInsert(transaction, tableName, input, "insert"));
4631
4651
  },
4632
- upsertBatch: async (tableName, input) => {
4652
+ upsertBatch: async (tableName, input, options2) => {
4633
4653
  open();
4634
4654
  staged += 1;
4635
- return guarded(() => this.#sessionInsert(transaction, tableName, input, "upsert"));
4655
+ return guarded(() => this.#sessionInsert(transaction, tableName, input, "upsert", options2));
4636
4656
  },
4637
4657
  updateBatch: async (tableName, input) => {
4638
4658
  open();
@@ -4866,10 +4886,17 @@ class MinnowDatabase {
4866
4886
  }
4867
4887
  return { added, removed };
4868
4888
  }
4869
- async #sessionInsert(transaction, tableName, input, kind, cascadeBudget = 1) {
4889
+ async #sessionInsert(transaction, tableName, input, kind, options, cascadeBudget = 1) {
4870
4890
  const table = await this.#findTable(tableName);
4871
- await this.#assertCompactionCapacity(table, transaction);
4872
- const { batch, generated, autoIncrement, rowCount } = await this.#fillDefaults(table, input);
4891
+ const sessionUpsertKeyColumn = kind === "upsert" ? getUniqueKeyColumn(table) : void 0;
4892
+ const normalizedConflictWhere = kind === "upsert" && options?.conflictWhere !== void 0 ? normalizeUpsertConflictWhere(table, options.conflictWhere) : void 0;
4893
+ if (normalizedConflictWhere !== void 0 && sessionUpsertKeyColumn === void 0) {
4894
+ throw new TypeError(`Table needs a unique key before it can be upserted: ${table.name}`);
4895
+ }
4896
+ const filled = await this.#fillDefaults(table, input);
4897
+ const { generated, autoIncrement } = filled;
4898
+ let batch = filled.batch;
4899
+ let rowCount = filled.rowCount;
4873
4900
  if (autoIncrement !== void 0 && autoIncrement.missingIndexes.length > 0) {
4874
4901
  const values = await this.store.reserveAutoIncrement(table.id, autoIncrement.column.id, autoIncrement.missingIndexes.length, autoIncrement.atLeast);
4875
4902
  patchAutoIncrementValues(batch, autoIncrement, values);
@@ -4878,6 +4905,32 @@ class MinnowDatabase {
4878
4905
  validateValue(autoIncrement.column, patched[rowIndex] ?? null, rowIndex);
4879
4906
  }
4880
4907
  }
4908
+ let sessionUpsertFirings = sessionUpsertKeyColumn === void 0 ? void 0 : await this.#upsertTriggerFirings(table, sessionUpsertKeyColumn, batch, rowCount, (sql, params) => this.#sessionQuery(transaction, sql, { params }), normalizedConflictWhere);
4909
+ let skippedRowCount = 0;
4910
+ if (normalizedConflictWhere !== void 0) {
4911
+ if (sessionUpsertFirings === void 0) {
4912
+ throw new Error("Upsert conflict classification is missing");
4913
+ }
4914
+ const filtered = applyUpsertConflictFilter(batch, rowCount, sessionUpsertFirings);
4915
+ skippedRowCount = filtered.skippedRowCount;
4916
+ if (filtered.rowCount !== rowCount) {
4917
+ remapGeneratedColumns(generated, filtered.acceptedRowIndexes);
4918
+ }
4919
+ batch = filtered.batch;
4920
+ rowCount = filtered.rowCount;
4921
+ sessionUpsertFirings = filtered.firings;
4922
+ if (rowCount === 0) {
4923
+ collectAutoIncrementGenerated(batch, generated, autoIncrement);
4924
+ return {
4925
+ tableName: table.name,
4926
+ segmentId: null,
4927
+ rowCount: 0,
4928
+ skippedRowCount,
4929
+ ...generated.size === 0 ? {} : { generatedColumns: Object.fromEntries(generated) }
4930
+ };
4931
+ }
4932
+ }
4933
+ await this.#assertCompactionCapacity(table, transaction);
4881
4934
  const keys = batchKeys(table, batch);
4882
4935
  if (keys !== void 0) {
4883
4936
  transaction.setUniqueKeyChanges({
@@ -4889,8 +4942,6 @@ class MinnowDatabase {
4889
4942
  await this.#assertForeignKeysPresent(table, (column) => batch.columns[column] ?? [], (sql, params) => this.#sessionQuery(transaction, sql, { params }), transaction);
4890
4943
  const rowIds = await this.store.reserveRowIds(table.id, rowCount);
4891
4944
  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
4945
  stageSecondaryUniqueInsertChanges(transaction, table, batch, kind === "upsert" ? sessionUpsertFirings?.oldImages : void 0);
4895
4946
  if (kind === "insert") {
4896
4947
  await this.#stageTriggerDerivedInserts(transaction, table, "insert", rowCount, insertValueAt, "before", cascadeBudget);
@@ -4908,6 +4959,7 @@ class MinnowDatabase {
4908
4959
  tableName: table.name,
4909
4960
  segmentId,
4910
4961
  rowCount,
4962
+ skippedRowCount,
4911
4963
  ...generated.size === 0 ? {} : { generatedColumns: Object.fromEntries(generated) }
4912
4964
  };
4913
4965
  }
@@ -5135,7 +5187,10 @@ class MinnowDatabase {
5135
5187
  }
5136
5188
  return (await this.#memoizedQuery(query.plan, `typed ${planMemoKey(query.plan)}`, {}, probe)).rows;
5137
5189
  }
5138
- async migrate(definition, options = {}) {
5190
+ async migrate(definition = this.#schema, options = {}) {
5191
+ if (definition === void 0) {
5192
+ throw new TypeError("migrate() needs a schema: pass a definition, or construct the database with { schema }");
5193
+ }
5139
5194
  let originalSteps;
5140
5195
  for (let attempt = 0; ; attempt += 1) {
5141
5196
  try {
@@ -6036,7 +6091,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
6036
6091
  let outcome;
6037
6092
  let version;
6038
6093
  if (options.writer === void 0) {
6039
- const completed = await this.write(apply);
6094
+ const completed = await this.#erased.write(apply);
6040
6095
  outcome = completed.result;
6041
6096
  version = completed.version;
6042
6097
  } else {
@@ -6141,7 +6196,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
6141
6196
  }
6142
6197
  const sourceKey = mergeSourceKeyExpression(statement, keyColumn.name);
6143
6198
  const sourceSql = mergeSourceSql(statement);
6144
- const { result: applied, version } = await this.write(async (session) => {
6199
+ const { result: applied, version } = await this.#erased.write(async (session) => {
6145
6200
  const sourceRows = (await session.query(sourceSql)).rows;
6146
6201
  const keys = sourceRows.map((row) => storedSqlValueFromExecution(keyColumn, evaluateJoinedRowExpression(sourceKey, { [statement.source.alias]: row })));
6147
6202
  const present = /* @__PURE__ */ new Map();
@@ -6722,7 +6777,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
6722
6777
  }
6723
6778
  await this.#assertStagedInsertKeysFree(table2, stagedKeyColumn, input, writer);
6724
6779
  }
6725
- const result = await (viaUpsert ? writer?.upsertBatch(statement.table, input) ?? this.upsertBatch(statement.table, input) : writer?.insertBatch(statement.table, input) ?? this.insertBatch(statement.table, input));
6780
+ 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));
6726
6781
  const generated = "generatedColumns" in result ? result.generatedColumns ?? {} : {};
6727
6782
  return {
6728
6783
  kind: "insert",
@@ -6854,7 +6909,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
6854
6909
  ...returnedRows2 === void 0 || returningColumns === void 0 ? {} : returningExecuteFields(table, returningColumns, returnedRows2)
6855
6910
  };
6856
6911
  }
6857
- const deleted = options.writer === void 0 ? await this.deleteBatch(table.name, { keys }) : await options.writer.deleteBatch(table.name, { keys });
6912
+ const deleted = options.writer === void 0 ? await this.#erased.deleteBatch(table.name, { keys }) : await options.writer.deleteBatch(table.name, { keys });
6858
6913
  return {
6859
6914
  kind: "delete",
6860
6915
  table: table.name,
@@ -6891,7 +6946,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
6891
6946
  returnedChanges[assignment.column] = executionValues;
6892
6947
  }
6893
6948
  }
6894
- const updated = options.writer === void 0 ? await this.updateBatch(table.name, { keys, changes }) : await options.writer.updateBatch(table.name, { keys, changes });
6949
+ const updated = options.writer === void 0 ? await this.#erased.updateBatch(table.name, { keys, changes }) : await options.writer.updateBatch(table.name, { keys, changes });
6895
6950
  const returnedRows = returningColumns === void 0 ? void 0 : rows.map((row, index) => Object.fromEntries(returningColumns.map((name) => [
6896
6951
  name,
6897
6952
  returnedChanges !== void 0 && name in returnedChanges ? returnedChanges[name]?.[index] ?? null : updated.generatedColumns?.[name] !== void 0 ? updated.generatedColumns[name][index] ?? null : row[name] ?? null
@@ -9275,7 +9330,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
9275
9330
  }
9276
9331
  const priorAttemptOutputStoredBytes = await this.#priorCompactionAttemptOutputStoredBytes(table.id, baseJobId);
9277
9332
  const maximumOutputStoredBytes = Math.max(0, floorWholeNumberProduct(selection.level0SourceStoredBytes, maxWriteAmplification, "Compaction maximum output stored bytes") - priorAttemptOutputStoredBytes);
9278
- const plannedOutputStoredBytesUpperBound = await this.#plannedPhysicalOutputStoredBytesUpperBound(rewritePlan, snapshot);
9333
+ const plannedOutputStoredBytesUpperBound = await this.#plannedPhysicalOutputStoredBytesUpperBound(rewritePlan, memoryBudgetBytes, snapshot);
9279
9334
  levelTwoBudget = {
9280
9335
  outputPartitionOrdinal,
9281
9336
  maxWriteAmplification,
@@ -9731,17 +9786,15 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
9731
9786
  const columnIndexById = new Map(table.columns.map((column, index) => [column.id, index]));
9732
9787
  const touched = /* @__PURE__ */ new Set();
9733
9788
  const deltaKeys = /* @__PURE__ */ new Map();
9734
- for (const segment of segments) {
9735
- if (!mergeSourceReferencesKeys(segment.kind))
9736
- continue;
9737
- const keys = [];
9738
- await this.#forEachMergeSourceKey(segment, keyColumn, snapshot, (value) => {
9739
- const key = overlayKeyOf(keyColumn.type, value);
9740
- keys.push(key);
9741
- touched.add(key);
9742
- });
9743
- deltaKeys.set(segment.segmentId, keys);
9744
- }
9789
+ const deltas = segments.filter((segment) => mergeSourceReferencesKeys(segment.kind));
9790
+ for (const segment of deltas)
9791
+ deltaKeys.set(segment.segmentId, []);
9792
+ const unusedBudgetBytes = memoryBudgetBytes - plannerMemoryBytes;
9793
+ await this.#forEachMergeSourceKey(deltas, keyColumn, unusedBudgetBytes, snapshot, (segment, value) => {
9794
+ const key = overlayKeyOf(keyColumn.type, value);
9795
+ deltaKeys.get(segment.segmentId)?.push(key);
9796
+ touched.add(key);
9797
+ });
9745
9798
  let slotCount = 0;
9746
9799
  for (const segment of segments) {
9747
9800
  if (mergeSourceBearsRows(segment.kind))
@@ -9813,7 +9866,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
9813
9866
  if (keys !== void 0) {
9814
9867
  keys.forEach(visit);
9815
9868
  } else if (touched.size > 0) {
9816
- await this.#forEachMergeSourceKey(segment, keyColumn, snapshot, (value, rowIndex) => {
9869
+ await this.#forEachMergeSourceKey([segment], keyColumn, unusedBudgetBytes, snapshot, (_, value, rowIndex) => {
9817
9870
  visit(overlayKeyOf(keyColumn.type, value), rowIndex);
9818
9871
  });
9819
9872
  }
@@ -9850,28 +9903,48 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
9850
9903
  }
9851
9904
  return { ...output.finish(), sourceOutputRowStarts };
9852
9905
  }
9853
- async #forEachMergeSourceKey(segment, column, snapshot, action) {
9854
- const planned = segment.columns.find((candidate) => candidate.columnId === column.id);
9855
- if (planned === void 0) {
9856
- throw new Error(`Mutation segment has no key column: ${segment.segmentId}`);
9906
+ async #forEachMergeSourceKey(segments, column, unusedBudgetBytes, snapshot, action) {
9907
+ const blocks = [];
9908
+ let largestEncodedBytes = 0;
9909
+ for (const segment of segments) {
9910
+ const planned = segment.columns.find((candidate) => candidate.columnId === column.id);
9911
+ if (planned === void 0) {
9912
+ throw new Error(`Mutation segment has no key column: ${segment.segmentId}`);
9913
+ }
9914
+ for (const source of planned.sourceBlocks) {
9915
+ blocks.push({
9916
+ blockId: source.blockId,
9917
+ storedBytes: source.storedBytes,
9918
+ encodedBytes: source.encodedBytes,
9919
+ segment,
9920
+ source
9921
+ });
9922
+ largestEncodedBytes = Math.max(largestEncodedBytes, source.encodedBytes);
9923
+ }
9857
9924
  }
9858
- let rowIndex = 0;
9859
- for (const source of planned.sourceBlocks) {
9860
- await this.#renewInternalLeaseIfNeeded(snapshot);
9861
- const bytes = await this.store.getBlock(source.blockId);
9862
- if (bytes === void 0)
9863
- throw new Error(`Compaction source block is missing: ${source.blockId}`);
9925
+ const limitBytes = safeWholeNumberSum([
9926
+ Math.max(0, unusedBudgetBytes),
9927
+ safeWholeNumberProduct(largestEncodedBytes, MERGE_PLANNER_DECODED_KEY_BLOCK_FACTOR, "Mutation compaction key decode memory")
9928
+ ], "Mutation compaction key decode memory");
9929
+ const rowIndexes = /* @__PURE__ */ new Map();
9930
+ await this.#decodeCompactionBlocks(blocks, { limitBytes, retainsDecoded: false }, snapshot, async (bytes, { source }) => {
9864
9931
  const description = inspectBlock(bytes);
9865
9932
  if (bytes.byteLength !== source.storedBytes || description.encodedLength !== source.encodedBytes || description.checksum !== source.checksum || description.rowCount !== source.rowCount || description.type !== column.type) {
9866
9933
  throw new Error(`Compaction source block differs from its plan: ${source.blockId}`);
9867
9934
  }
9868
- for (const value of (await decodeBlock(bytes)).column.values) {
9869
- action(value, rowIndex);
9935
+ return (await decodeBlock(bytes)).column.values;
9936
+ }, (values, { segment }) => {
9937
+ let rowIndex = rowIndexes.get(segment.segmentId) ?? 0;
9938
+ for (const value of values) {
9939
+ action(segment, value, rowIndex);
9870
9940
  rowIndex += 1;
9871
9941
  }
9872
- }
9873
- if (rowIndex !== segment.rowCount) {
9874
- throw new Error(`Mutation segment key rows differ: ${segment.segmentId}`);
9942
+ rowIndexes.set(segment.segmentId, rowIndex);
9943
+ });
9944
+ for (const segment of segments) {
9945
+ if ((rowIndexes.get(segment.segmentId) ?? 0) !== segment.rowCount) {
9946
+ throw new Error(`Mutation segment key rows differ: ${segment.segmentId}`);
9947
+ }
9875
9948
  }
9876
9949
  }
9877
9950
  async #refinePhysicalOutputWindows(columns, estimatedOutputs, targetBlockBytes, outputCompression, memoryBudgetBytes, snapshot) {
@@ -9930,12 +10003,12 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
9930
10003
  const loaded = await this.#loadPhysicalCompactionRanges(column, output, outputCompression, memoryBudgetBytes, snapshot);
9931
10004
  return measurePhysicalColumnRanges(column.type, loaded.ranges);
9932
10005
  }
9933
- async #plannedPhysicalOutputStoredBytesUpperBound(plan, snapshot) {
10006
+ async #plannedPhysicalOutputStoredBytesUpperBound(plan, memoryBudgetBytes, snapshot) {
9934
10007
  const layout = physicalRewriteLayout(plan);
9935
10008
  let total = 0;
9936
10009
  for (const output of layout.outputs) {
9937
10010
  for (const column of layout.columns) {
9938
- const measurement = await this.#measurePhysicalCompactionOutput(column, output, plan.outputCompression, Number.MAX_SAFE_INTEGER, snapshot);
10011
+ const measurement = await this.#measurePhysicalCompactionOutput(column, output, plan.outputCompression, memoryBudgetBytes, snapshot);
9939
10012
  total = safeWholeNumberSum([
9940
10013
  total,
9941
10014
  maximumPhysicalBlockByteLength(measurement.encodedByteLength, measurement.metadata, plan.outputCompression)
@@ -10296,26 +10369,84 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
10296
10369
  throw new CompactionMemoryBudgetError(memoryBudgetBytes, memoryBound);
10297
10370
  }
10298
10371
  const outputEnd = safeWholeNumberSum([output.rowStart, output.rowCount], "Compaction output row range");
10299
- const ranges = [];
10300
- for (const sourceBlock of overlappingPhysicalSourceRanges(column, output)) {
10301
- if (snapshot !== void 0)
10302
- await this.#renewInternalLeaseIfNeeded(snapshot);
10303
- const bytes = await this.store.getBlock(sourceBlock.blockId);
10304
- if (bytes === void 0) {
10305
- throw new Error(`A compaction source block is missing: ${sourceBlock.blockId}`);
10306
- }
10372
+ const sourceBlocks = overlappingPhysicalSourceRanges(column, output);
10373
+ const distinct = [];
10374
+ const seen = /* @__PURE__ */ new Set();
10375
+ for (const sourceBlock of sourceBlocks) {
10376
+ if (seen.has(sourceBlock.blockId))
10377
+ continue;
10378
+ seen.add(sourceBlock.blockId);
10379
+ distinct.push(sourceBlock);
10380
+ }
10381
+ const decoded = /* @__PURE__ */ new Map();
10382
+ const peakWorkingBytes = await this.#decodeCompactionBlocks(distinct, { limitBytes: memoryBudgetBytes, retainsDecoded: true }, snapshot, async (bytes, sourceBlock) => {
10307
10383
  const description = inspectBlock(bytes);
10308
10384
  if (bytes.byteLength !== sourceBlock.storedBytes || description.encodedLength !== sourceBlock.encodedBytes || description.checksum !== sourceBlock.checksum || description.rowCount !== sourceBlock.sourceBlockRowCount || description.type !== column.type) {
10309
10385
  throw new Error(`A compaction source block differs from its plan: ${sourceBlock.blockId}`);
10310
10386
  }
10311
- const decoded = await decodePhysicalBlock(bytes);
10387
+ return decodePhysicalBlock(bytes);
10388
+ }, (block, sourceBlock) => {
10389
+ decoded.set(sourceBlock.blockId, block);
10390
+ });
10391
+ const ranges = [];
10392
+ for (const sourceBlock of sourceBlocks) {
10393
+ const block = decoded.get(sourceBlock.blockId);
10394
+ if (block === void 0) {
10395
+ throw new Error(`A compaction source block is missing: ${sourceBlock.blockId}`);
10396
+ }
10312
10397
  const sourceEnd = safeWholeNumberSum([sourceBlock.outputRowStart, sourceBlock.rowCount], "Compaction source row range");
10313
10398
  const start = sourceBlock.sourceRowStart + Math.max(output.rowStart, sourceBlock.outputRowStart) - sourceBlock.outputRowStart;
10314
10399
  const end = sourceBlock.sourceRowStart + Math.min(outputEnd, sourceEnd) - sourceBlock.outputRowStart;
10315
- const slice = slicePhysicalColumn(decoded.column, start, end);
10400
+ const slice = slicePhysicalColumn(block.column, start, end);
10316
10401
  ranges.push({ column: slice, start: 0, end: slice.rowCount });
10317
10402
  }
10318
- return { ranges, peakWorkingBytes: memoryBound };
10403
+ return { ranges, peakWorkingBytes: Math.max(memoryBound, peakWorkingBytes) };
10404
+ }
10405
+ async #decodeCompactionBlocks(blocks, budget, snapshot, decode, consume) {
10406
+ let retainedBytes = 0;
10407
+ let peakBytes = 0;
10408
+ let start = 0;
10409
+ while (start < blocks.length) {
10410
+ let end = start;
10411
+ let transientBytes = 0;
10412
+ let storedBytes = 0;
10413
+ for (; end < blocks.length && end - start < MAX_STORAGE_BULK_READ_ITEMS; end += 1) {
10414
+ const block = blocks[end];
10415
+ if (block === void 0)
10416
+ break;
10417
+ const blockTransientBytes = safeWholeNumberSum([
10418
+ safeWholeNumberProduct(block.storedBytes, 2, "Compaction decode memory"),
10419
+ safeWholeNumberProduct(block.encodedBytes, 2, "Compaction decode memory")
10420
+ ], "Compaction decode memory");
10421
+ if (end > start && (transientBytes + blockTransientBytes > budget.limitBytes - retainedBytes || storedBytes + block.storedBytes > MAX_BLOCK_READ_BATCH_BYTES)) {
10422
+ break;
10423
+ }
10424
+ transientBytes += blockTransientBytes;
10425
+ storedBytes += block.storedBytes;
10426
+ }
10427
+ const group = blocks.slice(start, end);
10428
+ if (snapshot !== void 0)
10429
+ await this.#renewInternalLeaseIfNeeded(snapshot);
10430
+ const bytes = await this.#readBlockWindow(group.map((block) => block.blockId));
10431
+ const decoded = await Promise.all(group.map((block, index) => {
10432
+ const stored = bytes[index];
10433
+ if (stored === void 0) {
10434
+ throw new Error(`A compaction source block is missing: ${block.blockId}`);
10435
+ }
10436
+ return decode(stored, block);
10437
+ }));
10438
+ peakBytes = Math.max(peakBytes, retainedBytes + transientBytes);
10439
+ for (const [index, block] of group.entries()) {
10440
+ const value = decoded[index];
10441
+ if (value === void 0)
10442
+ throw new Error("Compaction decode lost a block");
10443
+ consume(value, block);
10444
+ if (budget.retainsDecoded)
10445
+ retainedBytes += block.encodedBytes;
10446
+ }
10447
+ start = end;
10448
+ }
10449
+ return peakBytes;
10319
10450
  }
10320
10451
  async #loadCompactionSources(job) {
10321
10452
  const segments = await Promise.all(job.sourceSegmentIds.map((id) => this.store.getSegment(id)));
@@ -14152,6 +14283,49 @@ function normalizeColumnLogicalValue(column, value) {
14152
14283
  }
14153
14284
  return value;
14154
14285
  }
14286
+ function mergeAscendingIndexes(left, right) {
14287
+ const merged = [];
14288
+ let leftIndex = 0;
14289
+ let rightIndex = 0;
14290
+ while (leftIndex < left.length || rightIndex < right.length) {
14291
+ const a = left[leftIndex];
14292
+ const b = right[rightIndex];
14293
+ if (b === void 0 || a !== void 0 && a <= b) {
14294
+ if (a !== void 0)
14295
+ merged.push(a);
14296
+ leftIndex += 1;
14297
+ } else {
14298
+ merged.push(b);
14299
+ rightIndex += 1;
14300
+ }
14301
+ }
14302
+ return merged;
14303
+ }
14304
+ function applyUpsertConflictFilter(batch, rowCount, classified) {
14305
+ const acceptedRowIndexes = mergeAscendingIndexes(classified.inserts, classified.updates);
14306
+ const skippedRowCount = classified.skipped.length;
14307
+ if (acceptedRowIndexes.length === rowCount) {
14308
+ return { batch, rowCount, skippedRowCount, acceptedRowIndexes, firings: classified };
14309
+ }
14310
+ const acceptedPosition = new Map(acceptedRowIndexes.map((original, position) => [original, position]));
14311
+ return {
14312
+ batch: selectBatchRows(batch, acceptedRowIndexes),
14313
+ rowCount: acceptedRowIndexes.length,
14314
+ skippedRowCount,
14315
+ acceptedRowIndexes,
14316
+ firings: {
14317
+ inserts: classified.inserts.map((index) => acceptedPosition.get(index) ?? -1),
14318
+ updates: classified.updates.map((index) => acceptedPosition.get(index) ?? -1),
14319
+ skipped: [],
14320
+ oldImages: acceptedRowIndexes.map((index) => classified.oldImages[index])
14321
+ }
14322
+ };
14323
+ }
14324
+ function remapGeneratedColumns(generated, acceptedRowIndexes) {
14325
+ for (const [name, values] of generated) {
14326
+ generated.set(name, acceptedRowIndexes.map((index) => values[index] ?? null));
14327
+ }
14328
+ }
14155
14329
  function selectBatchRows(input, indexes) {
14156
14330
  return {
14157
14331
  columns: Object.fromEntries(Object.entries(input.columns).map(([name, values]) => [
@@ -14431,6 +14605,14 @@ function autoCompactionHint(version, segments) {
14431
14605
  deltas
14432
14606
  };
14433
14607
  }
14608
+ function countLevelZeroSegments(segments) {
14609
+ let levelZero = 0;
14610
+ for (const segment of segments) {
14611
+ if (segment.level === 0)
14612
+ levelZero += 1;
14613
+ }
14614
+ return levelZero;
14615
+ }
14434
14616
  function autoCompactionDueHint(hint) {
14435
14617
  return hint.levelZero >= AUTO_COMPACT_SCAN_SEGMENTS || hint.deltas >= AUTO_COMPACT_DELTA_SEGMENTS;
14436
14618
  }
@@ -15881,6 +16063,7 @@ function sourceOrderTuple(segment, transactions, label) {
15881
16063
  };
15882
16064
  }
15883
16065
  const MERGE_PLANNER_KEY_BYTES = 96;
16066
+ const MERGE_PLANNER_DECODED_KEY_BLOCK_FACTOR = 4;
15884
16067
  function planLinearOutputPartitions(totalRows, partitionRows, firstOrder, nextOrder) {
15885
16068
  const count = Math.max(1, Math.ceil(totalRows / partitionRows));
15886
16069
  const orders = fractionalLogicalOrders(firstOrder, nextOrder, count);
@@ -15996,7 +16179,6 @@ function mergePlannerMemoryBound(table, segments, keyColumnId) {
15996
16179
  const PATCH_ROW_BYTES = 64;
15997
16180
  const PATCH_CELL_BYTES = 48;
15998
16181
  const RANGE_BYTES = 80;
15999
- const DECODED_KEY_BLOCK_FACTOR = 4;
16000
16182
  let slotRows = 0;
16001
16183
  let deltaKeys = 0;
16002
16184
  let patchRows = 0;
@@ -16027,7 +16209,7 @@ function mergePlannerMemoryBound(table, segments, keyColumnId) {
16027
16209
  safeWholeNumberProduct(columns, PATCH_CELL_BYTES, "Mutation patch cells")
16028
16210
  ], "Mutation compaction patch row"), "Mutation compaction patches"),
16029
16211
  safeWholeNumberProduct(safeWholeNumberSum([sourceBlocks, safeWholeNumberProduct(patchRows, columns, "Mutation patched cells")], "Mutation compaction ranges"), RANGE_BYTES, "Mutation compaction range bytes"),
16030
- safeWholeNumberProduct(largestKeyBlockBytes, DECODED_KEY_BLOCK_FACTOR, "Mutation compaction decoded key block")
16212
+ safeWholeNumberProduct(largestKeyBlockBytes, MERGE_PLANNER_DECODED_KEY_BLOCK_FACTOR, "Mutation compaction decoded key block")
16031
16213
  ], "Mutation compaction planner memory");
16032
16214
  }
16033
16215
  function mergeSourceBearsRows(kind) {