@minnowdb/core 0.7.10 → 0.9.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/artifact-cache.js +3 -2
- package/dist/engine/buffered-writer.js +12 -2
- package/dist/engine/client.d.ts +9 -0
- package/dist/engine/client.js +112 -20
- package/dist/engine/database.d.ts +5 -3
- package/dist/engine/database.js +2199 -1675
- package/dist/engine/errors.d.ts +21 -2
- package/dist/engine/errors.js +30 -1
- package/dist/engine/index.d.ts +1 -0
- package/dist/engine/index.js +4 -0
- package/dist/engine/live-accept.js +13 -0
- package/dist/engine/live-aggregate.js +264 -0
- package/dist/engine/live-patch.d.ts +21 -0
- package/dist/engine/live-patch.js +31 -0
- package/dist/engine/live.d.ts +14 -0
- package/dist/engine/live.js +146 -142
- package/dist/engine/optimizer.js +11 -6
- package/dist/engine/query-cache.js +19 -15
- package/dist/engine/query-generations.js +61 -0
- package/dist/engine/query-identity.js +41 -0
- package/dist/engine/query.d.ts +7 -13
- package/dist/engine/query.js +298 -435
- package/dist/engine/result-state.d.ts +7 -0
- package/dist/engine/result-state.js +15 -0
- package/dist/engine/sql-domains.js +121 -0
- package/dist/engine/sql-semantics.js +7 -4
- package/dist/engine/typed-live.js +28 -20
- package/dist/engine/vector.d.ts +4 -0
- package/dist/engine/vector.js +14 -14
- package/dist/engine/windows.d.ts +12 -0
- package/dist/engine/windows.js +387 -0
- package/dist/engine/worker-server.d.ts +1 -1
- package/dist/engine/worker-server.js +28 -15
- package/dist/engine/write-coordinator.js +54 -0
- package/dist/plan/model.d.ts +1 -1
- package/dist/storage/indexeddb.js +134 -89
- package/dist/storage/opfs/index.d.ts +1 -1
- package/dist/storage/opfs/index.js +3 -1
- package/dist/storage/opfs/leader.js +20 -9
- package/dist/storage/opfs/rpc.js +3 -1
- package/dist/storage/opfs/store.d.ts +2 -0
- package/dist/storage/opfs/store.js +119 -43
- package/dist/storage/toolkit/record-core.js +2 -1
- package/dist/storage/types.d.ts +14 -0
- package/dist/storage/types.js +21 -0
- package/dist/transactions/index.d.ts +3 -0
- package/dist/transactions/index.js +4 -1
- package/dist/worker-protocol/index.d.ts +1 -1
- package/dist/worker-protocol/index.js +1 -1
- package/package.json +2 -2
- package/postgres-feature-profile.json +6 -1
- package/sql-feature-matrix.json +20 -27
- package/dist/date-value.d.ts +0 -20
- package/dist/engine/artifact-cache.d.ts +0 -29
- package/dist/engine/byte-estimates.d.ts +0 -11
- package/dist/engine/cancellation.d.ts +0 -2
- package/dist/engine/defaults.d.ts +0 -29
- package/dist/engine/group-index.d.ts +0 -33
- package/dist/engine/join-index.d.ts +0 -10
- package/dist/engine/live-equal.d.ts +0 -7
- package/dist/engine/point-read.d.ts +0 -59
- package/dist/engine/query-cache.d.ts +0 -21
- package/dist/engine/result-wire.d.ts +0 -70
- package/dist/engine/sort-keys.d.ts +0 -73
- package/dist/engine/sql-domains.d.ts +0 -93
- package/dist/engine/sql-functions.d.ts +0 -11
- package/dist/engine/sql-json.d.ts +0 -40
- package/dist/engine/sql-semantics.d.ts +0 -66
- package/dist/engine/worker-store-indexeddb.d.ts +0 -2
- package/dist/engine/worker-store-memory.d.ts +0 -2
- package/dist/engine/worker-store-opfs.d.ts +0 -2
- package/dist/engine/write-block-planner.d.ts +0 -19
- package/dist/storage/opfs/leader.d.ts +0 -460
- package/dist/storage/opfs/rpc.d.ts +0 -82
- package/dist/storage/opfs/snapshot-ledger.d.ts +0 -41
package/dist/engine/live.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import { createLiveQueryPatch } from "./live-patch.js";
|
|
2
|
+
import { acceptLiveExecution } from "./live-accept.js";
|
|
3
|
+
import { copyQueryResult, queryResultRetainedBytes, sameQueryRow } from "./query-cache.js";
|
|
4
|
+
import { encodeQueryIdentity } from "./query-identity.js";
|
|
5
|
+
import { OpfsCoordinationError } from "../storage/types.js";
|
|
1
6
|
import { LiveQueryLimitError } from "./errors.js";
|
|
2
7
|
const DEFAULT_LIVE_QUERY_MAX_GROUPS = 256;
|
|
3
8
|
const DEFAULT_LIVE_QUERY_MAX_SUBSCRIPTIONS = 1024;
|
|
@@ -6,12 +11,6 @@ const MAX_LIVE_QUERY_SUBSCRIPTIONS = 16384;
|
|
|
6
11
|
const MAX_LIVE_QUERY_SETS_PER_DATABASE = 256;
|
|
7
12
|
const LIVE_QUERY_EXECUTION_CONCURRENCY = 8;
|
|
8
13
|
import { LiveQueryLimitError as LiveQueryLimitError2 } from "./errors.js";
|
|
9
|
-
function sameQueryValue(left, right) {
|
|
10
|
-
if (left instanceof Date || right instanceof Date) {
|
|
11
|
-
return left instanceof Date && right instanceof Date && Object.is(dateMilliseconds(left), dateMilliseconds(right));
|
|
12
|
-
}
|
|
13
|
-
return Object.is(left, right);
|
|
14
|
-
}
|
|
15
14
|
function sameResult(left, right) {
|
|
16
15
|
if (left.columns.length !== right.columns.length || left.rows.length !== right.rows.length) {
|
|
17
16
|
return false;
|
|
@@ -29,75 +28,11 @@ function sameResult(left, right) {
|
|
|
29
28
|
const rightRow = right.rows[rowIndex];
|
|
30
29
|
if (leftRow === void 0 || rightRow === void 0)
|
|
31
30
|
return false;
|
|
32
|
-
if (leftRow
|
|
33
|
-
|
|
34
|
-
for (const column of columns) {
|
|
35
|
-
if (!sameQueryValue(leftRow[column] ?? null, rightRow[column] ?? null))
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
31
|
+
if (!sameQueryRow(leftRow, rightRow, columns))
|
|
32
|
+
return false;
|
|
38
33
|
}
|
|
39
34
|
return true;
|
|
40
35
|
}
|
|
41
|
-
function cloneRow(row, columns) {
|
|
42
|
-
const cloned = {};
|
|
43
|
-
for (const column of columns) {
|
|
44
|
-
const value = row[column] ?? null;
|
|
45
|
-
const copy = value instanceof Date ? copyDate(value) : value;
|
|
46
|
-
if (column === "__proto__") {
|
|
47
|
-
Object.defineProperty(cloned, column, {
|
|
48
|
-
value: copy,
|
|
49
|
-
enumerable: true,
|
|
50
|
-
configurable: true,
|
|
51
|
-
writable: true
|
|
52
|
-
});
|
|
53
|
-
} else
|
|
54
|
-
cloned[column] = copy;
|
|
55
|
-
}
|
|
56
|
-
return cloned;
|
|
57
|
-
}
|
|
58
|
-
function cloneResult(result) {
|
|
59
|
-
const columns = [...result.columns];
|
|
60
|
-
return {
|
|
61
|
-
columns,
|
|
62
|
-
columnDomains: structuredClone(result.columnDomains),
|
|
63
|
-
rows: result.rows.map((row) => cloneRow(row, columns))
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
function encodeQueryIdentity(value, ancestors = /* @__PURE__ */ new Set()) {
|
|
67
|
-
if (value === null)
|
|
68
|
-
return "z";
|
|
69
|
-
if (typeof value === "undefined")
|
|
70
|
-
return "u";
|
|
71
|
-
if (typeof value === "boolean")
|
|
72
|
-
return value ? "b1" : "b0";
|
|
73
|
-
if (typeof value === "number") {
|
|
74
|
-
if (Number.isNaN(value))
|
|
75
|
-
return "nNaN;";
|
|
76
|
-
if (Object.is(value, -0))
|
|
77
|
-
return "n-0;";
|
|
78
|
-
return `n${String(value)};`;
|
|
79
|
-
}
|
|
80
|
-
if (typeof value === "string")
|
|
81
|
-
return `s${String(value.length)}:${value}`;
|
|
82
|
-
if (value instanceof Date)
|
|
83
|
-
return `d${String(dateMilliseconds(value))};`;
|
|
84
|
-
if (typeof value !== "object") {
|
|
85
|
-
throw new TypeError(`Unsupported live-query identity value: ${typeof value}`);
|
|
86
|
-
}
|
|
87
|
-
if (ancestors.has(value))
|
|
88
|
-
throw new TypeError("Live-query identity contains a cycle");
|
|
89
|
-
ancestors.add(value);
|
|
90
|
-
let encoded;
|
|
91
|
-
if (Array.isArray(value)) {
|
|
92
|
-
encoded = `a${String(value.length)}[${value.map((item) => encodeQueryIdentity(item, ancestors)).join("")}]`;
|
|
93
|
-
} else {
|
|
94
|
-
const record = value;
|
|
95
|
-
const keys = Object.keys(record).sort();
|
|
96
|
-
encoded = `o${String(keys.length)}{${keys.map((key) => `${encodeQueryIdentity(key, ancestors)}${encodeQueryIdentity(record[key], ancestors)}`).join("")}}`;
|
|
97
|
-
}
|
|
98
|
-
ancestors.delete(value);
|
|
99
|
-
return encoded;
|
|
100
|
-
}
|
|
101
36
|
function queryKey(query) {
|
|
102
37
|
return encodeQueryIdentity(typeof query === "string" ? ["sql", query] : query.kind === "sql-query" ? ["sql-query", query.sql, query.params] : ["typed-query", query.plan]);
|
|
103
38
|
}
|
|
@@ -134,13 +69,19 @@ function groupExecutes(group) {
|
|
|
134
69
|
}
|
|
135
70
|
return false;
|
|
136
71
|
}
|
|
137
|
-
function groupMemoizes(group) {
|
|
72
|
+
function groupMemoizes(group, changed = true) {
|
|
138
73
|
for (const subscriber of group.subscribers) {
|
|
139
|
-
if (!subscriber.closed && subscriber.kind === "observer")
|
|
74
|
+
if (!subscriber.closed && subscriber.kind === "observer" && (changed || subscriber.options.suppressUnchanged !== true))
|
|
140
75
|
return true;
|
|
141
76
|
}
|
|
142
77
|
return false;
|
|
143
78
|
}
|
|
79
|
+
function callLiveCallback(callback) {
|
|
80
|
+
try {
|
|
81
|
+
callback?.();
|
|
82
|
+
} catch {
|
|
83
|
+
}
|
|
84
|
+
}
|
|
144
85
|
class LiveQuerySet {
|
|
145
86
|
#host;
|
|
146
87
|
#channel;
|
|
@@ -153,6 +94,7 @@ class LiveQuerySet {
|
|
|
153
94
|
#lagging = /* @__PURE__ */ new Set();
|
|
154
95
|
#maxGroups;
|
|
155
96
|
#maxSubscriptions;
|
|
97
|
+
#maxRetainedBytes;
|
|
156
98
|
#sharedResults;
|
|
157
99
|
#incremental;
|
|
158
100
|
#stats = {
|
|
@@ -167,6 +109,7 @@ class LiveQuerySet {
|
|
|
167
109
|
maintained: 0,
|
|
168
110
|
groupsVisited: 0,
|
|
169
111
|
retainedRows: 0,
|
|
112
|
+
retainedBytes: 0,
|
|
170
113
|
sharedExecutions: 0,
|
|
171
114
|
lastSweepMs: 0
|
|
172
115
|
};
|
|
@@ -176,9 +119,12 @@ class LiveQuerySet {
|
|
|
176
119
|
#executionWaiters = [];
|
|
177
120
|
#sweepChain = Promise.resolve();
|
|
178
121
|
#sweepQueued = false;
|
|
122
|
+
#retryTimer;
|
|
123
|
+
#retryDelayMs = 100;
|
|
179
124
|
#pollTimer;
|
|
180
125
|
#closed = false;
|
|
181
126
|
#subscriptionCount = 0;
|
|
127
|
+
#retainedBytes = 0;
|
|
182
128
|
#ownsChannel;
|
|
183
129
|
#onClosed;
|
|
184
130
|
constructor(host, options = {}) {
|
|
@@ -187,6 +133,9 @@ class LiveQuerySet {
|
|
|
187
133
|
}
|
|
188
134
|
this.#maxGroups = boundedLiveLimit(options.maxGroups ?? DEFAULT_LIVE_QUERY_MAX_GROUPS, MAX_LIVE_QUERY_GROUPS, "group");
|
|
189
135
|
this.#maxSubscriptions = boundedLiveLimit(options.maxSubscriptions ?? DEFAULT_LIVE_QUERY_MAX_SUBSCRIPTIONS, MAX_LIVE_QUERY_SUBSCRIPTIONS, "subscription");
|
|
136
|
+
this.#maxRetainedBytes = options.maxRetainedBytes ?? 64 * 1024 * 1024;
|
|
137
|
+
if (!Number.isSafeInteger(this.#maxRetainedBytes) || this.#maxRetainedBytes < 0)
|
|
138
|
+
throw new RangeError("Live query retained byte limit must be a non-negative safe integer");
|
|
190
139
|
this.#sharedResults = options.sharedResults === true;
|
|
191
140
|
this.#incremental = options.incremental !== false;
|
|
192
141
|
this.#host = host;
|
|
@@ -212,7 +161,7 @@ class LiveQuerySet {
|
|
|
212
161
|
let retainedRows = 0;
|
|
213
162
|
for (const group of this.#groups.values())
|
|
214
163
|
retainedRows += group.result?.rows.length ?? 0;
|
|
215
|
-
return { ...this.#stats, retainedRows };
|
|
164
|
+
return { ...this.#stats, retainedRows, retainedBytes: this.#retainedBytes };
|
|
216
165
|
}
|
|
217
166
|
#freshProbe() {
|
|
218
167
|
let pending = this.#pendingProbe;
|
|
@@ -236,72 +185,59 @@ class LiveQuerySet {
|
|
|
236
185
|
}
|
|
237
186
|
this.#subscriptionCount += 1;
|
|
238
187
|
}
|
|
239
|
-
|
|
240
|
-
this.#
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
group.subscribers.add(subscriber);
|
|
256
|
-
await this.#settleGroup(group, opened.fresh, true);
|
|
257
|
-
this.#throwIfClosed();
|
|
258
|
-
if (!subscriber.delivered) {
|
|
259
|
-
const result = group.result ?? (await this.#executeGroup(group)).result;
|
|
260
|
-
this.#throwIfClosed();
|
|
261
|
-
if (!subscriber.closed) {
|
|
262
|
-
this.#deliverResult(group, subscriber, result, { ...group.seenProbe, initial: true });
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
} catch (error) {
|
|
266
|
-
if (group !== void 0 && subscriber !== void 0) {
|
|
267
|
-
this.#removeSubscriber(group, subscriber, false);
|
|
268
|
-
} else {
|
|
269
|
-
this.#subscriptionCount -= 1;
|
|
270
|
-
if (group !== void 0)
|
|
271
|
-
this.#removeEmptyGroup(group);
|
|
272
|
-
}
|
|
273
|
-
throw error;
|
|
274
|
-
}
|
|
275
|
-
return this.#subscriptionHandle(group, subscriber);
|
|
188
|
+
subscribe(query, options) {
|
|
189
|
+
return this.#register(query, {
|
|
190
|
+
kind: "result",
|
|
191
|
+
options,
|
|
192
|
+
delivered: false,
|
|
193
|
+
closed: false,
|
|
194
|
+
seenDelivery: -1
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
subscribePatches(query, options) {
|
|
198
|
+
return this.subscribe(query, {
|
|
199
|
+
sharedResults: true,
|
|
200
|
+
onChange: (result, delivery) => options.onPatch(createLiveQueryPatch(result, delivery), delivery),
|
|
201
|
+
...options.onError === void 0 ? {} : { onError: options.onError.bind(options) },
|
|
202
|
+
...options.onComplete === void 0 ? {} : { onComplete: options.onComplete.bind(options) }
|
|
203
|
+
});
|
|
276
204
|
}
|
|
277
|
-
|
|
205
|
+
observe(query, options) {
|
|
206
|
+
return this.#register(query, { kind: "observer", options, delivered: false, closed: false });
|
|
207
|
+
}
|
|
208
|
+
async #register(query, subscriber) {
|
|
278
209
|
this.#throwIfClosed();
|
|
279
210
|
this.#reserveSubscription();
|
|
280
211
|
let group;
|
|
281
|
-
let
|
|
212
|
+
let installed = false;
|
|
282
213
|
try {
|
|
283
214
|
const opened = await this.#getOrOpenGroup(query);
|
|
284
215
|
group = opened.group;
|
|
285
216
|
this.#throwIfClosed();
|
|
286
|
-
subscriber = {
|
|
287
|
-
kind: "observer",
|
|
288
|
-
options,
|
|
289
|
-
delivered: false,
|
|
290
|
-
closed: false
|
|
291
|
-
};
|
|
292
217
|
group.subscribers.add(subscriber);
|
|
293
|
-
|
|
218
|
+
group.pendingSubscriptions -= 1;
|
|
219
|
+
installed = true;
|
|
220
|
+
await this.#settleGroup(group, opened.fresh, subscriber.kind === "result" || subscriber.options.suppressUnchanged === true);
|
|
294
221
|
this.#throwIfClosed();
|
|
295
222
|
if (!subscriber.delivered) {
|
|
296
|
-
|
|
223
|
+
if (subscriber.kind === "observer")
|
|
224
|
+
this.#deliverInvalidation(subscriber, { ...group.seenProbe, initial: true });
|
|
225
|
+
else {
|
|
226
|
+
const result = group.result ?? (await this.#executeGroup(group)).result;
|
|
227
|
+
this.#throwIfClosed();
|
|
228
|
+
if (!subscriber.closed)
|
|
229
|
+
this.#deliverResult(group, subscriber, result, { ...group.seenProbe, initial: true });
|
|
230
|
+
}
|
|
297
231
|
}
|
|
298
232
|
} catch (error) {
|
|
299
|
-
if (group !== void 0 &&
|
|
233
|
+
if (group !== void 0 && installed) {
|
|
300
234
|
this.#removeSubscriber(group, subscriber, false);
|
|
301
235
|
} else {
|
|
302
236
|
this.#subscriptionCount -= 1;
|
|
303
|
-
if (group !== void 0)
|
|
237
|
+
if (group !== void 0) {
|
|
238
|
+
group.pendingSubscriptions -= 1;
|
|
304
239
|
this.#removeEmptyGroup(group);
|
|
240
|
+
}
|
|
305
241
|
}
|
|
306
242
|
throw error;
|
|
307
243
|
}
|
|
@@ -337,30 +273,39 @@ class LiveQuerySet {
|
|
|
337
273
|
subscriber.closed = true;
|
|
338
274
|
this.#subscriptionCount -= 1;
|
|
339
275
|
group.subscribers.delete(subscriber);
|
|
340
|
-
if (complete)
|
|
341
|
-
subscriber.options.onComplete?.();
|
|
342
276
|
this.#removeEmptyGroup(group);
|
|
277
|
+
if (complete)
|
|
278
|
+
callLiveCallback(() => subscriber.options.onComplete?.());
|
|
343
279
|
}
|
|
344
280
|
#removeEmptyGroup(group) {
|
|
345
|
-
if (group.subscribers.size !== 0)
|
|
281
|
+
if (group.subscribers.size !== 0 || group.pendingSubscriptions !== 0)
|
|
346
282
|
return;
|
|
347
283
|
if (this.#groups.get(group.key) !== group)
|
|
348
284
|
return;
|
|
349
285
|
this.#groups.delete(group.key);
|
|
286
|
+
this.#retainedBytes -= group.retainedBytes;
|
|
287
|
+
group.retainedBytes = 0;
|
|
288
|
+
group.result = void 0;
|
|
289
|
+
group.maintenance = void 0;
|
|
350
290
|
this.#lagging.delete(group);
|
|
351
291
|
this.#unindexGroup(group, group.dependencies);
|
|
352
292
|
}
|
|
353
293
|
async #getOrOpenGroup(query) {
|
|
294
|
+
if (typeof query !== "string")
|
|
295
|
+
query = structuredClone(query);
|
|
354
296
|
const key = queryKey(query);
|
|
355
297
|
const existing = this.#groups.get(key);
|
|
356
298
|
if (existing !== void 0) {
|
|
357
299
|
this.#stats.sharedExecutions += 1;
|
|
300
|
+
existing.pendingSubscriptions += 1;
|
|
358
301
|
return { group: existing, fresh: false };
|
|
359
302
|
}
|
|
360
303
|
const opening = this.#opening.get(key);
|
|
361
304
|
if (opening !== void 0) {
|
|
362
305
|
this.#stats.sharedExecutions += 1;
|
|
363
|
-
|
|
306
|
+
const group = await opening;
|
|
307
|
+
group.pendingSubscriptions += 1;
|
|
308
|
+
return { group, fresh: false };
|
|
364
309
|
}
|
|
365
310
|
if (this.#groups.size + this.#opening.size >= this.#maxGroups) {
|
|
366
311
|
throw new LiveQueryLimitError("group", this.#maxGroups);
|
|
@@ -368,7 +313,9 @@ class LiveQuerySet {
|
|
|
368
313
|
const created = this.#openGroup(key, query);
|
|
369
314
|
this.#opening.set(key, created);
|
|
370
315
|
try {
|
|
371
|
-
|
|
316
|
+
const group = await created;
|
|
317
|
+
group.pendingSubscriptions += 1;
|
|
318
|
+
return { group, fresh: true };
|
|
372
319
|
} finally {
|
|
373
320
|
this.#opening.delete(key);
|
|
374
321
|
}
|
|
@@ -380,8 +327,10 @@ class LiveQuerySet {
|
|
|
380
327
|
query,
|
|
381
328
|
dependencies,
|
|
382
329
|
subscribers: /* @__PURE__ */ new Set(),
|
|
330
|
+
pendingSubscriptions: 0,
|
|
383
331
|
seenProbe: after,
|
|
384
332
|
result: void 0,
|
|
333
|
+
retainedBytes: 0,
|
|
385
334
|
execution: void 0,
|
|
386
335
|
deliveries: 0,
|
|
387
336
|
maintenance: void 0,
|
|
@@ -445,14 +394,18 @@ class LiveQuerySet {
|
|
|
445
394
|
await this.#acquireExecutionSlot();
|
|
446
395
|
let executed;
|
|
447
396
|
let maintenance;
|
|
397
|
+
let retainedBytes;
|
|
398
|
+
let candidate;
|
|
448
399
|
try {
|
|
449
400
|
if (this.#incremental && this.#host.executeMaintainable !== void 0 && !group.unmaintainable) {
|
|
450
401
|
const maintained = await this.#host.executeMaintainable(group.query, context);
|
|
451
402
|
if (maintained === void 0)
|
|
452
403
|
group.unmaintainable = true;
|
|
453
404
|
else {
|
|
405
|
+
candidate = maintained;
|
|
454
406
|
executed = maintained.result;
|
|
455
407
|
maintenance = maintained.state;
|
|
408
|
+
retainedBytes = maintained.retainedBytes;
|
|
456
409
|
}
|
|
457
410
|
}
|
|
458
411
|
executed ??= await this.#host.execute(group.query, context);
|
|
@@ -461,8 +414,9 @@ class LiveQuerySet {
|
|
|
461
414
|
}
|
|
462
415
|
const previous = group.result;
|
|
463
416
|
const changed = previous === void 0 || !sameResult(previous, executed);
|
|
464
|
-
group
|
|
465
|
-
group
|
|
417
|
+
this.#retain(group, executed, maintenance, retainedBytes, candidate);
|
|
418
|
+
if (candidate !== void 0 && context !== void 0 && groupMemoizes(group, changed))
|
|
419
|
+
await this.#memoizeGroup(group, executed, context.probe);
|
|
466
420
|
return { result: executed, changed };
|
|
467
421
|
})();
|
|
468
422
|
group.execution = execution;
|
|
@@ -473,6 +427,28 @@ class LiveQuerySet {
|
|
|
473
427
|
group.execution = void 0;
|
|
474
428
|
}
|
|
475
429
|
}
|
|
430
|
+
async #memoizeGroup(group, result, probe) {
|
|
431
|
+
if (this.#closed || this.#groups.get(group.key) !== group)
|
|
432
|
+
return;
|
|
433
|
+
try {
|
|
434
|
+
await this.#host.memoize?.(group.query, result, probe);
|
|
435
|
+
} catch {
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
#retain(group, result, state, hint, candidate) {
|
|
439
|
+
if (this.#closed || this.#groups.get(group.key) !== group)
|
|
440
|
+
return;
|
|
441
|
+
const bytes = hint ?? (result === group.result && state === group.maintenance ? group.retainedBytes : queryResultRetainedBytes(result) + result.rows.length * 48);
|
|
442
|
+
const total = this.#retainedBytes - group.retainedBytes + bytes;
|
|
443
|
+
if (!Number.isSafeInteger(bytes) || bytes < 0 || !Number.isSafeInteger(total) || total > this.#maxRetainedBytes)
|
|
444
|
+
throw new LiveQueryLimitError("byte", this.#maxRetainedBytes);
|
|
445
|
+
if (candidate !== void 0)
|
|
446
|
+
acceptLiveExecution(candidate);
|
|
447
|
+
group.result = result;
|
|
448
|
+
group.maintenance = state;
|
|
449
|
+
group.retainedBytes = bytes;
|
|
450
|
+
this.#retainedBytes = total;
|
|
451
|
+
}
|
|
476
452
|
async #acquireExecutionSlot() {
|
|
477
453
|
if (this.#executing < LIVE_QUERY_EXECUTION_CONCURRENCY) {
|
|
478
454
|
this.#executing += 1;
|
|
@@ -509,8 +485,9 @@ class LiveQuerySet {
|
|
|
509
485
|
verdict.declined = true;
|
|
510
486
|
return { result: retained, changed: false };
|
|
511
487
|
}
|
|
512
|
-
group.result
|
|
513
|
-
group
|
|
488
|
+
this.#retain(group, maintained.result, maintained.state, maintained.retainedBytes, maintained);
|
|
489
|
+
if (groupMemoizes(group, maintained.changed))
|
|
490
|
+
await this.#memoizeGroup(group, maintained.result, probe);
|
|
514
491
|
return {
|
|
515
492
|
result: maintained.result,
|
|
516
493
|
changed: maintained.changed,
|
|
@@ -531,7 +508,7 @@ class LiveQuerySet {
|
|
|
531
508
|
return;
|
|
532
509
|
subscriber.delivered = true;
|
|
533
510
|
const consecutive = subscriber.seenDelivery === group.deliveries - 1;
|
|
534
|
-
subscriber.options.onChange(this.#sharedResults ? result :
|
|
511
|
+
subscriber.options.onChange(this.#sharedResults || subscriber.options.sharedResults === true ? result : copyQueryResult(result), retained !== void 0 && consecutive && !delivery.initial ? { ...delivery, retained } : delivery);
|
|
535
512
|
subscriber.seenDelivery = group.deliveries;
|
|
536
513
|
}
|
|
537
514
|
#deliverInvalidation(subscriber, invalidation) {
|
|
@@ -557,11 +534,17 @@ class LiveQuerySet {
|
|
|
557
534
|
this.#channel?.close?.();
|
|
558
535
|
if (this.#pollTimer !== void 0)
|
|
559
536
|
clearInterval(this.#pollTimer);
|
|
537
|
+
if (this.#retryTimer !== void 0)
|
|
538
|
+
clearTimeout(this.#retryTimer);
|
|
560
539
|
const groups = [...this.#groups.values()];
|
|
561
540
|
this.#groups.clear();
|
|
541
|
+
this.#retainedBytes = 0;
|
|
562
542
|
this.#groupsByTable.clear();
|
|
563
543
|
this.#lagging.clear();
|
|
564
544
|
for (const group of groups) {
|
|
545
|
+
group.result = void 0;
|
|
546
|
+
group.maintenance = void 0;
|
|
547
|
+
group.retainedBytes = 0;
|
|
565
548
|
const subscribers = [...group.subscribers];
|
|
566
549
|
group.subscribers.clear();
|
|
567
550
|
for (const subscriber of subscribers) {
|
|
@@ -569,28 +552,46 @@ class LiveQuerySet {
|
|
|
569
552
|
continue;
|
|
570
553
|
subscriber.closed = true;
|
|
571
554
|
this.#subscriptionCount -= 1;
|
|
572
|
-
subscriber.options.onComplete?.();
|
|
555
|
+
callLiveCallback(() => subscriber.options.onComplete?.());
|
|
573
556
|
}
|
|
574
557
|
}
|
|
575
|
-
this.#onClosed
|
|
558
|
+
callLiveCallback(this.#onClosed);
|
|
576
559
|
}
|
|
577
560
|
#hint() {
|
|
578
561
|
if (this.#closed)
|
|
579
562
|
return;
|
|
580
563
|
this.#stats.hints += 1;
|
|
564
|
+
if (this.#retryTimer !== void 0)
|
|
565
|
+
return;
|
|
581
566
|
if (this.#sweepQueued)
|
|
582
567
|
return;
|
|
583
568
|
this.#sweepQueued = true;
|
|
584
569
|
this.#sweepChain = this.#sweepChain.then(async () => {
|
|
585
570
|
this.#sweepQueued = false;
|
|
571
|
+
if (this.#retryTimer !== void 0)
|
|
572
|
+
return;
|
|
586
573
|
try {
|
|
587
574
|
await this.#sweep();
|
|
575
|
+
this.#retryDelayMs = 100;
|
|
588
576
|
} catch (error) {
|
|
577
|
+
if (error instanceof OpfsCoordinationError) {
|
|
578
|
+
this.#retrySweep();
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
589
581
|
for (const group of this.#groups.values())
|
|
590
582
|
this.#notifyGroupError(group, error);
|
|
591
583
|
}
|
|
592
584
|
});
|
|
593
585
|
}
|
|
586
|
+
#retrySweep() {
|
|
587
|
+
if (this.#closed || this.#groups.size === 0 || this.#retryTimer !== void 0)
|
|
588
|
+
return;
|
|
589
|
+
this.#retryTimer = setTimeout(() => {
|
|
590
|
+
this.#retryTimer = void 0;
|
|
591
|
+
this.#hint();
|
|
592
|
+
}, this.#retryDelayMs);
|
|
593
|
+
this.#retryDelayMs = Math.min(this.#retryDelayMs * 2, 5e3);
|
|
594
|
+
}
|
|
594
595
|
#stillOpen() {
|
|
595
596
|
return !this.#closed;
|
|
596
597
|
}
|
|
@@ -668,6 +669,8 @@ class LiveQuerySet {
|
|
|
668
669
|
await this.#refreshDependencies(group);
|
|
669
670
|
} catch (error) {
|
|
670
671
|
this.#lagging.add(group);
|
|
672
|
+
if (error instanceof OpfsCoordinationError)
|
|
673
|
+
throw error;
|
|
671
674
|
this.#notifyGroupError(group, error);
|
|
672
675
|
return;
|
|
673
676
|
}
|
|
@@ -735,29 +738,31 @@ class LiveQuerySet {
|
|
|
735
738
|
if (isObserver(subscriber)) {
|
|
736
739
|
if (execution !== void 0 && !execution.changed && subscriber.options.suppressUnchanged === true) {
|
|
737
740
|
this.#stats.notificationsSuppressed += 1;
|
|
738
|
-
|
|
741
|
+
continue;
|
|
739
742
|
}
|
|
740
743
|
try {
|
|
741
744
|
this.#deliverInvalidation(subscriber, invalidation);
|
|
742
745
|
this.#stats.invalidations += 1;
|
|
743
746
|
} catch (error) {
|
|
744
|
-
subscriber.options.onError?.(error);
|
|
747
|
+
callLiveCallback(() => subscriber.options.onError?.(error));
|
|
745
748
|
}
|
|
746
749
|
} else if (isResultSubscriber(subscriber) && execution !== void 0) {
|
|
747
750
|
if (!execution.changed) {
|
|
748
751
|
this.#stats.notificationsSuppressed += 1;
|
|
749
|
-
|
|
752
|
+
continue;
|
|
750
753
|
}
|
|
751
754
|
try {
|
|
752
755
|
this.#deliverResult(group, subscriber, execution.result, invalidation, execution.retained);
|
|
753
756
|
} catch (error) {
|
|
754
|
-
subscriber.options.onError?.(error);
|
|
757
|
+
callLiveCallback(() => subscriber.options.onError?.(error));
|
|
755
758
|
}
|
|
756
759
|
}
|
|
757
760
|
}
|
|
758
761
|
this.#settleProbe(group, current);
|
|
759
762
|
} catch (error) {
|
|
760
763
|
this.#lagging.add(group);
|
|
764
|
+
if (error instanceof OpfsCoordinationError)
|
|
765
|
+
throw error;
|
|
761
766
|
this.#notifyGroupError(group, error);
|
|
762
767
|
}
|
|
763
768
|
}
|
|
@@ -769,7 +774,7 @@ class LiveQuerySet {
|
|
|
769
774
|
for (const subscriber of group.subscribers) {
|
|
770
775
|
if (subscriber.closed)
|
|
771
776
|
continue;
|
|
772
|
-
subscriber.options.onError?.(error);
|
|
777
|
+
callLiveCallback(() => subscriber.options.onError?.(error));
|
|
773
778
|
}
|
|
774
779
|
}
|
|
775
780
|
async #changedTablesSince(after, until) {
|
|
@@ -797,7 +802,6 @@ class LiveQuerySet {
|
|
|
797
802
|
}
|
|
798
803
|
}
|
|
799
804
|
}
|
|
800
|
-
import { copyDate, dateMilliseconds } from "../date-value.js";
|
|
801
805
|
export {
|
|
802
806
|
DEFAULT_LIVE_QUERY_MAX_GROUPS,
|
|
803
807
|
DEFAULT_LIVE_QUERY_MAX_SUBSCRIPTIONS,
|
package/dist/engine/optimizer.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { encodeQueryIdentity } from "./query-identity.js";
|
|
1
2
|
import { dateIsoString, dateMilliseconds } from "../date-value.js";
|
|
2
3
|
import { crossJoinPlan } from "../plan/model.js";
|
|
3
4
|
import { blockHasRowWindow, blockHasSubqueries, childExpressions, DUAL_TABLE, expressionAliases, forEachBlockExpression, forEachNestedBlock, hasAggregate, isAggregateCall, isScalarFunctionName, mapBlockExpressions, mapChildExpressions, parseQuantified, scalarFunctionNames, scalarFunctionValue, splitCondition, statementDatetimeNames, volatileScalarFunctionNames, transparentProjectionSource, dateTruncValue, integerQuotient } from "./query.js";
|
|
@@ -557,7 +558,7 @@ function propagateJoinKeyConstants(block) {
|
|
|
557
558
|
}
|
|
558
559
|
if (pairs.length === 0)
|
|
559
560
|
return;
|
|
560
|
-
const signature = (predicate) =>
|
|
561
|
+
const signature = (predicate) => encodeQueryIdentity(predicate);
|
|
561
562
|
const present = new Set(block.predicates.map(signature));
|
|
562
563
|
const implied = [];
|
|
563
564
|
const constantSide = (predicate) => {
|
|
@@ -785,9 +786,9 @@ function decorrelateBlock(block, nextAlias) {
|
|
|
785
786
|
if (!expressionHasCorrelatedSubquery(item.expression))
|
|
786
787
|
continue;
|
|
787
788
|
if (grouped) {
|
|
788
|
-
const groupedExpressions = new Set(block.groupBy.map((group) =>
|
|
789
|
+
const groupedExpressions = new Set(block.groupBy.map((group) => encodeQueryIdentity(group)));
|
|
789
790
|
const outerReferences = correlatedOuterReferences(item.expression);
|
|
790
|
-
if (containsAggregateCall(item.expression) || outerReferences.some((reference) => !groupedExpressions.has(
|
|
791
|
+
if (containsAggregateCall(item.expression) || outerReferences.some((reference) => !groupedExpressions.has(encodeQueryIdentity({ kind: "column", reference })))) {
|
|
791
792
|
throw new TypeError("A grouped correlated select-list subquery must reference only GROUP BY columns and cannot be nested inside an aggregate");
|
|
792
793
|
}
|
|
793
794
|
}
|
|
@@ -2428,7 +2429,11 @@ function foldExpression(expression) {
|
|
|
2428
2429
|
if (folded === null || typeof folded === "string" || typeof folded === "boolean" || folded instanceof Date || typeof folded === "number" && Number.isFinite(folded)) {
|
|
2429
2430
|
const target = foldedArguments[1];
|
|
2430
2431
|
const targetWord = expression.name === "CAST" && target?.kind === "literal" && typeof target.value === "string" ? target.value : void 0;
|
|
2431
|
-
const
|
|
2432
|
+
const first = literalValues[0];
|
|
2433
|
+
const sqlDomain = expression.name === "ARRAY" ? {
|
|
2434
|
+
kind: "array",
|
|
2435
|
+
element: typeof first === "number" ? "DOUBLE" : typeof first === "boolean" ? "BOOLEAN" : first instanceof Date ? "TIMESTAMP" : "TEXT"
|
|
2436
|
+
} : targetWord?.startsWith("numeric:") === true ? (() => {
|
|
2432
2437
|
const [, precisionWord = "", scaleWord = ""] = targetWord.split(":");
|
|
2433
2438
|
return {
|
|
2434
2439
|
kind: "numeric",
|
|
@@ -2565,8 +2570,8 @@ function rewriteForInner(expression, source, derived, singleSource) {
|
|
|
2565
2570
|
if (containsAggregateOrWindow(item.expression))
|
|
2566
2571
|
return void 0;
|
|
2567
2572
|
if (derived.groupBy.length > 0) {
|
|
2568
|
-
const signature =
|
|
2569
|
-
if (!derived.groupBy.some((group) =>
|
|
2573
|
+
const signature = encodeQueryIdentity(item.expression);
|
|
2574
|
+
if (!derived.groupBy.some((group) => encodeQueryIdentity(group) === signature)) {
|
|
2570
2575
|
return void 0;
|
|
2571
2576
|
}
|
|
2572
2577
|
}
|
|
@@ -1,25 +1,28 @@
|
|
|
1
|
+
import { encodeQueryIdentity } from "./query-identity.js";
|
|
1
2
|
import { copyDate, dateMilliseconds } from "../date-value.js";
|
|
2
3
|
import { estimateValuesBytes } from "./byte-estimates.js";
|
|
3
|
-
import { copyQueryResultExternalization } from "./
|
|
4
|
+
import { copyQueryResultExternalization } from "./result-state.js";
|
|
4
5
|
import { defineSqlResultProperty } from "./sql-semantics.js";
|
|
5
6
|
const RESULT_MEMO_MAX_BYTES = 4 * 1024 * 1024;
|
|
7
|
+
function sameQueryRow(left, right, columns) {
|
|
8
|
+
if (left === right)
|
|
9
|
+
return true;
|
|
10
|
+
for (const column of columns) {
|
|
11
|
+
const a = left[column] ?? null;
|
|
12
|
+
const b = right[column] ?? null;
|
|
13
|
+
if (a instanceof Date || b instanceof Date) {
|
|
14
|
+
if (!(a instanceof Date && b instanceof Date) || !Object.is(dateMilliseconds(a), dateMilliseconds(b)))
|
|
15
|
+
return false;
|
|
16
|
+
} else if (!Object.is(a, b))
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
6
21
|
function queryResultMemoKey(sql, params) {
|
|
7
22
|
return JSON.stringify([sql, params.map(encodeParameter)]);
|
|
8
23
|
}
|
|
9
24
|
function planMemoKey(plan) {
|
|
10
|
-
return
|
|
11
|
-
if (value instanceof Date)
|
|
12
|
-
return { $date: dateMilliseconds(value) };
|
|
13
|
-
if (typeof value === "bigint")
|
|
14
|
-
return { $bigint: value.toString() };
|
|
15
|
-
if (typeof value === "number") {
|
|
16
|
-
if (Object.is(value, -0))
|
|
17
|
-
return { $number: "-0" };
|
|
18
|
-
if (!Number.isFinite(value))
|
|
19
|
-
return { $number: String(value) };
|
|
20
|
-
}
|
|
21
|
-
return value;
|
|
22
|
-
});
|
|
25
|
+
return encodeQueryIdentity(plan);
|
|
23
26
|
}
|
|
24
27
|
function copyQueryResult(result) {
|
|
25
28
|
const columns = result.columns;
|
|
@@ -67,5 +70,6 @@ export {
|
|
|
67
70
|
copyQueryResult,
|
|
68
71
|
planMemoKey,
|
|
69
72
|
queryResultMemoKey,
|
|
70
|
-
queryResultRetainedBytes
|
|
73
|
+
queryResultRetainedBytes,
|
|
74
|
+
sameQueryRow
|
|
71
75
|
};
|