@minnowdb/core 0.7.8 → 0.8.0
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 +5 -2
- package/dist/engine/client.js +14 -4
- package/dist/engine/database.js +632 -39
- package/dist/engine/errors.d.ts +2 -2
- package/dist/engine/errors.js +1 -1
- package/dist/engine/keyed-live.js +55 -18
- package/dist/engine/live-aggregate.d.ts +12 -0
- package/dist/engine/live-aggregate.js +226 -0
- package/dist/engine/live-patch.d.ts +21 -0
- package/dist/engine/live-patch.js +31 -0
- package/dist/engine/live.d.ts +107 -8
- package/dist/engine/live.js +353 -187
- package/dist/engine/optimizer.js +11 -6
- package/dist/engine/query-cache.js +2 -13
- package/dist/engine/query-generations.d.ts +8 -0
- package/dist/engine/query-generations.js +61 -0
- package/dist/engine/query-identity.d.ts +3 -0
- package/dist/engine/query-identity.js +41 -0
- package/dist/engine/query.d.ts +6 -11
- package/dist/engine/query.js +294 -428
- package/dist/engine/sql-domains.d.ts +4 -0
- package/dist/engine/sql-domains.js +121 -0
- package/dist/engine/sql-semantics.js +7 -4
- package/dist/engine/typed-live.d.ts +12 -1
- package/dist/engine/typed-live.js +110 -25
- package/dist/engine/windows.d.ts +12 -0
- package/dist/engine/windows.js +386 -0
- package/dist/engine/worker-server.js +12 -5
- package/dist/plan/model.d.ts +1 -1
- package/dist/worker-protocol/index.d.ts +1 -1
- package/dist/worker-protocol/index.js +1 -1
- package/package.json +1 -1
- package/postgres-feature-profile.json +6 -1
- package/sql-feature-matrix.json +20 -27
package/dist/engine/database.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { LiveAggregate } from "./live-aggregate.js";
|
|
2
|
+
import { QueryGenerations } from "./query-generations.js";
|
|
3
|
+
import { applyWindowFunctionsAsync } from "./windows.js";
|
|
1
4
|
import { crossJoinPlan } from "../plan/model.js";
|
|
2
5
|
import { definedVectors, toColumnarBatch } from "./batch.js";
|
|
3
6
|
import { ArtifactCache } from "./artifact-cache.js";
|
|
@@ -16,12 +19,12 @@ import { cachedQueryTerms, FTS_TOKENIZER_VERSION, renderDocumentValue, tokenize
|
|
|
16
19
|
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
20
|
import { decodeSnapshotFrameStream, encodeSnapshotFrameStreamFooter, encodeSnapshotFrameStreamHeader, extendSnapshotFrameStreamChecksum, snapshotFrameEnvelopeParts, snapshotFrameStreamHeaderIdentity } from "../storage/snapshot.js";
|
|
18
21
|
import { Snapshot, TransactionManager } from "../transactions/index.js";
|
|
19
|
-
import {
|
|
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";
|
|
20
23
|
import { copyQueryResult, planMemoKey, queryResultMemoKey, queryResultRetainedBytes, RESULT_MEMO_MAX_BYTES } from "./query-cache.js";
|
|
21
24
|
import { QueryMemoryBudgetError, QueryMemoryContext, DEFAULT_QUERY_MEMORY_BUDGET_BYTES } from "./memory.js";
|
|
22
25
|
import { LiveQueryLimitError, LiveQuerySet, MAX_LIVE_QUERY_SETS_PER_DATABASE } from "./live.js";
|
|
23
26
|
import { chooseJoinOrder, optimizePlan, qualifyCorrelatedReferences, renderPlan } from "./optimizer.js";
|
|
24
|
-
import { encodeSqlEqualityValue, readUntypedText } from "./sql-semantics.js";
|
|
27
|
+
import { encodeSqlEqualityValue, readUntypedText, compareSqlValues } from "./sql-semantics.js";
|
|
25
28
|
import { exactNumericAsNumber, exactNumericValue, externalSqlDomainValue, externalSqlTextValue, isDateDomainValue, isExactNumeric, isSqlDomainValue, normalizeSqlDomainValue, protectedSqlTextValue } from "./sql-domains.js";
|
|
26
29
|
import { toCatalog } from "./catalog.js";
|
|
27
30
|
import { applyColumnSteps, assertColumnDroppable, compileGeneratedColumnExpression, declaredForeignKeys, isDestructiveStep, planMigration } from "./schema.js";
|
|
@@ -576,6 +579,7 @@ class MinnowDatabase {
|
|
|
576
579
|
#liveSets = /* @__PURE__ */ new Set();
|
|
577
580
|
#liveProofContexts = /* @__PURE__ */ new Map();
|
|
578
581
|
#artifactCache;
|
|
582
|
+
#queryGenerations;
|
|
579
583
|
#queryExecutionMemoryBudgetBytes;
|
|
580
584
|
#ftsAutoIndexRows;
|
|
581
585
|
#autoCompact;
|
|
@@ -657,6 +661,7 @@ class MinnowDatabase {
|
|
|
657
661
|
if (!Number.isSafeInteger(this.#transactionOwnerLeaseMs) || this.#transactionOwnerLeaseMs <= 0) {
|
|
658
662
|
throw new RangeError("Transaction owner lease lifetime must be a positive whole number");
|
|
659
663
|
}
|
|
664
|
+
this.#queryGenerations = new QueryGenerations((after, limit) => this.store.listManifestPage(after, limit));
|
|
660
665
|
this.#artifactCache = new ArtifactCache(options.bufferPoolBytes ?? 64 * 1024 * 1024);
|
|
661
666
|
const queryBudget = options.executionMemoryBudgetBytes;
|
|
662
667
|
this.#queryExecutionMemoryBudgetBytes = queryBudget === null ? Number.MAX_SAFE_INTEGER : positiveWholeNumber(queryBudget ?? DEFAULT_QUERY_MEMORY_BUDGET_BYTES, "Query execution memory budget");
|
|
@@ -1124,11 +1129,11 @@ class MinnowDatabase {
|
|
|
1124
1129
|
this.#activeReadReservations -= 1;
|
|
1125
1130
|
}
|
|
1126
1131
|
}
|
|
1127
|
-
async #queryWithReadReservation(sql, options) {
|
|
1132
|
+
async #queryWithReadReservation(sql, options, probe, storeMemo = true) {
|
|
1128
1133
|
throwIfAborted(options.signal);
|
|
1129
1134
|
await this.#settleExpiredStatementTransaction();
|
|
1130
1135
|
throwIfAborted(options.signal);
|
|
1131
|
-
return this.#openTransaction === void 0 ? this.#withReadReservation(() => this.#queryUnreserved(sql, options)) : this.#queryUnreserved(sql, options);
|
|
1136
|
+
return this.#openTransaction === void 0 ? this.#withReadReservation(() => this.#queryUnreserved(sql, options, probe, storeMemo)) : this.#queryUnreserved(sql, options, probe, storeMemo);
|
|
1132
1137
|
}
|
|
1133
1138
|
async #assistAutomaticCollection() {
|
|
1134
1139
|
if (!this.#autoCollect && !this.#manualCollectionDebtInitialized) {
|
|
@@ -2748,6 +2753,12 @@ class MinnowDatabase {
|
|
|
2748
2753
|
const expected = schema2?.[index];
|
|
2749
2754
|
if (expected === void 0)
|
|
2750
2755
|
return;
|
|
2756
|
+
if (column.unknown === true)
|
|
2757
|
+
return;
|
|
2758
|
+
if (expected.unknown === true) {
|
|
2759
|
+
schema2 = schema2?.map((entry, position) => position === index ? { ...column, name: entry.name } : entry);
|
|
2760
|
+
return;
|
|
2761
|
+
}
|
|
2751
2762
|
if (column.type === expected.type && column.sqlDomain?.kind === "numeric" && expected.sqlDomain?.kind === "numeric") {
|
|
2752
2763
|
if (JSON.stringify(column.sqlDomain) !== JSON.stringify(expected.sqlDomain)) {
|
|
2753
2764
|
schema2 = schema2?.map((entry, position) => position === index ? { ...entry, sqlDomain: { kind: "numeric" } } : entry);
|
|
@@ -2769,7 +2780,7 @@ class MinnowDatabase {
|
|
|
2769
2780
|
if (source.windowed !== void 0) {
|
|
2770
2781
|
const innerKey = await this.#blockResultCacheKey(source.windowed.block, snapshot, visibility, realTables, cacheResults);
|
|
2771
2782
|
throwIfAborted(signal);
|
|
2772
|
-
const columnarKey2 = innerKey === void 0 ? void 0 : `ctw|${
|
|
2783
|
+
const columnarKey2 = innerKey === void 0 ? void 0 : `ctw|${planMemoKey(source.windowed.windows)}|${innerKey}`;
|
|
2773
2784
|
if (columnarKey2 !== void 0) {
|
|
2774
2785
|
const hit = this.#cacheGet(columnarKey2);
|
|
2775
2786
|
if (hit !== void 0) {
|
|
@@ -2779,8 +2790,10 @@ class MinnowDatabase {
|
|
|
2779
2790
|
}
|
|
2780
2791
|
}
|
|
2781
2792
|
const { result: inner, schema: innerSchema } = await this.#executeBlockWithSchemaCached(source.windowed.block, snapshot, visibility, memory, realTables, typedSchemas, cacheResults, allowSpill, forceSpill, spillPageRows, signal);
|
|
2782
|
-
const windowed =
|
|
2783
|
-
copyRows: cacheResults
|
|
2793
|
+
const windowed = await applyWindowFunctionsAsync(inner, source.windowed.windows, {
|
|
2794
|
+
copyRows: cacheResults,
|
|
2795
|
+
memoryContext: memory,
|
|
2796
|
+
...signal === void 0 ? {} : { signal }
|
|
2784
2797
|
});
|
|
2785
2798
|
const schema2 = [
|
|
2786
2799
|
...innerSchema,
|
|
@@ -2857,7 +2870,7 @@ class MinnowDatabase {
|
|
|
2857
2870
|
async query(sql, options = {}) {
|
|
2858
2871
|
return this.#queryWithReadReservation(sql, options);
|
|
2859
2872
|
}
|
|
2860
|
-
async #queryUnreserved(sql, options) {
|
|
2873
|
+
async #queryUnreserved(sql, options, probe, storeMemo = true) {
|
|
2861
2874
|
throwIfAborted(options.signal);
|
|
2862
2875
|
const open = this.#openTransaction;
|
|
2863
2876
|
if (open !== void 0) {
|
|
@@ -2883,9 +2896,9 @@ class MinnowDatabase {
|
|
|
2883
2896
|
}
|
|
2884
2897
|
}
|
|
2885
2898
|
const plan = bindPlanParameters(compiled, options.params);
|
|
2886
|
-
const
|
|
2899
|
+
const readProbe = this.store.getCatalogProbe.bind(this.store);
|
|
2887
2900
|
const memoizable = options.memoize !== false && cacheableQueryInput(sql, options.params ?? []) && plan.usesStatementDatetime !== true && plan.usesVolatileFunctions !== true && plan.usesSequenceCalls !== true && options.version === void 0 && options.executionMemoryBudgetBytes === void 0 && options.spillToStorage === void 0 && options.spillPageRows === void 0;
|
|
2888
|
-
const result = !memoizable ? await this.#queryCompiled(plan, options) : await this.#memoizedQuery(plan, `res ${queryResultMemoKey(sql, options.params ?? [])}`, options, probe);
|
|
2901
|
+
const result = !memoizable ? await this.#queryCompiled(plan, options, probe) : await this.#memoizedQuery(plan, `res ${queryResultMemoKey(sql, options.params ?? [])}`, options, readProbe, probe, storeMemo);
|
|
2889
2902
|
throwIfAborted(options.signal);
|
|
2890
2903
|
return externalizeQueryResult(result);
|
|
2891
2904
|
}
|
|
@@ -2974,23 +2987,34 @@ class MinnowDatabase {
|
|
|
2974
2987
|
});
|
|
2975
2988
|
}
|
|
2976
2989
|
}
|
|
2977
|
-
async #memoizedQuery(plan, key, options, probe) {
|
|
2990
|
+
async #memoizedQuery(plan, key, options, probe, before, store = true) {
|
|
2978
2991
|
throwIfAborted(options.signal);
|
|
2979
|
-
|
|
2992
|
+
before ??= await probe();
|
|
2980
2993
|
throwIfAborted(options.signal);
|
|
2981
|
-
const
|
|
2994
|
+
const dependencyKey = `deps ${String(before.schemaEpoch)} ${key}`;
|
|
2995
|
+
let dependencies = this.#cacheGet(dependencyKey);
|
|
2996
|
+
if (dependencies === void 0) {
|
|
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}`;
|
|
3003
|
+
const cached = this.#cacheGet(resultKey);
|
|
2982
3004
|
if (cached !== void 0) {
|
|
2983
3005
|
options.onStats?.({ peakMemoryBytes: 0 });
|
|
2984
3006
|
return copyQueryResult(cached);
|
|
2985
3007
|
}
|
|
2986
3008
|
const result = await this.#queryCompiled(plan, options, before);
|
|
2987
3009
|
throwIfAborted(options.signal);
|
|
3010
|
+
if (!store)
|
|
3011
|
+
return result;
|
|
2988
3012
|
const bytes = queryResultRetainedBytes(result);
|
|
2989
3013
|
if (bytes <= RESULT_MEMO_MAX_BYTES) {
|
|
2990
3014
|
const after = await probe();
|
|
2991
3015
|
throwIfAborted(options.signal);
|
|
2992
3016
|
if (after.catalogEpoch === before.catalogEpoch) {
|
|
2993
|
-
this.#cachePut(
|
|
3017
|
+
this.#cachePut(resultKey, copyQueryResult(result), bytes);
|
|
2994
3018
|
}
|
|
2995
3019
|
}
|
|
2996
3020
|
return result;
|
|
@@ -3081,7 +3105,34 @@ class MinnowDatabase {
|
|
|
3081
3105
|
if (plan.base.table !== DUAL_TABLE || plan.joins.length > 0) {
|
|
3082
3106
|
throw new TypeError("NEXTVAL and CURRVAL currently require a SELECT without FROM");
|
|
3083
3107
|
}
|
|
3084
|
-
const resolved = structuredClone(plan);
|
|
3108
|
+
const resolved = resolveStatementDatetimes(structuredClone(plan), this.#now());
|
|
3109
|
+
const evaluate = (expression) => evaluateRowExpression(expression, DUAL_TABLE, {});
|
|
3110
|
+
const literal = (value) => ({
|
|
3111
|
+
kind: "literal",
|
|
3112
|
+
value,
|
|
3113
|
+
internalSqlValue: true
|
|
3114
|
+
});
|
|
3115
|
+
const omitted = resolved.limit === 0;
|
|
3116
|
+
const matches = !omitted && resolved.predicates.every((predicate) => evaluate({ kind: "condition", ...predicate }) === true);
|
|
3117
|
+
if (!omitted)
|
|
3118
|
+
resolved.predicates = matches ? [] : [
|
|
3119
|
+
{
|
|
3120
|
+
left: literal(false),
|
|
3121
|
+
operator: "=",
|
|
3122
|
+
right: literal(true)
|
|
3123
|
+
}
|
|
3124
|
+
];
|
|
3125
|
+
const discarded = !matches;
|
|
3126
|
+
if (discarded) {
|
|
3127
|
+
const omit = (expression) => {
|
|
3128
|
+
if (expression.kind === "call" && sequenceFunctionNames.has(expression.name))
|
|
3129
|
+
return literal(0);
|
|
3130
|
+
return mapChildExpressions(expression, omit);
|
|
3131
|
+
};
|
|
3132
|
+
for (const item of resolved.select)
|
|
3133
|
+
item.expression = omit(item.expression);
|
|
3134
|
+
return resolved;
|
|
3135
|
+
}
|
|
3085
3136
|
const rewrite = async (expression) => {
|
|
3086
3137
|
if (expression.kind === "call" && (expression.name === "NEXTVAL" || expression.name === "CURRVAL")) {
|
|
3087
3138
|
const argument = expression.arguments[0];
|
|
@@ -3105,6 +3156,21 @@ class MinnowDatabase {
|
|
|
3105
3156
|
this.#sequenceCurrValues.set(name, value);
|
|
3106
3157
|
return { kind: "literal", value };
|
|
3107
3158
|
}
|
|
3159
|
+
if (expression.kind === "case") {
|
|
3160
|
+
for (const branch of expression.branches) {
|
|
3161
|
+
if (evaluate(await rewrite(branch.when)) === true)
|
|
3162
|
+
return rewrite(branch.then);
|
|
3163
|
+
}
|
|
3164
|
+
return expression.otherwise === void 0 ? literal(null) : rewrite(expression.otherwise);
|
|
3165
|
+
}
|
|
3166
|
+
if (expression.kind === "call" && expression.name === "COALESCE") {
|
|
3167
|
+
for (const argument of expression.arguments) {
|
|
3168
|
+
const value = evaluate(await rewrite(argument));
|
|
3169
|
+
if (value !== null)
|
|
3170
|
+
return literal(value);
|
|
3171
|
+
}
|
|
3172
|
+
return literal(null);
|
|
3173
|
+
}
|
|
3108
3174
|
if (expression.kind === "binary" || expression.kind === "condition" || expression.kind === "logical") {
|
|
3109
3175
|
expression.left = await rewrite(expression.left);
|
|
3110
3176
|
expression.right = await rewrite(expression.right);
|
|
@@ -3122,13 +3188,6 @@ class MinnowDatabase {
|
|
|
3122
3188
|
}
|
|
3123
3189
|
} else if (expression.kind === "not") {
|
|
3124
3190
|
expression.operand = await rewrite(expression.operand);
|
|
3125
|
-
} else if (expression.kind === "case") {
|
|
3126
|
-
for (const branch of expression.branches) {
|
|
3127
|
-
branch.when = await rewrite(branch.when);
|
|
3128
|
-
branch.then = await rewrite(branch.then);
|
|
3129
|
-
}
|
|
3130
|
-
if (expression.otherwise !== void 0)
|
|
3131
|
-
expression.otherwise = await rewrite(expression.otherwise);
|
|
3132
3191
|
}
|
|
3133
3192
|
return expression;
|
|
3134
3193
|
};
|
|
@@ -3729,19 +3788,15 @@ class MinnowDatabase {
|
|
|
3729
3788
|
return this.store.getCatalogProbe();
|
|
3730
3789
|
},
|
|
3731
3790
|
manifestPage: (afterVersion, limit) => this.store.listManifestPage(afterVersion, limit),
|
|
3732
|
-
dependencyTableIds: async (query) => {
|
|
3791
|
+
dependencyTableIds: async (query, probe) => {
|
|
3733
3792
|
const compiled = compileLiveQuery(query);
|
|
3734
|
-
const plan = await this.#applyCatalogRewrites(compiled);
|
|
3793
|
+
const plan = await this.#applyCatalogRewrites(compiled, probe);
|
|
3735
3794
|
const tables = await this.#findRealBlockTables(plan);
|
|
3736
3795
|
return new Set([...tables.values()].map((record) => record.id));
|
|
3737
3796
|
},
|
|
3738
|
-
execute:
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
if (query.kind === "typed-query")
|
|
3742
|
-
return this.#queryCompiled(query.plan);
|
|
3743
|
-
return this.query(query.sql, { params: query.params });
|
|
3744
|
-
},
|
|
3797
|
+
execute: (query, context) => this.#liveExecute(query, context),
|
|
3798
|
+
executeMaintainable: (query, context) => this.#liveExecuteMaintainable(compileLiveQuery(query), context),
|
|
3799
|
+
maintain: (_query, result, state, tableIds, after, until, probe) => this.#liveMaintain(result, state, tableIds, after, until, probe),
|
|
3745
3800
|
changeCanAffect: (query, tableIds, after, until) => this.#liveChangeCanAffect(query, tableIds, after, until)
|
|
3746
3801
|
}, {
|
|
3747
3802
|
...options,
|
|
@@ -3754,6 +3809,282 @@ class MinnowDatabase {
|
|
|
3754
3809
|
this.#liveSets.add(set);
|
|
3755
3810
|
return set;
|
|
3756
3811
|
}
|
|
3812
|
+
async #liveExecute(query, context) {
|
|
3813
|
+
if (context === void 0) {
|
|
3814
|
+
if (typeof query === "string")
|
|
3815
|
+
return this.query(query);
|
|
3816
|
+
if (query.kind === "typed-query")
|
|
3817
|
+
return externalizeQueryResult(await this.#withReadReservation(() => this.#queryCompiled(query.plan)));
|
|
3818
|
+
return this.query(query.sql, { params: query.params });
|
|
3819
|
+
}
|
|
3820
|
+
const { probe, memoize } = context;
|
|
3821
|
+
if (typeof query !== "string" && query.kind === "typed-query") {
|
|
3822
|
+
const plan = query.plan;
|
|
3823
|
+
const memoizable = plan.usesStatementDatetime !== true && plan.usesVolatileFunctions !== true && plan.usesSequenceCalls !== true;
|
|
3824
|
+
return externalizeQueryResult(await this.#withReadReservation(() => memoizable ? this.#memoizedQuery(plan, `typed ${planMemoKey(plan)}`, {}, this.store.getCatalogProbe.bind(this.store), probe, memoize) : this.#queryCompiled(plan, {}, probe)));
|
|
3825
|
+
}
|
|
3826
|
+
if (typeof query === "string")
|
|
3827
|
+
return this.#queryWithReadReservation(query, {}, probe, memoize);
|
|
3828
|
+
return this.#queryWithReadReservation(query.sql, { params: query.params }, probe, memoize);
|
|
3829
|
+
}
|
|
3830
|
+
async #liveMaintenancePlan(compiled, probe) {
|
|
3831
|
+
if (compiled.usesStatementDatetime === true || compiled.usesVolatileFunctions === true || compiled.usesSequenceCalls === true) {
|
|
3832
|
+
return void 0;
|
|
3833
|
+
}
|
|
3834
|
+
const plan = await this.#applyCatalogRewrites(compiled, probe);
|
|
3835
|
+
const base = plan.base;
|
|
3836
|
+
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
|
+
return void 0;
|
|
3838
|
+
}
|
|
3839
|
+
if (plan.joins.length > 0) {
|
|
3840
|
+
const join = plan.joins[0];
|
|
3841
|
+
if (plan.joins.length !== 1 || join === void 0 || !["inner", "left"].includes(join.kind) || join.on !== void 0 || join.derived !== void 0 || join.union !== void 0 || join.windowed !== void 0 || join.recursive !== void 0)
|
|
3842
|
+
return void 0;
|
|
3843
|
+
const lookup = await this.#findTable(join.table);
|
|
3844
|
+
const lookupKey = getUniqueKeyColumn(lookup);
|
|
3845
|
+
if (lookupKey === void 0 || lookupKey.sqlDomain !== void 0)
|
|
3846
|
+
return void 0;
|
|
3847
|
+
const lookupReference = `${join.alias}.${lookupKey.name}`;
|
|
3848
|
+
const joinedKey = [join.left, join.right].find((expression) => expression.kind === "column" && expression.reference === lookupReference);
|
|
3849
|
+
if (joinedKey === void 0)
|
|
3850
|
+
return void 0;
|
|
3851
|
+
}
|
|
3852
|
+
const table = await this.#findTable(base.table);
|
|
3853
|
+
const keyColumn = getUniqueKeyColumn(table);
|
|
3854
|
+
if (keyColumn === void 0 || keyColumn.hidden === true || keyColumn.sqlDomain !== void 0)
|
|
3855
|
+
return void 0;
|
|
3856
|
+
const aggregate = LiveAggregate.plan(plan, `${base.alias}.${keyColumn.name}`);
|
|
3857
|
+
if (aggregate !== void 0)
|
|
3858
|
+
return {
|
|
3859
|
+
tableId: table.id,
|
|
3860
|
+
keyColumnId: keyColumn.id,
|
|
3861
|
+
qualifiedKey: `${base.alias}.${keyColumn.name}`,
|
|
3862
|
+
fullPlan: aggregate.inputPlan,
|
|
3863
|
+
deltaPlan: aggregate.inputPlan,
|
|
3864
|
+
publicColumns: plan.select.map((item) => item.alias),
|
|
3865
|
+
columnDomains: [],
|
|
3866
|
+
orderTerms: [],
|
|
3867
|
+
limit: void 0,
|
|
3868
|
+
offset: 0,
|
|
3869
|
+
margin: 0,
|
|
3870
|
+
complete: true,
|
|
3871
|
+
rows: [],
|
|
3872
|
+
keys: [],
|
|
3873
|
+
positions: /* @__PURE__ */ new Map(),
|
|
3874
|
+
order: [],
|
|
3875
|
+
aggregate
|
|
3876
|
+
};
|
|
3877
|
+
if (plan.groupBy.length > 0 || plan.having.length > 0)
|
|
3878
|
+
return void 0;
|
|
3879
|
+
const rowLocal = (expression) => {
|
|
3880
|
+
if (expression.kind === "subquery" || expression.kind === "exists" || expression.kind === "window" || expression.kind === "wildcard" || expression.kind === "parameter" || hasAggregate(expression)) {
|
|
3881
|
+
return false;
|
|
3882
|
+
}
|
|
3883
|
+
return childExpressions(expression).every(rowLocal);
|
|
3884
|
+
};
|
|
3885
|
+
if (!plan.select.every((item) => rowLocal(item.expression)))
|
|
3886
|
+
return void 0;
|
|
3887
|
+
if (!plan.predicates.every(({ left, right }) => rowLocal(left) && rowLocal(right))) {
|
|
3888
|
+
return void 0;
|
|
3889
|
+
}
|
|
3890
|
+
if (!plan.orderBy.every((term) => rowLocal(term.expression)))
|
|
3891
|
+
return void 0;
|
|
3892
|
+
const publicColumns = plan.select.map((item) => item.alias);
|
|
3893
|
+
if (publicColumns.some((name) => name.startsWith(LIVE_HIDDEN_PREFIX)))
|
|
3894
|
+
return void 0;
|
|
3895
|
+
const fullPlan = clonePlanTree(plan);
|
|
3896
|
+
const qualifiedKey = `${base.alias}.${keyColumn.name}`;
|
|
3897
|
+
fullPlan.select.push({
|
|
3898
|
+
expression: { kind: "column", reference: qualifiedKey },
|
|
3899
|
+
alias: LIVE_KEY_ALIAS
|
|
3900
|
+
});
|
|
3901
|
+
const orderTerms = [];
|
|
3902
|
+
for (const [index, term] of plan.orderBy.entries()) {
|
|
3903
|
+
let alias;
|
|
3904
|
+
if (term.expression.kind === "column" && !term.expression.reference.includes(".")) {
|
|
3905
|
+
const output = term.expression.reference;
|
|
3906
|
+
if (publicColumns.includes(output))
|
|
3907
|
+
alias = output;
|
|
3908
|
+
} else if (term.expression.kind === "literal" && typeof term.expression.value === "number") {
|
|
3909
|
+
const position = term.expression.value;
|
|
3910
|
+
if (!Number.isInteger(position) || position < 1 || position > publicColumns.length) {
|
|
3911
|
+
return void 0;
|
|
3912
|
+
}
|
|
3913
|
+
alias = publicColumns[position - 1];
|
|
3914
|
+
}
|
|
3915
|
+
if (alias === void 0) {
|
|
3916
|
+
alias = `${LIVE_ORDER_ALIAS}${String(index)}`;
|
|
3917
|
+
fullPlan.select.push({ expression: clonePlanTree(term.expression), alias });
|
|
3918
|
+
}
|
|
3919
|
+
orderTerms.push({ alias, descending: term.direction === "desc", nulls: term.nulls });
|
|
3920
|
+
}
|
|
3921
|
+
const deltaPlan = clonePlanTree(fullPlan);
|
|
3922
|
+
deltaPlan.orderBy = [];
|
|
3923
|
+
delete deltaPlan.limit;
|
|
3924
|
+
delete deltaPlan.offset;
|
|
3925
|
+
const margin = plan.limit === void 0 || orderTerms.length === 0 ? 0 : Math.max(LIVE_WINDOW_MARGIN_MIN, Math.min(plan.limit, LIVE_WINDOW_MARGIN_MAX));
|
|
3926
|
+
if (margin > 0)
|
|
3927
|
+
fullPlan.limit = (plan.limit ?? 0) + margin;
|
|
3928
|
+
return {
|
|
3929
|
+
tableId: table.id,
|
|
3930
|
+
keyColumnId: keyColumn.id,
|
|
3931
|
+
qualifiedKey,
|
|
3932
|
+
fullPlan,
|
|
3933
|
+
deltaPlan,
|
|
3934
|
+
publicColumns,
|
|
3935
|
+
columnDomains: [],
|
|
3936
|
+
orderTerms,
|
|
3937
|
+
limit: plan.limit,
|
|
3938
|
+
offset: plan.offset ?? 0,
|
|
3939
|
+
margin,
|
|
3940
|
+
complete: true,
|
|
3941
|
+
rows: [],
|
|
3942
|
+
keys: [],
|
|
3943
|
+
positions: /* @__PURE__ */ new Map(),
|
|
3944
|
+
order: []
|
|
3945
|
+
};
|
|
3946
|
+
}
|
|
3947
|
+
async #liveExecuteMaintainable(compiled, context) {
|
|
3948
|
+
const probe = context?.probe ?? await this.store.getCatalogProbe();
|
|
3949
|
+
const state = await this.#liveMaintenancePlan(compiled, probe);
|
|
3950
|
+
if (state === void 0)
|
|
3951
|
+
return void 0;
|
|
3952
|
+
if (state.aggregate !== void 0) {
|
|
3953
|
+
try {
|
|
3954
|
+
const input = await this.#withReadReservation(() => this.#queryCompiled(state.fullPlan, {}, probe));
|
|
3955
|
+
const aggregate = state.aggregate.patch(input, /* @__PURE__ */ new Set(), liveKeyToken);
|
|
3956
|
+
return {
|
|
3957
|
+
result: aggregate.result(),
|
|
3958
|
+
state: { ...state, aggregate },
|
|
3959
|
+
retainedBytes: aggregate.retainedBytes
|
|
3960
|
+
};
|
|
3961
|
+
} catch {
|
|
3962
|
+
return void 0;
|
|
3963
|
+
}
|
|
3964
|
+
}
|
|
3965
|
+
let executed;
|
|
3966
|
+
try {
|
|
3967
|
+
executed = externalizeQueryResult(await this.#withReadReservation(() => this.#queryCompiled(state.fullPlan, {}, probe)));
|
|
3968
|
+
} catch {
|
|
3969
|
+
return void 0;
|
|
3970
|
+
}
|
|
3971
|
+
const split = splitLiveHiddenColumns(executed, state);
|
|
3972
|
+
return liveMaintainedOutcome({
|
|
3973
|
+
...state,
|
|
3974
|
+
columnDomains: split.result.columnDomains,
|
|
3975
|
+
complete: state.fullPlan.limit === void 0 || split.result.rows.length < state.fullPlan.limit,
|
|
3976
|
+
rows: split.result.rows,
|
|
3977
|
+
keys: split.keys,
|
|
3978
|
+
order: split.order
|
|
3979
|
+
}, void 0);
|
|
3980
|
+
}
|
|
3981
|
+
async #liveMaintain(retained, state, tableIds, after, until, probe) {
|
|
3982
|
+
if (tableIds.length !== 1 || tableIds[0] !== state.tableId)
|
|
3983
|
+
return void 0;
|
|
3984
|
+
const context = await this.#liveProofContext(after, until);
|
|
3985
|
+
const entry = await this.#liveProofTable(context, state.tableId);
|
|
3986
|
+
if (entry === void 0)
|
|
3987
|
+
return void 0;
|
|
3988
|
+
for (const version of context.changedVersions.get(state.tableId) ?? []) {
|
|
3989
|
+
if (!entry.coveredVersions.has(version))
|
|
3990
|
+
return void 0;
|
|
3991
|
+
}
|
|
3992
|
+
const changed = await this.#liveChangedKeys(entry, state.keyColumnId);
|
|
3993
|
+
if (changed === void 0)
|
|
3994
|
+
return void 0;
|
|
3995
|
+
const { changedKeys, existingKeys } = changed;
|
|
3996
|
+
if (changedKeys.size === 0)
|
|
3997
|
+
return { result: retained, state, changed: false };
|
|
3998
|
+
if (state.aggregate !== void 0) {
|
|
3999
|
+
const deltaPlan2 = {
|
|
4000
|
+
...state.deltaPlan,
|
|
4001
|
+
predicates: [
|
|
4002
|
+
...state.deltaPlan.predicates,
|
|
4003
|
+
{
|
|
4004
|
+
left: { kind: "column", reference: state.qualifiedKey },
|
|
4005
|
+
operator: "IN",
|
|
4006
|
+
right: {
|
|
4007
|
+
kind: "list",
|
|
4008
|
+
items: [...changedKeys.values()].map((value) => ({ kind: "literal", value }))
|
|
4009
|
+
}
|
|
4010
|
+
}
|
|
4011
|
+
]
|
|
4012
|
+
};
|
|
4013
|
+
const input = await this.#withReadReservation(() => this.#queryCompiled(deltaPlan2, {}, probe));
|
|
4014
|
+
const aggregate = state.aggregate.patch(input, new Set(changedKeys.keys()), liveKeyToken);
|
|
4015
|
+
const result = aggregate.result();
|
|
4016
|
+
const changed2 = result.rows.length !== retained.rows.length || result.rows.some((row, index) => {
|
|
4017
|
+
const previous = retained.rows[index];
|
|
4018
|
+
return previous === void 0 || !sameLiveRow(previous, row, result.columns);
|
|
4019
|
+
});
|
|
4020
|
+
return {
|
|
4021
|
+
result,
|
|
4022
|
+
state: { ...state, aggregate },
|
|
4023
|
+
retainedBytes: aggregate.retainedBytes,
|
|
4024
|
+
changed: changed2
|
|
4025
|
+
};
|
|
4026
|
+
}
|
|
4027
|
+
const affected = /* @__PURE__ */ new Set();
|
|
4028
|
+
const retainedTokens = state.positions;
|
|
4029
|
+
for (const token of changedKeys.keys()) {
|
|
4030
|
+
const index = retainedTokens.get(token);
|
|
4031
|
+
if (index !== void 0)
|
|
4032
|
+
affected.add(index);
|
|
4033
|
+
}
|
|
4034
|
+
const fullLimit = state.limit === void 0 ? void 0 : state.limit + state.margin;
|
|
4035
|
+
const truncated = !state.complete;
|
|
4036
|
+
if (truncated && affected.size > 0 && state.orderTerms.length === 0)
|
|
4037
|
+
return void 0;
|
|
4038
|
+
if (state.offset > 0) {
|
|
4039
|
+
for (const token of existingKeys)
|
|
4040
|
+
if (!retainedTokens.has(token))
|
|
4041
|
+
return void 0;
|
|
4042
|
+
}
|
|
4043
|
+
const deltaPlan = {
|
|
4044
|
+
...state.deltaPlan,
|
|
4045
|
+
predicates: [
|
|
4046
|
+
...state.deltaPlan.predicates,
|
|
4047
|
+
{
|
|
4048
|
+
left: { kind: "column", reference: state.qualifiedKey },
|
|
4049
|
+
operator: "IN",
|
|
4050
|
+
right: {
|
|
4051
|
+
kind: "list",
|
|
4052
|
+
items: [...changedKeys.values()].map((value) => ({ kind: "literal", value }))
|
|
4053
|
+
}
|
|
4054
|
+
}
|
|
4055
|
+
]
|
|
4056
|
+
};
|
|
4057
|
+
const delta = splitLiveHiddenColumns(externalizeQueryResult(await this.#withReadReservation(() => this.#queryCompiled(deltaPlan, {}, probe))), state);
|
|
4058
|
+
const compare = liveOrderComparator(state.orderTerms);
|
|
4059
|
+
const added = {
|
|
4060
|
+
rows: delta.result.rows,
|
|
4061
|
+
keys: delta.keys,
|
|
4062
|
+
order: delta.order,
|
|
4063
|
+
previousIndex: new Int32Array(delta.result.rows.length).fill(-1)
|
|
4064
|
+
};
|
|
4065
|
+
let merged;
|
|
4066
|
+
try {
|
|
4067
|
+
const kept = filterLiveRows(state, (index) => !affected.has(index));
|
|
4068
|
+
if (state.offset > 0 && added.rows.length > 0) {
|
|
4069
|
+
if (kept.rows.length === 0 || added.rows.some((_, index) => compare(added.order, index, kept.order, 0) < 0)) {
|
|
4070
|
+
return void 0;
|
|
4071
|
+
}
|
|
4072
|
+
}
|
|
4073
|
+
merged = mergeLiveRows(kept, added, compare, state.orderTerms.length > 0);
|
|
4074
|
+
} catch {
|
|
4075
|
+
return void 0;
|
|
4076
|
+
}
|
|
4077
|
+
if (fullLimit !== void 0 && merged.rows.length > fullLimit)
|
|
4078
|
+
merged = trimLiveRows(merged, fullLimit);
|
|
4079
|
+
if (truncated && affected.size > 0 && state.limit !== void 0) {
|
|
4080
|
+
const oldEdge = state.rows.length - 1;
|
|
4081
|
+
const visibleEdge = Math.min(merged.rows.length, state.limit) - 1;
|
|
4082
|
+
if (merged.rows.length < state.limit || oldEdge < 0 || visibleEdge < 0 || compare(merged.order, visibleEdge, state.order, oldEdge) > 0) {
|
|
4083
|
+
return void 0;
|
|
4084
|
+
}
|
|
4085
|
+
}
|
|
4086
|
+
return liveMaintainedOutcome({ ...state, rows: merged.rows, keys: merged.keys, order: merged.order }, retained, merged.previousIndex);
|
|
4087
|
+
}
|
|
3757
4088
|
async #liveChangeCanAffect(query, tableIds, after, until) {
|
|
3758
4089
|
const plan = typeof query === "string" ? this.#compileCached(query) : query.kind === "typed-query" ? query.plan : bindPlanParameters(this.#compileCached(query.sql), query.params);
|
|
3759
4090
|
if (planContainsFts(plan))
|
|
@@ -3855,6 +4186,51 @@ class MinnowDatabase {
|
|
|
3855
4186
|
}
|
|
3856
4187
|
return context;
|
|
3857
4188
|
}
|
|
4189
|
+
#liveChangedKeys(entry, keyColumnId) {
|
|
4190
|
+
const cached = entry.changedKeys.get(keyColumnId);
|
|
4191
|
+
if (cached !== void 0)
|
|
4192
|
+
return cached;
|
|
4193
|
+
const pending = (async () => {
|
|
4194
|
+
const blockIds = [];
|
|
4195
|
+
const existingBlockIds = /* @__PURE__ */ new Set();
|
|
4196
|
+
let deltaRows = 0;
|
|
4197
|
+
for (const segment of entry.windowSegments) {
|
|
4198
|
+
if (segment.kind === "base")
|
|
4199
|
+
continue;
|
|
4200
|
+
const ids = segment.columnBlockIds[keyColumnId];
|
|
4201
|
+
if (ids === void 0)
|
|
4202
|
+
return void 0;
|
|
4203
|
+
deltaRows += segment.rowCount;
|
|
4204
|
+
if (deltaRows > LIVE_MAINTENANCE_MAX_DELTA_ROWS)
|
|
4205
|
+
return void 0;
|
|
4206
|
+
blockIds.push(...ids);
|
|
4207
|
+
if (segment.kind !== "insert")
|
|
4208
|
+
for (const id of ids)
|
|
4209
|
+
existingBlockIds.add(id);
|
|
4210
|
+
}
|
|
4211
|
+
const changedKeys = /* @__PURE__ */ new Map();
|
|
4212
|
+
const existingKeys = /* @__PURE__ */ new Set();
|
|
4213
|
+
if (blockIds.length > 0) {
|
|
4214
|
+
const decoded = await this.#loadDecodedBlocks([...new Set(blockIds)]);
|
|
4215
|
+
for (const blockId of blockIds) {
|
|
4216
|
+
const column = decoded.get(blockId);
|
|
4217
|
+
if (column === void 0)
|
|
4218
|
+
return void 0;
|
|
4219
|
+
for (const value of column.values) {
|
|
4220
|
+
if (value === null)
|
|
4221
|
+
continue;
|
|
4222
|
+
const token = liveKeyToken(value);
|
|
4223
|
+
changedKeys.set(token, value);
|
|
4224
|
+
if (existingBlockIds.has(blockId))
|
|
4225
|
+
existingKeys.add(token);
|
|
4226
|
+
}
|
|
4227
|
+
}
|
|
4228
|
+
}
|
|
4229
|
+
return { changedKeys, existingKeys };
|
|
4230
|
+
})();
|
|
4231
|
+
entry.changedKeys.set(keyColumnId, pending);
|
|
4232
|
+
return pending;
|
|
4233
|
+
}
|
|
3858
4234
|
#liveProofTable(context, tableId) {
|
|
3859
4235
|
const cached = context.tables.get(tableId);
|
|
3860
4236
|
if (cached !== void 0)
|
|
@@ -3875,7 +4251,7 @@ class MinnowDatabase {
|
|
|
3875
4251
|
coveredVersions.add(committed);
|
|
3876
4252
|
windowSegments.push(segment);
|
|
3877
4253
|
}
|
|
3878
|
-
return { table, windowSegments, coveredVersions };
|
|
4254
|
+
return { table, windowSegments, coveredVersions, changedKeys: /* @__PURE__ */ new Map() };
|
|
3879
4255
|
})();
|
|
3880
4256
|
context.tables.set(tableId, entry);
|
|
3881
4257
|
return entry;
|
|
@@ -5181,11 +5557,15 @@ class MinnowDatabase {
|
|
|
5181
5557
|
set.notifyLocalCommit();
|
|
5182
5558
|
}
|
|
5183
5559
|
async run(query) {
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5560
|
+
await this.#settleExpiredStatementTransaction();
|
|
5561
|
+
const open = this.#openTransaction;
|
|
5562
|
+
const plan = query.plan;
|
|
5563
|
+
const execute = async () => {
|
|
5564
|
+
const probe = this.store.getCatalogProbe.bind(this.store);
|
|
5565
|
+
return plan.usesStatementDatetime === true || plan.usesVolatileFunctions === true || plan.usesSequenceCalls === true ? this.#queryCompiled(plan) : this.#memoizedQuery(plan, `typed ${planMemoKey(plan)}`, {}, probe);
|
|
5566
|
+
};
|
|
5567
|
+
const result = open === void 0 ? await this.#withReadReservation(execute) : await this.#duringTransaction(open, () => open.session.queryPlan(plan));
|
|
5568
|
+
return externalizeQueryResult(result).rows;
|
|
5189
5569
|
}
|
|
5190
5570
|
async migrate(definition = this.#schema, options = {}) {
|
|
5191
5571
|
if (definition === void 0) {
|
|
@@ -7968,10 +8348,12 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
7968
8348
|
const table = realTables.get(name);
|
|
7969
8349
|
if (table === void 0)
|
|
7970
8350
|
return void 0;
|
|
7971
|
-
parts.push(`${table.id}
|
|
8351
|
+
parts.push(`${table.id}:${String(table.revision)}`);
|
|
7972
8352
|
}
|
|
7973
8353
|
try {
|
|
7974
|
-
|
|
8354
|
+
const ids = collectRealTableNames(block).sort().map((name) => realTables.get(name)?.id ?? name);
|
|
8355
|
+
const generation = await this.#queryGenerations.key(ids, snapshot.version);
|
|
8356
|
+
return `blk\0${parts.join(";")}\0${generation}\0${planMemoKey(block)}`;
|
|
7975
8357
|
} catch {
|
|
7976
8358
|
return void 0;
|
|
7977
8359
|
}
|
|
@@ -14189,6 +14571,217 @@ function searchableFtsColumns(table) {
|
|
|
14189
14571
|
return void 0;
|
|
14190
14572
|
return visibleTableColumns(table).filter((column) => column.type !== "boolean").map((column) => column.name);
|
|
14191
14573
|
}
|
|
14574
|
+
const LIVE_HIDDEN_PREFIX = "__minnow_live_";
|
|
14575
|
+
const LIVE_KEY_ALIAS = `${LIVE_HIDDEN_PREFIX}key`;
|
|
14576
|
+
const LIVE_ORDER_ALIAS = `${LIVE_HIDDEN_PREFIX}order_`;
|
|
14577
|
+
const LIVE_MAINTENANCE_MAX_DELTA_ROWS = 2048;
|
|
14578
|
+
const LIVE_WINDOW_MARGIN_MIN = 16;
|
|
14579
|
+
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
|
+
function liveKeyToken(value) {
|
|
14594
|
+
if (typeof value === "number")
|
|
14595
|
+
return `n:${String(value)}`;
|
|
14596
|
+
if (typeof value === "string")
|
|
14597
|
+
return `s:${value}`;
|
|
14598
|
+
if (typeof value === "boolean")
|
|
14599
|
+
return value ? "b:1" : "b:0";
|
|
14600
|
+
if (value instanceof Date)
|
|
14601
|
+
return `d:${String(dateMilliseconds(value))}`;
|
|
14602
|
+
return "z";
|
|
14603
|
+
}
|
|
14604
|
+
function splitLiveHiddenColumns(executed, state) {
|
|
14605
|
+
const publicCount = state.publicColumns.length;
|
|
14606
|
+
const hidden = executed.columns.slice(publicCount);
|
|
14607
|
+
const count = executed.rows.length;
|
|
14608
|
+
const keys = new Array(count);
|
|
14609
|
+
const order = state.orderTerms.map(() => new Array(count));
|
|
14610
|
+
for (let index = 0; index < count; index += 1) {
|
|
14611
|
+
const row = executed.rows[index] ?? {};
|
|
14612
|
+
keys[index] = row[LIVE_KEY_ALIAS] ?? null;
|
|
14613
|
+
for (const [term, { alias }] of state.orderTerms.entries()) {
|
|
14614
|
+
const values = order[term];
|
|
14615
|
+
if (values !== void 0)
|
|
14616
|
+
values[index] = row[alias] ?? null;
|
|
14617
|
+
}
|
|
14618
|
+
for (const column of hidden)
|
|
14619
|
+
Reflect.deleteProperty(row, column);
|
|
14620
|
+
}
|
|
14621
|
+
return {
|
|
14622
|
+
result: {
|
|
14623
|
+
columns: executed.columns.slice(0, publicCount),
|
|
14624
|
+
columnDomains: executed.columnDomains.slice(0, publicCount),
|
|
14625
|
+
rows: executed.rows
|
|
14626
|
+
},
|
|
14627
|
+
keys,
|
|
14628
|
+
order
|
|
14629
|
+
};
|
|
14630
|
+
}
|
|
14631
|
+
function liveMaintainedOutcome(state, previous, previousIndex) {
|
|
14632
|
+
const positions = /* @__PURE__ */ new Map();
|
|
14633
|
+
let retainedBytes = 128 + planMemoKey(state.fullPlan).length * 2 + planMemoKey(state.deltaPlan).length * 2;
|
|
14634
|
+
for (const [index, key] of state.keys.entries()) {
|
|
14635
|
+
const token = liveKeyToken(key);
|
|
14636
|
+
positions.set(token, index);
|
|
14637
|
+
retainedBytes += 64 + token.length * 2;
|
|
14638
|
+
}
|
|
14639
|
+
for (const row of state.rows)
|
|
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 };
|
|
14644
|
+
const visibleCount = state.limit === void 0 ? state.rows.length : Math.min(state.rows.length, state.limit);
|
|
14645
|
+
const visible = state.rows.slice(0, visibleCount);
|
|
14646
|
+
if (previous === void 0) {
|
|
14647
|
+
return {
|
|
14648
|
+
result: {
|
|
14649
|
+
columns: [...state.publicColumns],
|
|
14650
|
+
columnDomains: [...state.columnDomains],
|
|
14651
|
+
rows: visible
|
|
14652
|
+
},
|
|
14653
|
+
state,
|
|
14654
|
+
retainedBytes,
|
|
14655
|
+
changed: true
|
|
14656
|
+
};
|
|
14657
|
+
}
|
|
14658
|
+
const previousCount = previous.rows.length;
|
|
14659
|
+
const retained = new Int32Array(visibleCount);
|
|
14660
|
+
let same = visibleCount === previousCount;
|
|
14661
|
+
let kept = 0;
|
|
14662
|
+
for (let index = 0; index < visibleCount; index += 1) {
|
|
14663
|
+
const was = previousIndex?.[index] ?? -1;
|
|
14664
|
+
if (was >= 0 && was < previousCount) {
|
|
14665
|
+
retained[index] = was;
|
|
14666
|
+
kept += 1;
|
|
14667
|
+
if (was !== index)
|
|
14668
|
+
same = false;
|
|
14669
|
+
continue;
|
|
14670
|
+
}
|
|
14671
|
+
retained[index] = -1;
|
|
14672
|
+
const before = previous.rows[index];
|
|
14673
|
+
const now = visible[index];
|
|
14674
|
+
if (before !== void 0 && now !== void 0 && sameLiveRow(before, now, previous.columns)) {
|
|
14675
|
+
visible[index] = before;
|
|
14676
|
+
retained[index] = index;
|
|
14677
|
+
kept += 1;
|
|
14678
|
+
} else
|
|
14679
|
+
same = false;
|
|
14680
|
+
}
|
|
14681
|
+
if (same)
|
|
14682
|
+
return { result: previous, state, retainedBytes, changed: false };
|
|
14683
|
+
return {
|
|
14684
|
+
result: {
|
|
14685
|
+
columns: [...state.publicColumns],
|
|
14686
|
+
columnDomains: [...state.columnDomains],
|
|
14687
|
+
rows: visible
|
|
14688
|
+
},
|
|
14689
|
+
state,
|
|
14690
|
+
retainedBytes,
|
|
14691
|
+
changed: true,
|
|
14692
|
+
...kept > 0 ? { retained } : {}
|
|
14693
|
+
};
|
|
14694
|
+
}
|
|
14695
|
+
function filterLiveRows(state, keep) {
|
|
14696
|
+
const rows = [];
|
|
14697
|
+
const keys = [];
|
|
14698
|
+
const order = state.order.map(() => new Array());
|
|
14699
|
+
const previous = [];
|
|
14700
|
+
for (let index = 0; index < state.rows.length; index += 1) {
|
|
14701
|
+
const row = state.rows[index];
|
|
14702
|
+
if (row === void 0 || !keep(index))
|
|
14703
|
+
continue;
|
|
14704
|
+
rows.push(row);
|
|
14705
|
+
keys.push(state.keys[index] ?? null);
|
|
14706
|
+
for (const [term, values] of state.order.entries()) {
|
|
14707
|
+
order[term]?.push(values[index] ?? null);
|
|
14708
|
+
}
|
|
14709
|
+
previous.push(index);
|
|
14710
|
+
}
|
|
14711
|
+
return { rows, keys, order, previousIndex: Int32Array.from(previous) };
|
|
14712
|
+
}
|
|
14713
|
+
function trimLiveRows(rows, count) {
|
|
14714
|
+
return {
|
|
14715
|
+
rows: rows.rows.slice(0, count),
|
|
14716
|
+
keys: rows.keys.slice(0, count),
|
|
14717
|
+
order: rows.order.map((values) => values.slice(0, count)),
|
|
14718
|
+
previousIndex: rows.previousIndex.slice(0, count)
|
|
14719
|
+
};
|
|
14720
|
+
}
|
|
14721
|
+
function liveOrderComparator(terms) {
|
|
14722
|
+
return (leftOrder, leftIndex, rightOrder, rightIndex) => {
|
|
14723
|
+
for (const [term, { descending, nulls }] of terms.entries()) {
|
|
14724
|
+
const a = leftOrder[term]?.[leftIndex] ?? null;
|
|
14725
|
+
const b = rightOrder[term]?.[rightIndex] ?? null;
|
|
14726
|
+
if (a === null || b === null) {
|
|
14727
|
+
if (a === null && b === null)
|
|
14728
|
+
continue;
|
|
14729
|
+
const nullsFirst = nulls === "first" || nulls === void 0 && descending;
|
|
14730
|
+
return a === null ? nullsFirst ? -1 : 1 : nullsFirst ? 1 : -1;
|
|
14731
|
+
}
|
|
14732
|
+
let comparison = compareSqlValues(a, b);
|
|
14733
|
+
if (descending)
|
|
14734
|
+
comparison = -comparison;
|
|
14735
|
+
if (comparison !== 0)
|
|
14736
|
+
return comparison;
|
|
14737
|
+
}
|
|
14738
|
+
return 0;
|
|
14739
|
+
};
|
|
14740
|
+
}
|
|
14741
|
+
function mergeLiveRows(kept, added, compare, ordered) {
|
|
14742
|
+
if (added.rows.length === 0)
|
|
14743
|
+
return kept;
|
|
14744
|
+
const terms = kept.order.length;
|
|
14745
|
+
const total = kept.rows.length + added.rows.length;
|
|
14746
|
+
const rows = new Array(total);
|
|
14747
|
+
const keys = new Array(total);
|
|
14748
|
+
const order = Array.from({ length: terms }, () => new Array(total));
|
|
14749
|
+
const previousIndex = new Int32Array(total);
|
|
14750
|
+
const take = (source, from, to) => {
|
|
14751
|
+
rows[to] = source.rows[from] ?? {};
|
|
14752
|
+
keys[to] = source.keys[from] ?? null;
|
|
14753
|
+
for (let term = 0; term < terms; term += 1) {
|
|
14754
|
+
const values = order[term];
|
|
14755
|
+
if (values !== void 0)
|
|
14756
|
+
values[to] = source.order[term]?.[from] ?? null;
|
|
14757
|
+
}
|
|
14758
|
+
previousIndex[to] = source.previousIndex[from] ?? -1;
|
|
14759
|
+
};
|
|
14760
|
+
if (!ordered) {
|
|
14761
|
+
for (let index = 0; index < kept.rows.length; index += 1)
|
|
14762
|
+
take(kept, index, index);
|
|
14763
|
+
for (let index = 0; index < added.rows.length; index += 1) {
|
|
14764
|
+
take(added, index, kept.rows.length + index);
|
|
14765
|
+
}
|
|
14766
|
+
return { rows, keys, order, previousIndex };
|
|
14767
|
+
}
|
|
14768
|
+
const addedIndexes = added.rows.map((_, index) => index);
|
|
14769
|
+
addedIndexes.sort((left, right) => compare(added.order, left, added.order, right));
|
|
14770
|
+
let keptIndex = 0;
|
|
14771
|
+
let addedPosition = 0;
|
|
14772
|
+
for (let to = 0; to < total; to += 1) {
|
|
14773
|
+
const addedIndex = addedIndexes[addedPosition];
|
|
14774
|
+
const takeAdded = addedIndex !== void 0 && (keptIndex >= kept.rows.length || compare(added.order, addedIndex, kept.order, keptIndex) < 0);
|
|
14775
|
+
if (takeAdded) {
|
|
14776
|
+
take(added, addedIndex, to);
|
|
14777
|
+
addedPosition += 1;
|
|
14778
|
+
} else {
|
|
14779
|
+
take(kept, keptIndex, to);
|
|
14780
|
+
keptIndex += 1;
|
|
14781
|
+
}
|
|
14782
|
+
}
|
|
14783
|
+
return { rows, keys, order, previousIndex };
|
|
14784
|
+
}
|
|
14192
14785
|
function getUniqueKeyColumn(table) {
|
|
14193
14786
|
if (table.uniqueKeyColumnId === void 0)
|
|
14194
14787
|
return void 0;
|