data-finder-ts 0.1.3 → 0.1.5

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 (62) hide show
  1. package/README.md +17 -55
  2. package/dist/chunk-BGEX377Q.js +230 -0
  3. package/dist/chunk-BGEX377Q.js.map +1 -0
  4. package/dist/chunk-BKPWV3OF.js +962 -0
  5. package/dist/chunk-BKPWV3OF.js.map +1 -0
  6. package/dist/{chunk-EYXBYV2X.js → chunk-GPHWC533.js} +2 -2
  7. package/dist/chunk-KAK75RVS.js +198 -0
  8. package/dist/chunk-KAK75RVS.js.map +1 -0
  9. package/dist/chunk-MGEL5RWM.js +628 -0
  10. package/dist/chunk-MGEL5RWM.js.map +1 -0
  11. package/dist/chunk-PF6EJZHB.js +1015 -0
  12. package/dist/chunk-PF6EJZHB.js.map +1 -0
  13. package/dist/datafinder/attribute.cjs +595 -18
  14. package/dist/datafinder/attribute.cjs.map +1 -1
  15. package/dist/datafinder/attribute.d.cts +73 -10
  16. package/dist/datafinder/attribute.d.ts +73 -10
  17. package/dist/datafinder/attribute.js +2 -2
  18. package/dist/datafinder/runner.cjs +1589 -288
  19. package/dist/datafinder/runner.cjs.map +1 -1
  20. package/dist/datafinder/runner.d.cts +41 -7
  21. package/dist/datafinder/runner.d.ts +41 -7
  22. package/dist/datafinder/runner.js +12 -6
  23. package/dist/datafinder/sql-generator.cjs +1484 -281
  24. package/dist/datafinder/sql-generator.cjs.map +1 -1
  25. package/dist/datafinder/sql-generator.d.cts +19 -14
  26. package/dist/datafinder/sql-generator.d.ts +19 -14
  27. package/dist/datafinder/sql-generator.js +9 -7
  28. package/dist/datafinder/typed-attributes.cjs +856 -93
  29. package/dist/datafinder/typed-attributes.cjs.map +1 -1
  30. package/dist/datafinder/typed-attributes.d.cts +3 -135
  31. package/dist/datafinder/typed-attributes.d.ts +3 -135
  32. package/dist/datafinder/typed-attributes.js +3 -3
  33. package/dist/dialect-CogGPm_y.d.cts +38 -0
  34. package/dist/dialect-aaG1bDi5.d.ts +38 -0
  35. package/dist/index.cjs +2080 -480
  36. package/dist/index.cjs.map +1 -1
  37. package/dist/index.d.cts +119 -14
  38. package/dist/index.d.ts +119 -14
  39. package/dist/index.js +362 -12
  40. package/dist/index.js.map +1 -1
  41. package/dist/model/milestoning.cjs +529 -0
  42. package/dist/model/milestoning.cjs.map +1 -1
  43. package/dist/model/milestoning.js +2 -2
  44. package/dist/model/relational.cjs +608 -12
  45. package/dist/model/relational.cjs.map +1 -1
  46. package/dist/model/relational.d.cts +456 -47
  47. package/dist/model/relational.d.ts +456 -47
  48. package/dist/model/relational.js +61 -3
  49. package/dist/typed-attributes-CUTf_6Ru.d.ts +328 -0
  50. package/dist/typed-attributes-CdWXq_78.d.cts +328 -0
  51. package/package.json +5 -2
  52. package/dist/chunk-3SSWFES4.js +0 -448
  53. package/dist/chunk-3SSWFES4.js.map +0 -1
  54. package/dist/chunk-5K2Z3YDY.js +0 -393
  55. package/dist/chunk-5K2Z3YDY.js.map +0 -1
  56. package/dist/chunk-6QTCGJWX.js +0 -98
  57. package/dist/chunk-6QTCGJWX.js.map +0 -1
  58. package/dist/chunk-DFYZVBII.js +0 -652
  59. package/dist/chunk-DFYZVBII.js.map +0 -1
  60. package/dist/chunk-V647VNMY.js +0 -135
  61. package/dist/chunk-V647VNMY.js.map +0 -1
  62. /package/dist/{chunk-EYXBYV2X.js.map → chunk-GPHWC533.js.map} +0 -0
@@ -20,37 +20,73 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/datafinder/runner.ts
21
21
  var runner_exports = {};
22
22
  __export(runner_exports, {
23
+ DEFAULT_TIMEOUT_MS: () => DEFAULT_TIMEOUT_MS,
23
24
  FinderResult: () => FinderResult,
25
+ QueryTimeoutError: () => QueryTimeoutError,
24
26
  clearRunner: () => clearRunner,
25
27
  convertInputsAndSelect: () => convertInputsAndSelect,
28
+ convertInputsAndSelectForDateRange: () => convertInputsAndSelectForDateRange,
26
29
  getRunner: () => getRunner,
27
30
  registerRunner: () => registerRunner
28
31
  });
29
32
  module.exports = __toCommonJS(runner_exports);
30
33
 
31
34
  // src/model/relational.ts
35
+ var LIKE_ESCAPE = "!";
36
+ function escapeLike(value) {
37
+ return value.replaceAll(LIKE_ESCAPE, LIKE_ESCAPE + LIKE_ESCAPE).replaceAll("%", LIKE_ESCAPE + "%").replaceAll("_", LIKE_ESCAPE + "_");
38
+ }
32
39
  var RelationalOperationElement = class {
33
40
  };
34
41
  var Operation = class extends RelationalOperationElement {
35
- andOp(other) {
42
+ /** Name this column in the result: `f.price().sum().alias('Total')`, `f.age().gt(30).alias('Adult')`. */
43
+ alias(name) {
44
+ return new Alias(this, name);
45
+ }
46
+ and(other) {
36
47
  return new LogicalOperation(this, "AND" /* AND */, other);
37
48
  }
49
+ /** Combine with `other` using OR. A chain reads left to right: `a.or(b).and(c)` is `(a OR b) AND c`. */
50
+ or(other) {
51
+ return new LogicalOperation(this, "OR" /* OR */, other);
52
+ }
38
53
  };
39
54
  var NoOperation = class extends Operation {
40
- andOp(other) {
55
+ and(other) {
41
56
  return other;
42
57
  }
58
+ /** No filter matches every row, so OR-ing anything with it still matches every row. */
59
+ or(_other) {
60
+ return this;
61
+ }
43
62
  };
44
63
  var ConstantOperation = class extends RelationalOperationElement {
64
+ /** Name this column in the result. */
65
+ alias(name) {
66
+ return new Alias(this, name);
67
+ }
68
+ /** Sort by this expression, smallest first. */
69
+ ascending() {
70
+ return new SortOperation(this, "ASC" /* ASC */);
71
+ }
72
+ /** Sort by this expression, largest first. */
73
+ descending() {
74
+ return new SortOperation(this, "DESC" /* DESC */);
75
+ }
76
+ };
77
+ var NumericConstantOperation = class extends ConstantOperation {
78
+ arithmeticOperand() {
79
+ return this;
80
+ }
45
81
  };
46
- var IntegerConstantOperation = class extends ConstantOperation {
82
+ var IntegerConstantOperation = class extends NumericConstantOperation {
47
83
  constructor(value) {
48
84
  super();
49
85
  this.value = value;
50
86
  }
51
87
  value;
52
88
  };
53
- var FloatConstantOperation = class extends ConstantOperation {
89
+ var FloatConstantOperation = class extends NumericConstantOperation {
54
90
  constructor(value) {
55
91
  super();
56
92
  this.value = value;
@@ -64,6 +100,9 @@ var StringConstantOperation = class extends ConstantOperation {
64
100
  }
65
101
  value;
66
102
  };
103
+ var DATE_ONLY_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
104
+ var DateOnly = class extends Date {
105
+ };
67
106
  var DateConstantOperation = class extends ConstantOperation {
68
107
  constructor(value) {
69
108
  super();
@@ -78,7 +117,7 @@ var DateTimeConstantOperation = class extends ConstantOperation {
78
117
  }
79
118
  value;
80
119
  };
81
- var DecimalConstantOperation = class extends ConstantOperation {
120
+ var DecimalConstantOperation = class extends NumericConstantOperation {
82
121
  constructor(value) {
83
122
  super();
84
123
  this.value = value;
@@ -112,6 +151,20 @@ var IsNullOperation = class extends UnaryOperation {
112
151
  };
113
152
  var IsNotNullOperation = class extends UnaryOperation {
114
153
  };
154
+ var InOperation = class extends UnaryOperation {
155
+ constructor(element, values) {
156
+ super(element);
157
+ this.values = values;
158
+ }
159
+ values;
160
+ };
161
+ var NotInOperation = class extends UnaryOperation {
162
+ constructor(element, values) {
163
+ super(element);
164
+ this.values = values;
165
+ }
166
+ values;
167
+ };
115
168
  var ComparisonOperation = class extends BinaryOperation {
116
169
  constructor(left, operator, right) {
117
170
  super(left, right);
@@ -135,21 +188,41 @@ var WindowSpecification = class {
135
188
  orderBy;
136
189
  };
137
190
  var AggregateOperation = class _AggregateOperation extends UnaryOperation {
138
- constructor(element, operator, displayName, window) {
191
+ constructor(element, operator, displayName, window, separator, orderBy = []) {
139
192
  super(element);
140
193
  this.operator = operator;
141
194
  this.displayName = displayName;
142
195
  this.window = window;
196
+ this.separator = separator;
197
+ this.orderBy = orderBy;
143
198
  }
144
199
  operator;
145
200
  displayName;
146
201
  window;
202
+ separator;
203
+ orderBy;
204
+ /** Sort by this expression, smallest first. */
205
+ ascending() {
206
+ return new SortOperation(this, "ASC" /* ASC */);
207
+ }
208
+ /** Sort by this expression, largest first. */
209
+ descending() {
210
+ return new SortOperation(this, "DESC" /* DESC */);
211
+ }
212
+ arithmeticOperand() {
213
+ return this;
214
+ }
215
+ comparisonOperand() {
216
+ return this;
217
+ }
147
218
  over(partitionBy, orderBy) {
148
219
  return new _AggregateOperation(
149
220
  this.element,
150
221
  this.operator,
151
222
  this.displayName,
152
- new WindowSpecification(partitionBy ?? [], orderBy ?? [])
223
+ new WindowSpecification(partitionBy ?? [], orderBy ?? []),
224
+ this.separator,
225
+ this.orderBy
153
226
  );
154
227
  }
155
228
  };
@@ -167,6 +240,14 @@ var WindowFunctionOperation = class _WindowFunctionOperation extends UnaryOperat
167
240
  secondArg;
168
241
  extraArgs;
169
242
  window;
243
+ /** Sort by this expression, smallest first. */
244
+ ascending() {
245
+ return new SortOperation(this, "ASC" /* ASC */);
246
+ }
247
+ /** Sort by this expression, largest first. */
248
+ descending() {
249
+ return new SortOperation(this, "DESC" /* DESC */);
250
+ }
170
251
  over(partitionBy, orderBy) {
171
252
  return new _WindowFunctionOperation(
172
253
  this.element,
@@ -190,7 +271,38 @@ var ScalarFunctionOperation = class extends UnaryOperation {
190
271
  displayName;
191
272
  secondArg;
192
273
  extraArgs;
274
+ /** Sort by this expression, smallest first. */
275
+ ascending() {
276
+ return new SortOperation(this, "ASC" /* ASC */);
277
+ }
278
+ /** Sort by this expression, largest first. */
279
+ descending() {
280
+ return new SortOperation(this, "DESC" /* DESC */);
281
+ }
282
+ /** This expression as an operand of a comparison. */
283
+ comparisonOperand() {
284
+ return this;
285
+ }
286
+ arithmeticOperand() {
287
+ return this;
288
+ }
289
+ /** Only a function that gives a number can be used in arithmetic. */
290
+ assertNumeric() {
291
+ if (!NUMERIC_FUNCTIONS.has(this.func)) {
292
+ throw new TypeError(`${this.func} does not give a number, so it cannot be used in arithmetic`);
293
+ }
294
+ }
193
295
  };
296
+ var NUMERIC_FUNCTIONS = /* @__PURE__ */ new Set([
297
+ "ABS" /* ABS */,
298
+ "CEILING" /* CEILING */,
299
+ "FLOOR" /* FLOOR */,
300
+ "MOD" /* MOD */,
301
+ "POWER" /* POWER */,
302
+ "SQRT" /* SQRT */,
303
+ "ROUND" /* ROUND */,
304
+ "LENGTH" /* LENGTH */
305
+ ]);
194
306
  var DateExtractOperation = class extends UnaryOperation {
195
307
  constructor(element, part, displayName) {
196
308
  super(element);
@@ -199,6 +311,34 @@ var DateExtractOperation = class extends UnaryOperation {
199
311
  }
200
312
  part;
201
313
  displayName;
314
+ /** Sort by this expression, smallest first. */
315
+ ascending() {
316
+ return new SortOperation(this, "ASC" /* ASC */);
317
+ }
318
+ /** Sort by this expression, largest first. */
319
+ descending() {
320
+ return new SortOperation(this, "DESC" /* DESC */);
321
+ }
322
+ };
323
+ var DateTruncOperation = class extends UnaryOperation {
324
+ constructor(element, part, displayName) {
325
+ super(element);
326
+ this.part = part;
327
+ this.displayName = displayName;
328
+ }
329
+ part;
330
+ displayName;
331
+ /** Sort by this expression, earliest first. */
332
+ ascending() {
333
+ return new SortOperation(this, "ASC" /* ASC */);
334
+ }
335
+ /** Sort by this expression, latest first. */
336
+ descending() {
337
+ return new SortOperation(this, "DESC" /* DESC */);
338
+ }
339
+ comparisonOperand() {
340
+ return this;
341
+ }
202
342
  };
203
343
  var DateArithmeticOperation = class extends UnaryOperation {
204
344
  constructor(element, n, unit, isAdd = true, displayName) {
@@ -212,6 +352,14 @@ var DateArithmeticOperation = class extends UnaryOperation {
212
352
  unit;
213
353
  isAdd;
214
354
  displayName;
355
+ /** Sort by this expression, smallest first. */
356
+ ascending() {
357
+ return new SortOperation(this, "ASC" /* ASC */);
358
+ }
359
+ /** Sort by this expression, largest first. */
360
+ descending() {
361
+ return new SortOperation(this, "DESC" /* DESC */);
362
+ }
215
363
  };
216
364
  var DateDiffOperation = class extends UnaryOperation {
217
365
  constructor(element, other, unit, displayName) {
@@ -223,20 +371,31 @@ var DateDiffOperation = class extends UnaryOperation {
223
371
  other;
224
372
  unit;
225
373
  displayName;
374
+ /** Sort by this expression, smallest first. */
375
+ ascending() {
376
+ return new SortOperation(this, "ASC" /* ASC */);
377
+ }
378
+ /** Sort by this expression, largest first. */
379
+ descending() {
380
+ return new SortOperation(this, "DESC" /* DESC */);
381
+ }
226
382
  };
227
383
  var Column = class extends RelationalOperationElement {
228
- constructor(name, type, owner, primaryKey = false) {
384
+ constructor(name, type, owner, primaryKey = false, nullable = true) {
229
385
  super();
230
386
  this.name = name;
231
387
  this.type = type;
232
388
  this.owner = owner;
233
389
  this.primaryKey = primaryKey;
390
+ this.nullable = nullable && !primaryKey;
234
391
  }
235
392
  name;
236
393
  type;
237
394
  owner;
238
395
  primaryKey;
239
396
  table;
397
+ /** Whether the column may hold NULL. As in Legend, a primary key is never null. */
398
+ nullable;
240
399
  };
241
400
  var Table = class {
242
401
  constructor(name, columns, schema) {
@@ -265,13 +424,28 @@ var Table = class {
265
424
  }
266
425
  };
267
426
  var ColumnWithJoin = class extends RelationalOperationElement {
268
- constructor(column, parent) {
427
+ /**
428
+ * `embedded` is an alternate reference to a flat (denormalized) column on the owning table that
429
+ * holds the same value without the join; the SQL generator uses it when the join can be elided.
430
+ */
431
+ constructor(column, parent, embedded, _optional = false) {
269
432
  super();
270
433
  this.column = column;
271
434
  this.parent = parent;
435
+ this.embedded = embedded;
436
+ this._optional = _optional;
272
437
  }
273
438
  column;
274
439
  parent;
440
+ embedded;
441
+ _optional;
442
+ /** Whether the value can be missing: its property is optional, or a navigation on the way to it is. */
443
+ get optional() {
444
+ for (let node = this.parent; node !== void 0; node = node.parent) {
445
+ if (node.optional) return true;
446
+ }
447
+ return this._optional;
448
+ }
275
449
  };
276
450
  var SortOperation = class {
277
451
  constructor(column, direction) {
@@ -287,19 +461,366 @@ var CountAllOperation = class extends RelationalOperationElement {
287
461
  this.table = table;
288
462
  }
289
463
  table;
464
+ /** Name this column in the result. */
465
+ alias(name) {
466
+ return new Alias(this, name);
467
+ }
468
+ /** Sort by this expression, smallest first. */
469
+ ascending() {
470
+ return new SortOperation(this, "ASC" /* ASC */);
471
+ }
472
+ /** Sort by this expression, largest first. */
473
+ descending() {
474
+ return new SortOperation(this, "DESC" /* DESC */);
475
+ }
476
+ comparisonOperand() {
477
+ return this;
478
+ }
479
+ };
480
+ var Alias = class _Alias extends RelationalOperationElement {
481
+ constructor(element, name) {
482
+ super();
483
+ this.element = element;
484
+ this.name = name;
485
+ }
486
+ element;
487
+ name;
488
+ alias(name) {
489
+ return new _Alias(this.element, name);
490
+ }
491
+ /** Sorts by the column the alias names. */
492
+ ascending() {
493
+ return new SortOperation(this.element, "ASC" /* ASC */);
494
+ }
495
+ /** Sorts by the column the alias names. */
496
+ descending() {
497
+ return new SortOperation(this.element, "DESC" /* DESC */);
498
+ }
499
+ };
500
+ var CaseOperation = class extends Operation {
501
+ constructor(condition, then, fallback, displayName) {
502
+ super();
503
+ this.condition = condition;
504
+ this.then = then;
505
+ this.fallback = fallback;
506
+ this.displayName = displayName;
507
+ }
508
+ condition;
509
+ then;
510
+ fallback;
511
+ displayName;
290
512
  };
513
+ var ExistsOperation = class extends RelationalOperationElement {
514
+ constructor(node) {
515
+ super();
516
+ this.node = node;
517
+ }
518
+ node;
519
+ /** Name this column in the result. */
520
+ alias(name) {
521
+ return new Alias(this, name);
522
+ }
523
+ and(other) {
524
+ return new LogicalOperation(this, "AND" /* AND */, other);
525
+ }
526
+ or(other) {
527
+ return new LogicalOperation(this, "OR" /* OR */, other);
528
+ }
529
+ };
530
+ var NotExistsOperation = class extends RelationalOperationElement {
531
+ constructor(node) {
532
+ super();
533
+ this.node = node;
534
+ }
535
+ node;
536
+ /** Name this column in the result. */
537
+ alias(name) {
538
+ return new Alias(this, name);
539
+ }
540
+ and(other) {
541
+ return new LogicalOperation(this, "AND" /* AND */, other);
542
+ }
543
+ or(other) {
544
+ return new LogicalOperation(this, "OR" /* OR */, other);
545
+ }
546
+ };
547
+ var SelectOperation = class {
548
+ constructor(display, filter, orderBy = [], groupBy = [], limit, table, joinFilters = /* @__PURE__ */ new Map(), distinct = false, offset, having) {
549
+ this.display = display;
550
+ this.filter = filter;
551
+ this.orderBy = orderBy;
552
+ this.groupBy = groupBy;
553
+ this.limit = limit;
554
+ this.table = table;
555
+ this.joinFilters = joinFilters;
556
+ this.distinct = distinct;
557
+ this.offset = offset;
558
+ this.having = having;
559
+ }
560
+ display;
561
+ filter;
562
+ orderBy;
563
+ groupBy;
564
+ limit;
565
+ table;
566
+ joinFilters;
567
+ distinct;
568
+ offset;
569
+ having;
570
+ };
571
+ function containsAggregateOrWindow(op) {
572
+ if (op instanceof AggregateOperation || op instanceof WindowFunctionOperation || op instanceof CountAllOperation) return true;
573
+ if (op instanceof BinaryOperation) return containsAggregateOrWindow(op.left) || containsAggregateOrWindow(op.right);
574
+ if (op instanceof UnaryOperation || op instanceof Alias) return containsAggregateOrWindow(op.element);
575
+ return false;
576
+ }
577
+ function containsAggregate(op) {
578
+ if (op instanceof AggregateOperation || op instanceof CountAllOperation) return true;
579
+ if (op instanceof BinaryOperation) return containsAggregate(op.left) || containsAggregate(op.right);
580
+ if (op instanceof UnaryOperation || op instanceof Alias) return containsAggregate(op.element);
581
+ return false;
582
+ }
583
+ function valueOperand(value) {
584
+ if (typeof value === "boolean") return new BooleanConstantOperation(value);
585
+ if (typeof value === "number" && Number.isFinite(value)) {
586
+ return Number.isInteger(value) ? new IntegerConstantOperation(value) : new FloatConstantOperation(value);
587
+ }
588
+ if (typeof value === "string") return new StringConstantOperation(value);
589
+ if (value instanceof DateOnly) return new DateConstantOperation(value);
590
+ if (value instanceof Date) return new DateTimeConstantOperation(value);
591
+ throw new TypeError(`A value to compare with is a boolean, a finite number, a string or a date, not ${value === null ? "null" : typeof value}`);
592
+ }
593
+ function operandOf(other) {
594
+ if (other instanceof RelationalOperationElement) return other;
595
+ if (typeof other?.cwj === "function") return other.cwj();
596
+ return valueOperand(other);
597
+ }
598
+ var Comparable = class {
599
+ _compared(operator, other) {
600
+ return new ComparisonOperation(this.comparisonOperand(), operator, operandOf(other));
601
+ }
602
+ /** Filter to rows where this expression equals `other`. */
603
+ eq(other) {
604
+ return this._compared("EQUAL" /* EQUAL */, other);
605
+ }
606
+ /** Filter to rows where this expression does not equal `other`. */
607
+ ne(other) {
608
+ return this._compared("NOT_EQUAL" /* NOT_EQUAL */, other);
609
+ }
610
+ /** Filter to rows where this expression is less than `other`. */
611
+ lt(other) {
612
+ return this._compared("LESS_THAN" /* LESS_THAN */, other);
613
+ }
614
+ /** Filter to rows where this expression is at most `other`. */
615
+ lte(other) {
616
+ return this._compared("LESS_THAN_OR_EQUAL_TO" /* LESS_THAN_OR_EQUAL_TO */, other);
617
+ }
618
+ /** Filter to rows where this expression is greater than `other`. */
619
+ gt(other) {
620
+ return this._compared("GREATER_THAN" /* GREATER_THAN */, other);
621
+ }
622
+ /** Filter to rows where this expression is at least `other`. */
623
+ gte(other) {
624
+ return this._compared("GREATER_THAN_OR_EQUAL_TO" /* GREATER_THAN_OR_EQUAL_TO */, other);
625
+ }
626
+ };
627
+ function sample(ddof) {
628
+ if (ddof !== 0 && ddof !== 1) throw new RangeError(`ddof is 0 (population) or 1 (sample), not ${ddof}`);
629
+ return ddof === 1;
630
+ }
631
+ var StringPredicates = class {
632
+ _like(pattern) {
633
+ return new ComparisonOperation(this.comparisonOperand(), "LIKE" /* LIKE */, new StringConstantOperation(pattern));
634
+ }
635
+ /** Filter to rows where this text contains `value`. */
636
+ contains(value) {
637
+ return this._like(`%${escapeLike(value)}%`);
638
+ }
639
+ /** Filter to rows where this text starts with `prefix`. */
640
+ startsWith(prefix) {
641
+ return this._like(`${escapeLike(prefix)}%`);
642
+ }
643
+ /** Filter to rows where this text ends with `suffix`. */
644
+ endsWith(suffix) {
645
+ return this._like(`%${escapeLike(suffix)}`);
646
+ }
647
+ };
648
+ var Aggregates = class {
649
+ _aggregate(operator, label) {
650
+ return new AggregateOperation(this, operator, label + " Expression");
651
+ }
652
+ /** Sum of the values in the group. */
653
+ sum() {
654
+ return this._aggregate("SUM" /* SUM */, "Sum");
655
+ }
656
+ /** Smallest value in the group. */
657
+ min() {
658
+ return this._aggregate("MIN" /* MIN */, "Min");
659
+ }
660
+ /** Largest value in the group. */
661
+ max() {
662
+ return this._aggregate("MAX" /* MAX */, "Max");
663
+ }
664
+ /** Arithmetic mean of the values in the group. */
665
+ average() {
666
+ return this._aggregate("AVERAGE" /* AVERAGE */, "Average");
667
+ }
668
+ /** Number of non-null values in the group. */
669
+ count() {
670
+ return this._aggregate("COUNT" /* COUNT */, "Count");
671
+ }
672
+ /** Standard deviation of the values in the group: the sample one by default, `ddof = 0` for the population one. */
673
+ std(ddof = 1) {
674
+ return this._aggregate(sample(ddof) ? "STDDEV_SAMP" /* STDDEV_SAMP */ : "STDDEV_POP" /* STDDEV_POP */, "Std");
675
+ }
676
+ /** Variance of the values in the group: the sample one by default, `ddof = 0` for the population one. */
677
+ var(ddof = 1) {
678
+ return this._aggregate(sample(ddof) ? "VAR_SAMP" /* VAR_SAMP */ : "VAR_POP" /* VAR_POP */, "Var");
679
+ }
680
+ };
681
+ var BooleanAggregates = class {
682
+ /** True for a group where every row satisfies the condition. */
683
+ all() {
684
+ return new AggregateOperation(this, "BOOL_AND" /* BOOL_AND */, "All Expression");
685
+ }
686
+ /** True for a group where at least one row satisfies the condition. */
687
+ any() {
688
+ return new AggregateOperation(this, "BOOL_OR" /* BOOL_OR */, "Any Expression");
689
+ }
690
+ };
691
+ function applyMixins(derived, bases) {
692
+ for (const base of bases) {
693
+ for (const name of Object.getOwnPropertyNames(base.prototype)) {
694
+ if (name === "constructor" || Object.prototype.hasOwnProperty.call(derived.prototype, name)) continue;
695
+ Object.defineProperty(derived.prototype, name, Object.getOwnPropertyDescriptor(base.prototype, name));
696
+ }
697
+ }
698
+ }
699
+ var Arithmetic = class {
700
+ /** Raises if this expression is not a number. */
701
+ assertNumeric() {
702
+ }
703
+ _arithmetic(operator, other) {
704
+ this.assertNumeric();
705
+ let operand;
706
+ if (typeof other === "number") {
707
+ if (!Number.isFinite(other)) throw new TypeError("Arithmetic is between numbers and numeric columns, not a non-finite number");
708
+ operand = valueOperand(other);
709
+ } else if (other !== null && typeof other === "object" && typeof other.arithmeticOperand === "function") {
710
+ other.assertNumeric?.();
711
+ operand = other.arithmeticOperand();
712
+ } else {
713
+ throw new TypeError(`Arithmetic is between numbers and numeric columns, not ${other === null ? "null" : typeof other}`);
714
+ }
715
+ return new ArithmeticOperation(this.arithmeticOperand(), operator, operand);
716
+ }
717
+ /** `this + other`. */
718
+ plus(other) {
719
+ return this._arithmetic("+" /* ADD */, other);
720
+ }
721
+ /** `this - other`. */
722
+ minus(other) {
723
+ return this._arithmetic("-" /* SUBTRACT */, other);
724
+ }
725
+ /** `this * other`. */
726
+ times(other) {
727
+ return this._arithmetic("*" /* MULTIPLY */, other);
728
+ }
729
+ /** `this / other`. */
730
+ dividedBy(other) {
731
+ return this._arithmetic("/" /* DIVIDE */, other);
732
+ }
733
+ };
734
+ var ArithmeticOperation = class extends BinaryOperation {
735
+ constructor(left, operator, right, displayName) {
736
+ super(left, right);
737
+ this.operator = operator;
738
+ this.displayName = displayName;
739
+ }
740
+ operator;
741
+ displayName;
742
+ /** Sort by this expression, smallest first. */
743
+ ascending() {
744
+ return new SortOperation(this, "ASC" /* ASC */);
745
+ }
746
+ /** Sort by this expression, largest first. */
747
+ descending() {
748
+ return new SortOperation(this, "DESC" /* DESC */);
749
+ }
750
+ arithmeticOperand() {
751
+ return this;
752
+ }
753
+ comparisonOperand() {
754
+ return this;
755
+ }
756
+ };
757
+ var ConcatOperation = class extends Operation {
758
+ constructor(parts, displayName) {
759
+ super();
760
+ this.parts = parts;
761
+ this.displayName = displayName;
762
+ }
763
+ parts;
764
+ displayName;
765
+ /** Join more text on the end: `f.first().concat(' ', f.last())`. A concatenation joins into one. */
766
+ concat(...more) {
767
+ return concat(this, ...more);
768
+ }
769
+ /** Sort by this expression, smallest first. */
770
+ ascending() {
771
+ return new SortOperation(this, "ASC" /* ASC */);
772
+ }
773
+ /** Sort by this expression, largest first. */
774
+ descending() {
775
+ return new SortOperation(this, "DESC" /* DESC */);
776
+ }
777
+ comparisonOperand() {
778
+ return this;
779
+ }
780
+ };
781
+ function concat(...parts) {
782
+ const partsOf = (part) => {
783
+ if (part instanceof ConcatOperation) return part.parts;
784
+ if (typeof part === "string") return [new StringConstantOperation(part)];
785
+ if (part instanceof RelationalOperationElement) return [part];
786
+ if (part !== null && typeof part === "object" && typeof part.functionInput === "function") return [part.functionInput()];
787
+ throw new TypeError(`Text is joined with text, not ${part === null ? "null" : typeof part}`);
788
+ };
789
+ return new ConcatOperation(parts.flatMap(partsOf));
790
+ }
791
+ applyMixins(ConcatOperation, [Comparable, StringPredicates]);
792
+ applyMixins(DateTruncOperation, [Comparable]);
793
+ applyMixins(ArithmeticOperation, [Arithmetic, Comparable]);
794
+ applyMixins(ScalarFunctionOperation, [Comparable, Arithmetic, StringPredicates]);
795
+ applyMixins(AggregateOperation, [Arithmetic, Comparable, StringPredicates]);
796
+ applyMixins(CountAllOperation, [Comparable]);
797
+ applyMixins(NumericConstantOperation, [Arithmetic]);
798
+ applyMixins(ConstantOperation, [Aggregates]);
799
+ applyMixins(ArithmeticOperation, [Aggregates]);
800
+ applyMixins(ScalarFunctionOperation, [Aggregates]);
801
+ applyMixins(CaseOperation, [Aggregates]);
802
+ applyMixins(IsNullOperation, [BooleanAggregates]);
803
+ applyMixins(IsNotNullOperation, [BooleanAggregates]);
804
+ applyMixins(InOperation, [BooleanAggregates]);
805
+ applyMixins(NotInOperation, [BooleanAggregates]);
806
+ applyMixins(ComparisonOperation, [BooleanAggregates]);
807
+ applyMixins(LogicalOperation, [BooleanAggregates]);
291
808
 
292
809
  // src/datafinder/attribute.ts
293
- var Attribute = class {
810
+ var Attribute = class _Attribute {
294
811
  _displayName;
295
812
  _column;
296
813
  _owner;
297
814
  _parent;
298
- constructor(displayName, columnName, columnDbType, owner, parent) {
815
+ _embedded;
816
+ _optional;
817
+ constructor(displayName, columnName, columnDbType, owner, parent, embedded, nullable = true, optional = false) {
818
+ this._optional = optional;
299
819
  this._displayName = displayName;
300
- this._column = new Column(columnName, columnDbType, owner);
820
+ this._column = new Column(columnName, columnDbType, owner, false, nullable);
301
821
  this._owner = owner;
302
822
  this._parent = parent;
823
+ this._embedded = embedded;
303
824
  }
304
825
  column() {
305
826
  return this._column;
@@ -313,33 +834,102 @@ var Attribute = class {
313
834
  displayName() {
314
835
  return this._displayName;
315
836
  }
837
+ /** Alternate attribute on a flat column of the owning table, used in place of this one when its join is elided. */
838
+ embedded() {
839
+ return this._embedded;
840
+ }
841
+ /** @internal The expression a function of this attribute applies to: its column. */
842
+ functionInput() {
843
+ return this.cwj();
844
+ }
845
+ /** @internal What a function of this attribute is labelled after: its display name. */
846
+ functionLabel() {
847
+ return this._displayName;
848
+ }
849
+ /** @internal This attribute as an operand of arithmetic: its column. */
850
+ arithmeticOperand() {
851
+ return this.cwj();
852
+ }
316
853
  cwj() {
317
- return new ColumnWithJoin(this._column, this._parent);
854
+ return new ColumnWithJoin(this._column, this._parent, this._embedded?.cwj(), this._optional);
318
855
  }
856
+ /** Count the number of non-null values. Use in a projection with `groupBy` to aggregate per group. */
319
857
  count() {
320
858
  return new AggregateOperation(
321
- new ColumnWithJoin(this._column, this._parent),
859
+ this.cwj(),
322
860
  "COUNT" /* COUNT */,
323
861
  this._displayName + " Count"
324
862
  );
325
863
  }
864
+ /** Name this column in the result: `f.price().sum().alias('Total')`. */
865
+ alias(name) {
866
+ return new Alias(this, name);
867
+ }
868
+ /** The number of different non-NULL values in the group: `COUNT(DISTINCT column)`. Use with `groupBy` to count per group. */
869
+ countDistinct() {
870
+ return new AggregateOperation(this.cwj(), "COUNT_DISTINCT" /* COUNT_DISTINCT */, this._displayName + " Count Distinct");
871
+ }
872
+ /** The value, or `defaultValue` when it is NULL: `name().orElse('N/A')`. Like `COALESCE` in SQL. */
873
+ orElse(defaultValue) {
874
+ if (!["string", "number", "boolean"].includes(typeof defaultValue)) {
875
+ throw new TypeError(`An orElse default is a string, number or boolean, not ${defaultValue === null ? "null" : typeof defaultValue}`);
876
+ }
877
+ return new ScalarFunctionOperation(this.cwj(), "COALESCE" /* COALESCE */, "Or Else " + this._displayName, void 0, [defaultValue]);
878
+ }
879
+ /** Sort this attribute in ascending order. Pass to `orderBy` or a window function's `orderBy`. */
326
880
  ascending() {
327
- return new SortOperation(new ColumnWithJoin(this._column, this._parent), "ASC" /* ASC */);
881
+ return new SortOperation(this.cwj(), "ASC" /* ASC */);
328
882
  }
883
+ /** Sort this attribute in descending order. Pass to `orderBy` or a window function's `orderBy`. */
329
884
  descending() {
330
- return new SortOperation(new ColumnWithJoin(this._column, this._parent), "DESC" /* DESC */);
885
+ return new SortOperation(this.cwj(), "DESC" /* DESC */);
331
886
  }
887
+ /** Filter to rows where this attribute has no value. */
332
888
  isNull() {
333
- return new IsNullOperation(new ColumnWithJoin(this._column, this._parent));
889
+ return new IsNullOperation(this.cwj());
334
890
  }
891
+ /** Filter to rows where this attribute has a value. */
335
892
  isNotNull() {
336
- return new IsNotNullOperation(new ColumnWithJoin(this._column, this._parent));
893
+ return new IsNotNullOperation(this.cwj());
894
+ }
895
+ /** Wrap a value as a constant of this attribute's type. Overridden by the typed attributes. */
896
+ toConstant(_value) {
897
+ throw new TypeError(`${this.constructor.name} does not support isIn / isNotIn`);
898
+ }
899
+ _constants(values) {
900
+ const constants = values.map((v) => this.toConstant(v));
901
+ if (constants.length === 0) throw new Error("isIn / isNotIn need at least one value");
902
+ return constants;
903
+ }
904
+ /** Filter to rows where this attribute equals any of `values`. */
905
+ isIn(values) {
906
+ return new InOperation(this.cwj(), this._constants(values));
907
+ }
908
+ /** Filter to rows where this attribute equals none of `values`. Rows with no value are excluded, as in SQL. */
909
+ isNotIn(values) {
910
+ return new NotInOperation(this.cwj(), this._constants(values));
337
911
  }
338
912
  _windowSpec(partitionBy, orderBy) {
339
- const pb = partitionBy === void 0 ? [] : Array.isArray(partitionBy) ? partitionBy : [partitionBy];
913
+ const pbArr = partitionBy === void 0 ? [] : Array.isArray(partitionBy) ? partitionBy : [partitionBy];
914
+ const pb = pbArr.map((x) => x instanceof _Attribute ? x.cwj() : x);
340
915
  const ob = orderBy === void 0 ? [] : Array.isArray(orderBy) ? orderBy : [orderBy];
341
916
  return new WindowSpecification(pb, ob);
342
917
  }
918
+ /**
919
+ * Assign a rank to each row within the ordered window, following pandas `Series.rank` conventions.
920
+ *
921
+ * `method` controls tie-breaking (default `'min'`):
922
+ * - `'min'` — tied rows share the lowest rank; the next rank skips.
923
+ * - `'dense'` — like `'min'` but ranks are contiguous (no gaps).
924
+ * - `'first'` — each row gets a unique sequential integer (no ties).
925
+ * - `'max'` — only meaningful with `pct: true` (see below).
926
+ *
927
+ * When `pct: true` the result is a relative rank between 0 and 1:
928
+ * - `method: 'min'` (default) → fraction of values strictly below this row (PERCENT_RANK).
929
+ * - `method: 'max'` → fraction of values at or below this row (cumulative distribution).
930
+ *
931
+ * `partitionBy` restarts the ranking within each group of distinct values.
932
+ */
343
933
  rank(opts = {}) {
344
934
  const { method = "min", pct = false, partitionBy, orderBy } = opts;
345
935
  let func;
@@ -364,6 +954,11 @@ var Attribute = class {
364
954
  }
365
955
  return new WindowFunctionOperation(null, func, name, void 0, [], this._windowSpec(partitionBy, orderBy));
366
956
  }
957
+ /**
958
+ * Split rows into `buckets` equal-sized quantile bins ordered by `orderBy`.
959
+ * Each row receives a bucket number from 1 (lowest) to `buckets` (highest).
960
+ * Named after `pandas.qcut`.
961
+ */
367
962
  qcut(buckets, opts = {}) {
368
963
  return new WindowFunctionOperation(
369
964
  null,
@@ -374,12 +969,17 @@ var Attribute = class {
374
969
  this._windowSpec(opts.partitionBy, opts.orderBy)
375
970
  );
376
971
  }
972
+ /**
973
+ * Return the value of this attribute from `periods` rows earlier (positive) or later (negative)
974
+ * within the ordered window, following `pandas.Series.shift`.
975
+ * Rows with no neighbour at the given offset return `null` (or `fillValue` if provided).
976
+ */
377
977
  shift(periods = 1, opts = {}) {
378
978
  const extraArgs = opts.fillValue !== void 0 ? [opts.fillValue] : [];
379
979
  const func = periods >= 0 ? "LAG" /* LAG */ : "LEAD" /* LEAD */;
380
980
  const label = periods >= 0 ? "Lag" : "Lead";
381
981
  return new WindowFunctionOperation(
382
- new ColumnWithJoin(this._column, this._parent),
982
+ this.cwj(),
383
983
  func,
384
984
  label + " " + this._displayName,
385
985
  Math.abs(periods),
@@ -387,9 +987,13 @@ var Attribute = class {
387
987
  this._windowSpec(opts.partitionBy, opts.orderBy)
388
988
  );
389
989
  }
990
+ /**
991
+ * Return the first value of this attribute in the window frame.
992
+ * Use `partitionBy` to restart the frame for each group.
993
+ */
390
994
  first(opts = {}) {
391
995
  return new WindowFunctionOperation(
392
- new ColumnWithJoin(this._column, this._parent),
996
+ this.cwj(),
393
997
  "FIRST_VALUE" /* FIRST_VALUE */,
394
998
  "First " + this._displayName,
395
999
  void 0,
@@ -397,9 +1001,13 @@ var Attribute = class {
397
1001
  this._windowSpec(opts.partitionBy, opts.orderBy)
398
1002
  );
399
1003
  }
1004
+ /**
1005
+ * Return the last value of this attribute in the window frame.
1006
+ * Use `partitionBy` to restart the frame for each group.
1007
+ */
400
1008
  last(opts = {}) {
401
1009
  return new WindowFunctionOperation(
402
- new ColumnWithJoin(this._column, this._parent),
1010
+ this.cwj(),
403
1011
  "LAST_VALUE" /* LAST_VALUE */,
404
1012
  "Last " + this._displayName,
405
1013
  void 0,
@@ -486,217 +1094,538 @@ var MilestonedTable = class extends Table {
486
1094
  milestoningColumns;
487
1095
  };
488
1096
 
1097
+ // src/datafinder/scalar-functions.ts
1098
+ var StringFunctionOperation = class extends ScalarFunctionOperation {
1099
+ functionInput() {
1100
+ return this;
1101
+ }
1102
+ functionLabel() {
1103
+ return labelOf(this);
1104
+ }
1105
+ };
1106
+ var NumericFunctionOperation = class extends ScalarFunctionOperation {
1107
+ functionInput() {
1108
+ return this;
1109
+ }
1110
+ functionLabel() {
1111
+ return labelOf(this);
1112
+ }
1113
+ };
1114
+ function labelOf(operation) {
1115
+ return operation.displayName ?? operation.func.charAt(0) + operation.func.slice(1).toLowerCase();
1116
+ }
1117
+ function stringFunction(element, func, label, secondArg, extraArgs = []) {
1118
+ return new StringFunctionOperation(element, func, label, secondArg, extraArgs);
1119
+ }
1120
+ function numberFunction(element, func, label, secondArg) {
1121
+ return new NumericFunctionOperation(element, func, label, secondArg);
1122
+ }
1123
+ var StringFunctions = class {
1124
+ /** Join text on the end: `f.first().concat(' ', f.last())`. A NULL part makes the whole NULL. */
1125
+ concat(...more) {
1126
+ return concat(this, ...more);
1127
+ }
1128
+ /** Convert every character to uppercase. */
1129
+ toUpperCase() {
1130
+ return stringFunction(this.functionInput(), "UPPER" /* UPPER */, "Upper " + this.functionLabel());
1131
+ }
1132
+ /** Convert every character to lowercase. */
1133
+ toLowerCase() {
1134
+ return stringFunction(this.functionInput(), "LOWER" /* LOWER */, "Lower " + this.functionLabel());
1135
+ }
1136
+ /** Remove leading and trailing whitespace. */
1137
+ trim() {
1138
+ return stringFunction(this.functionInput(), "TRIM" /* TRIM */, "Trim " + this.functionLabel());
1139
+ }
1140
+ /** Remove leading whitespace. */
1141
+ trimStart() {
1142
+ return stringFunction(this.functionInput(), "LTRIM" /* LTRIM */, "Trim Start " + this.functionLabel());
1143
+ }
1144
+ /** Remove trailing whitespace. */
1145
+ trimEnd() {
1146
+ return stringFunction(this.functionInput(), "RTRIM" /* RTRIM */, "Trim End " + this.functionLabel());
1147
+ }
1148
+ /** Return the number of characters in the string. */
1149
+ length() {
1150
+ return numberFunction(this.functionInput(), "LENGTH" /* LENGTH */, "Length " + this.functionLabel());
1151
+ }
1152
+ /** Return the string with characters in reverse order. */
1153
+ reverse() {
1154
+ return stringFunction(this.functionInput(), "REVERSE" /* REVERSE */, "Reverse " + this.functionLabel());
1155
+ }
1156
+ /** Return the first `n` characters. */
1157
+ left(n) {
1158
+ return stringFunction(this.functionInput(), "LEFT" /* LEFT */, "Left " + this.functionLabel(), n);
1159
+ }
1160
+ /** Return the last `n` characters. */
1161
+ right(n) {
1162
+ return stringFunction(this.functionInput(), "RIGHT" /* RIGHT */, "Right " + this.functionLabel(), n);
1163
+ }
1164
+ /** Return the string repeated `n` times. */
1165
+ repeat(n) {
1166
+ return stringFunction(this.functionInput(), "REPEAT" /* REPEAT */, "Repeat " + this.functionLabel(), n);
1167
+ }
1168
+ /** Replace all occurrences of `fromStr` with `toStr`. */
1169
+ replace(fromStr, toStr) {
1170
+ return stringFunction(this.functionInput(), "REPLACE" /* REPLACE */, "Replace " + this.functionLabel(), void 0, [fromStr, toStr]);
1171
+ }
1172
+ /**
1173
+ * Extract a portion of the string.
1174
+ * `start` is zero-based (following Python convention); `length` is optional.
1175
+ */
1176
+ substring(start, length) {
1177
+ const args = length !== void 0 ? [start + 1, length] : [start + 1];
1178
+ return stringFunction(this.functionInput(), "SUBSTRING" /* SUBSTRING */, "Substring " + this.functionLabel(), void 0, args);
1179
+ }
1180
+ };
1181
+ var NumericFunctions = class {
1182
+ /** Absolute value (distance from zero, always non-negative). */
1183
+ abs() {
1184
+ return numberFunction(this.functionInput(), "ABS" /* ABS */, "Abs " + this.functionLabel());
1185
+ }
1186
+ /** Round up to the nearest integer. */
1187
+ ceil() {
1188
+ return numberFunction(this.functionInput(), "CEILING" /* CEILING */, "Ceil " + this.functionLabel());
1189
+ }
1190
+ /** Round down to the nearest integer. */
1191
+ floor() {
1192
+ return numberFunction(this.functionInput(), "FLOOR" /* FLOOR */, "Floor " + this.functionLabel());
1193
+ }
1194
+ /** Square root. */
1195
+ sqrt() {
1196
+ return numberFunction(this.functionInput(), "SQRT" /* SQRT */, "Sqrt " + this.functionLabel());
1197
+ }
1198
+ /** Remainder after dividing by `n`. */
1199
+ mod(n) {
1200
+ return numberFunction(this.functionInput(), "MOD" /* MOD */, "Mod " + this.functionLabel(), n);
1201
+ }
1202
+ /** Raise to the power of `n`. */
1203
+ power(n) {
1204
+ return numberFunction(this.functionInput(), "POWER" /* POWER */, "Power " + this.functionLabel(), n);
1205
+ }
1206
+ /** Round to `d` decimal places, or to the nearest integer when `d` is omitted. */
1207
+ round(d) {
1208
+ return numberFunction(this.functionInput(), "ROUND" /* ROUND */, "Round " + this.functionLabel(), d);
1209
+ }
1210
+ };
1211
+ applyMixins(StringFunctionOperation, [StringFunctions]);
1212
+ applyMixins(NumericFunctionOperation, [NumericFunctions]);
1213
+
489
1214
  // src/datafinder/typed-attributes.ts
1215
+ var MinMax = class {
1216
+ /** Smallest value in the group. */
1217
+ min() {
1218
+ return new AggregateOperation(this.cwj(), "MIN" /* MIN */, "Min " + this.displayName());
1219
+ }
1220
+ /** Largest value in the group. */
1221
+ max() {
1222
+ return new AggregateOperation(this.cwj(), "MAX" /* MAX */, "Max " + this.displayName());
1223
+ }
1224
+ };
1225
+ var StringAttribute = class extends Attribute {
1226
+ constructor(displayName, columnName, columnDbType, owner, parent, embedded, nullable = true, optional = false) {
1227
+ super(displayName, columnName, columnDbType, owner, parent, embedded, nullable, optional);
1228
+ }
1229
+ toConstant(value) {
1230
+ return new StringConstantOperation(String(value));
1231
+ }
1232
+ /**
1233
+ * Join the values of a group into one text with `separator` between them. `orderBy` is the order they are
1234
+ * joined in (`[f.name().ascending()]`); without it the order is not defined. Missing values are skipped.
1235
+ */
1236
+ join(separator = "", orderBy = []) {
1237
+ return new AggregateOperation(this.cwj(), "STRING_AGG" /* STRING_AGG */, "Join " + this.displayName(), void 0, separator, orderBy);
1238
+ }
1239
+ /** Filter to rows where this attribute equals `value`. */
1240
+ eq(value) {
1241
+ return new ComparisonOperation(this.cwj(), "EQUAL" /* EQUAL */, new StringConstantOperation(value));
1242
+ }
1243
+ /** Filter to rows where this attribute does not equal `value`. */
1244
+ ne(value) {
1245
+ return new ComparisonOperation(this.cwj(), "NOT_EQUAL" /* NOT_EQUAL */, new StringConstantOperation(value));
1246
+ }
1247
+ /** Filter to rows where this attribute contains `value` as a substring. */
1248
+ contains(value) {
1249
+ return new ComparisonOperation(this.cwj(), "LIKE" /* LIKE */, new StringConstantOperation(`%${escapeLike(value)}%`));
1250
+ }
1251
+ /** Filter to rows where this attribute begins with `prefix`. */
1252
+ startsWith(prefix) {
1253
+ return new ComparisonOperation(this.cwj(), "LIKE" /* LIKE */, new StringConstantOperation(`${escapeLike(prefix)}%`));
1254
+ }
1255
+ /** Filter to rows where this attribute ends with `suffix`. */
1256
+ endsWith(suffix) {
1257
+ return new ComparisonOperation(this.cwj(), "LIKE" /* LIKE */, new StringConstantOperation(`%${escapeLike(suffix)}`));
1258
+ }
1259
+ /** Filter to rows where this attribute does not contain `value` as a substring. */
1260
+ notContains(value) {
1261
+ return new ComparisonOperation(this.cwj(), "NOT_LIKE" /* NOT_LIKE */, new StringConstantOperation(`%${escapeLike(value)}%`));
1262
+ }
1263
+ };
1264
+ applyMixins(StringAttribute, [MinMax, StringFunctions]);
1265
+ var NumericAttribute = class extends Attribute {
1266
+ constructor(displayName, columnName, columnDbType, owner, parent, embedded, nullable = true, optional = false) {
1267
+ super(displayName, columnName, columnDbType, owner, parent, embedded, nullable, optional);
1268
+ }
1269
+ /** Sum of all values in the group. */
1270
+ sum() {
1271
+ return new AggregateOperation(this.cwj(), "SUM" /* SUM */, "Sum " + this.displayName());
1272
+ }
1273
+ /** Arithmetic mean of all values in the group. */
1274
+ average() {
1275
+ return new AggregateOperation(this.cwj(), "AVERAGE" /* AVERAGE */, "Average " + this.displayName());
1276
+ }
1277
+ /** Standard deviation of the values in the group: the sample one by default, `ddof = 0` for the population one, as `Series.std`. */
1278
+ std(ddof = 1) {
1279
+ const operator = sample(ddof) ? "STDDEV_SAMP" /* STDDEV_SAMP */ : "STDDEV_POP" /* STDDEV_POP */;
1280
+ return new AggregateOperation(this.cwj(), operator, "Std " + this.displayName());
1281
+ }
1282
+ /** Variance of the values in the group: the sample one by default, `ddof = 0` for the population one, as `Series.var`. */
1283
+ var(ddof = 1) {
1284
+ const operator = sample(ddof) ? "VAR_SAMP" /* VAR_SAMP */ : "VAR_POP" /* VAR_POP */;
1285
+ return new AggregateOperation(this.cwj(), operator, "Var " + this.displayName());
1286
+ }
1287
+ };
1288
+ applyMixins(NumericAttribute, [MinMax, NumericFunctions, Arithmetic]);
490
1289
  function parseDate(value) {
491
1290
  if (value instanceof Date) return value;
492
1291
  const d = new Date(value);
493
1292
  if (isNaN(d.getTime())) throw new Error(`Cannot parse date: ${value}`);
494
1293
  return d;
495
1294
  }
1295
+ function temporalConstant(value) {
1296
+ if (typeof value === "string" && DATE_ONLY_PATTERN.test(value)) return new DateConstantOperation(new DateOnly(value));
1297
+ const date = parseDate(value);
1298
+ return date instanceof DateOnly ? new DateConstantOperation(date) : new DateTimeConstantOperation(date);
1299
+ }
496
1300
  var DateAttribute = class extends Attribute {
497
- constructor(displayName, columnName, columnDbType, owner, parent) {
498
- super(displayName, columnName, columnDbType, owner, parent);
1301
+ constructor(displayName, columnName, columnDbType, owner, parent, embedded, nullable = true, optional = false) {
1302
+ super(displayName, columnName, columnDbType, owner, parent, embedded, nullable, optional);
1303
+ }
1304
+ toConstant(value) {
1305
+ return new DateConstantOperation(parseDate(value));
499
1306
  }
1307
+ /** Filter to rows where this date equals `value`. */
500
1308
  eq(value) {
501
1309
  return new ComparisonOperation(this.cwj(), "EQUAL" /* EQUAL */, new DateConstantOperation(parseDate(value)));
502
1310
  }
1311
+ /** Filter to rows where this date does not equal `value`. */
503
1312
  ne(value) {
504
1313
  return new ComparisonOperation(this.cwj(), "NOT_EQUAL" /* NOT_EQUAL */, new DateConstantOperation(parseDate(value)));
505
1314
  }
1315
+ /** Filter to rows where this date is after `value`. */
506
1316
  gt(value) {
507
1317
  return new ComparisonOperation(this.cwj(), "GREATER_THAN" /* GREATER_THAN */, new DateConstantOperation(parseDate(value)));
508
1318
  }
1319
+ /** Filter to rows where this date is before `value`. */
509
1320
  lt(value) {
510
1321
  return new ComparisonOperation(this.cwj(), "LESS_THAN" /* LESS_THAN */, new DateConstantOperation(parseDate(value)));
511
1322
  }
1323
+ /** Filter to rows where this date is on or after `value`. */
512
1324
  gte(value) {
513
1325
  return new ComparisonOperation(this.cwj(), "GREATER_THAN_OR_EQUAL_TO" /* GREATER_THAN_OR_EQUAL_TO */, new DateConstantOperation(parseDate(value)));
514
1326
  }
1327
+ /** Filter to rows where this date is on or before `value`. */
515
1328
  lte(value) {
516
1329
  return new ComparisonOperation(this.cwj(), "LESS_THAN_OR_EQUAL_TO" /* LESS_THAN_OR_EQUAL_TO */, new DateConstantOperation(parseDate(value)));
517
1330
  }
1331
+ /** Extract the four-digit calendar year. */
518
1332
  year() {
519
1333
  return new DateExtractOperation(this.cwj(), "YEAR" /* YEAR */, "Year " + this.displayName());
520
1334
  }
1335
+ /** Extract the month number (1–12). */
521
1336
  month() {
522
1337
  return new DateExtractOperation(this.cwj(), "MONTH" /* MONTH */, "Month " + this.displayName());
523
1338
  }
1339
+ /** Extract the day of the month (1–31). */
524
1340
  day() {
525
1341
  return new DateExtractOperation(this.cwj(), "DAY" /* DAY */, "Day " + this.displayName());
526
1342
  }
1343
+ /** Extract the quarter (1–4). */
527
1344
  quarter() {
528
1345
  return new DateExtractOperation(this.cwj(), "QUARTER" /* QUARTER */, "Quarter " + this.displayName());
529
1346
  }
1347
+ /** Extract the ISO week number (1–53). */
530
1348
  week() {
531
1349
  return new DateExtractOperation(this.cwj(), "WEEK" /* WEEK */, "Week " + this.displayName());
532
1350
  }
1351
+ /** Extract the day of the week (0 = Sunday through 6 = Saturday). */
533
1352
  dayOfWeek() {
534
1353
  return new DateExtractOperation(this.cwj(), "DOW" /* DOW */, "Day Of Week " + this.displayName());
535
1354
  }
1355
+ /** The date of the first day of the week the date falls in (a Monday). */
1356
+ firstDayOfWeek() {
1357
+ return new DateTruncOperation(this.cwj(), "WEEK" /* WEEK */, "First Day Of Week " + this.displayName());
1358
+ }
1359
+ /** The date of the first day of the month the date falls in. */
1360
+ firstDayOfMonth() {
1361
+ return new DateTruncOperation(this.cwj(), "MONTH" /* MONTH */, "First Day Of Month " + this.displayName());
1362
+ }
1363
+ /** The date of the first day of the quarter the date falls in. */
1364
+ firstDayOfQuarter() {
1365
+ return new DateTruncOperation(this.cwj(), "QUARTER" /* QUARTER */, "First Day Of Quarter " + this.displayName());
1366
+ }
1367
+ /** The date of the first day of the year the date falls in. */
1368
+ firstDayOfYear() {
1369
+ return new DateTruncOperation(this.cwj(), "YEAR" /* YEAR */, "First Day Of Year " + this.displayName());
1370
+ }
1371
+ /** Add `n` days to this date. */
536
1372
  addDays(n) {
537
1373
  return new DateArithmeticOperation(this.cwj(), n, "DAY" /* DAY */, true, "Add Days " + this.displayName());
538
1374
  }
1375
+ /** Add `n` months to this date. */
539
1376
  addMonths(n) {
540
1377
  return new DateArithmeticOperation(this.cwj(), n, "MONTH" /* MONTH */, true, "Add Months " + this.displayName());
541
1378
  }
1379
+ /** Add `n` years to this date. */
542
1380
  addYears(n) {
543
1381
  return new DateArithmeticOperation(this.cwj(), n, "YEAR" /* YEAR */, true, "Add Years " + this.displayName());
544
1382
  }
1383
+ /** Subtract `n` days from this date. */
545
1384
  subtractDays(n) {
546
1385
  return new DateArithmeticOperation(this.cwj(), n, "DAY" /* DAY */, false, "Subtract Days " + this.displayName());
547
1386
  }
1387
+ /** Subtract `n` months from this date. */
548
1388
  subtractMonths(n) {
549
1389
  return new DateArithmeticOperation(this.cwj(), n, "MONTH" /* MONTH */, false, "Subtract Months " + this.displayName());
550
1390
  }
1391
+ /** Subtract `n` years from this date. */
551
1392
  subtractYears(n) {
552
1393
  return new DateArithmeticOperation(this.cwj(), n, "YEAR" /* YEAR */, false, "Subtract Years " + this.displayName());
553
1394
  }
1395
+ /** Number of whole days between this date and `other`. */
554
1396
  diffDays(other) {
555
1397
  return new DateDiffOperation(this.cwj(), other, "DAY" /* DAY */, "Diff Days " + this.displayName());
556
1398
  }
1399
+ /** Number of whole months between this date and `other`. */
557
1400
  diffMonths(other) {
558
1401
  return new DateDiffOperation(this.cwj(), other, "MONTH" /* MONTH */, "Diff Months " + this.displayName());
559
1402
  }
1403
+ /** Number of whole years between this date and `other`. */
560
1404
  diffYears(other) {
561
1405
  return new DateDiffOperation(this.cwj(), other, "YEAR" /* YEAR */, "Diff Years " + this.displayName());
562
1406
  }
563
1407
  };
564
1408
  var DateTimeAttribute = class extends Attribute {
565
- constructor(displayName, columnName, columnDbType, owner, parent) {
566
- super(displayName, columnName, columnDbType, owner, parent);
1409
+ constructor(displayName, columnName, columnDbType, owner, parent, embedded, nullable = true, optional = false) {
1410
+ super(displayName, columnName, columnDbType, owner, parent, embedded, nullable, optional);
1411
+ }
1412
+ toConstant(value) {
1413
+ return temporalConstant(value);
567
1414
  }
1415
+ /** Filter to rows where this timestamp equals `value`. */
568
1416
  eq(value) {
569
- return new ComparisonOperation(this.cwj(), "EQUAL" /* EQUAL */, new DateTimeConstantOperation(parseDate(value)));
1417
+ return new ComparisonOperation(this.cwj(), "EQUAL" /* EQUAL */, temporalConstant(value));
570
1418
  }
1419
+ /** Filter to rows where this timestamp does not equal `value`. */
571
1420
  ne(value) {
572
- return new ComparisonOperation(this.cwj(), "NOT_EQUAL" /* NOT_EQUAL */, new DateTimeConstantOperation(parseDate(value)));
1421
+ return new ComparisonOperation(this.cwj(), "NOT_EQUAL" /* NOT_EQUAL */, temporalConstant(value));
573
1422
  }
1423
+ /** Filter to rows where this timestamp is after `value`. */
574
1424
  gt(value) {
575
- return new ComparisonOperation(this.cwj(), "GREATER_THAN" /* GREATER_THAN */, new DateTimeConstantOperation(parseDate(value)));
1425
+ return new ComparisonOperation(this.cwj(), "GREATER_THAN" /* GREATER_THAN */, temporalConstant(value));
576
1426
  }
1427
+ /** Filter to rows where this timestamp is before `value`. */
577
1428
  lt(value) {
578
- return new ComparisonOperation(this.cwj(), "LESS_THAN" /* LESS_THAN */, new DateTimeConstantOperation(parseDate(value)));
1429
+ return new ComparisonOperation(this.cwj(), "LESS_THAN" /* LESS_THAN */, temporalConstant(value));
579
1430
  }
1431
+ /** Filter to rows where this timestamp is on or after `value`. */
580
1432
  gte(value) {
581
- return new ComparisonOperation(this.cwj(), "GREATER_THAN_OR_EQUAL_TO" /* GREATER_THAN_OR_EQUAL_TO */, new DateTimeConstantOperation(parseDate(value)));
1433
+ return new ComparisonOperation(this.cwj(), "GREATER_THAN_OR_EQUAL_TO" /* GREATER_THAN_OR_EQUAL_TO */, temporalConstant(value));
582
1434
  }
1435
+ /** Filter to rows where this timestamp is on or before `value`. */
583
1436
  lte(value) {
584
- return new ComparisonOperation(this.cwj(), "LESS_THAN_OR_EQUAL_TO" /* LESS_THAN_OR_EQUAL_TO */, new DateTimeConstantOperation(parseDate(value)));
1437
+ return new ComparisonOperation(this.cwj(), "LESS_THAN_OR_EQUAL_TO" /* LESS_THAN_OR_EQUAL_TO */, temporalConstant(value));
585
1438
  }
1439
+ /** Extract the four-digit calendar year. */
586
1440
  year() {
587
1441
  return new DateExtractOperation(this.cwj(), "YEAR" /* YEAR */, "Year " + this.displayName());
588
1442
  }
1443
+ /** Extract the month number (1–12). */
589
1444
  month() {
590
1445
  return new DateExtractOperation(this.cwj(), "MONTH" /* MONTH */, "Month " + this.displayName());
591
1446
  }
1447
+ /** Extract the day of the month (1–31). */
592
1448
  day() {
593
1449
  return new DateExtractOperation(this.cwj(), "DAY" /* DAY */, "Day " + this.displayName());
594
1450
  }
1451
+ /** Extract the hour (0–23). */
595
1452
  hour() {
596
1453
  return new DateExtractOperation(this.cwj(), "HOUR" /* HOUR */, "Hour " + this.displayName());
597
1454
  }
1455
+ /** Extract the minute (0–59). */
598
1456
  minute() {
599
1457
  return new DateExtractOperation(this.cwj(), "MINUTE" /* MINUTE */, "Minute " + this.displayName());
600
1458
  }
1459
+ /** Extract the second (0–59). */
601
1460
  second() {
602
1461
  return new DateExtractOperation(this.cwj(), "SECOND" /* SECOND */, "Second " + this.displayName());
603
1462
  }
1463
+ /** Extract the quarter (1–4). */
604
1464
  quarter() {
605
1465
  return new DateExtractOperation(this.cwj(), "QUARTER" /* QUARTER */, "Quarter " + this.displayName());
606
1466
  }
1467
+ /** Extract the ISO week number (1–53). */
607
1468
  week() {
608
1469
  return new DateExtractOperation(this.cwj(), "WEEK" /* WEEK */, "Week " + this.displayName());
609
1470
  }
1471
+ /** Extract the day of the week (0 = Sunday through 6 = Saturday). */
610
1472
  dayOfWeek() {
611
1473
  return new DateExtractOperation(this.cwj(), "DOW" /* DOW */, "Day Of Week " + this.displayName());
612
1474
  }
1475
+ /** The date of the first day of the week the date falls in (a Monday). */
1476
+ firstDayOfWeek() {
1477
+ return new DateTruncOperation(this.cwj(), "WEEK" /* WEEK */, "First Day Of Week " + this.displayName());
1478
+ }
1479
+ /** The date of the first day of the month the date falls in. */
1480
+ firstDayOfMonth() {
1481
+ return new DateTruncOperation(this.cwj(), "MONTH" /* MONTH */, "First Day Of Month " + this.displayName());
1482
+ }
1483
+ /** The date of the first day of the quarter the date falls in. */
1484
+ firstDayOfQuarter() {
1485
+ return new DateTruncOperation(this.cwj(), "QUARTER" /* QUARTER */, "First Day Of Quarter " + this.displayName());
1486
+ }
1487
+ /** The date of the first day of the year the date falls in. */
1488
+ firstDayOfYear() {
1489
+ return new DateTruncOperation(this.cwj(), "YEAR" /* YEAR */, "First Day Of Year " + this.displayName());
1490
+ }
1491
+ /** Add `n` days to this timestamp. */
613
1492
  addDays(n) {
614
1493
  return new DateArithmeticOperation(this.cwj(), n, "DAY" /* DAY */, true, "Add Days " + this.displayName());
615
1494
  }
1495
+ /** Add `n` months to this timestamp. */
616
1496
  addMonths(n) {
617
1497
  return new DateArithmeticOperation(this.cwj(), n, "MONTH" /* MONTH */, true, "Add Months " + this.displayName());
618
1498
  }
1499
+ /** Add `n` years to this timestamp. */
619
1500
  addYears(n) {
620
1501
  return new DateArithmeticOperation(this.cwj(), n, "YEAR" /* YEAR */, true, "Add Years " + this.displayName());
621
1502
  }
1503
+ /** Add `n` hours to this timestamp. */
622
1504
  addHours(n) {
623
1505
  return new DateArithmeticOperation(this.cwj(), n, "HOUR" /* HOUR */, true, "Add Hours " + this.displayName());
624
1506
  }
1507
+ /** Add `n` minutes to this timestamp. */
625
1508
  addMinutes(n) {
626
1509
  return new DateArithmeticOperation(this.cwj(), n, "MINUTE" /* MINUTE */, true, "Add Minutes " + this.displayName());
627
1510
  }
1511
+ /** Add `n` seconds to this timestamp. */
628
1512
  addSeconds(n) {
629
1513
  return new DateArithmeticOperation(this.cwj(), n, "SECOND" /* SECOND */, true, "Add Seconds " + this.displayName());
630
1514
  }
1515
+ /** Subtract `n` days from this timestamp. */
631
1516
  subtractDays(n) {
632
1517
  return new DateArithmeticOperation(this.cwj(), n, "DAY" /* DAY */, false, "Subtract Days " + this.displayName());
633
1518
  }
1519
+ /** Subtract `n` months from this timestamp. */
634
1520
  subtractMonths(n) {
635
1521
  return new DateArithmeticOperation(this.cwj(), n, "MONTH" /* MONTH */, false, "Subtract Months " + this.displayName());
636
1522
  }
1523
+ /** Subtract `n` years from this timestamp. */
637
1524
  subtractYears(n) {
638
1525
  return new DateArithmeticOperation(this.cwj(), n, "YEAR" /* YEAR */, false, "Subtract Years " + this.displayName());
639
1526
  }
1527
+ /** Subtract `n` hours from this timestamp. */
640
1528
  subtractHours(n) {
641
1529
  return new DateArithmeticOperation(this.cwj(), n, "HOUR" /* HOUR */, false, "Subtract Hours " + this.displayName());
642
1530
  }
1531
+ /** Subtract `n` minutes from this timestamp. */
643
1532
  subtractMinutes(n) {
644
1533
  return new DateArithmeticOperation(this.cwj(), n, "MINUTE" /* MINUTE */, false, "Subtract Minutes " + this.displayName());
645
1534
  }
1535
+ /** Subtract `n` seconds from this timestamp. */
646
1536
  subtractSeconds(n) {
647
1537
  return new DateArithmeticOperation(this.cwj(), n, "SECOND" /* SECOND */, false, "Subtract Seconds " + this.displayName());
648
1538
  }
1539
+ /** Number of whole days between this timestamp and `other`. */
649
1540
  diffDays(other) {
650
1541
  return new DateDiffOperation(this.cwj(), other, "DAY" /* DAY */, "Diff Days " + this.displayName());
651
1542
  }
1543
+ /** Number of whole months between this timestamp and `other`. */
652
1544
  diffMonths(other) {
653
1545
  return new DateDiffOperation(this.cwj(), other, "MONTH" /* MONTH */, "Diff Months " + this.displayName());
654
1546
  }
1547
+ /** Number of whole years between this timestamp and `other`. */
655
1548
  diffYears(other) {
656
1549
  return new DateDiffOperation(this.cwj(), other, "YEAR" /* YEAR */, "Diff Years " + this.displayName());
657
1550
  }
1551
+ /** Number of whole hours between this timestamp and `other`. */
658
1552
  diffHours(other) {
659
1553
  return new DateDiffOperation(this.cwj(), other, "HOUR" /* HOUR */, "Diff Hours " + this.displayName());
660
1554
  }
1555
+ /** Number of whole minutes between this timestamp and `other`. */
661
1556
  diffMinutes(other) {
662
1557
  return new DateDiffOperation(this.cwj(), other, "MINUTE" /* MINUTE */, "Diff Minutes " + this.displayName());
663
1558
  }
1559
+ /** Number of whole seconds between this timestamp and `other`. */
664
1560
  diffSeconds(other) {
665
1561
  return new DateDiffOperation(this.cwj(), other, "SECOND" /* SECOND */, "Diff Seconds " + this.displayName());
666
1562
  }
667
1563
  };
1564
+ applyMixins(DateAttribute, [MinMax]);
1565
+ applyMixins(DateTimeAttribute, [MinMax]);
668
1566
 
669
- // src/datafinder/finder.ts
670
- var ExistsOperation = class extends RelationalOperationElement {
671
- constructor(node) {
672
- super();
673
- this.node = node;
674
- }
675
- node;
676
- andOp(other) {
677
- return new LogicalOperation(this, "AND" /* AND */, other);
678
- }
1567
+ // src/datafinder/dialect.ts
1568
+ var doubleQuoted = (name) => '"' + name.replaceAll('"', '""') + '"';
1569
+ var quoted = (value) => "'" + value.replaceAll("'", "''") + "'";
1570
+ var SECONDS_PER = { ["HOUR" /* HOUR */]: 3600, ["MINUTE" /* MINUTE */]: 60, ["SECOND" /* SECOND */]: 1 };
1571
+ var POSTGRES = {
1572
+ name: "postgres",
1573
+ quoteIdentifier: doubleQuoted,
1574
+ stringLiteral: quoted,
1575
+ orderDirection: (direction) => direction === "ASC" /* ASC */ ? " ASC" : " DESC",
1576
+ doubleType: "DOUBLE PRECISION",
1577
+ dayOfWeek: (expression) => `EXTRACT(DOW FROM ${expression})`,
1578
+ dateDiff(unit, from, to) {
1579
+ const start = `CAST(${from} AS TIMESTAMP)`;
1580
+ const end = `CAST(${to} AS TIMESTAMP)`;
1581
+ switch (unit) {
1582
+ case "DAY" /* DAY */:
1583
+ return `(CAST(${to} AS DATE) - CAST(${from} AS DATE))`;
1584
+ case "MONTH" /* MONTH */:
1585
+ return `CAST(EXTRACT(YEAR FROM AGE(${end}, ${start})) * 12 + EXTRACT(MONTH FROM AGE(${end}, ${start})) AS BIGINT)`;
1586
+ case "YEAR" /* YEAR */:
1587
+ return `CAST(EXTRACT(YEAR FROM AGE(${end}, ${start})) AS BIGINT)`;
1588
+ default: {
1589
+ const seconds = SECONDS_PER[unit];
1590
+ if (seconds === void 0) throw new Error(`Cannot take the difference in ${unit}`);
1591
+ return `CAST(FLOOR(EXTRACT(EPOCH FROM (${end} - ${start})) / ${seconds}) AS BIGINT)`;
1592
+ }
1593
+ }
1594
+ },
1595
+ stringAgg: (expression, separator, orderBy) => `STRING_AGG(${expression}, ${separator}${orderBy.length > 0 ? " ORDER BY " + orderBy.join(", ") : ""})`
679
1596
  };
680
- var NotExistsOperation = class extends RelationalOperationElement {
681
- constructor(node) {
682
- super();
683
- this.node = node;
684
- }
685
- node;
686
- andOp(other) {
687
- return new LogicalOperation(this, "AND" /* AND */, other);
688
- }
1597
+ var DUCKDB = {
1598
+ ...POSTGRES,
1599
+ name: "duckdb",
1600
+ // DuckDB sorts nulls last whichever way the sort goes
1601
+ orderDirection: (direction) => direction === "ASC" /* ASC */ ? " ASC" : " DESC NULLS FIRST",
1602
+ doubleType: "DOUBLE",
1603
+ dateDiff: (unit, from, to) => `DATE_DIFF('${unit.toLowerCase()}', ${from}, ${to})`
1604
+ };
1605
+ var DATABRICKS = {
1606
+ name: "databricks",
1607
+ // a double-quoted token is a string in Databricks; identifiers take backticks
1608
+ quoteIdentifier: (name) => "`" + name.replaceAll("`", "``") + "`",
1609
+ // a backslash starts an escape in a Databricks string literal, and a doubled quote is not one: Spark reads
1610
+ // 'it''s' as the two literals 'it' and 's' and joins them, losing the quote. A quote is escaped with a backslash.
1611
+ stringLiteral: (value) => "'" + value.replaceAll("\\", "\\\\").replaceAll("'", "\\'") + "'",
1612
+ // Databricks sorts nulls first when ascending, last when descending
1613
+ orderDirection: (direction) => direction === "ASC" /* ASC */ ? " ASC NULLS LAST" : " DESC NULLS FIRST",
1614
+ doubleType: "DOUBLE",
1615
+ // DAYOFWEEK is 1 = Sunday through 7 = Saturday
1616
+ dayOfWeek: (expression) => `(DAYOFWEEK(${expression}) - 1)`,
1617
+ dateDiff: (unit, from, to) => `DATEDIFF(${unit}, ${from}, ${to})`,
1618
+ stringAgg: (expression, separator, orderBy) => `LISTAGG(${expression}, ${separator})${orderBy.length > 0 ? " WITHIN GROUP (ORDER BY " + orderBy.join(", ") + ")" : ""}`
689
1619
  };
1620
+ var DIALECTS = { postgres: POSTGRES, duckdb: DUCKDB, databricks: DATABRICKS };
1621
+ function dialectOf(dialect) {
1622
+ if (typeof dialect !== "string") return dialect;
1623
+ const found = DIALECTS[dialect];
1624
+ if (found === void 0) throw new Error(`Unknown SQL dialect '${dialect}': one of ${Object.keys(DIALECTS).join(", ")}`);
1625
+ return found;
1626
+ }
690
1627
 
691
1628
  // src/datafinder/sql-generator.ts
692
- var Alias = class {
693
- constructor(element, name) {
694
- this.element = element;
695
- this.name = name;
696
- }
697
- element;
698
- name;
699
- };
700
1629
  var TableAlias = class {
701
1630
  constructor(table, alias) {
702
1631
  this.table = table;
@@ -723,22 +1652,12 @@ var Join = class {
723
1652
  source;
724
1653
  target;
725
1654
  filterOp;
726
- };
727
- var SelectOperation = class {
728
- constructor(display, filter, orderBy = [], groupBy = [], limit, table) {
729
- this.display = display;
730
- this.filter = filter;
731
- this.orderBy = orderBy;
732
- this.groupBy = groupBy;
733
- this.limit = limit;
734
- this.table = table;
1655
+ /** `source = target`, written with the key column first as Legend writes a join defined as key = foreign key. */
1656
+ condition() {
1657
+ let [first, second] = [this.source, this.target];
1658
+ if (second.column.primaryKey && !first.column.primaryKey) [first, second] = [second, first];
1659
+ return first.tableAlias.alias + "." + first.column.name + " = " + second.tableAlias.alias + "." + second.column.name;
735
1660
  }
736
- display;
737
- filter;
738
- orderBy;
739
- groupBy;
740
- limit;
741
- table;
742
1661
  };
743
1662
  function sqlFormatDatetime(value) {
744
1663
  const pad = (n, w = 2) => String(n).padStart(w, "0");
@@ -765,6 +1684,13 @@ var COMPARISON_OPERATOR_STR = {
765
1684
  var AGGREGATE_SQL_NAMES = {
766
1685
  ["AVERAGE" /* AVERAGE */]: "AVG"
767
1686
  };
1687
+ var GUARDED_COMPARISONS = /* @__PURE__ */ new Set([
1688
+ "LESS_THAN" /* LESS_THAN */,
1689
+ "GREATER_THAN" /* GREATER_THAN */,
1690
+ "LESS_THAN_OR_EQUAL_TO" /* LESS_THAN_OR_EQUAL_TO */,
1691
+ "GREATER_THAN_OR_EQUAL_TO" /* GREATER_THAN_OR_EQUAL_TO */,
1692
+ "LIKE" /* LIKE */
1693
+ ]);
768
1694
  var WINDOW_SQL_NAMES = {
769
1695
  ["ROW_NUMBER" /* ROW_NUMBER */]: "ROW_NUMBER",
770
1696
  ["RANK" /* RANK */]: "RANK",
@@ -796,31 +1722,63 @@ var SCALAR_SQL_NAMES = {
796
1722
  ["RIGHT" /* RIGHT */]: "RIGHT",
797
1723
  ["REPEAT" /* REPEAT */]: "REPEAT",
798
1724
  ["REPLACE" /* REPLACE */]: "REPLACE",
799
- ["SUBSTRING" /* SUBSTRING */]: "SUBSTRING"
1725
+ ["SUBSTRING" /* SUBSTRING */]: "SUBSTRING",
1726
+ ["COALESCE" /* COALESCE */]: "COALESCE"
800
1727
  };
801
- function constantValueString(op) {
802
- if (op instanceof StringConstantOperation) return "'" + op.value + "'";
1728
+ function constantValueString(op, d) {
1729
+ if (op instanceof StringConstantOperation) return d.stringLiteral(op.value);
803
1730
  if (op instanceof DateConstantOperation) return sqlFormatDate(op.value);
804
1731
  if (op instanceof DateTimeConstantOperation) return sqlFormatDatetime(op.value);
805
1732
  if (op instanceof IntegerConstantOperation) return String(op.value);
806
- if (op instanceof FloatConstantOperation) return String(op.value);
1733
+ if (op instanceof FloatConstantOperation) return Number.isInteger(op.value) && Math.abs(op.value) < 1e21 ? op.value.toFixed(1) : String(op.value);
807
1734
  if (op instanceof BooleanConstantOperation) return op.value ? "TRUE" : "FALSE";
808
1735
  if (op instanceof DecimalConstantOperation) return String(op.value);
809
1736
  throw new Error("Unknown constant operation");
810
1737
  }
811
- function windowSpecToString(window) {
1738
+ function selectedConstantString(op, d) {
1739
+ if (op instanceof FloatConstantOperation) return "CAST(" + constantValueString(op, d) + " AS " + d.doubleType + ")";
1740
+ if (op instanceof DateTimeConstantOperation) return "TIMESTAMP " + constantValueString(op, d);
1741
+ if (op instanceof DateConstantOperation) return "DATE " + constantValueString(op, d);
1742
+ return constantValueString(op, d);
1743
+ }
1744
+ function windowSpecToString(window, d) {
812
1745
  const parts = [];
813
1746
  if (window.partitionBy.length > 0) {
814
- parts.push("PARTITION BY " + window.partitionBy.map((p) => sqlOperationToString(p)).join(", "));
1747
+ parts.push("PARTITION BY " + window.partitionBy.map((p) => sqlOperationToString(p, d)).join(", "));
815
1748
  }
816
1749
  if (window.orderBy.length > 0) {
817
1750
  parts.push("ORDER BY " + window.orderBy.map(
818
- (s) => sqlOperationToString(s.column) + (s.direction === "ASC" /* ASC */ ? " ASC" : " DESC")
1751
+ (s) => sqlOperationToString(s.column, d) + d.orderDirection(s.direction)
819
1752
  ).join(", "));
820
1753
  }
821
1754
  return "OVER (" + parts.join(" ") + ")";
822
1755
  }
823
- function sqlOperationToString(operation) {
1756
+ var ARITHMETIC_PRECEDENCE = {
1757
+ ["+" /* ADD */]: 1,
1758
+ ["-" /* SUBTRACT */]: 1,
1759
+ ["*" /* MULTIPLY */]: 2,
1760
+ ["/" /* DIVIDE */]: 2
1761
+ };
1762
+ function arithmeticNeedsParentheses(parent, child, onRight) {
1763
+ if (!(child instanceof ArithmeticOperation)) return false;
1764
+ const here = ARITHMETIC_PRECEDENCE[parent.operator];
1765
+ const there = ARITHMETIC_PRECEDENCE[child.operator];
1766
+ return there < here || there === here && onRight && (parent.operator === "-" /* SUBTRACT */ || parent.operator === "/" /* DIVIDE */);
1767
+ }
1768
+ function operandString(operation, d) {
1769
+ return operation instanceof ConstantOperation ? constantValueString(operation, d) : sqlOperationToString(operation, d);
1770
+ }
1771
+ var RenderedSql = class extends RelationalOperationElement {
1772
+ constructor(sql) {
1773
+ super();
1774
+ this.sql = sql;
1775
+ }
1776
+ sql;
1777
+ };
1778
+ function isCondition(op) {
1779
+ return op instanceof ComparisonOperation || op instanceof LogicalOperation || op instanceof IsNullOperation || op instanceof IsNotNullOperation || op instanceof InOperation || op instanceof NotInOperation || op instanceof ExistsOperation || op instanceof NotExistsOperation;
1780
+ }
1781
+ function sqlOperationToString(operation, d) {
824
1782
  if (operation instanceof TableAliasColumn) {
825
1783
  return operation.tableAlias.alias + "." + operation.column.name;
826
1784
  }
@@ -828,49 +1786,77 @@ function sqlOperationToString(operation) {
828
1786
  const fn = WINDOW_SQL_NAMES[operation.func];
829
1787
  const parts = [];
830
1788
  if (operation.element !== null && operation.element !== void 0) {
831
- parts.push(sqlOperationToString(operation.element));
1789
+ parts.push(sqlOperationToString(operation.element, d));
832
1790
  }
833
1791
  if (operation.secondArg !== void 0) parts.push(String(operation.secondArg));
834
1792
  for (const arg of operation.extraArgs) {
835
- parts.push(typeof arg === "string" ? "'" + arg + "'" : String(arg));
1793
+ parts.push(typeof arg === "string" ? d.stringLiteral(arg) : String(arg));
836
1794
  }
837
1795
  const sql = fn + "(" + parts.join(", ") + ")";
838
- return sql + (operation.window !== void 0 ? " " + windowSpecToString(operation.window) : " OVER ()");
1796
+ return sql + (operation.window !== void 0 ? " " + windowSpecToString(operation.window, d) : " OVER ()");
1797
+ }
1798
+ if (operation instanceof AggregateOperation && operation.operator === "COUNT_DISTINCT" /* COUNT_DISTINCT */) {
1799
+ return "COUNT(DISTINCT " + sqlOperationToString(operation.element, d) + ")";
1800
+ }
1801
+ if (operation instanceof AggregateOperation && operation.operator === "STRING_AGG" /* STRING_AGG */) {
1802
+ let sql = d.stringAgg(
1803
+ sqlOperationToString(operation.element, d),
1804
+ d.stringLiteral(operation.separator ?? ""),
1805
+ operation.orderBy.map((s) => sqlOperationToString(s.column, d) + d.orderDirection(s.direction))
1806
+ );
1807
+ if (operation.window !== void 0) sql += " " + windowSpecToString(operation.window, d);
1808
+ return sql;
839
1809
  }
840
1810
  if (operation instanceof AggregateOperation) {
841
1811
  const fn = AGGREGATE_SQL_NAMES[operation.operator] ?? operation.operator;
842
- let sql = fn + "(" + sqlOperationToString(operation.element) + ")";
1812
+ let sql = fn + "(" + sqlOperationToString(operation.element, d) + ")";
843
1813
  if (operation.window !== void 0) {
844
- sql += " " + windowSpecToString(operation.window);
1814
+ sql += " " + windowSpecToString(operation.window, d);
845
1815
  }
846
1816
  return sql;
847
1817
  }
848
1818
  if (operation instanceof ScalarFunctionOperation) {
849
1819
  const fn = SCALAR_SQL_NAMES[operation.func];
850
- const parts = [sqlOperationToString(operation.element)];
1820
+ const parts = [sqlOperationToString(operation.element, d)];
851
1821
  if (operation.secondArg !== void 0) parts.push(String(operation.secondArg));
852
1822
  for (const arg of operation.extraArgs) {
853
- parts.push(typeof arg === "string" ? "'" + arg + "'" : String(arg));
1823
+ parts.push(typeof arg === "string" ? d.stringLiteral(arg) : String(arg));
854
1824
  }
855
1825
  return fn + "(" + parts.join(", ") + ")";
856
1826
  }
857
1827
  if (operation instanceof DateExtractOperation) {
858
- return "EXTRACT(" + operation.part + " FROM " + sqlOperationToString(operation.element) + ")";
1828
+ const element = sqlOperationToString(operation.element, d);
1829
+ return operation.part === "DOW" /* DOW */ ? d.dayOfWeek(element) : "EXTRACT(" + operation.part + " FROM " + element + ")";
1830
+ }
1831
+ if (operation instanceof DateTruncOperation) {
1832
+ return "CAST(DATE_TRUNC('" + operation.part.toLowerCase() + "', " + sqlOperationToString(operation.element, d) + ") AS DATE)";
859
1833
  }
860
1834
  if (operation instanceof DateArithmeticOperation) {
861
1835
  const op = operation.isAdd ? "+" : "-";
862
- return sqlOperationToString(operation.element) + " " + op + " INTERVAL " + operation.n + " " + operation.unit;
1836
+ return sqlOperationToString(operation.element, d) + " " + op + " INTERVAL '" + operation.n + "' " + operation.unit;
863
1837
  }
864
1838
  if (operation instanceof DateDiffOperation) {
865
- const otherSql = sqlFormatDatetime(operation.other);
866
- return "DATE_DIFF('" + operation.unit.toLowerCase() + "', " + sqlOperationToString(operation.element) + ", " + otherSql + ")";
1839
+ return d.dateDiff(operation.unit, sqlOperationToString(operation.element, d), "TIMESTAMP " + sqlFormatDatetime(operation.other));
867
1840
  }
868
1841
  if (operation instanceof CountAllOperation) return "COUNT(*)";
1842
+ if (operation instanceof ConstantOperation) return selectedConstantString(operation, d);
1843
+ if (operation instanceof RenderedSql) return operation.sql;
1844
+ if (operation instanceof ConcatOperation) return operation.parts.map((part) => operandString(part, d)).join(" || ");
1845
+ if (operation instanceof ArithmeticOperation) {
1846
+ let left = operandString(operation.left, d);
1847
+ let right = operandString(operation.right, d);
1848
+ if (arithmeticNeedsParentheses(operation, operation.left, false)) left = "(" + left + ")";
1849
+ if (arithmeticNeedsParentheses(operation, operation.right, true)) right = "(" + right + ")";
1850
+ return left + " " + operation.operator + " " + right;
1851
+ }
869
1852
  if (operation instanceof Alias) {
870
- return sqlOperationToString(operation.element) + ' AS "' + operation.name + '"';
1853
+ return sqlOperationToString(operation.element, d) + " AS " + d.quoteIdentifier(operation.name);
871
1854
  }
872
1855
  throw new TypeError("Unknown operation in sqlOperationToString: " + operation.constructor.name);
873
1856
  }
1857
+ function embeddedOf(ref) {
1858
+ return ref instanceof Attribute ? ref.embedded() : ref.embedded;
1859
+ }
874
1860
  function findColumn(operation) {
875
1861
  if (operation instanceof UnaryOperation) return findColumn(operation.element);
876
1862
  if (operation instanceof ColumnWithJoin) return operation;
@@ -884,89 +1870,195 @@ function openEndClause(endAttr, value, infiniteDatetime) {
884
1870
  }
885
1871
  return gtOp;
886
1872
  }
1873
+ function andAll(ops) {
1874
+ const present = ops.filter((op) => op !== null);
1875
+ if (present.length === 0) return null;
1876
+ return present.slice(1).reduce((left, right) => new LogicalOperation(left, "AND" /* AND */, right), present[0]);
1877
+ }
1878
+ function processingPredicate(mc, processingDatetime, joinNode) {
1879
+ if (!(mc instanceof ProcessingTemporalColumns || mc instanceof BusinessDateAndProcessingTemporalColumns || mc instanceof BiTemporalColumns) || processingDatetime === null) return null;
1880
+ const startAt = new DateTimeAttribute("start_at", mc.startAtColumn.name, mc.startAtColumn.type, mc.startAtColumn.table.qualifiedName, joinNode);
1881
+ const endAt = new DateTimeAttribute("end_at", mc.endAtColumn.name, mc.endAtColumn.type, mc.endAtColumn.table.qualifiedName, joinNode);
1882
+ return new LogicalOperation(startAt.lte(processingDatetime), "AND" /* AND */, openEndClause(endAt, processingDatetime, mc.infiniteDatetime));
1883
+ }
1884
+ function businessPredicate(mc, businessDate, joinNode) {
1885
+ if (businessDate === null) return null;
1886
+ if (mc instanceof BiTemporalColumns) {
1887
+ const dateFrom = new DateAttribute("business_date_from", mc.businessDateFromColumn.name, mc.businessDateFromColumn.type, mc.businessDateFromColumn.table.qualifiedName, joinNode);
1888
+ const dateTo = new DateAttribute("business_date_to", mc.businessDateToColumn.name, mc.businessDateToColumn.type, mc.businessDateToColumn.table.qualifiedName, joinNode);
1889
+ return new LogicalOperation(dateFrom.lte(businessDate), "AND" /* AND */, openEndClause(dateTo, businessDate, mc.infiniteDatetime));
1890
+ }
1891
+ if (mc instanceof BusinessDateAndProcessingTemporalColumns || mc instanceof SingleBusinessDateColumn) {
1892
+ const businessAtt = new DateAttribute("business_date", mc.businessDateColumn.name, mc.businessDateColumn.type, mc.businessDateColumn.table.qualifiedName, joinNode);
1893
+ return businessAtt.eq(businessDate);
1894
+ }
1895
+ return null;
1896
+ }
1897
+ function businessRangePredicate(mc, businessDateFrom, businessDateTo, joinNode) {
1898
+ if (businessDateFrom === null || businessDateTo === null) return null;
1899
+ if (mc instanceof BiTemporalColumns) {
1900
+ const dateFrom = new DateAttribute("business_date_from", mc.businessDateFromColumn.name, mc.businessDateFromColumn.type, mc.businessDateFromColumn.table.qualifiedName, joinNode);
1901
+ const dateTo = new DateAttribute("business_date_to", mc.businessDateToColumn.name, mc.businessDateToColumn.type, mc.businessDateToColumn.table.qualifiedName, joinNode);
1902
+ return new LogicalOperation(dateFrom.lte(businessDateTo), "AND" /* AND */, openEndClause(dateTo, businessDateFrom, mc.infiniteDatetime));
1903
+ }
1904
+ if (mc instanceof BusinessDateAndProcessingTemporalColumns || mc instanceof SingleBusinessDateColumn) {
1905
+ const businessAtt = new DateAttribute("business_date", mc.businessDateColumn.name, mc.businessDateColumn.type, mc.businessDateColumn.table.qualifiedName, joinNode);
1906
+ return new LogicalOperation(businessAtt.gte(businessDateFrom), "AND" /* AND */, businessAtt.lte(businessDateTo));
1907
+ }
1908
+ return null;
1909
+ }
887
1910
  function buildMilestoningFilterOperation(businessDate, processingDatetime, table, joinNode) {
888
1911
  const mc = table.milestoningColumns;
1912
+ return andAll([processingPredicate(mc, processingDatetime, joinNode), businessPredicate(mc, businessDate, joinNode)]);
1913
+ }
1914
+ function buildMilestoningFilterOperationForDateRange(businessDateFrom, businessDateTo, processingDatetime, table, joinNode) {
1915
+ const mc = table.milestoningColumns;
1916
+ return andAll([
1917
+ processingPredicate(mc, processingDatetime, joinNode),
1918
+ businessRangePredicate(mc, businessDateFrom, businessDateTo, joinNode)
1919
+ ]);
1920
+ }
1921
+ function walkRefs(op, visit, visitExists) {
1922
+ if (op === null || op === void 0) return;
1923
+ if (op instanceof Attribute || op instanceof ColumnWithJoin) {
1924
+ visit(op);
1925
+ } else if (op instanceof ExistsOperation || op instanceof NotExistsOperation) {
1926
+ visitExists?.(op.node);
1927
+ } else if (op instanceof AggregateOperation) {
1928
+ walkRefs(op.element, visit, visitExists);
1929
+ if (op.window) walkWindowSpec(op.window, visit, visitExists);
1930
+ for (const sort of op.orderBy) walkRefs(sort, visit, visitExists);
1931
+ } else if (op instanceof WindowFunctionOperation) {
1932
+ if (op.element) walkRefs(op.element, visit, visitExists);
1933
+ if (op.window) walkWindowSpec(op.window, visit, visitExists);
1934
+ } else if (op instanceof ScalarFunctionOperation || op instanceof DateExtractOperation || op instanceof DateTruncOperation || op instanceof DateArithmeticOperation || op instanceof DateDiffOperation || op instanceof IsNullOperation || op instanceof IsNotNullOperation || op instanceof InOperation || op instanceof NotInOperation) {
1935
+ walkRefs(op.element, visit, visitExists);
1936
+ } else if (op instanceof Alias) {
1937
+ walkRefs(op.element, visit, visitExists);
1938
+ } else if (op instanceof ConcatOperation) {
1939
+ for (const part of op.parts) walkRefs(part, visit, visitExists);
1940
+ } else if (op instanceof ArithmeticOperation) {
1941
+ walkRefs(op.left, visit, visitExists);
1942
+ walkRefs(op.right, visit, visitExists);
1943
+ } else if (op instanceof CaseOperation) {
1944
+ walkRefs(op.condition, visit, visitExists);
1945
+ walkRefs(op.then, visit, visitExists);
1946
+ walkRefs(op.fallback, visit, visitExists);
1947
+ } else if (op instanceof SortOperation) {
1948
+ walkRefs(op.column, visit, visitExists);
1949
+ } else if (op instanceof LogicalOperation || op instanceof ComparisonOperation) {
1950
+ walkRefs(op.left, visit, visitExists);
1951
+ walkRefs(op.right, visit, visitExists);
1952
+ }
1953
+ }
1954
+ function walkWindowSpec(window, visit, visitExists) {
1955
+ for (const p of window.partitionBy) walkRefs(p, visit, visitExists);
1956
+ for (const s of window.orderBy) walkRefs(s.column, visit, visitExists);
1957
+ }
1958
+ function buildJoinFilterFromProperties(node, filter, columnMap) {
889
1959
  const ops = [];
890
- if (mc instanceof BiTemporalColumns) {
891
- if (businessDate !== null) {
892
- const dateFrom = new DateAttribute("business_date_from", mc.businessDateFromColumn.name, mc.businessDateFromColumn.type, mc.businessDateFromColumn.table.qualifiedName, joinNode);
893
- const dateTo = new DateAttribute("business_date_to", mc.businessDateToColumn.name, mc.businessDateToColumn.type, mc.businessDateToColumn.table.qualifiedName, joinNode);
894
- ops.push(new LogicalOperation(dateFrom.lte(businessDate), "AND" /* AND */, openEndClause(dateTo, businessDate, mc.infiniteDatetime)));
895
- }
896
- if (processingDatetime !== null) {
897
- const startAt = new DateTimeAttribute("start_at", mc.startAtColumn.name, mc.startAtColumn.type, mc.startAtColumn.table.qualifiedName, joinNode);
898
- const endAt = new DateTimeAttribute("end_at", mc.endAtColumn.name, mc.endAtColumn.type, mc.endAtColumn.table.qualifiedName, joinNode);
899
- ops.push(new LogicalOperation(startAt.lte(processingDatetime), "AND" /* AND */, openEndClause(endAt, processingDatetime, mc.infiniteDatetime)));
900
- }
901
- } else if (mc instanceof BusinessDateAndProcessingTemporalColumns) {
902
- if (businessDate !== null) {
903
- const businessAtt = new DateAttribute("business_date", mc.businessDateColumn.name, mc.businessDateColumn.type, mc.businessDateColumn.table.qualifiedName, joinNode);
904
- ops.push(businessAtt.eq(businessDate));
905
- }
906
- if (processingDatetime !== null) {
907
- const startAt = new DateTimeAttribute("start_at", mc.startAtColumn.name, mc.startAtColumn.type, mc.startAtColumn.table.qualifiedName, joinNode);
908
- const endAt = new DateTimeAttribute("end_at", mc.endAtColumn.name, mc.endAtColumn.type, mc.endAtColumn.table.qualifiedName, joinNode);
909
- ops.push(new LogicalOperation(startAt.lte(processingDatetime), "AND" /* AND */, openEndClause(endAt, processingDatetime, mc.infiniteDatetime)));
1960
+ for (const [key, value] of Object.entries(filter)) {
1961
+ const entry = columnMap[key];
1962
+ if (entry === void 0) {
1963
+ throw new Error(`Unknown property '${key}' \u2014 valid properties are: ${Object.keys(columnMap).sort().join(", ")}`);
910
1964
  }
911
- } else if (mc instanceof ProcessingTemporalColumns && processingDatetime !== null) {
912
- const startAt = new DateTimeAttribute("start_at", mc.startAtColumn.name, mc.startAtColumn.type, mc.startAtColumn.table.qualifiedName, joinNode);
913
- const endAt = new DateTimeAttribute("end_at", mc.endAtColumn.name, mc.endAtColumn.type, mc.endAtColumn.table.qualifiedName, joinNode);
914
- ops.push(new LogicalOperation(startAt.lte(processingDatetime), "AND" /* AND */, openEndClause(endAt, processingDatetime, mc.infiniteDatetime)));
915
- } else if (mc instanceof SingleBusinessDateColumn && businessDate !== null) {
916
- const businessAtt = new DateAttribute("business_date", mc.businessDateColumn.name, mc.businessDateColumn.type, mc.businessDateColumn.table.qualifiedName, joinNode);
917
- ops.push(businessAtt.eq(businessDate));
918
- }
919
- if (ops.length === 0) return null;
920
- if (ops.length === 1) return ops[0];
921
- return new LogicalOperation(ops[0], "AND" /* AND */, ops[1]);
1965
+ const [name, type, table] = entry;
1966
+ const col = new ColumnWithJoin(new Column(name, type, table), node);
1967
+ let constant;
1968
+ if (typeof value === "boolean") constant = new BooleanConstantOperation(value);
1969
+ else if (typeof value === "number") constant = Number.isInteger(value) ? new IntegerConstantOperation(value) : new FloatConstantOperation(value);
1970
+ else if (value instanceof Date) constant = type.toUpperCase() === "DATE" ? new DateConstantOperation(value) : new DateTimeConstantOperation(value);
1971
+ else constant = new StringConstantOperation(String(value));
1972
+ ops.push(new ComparisonOperation(col, "EQUAL" /* EQUAL */, constant));
1973
+ }
1974
+ return ops.reduce((acc, op) => new LogicalOperation(acc, "AND" /* AND */, op));
922
1975
  }
923
- function buildQueryOperation(businessDate, processingDatetime, columns, table, op, orderBy = [], groupBy = [], limit) {
1976
+ function withAncestors(nodes) {
1977
+ const ordered = /* @__PURE__ */ new Set();
1978
+ const add = (node) => {
1979
+ if (ordered.has(node)) return;
1980
+ if (node.parent !== void 0) add(node.parent);
1981
+ ordered.add(node);
1982
+ };
1983
+ for (const node of nodes) add(node);
1984
+ return [...ordered];
1985
+ }
1986
+ function buildQueryOperation(businessDate, processingDatetime, columns, table, op, orderBy = [], groupBy = [], limit, businessDateTo = null, distinct = false, offset, having) {
924
1987
  let filter = op;
1988
+ const milestoningFor = (t, node) => businessDateTo !== null ? buildMilestoningFilterOperationForDateRange(businessDate, businessDateTo, processingDatetime, t, node) : buildMilestoningFilterOperation(businessDate, processingDatetime, t, node);
925
1989
  if (table instanceof MilestonedTable) {
926
- const milestonedOp = buildMilestoningFilterOperation(businessDate, processingDatetime, table);
1990
+ const milestonedOp = milestoningFor(table);
927
1991
  if (milestonedOp !== null) {
928
1992
  filter = filter instanceof NoOperation ? milestonedOp : new LogicalOperation(filter, "AND" /* AND */, milestonedOp);
929
1993
  }
930
1994
  }
931
- const requiredJoins = [];
932
- const requiredJoinIds = /* @__PURE__ */ new Set();
933
- for (const col of columns) {
934
- if (col instanceof CountAllOperation) continue;
935
- let node;
936
- if (col instanceof Attribute) {
937
- node = col.parent();
938
- } else {
939
- node = findColumn(col).parent;
1995
+ const requiredJoins = /* @__PURE__ */ new Set();
1996
+ const collect = (ref) => {
1997
+ const parent = ref instanceof Attribute ? ref.parent() : ref.parent;
1998
+ if (parent !== void 0) requiredJoins.add(parent);
1999
+ };
2000
+ const collectNode = (node) => requiredJoins.add(node);
2001
+ for (const col of columns) walkRefs(col, collect, collectNode);
2002
+ for (const expr of groupBy) walkRefs(expr, collect, collectNode);
2003
+ for (const sort of orderBy) walkRefs(sort, collect, collectNode);
2004
+ walkRefs(op, collect, collectNode);
2005
+ const joinFilters = /* @__PURE__ */ new Map();
2006
+ for (const node of withAncestors(requiredJoins)) {
2007
+ let combined;
2008
+ if (Object.keys(node.join.filterKwargs).length > 0) {
2009
+ combined = buildJoinFilterFromProperties(node, node.join.filterKwargs, node.join.filterColumnMap);
940
2010
  }
941
- if (node !== void 0 && !requiredJoinIds.has(node)) {
942
- requiredJoinIds.add(node);
943
- requiredJoins.push(node);
944
- }
945
- }
946
- for (const node of requiredJoins) {
947
2011
  if (node.join.target instanceof MilestonedTable) {
948
- const milestonedOp = buildMilestoningFilterOperation(businessDate, processingDatetime, node.join.target, node);
2012
+ const milestonedOp = milestoningFor(node.join.target, node);
949
2013
  if (milestonedOp !== null) {
950
- node.join.filter = milestonedOp;
2014
+ combined = combined === void 0 ? milestonedOp : new LogicalOperation(combined, "AND" /* AND */, milestonedOp);
951
2015
  }
952
2016
  }
2017
+ if (combined !== void 0) joinFilters.set(node, combined);
953
2018
  }
954
- return new SelectOperation(columns, filter, orderBy, groupBy, limit, table);
2019
+ return new SelectOperation(columns, filter, orderBy, groupBy, limit, table, joinFilters, distinct, offset, having);
2020
+ }
2021
+ function needsParentheses(parent, child) {
2022
+ return child instanceof LogicalOperation && child.operator !== parent.operator;
2023
+ }
2024
+ function reachesToMany(op) {
2025
+ if (op instanceof ColumnWithJoin) {
2026
+ for (let node = op.parent; node !== void 0; node = node.parent) if (node.many) return true;
2027
+ return false;
2028
+ }
2029
+ if (op instanceof UnaryOperation || op instanceof Alias) return reachesToMany(op.element);
2030
+ if (op instanceof BinaryOperation) return reachesToMany(op.left) || reachesToMany(op.right);
2031
+ return false;
2032
+ }
2033
+ function unaliased(op) {
2034
+ return op instanceof Alias ? unaliased(op.element) : op;
955
2035
  }
956
2036
  var SQLQueryGenerator = class {
2037
+ constructor(_dialect) {
2038
+ this._dialect = _dialect;
2039
+ }
2040
+ _dialect;
957
2041
  _select = [];
958
2042
  _from = /* @__PURE__ */ new Set();
959
2043
  _join = [];
960
2044
  _groupByParts = [];
2045
+ _having = "";
961
2046
  _orderByParts = [];
962
2047
  _where = "";
963
2048
  _limit;
2049
+ _distinct = false;
2050
+ _offset;
964
2051
  _tableAliasIncr = 0;
965
2052
  _tableAliasesByTable = /* @__PURE__ */ new Map();
966
2053
  _addedJoinIds = /* @__PURE__ */ new Set();
967
2054
  _rootTable;
2055
+ _elidableNodes = /* @__PURE__ */ new Set();
2056
+ _joinFilters = /* @__PURE__ */ new Map();
2057
+ _guardOptional = true;
968
2058
  generate(select) {
969
2059
  this._rootTable = select.table;
2060
+ this._elidableNodes = this._computeElidableNodes(select);
2061
+ this._joinFilters = select.joinFilters;
970
2062
  if (this._rootTable !== void 0) {
971
2063
  this._from.add(this._tableAliasForTable(this._rootTable.qualifiedName, this._rootTable.qualifiedName));
972
2064
  }
@@ -979,46 +2071,104 @@ var SQLQueryGenerator = class {
979
2071
  for (const node of groupByJoins.values()) {
980
2072
  this._addJoin(node);
981
2073
  }
982
- this._groupByParts = select.groupBy.map((a) => this._attrToColString(a));
2074
+ this._groupByParts = this._groupByPartsFor(select);
2075
+ this._having = "";
2076
+ if (select.having !== void 0) {
2077
+ const havingJoins = /* @__PURE__ */ new Map();
2078
+ this._collectRequiredJoins(select.having, havingJoins);
2079
+ for (const node of havingJoins.values()) this._addJoin(node);
2080
+ this._guardOptional = false;
2081
+ try {
2082
+ this._having = this.buildFilter(select.having);
2083
+ } finally {
2084
+ this._guardOptional = true;
2085
+ }
2086
+ }
983
2087
  this._orderByParts = select.orderBy.map(
984
- (s) => this.buildFilter(s.column) + (s.direction === "ASC" /* ASC */ ? " ASC" : " DESC")
2088
+ (s) => this._sortExpression(s.column) + this._dialect.orderDirection(s.direction)
985
2089
  );
986
2090
  this._limit = select.limit;
2091
+ this._distinct = select.distinct;
2092
+ this._offset = select.offset;
2093
+ }
2094
+ /**
2095
+ * The GROUP BY of a query: the grouping it was given, else the grouping its columns imply.
2096
+ *
2097
+ * Columns that are not aggregated are what the aggregates are computed for, so a query that selects both is
2098
+ * grouped by those columns (as a DataFrame's `groupby(columns).agg(...)` does). An aggregate read through a
2099
+ * to-many association is computed for each row of the root table, as Legend does, so the root's primary key
2100
+ * is grouped by too. A query that selects only aggregates over the root's own columns is not grouped: the
2101
+ * aggregate covers every row. A constant needs no grouping: a bare
2102
+ * `GROUP BY 7` would be a column position.
2103
+ */
2104
+ _groupByPartsFor(select) {
2105
+ if (select.groupBy.length > 0) return select.groupBy.map((a) => this._attrToColString(a));
2106
+ const aggregates = select.display.filter(containsAggregate);
2107
+ if (aggregates.length === 0) return [];
2108
+ const perRoot = select.table !== void 0 && aggregates.some(reachesToMany);
2109
+ const keys = perRoot ? select.table.columns.filter((c) => c.primaryKey).map((c) => this._columnToString(c)) : [];
2110
+ const grouped = select.display.map((column, i) => ({ column, shown: this._select[i] })).filter(({ column }) => !containsAggregateOrWindow(column) && !(unaliased(column) instanceof ConstantOperation)).map(({ shown }) => sqlOperationToString(shown.element, this._dialect));
2111
+ return [.../* @__PURE__ */ new Set([...keys, ...grouped])];
2112
+ }
2113
+ /** What a sort orders by: a column, or an expression such as an aggregate. */
2114
+ _sortExpression(expression) {
2115
+ if (expression instanceof ColumnWithJoin || expression instanceof Attribute) return this.buildFilter(expression);
2116
+ return sqlOperationToString(this._rewriteOperation(expression), this._dialect);
2117
+ }
2118
+ _columnToString(column) {
2119
+ return this._tableAliasForTable(column.owner, column.owner).alias + "." + column.name;
987
2120
  }
988
2121
  _attrToColString(attr) {
989
- const node = attr.parent();
990
- const ta = this._tableAliasForTable(attr.owner(), node ?? attr.owner());
991
- return ta.alias + "." + attr.column().name;
2122
+ const { column, parent } = this._resolve(attr);
2123
+ const ta = this._tableAliasForTable(column.owner, parent ?? column.owner);
2124
+ return ta.alias + "." + column.name;
992
2125
  }
993
2126
  _collectRequiredJoins(op, out) {
994
- if (op === null || op === void 0) return;
995
- if (op instanceof Attribute) {
996
- const p = op.parent();
997
- if (p !== void 0) out.set(p, p);
998
- } else if (op instanceof ColumnWithJoin) {
999
- if (op.parent !== void 0) out.set(op.parent, op.parent);
1000
- } else if (op instanceof AggregateOperation) {
1001
- this._collectRequiredJoins(op.element, out);
1002
- if (op.window) this._collectRequiredJoins(op.window, out);
1003
- } else if (op instanceof WindowFunctionOperation) {
1004
- if (op.element) this._collectRequiredJoins(op.element, out);
1005
- if (op.window) this._collectRequiredJoinsFromWindowSpec(op.window, out);
1006
- } else if (op instanceof ScalarFunctionOperation || op instanceof DateExtractOperation || op instanceof DateArithmeticOperation || op instanceof DateDiffOperation) {
1007
- this._collectRequiredJoins(op.element, out);
1008
- } else if (op instanceof SortOperation) {
1009
- this._collectRequiredJoins(op.column, out);
1010
- } else if (op instanceof LogicalOperation || op instanceof ComparisonOperation) {
1011
- this._collectRequiredJoins(op.left, out);
1012
- this._collectRequiredJoins(op.right, out);
2127
+ walkRefs(op, (ref) => {
2128
+ const { parent } = this._resolve(ref);
2129
+ if (parent !== void 0) out.set(parent, parent);
2130
+ });
2131
+ }
2132
+ /**
2133
+ * Determine upfront which join nodes can be satisfied entirely by embedded (join-less) alternates:
2134
+ * a node is elidable when every reference to it has an embedded alternate. A reference without one
2135
+ * (e.g. a milestoning column) keeps the join for every reference to that node.
2136
+ */
2137
+ _computeElidableNodes(select) {
2138
+ const refsByNode = /* @__PURE__ */ new Map();
2139
+ const record = (ref) => {
2140
+ const parent = ref instanceof Attribute ? ref.parent() : ref.parent;
2141
+ if (parent === void 0) return;
2142
+ const refs = refsByNode.get(parent);
2143
+ if (refs) refs.push(ref);
2144
+ else refsByNode.set(parent, [ref]);
2145
+ };
2146
+ for (const col of select.display) walkRefs(col, record);
2147
+ for (const expr of select.groupBy) walkRefs(expr, record);
2148
+ for (const sort of select.orderBy) walkRefs(sort, record);
2149
+ walkRefs(select.filter, record);
2150
+ const elidable = /* @__PURE__ */ new Set();
2151
+ for (const [node, refs] of refsByNode) {
2152
+ if (refs.every((ref) => embeddedOf(ref) !== void 0)) elidable.add(node);
1013
2153
  }
1014
- }
1015
- _collectRequiredJoinsFromWindowSpec(window, out) {
1016
- for (const p of window.partitionBy) {
1017
- this._collectRequiredJoins(p, out);
2154
+ return elidable;
2155
+ }
2156
+ /** The (column, parent) a reference should resolve to, substituting its embedded alternate when its join is elided. */
2157
+ _resolve(ref) {
2158
+ const column = ref instanceof Attribute ? ref.column() : ref.column;
2159
+ const parent = ref instanceof Attribute ? ref.parent() : ref.parent;
2160
+ if (parent !== void 0 && this._elidableNodes.has(parent)) {
2161
+ const embedded = embeddedOf(ref);
2162
+ if (embedded !== void 0) return this._resolve(embedded);
1018
2163
  }
1019
- for (const s of window.orderBy) {
1020
- this._collectRequiredJoins(s.column, out);
2164
+ return { column, parent };
2165
+ }
2166
+ /** Whether the value `op` reads can be missing, in the join or embedded form that is used for it. */
2167
+ _resolveOptional(op) {
2168
+ if (op.parent !== void 0 && this._elidableNodes.has(op.parent) && op.embedded !== void 0) {
2169
+ return this._resolveOptional(op.embedded);
1021
2170
  }
2171
+ return op.optional;
1022
2172
  }
1023
2173
  _rewriteWindowSpec(window) {
1024
2174
  const partitionBy = window.partitionBy.map((p) => this._rewriteOperation(p));
@@ -1026,13 +2176,9 @@ var SQLQueryGenerator = class {
1026
2176
  return new WindowSpecification(partitionBy, orderBy);
1027
2177
  }
1028
2178
  _rewriteOperation(op) {
1029
- if (op instanceof Attribute) {
1030
- const ta = this._tableAliasForTable(op.owner(), op.parent() ?? op.owner());
1031
- return new TableAliasColumn(op.column(), ta);
1032
- }
1033
- if (op instanceof ColumnWithJoin) {
1034
- const ta = op.parent !== void 0 ? this._tableAliasForTable(op.column.owner, op.parent) : this._tableAliasForTable(op.column.owner, op.column.owner);
1035
- return new TableAliasColumn(op.column, ta);
2179
+ if (op instanceof Attribute || op instanceof ColumnWithJoin) {
2180
+ const { column, parent } = this._resolve(op);
2181
+ return new TableAliasColumn(column, this._tableAliasForTable(column.owner, parent ?? column.owner));
1036
2182
  }
1037
2183
  if (op instanceof Column) {
1038
2184
  const ta = this._tableAliasForTable(op.owner, op.owner);
@@ -1043,9 +2189,17 @@ var SQLQueryGenerator = class {
1043
2189
  this._rewriteOperation(op.element),
1044
2190
  op.operator,
1045
2191
  op.displayName,
1046
- op.window ? this._rewriteWindowSpec(op.window) : void 0
2192
+ op.window ? this._rewriteWindowSpec(op.window) : void 0,
2193
+ op.separator,
2194
+ op.orderBy.map((s) => new SortOperation(this._rewriteOperation(s.column), s.direction))
1047
2195
  );
1048
2196
  }
2197
+ if (op instanceof CaseOperation) return new RenderedSql(this._caseSql(op));
2198
+ if (isCondition(op)) return new RenderedSql(this.buildFilter(op));
2199
+ if (op instanceof ConcatOperation) return new ConcatOperation(op.parts.map((part) => this._rewriteOperation(part)), op.displayName);
2200
+ if (op instanceof ArithmeticOperation) {
2201
+ return new ArithmeticOperation(this._rewriteOperation(op.left), op.operator, this._rewriteOperation(op.right), op.displayName);
2202
+ }
1049
2203
  if (op instanceof WindowFunctionOperation) {
1050
2204
  const el = op.element ? this._rewriteOperation(op.element) : null;
1051
2205
  return new WindowFunctionOperation(
@@ -1063,6 +2217,9 @@ var SQLQueryGenerator = class {
1063
2217
  if (op instanceof DateExtractOperation) {
1064
2218
  return new DateExtractOperation(this._rewriteOperation(op.element), op.part, op.displayName);
1065
2219
  }
2220
+ if (op instanceof DateTruncOperation) {
2221
+ return new DateTruncOperation(this._rewriteOperation(op.element), op.part, op.displayName);
2222
+ }
1066
2223
  if (op instanceof DateArithmeticOperation) {
1067
2224
  return new DateArithmeticOperation(this._rewriteOperation(op.element), op.n, op.unit, op.isAdd, op.displayName);
1068
2225
  }
@@ -1071,6 +2228,13 @@ var SQLQueryGenerator = class {
1071
2228
  }
1072
2229
  return op;
1073
2230
  }
2231
+ /** The CASE written out now: its condition can only be rendered while the query is being built. */
2232
+ _caseSql(op) {
2233
+ const branch = (value) => value instanceof ConstantOperation ? constantValueString(value, this._dialect) : sqlOperationToString(this._rewriteOperation(value), this._dialect);
2234
+ let sql = "CASE WHEN " + this.buildFilter(op.condition) + " THEN " + branch(op.then);
2235
+ if (op.fallback !== void 0) sql += " ELSE " + branch(op.fallback);
2236
+ return sql + " END";
2237
+ }
1074
2238
  processSelect(cols) {
1075
2239
  const requiredJoins = /* @__PURE__ */ new Map();
1076
2240
  for (const col of cols) {
@@ -1079,88 +2243,96 @@ var SQLQueryGenerator = class {
1079
2243
  for (const node of requiredJoins.values()) {
1080
2244
  this._addJoin(node);
1081
2245
  }
1082
- for (const col of cols) {
1083
- if (col instanceof Attribute) {
1084
- const node = col.parent();
1085
- let ta;
1086
- if (node !== void 0) {
1087
- ta = this._tableAliasForTable(col.owner(), node);
1088
- } else {
1089
- ta = this._tableAliasForTable(col.owner(), col.owner());
1090
- this._from.add(ta);
1091
- }
1092
- this._select.push(new Alias(new TableAliasColumn(col.column(), ta), col.displayName()));
1093
- } else if (col instanceof WindowFunctionOperation) {
1094
- const rewritten = this._rewriteOperation(col);
1095
- let alias;
1096
- if (col.displayName) {
1097
- alias = col.displayName;
1098
- } else if (["ROW_NUMBER" /* ROW_NUMBER */, "RANK" /* RANK */, "DENSE_RANK" /* DENSE_RANK */].includes(col.func)) {
1099
- alias = col.func.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
1100
- } else if (col.element) {
1101
- alias = col.func + " " + findColumn(col).column.name;
1102
- } else {
1103
- alias = col.func;
1104
- }
1105
- this._select.push(new Alias(rewritten, alias));
1106
- } else if (col instanceof AggregateOperation) {
1107
- const colNested = findColumn(col);
1108
- const node = colNested.parent;
1109
- let ta;
1110
- if (node !== void 0) {
1111
- ta = this._tableAliasForTable(colNested.column.owner, node);
1112
- } else {
1113
- ta = this._tableAliasForTable(colNested.column.owner, colNested.column.owner);
1114
- this._from.add(ta);
1115
- }
1116
- const alias = col.displayName ?? col.operator + " " + colNested.column.name;
1117
- const rewrittenAgg = new AggregateOperation(
1118
- new TableAliasColumn(colNested.column, ta),
1119
- col.operator,
1120
- void 0,
1121
- col.window ? this._rewriteWindowSpec(col.window) : void 0
1122
- );
1123
- this._select.push(new Alias(rewrittenAgg, alias));
1124
- } else if (col instanceof ScalarFunctionOperation) {
1125
- const colNested = findColumn(col);
1126
- const node = colNested.parent;
1127
- let ta;
1128
- if (node !== void 0) {
1129
- ta = this._tableAliasForTable(colNested.column.owner, node);
1130
- } else {
1131
- ta = this._tableAliasForTable(colNested.column.owner, colNested.column.owner);
1132
- this._from.add(ta);
1133
- }
1134
- const alias = col.displayName ?? col.func + " " + colNested.column.name;
1135
- this._select.push(new Alias(
1136
- new ScalarFunctionOperation(new TableAliasColumn(colNested.column, ta), col.func, void 0, col.secondArg, col.extraArgs),
1137
- alias
1138
- ));
1139
- } else if (col instanceof DateExtractOperation || col instanceof DateArithmeticOperation || col instanceof DateDiffOperation) {
1140
- const colNested = findColumn(col);
1141
- const node = colNested.parent;
1142
- let ta;
1143
- if (node !== void 0) {
1144
- ta = this._tableAliasForTable(colNested.column.owner, node);
1145
- } else {
1146
- ta = this._tableAliasForTable(colNested.column.owner, colNested.column.owner);
1147
- this._from.add(ta);
1148
- }
1149
- const alias = col.displayName ?? colNested.column.name;
1150
- let aliasedOp;
1151
- if (col instanceof DateExtractOperation) {
1152
- aliasedOp = new DateExtractOperation(new TableAliasColumn(colNested.column, ta), col.part);
1153
- } else if (col instanceof DateArithmeticOperation) {
1154
- aliasedOp = new DateArithmeticOperation(new TableAliasColumn(colNested.column, ta), col.n, col.unit, col.isAdd);
1155
- } else {
1156
- aliasedOp = new DateDiffOperation(new TableAliasColumn(colNested.column, ta), col.other, col.unit);
1157
- }
1158
- this._select.push(new Alias(aliasedOp, alias));
1159
- } else if (col instanceof CountAllOperation) {
1160
- const ta = this._tableAliasForTable(col.table, col.table);
2246
+ for (const col of cols) this._selectColumn(col);
2247
+ }
2248
+ /** Add one column to the select list. An alias reads the column it wraps, then names the entry. */
2249
+ _selectColumn(col) {
2250
+ if (col instanceof Alias) {
2251
+ this._selectColumn(col.element);
2252
+ const read = this._select.pop();
2253
+ this._select.push(new Alias(read.element, col.name));
2254
+ } else if (col instanceof Attribute) {
2255
+ const { column, parent: node } = this._resolve(col);
2256
+ let ta;
2257
+ if (node !== void 0) {
2258
+ ta = this._tableAliasForTable(column.owner, node);
2259
+ } else {
2260
+ ta = this._tableAliasForTable(column.owner, column.owner);
2261
+ this._from.add(ta);
2262
+ }
2263
+ this._select.push(new Alias(new TableAliasColumn(column, ta), col.displayName()));
2264
+ } else if (col instanceof WindowFunctionOperation) {
2265
+ const rewritten = this._rewriteOperation(col);
2266
+ let alias;
2267
+ if (col.displayName) {
2268
+ alias = col.displayName;
2269
+ } else if (["ROW_NUMBER" /* ROW_NUMBER */, "RANK" /* RANK */, "DENSE_RANK" /* DENSE_RANK */].includes(col.func)) {
2270
+ alias = col.func.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
2271
+ } else if (col.element) {
2272
+ alias = col.func + " " + findColumn(col).column.name;
2273
+ } else {
2274
+ alias = col.func;
2275
+ }
2276
+ this._select.push(new Alias(rewritten, alias));
2277
+ } else if (col instanceof AggregateOperation && !(col.element instanceof ColumnWithJoin)) {
2278
+ this._select.push(new Alias(this._rewriteOperation(col), col.displayName ?? "Expression"));
2279
+ } else if (col instanceof AggregateOperation) {
2280
+ const colNested = this._resolve(findColumn(col));
2281
+ const node = colNested.parent;
2282
+ let ta;
2283
+ if (node !== void 0) {
2284
+ ta = this._tableAliasForTable(colNested.column.owner, node);
2285
+ } else {
2286
+ ta = this._tableAliasForTable(colNested.column.owner, colNested.column.owner);
1161
2287
  this._from.add(ta);
1162
- this._select.push(new Alias(col, "Count"));
1163
2288
  }
2289
+ const alias = col.displayName ?? col.operator + " " + findColumn(col).column.name;
2290
+ const rewrittenAgg = new AggregateOperation(
2291
+ new TableAliasColumn(colNested.column, ta),
2292
+ col.operator,
2293
+ void 0,
2294
+ col.window ? this._rewriteWindowSpec(col.window) : void 0,
2295
+ col.separator,
2296
+ col.orderBy.map((s) => new SortOperation(this._rewriteOperation(s.column), s.direction))
2297
+ );
2298
+ this._select.push(new Alias(rewrittenAgg, alias));
2299
+ } else if (col instanceof ScalarFunctionOperation) {
2300
+ this._select.push(new Alias(this._rewriteOperation(col), col.displayName ?? col.func + " " + findColumn(col).column.name));
2301
+ } else if (col instanceof DateExtractOperation || col instanceof DateArithmeticOperation || col instanceof DateDiffOperation) {
2302
+ const colNested = this._resolve(findColumn(col));
2303
+ const node = colNested.parent;
2304
+ let ta;
2305
+ if (node !== void 0) {
2306
+ ta = this._tableAliasForTable(colNested.column.owner, node);
2307
+ } else {
2308
+ ta = this._tableAliasForTable(colNested.column.owner, colNested.column.owner);
2309
+ this._from.add(ta);
2310
+ }
2311
+ const alias = col.displayName ?? findColumn(col).column.name;
2312
+ let aliasedOp;
2313
+ if (col instanceof DateExtractOperation) {
2314
+ aliasedOp = new DateExtractOperation(new TableAliasColumn(colNested.column, ta), col.part);
2315
+ } else if (col instanceof DateArithmeticOperation) {
2316
+ aliasedOp = new DateArithmeticOperation(new TableAliasColumn(colNested.column, ta), col.n, col.unit, col.isAdd);
2317
+ } else {
2318
+ aliasedOp = new DateDiffOperation(new TableAliasColumn(colNested.column, ta), col.other, col.unit);
2319
+ }
2320
+ this._select.push(new Alias(aliasedOp, alias));
2321
+ } else if (col instanceof CaseOperation) {
2322
+ this._select.push(new Alias(this._rewriteOperation(col), col.displayName ?? "Expression"));
2323
+ } else if (col instanceof ConcatOperation) {
2324
+ this._select.push(new Alias(this._rewriteOperation(col), col.displayName ?? "Expression"));
2325
+ } else if (col instanceof ArithmeticOperation) {
2326
+ this._select.push(new Alias(this._rewriteOperation(col), col.displayName ?? "Expression"));
2327
+ } else if (col instanceof DateTruncOperation) {
2328
+ this._select.push(new Alias(this._rewriteOperation(col), col.displayName ?? findColumn(col).column.name));
2329
+ } else if (isCondition(col)) {
2330
+ this._select.push(new Alias(new RenderedSql(this.buildFilter(col)), "Expression"));
2331
+ } else if (col instanceof ConstantOperation) {
2332
+ this._select.push(new Alias(col, "Constant"));
2333
+ } else if (col instanceof CountAllOperation) {
2334
+ if (col.table !== "") this._from.add(this._tableAliasForTable(col.table, col.table));
2335
+ this._select.push(new Alias(col, "Count"));
1164
2336
  }
1165
2337
  }
1166
2338
  _addJoin(node) {
@@ -1174,7 +2346,9 @@ var SQLQueryGenerator = class {
1174
2346
  const sc = new TableAliasColumn(left, srcTa);
1175
2347
  const right = join.right;
1176
2348
  const tc = new TableAliasColumn(right, this._tableAliasForTable(right.owner, node));
1177
- this._join.push(new Join(sc, tc, join.filter));
2349
+ const queryFilter = this._joinFilters.get(node);
2350
+ const filterOp = join.filter !== void 0 && queryFilter !== void 0 ? new LogicalOperation(join.filter, "AND" /* AND */, queryFilter) : join.filter ?? queryFilter;
2351
+ this._join.push(new Join(sc, tc, filterOp));
1178
2352
  }
1179
2353
  buildFilter(op) {
1180
2354
  if (op instanceof NoOperation) return "";
@@ -1186,29 +2360,53 @@ var SQLQueryGenerator = class {
1186
2360
  const col = op.node.join.right;
1187
2361
  return this.buildFilter(new ColumnWithJoin(col, op.node)) + " IS NULL";
1188
2362
  }
2363
+ if (op instanceof InOperation) {
2364
+ return this.buildFilter(op.element) + " IN (" + op.values.map((value) => constantValueString(value, this._dialect)).join(", ") + ")";
2365
+ }
2366
+ if (op instanceof NotInOperation) {
2367
+ const column = this.buildFilter(op.element);
2368
+ const sql = column + " NOT IN (" + op.values.map((value) => constantValueString(value, this._dialect)).join(", ") + ")";
2369
+ if (op.element instanceof ColumnWithJoin && this._resolve(op.element).column.nullable) {
2370
+ return "(" + sql + " OR " + column + " IS NULL)";
2371
+ }
2372
+ return sql;
2373
+ }
1189
2374
  if (op instanceof IsNotNullOperation) return this.buildFilter(op.element) + " IS NOT NULL";
1190
2375
  if (op instanceof IsNullOperation) return this.buildFilter(op.element) + " IS NULL";
1191
2376
  if (op instanceof LogicalOperation) {
1192
2377
  let left = this.buildFilter(op.left);
1193
2378
  let right = this.buildFilter(op.right);
1194
- if (op.left instanceof LogicalOperation) left = "(" + left + ")";
1195
- if (op.right instanceof LogicalOperation) right = "(" + right + ")";
2379
+ if (needsParentheses(op, op.left)) left = "(" + left + ")";
2380
+ if (needsParentheses(op, op.right)) right = "(" + right + ")";
1196
2381
  return left + LOGICAL_OPERATOR_STR[op.operator] + right;
1197
2382
  }
1198
2383
  if (op instanceof ComparisonOperation) {
1199
- return this.buildFilter(op.left) + COMPARISON_OPERATOR_STR[op.operator] + this.buildFilter(op.right);
2384
+ let sql = this.buildFilter(op.left) + COMPARISON_OPERATOR_STR[op.operator] + this.buildFilter(op.right);
2385
+ if ((op.operator === "LIKE" /* LIKE */ || op.operator === "NOT_LIKE" /* NOT_LIKE */) && op.right instanceof StringConstantOperation && (op.right.value.includes(LIKE_ESCAPE) || op.right.value.includes("\\"))) {
2386
+ sql += " ESCAPE '" + LIKE_ESCAPE + "'";
2387
+ }
2388
+ if (this._guardOptional && GUARDED_COMPARISONS.has(op.operator) && op.left instanceof ColumnWithJoin && this._resolveOptional(op.left)) {
2389
+ sql = "(" + this.buildFilter(op.left) + " IS NOT NULL AND " + sql + ")";
2390
+ }
2391
+ return sql;
2392
+ }
2393
+ if (op instanceof ConstantOperation) return constantValueString(op, this._dialect);
2394
+ if (op instanceof ScalarFunctionOperation || op instanceof ArithmeticOperation || op instanceof ConcatOperation || op instanceof DateTruncOperation || op instanceof AggregateOperation || op instanceof CountAllOperation) {
2395
+ const required = /* @__PURE__ */ new Map();
2396
+ this._collectRequiredJoins(op, required);
2397
+ for (const node of required.values()) this._addJoin(node);
2398
+ return sqlOperationToString(this._rewriteOperation(op), this._dialect);
1200
2399
  }
1201
- if (op instanceof ConstantOperation) return constantValueString(op);
1202
2400
  if (op instanceof ColumnWithJoin) {
1203
- const node = op.parent;
2401
+ const { column, parent: node } = this._resolve(op);
1204
2402
  let ta;
1205
2403
  if (node !== void 0) {
1206
- ta = this._tableAliasForTable(op.column.owner, node);
2404
+ ta = this._tableAliasForTable(column.owner, node);
1207
2405
  this._addJoin(node);
1208
2406
  } else {
1209
- ta = this._tableAliasForTable(op.column.owner, op.column.owner);
2407
+ ta = this._tableAliasForTable(column.owner, column.owner);
1210
2408
  }
1211
- return ta.alias + "." + op.column.name;
2409
+ return ta.alias + "." + column.name;
1212
2410
  }
1213
2411
  if (op instanceof Column) {
1214
2412
  const ta = this._tableAliasForTable(op.owner, op.owner);
@@ -1226,9 +2424,9 @@ var SQLQueryGenerator = class {
1226
2424
  }
1227
2425
  buildQueryString() {
1228
2426
  const joins = this._join.map(
1229
- (j) => " LEFT OUTER JOIN " + j.target.tableAlias.table + " AS " + j.target.tableAlias.alias + " ON " + j.source.tableAlias.alias + "." + j.source.column.name + " = " + j.target.tableAlias.alias + "." + j.target.column.name + (j.filterOp ? " AND " + this.buildFilter(j.filterOp) : "")
2427
+ (j) => " LEFT OUTER JOIN " + j.target.tableAlias.table + " AS " + j.target.tableAlias.alias + " ON (" + j.condition() + (j.filterOp ? " AND " + this.buildFilter(j.filterOp) : "") + ")"
1230
2428
  );
1231
- return "SELECT " + this._select.map((ca) => sqlOperationToString(ca)).join(",") + " FROM " + Array.from(this._from).map((ta) => ta.table + " AS " + ta.alias).join(",") + joins.join("") + this._buildWhere() + this._buildGroupBy() + this._buildOrderBy() + this._buildLimit();
2429
+ return (this._distinct ? "SELECT DISTINCT " : "SELECT ") + this._select.map((ca) => sqlOperationToString(ca, this._dialect)).join(",") + " FROM " + Array.from(this._from).map((ta) => ta.table + " AS " + ta.alias).join(",") + joins.join("") + this._buildWhere() + this._buildGroupBy() + this._buildHaving() + this._buildOrderBy() + this._buildLimit() + this._buildOffset();
1232
2430
  }
1233
2431
  _buildWhere() {
1234
2432
  return this._where.length > 0 ? " WHERE " + this._where : "";
@@ -1236,24 +2434,39 @@ var SQLQueryGenerator = class {
1236
2434
  _buildGroupBy() {
1237
2435
  return this._groupByParts.length > 0 ? " GROUP BY " + this._groupByParts.join(", ") : "";
1238
2436
  }
2437
+ _buildHaving() {
2438
+ return this._having.length > 0 ? " HAVING " + this._having : "";
2439
+ }
1239
2440
  _buildOrderBy() {
1240
2441
  return this._orderByParts.length > 0 ? " ORDER BY " + this._orderByParts.join(", ") : "";
1241
2442
  }
2443
+ _buildOffset() {
2444
+ return this._offset !== void 0 ? " OFFSET " + this._offset : "";
2445
+ }
1242
2446
  _buildLimit() {
1243
2447
  return this._limit !== void 0 ? " LIMIT " + this._limit : "";
1244
2448
  }
1245
2449
  };
1246
- function selectSqlToString(selectOperation) {
1247
- const qg = new SQLQueryGenerator();
2450
+ function selectSqlToString(selectOperation, dialect = "postgres") {
2451
+ const qg = new SQLQueryGenerator(dialectOf(dialect));
1248
2452
  qg.generate(selectOperation);
1249
2453
  return qg.buildQueryString();
1250
2454
  }
1251
- function toSql(businessDate, processingDatetime, columns, table, op, orderBy = [], groupBy = [], limit) {
1252
- const selectOp = buildQueryOperation(businessDate, processingDatetime, columns, table, op, orderBy, groupBy, limit);
1253
- return selectSqlToString(selectOp);
2455
+ function toSql(businessDate, processingDatetime, columns, table, op, orderBy = [], groupBy = [], limit, businessDateTo = null, distinct = false, offset, having, dialect = "postgres") {
2456
+ const selectOp = buildQueryOperation(businessDate, processingDatetime, columns, table, op, orderBy, groupBy, limit, businessDateTo, distinct, offset, having);
2457
+ return selectSqlToString(selectOp, dialect);
1254
2458
  }
1255
2459
 
1256
2460
  // src/datafinder/runner.ts
2461
+ var DEFAULT_TIMEOUT_MS = 6e4;
2462
+ var QueryTimeoutError = class extends Error {
2463
+ constructor(timeoutMs) {
2464
+ super(`Query exceeded ${timeoutMs}ms timeout`);
2465
+ this.timeoutMs = timeoutMs;
2466
+ this.name = "QueryTimeoutError";
2467
+ }
2468
+ timeoutMs;
2469
+ };
1257
2470
  var _runner = null;
1258
2471
  function registerRunner(runner) {
1259
2472
  _runner = runner;
@@ -1265,27 +2478,50 @@ function getRunner() {
1265
2478
  if (!_runner) throw new Error("No query runner registered. Call registerRunner() first.");
1266
2479
  return _runner;
1267
2480
  }
2481
+ var expressionIds = /* @__PURE__ */ new WeakMap();
2482
+ var nextExpressionId = 1;
2483
+ function sortTarget(sort) {
2484
+ const column = sort.column;
2485
+ if (!(column instanceof ColumnWithJoin)) {
2486
+ if (!expressionIds.has(column)) expressionIds.set(column, nextExpressionId++);
2487
+ return JSON.stringify(["expression", expressionIds.get(column)]);
2488
+ }
2489
+ const joins = [];
2490
+ for (let node = column.parent; node !== void 0; node = node.parent) joins.push(node.join.name);
2491
+ return JSON.stringify([column.column.owner, column.column.name, joins]);
2492
+ }
1268
2493
  var FinderResult = class {
1269
- constructor(_businessDate, _processingDatetime, _columns, _table, _op) {
2494
+ constructor(_businessDate, _processingDatetime, _columns, _table, _op, _businessDateTo = null) {
1270
2495
  this._businessDate = _businessDate;
1271
2496
  this._processingDatetime = _processingDatetime;
1272
2497
  this._columns = _columns;
1273
2498
  this._table = _table;
1274
2499
  this._op = _op;
2500
+ this._businessDateTo = _businessDateTo;
1275
2501
  }
1276
2502
  _businessDate;
1277
2503
  _processingDatetime;
1278
2504
  _columns;
1279
2505
  _table;
1280
2506
  _op;
2507
+ _businessDateTo;
1281
2508
  _orderBy = [];
1282
2509
  _groupBy = [];
1283
2510
  _limit;
1284
- orderByOp(...sorts) {
1285
- this._orderBy = sorts;
2511
+ _distinct = false;
2512
+ _offset;
2513
+ _having;
2514
+ _timeoutMs = DEFAULT_TIMEOUT_MS;
2515
+ /**
2516
+ * Order the rows. Calls accumulate: the sorts of the latest call take priority and earlier ones order the
2517
+ * rows it leaves tied, as in a chain of `sortBy` calls. Sorting a column again replaces its earlier direction.
2518
+ */
2519
+ orderBy(...sorts) {
2520
+ const resorted = new Set(sorts.map(sortTarget));
2521
+ this._orderBy = [...sorts, ...this._orderBy.filter((s) => !resorted.has(sortTarget(s)))];
1286
2522
  return this;
1287
2523
  }
1288
- groupByOp(...attrs) {
2524
+ groupBy(...attrs) {
1289
2525
  this._groupBy = attrs;
1290
2526
  return this;
1291
2527
  }
@@ -1293,7 +2529,40 @@ var FinderResult = class {
1293
2529
  this._limit = n;
1294
2530
  return this;
1295
2531
  }
1296
- toSql() {
2532
+ /** Skip the first `n` rows. Sort the rows first: without an order, which rows are first is not defined. */
2533
+ offset(n) {
2534
+ if (!Number.isInteger(n) || n < 0) throw new Error("offset cannot be negative");
2535
+ this._offset = n;
2536
+ return this;
2537
+ }
2538
+ /** Rows `start` up to but not including `stop`, counting from 0: `offset(start).limit(stop - start)`. */
2539
+ slice(start, stop) {
2540
+ if (!Number.isInteger(start) || !Number.isInteger(stop) || start < 0 || stop < start) {
2541
+ throw new Error("slice needs 0 <= start <= stop");
2542
+ }
2543
+ return this.offset(start).limit(stop - start);
2544
+ }
2545
+ /**
2546
+ * Keep the groups for which every condition holds: `HAVING`. A condition tests an aggregate or a grouping column:
2547
+ * `f.findAll(null, null, [f.category(), countAll()]).groupBy(f.category()).having(countAll().gt(1))`.
2548
+ * Calls accumulate, as `and` does.
2549
+ */
2550
+ having(...conditions) {
2551
+ for (const condition of conditions) this._having = this._having === void 0 ? condition : this._having.and(condition);
2552
+ return this;
2553
+ }
2554
+ /** Return each distinct row once: `SELECT DISTINCT`. A sort on a distinct query has to use a selected column. */
2555
+ distinct() {
2556
+ this._distinct = true;
2557
+ return this;
2558
+ }
2559
+ /** Abort the query and throw QueryTimeoutError if it runs longer than `milliseconds` (default 60000). */
2560
+ timeout(milliseconds) {
2561
+ this._timeoutMs = milliseconds;
2562
+ return this;
2563
+ }
2564
+ /** The query as SQL, in Postgres unless a dialect is given: `toSql('databricks')`. Nothing is run. */
2565
+ toSql(dialect = "postgres") {
1297
2566
  return toSql(
1298
2567
  this._businessDate,
1299
2568
  this._processingDatetime,
@@ -1302,7 +2571,12 @@ var FinderResult = class {
1302
2571
  this._op,
1303
2572
  this._orderBy,
1304
2573
  this._groupBy,
1305
- this._limit
2574
+ this._limit,
2575
+ this._businessDateTo,
2576
+ this._distinct,
2577
+ this._offset,
2578
+ this._having,
2579
+ dialect
1306
2580
  );
1307
2581
  }
1308
2582
  async toRows() {
@@ -1314,14 +2588,25 @@ var FinderResult = class {
1314
2588
  this._op,
1315
2589
  this._orderBy,
1316
2590
  this._groupBy,
1317
- this._limit
2591
+ this._limit,
2592
+ this._timeoutMs,
2593
+ this._businessDateTo,
2594
+ this._distinct,
2595
+ this._offset,
2596
+ this._having
1318
2597
  );
1319
2598
  }
1320
2599
  };
2600
+ function convertProcessingDate(value) {
2601
+ if (typeof value === "string" && DATE_ONLY_PATTERN.test(value)) return new DateOnly(value + "T00:00:00Z");
2602
+ return convertDate(value);
2603
+ }
1321
2604
  function convertDate(value) {
1322
2605
  if (value === null) return null;
1323
2606
  if (value instanceof Date) return value;
1324
- const normalized = /^\d{4}-\d{2}-\d{2}$/.test(value) ? value + "T00:00:00Z" : value;
2607
+ let normalized = value;
2608
+ if (DATE_ONLY_PATTERN.test(value)) normalized = value + "T00:00:00Z";
2609
+ else if (/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}(:\d{2}(\.\d+)?)?$/.test(value)) normalized = value.replace(" ", "T") + "Z";
1325
2610
  const d = new Date(normalized);
1326
2611
  if (isNaN(d.getTime())) throw new Error(`Cannot parse date: ${value}`);
1327
2612
  return d;
@@ -1329,17 +2614,33 @@ function convertDate(value) {
1329
2614
  function convertInputsAndSelect(businessDate, processingDatetime, columns, table, op = new NoOperation()) {
1330
2615
  return new FinderResult(
1331
2616
  convertDate(businessDate),
1332
- convertDate(processingDatetime),
2617
+ convertProcessingDate(processingDatetime),
1333
2618
  columns,
1334
2619
  table,
1335
2620
  op
1336
2621
  );
1337
2622
  }
2623
+ function convertInputsAndSelectForDateRange(businessDateFrom, businessDateTo, processingDatetime, columns, table, op = new NoOperation()) {
2624
+ if (businessDateFrom == null) throw new Error("businessDateFrom is required for findForDateRange");
2625
+ if (businessDateTo == null) throw new Error("businessDateTo is required for findForDateRange");
2626
+ if (processingDatetime == null) throw new Error("processingValidAt is required for findForDateRange");
2627
+ return new FinderResult(
2628
+ convertDate(businessDateFrom),
2629
+ convertProcessingDate(processingDatetime),
2630
+ columns,
2631
+ table,
2632
+ op,
2633
+ convertDate(businessDateTo)
2634
+ );
2635
+ }
1338
2636
  // Annotate the CommonJS export names for ESM import in node:
1339
2637
  0 && (module.exports = {
2638
+ DEFAULT_TIMEOUT_MS,
1340
2639
  FinderResult,
2640
+ QueryTimeoutError,
1341
2641
  clearRunner,
1342
2642
  convertInputsAndSelect,
2643
+ convertInputsAndSelectForDateRange,
1343
2644
  getRunner,
1344
2645
  registerRunner
1345
2646
  });