@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
|
@@ -91,3 +91,7 @@ export declare function concatenatedSqlValue(leftValue: unknown, rightValue: unk
|
|
|
91
91
|
*/
|
|
92
92
|
export declare function intervalDomainCompare(left: unknown, right: unknown): number | undefined;
|
|
93
93
|
export declare function isSqlDomainValue(value: unknown): value is string;
|
|
94
|
+
/** SQL extraction from TIME and INTERVAL without collapsing calendar months into a timestamp. */
|
|
95
|
+
export declare function temporalDomainPart(field: string, value: unknown): number | undefined;
|
|
96
|
+
/** Structural ordering for shipped JSONB and one-dimensional ARRAY values. */
|
|
97
|
+
export declare function structuredDomainCompare(left: unknown, right: unknown): number | undefined;
|
|
@@ -722,6 +722,125 @@ function intervalDomainCompare(left, right) {
|
|
|
722
722
|
function isSqlDomainValue(value) {
|
|
723
723
|
return typeof value === "string" && value.startsWith(PREFIX);
|
|
724
724
|
}
|
|
725
|
+
function temporalDomainPart(field, value) {
|
|
726
|
+
if (typeof value !== "string")
|
|
727
|
+
return void 0;
|
|
728
|
+
let months = 0;
|
|
729
|
+
let days = 0;
|
|
730
|
+
let usecs;
|
|
731
|
+
const interval = value.startsWith(INTERVAL_VALUE);
|
|
732
|
+
if (interval) {
|
|
733
|
+
[months, days, usecs] = JSON.parse(value.slice(INTERVAL_VALUE.length));
|
|
734
|
+
} else if (value.startsWith(TIME_VALUE)) {
|
|
735
|
+
const [hour = "0", minute = "0", second = "0"] = value.slice(TIME_VALUE.length).split(":");
|
|
736
|
+
usecs = Number(hour) * 36e8 + Number(minute) * 6e7 + Math.round(Number(second) * 1e6);
|
|
737
|
+
} else
|
|
738
|
+
return void 0;
|
|
739
|
+
switch (field) {
|
|
740
|
+
case "hour":
|
|
741
|
+
return Math.trunc(usecs / 36e8);
|
|
742
|
+
case "minute":
|
|
743
|
+
return Math.trunc(usecs / 6e7) % 60;
|
|
744
|
+
case "second":
|
|
745
|
+
return usecs % 6e7 / 1e6;
|
|
746
|
+
case "milliseconds":
|
|
747
|
+
return usecs % 6e7 / 1e3;
|
|
748
|
+
case "microseconds":
|
|
749
|
+
return usecs % 6e7;
|
|
750
|
+
case "epoch":
|
|
751
|
+
return interval ? (Math.trunc(months / 12) * 365.25 + months % 12 * 30 + days) * 86400 + usecs / 1e6 : usecs / 1e6;
|
|
752
|
+
}
|
|
753
|
+
if (interval) {
|
|
754
|
+
const years = Math.trunc(months / 12);
|
|
755
|
+
switch (field) {
|
|
756
|
+
case "year":
|
|
757
|
+
return years;
|
|
758
|
+
case "month":
|
|
759
|
+
return months % 12;
|
|
760
|
+
case "day":
|
|
761
|
+
return days;
|
|
762
|
+
case "quarter":
|
|
763
|
+
return Math.trunc(months % 12 / 3) + 1;
|
|
764
|
+
case "decade":
|
|
765
|
+
return Math.trunc(years / 10);
|
|
766
|
+
case "century":
|
|
767
|
+
return Math.trunc(years / 100);
|
|
768
|
+
case "millennium":
|
|
769
|
+
return Math.trunc(years / 1e3);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
throw new TypeError(`Cannot extract ${field} from ${interval ? "INTERVAL" : "TIME"}`);
|
|
773
|
+
}
|
|
774
|
+
function structuredDomainCompare(left, right) {
|
|
775
|
+
if (typeof left !== "string" || typeof right !== "string")
|
|
776
|
+
return void 0;
|
|
777
|
+
const array = left.startsWith(ARRAY_VALUE) && right.startsWith(ARRAY_VALUE);
|
|
778
|
+
const jsonb = left.startsWith(JSONB_VALUE) && right.startsWith(JSONB_VALUE);
|
|
779
|
+
if (!array && !jsonb)
|
|
780
|
+
return void 0;
|
|
781
|
+
if (left === right)
|
|
782
|
+
return 0;
|
|
783
|
+
const prefix = array ? ARRAY_VALUE : JSONB_VALUE;
|
|
784
|
+
const a = JSON.parse(left.slice(prefix.length));
|
|
785
|
+
const b = JSON.parse(right.slice(prefix.length));
|
|
786
|
+
if (array && Array.isArray(a) && Array.isArray(b)) {
|
|
787
|
+
for (let index = 0; index < Math.min(a.length, b.length); index += 1) {
|
|
788
|
+
const x = a[index];
|
|
789
|
+
const y = b[index];
|
|
790
|
+
const order = x === null || y === null ? x === y ? 0 : x === null ? 1 : -1 : compareJsonStructure(x, y);
|
|
791
|
+
if (order !== 0)
|
|
792
|
+
return order;
|
|
793
|
+
}
|
|
794
|
+
return a.length - b.length;
|
|
795
|
+
}
|
|
796
|
+
if (Array.isArray(a) && a.length === 0)
|
|
797
|
+
return Array.isArray(b) && b.length === 0 ? 0 : -1;
|
|
798
|
+
if (Array.isArray(b) && b.length === 0)
|
|
799
|
+
return 1;
|
|
800
|
+
return compareJsonStructure(a, b);
|
|
801
|
+
}
|
|
802
|
+
function compareJsonStructure(left, right) {
|
|
803
|
+
if (left === right)
|
|
804
|
+
return 0;
|
|
805
|
+
const rank = (value) => value === null ? 0 : typeof value === "string" ? 1 : typeof value === "number" ? 2 : typeof value === "boolean" ? 3 : Array.isArray(value) ? 4 : 5;
|
|
806
|
+
const difference = rank(left) - rank(right);
|
|
807
|
+
if (difference !== 0)
|
|
808
|
+
return difference;
|
|
809
|
+
if (typeof left === "number" && typeof right === "number")
|
|
810
|
+
return left - right;
|
|
811
|
+
if (typeof left === "string" && typeof right === "string")
|
|
812
|
+
return left < right ? -1 : 1;
|
|
813
|
+
if (typeof left === "boolean" && typeof right === "boolean")
|
|
814
|
+
return Number(left) - Number(right);
|
|
815
|
+
if (Array.isArray(left) && Array.isArray(right)) {
|
|
816
|
+
if (left.length !== right.length)
|
|
817
|
+
return left.length - right.length;
|
|
818
|
+
for (let index = 0; index < left.length; index += 1) {
|
|
819
|
+
const order = compareJsonStructure(left[index], right[index]);
|
|
820
|
+
if (order !== 0)
|
|
821
|
+
return order;
|
|
822
|
+
}
|
|
823
|
+
return 0;
|
|
824
|
+
}
|
|
825
|
+
const a = left;
|
|
826
|
+
const b = right;
|
|
827
|
+
const encoder = new TextEncoder();
|
|
828
|
+
const keyOrder = (x, y) => encoder.encode(x).length - encoder.encode(y).length || (x === y ? 0 : x < y ? -1 : 1);
|
|
829
|
+
const keysA = Object.keys(a).sort(keyOrder);
|
|
830
|
+
const keysB = Object.keys(b).sort(keyOrder);
|
|
831
|
+
if (keysA.length !== keysB.length)
|
|
832
|
+
return keysA.length - keysB.length;
|
|
833
|
+
for (let index = 0; index < keysA.length; index += 1) {
|
|
834
|
+
const x = keysA[index] ?? "";
|
|
835
|
+
const y = keysB[index] ?? "";
|
|
836
|
+
if (x !== y)
|
|
837
|
+
return x < y ? -1 : 1;
|
|
838
|
+
const order = compareJsonStructure(a[x], b[y]);
|
|
839
|
+
if (order !== 0)
|
|
840
|
+
return order;
|
|
841
|
+
}
|
|
842
|
+
return 0;
|
|
843
|
+
}
|
|
725
844
|
export {
|
|
726
845
|
arrayDomainValue,
|
|
727
846
|
boundedJsonText,
|
|
@@ -751,6 +870,8 @@ export {
|
|
|
751
870
|
normalizeSqlDomainValue,
|
|
752
871
|
preservedJsonDomainValue,
|
|
753
872
|
protectedSqlTextValue,
|
|
873
|
+
structuredDomainCompare,
|
|
874
|
+
temporalDomainPart,
|
|
754
875
|
timeDomainValue,
|
|
755
876
|
uuidDomainValue
|
|
756
877
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { dateMilliseconds } from "../date-value.js";
|
|
2
2
|
import { assertWellFormedString } from "../block-format/unicode.js";
|
|
3
3
|
import { MAX_SQL_NESTING_DEPTH, MAX_SQL_PATTERN_CHARACTERS, MAX_SQL_PATTERN_MATCH_STEPS } from "./cache-limits.js";
|
|
4
|
-
import { collatedDomainCompare, enumDomainCompare, exactNumericCompare, externalSqlDomainValue, externalSqlTextValue, intervalDomainCompare, isDateDomainValue, isSqlDomainValue } from "./sql-domains.js";
|
|
4
|
+
import { structuredDomainCompare, collatedDomainCompare, enumDomainCompare, exactNumericCompare, externalSqlDomainValue, externalSqlTextValue, intervalDomainCompare, isDateDomainValue, isSqlDomainValue } from "./sql-domains.js";
|
|
5
5
|
const SQL_TIMESTAMP_TEXT = /^(\d{4}-\d{2}-\d{2})(?:[ T](\d{2}:\d{2}(?::\d{2}(?:\.\d{1,3})?)?))?(Z|[+-]\d{2}:?\d{2})?$/;
|
|
6
6
|
function parseSqlTimestampText(text) {
|
|
7
7
|
const match = SQL_TIMESTAMP_TEXT.exec(text.trim());
|
|
@@ -33,9 +33,9 @@ function readUntypedText(type, text) {
|
|
|
33
33
|
}
|
|
34
34
|
if (type === "boolean") {
|
|
35
35
|
const lowered = text.trim().toLowerCase();
|
|
36
|
-
if (
|
|
36
|
+
if (/^(?:t(?:r(?:u(?:e)?)?)?|y(?:e(?:s)?)?|on|1)$/.test(lowered))
|
|
37
37
|
return true;
|
|
38
|
-
if (
|
|
38
|
+
if (/^(?:f(?:a(?:l(?:s(?:e)?)?)?)?|n(?:o)?|of(?:f)?|0)$/.test(lowered))
|
|
39
39
|
return false;
|
|
40
40
|
}
|
|
41
41
|
return text;
|
|
@@ -72,6 +72,9 @@ function compareSqlValues(left, right) {
|
|
|
72
72
|
}
|
|
73
73
|
return compareSqlStrings(plainLeft, plainRight);
|
|
74
74
|
}
|
|
75
|
+
const structured = structuredDomainCompare(left, right);
|
|
76
|
+
if (structured !== void 0)
|
|
77
|
+
return structured;
|
|
75
78
|
const collated = collatedDomainCompare(left, right);
|
|
76
79
|
if (collated !== void 0)
|
|
77
80
|
return collated;
|
|
@@ -347,7 +350,7 @@ function generalLikeMatcher(tokens, caseInsensitive) {
|
|
|
347
350
|
}
|
|
348
351
|
function compileLikePattern(pattern, caseInsensitive = false, escape) {
|
|
349
352
|
assertBoundedPattern(pattern, "LIKE pattern");
|
|
350
|
-
const exactEscape = escape === void 0 ? void 0 : exactSingleCharacter(escape, "LIKE escape");
|
|
353
|
+
const exactEscape = escape === void 0 || escape === "" ? void 0 : exactSingleCharacter(escape, "LIKE escape");
|
|
351
354
|
const key = JSON.stringify([caseInsensitive, exactEscape ?? null, pattern]);
|
|
352
355
|
const cached = cachedPattern(likeCache, key);
|
|
353
356
|
if (cached !== void 0)
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import type { LiveQueryInput, LiveQueryObserveOptions } from "./live.js";
|
|
1
|
+
import type { LiveQueryInput, LiveQueryObserveOptions, LiveQuerySubscribeOptions } from "./live.js";
|
|
2
|
+
import type { QueryResult } from "./query.js";
|
|
2
3
|
/** The structural live-query surface shared by MinnowDatabase and its worker client. */
|
|
3
4
|
export interface LiveQueryBackend {
|
|
4
5
|
observe(query: LiveQueryInput, options: LiveQueryObserveOptions): Promise<LiveQuerySubscriptionLike>;
|
|
6
|
+
/** Result delivery, used when the source can decode the engine's result itself. */
|
|
7
|
+
subscribe?(query: LiveQueryInput, options: LiveQuerySubscribeOptions): Promise<LiveQuerySubscriptionLike>;
|
|
5
8
|
refresh(): Promise<void>;
|
|
6
9
|
close(): void | Promise<void>;
|
|
7
10
|
}
|
|
@@ -22,6 +25,14 @@ export interface LiveQueryDriver {
|
|
|
22
25
|
export interface LiveQuerySource<out TRow> {
|
|
23
26
|
readonly query: LiveQueryInput;
|
|
24
27
|
execute(signal?: AbortSignal): Promise<readonly TRow[]>;
|
|
28
|
+
/**
|
|
29
|
+
* Turns a result the engine delivered into the adapter's rows. With it, the query subscribes
|
|
30
|
+
* for results rather than invalidations: the engine executes or patches the statement where
|
|
31
|
+
* the data is, compares, and hands over a changed result once — over a worker channel, as
|
|
32
|
+
* one columnar transfer — and `execute` is never called after the statement is registered.
|
|
33
|
+
* Without it, an invalidation is followed by `execute`, which the engine's memo serves.
|
|
34
|
+
*/
|
|
35
|
+
decode?(result: QueryResult): readonly TRow[] | Promise<readonly TRow[]>;
|
|
25
36
|
}
|
|
26
37
|
export type LiveSnapshot<TRow> = {
|
|
27
38
|
readonly status: "loading";
|
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
import { sameLiveValue } from "./live-equal.js";
|
|
2
|
-
function
|
|
3
|
-
|
|
2
|
+
function reconcileRows(previous, next, retained) {
|
|
3
|
+
const rows = new Array(next.length);
|
|
4
|
+
let changed = previous.length !== next.length;
|
|
5
|
+
const provenance = retained?.length === next.length ? retained : void 0;
|
|
6
|
+
for (let index = 0; index < next.length; index += 1) {
|
|
7
|
+
const row = next[index];
|
|
8
|
+
const was = provenance?.[index] ?? -1;
|
|
9
|
+
if (was >= 0 && was < previous.length && sameLiveValue(previous[was], row)) {
|
|
10
|
+
rows[index] = previous[was];
|
|
11
|
+
if (was !== index)
|
|
12
|
+
changed = true;
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
const before = previous[index];
|
|
16
|
+
if (index < previous.length && sameLiveValue(before, row))
|
|
17
|
+
rows[index] = before;
|
|
18
|
+
else {
|
|
19
|
+
rows[index] = row;
|
|
20
|
+
changed = true;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return changed ? Object.freeze(rows) : void 0;
|
|
4
24
|
}
|
|
5
25
|
class LiveQuery {
|
|
6
26
|
#listeners = /* @__PURE__ */ new Set();
|
|
@@ -11,10 +31,14 @@ class LiveQuery {
|
|
|
11
31
|
#subscription;
|
|
12
32
|
#observationGeneration = 0;
|
|
13
33
|
#queued;
|
|
34
|
+
#lastDelivered;
|
|
35
|
+
#deliveriesReceived = 0;
|
|
36
|
+
#rowsFromDelivery = 0;
|
|
14
37
|
#execution;
|
|
15
38
|
#executionAbort;
|
|
16
39
|
#invalidationSequence = 0;
|
|
17
40
|
#closed = false;
|
|
41
|
+
#refreshLeases = 0;
|
|
18
42
|
constructor(backend, source, onClose) {
|
|
19
43
|
this.#backend = backend;
|
|
20
44
|
this.#source = source;
|
|
@@ -34,27 +58,48 @@ class LiveQuery {
|
|
|
34
58
|
return;
|
|
35
59
|
subscribed = false;
|
|
36
60
|
this.#listeners.delete(registered);
|
|
37
|
-
if (this.#listeners.size === 0)
|
|
61
|
+
if (this.#listeners.size === 0 && this.#refreshLeases === 0)
|
|
38
62
|
this.#stopObservation();
|
|
39
63
|
};
|
|
40
64
|
};
|
|
41
65
|
async refresh() {
|
|
42
66
|
if (this.#closed)
|
|
43
67
|
throw new Error("Live query is closed");
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
this.#
|
|
68
|
+
this.#refreshLeases += 1;
|
|
69
|
+
try {
|
|
70
|
+
const sequence = this.#invalidationSequence;
|
|
71
|
+
if (this.#listeners.size > 0 || this.#decodes()) {
|
|
72
|
+
if (this.#subscription === void 0)
|
|
73
|
+
this.#startObservation();
|
|
74
|
+
const opening = this.#subscription;
|
|
75
|
+
if (opening !== void 0)
|
|
76
|
+
await opening.catch(() => void 0);
|
|
77
|
+
await this.#backend.refresh();
|
|
78
|
+
}
|
|
79
|
+
if (this.#invalidationSequence === sequence) {
|
|
80
|
+
const version = this.#snapshot.status === "loading" ? null : this.#snapshot.version;
|
|
81
|
+
if (this.#decodes()) {
|
|
82
|
+
const last = this.#lastDelivered;
|
|
83
|
+
if (this.#snapshot.status === "error" && last !== void 0)
|
|
84
|
+
this.#schedule(last);
|
|
85
|
+
} else
|
|
86
|
+
this.#schedule({ manifestVersion: version, catalogEpoch: 0, initial: false });
|
|
87
|
+
}
|
|
88
|
+
await this.#waitForIdle();
|
|
89
|
+
} finally {
|
|
90
|
+
this.#refreshLeases -= 1;
|
|
91
|
+
if (this.#refreshLeases === 0 && this.#listeners.size === 0)
|
|
92
|
+
this.#stopObservation();
|
|
56
93
|
}
|
|
57
|
-
|
|
94
|
+
}
|
|
95
|
+
#decodes() {
|
|
96
|
+
return this.#source.decode !== void 0 && this.#backend.subscribe !== void 0;
|
|
97
|
+
}
|
|
98
|
+
async #decodeDelivered(result) {
|
|
99
|
+
const source = this.#source;
|
|
100
|
+
if (source.decode === void 0)
|
|
101
|
+
throw new TypeError("Live query source lost its decoder");
|
|
102
|
+
return source.decode(result);
|
|
58
103
|
}
|
|
59
104
|
close() {
|
|
60
105
|
if (this.#closed)
|
|
@@ -75,7 +120,36 @@ class LiveQuery {
|
|
|
75
120
|
if (this.#subscription !== void 0 || this.#closed)
|
|
76
121
|
return;
|
|
77
122
|
const generation = this.#observationGeneration += 1;
|
|
123
|
+
if (this.#source.decode !== void 0 && this.#backend.subscribe !== void 0) {
|
|
124
|
+
const subscription2 = this.#backend.subscribe(this.#source.query, {
|
|
125
|
+
onChange: (result, delivery) => {
|
|
126
|
+
if (generation !== this.#observationGeneration || this.#closed)
|
|
127
|
+
return;
|
|
128
|
+
this.#deliveriesReceived += 1;
|
|
129
|
+
this.#schedule({ result, delivery, sequence: this.#deliveriesReceived });
|
|
130
|
+
},
|
|
131
|
+
onError: (error) => {
|
|
132
|
+
if (generation !== this.#observationGeneration || this.#closed)
|
|
133
|
+
return;
|
|
134
|
+
this.#setError(error, this.#currentVersion());
|
|
135
|
+
},
|
|
136
|
+
onComplete: () => {
|
|
137
|
+
if (generation !== this.#observationGeneration)
|
|
138
|
+
return;
|
|
139
|
+
this.#subscription = void 0;
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
this.#subscription = subscription2;
|
|
143
|
+
subscription2.catch((error) => {
|
|
144
|
+
if (generation !== this.#observationGeneration || this.#closed)
|
|
145
|
+
return;
|
|
146
|
+
this.#subscription = void 0;
|
|
147
|
+
this.#setError(error, this.#currentVersion());
|
|
148
|
+
});
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
78
151
|
const subscription = this.#backend.observe(this.#source.query, {
|
|
152
|
+
suppressUnchanged: true,
|
|
79
153
|
onInvalidate: (invalidation) => {
|
|
80
154
|
if (generation !== this.#observationGeneration || this.#closed)
|
|
81
155
|
return;
|
|
@@ -109,9 +183,9 @@ class LiveQuery {
|
|
|
109
183
|
void subscription.then((handle) => handle.close()).catch(() => void 0);
|
|
110
184
|
}
|
|
111
185
|
}
|
|
112
|
-
#schedule(
|
|
186
|
+
#schedule(work) {
|
|
113
187
|
this.#invalidationSequence += 1;
|
|
114
|
-
this.#queued =
|
|
188
|
+
this.#queued = work;
|
|
115
189
|
if (this.#execution !== void 0)
|
|
116
190
|
return;
|
|
117
191
|
const execution = this.#drain();
|
|
@@ -125,24 +199,35 @@ class LiveQuery {
|
|
|
125
199
|
}
|
|
126
200
|
async #drain() {
|
|
127
201
|
while (this.#queued !== void 0 && !this.#closed) {
|
|
128
|
-
const
|
|
202
|
+
const work = this.#queued;
|
|
129
203
|
this.#queued = void 0;
|
|
130
204
|
const abort = new AbortController();
|
|
131
205
|
this.#executionAbort = abort;
|
|
206
|
+
const invalidation = "result" in work ? work.delivery : work;
|
|
132
207
|
try {
|
|
133
|
-
|
|
208
|
+
let executed;
|
|
209
|
+
if ("result" in work) {
|
|
210
|
+
this.#lastDelivered = work;
|
|
211
|
+
executed = await this.#decodeDelivered(work.result);
|
|
212
|
+
this.#lastDelivered = void 0;
|
|
213
|
+
} else
|
|
214
|
+
executed = await this.#source.execute(abort.signal);
|
|
134
215
|
if (this.#executionWasCancelled(abort))
|
|
135
216
|
continue;
|
|
136
217
|
if (this.#hasQueuedInvalidation())
|
|
137
218
|
continue;
|
|
138
219
|
const previous = this.#snapshot.rows;
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
220
|
+
const provenance = "result" in work && this.#rowsFromDelivery === work.sequence - 1 ? work.delivery.retained : void 0;
|
|
221
|
+
const rows = reconcileRows(previous, executed, provenance);
|
|
222
|
+
if ("result" in work)
|
|
223
|
+
this.#rowsFromDelivery = work.sequence;
|
|
224
|
+
if (rows === void 0) {
|
|
225
|
+
if (this.#snapshot.status === "ready" && this.#snapshot.version === invalidation.manifestVersion) {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
143
228
|
this.#snapshot = {
|
|
144
229
|
status: "ready",
|
|
145
|
-
rows: previous,
|
|
230
|
+
rows: this.#snapshot.status === "loading" ? Object.freeze([...previous]) : previous,
|
|
146
231
|
version: invalidation.manifestVersion
|
|
147
232
|
};
|
|
148
233
|
} else {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { QueryResult, WindowSpec } from "../plan/model.js";
|
|
2
|
+
import { QueryMemoryContext } from "./memory.js";
|
|
3
|
+
interface WindowOptions {
|
|
4
|
+
copyRows?: boolean;
|
|
5
|
+
memoryContext?: QueryMemoryContext;
|
|
6
|
+
signal?: AbortSignal;
|
|
7
|
+
}
|
|
8
|
+
/** Synchronous executor used by standalone queries; shares every kernel with asynchronous reads. */
|
|
9
|
+
export declare function applyWindowFunctions(result: QueryResult, windows: readonly WindowSpec[], options?: WindowOptions): QueryResult;
|
|
10
|
+
/** Cooperatively runs window passes so cancellation can arrive while a large partition is active. */
|
|
11
|
+
export declare function applyWindowFunctionsAsync(result: QueryResult, windows: readonly WindowSpec[], options?: WindowOptions): Promise<QueryResult>;
|
|
12
|
+
export {};
|