@minnowdb/core 0.8.0 → 0.9.1

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.
Files changed (66) hide show
  1. package/dist/engine/artifact-cache.js +3 -2
  2. package/dist/engine/buffered-writer.js +12 -2
  3. package/dist/engine/client.d.ts +8 -2
  4. package/dist/engine/client.js +106 -22
  5. package/dist/engine/database.d.ts +5 -3
  6. package/dist/engine/database.js +2044 -1674
  7. package/dist/engine/errors.d.ts +19 -0
  8. package/dist/engine/errors.js +29 -0
  9. package/dist/engine/index.d.ts +1 -0
  10. package/dist/engine/index.js +4 -0
  11. package/dist/engine/live-accept.js +13 -0
  12. package/dist/engine/live-aggregate.js +54 -16
  13. package/dist/engine/live.d.ts +2 -0
  14. package/dist/engine/live.js +109 -108
  15. package/dist/engine/query-cache.js +17 -2
  16. package/dist/engine/query.d.ts +1 -2
  17. package/dist/engine/query.js +4 -7
  18. package/dist/engine/result-state.d.ts +7 -0
  19. package/dist/engine/result-state.js +15 -0
  20. package/dist/engine/vector.d.ts +4 -0
  21. package/dist/engine/vector.js +14 -14
  22. package/dist/engine/windows.js +15 -14
  23. package/dist/engine/worker-server.d.ts +1 -1
  24. package/dist/engine/worker-server.js +28 -15
  25. package/dist/engine/write-coordinator.js +54 -0
  26. package/dist/storage/indexeddb.js +134 -89
  27. package/dist/storage/opfs/index.d.ts +1 -1
  28. package/dist/storage/opfs/index.js +3 -1
  29. package/dist/storage/opfs/leader.js +20 -9
  30. package/dist/storage/opfs/rpc.js +3 -1
  31. package/dist/storage/opfs/store.d.ts +2 -0
  32. package/dist/storage/opfs/store.js +128 -40
  33. package/dist/storage/toolkit/record-core.js +2 -1
  34. package/dist/storage/types.d.ts +14 -0
  35. package/dist/storage/types.js +21 -0
  36. package/dist/transactions/index.d.ts +3 -0
  37. package/dist/transactions/index.js +4 -1
  38. package/dist/worker-protocol/index.d.ts +1 -1
  39. package/dist/worker-protocol/index.js +1 -1
  40. package/package.json +2 -2
  41. package/dist/date-value.d.ts +0 -20
  42. package/dist/engine/artifact-cache.d.ts +0 -29
  43. package/dist/engine/byte-estimates.d.ts +0 -11
  44. package/dist/engine/cancellation.d.ts +0 -2
  45. package/dist/engine/defaults.d.ts +0 -29
  46. package/dist/engine/group-index.d.ts +0 -33
  47. package/dist/engine/join-index.d.ts +0 -10
  48. package/dist/engine/live-aggregate.d.ts +0 -12
  49. package/dist/engine/live-equal.d.ts +0 -7
  50. package/dist/engine/point-read.d.ts +0 -59
  51. package/dist/engine/query-cache.d.ts +0 -21
  52. package/dist/engine/query-generations.d.ts +0 -8
  53. package/dist/engine/query-identity.d.ts +0 -3
  54. package/dist/engine/result-wire.d.ts +0 -70
  55. package/dist/engine/sort-keys.d.ts +0 -73
  56. package/dist/engine/sql-domains.d.ts +0 -97
  57. package/dist/engine/sql-functions.d.ts +0 -11
  58. package/dist/engine/sql-json.d.ts +0 -40
  59. package/dist/engine/sql-semantics.d.ts +0 -66
  60. package/dist/engine/worker-store-indexeddb.d.ts +0 -2
  61. package/dist/engine/worker-store-memory.d.ts +0 -2
  62. package/dist/engine/worker-store-opfs.d.ts +0 -2
  63. package/dist/engine/write-block-planner.d.ts +0 -19
  64. package/dist/storage/opfs/leader.d.ts +0 -460
  65. package/dist/storage/opfs/rpc.d.ts +0 -82
  66. package/dist/storage/opfs/snapshot-ledger.d.ts +0 -41
@@ -1,6 +1,8 @@
1
1
  import { createLiveQueryPatch } from "./live-patch.js";
2
- import { queryResultRetainedBytes } from "./query-cache.js";
2
+ import { acceptLiveExecution } from "./live-accept.js";
3
+ import { copyQueryResult, queryResultRetainedBytes, sameQueryRow } from "./query-cache.js";
3
4
  import { encodeQueryIdentity } from "./query-identity.js";
5
+ import { OpfsCoordinationError } from "../storage/types.js";
4
6
  import { LiveQueryLimitError } from "./errors.js";
5
7
  const DEFAULT_LIVE_QUERY_MAX_GROUPS = 256;
6
8
  const DEFAULT_LIVE_QUERY_MAX_SUBSCRIPTIONS = 1024;
@@ -9,12 +11,6 @@ const MAX_LIVE_QUERY_SUBSCRIPTIONS = 16384;
9
11
  const MAX_LIVE_QUERY_SETS_PER_DATABASE = 256;
10
12
  const LIVE_QUERY_EXECUTION_CONCURRENCY = 8;
11
13
  import { LiveQueryLimitError as LiveQueryLimitError2 } from "./errors.js";
12
- function sameQueryValue(left, right) {
13
- if (left instanceof Date || right instanceof Date) {
14
- return left instanceof Date && right instanceof Date && Object.is(dateMilliseconds(left), dateMilliseconds(right));
15
- }
16
- return Object.is(left, right);
17
- }
18
14
  function sameResult(left, right) {
19
15
  if (left.columns.length !== right.columns.length || left.rows.length !== right.rows.length) {
20
16
  return false;
@@ -32,40 +28,11 @@ function sameResult(left, right) {
32
28
  const rightRow = right.rows[rowIndex];
33
29
  if (leftRow === void 0 || rightRow === void 0)
34
30
  return false;
35
- if (leftRow === rightRow)
36
- continue;
37
- for (const column of columns) {
38
- if (!sameQueryValue(leftRow[column] ?? null, rightRow[column] ?? null))
39
- return false;
40
- }
31
+ if (!sameQueryRow(leftRow, rightRow, columns))
32
+ return false;
41
33
  }
42
34
  return true;
43
35
  }
44
- function cloneRow(row, columns) {
45
- const cloned = {};
46
- for (const column of columns) {
47
- const value = row[column] ?? null;
48
- const copy = value instanceof Date ? copyDate(value) : value;
49
- if (column === "__proto__") {
50
- Object.defineProperty(cloned, column, {
51
- value: copy,
52
- enumerable: true,
53
- configurable: true,
54
- writable: true
55
- });
56
- } else
57
- cloned[column] = copy;
58
- }
59
- return cloned;
60
- }
61
- function cloneResult(result) {
62
- const columns = [...result.columns];
63
- return {
64
- columns,
65
- columnDomains: structuredClone(result.columnDomains),
66
- rows: result.rows.map((row) => cloneRow(row, columns))
67
- };
68
- }
69
36
  function queryKey(query) {
70
37
  return encodeQueryIdentity(typeof query === "string" ? ["sql", query] : query.kind === "sql-query" ? ["sql-query", query.sql, query.params] : ["typed-query", query.plan]);
71
38
  }
@@ -102,9 +69,9 @@ function groupExecutes(group) {
102
69
  }
103
70
  return false;
104
71
  }
105
- function groupMemoizes(group) {
72
+ function groupMemoizes(group, changed = true) {
106
73
  for (const subscriber of group.subscribers) {
107
- if (!subscriber.closed && subscriber.kind === "observer")
74
+ if (!subscriber.closed && subscriber.kind === "observer" && (changed || subscriber.options.suppressUnchanged !== true))
108
75
  return true;
109
76
  }
110
77
  return false;
@@ -152,9 +119,12 @@ class LiveQuerySet {
152
119
  #executionWaiters = [];
153
120
  #sweepChain = Promise.resolve();
154
121
  #sweepQueued = false;
122
+ #retryTimer;
123
+ #retryDelayMs = 100;
155
124
  #pollTimer;
156
125
  #closed = false;
157
126
  #subscriptionCount = 0;
127
+ #retainedBytes = 0;
158
128
  #ownsChannel;
159
129
  #onClosed;
160
130
  constructor(host, options = {}) {
@@ -191,10 +161,7 @@ class LiveQuerySet {
191
161
  let retainedRows = 0;
192
162
  for (const group of this.#groups.values())
193
163
  retainedRows += group.result?.rows.length ?? 0;
194
- let retainedBytes = 0;
195
- for (const group of this.#groups.values())
196
- retainedBytes += group.retainedBytes;
197
- return { ...this.#stats, retainedRows, retainedBytes };
164
+ return { ...this.#stats, retainedRows, retainedBytes: this.#retainedBytes };
198
165
  }
199
166
  #freshProbe() {
200
167
  let pending = this.#pendingProbe;
@@ -218,43 +185,14 @@ class LiveQuerySet {
218
185
  }
219
186
  this.#subscriptionCount += 1;
220
187
  }
221
- async subscribe(query, options) {
222
- this.#throwIfClosed();
223
- this.#reserveSubscription();
224
- let group;
225
- let subscriber;
226
- try {
227
- const opened = await this.#getOrOpenGroup(query);
228
- group = opened.group;
229
- this.#throwIfClosed();
230
- subscriber = {
231
- kind: "result",
232
- options,
233
- delivered: false,
234
- closed: false,
235
- seenDelivery: -1
236
- };
237
- group.subscribers.add(subscriber);
238
- await this.#settleGroup(group, opened.fresh, true);
239
- this.#throwIfClosed();
240
- if (!subscriber.delivered) {
241
- const result = group.result ?? (await this.#executeGroup(group)).result;
242
- this.#throwIfClosed();
243
- if (!subscriber.closed) {
244
- this.#deliverResult(group, subscriber, result, { ...group.seenProbe, initial: true });
245
- }
246
- }
247
- } catch (error) {
248
- if (group !== void 0 && subscriber !== void 0) {
249
- this.#removeSubscriber(group, subscriber, false);
250
- } else {
251
- this.#subscriptionCount -= 1;
252
- if (group !== void 0)
253
- this.#removeEmptyGroup(group);
254
- }
255
- throw error;
256
- }
257
- 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
+ });
258
196
  }
259
197
  subscribePatches(query, options) {
260
198
  return this.subscribe(query, {
@@ -264,34 +202,42 @@ class LiveQuerySet {
264
202
  ...options.onComplete === void 0 ? {} : { onComplete: options.onComplete.bind(options) }
265
203
  });
266
204
  }
267
- async observe(query, options) {
205
+ observe(query, options) {
206
+ return this.#register(query, { kind: "observer", options, delivered: false, closed: false });
207
+ }
208
+ async #register(query, subscriber) {
268
209
  this.#throwIfClosed();
269
210
  this.#reserveSubscription();
270
211
  let group;
271
- let subscriber;
212
+ let installed = false;
272
213
  try {
273
214
  const opened = await this.#getOrOpenGroup(query);
274
215
  group = opened.group;
275
216
  this.#throwIfClosed();
276
- subscriber = {
277
- kind: "observer",
278
- options,
279
- delivered: false,
280
- closed: false
281
- };
282
217
  group.subscribers.add(subscriber);
283
- await this.#settleGroup(group, opened.fresh, options.suppressUnchanged === true);
218
+ group.pendingSubscriptions -= 1;
219
+ installed = true;
220
+ await this.#settleGroup(group, opened.fresh, subscriber.kind === "result" || subscriber.options.suppressUnchanged === true);
284
221
  this.#throwIfClosed();
285
222
  if (!subscriber.delivered) {
286
- this.#deliverInvalidation(subscriber, { ...group.seenProbe, initial: true });
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
+ }
287
231
  }
288
232
  } catch (error) {
289
- if (group !== void 0 && subscriber !== void 0) {
233
+ if (group !== void 0 && installed) {
290
234
  this.#removeSubscriber(group, subscriber, false);
291
235
  } else {
292
236
  this.#subscriptionCount -= 1;
293
- if (group !== void 0)
237
+ if (group !== void 0) {
238
+ group.pendingSubscriptions -= 1;
294
239
  this.#removeEmptyGroup(group);
240
+ }
295
241
  }
296
242
  throw error;
297
243
  }
@@ -327,30 +273,39 @@ class LiveQuerySet {
327
273
  subscriber.closed = true;
328
274
  this.#subscriptionCount -= 1;
329
275
  group.subscribers.delete(subscriber);
276
+ this.#removeEmptyGroup(group);
330
277
  if (complete)
331
278
  callLiveCallback(() => subscriber.options.onComplete?.());
332
- this.#removeEmptyGroup(group);
333
279
  }
334
280
  #removeEmptyGroup(group) {
335
- if (group.subscribers.size !== 0)
281
+ if (group.subscribers.size !== 0 || group.pendingSubscriptions !== 0)
336
282
  return;
337
283
  if (this.#groups.get(group.key) !== group)
338
284
  return;
339
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;
340
290
  this.#lagging.delete(group);
341
291
  this.#unindexGroup(group, group.dependencies);
342
292
  }
343
293
  async #getOrOpenGroup(query) {
294
+ if (typeof query !== "string")
295
+ query = structuredClone(query);
344
296
  const key = queryKey(query);
345
297
  const existing = this.#groups.get(key);
346
298
  if (existing !== void 0) {
347
299
  this.#stats.sharedExecutions += 1;
300
+ existing.pendingSubscriptions += 1;
348
301
  return { group: existing, fresh: false };
349
302
  }
350
303
  const opening = this.#opening.get(key);
351
304
  if (opening !== void 0) {
352
305
  this.#stats.sharedExecutions += 1;
353
- return { group: await opening, fresh: false };
306
+ const group = await opening;
307
+ group.pendingSubscriptions += 1;
308
+ return { group, fresh: false };
354
309
  }
355
310
  if (this.#groups.size + this.#opening.size >= this.#maxGroups) {
356
311
  throw new LiveQueryLimitError("group", this.#maxGroups);
@@ -358,7 +313,9 @@ class LiveQuerySet {
358
313
  const created = this.#openGroup(key, query);
359
314
  this.#opening.set(key, created);
360
315
  try {
361
- return { group: await created, fresh: true };
316
+ const group = await created;
317
+ group.pendingSubscriptions += 1;
318
+ return { group, fresh: true };
362
319
  } finally {
363
320
  this.#opening.delete(key);
364
321
  }
@@ -370,6 +327,7 @@ class LiveQuerySet {
370
327
  query,
371
328
  dependencies,
372
329
  subscribers: /* @__PURE__ */ new Set(),
330
+ pendingSubscriptions: 0,
373
331
  seenProbe: after,
374
332
  result: void 0,
375
333
  retainedBytes: 0,
@@ -437,12 +395,14 @@ class LiveQuerySet {
437
395
  let executed;
438
396
  let maintenance;
439
397
  let retainedBytes;
398
+ let candidate;
440
399
  try {
441
400
  if (this.#incremental && this.#host.executeMaintainable !== void 0 && !group.unmaintainable) {
442
401
  const maintained = await this.#host.executeMaintainable(group.query, context);
443
402
  if (maintained === void 0)
444
403
  group.unmaintainable = true;
445
404
  else {
405
+ candidate = maintained;
446
406
  executed = maintained.result;
447
407
  maintenance = maintained.state;
448
408
  retainedBytes = maintained.retainedBytes;
@@ -454,7 +414,9 @@ class LiveQuerySet {
454
414
  }
455
415
  const previous = group.result;
456
416
  const changed = previous === void 0 || !sameResult(previous, executed);
457
- this.#retain(group, executed, maintenance, retainedBytes);
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);
458
420
  return { result: executed, changed };
459
421
  })();
460
422
  group.execution = execution;
@@ -465,17 +427,27 @@ class LiveQuerySet {
465
427
  group.execution = void 0;
466
428
  }
467
429
  }
468
- #retain(group, result, state, hint) {
469
- const bytes = Math.max(queryResultRetainedBytes(result) + result.rows.length * 48, hint ?? (state === group.maintenance ? group.retainedBytes : 0));
470
- let total = bytes;
471
- for (const other of this.#groups.values())
472
- if (other !== group)
473
- total += other.retainedBytes;
474
- if (!Number.isSafeInteger(total) || total > this.#maxRetainedBytes)
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)
475
444
  throw new LiveQueryLimitError("byte", this.#maxRetainedBytes);
445
+ if (candidate !== void 0)
446
+ acceptLiveExecution(candidate);
476
447
  group.result = result;
477
448
  group.maintenance = state;
478
449
  group.retainedBytes = bytes;
450
+ this.#retainedBytes = total;
479
451
  }
480
452
  async #acquireExecutionSlot() {
481
453
  if (this.#executing < LIVE_QUERY_EXECUTION_CONCURRENCY) {
@@ -513,7 +485,9 @@ class LiveQuerySet {
513
485
  verdict.declined = true;
514
486
  return { result: retained, changed: false };
515
487
  }
516
- this.#retain(group, maintained.result, maintained.state, maintained.retainedBytes);
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);
517
491
  return {
518
492
  result: maintained.result,
519
493
  changed: maintained.changed,
@@ -534,7 +508,7 @@ class LiveQuerySet {
534
508
  return;
535
509
  subscriber.delivered = true;
536
510
  const consecutive = subscriber.seenDelivery === group.deliveries - 1;
537
- subscriber.options.onChange(this.#sharedResults || subscriber.options.sharedResults === true ? result : cloneResult(result), retained !== void 0 && consecutive && !delivery.initial ? { ...delivery, retained } : delivery);
511
+ subscriber.options.onChange(this.#sharedResults || subscriber.options.sharedResults === true ? result : copyQueryResult(result), retained !== void 0 && consecutive && !delivery.initial ? { ...delivery, retained } : delivery);
538
512
  subscriber.seenDelivery = group.deliveries;
539
513
  }
540
514
  #deliverInvalidation(subscriber, invalidation) {
@@ -560,11 +534,17 @@ class LiveQuerySet {
560
534
  this.#channel?.close?.();
561
535
  if (this.#pollTimer !== void 0)
562
536
  clearInterval(this.#pollTimer);
537
+ if (this.#retryTimer !== void 0)
538
+ clearTimeout(this.#retryTimer);
563
539
  const groups = [...this.#groups.values()];
564
540
  this.#groups.clear();
541
+ this.#retainedBytes = 0;
565
542
  this.#groupsByTable.clear();
566
543
  this.#lagging.clear();
567
544
  for (const group of groups) {
545
+ group.result = void 0;
546
+ group.maintenance = void 0;
547
+ group.retainedBytes = 0;
568
548
  const subscribers = [...group.subscribers];
569
549
  group.subscribers.clear();
570
550
  for (const subscriber of subscribers) {
@@ -581,19 +561,37 @@ class LiveQuerySet {
581
561
  if (this.#closed)
582
562
  return;
583
563
  this.#stats.hints += 1;
564
+ if (this.#retryTimer !== void 0)
565
+ return;
584
566
  if (this.#sweepQueued)
585
567
  return;
586
568
  this.#sweepQueued = true;
587
569
  this.#sweepChain = this.#sweepChain.then(async () => {
588
570
  this.#sweepQueued = false;
571
+ if (this.#retryTimer !== void 0)
572
+ return;
589
573
  try {
590
574
  await this.#sweep();
575
+ this.#retryDelayMs = 100;
591
576
  } catch (error) {
577
+ if (error instanceof OpfsCoordinationError) {
578
+ this.#retrySweep();
579
+ return;
580
+ }
592
581
  for (const group of this.#groups.values())
593
582
  this.#notifyGroupError(group, error);
594
583
  }
595
584
  });
596
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
+ }
597
595
  #stillOpen() {
598
596
  return !this.#closed;
599
597
  }
@@ -671,6 +669,8 @@ class LiveQuerySet {
671
669
  await this.#refreshDependencies(group);
672
670
  } catch (error) {
673
671
  this.#lagging.add(group);
672
+ if (error instanceof OpfsCoordinationError)
673
+ throw error;
674
674
  this.#notifyGroupError(group, error);
675
675
  return;
676
676
  }
@@ -761,6 +761,8 @@ class LiveQuerySet {
761
761
  this.#settleProbe(group, current);
762
762
  } catch (error) {
763
763
  this.#lagging.add(group);
764
+ if (error instanceof OpfsCoordinationError)
765
+ throw error;
764
766
  this.#notifyGroupError(group, error);
765
767
  }
766
768
  }
@@ -800,7 +802,6 @@ class LiveQuerySet {
800
802
  }
801
803
  }
802
804
  }
803
- import { copyDate, dateMilliseconds } from "../date-value.js";
804
805
  export {
805
806
  DEFAULT_LIVE_QUERY_MAX_GROUPS,
806
807
  DEFAULT_LIVE_QUERY_MAX_SUBSCRIPTIONS,
@@ -1,9 +1,23 @@
1
1
  import { encodeQueryIdentity } from "./query-identity.js";
2
2
  import { copyDate, dateMilliseconds } from "../date-value.js";
3
3
  import { estimateValuesBytes } from "./byte-estimates.js";
4
- import { copyQueryResultExternalization } from "./query.js";
4
+ import { copyQueryResultExternalization } from "./result-state.js";
5
5
  import { defineSqlResultProperty } from "./sql-semantics.js";
6
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
+ }
7
21
  function queryResultMemoKey(sql, params) {
8
22
  return JSON.stringify([sql, params.map(encodeParameter)]);
9
23
  }
@@ -56,5 +70,6 @@ export {
56
70
  copyQueryResult,
57
71
  planMemoKey,
58
72
  queryResultMemoKey,
59
- queryResultRetainedBytes
73
+ queryResultRetainedBytes,
74
+ sameQueryRow
60
75
  };
@@ -1,3 +1,4 @@
1
+ export { copyQueryResultExternalization, markQueryResultExternal } from "./result-state.js";
1
2
  import type { DatabaseRow } from "./database.js";
2
3
  import type { ColumnDefault, ColumnGenerated, SqlDomain } from "../storage/types.js";
3
4
  import type { AggregateName, ComparisonOperator, CompiledQuery, Expression, FtsStats, JoinPlan, Predicate, PredicateOperator, QueryResult, QueryRow, QueryValue, ScalarFunctionName, SelectItem, SelectTail, SetOperator, TableSource, WindowSpec } from "../plan/model.js";
@@ -708,8 +709,6 @@ export declare function expressionAliases(expression: Expression): Set<string>;
708
709
  * so comparison falls through to the next term.
709
710
  */
710
711
  export declare function nullOrder(left: unknown, right: unknown, nulls: "first" | "last" | undefined, direction: "asc" | "desc"): number | undefined;
711
- /** Carries the internal no-conversion proof across a defensive result copy. */
712
- export declare function copyQueryResultExternalization(source: QueryResult, copy: QueryResult): QueryResult;
713
712
  /** Removes internal domain tags only after every comparison, group, join, and sort is complete. */
714
713
  export declare function externalizeQueryResult(result: QueryResult): QueryResult;
715
714
  interface SelectBlockParts {
@@ -1,4 +1,6 @@
1
1
  import { encodeQueryIdentity } from "./query-identity.js";
2
+ import { alreadyExternalResults, copyQueryResultExternalization } from "./result-state.js";
3
+ import { copyQueryResultExternalization as copyQueryResultExternalization2, markQueryResultExternal } from "./result-state.js";
2
4
  import { civilFromDays, copyDate, dateIsoString, dateMilliseconds, daysFromCivil, epochDays, dateUtcDate, dateUtcFullYear, dateUtcMonth, setDateUtcDate, setDateUtcMonth } from "../date-value.js";
3
5
  import { crossJoinPlan } from "../plan/model.js";
4
6
  import { assertWellFormedString, wellFormedUtf8ByteLength } from "../block-format/unicode.js";
@@ -4674,17 +4676,11 @@ function asQueryValue(value) {
4674
4676
  return null;
4675
4677
  throw new TypeError("Query produced an unsupported value");
4676
4678
  }
4677
- const alreadyExternalResults = /* @__PURE__ */ new WeakSet();
4678
4679
  function markExternalizationState(result, outputNeedsExternalization) {
4679
4680
  if (outputNeedsExternalization === false)
4680
4681
  alreadyExternalResults.add(result);
4681
4682
  return result;
4682
4683
  }
4683
- function copyQueryResultExternalization(source, copy) {
4684
- if (alreadyExternalResults.has(source))
4685
- alreadyExternalResults.add(copy);
4686
- return copy;
4687
- }
4688
4684
  function externalizeQueryResult(result) {
4689
4685
  if (alreadyExternalResults.has(result))
4690
4686
  return result;
@@ -9900,7 +9896,7 @@ export {
9900
9896
  compileQuery,
9901
9897
  compileStatement,
9902
9898
  compoundSelectBlock,
9903
- copyQueryResultExternalization,
9899
+ copyQueryResultExternalization2 as copyQueryResultExternalization,
9904
9900
  createPreparedColumnarQuery,
9905
9901
  createPreparedQuery,
9906
9902
  createRecursiveCteState,
@@ -9940,6 +9936,7 @@ export {
9940
9936
  likeMatches,
9941
9937
  mapBlockExpressions,
9942
9938
  mapChildExpressions,
9939
+ markQueryResultExternal,
9943
9940
  nullOrder,
9944
9941
  orderOutputName,
9945
9942
  parseQuantified,
@@ -0,0 +1,7 @@
1
+ import type { QueryResult } from "../plan/model.js";
2
+ /** Shared boundary state; result copies must not need the SQL compiler or evaluators. */
3
+ export declare const alreadyExternalResults: WeakSet<QueryResult>;
4
+ /** Internal ownership boundary for results assembled entirely from already-public row values. */
5
+ export declare function markQueryResultExternal(result: QueryResult): QueryResult;
6
+ /** Carries the internal no-conversion proof across a defensive result copy. */
7
+ export declare function copyQueryResultExternalization(source: QueryResult, copy: QueryResult): QueryResult;
@@ -0,0 +1,15 @@
1
+ const alreadyExternalResults = /* @__PURE__ */ new WeakSet();
2
+ function markQueryResultExternal(result) {
3
+ alreadyExternalResults.add(result);
4
+ return result;
5
+ }
6
+ function copyQueryResultExternalization(source, copy) {
7
+ if (alreadyExternalResults.has(source))
8
+ alreadyExternalResults.add(copy);
9
+ return copy;
10
+ }
11
+ export {
12
+ alreadyExternalResults,
13
+ copyQueryResultExternalization,
14
+ markQueryResultExternal
15
+ };
@@ -12,6 +12,8 @@ export interface VectorWindow {
12
12
  readonly length: number;
13
13
  }
14
14
  interface VectorBase {
15
+ /** Present on immutable block vectors, so keyed reads need no decoded-block lookup. */
16
+ readonly nullCount?: number;
15
17
  readonly validity: Uint8Array;
16
18
  readonly length: number;
17
19
  readonly window?: VectorWindow;
@@ -110,5 +112,7 @@ interface PrepareVectorQueryOptions {
110
112
  export declare function createColumnarTable(name: string, columns: ReadonlyMap<string, ColumnarColumnInput>, uniqueKey?: string): ColumnarTable;
111
113
  export declare function columnarTableFromRows(name: string, rows: readonly DatabaseRow[], projectedColumnNames?: readonly string[], protectText?: boolean): ColumnarTable;
112
114
  export declare function prepareVectorQuery(plan: CompiledQuery, inputTables: ReadonlyMap<string, ColumnarTable>, options?: PrepareVectorQueryOptions): PreparedVectorQuery;
115
+ /** Builds directly from a row/column reader without retaining an intermediate value array. */
116
+ export declare function createColumnVector(type: VectorType, length: number, valueAt: (index: number) => unknown): ColumnVector;
113
117
  export declare function vectorValue(vector: ColumnVector, rowIndex: number): QueryValue;
114
118
  export {};
@@ -90,7 +90,7 @@ function createColumnarTable(name, columns, uniqueKey) {
90
90
  if (column.values.length !== rowCount) {
91
91
  throw new Error(`Column row count mismatch: ${name}.${columnName}`);
92
92
  }
93
- vectors.set(columnName, createVector(column));
93
+ vectors.set(columnName, createColumnVector(column.type, rowCount, (index) => column.values[index]));
94
94
  }
95
95
  if (uniqueKey !== void 0 && !vectors.has(uniqueKey)) {
96
96
  throw new Error(`Unique-key vector is missing: ${name}.${uniqueKey}`);
@@ -205,21 +205,20 @@ function prepareVectorQuery(plan, inputTables, options = {}) {
205
205
  throw error;
206
206
  }
207
207
  }
208
- function createVector(input) {
209
- validateVectorType(input.type);
210
- const length = input.values.length;
208
+ function createColumnVector(type, length, valueAt) {
209
+ validateVectorType(type);
211
210
  const validity = new Uint8Array(Math.ceil(length / 8));
212
- if (input.type === "string") {
211
+ if (type === "string") {
213
212
  const dictionary = [];
214
213
  const dictionaryIndex = /* @__PURE__ */ new Map();
215
214
  const codes = new Uint32Array(length);
216
215
  codes.fill(NULL_STRING_CODE);
217
216
  for (let index = 0; index < length; index += 1) {
218
- const value = input.values[index];
217
+ const value = valueAt(index);
219
218
  if (value === null)
220
219
  continue;
221
220
  if (typeof value !== "string")
222
- throw vectorTypeError(input.type, value);
221
+ throw vectorTypeError(type, value);
223
222
  setValid(validity, index);
224
223
  let code = dictionaryIndex.get(value);
225
224
  if (code === void 0) {
@@ -231,14 +230,14 @@ function createVector(input) {
231
230
  }
232
231
  return { kind: "string", length, validity, codes, dictionary };
233
232
  }
234
- if (input.type === "boolean") {
233
+ if (type === "boolean") {
235
234
  const values2 = new Uint8Array(length);
236
235
  for (let index = 0; index < length; index += 1) {
237
- const value = input.values[index];
236
+ const value = valueAt(index);
238
237
  if (value === null)
239
238
  continue;
240
239
  if (typeof value !== "boolean")
241
- throw vectorTypeError(input.type, value);
240
+ throw vectorTypeError(type, value);
242
241
  setValid(validity, index);
243
242
  values2[index] = value ? 1 : 0;
244
243
  }
@@ -246,16 +245,16 @@ function createVector(input) {
246
245
  }
247
246
  const values = new Float64Array(length);
248
247
  for (let index = 0; index < length; index += 1) {
249
- const value = input.values[index];
248
+ const value = valueAt(index);
250
249
  if (value === null)
251
250
  continue;
252
- const numericValue = input.type === "datetime" ? value instanceof Date ? dateMilliseconds(value) : Number.NaN : typeof value === "number" ? value : Number.NaN;
251
+ const numericValue = type === "datetime" ? value instanceof Date ? dateMilliseconds(value) : Number.NaN : typeof value === "number" ? value : Number.NaN;
253
252
  if (!Number.isFinite(numericValue))
254
- throw vectorTypeError(input.type, value);
253
+ throw vectorTypeError(type, value);
255
254
  setValid(validity, index);
256
255
  values[index] = numericValue;
257
256
  }
258
- return input.type === "datetime" ? { kind: "datetime", length, validity, values } : { kind: "number", length, validity, values };
257
+ return type === "datetime" ? { kind: "datetime", length, validity, values } : { kind: "number", length, validity, values };
259
258
  }
260
259
  function validateVectorType(type) {
261
260
  if (type !== "boolean" && type !== "number" && type !== "string" && type !== "datetime") {
@@ -4876,6 +4875,7 @@ function safeMemoryProduct(left, right, label) {
4876
4875
  }
4877
4876
  export {
4878
4877
  columnarTableFromRows,
4878
+ createColumnVector,
4879
4879
  createColumnarTable,
4880
4880
  prepareVectorQuery,
4881
4881
  vectorValue