lakeql 0.1.8 → 0.2.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.
@@ -0,0 +1,905 @@
1
+ import { partitionRows, sortWindowRows, partitionSpans, compatibleWindowSortGroups, stableKey, normalizedWindowName, isAggregateWindow, samePeer, compareWindowRows, requireExactWindowAggregate, filterRow, numericAggregateValue, compareAggregateScalars } from './chunk-LWF5FKHB.js';
2
+ import { LakeqlError, evaluate, jsonSafeValue, isTimestampValue, applyIntervalToTimestamp, isIntervalValue, isNonNegativeInterval } from './chunk-WGHR35QU.js';
3
+
4
+ // ../core/dist/window-aggregate.js
5
+ function aggregateWindowValue(rows, expr) {
6
+ if (typeof expr.fn !== "object") {
7
+ throw new LakeqlError("LAKEQL_TYPE_ERROR", "Window expression is not an aggregate");
8
+ }
9
+ const op = expr.fn.aggregate;
10
+ requireExactWindowAggregate(op);
11
+ const state = new AggregateState(op, quantile(expr));
12
+ for (const row of rows) {
13
+ if (!filterRow(expr, row))
14
+ continue;
15
+ const value = aggregateInputValue(expr, row);
16
+ state.add(value, expr.distinct === true);
17
+ }
18
+ return state.value();
19
+ }
20
+ function aggregateInputValue(expr, row) {
21
+ if (expr.args.length === 0)
22
+ return 1;
23
+ const arg = expr.args[0];
24
+ if (arg === void 0)
25
+ return 1;
26
+ return evaluate(arg, row);
27
+ }
28
+ var AggregateState = class {
29
+ op;
30
+ quantile;
31
+ count = 0;
32
+ sum = 0;
33
+ mean = 0;
34
+ m2 = 0;
35
+ min;
36
+ max;
37
+ first;
38
+ last;
39
+ values = [];
40
+ modeValues = /* @__PURE__ */ new Map();
41
+ seenDistinct = /* @__PURE__ */ new Set();
42
+ constructor(op, quantile2) {
43
+ this.op = op;
44
+ this.quantile = quantile2;
45
+ }
46
+ add(value, distinct) {
47
+ if (this.op === "count") {
48
+ if (value !== null && this.addDistinct(value, distinct))
49
+ this.count += 1;
50
+ return;
51
+ }
52
+ if (value === null || !this.addDistinct(value, distinct))
53
+ return;
54
+ switch (this.op) {
55
+ case "sum":
56
+ case "avg":
57
+ this.addNumber(value);
58
+ break;
59
+ case "var_samp":
60
+ case "var_pop":
61
+ case "stddev_samp":
62
+ case "stddev_pop":
63
+ this.addVariance(value);
64
+ break;
65
+ case "min":
66
+ if (this.min === void 0 || compareScalar(value, this.min) < 0)
67
+ this.min = value;
68
+ break;
69
+ case "max":
70
+ if (this.max === void 0 || compareScalar(value, this.max) > 0)
71
+ this.max = value;
72
+ break;
73
+ case "first":
74
+ case "any":
75
+ if (this.first === void 0)
76
+ this.first = value;
77
+ break;
78
+ case "last":
79
+ this.last = value;
80
+ break;
81
+ case "median":
82
+ case "quantile":
83
+ this.values.push(numericValue(this.op, value));
84
+ break;
85
+ case "mode":
86
+ this.addMode(value);
87
+ break;
88
+ case "count_distinct":
89
+ this.count += 1;
90
+ break;
91
+ case "approx_count_distinct":
92
+ requireExactWindowAggregate(this.op);
93
+ break;
94
+ }
95
+ }
96
+ value() {
97
+ switch (this.op) {
98
+ case "count":
99
+ case "count_distinct":
100
+ return this.count;
101
+ case "sum":
102
+ return this.count === 0 ? null : this.sum;
103
+ case "avg":
104
+ return this.count === 0 ? null : this.sum / this.count;
105
+ case "var_samp":
106
+ return this.count <= 1 ? null : this.m2 / (this.count - 1);
107
+ case "var_pop":
108
+ return this.count === 0 ? null : this.m2 / this.count;
109
+ case "stddev_samp":
110
+ return this.count <= 1 ? null : Math.sqrt(this.m2 / (this.count - 1));
111
+ case "stddev_pop":
112
+ return this.count === 0 ? null : Math.sqrt(this.m2 / this.count);
113
+ case "min":
114
+ return this.min ?? null;
115
+ case "max":
116
+ return this.max ?? null;
117
+ case "first":
118
+ case "any":
119
+ return this.first ?? null;
120
+ case "last":
121
+ return this.last ?? null;
122
+ case "median":
123
+ return continuousQuantile(this.values, 0.5);
124
+ case "quantile":
125
+ return continuousQuantile(this.values, this.quantile ?? 0.5);
126
+ case "mode":
127
+ return this.mode();
128
+ case "approx_count_distinct":
129
+ requireExactWindowAggregate(this.op);
130
+ return null;
131
+ }
132
+ }
133
+ addDistinct(value, distinct) {
134
+ if (!distinct && this.op !== "count_distinct")
135
+ return true;
136
+ const key = JSON.stringify(jsonSafeValue(value));
137
+ if (this.seenDistinct.has(key))
138
+ return false;
139
+ this.seenDistinct.add(key);
140
+ return true;
141
+ }
142
+ addNumber(value) {
143
+ const numeric = numericValue(this.op, value);
144
+ this.sum += numeric;
145
+ this.count += 1;
146
+ }
147
+ addVariance(value) {
148
+ const numeric = numericValue(this.op, value);
149
+ this.count += 1;
150
+ const delta = numeric - this.mean;
151
+ this.mean += delta / this.count;
152
+ this.m2 += delta * (numeric - this.mean);
153
+ }
154
+ addMode(value) {
155
+ const key = JSON.stringify(jsonSafeValue(value));
156
+ const current = this.modeValues.get(key);
157
+ if (current === void 0) {
158
+ this.modeValues.set(key, { value, count: 1 });
159
+ return;
160
+ }
161
+ current.count += 1;
162
+ }
163
+ mode() {
164
+ let best;
165
+ for (const entry of this.modeValues.values()) {
166
+ if (best === void 0 || entry.count > best.count || entry.count === best.count && compareScalar(entry.value, best.value) < 0) {
167
+ best = entry;
168
+ }
169
+ }
170
+ return best?.value ?? null;
171
+ }
172
+ };
173
+ function quantile(expr) {
174
+ if (typeof expr.fn !== "object" || expr.fn.aggregate !== "quantile")
175
+ return void 0;
176
+ const arg = expr.args[1];
177
+ if (arg === void 0 || arg.kind !== "literal" || typeof arg.value !== "number") {
178
+ throw new LakeqlError("LAKEQL_TYPE_ERROR", "quantile_cont window percentile must be numeric");
179
+ }
180
+ if (!Number.isFinite(arg.value) || arg.value < 0 || arg.value > 1) {
181
+ throw new LakeqlError("LAKEQL_TYPE_ERROR", "quantile_cont percentile must be between 0 and 1");
182
+ }
183
+ return arg.value;
184
+ }
185
+ function continuousQuantile(values, q) {
186
+ const sorted = values.map((value) => numericValue("quantile", value)).sort((a, b) => a - b);
187
+ if (sorted.length === 0)
188
+ return null;
189
+ const position = (sorted.length - 1) * q;
190
+ const lower = Math.floor(position);
191
+ const upper = Math.ceil(position);
192
+ const lowerValue = sorted[lower] ?? 0;
193
+ const upperValue = sorted[upper] ?? lowerValue;
194
+ return lowerValue + (upperValue - lowerValue) * (position - lower);
195
+ }
196
+ function numericValue(op, value) {
197
+ return numericAggregateValue(op, value);
198
+ }
199
+ function compareScalar(left, right) {
200
+ return compareAggregateScalars(left, right);
201
+ }
202
+
203
+ // ../core/dist/window-frame.js
204
+ function frameRows(partition, index, expr) {
205
+ const span = frameSpan(partition, index, expr);
206
+ const excluded = excludedRows(partition, index, span, expr.over.frame);
207
+ const out = [];
208
+ for (let rowIndex = span.start; rowIndex < span.end; rowIndex += 1) {
209
+ if (excluded.has(rowIndex))
210
+ continue;
211
+ const row = partition[rowIndex]?.row;
212
+ if (row !== void 0)
213
+ out.push(row);
214
+ }
215
+ return out;
216
+ }
217
+ function frameSpan(partition, index, expr) {
218
+ const frame = effectiveFrame(expr);
219
+ switch (frame.mode) {
220
+ case "rows":
221
+ return rowsFrameSpan(partition, index, frame);
222
+ case "groups":
223
+ return groupsFrameSpan(partition, index, frame);
224
+ case "range":
225
+ return rangeFrameSpan(partition, index, expr, frame);
226
+ }
227
+ }
228
+ function effectiveFrame(expr) {
229
+ if (expr.over.frame !== void 0)
230
+ return expr.over.frame;
231
+ if (expr.over.orderBy.length === 0) {
232
+ return {
233
+ mode: "rows",
234
+ start: { kind: "unbounded-preceding" },
235
+ end: { kind: "unbounded-following" },
236
+ exclude: "no-others"
237
+ };
238
+ }
239
+ return {
240
+ mode: "range",
241
+ start: { kind: "unbounded-preceding" },
242
+ end: { kind: "current-row" },
243
+ exclude: "no-others"
244
+ };
245
+ }
246
+ function rowsFrameSpan(partition, index, frame) {
247
+ return normalizeSpan(partition.length, {
248
+ start: rowBoundIndex(partition, index, frame.start),
249
+ end: rowBoundIndex(partition, index, frame.end) + 1
250
+ });
251
+ }
252
+ function rowBoundIndex(partition, index, bound) {
253
+ switch (bound.kind) {
254
+ case "unbounded-preceding":
255
+ return 0;
256
+ case "unbounded-following":
257
+ return partition.length - 1;
258
+ case "current-row":
259
+ return index;
260
+ case "preceding":
261
+ return index - offset(bound, partition[index]?.row);
262
+ case "following":
263
+ return index + offset(bound, partition[index]?.row);
264
+ }
265
+ }
266
+ function groupsFrameSpan(partition, index, frame) {
267
+ const groups = peerGroups(partition);
268
+ const groupIndex = groups.findIndex((group) => index >= group.start && index < group.end);
269
+ if (groupIndex === -1)
270
+ return { start: 0, end: 0 };
271
+ const startGroup = groupBoundIndex(groups.length, groupIndex, frame.start, partition[index]?.row);
272
+ const endGroup = groupBoundIndex(groups.length, groupIndex, frame.end, partition[index]?.row);
273
+ if (endGroup < 0 || startGroup >= groups.length)
274
+ return { start: 0, end: 0 };
275
+ const clampedStartGroup = clamp(startGroup, 0, groups.length - 1);
276
+ const clampedEndGroup = clamp(endGroup, 0, groups.length - 1);
277
+ return normalizeSpan(partition.length, {
278
+ start: groups[clampedStartGroup]?.start ?? 0,
279
+ end: groups[clampedEndGroup]?.end ?? partition.length
280
+ });
281
+ }
282
+ function groupBoundIndex(groupCount, groupIndex, bound, row) {
283
+ switch (bound.kind) {
284
+ case "unbounded-preceding":
285
+ return 0;
286
+ case "unbounded-following":
287
+ return groupCount - 1;
288
+ case "current-row":
289
+ return groupIndex;
290
+ case "preceding":
291
+ return groupIndex - offset(bound, row);
292
+ case "following":
293
+ return groupIndex + offset(bound, row);
294
+ }
295
+ }
296
+ function rangeFrameSpan(partition, index, expr, frame) {
297
+ if (frame.start.offset !== void 0 || frame.end.offset !== void 0) {
298
+ return rangeOffsetFrameSpan(partition, index, expr, frame);
299
+ }
300
+ const peer = peerSpan(partition, index);
301
+ return normalizeSpan(partition.length, {
302
+ start: rangeEndpoint(partition.length, peer, frame.start, "start"),
303
+ end: rangeEndpoint(partition.length, peer, frame.end, "end") + 1
304
+ });
305
+ }
306
+ function rangeEndpoint(rowCount, peer, bound, side) {
307
+ switch (bound.kind) {
308
+ case "unbounded-preceding":
309
+ return 0;
310
+ case "unbounded-following":
311
+ return rowCount - 1;
312
+ case "current-row":
313
+ return side === "start" ? peer.start : peer.end - 1;
314
+ case "preceding":
315
+ case "following":
316
+ throw new LakeqlError("LAKEQL_SQL_UNSUPPORTED", "RANGE offset frames require the range-offset backend");
317
+ }
318
+ }
319
+ function rangeOffsetFrameSpan(partition, index, expr, frame) {
320
+ if (expr.over.orderBy.length !== 1) {
321
+ throw new LakeqlError("LAKEQL_SQL_UNSUPPORTED", "RANGE offset frames require exactly one ORDER BY expression");
322
+ }
323
+ const direction = expr.over.orderBy[0]?.direction ?? "asc";
324
+ const current = rangePoint(partition[index]?.orderKey[0], direction);
325
+ const start = rangeOffsetBound(current, frame.start, partition[index]?.row, direction);
326
+ const end = rangeOffsetBound(current, frame.end, partition[index]?.row, direction);
327
+ let first = 0;
328
+ while (first < partition.length && compareRangeValues(rangePoint(partition[first]?.orderKey[0], direction).normalized, start) < 0) {
329
+ first += 1;
330
+ }
331
+ let afterLast = first;
332
+ while (afterLast < partition.length && compareRangeValues(rangePoint(partition[afterLast]?.orderKey[0], direction).normalized, end) <= 0) {
333
+ afterLast += 1;
334
+ }
335
+ return normalizeSpan(partition.length, { start: first, end: afterLast });
336
+ }
337
+ function rangeOffsetBound(current, bound, row, direction) {
338
+ switch (bound.kind) {
339
+ case "unbounded-preceding":
340
+ return current.kind === "number" ? -Infinity : -rangeInfinity();
341
+ case "unbounded-following":
342
+ return current.kind === "number" ? Infinity : rangeInfinity();
343
+ case "current-row":
344
+ return current.normalized;
345
+ case "preceding":
346
+ if (current.kind === "number")
347
+ return current.normalized - numericRangeOffset(bound, row);
348
+ return shiftedTimestampRangeBound(current, bound, row, direction === "asc" ? -1 : 1, direction);
349
+ case "following":
350
+ if (current.kind === "number")
351
+ return current.normalized + numericRangeOffset(bound, row);
352
+ return shiftedTimestampRangeBound(current, bound, row, direction === "asc" ? 1 : -1, direction);
353
+ }
354
+ }
355
+ function shiftedTimestampRangeBound(current, bound, row, shiftDirection, direction) {
356
+ const interval = intervalRangeOffset(bound, row);
357
+ const shifted = applyIntervalToTimestamp(current.value, interval, shiftDirection);
358
+ return direction === "desc" ? -shifted.epochNanoseconds : shifted.epochNanoseconds;
359
+ }
360
+ function rangePoint(value, direction) {
361
+ if (typeof value === "number" && Number.isFinite(value)) {
362
+ return { kind: "number", value, normalized: direction === "asc" ? value : -value };
363
+ }
364
+ if (isTimestampValue(value)) {
365
+ return {
366
+ kind: "timestamp",
367
+ value,
368
+ normalized: direction === "asc" ? value.epochNanoseconds : -value.epochNanoseconds
369
+ };
370
+ }
371
+ throw new LakeqlError("LAKEQL_TYPE_ERROR", "RANGE offset frames require a non-null numeric or timestamp ORDER BY value");
372
+ }
373
+ function compareRangeValues(left, right) {
374
+ if (typeof left !== typeof right) {
375
+ throw new LakeqlError("LAKEQL_TYPE_ERROR", "RANGE frame bounds must have matching types");
376
+ }
377
+ if (left < right)
378
+ return -1;
379
+ if (left > right)
380
+ return 1;
381
+ return 0;
382
+ }
383
+ function rangeInfinity() {
384
+ return 1n << 255n;
385
+ }
386
+ function excludedRows(partition, index, span, frame) {
387
+ const exclude = frame?.exclude ?? "no-others";
388
+ const out = /* @__PURE__ */ new Set();
389
+ if (exclude === "no-others")
390
+ return out;
391
+ const peer = peerSpan(partition, index);
392
+ if (exclude === "current-row") {
393
+ out.add(index);
394
+ return out;
395
+ }
396
+ for (let rowIndex = Math.max(span.start, peer.start); rowIndex < Math.min(span.end, peer.end); rowIndex += 1) {
397
+ if (exclude === "ties" && rowIndex === index)
398
+ continue;
399
+ out.add(rowIndex);
400
+ }
401
+ return out;
402
+ }
403
+ function peerSpan(partition, index) {
404
+ let start = index;
405
+ while (start > 0 && samePeer(partition[start - 1], partition[index])) {
406
+ start -= 1;
407
+ }
408
+ let end = index + 1;
409
+ while (end < partition.length && samePeer(partition[index], partition[end])) {
410
+ end += 1;
411
+ }
412
+ return { start, end };
413
+ }
414
+ function peerGroups(partition) {
415
+ const groups = [];
416
+ let start = 0;
417
+ while (start < partition.length) {
418
+ const peer = peerSpan(partition, start);
419
+ groups.push(peer);
420
+ start = peer.end;
421
+ }
422
+ return groups;
423
+ }
424
+ function offset(bound, row) {
425
+ if (bound.offset === void 0) {
426
+ throw new LakeqlError("LAKEQL_TYPE_ERROR", "Window frame offset is required");
427
+ }
428
+ const value = evaluate(bound.offset, row ?? {});
429
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
430
+ throw new LakeqlError("LAKEQL_TYPE_ERROR", "Window frame offset must be a non-negative integer");
431
+ }
432
+ return value;
433
+ }
434
+ function numericRangeOffset(bound, row) {
435
+ if (bound.offset === void 0) {
436
+ throw new LakeqlError("LAKEQL_TYPE_ERROR", "Window frame offset is required");
437
+ }
438
+ const value = evaluate(bound.offset, row ?? {});
439
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
440
+ throw new LakeqlError("LAKEQL_TYPE_ERROR", "RANGE frame offset must be a non-negative finite number");
441
+ }
442
+ return value;
443
+ }
444
+ function intervalRangeOffset(bound, row) {
445
+ if (bound.offset === void 0) {
446
+ throw new LakeqlError("LAKEQL_TYPE_ERROR", "Window frame offset is required");
447
+ }
448
+ const value = evaluate(bound.offset, row ?? {});
449
+ if (!isIntervalValue(value) || !isNonNegativeInterval(value)) {
450
+ throw new LakeqlError("LAKEQL_TYPE_ERROR", "Timestamp RANGE frame offset must be a non-negative interval");
451
+ }
452
+ return value;
453
+ }
454
+ function normalizeSpan(rowCount, span) {
455
+ const start = clamp(span.start, 0, rowCount);
456
+ const end = clamp(span.end, 0, rowCount);
457
+ return start <= end ? { start, end } : { start: end, end };
458
+ }
459
+ function clamp(value, min, max) {
460
+ return Math.max(min, Math.min(max, value));
461
+ }
462
+
463
+ // ../core/dist/window-segment-tree.js
464
+ var EMPTY_NODE = {
465
+ count: 0,
466
+ sum: 0,
467
+ mean: 0,
468
+ m2: 0,
469
+ min: void 0,
470
+ max: void 0
471
+ };
472
+ function segmentTreeAggregateValues(partition, expr) {
473
+ if (typeof expr.fn !== "object" || expr.distinct === true)
474
+ return void 0;
475
+ if ((expr.over.frame?.exclude ?? "no-others") !== "no-others")
476
+ return void 0;
477
+ const op = expr.fn.aggregate;
478
+ if (!isSegmentAggregate(op))
479
+ return void 0;
480
+ const tree = new SegmentTree(partition.map((row) => leafNode(row, expr, op)));
481
+ return partition.map((_row, index) => aggregateValue(op, tree.query(frameSpan(partition, index, expr))));
482
+ }
483
+ function isSegmentAggregate(op) {
484
+ switch (op) {
485
+ case "count":
486
+ case "sum":
487
+ case "avg":
488
+ case "min":
489
+ case "max":
490
+ case "var_samp":
491
+ case "var_pop":
492
+ case "stddev_samp":
493
+ case "stddev_pop":
494
+ return true;
495
+ default:
496
+ return false;
497
+ }
498
+ }
499
+ function leafNode(windowRow, expr, op) {
500
+ if (!filterRow(expr, windowRow.row))
501
+ return EMPTY_NODE;
502
+ const value = aggregateInputValue2(expr, windowRow);
503
+ if (op === "count")
504
+ return value === null ? EMPTY_NODE : { ...EMPTY_NODE, count: 1 };
505
+ if (value === null)
506
+ return EMPTY_NODE;
507
+ if (op === "min" || op === "max")
508
+ return { ...EMPTY_NODE, count: 1, min: value, max: value };
509
+ const numeric = numericValue2(op, value);
510
+ return { count: 1, sum: numeric, mean: numeric, m2: 0, min: void 0, max: void 0 };
511
+ }
512
+ function aggregateInputValue2(expr, windowRow) {
513
+ const arg = expr.args[0];
514
+ if (arg === void 0)
515
+ return 1;
516
+ return evaluate(arg, windowRow.row);
517
+ }
518
+ function aggregateValue(op, node) {
519
+ switch (op) {
520
+ case "count":
521
+ return node.count;
522
+ case "sum":
523
+ return node.count === 0 ? null : node.sum;
524
+ case "avg":
525
+ return node.count === 0 ? null : node.sum / node.count;
526
+ case "min":
527
+ return node.min ?? null;
528
+ case "max":
529
+ return node.max ?? null;
530
+ case "var_samp":
531
+ return node.count <= 1 ? null : variance(node) / (node.count - 1);
532
+ case "var_pop":
533
+ return node.count === 0 ? null : variance(node) / node.count;
534
+ case "stddev_samp":
535
+ return node.count <= 1 ? null : Math.sqrt(variance(node) / (node.count - 1));
536
+ case "stddev_pop":
537
+ return node.count === 0 ? null : Math.sqrt(variance(node) / node.count);
538
+ default:
539
+ throw new LakeqlError("LAKEQL_SQL_UNSUPPORTED", `Unsupported segment aggregate ${op}`);
540
+ }
541
+ }
542
+ function variance(node) {
543
+ return Math.max(0, node.m2);
544
+ }
545
+ function numericValue2(op, value) {
546
+ return numericAggregateValue(op, value);
547
+ }
548
+ var SegmentTree = class {
549
+ size;
550
+ nodes;
551
+ constructor(leaves) {
552
+ let size = 1;
553
+ while (size < leaves.length)
554
+ size *= 2;
555
+ this.size = size;
556
+ this.nodes = Array.from({ length: size * 2 }, () => EMPTY_NODE);
557
+ for (let index = 0; index < leaves.length; index += 1) {
558
+ this.nodes[size + index] = leaves[index] ?? EMPTY_NODE;
559
+ }
560
+ for (let index = size - 1; index > 0; index -= 1) {
561
+ this.nodes[index] = mergeNodes(this.nodes[index * 2] ?? EMPTY_NODE, this.nodes[index * 2 + 1] ?? EMPTY_NODE);
562
+ }
563
+ }
564
+ query(span) {
565
+ let left = span.start + this.size;
566
+ let right = span.end + this.size;
567
+ let leftNode = EMPTY_NODE;
568
+ let rightNode = EMPTY_NODE;
569
+ while (left < right) {
570
+ if (left % 2 === 1) {
571
+ leftNode = mergeNodes(leftNode, this.nodes[left] ?? EMPTY_NODE);
572
+ left += 1;
573
+ }
574
+ if (right % 2 === 1) {
575
+ right -= 1;
576
+ rightNode = mergeNodes(this.nodes[right] ?? EMPTY_NODE, rightNode);
577
+ }
578
+ left = Math.floor(left / 2);
579
+ right = Math.floor(right / 2);
580
+ }
581
+ return mergeNodes(leftNode, rightNode);
582
+ }
583
+ };
584
+ function mergeNodes(left, right) {
585
+ if (left.count === 0)
586
+ return right;
587
+ if (right.count === 0)
588
+ return left;
589
+ const count = left.count + right.count;
590
+ const delta = right.mean - left.mean;
591
+ return {
592
+ count,
593
+ sum: left.sum + right.sum,
594
+ mean: left.mean + delta * right.count / count,
595
+ m2: left.m2 + right.m2 + delta * delta * (left.count * right.count) / count,
596
+ min: mergeOrderValue(left.min, right.min, "min"),
597
+ max: mergeOrderValue(left.max, right.max, "max")
598
+ };
599
+ }
600
+ function mergeOrderValue(left, right, op) {
601
+ if (left === void 0)
602
+ return right;
603
+ if (right === void 0)
604
+ return left;
605
+ const comparison = compareScalar2(left, right);
606
+ return op === "min" ? comparison <= 0 ? left : right : comparison >= 0 ? left : right;
607
+ }
608
+ function compareScalar2(left, right) {
609
+ return compareAggregateScalars(left, right);
610
+ }
611
+
612
+ // ../core/dist/window-backend.js
613
+ function applyWindowsToRows(inputRows, windows, options = {}) {
614
+ const rows = inputRows.map((row) => ({ ...row }));
615
+ options.enforceBufferedRows?.(rows.length);
616
+ if (options.estimateMemoryBytes !== void 0) {
617
+ options.enforceMemoryBytes?.(options.estimateMemoryBytes(rows));
618
+ }
619
+ for (const group of windowSortGroups(windows)) {
620
+ const sorted = partitionRows(rows, group.sortExpr);
621
+ sortWindowRows(sorted, group.sortExpr.over.orderBy);
622
+ for (const { alias, expr } of group.items) {
623
+ const windowRows = windowRowsForExpr(sorted, expr, group.sortExpr);
624
+ const spans = partitionSpans(windowRows);
625
+ const values = evaluateWindowRows(rows.length, windowRows, spans, expr, options);
626
+ for (let index = 0; index < rows.length; index += 1) {
627
+ const row = rows[index];
628
+ if (row !== void 0)
629
+ row[alias] = values[index] ?? null;
630
+ }
631
+ }
632
+ }
633
+ return rows;
634
+ }
635
+ function applyWindowsToRuns(inputRuns, windows, options = {}) {
636
+ const outputRuns = inputRuns.map((run) => run.map((row) => ({ ...row })));
637
+ for (const group of windowSortGroups(windows)) {
638
+ const sortedRuns = outputRuns.map((run, runIndex) => partitionRows(run, group.sortExpr).map((item, rowIndex) => ({
639
+ ...item,
640
+ runIndex,
641
+ rowIndex
642
+ })));
643
+ for (const run of sortedRuns)
644
+ sortWindowRows(run, group.sortExpr.over.orderBy);
645
+ for (const partition of mergeWindowPartitions(sortedRuns, group.sortExpr)) {
646
+ options.enforceWindowPartitionRows?.(partition.length);
647
+ if (options.estimateMemoryBytes !== void 0) {
648
+ options.enforceMemoryBytes?.(options.estimateMemoryBytes(partition.map((item) => item.row)));
649
+ }
650
+ for (const { alias, expr } of group.items) {
651
+ const windowRows = windowRowsForExpr(partition, expr, group.sortExpr);
652
+ const values = evaluatePartition(windowRows, expr);
653
+ for (let index = 0; index < windowRows.length; index += 1) {
654
+ const item = windowRows[index];
655
+ if (item === void 0)
656
+ continue;
657
+ const row = outputRuns[item.runIndex]?.[item.rowIndex];
658
+ if (row !== void 0)
659
+ row[alias] = values[index] ?? null;
660
+ }
661
+ }
662
+ }
663
+ }
664
+ return outputRuns;
665
+ }
666
+ function windowSortGroups(windows) {
667
+ return compatibleWindowSortGroups(Object.entries(windows).map(([alias, expr]) => ({ alias, expr })));
668
+ }
669
+ function windowRowsForExpr(sorted, expr, sortExpr) {
670
+ const orderKeyLength = expr.over.orderBy.length;
671
+ const projected = sorted.map((item) => ({
672
+ ...item,
673
+ orderKey: item.orderKey.slice(0, orderKeyLength)
674
+ }));
675
+ if (orderKeyLength === sortExpr.over.orderBy.length)
676
+ return projected;
677
+ stabilizePrefixPeers(projected);
678
+ return projected;
679
+ }
680
+ function* mergeWindowPartitions(runs, sortExpr) {
681
+ const cursors = runs.map(() => 0);
682
+ let current = [];
683
+ let currentPartitionKey;
684
+ while (true) {
685
+ let bestRun = -1;
686
+ let best;
687
+ for (let runIndex = 0; runIndex < runs.length; runIndex += 1) {
688
+ const candidate = runs[runIndex]?.[cursors[runIndex] ?? 0];
689
+ if (candidate === void 0)
690
+ continue;
691
+ if (best === void 0 || compareWindowRows(candidate, best, sortExpr.over.orderBy) < 0) {
692
+ best = candidate;
693
+ bestRun = runIndex;
694
+ }
695
+ }
696
+ if (best === void 0 || bestRun === -1)
697
+ break;
698
+ cursors[bestRun] = (cursors[bestRun] ?? 0) + 1;
699
+ const partitionKey = stableKey(best.partitionKey);
700
+ if (currentPartitionKey !== void 0 && partitionKey !== currentPartitionKey) {
701
+ yield current;
702
+ current = [];
703
+ }
704
+ currentPartitionKey = partitionKey;
705
+ current.push(best);
706
+ }
707
+ if (current.length > 0)
708
+ yield current;
709
+ }
710
+ function stabilizePrefixPeers(rows) {
711
+ let start = 0;
712
+ while (start < rows.length) {
713
+ const current = rows[start];
714
+ if (current === void 0)
715
+ break;
716
+ const partitionKey = stableKey(current.partitionKey);
717
+ const orderKey = stableKey(current.orderKey);
718
+ let end = start + 1;
719
+ while (end < rows.length && stableKey(rows[end]?.partitionKey ?? []) === partitionKey && stableKey(rows[end]?.orderKey ?? []) === orderKey) {
720
+ end += 1;
721
+ }
722
+ if (end - start > 1) {
723
+ rows.slice(start, end).sort((left, right) => left.originalIndex - right.originalIndex).forEach((row, index) => {
724
+ rows[start + index] = row;
725
+ });
726
+ }
727
+ start = end;
728
+ }
729
+ }
730
+ function evaluateWindowRows(rowCount, sorted, spans, expr, options) {
731
+ const output = new Array(rowCount).fill(null);
732
+ for (const span of spans) {
733
+ const partition = sorted.slice(span.start, span.end);
734
+ options.enforceWindowPartitionRows?.(partition.length);
735
+ const values = evaluatePartition(partition, expr);
736
+ for (let index = 0; index < partition.length; index += 1) {
737
+ const item = partition[index];
738
+ if (item !== void 0)
739
+ output[item.originalIndex] = values[index] ?? null;
740
+ }
741
+ }
742
+ return output;
743
+ }
744
+ function evaluatePartition(partition, expr) {
745
+ const rows = partition.map((item) => item.row);
746
+ const fn = normalizedWindowName(expr.fn);
747
+ if (isAggregateWindow(expr.fn)) {
748
+ const segmentValues = segmentTreeAggregateValues(partition, expr);
749
+ if (segmentValues !== void 0)
750
+ return segmentValues;
751
+ return partition.map((_item, index) => aggregateWindowValue(frameRows(partition, index, expr), expr));
752
+ }
753
+ switch (fn) {
754
+ case "row_number":
755
+ return rows.map((_row, index) => index + 1);
756
+ case "rank":
757
+ return rankValues(rows, expr, false);
758
+ case "dense_rank":
759
+ return rankValues(rows, expr, true);
760
+ case "percent_rank": {
761
+ if (rows.length <= 1)
762
+ return rows.map(() => 0);
763
+ const ranks = rankValues(rows, expr, false);
764
+ return ranks.map((rank) => (rank - 1) / (rows.length - 1));
765
+ }
766
+ case "cume_dist":
767
+ return cumeDistValues(rows, expr);
768
+ case "ntile":
769
+ return ntileValues(rows, expr);
770
+ case "lag":
771
+ return offsetValues(rows, expr, -1);
772
+ case "lead":
773
+ return offsetValues(rows, expr, 1);
774
+ case "first_value":
775
+ return partition.map((_item, index) => {
776
+ const row = firstFrameValue(frameRows(partition, index, expr), expr);
777
+ return row === void 0 ? null : evaluate(valueArg(expr), row);
778
+ });
779
+ case "last_value":
780
+ return partition.map((_item, index) => {
781
+ const row = lastFrameValue(frameRows(partition, index, expr), expr);
782
+ return row === void 0 ? null : evaluate(valueArg(expr), row);
783
+ });
784
+ case "nth_value":
785
+ return nthValue(partition, expr);
786
+ default:
787
+ throw new LakeqlError("LAKEQL_SQL_UNSUPPORTED", `Unsupported window function ${fn}`, {
788
+ function: fn
789
+ });
790
+ }
791
+ }
792
+ function rankValues(rows, expr, dense) {
793
+ const sorted = partitionRows(rows, expr);
794
+ const ranks = [];
795
+ let rank = 1;
796
+ let denseRank = 1;
797
+ for (let index = 0; index < sorted.length; index += 1) {
798
+ const previous = sorted[index - 1];
799
+ const current = sorted[index];
800
+ if (index > 0 && previous !== void 0 && current !== void 0 && !samePeer(previous, current)) {
801
+ rank = index + 1;
802
+ denseRank += 1;
803
+ }
804
+ ranks.push(dense ? denseRank : rank);
805
+ }
806
+ return ranks;
807
+ }
808
+ function cumeDistValues(rows, expr) {
809
+ const sorted = partitionRows(rows, expr);
810
+ const out = [];
811
+ for (let index = 0; index < sorted.length; index += 1) {
812
+ let endPeer = index;
813
+ while (endPeer + 1 < sorted.length) {
814
+ const current = sorted[index];
815
+ const next = sorted[endPeer + 1];
816
+ if (current === void 0 || next === void 0 || !samePeer(current, next))
817
+ break;
818
+ endPeer += 1;
819
+ }
820
+ out.push((endPeer + 1) / rows.length);
821
+ }
822
+ return out;
823
+ }
824
+ function ntileValues(rows, expr) {
825
+ const bucketCount = numericArg(expr, 0, 0);
826
+ if (!Number.isInteger(bucketCount) || bucketCount <= 0) {
827
+ throw new LakeqlError("LAKEQL_TYPE_ERROR", "ntile requires a positive integer bucket count");
828
+ }
829
+ const base = Math.floor(rows.length / bucketCount);
830
+ const extra = rows.length % bucketCount;
831
+ return rows.map((_row, index) => index < (base + 1) * extra ? Math.floor(index / (base + 1)) + 1 : extra + Math.floor((index - (base + 1) * extra) / base) + 1);
832
+ }
833
+ function offsetValues(rows, expr, direction) {
834
+ const offset2 = expr.args.length >= 2 ? numericArg(expr, 1, 1) : 1;
835
+ if (!Number.isInteger(offset2) || offset2 < 0) {
836
+ throw new LakeqlError("LAKEQL_TYPE_ERROR", "lag/lead offset must be a non-negative integer");
837
+ }
838
+ if (expr.ignoreNulls === true)
839
+ return offsetIgnoringNulls(rows, expr, direction, offset2);
840
+ return rows.map((row, index) => {
841
+ const target = rows[index + direction * offset2];
842
+ if (target === void 0)
843
+ return expr.args[2] === void 0 ? null : evaluate(expr.args[2], row);
844
+ return evaluate(valueArg(expr), target);
845
+ });
846
+ }
847
+ function offsetIgnoringNulls(rows, expr, direction, offset2) {
848
+ const arg = valueArg(expr);
849
+ return rows.map((row, index) => {
850
+ if (offset2 === 0)
851
+ return evaluate(arg, row);
852
+ let remaining = offset2;
853
+ for (let targetIndex = index + direction; targetIndex >= 0 && targetIndex < rows.length; targetIndex += direction) {
854
+ const target = rows[targetIndex];
855
+ if (target === void 0 || evaluate(arg, target) === null)
856
+ continue;
857
+ remaining -= 1;
858
+ if (remaining === 0)
859
+ return evaluate(arg, target);
860
+ }
861
+ return expr.args[2] === void 0 ? null : evaluate(expr.args[2], row);
862
+ });
863
+ }
864
+ function nthValue(partition, expr) {
865
+ const nth = numericArg(expr, 1, 1);
866
+ if (!Number.isInteger(nth) || nth <= 0) {
867
+ throw new LakeqlError("LAKEQL_TYPE_ERROR", "nth_value requires a positive integer position");
868
+ }
869
+ return partition.map((_item, index) => {
870
+ const rows = frameValueRows(frameRows(partition, index, expr), expr);
871
+ const target = rows[nth - 1];
872
+ return target === void 0 ? null : evaluate(valueArg(expr), target);
873
+ });
874
+ }
875
+ function valueArg(expr) {
876
+ const arg = expr.args[0];
877
+ if (arg === void 0) {
878
+ throw new LakeqlError("LAKEQL_TYPE_ERROR", `${normalizedWindowName(expr.fn)} requires a value argument`);
879
+ }
880
+ return arg;
881
+ }
882
+ function numericArg(expr, index, fallback) {
883
+ const arg = expr.args[index];
884
+ if (arg === void 0)
885
+ return fallback;
886
+ if (arg.kind !== "literal" || typeof arg.value !== "number") {
887
+ throw new LakeqlError("LAKEQL_TYPE_ERROR", `${normalizedWindowName(expr.fn)} argument must be numeric literal`);
888
+ }
889
+ return arg.value;
890
+ }
891
+ function firstFrameValue(rows, expr) {
892
+ return frameValueRows(rows, expr)[0];
893
+ }
894
+ function lastFrameValue(rows, expr) {
895
+ const values = frameValueRows(rows, expr);
896
+ return values[values.length - 1];
897
+ }
898
+ function frameValueRows(rows, expr) {
899
+ if (expr.ignoreNulls !== true)
900
+ return [...rows];
901
+ const arg = valueArg(expr);
902
+ return rows.filter((row) => evaluate(arg, row) !== null);
903
+ }
904
+
905
+ export { applyWindowsToRows, applyWindowsToRuns, evaluateWindowRows, windowRowsForExpr, windowSortGroups };