data-finder-ts 0.1.4 → 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 (58) hide show
  1. package/dist/{chunk-4JVW4TAB.js → chunk-BGEX377Q.js} +58 -8
  2. package/dist/chunk-BGEX377Q.js.map +1 -0
  3. package/dist/chunk-BKPWV3OF.js +962 -0
  4. package/dist/chunk-BKPWV3OF.js.map +1 -0
  5. package/dist/{chunk-PMPLCEWQ.js → chunk-GPHWC533.js} +2 -2
  6. package/dist/{chunk-FJYA2ASS.js → chunk-KAK75RVS.js} +74 -12
  7. package/dist/chunk-KAK75RVS.js.map +1 -0
  8. package/dist/{chunk-4LNZV6EN.js → chunk-MGEL5RWM.js} +206 -78
  9. package/dist/chunk-MGEL5RWM.js.map +1 -0
  10. package/dist/chunk-PF6EJZHB.js +1015 -0
  11. package/dist/chunk-PF6EJZHB.js.map +1 -0
  12. package/dist/datafinder/attribute.cjs +537 -11
  13. package/dist/datafinder/attribute.cjs.map +1 -1
  14. package/dist/datafinder/attribute.d.cts +29 -7
  15. package/dist/datafinder/attribute.d.ts +29 -7
  16. package/dist/datafinder/attribute.js +2 -2
  17. package/dist/datafinder/runner.cjs +1267 -215
  18. package/dist/datafinder/runner.cjs.map +1 -1
  19. package/dist/datafinder/runner.d.cts +28 -5
  20. package/dist/datafinder/runner.d.ts +28 -5
  21. package/dist/datafinder/runner.js +6 -6
  22. package/dist/datafinder/sql-generator.cjs +1203 -206
  23. package/dist/datafinder/sql-generator.cjs.map +1 -1
  24. package/dist/datafinder/sql-generator.d.cts +19 -5
  25. package/dist/datafinder/sql-generator.d.ts +19 -5
  26. package/dist/datafinder/sql-generator.js +9 -5
  27. package/dist/datafinder/typed-attributes.cjs +693 -88
  28. package/dist/datafinder/typed-attributes.cjs.map +1 -1
  29. package/dist/datafinder/typed-attributes.d.cts +3 -242
  30. package/dist/datafinder/typed-attributes.d.ts +3 -242
  31. package/dist/datafinder/typed-attributes.js +3 -3
  32. package/dist/dialect-CogGPm_y.d.cts +38 -0
  33. package/dist/dialect-aaG1bDi5.d.ts +38 -0
  34. package/dist/index.cjs +1677 -408
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +109 -5
  37. package/dist/index.d.ts +109 -5
  38. package/dist/index.js +321 -11
  39. package/dist/index.js.map +1 -1
  40. package/dist/model/milestoning.cjs +529 -0
  41. package/dist/model/milestoning.cjs.map +1 -1
  42. package/dist/model/milestoning.js +2 -2
  43. package/dist/model/relational.cjs +547 -16
  44. package/dist/model/relational.cjs.map +1 -1
  45. package/dist/model/relational.d.cts +421 -57
  46. package/dist/model/relational.d.ts +421 -57
  47. package/dist/model/relational.js +53 -3
  48. package/dist/typed-attributes-CUTf_6Ru.d.ts +328 -0
  49. package/dist/typed-attributes-CdWXq_78.d.cts +328 -0
  50. package/package.json +3 -2
  51. package/dist/chunk-4JVW4TAB.js.map +0 -1
  52. package/dist/chunk-4LNZV6EN.js.map +0 -1
  53. package/dist/chunk-FJYA2ASS.js.map +0 -1
  54. package/dist/chunk-MH4XMYVB.js +0 -696
  55. package/dist/chunk-MH4XMYVB.js.map +0 -1
  56. package/dist/chunk-UYMOE6YU.js +0 -509
  57. package/dist/chunk-UYMOE6YU.js.map +0 -1
  58. /package/dist/{chunk-PMPLCEWQ.js.map → chunk-GPHWC533.js.map} +0 -0
@@ -32,28 +32,61 @@ __export(runner_exports, {
32
32
  module.exports = __toCommonJS(runner_exports);
33
33
 
34
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
+ }
35
39
  var RelationalOperationElement = class {
36
40
  };
37
41
  var Operation = class extends RelationalOperationElement {
38
- 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) {
39
47
  return new LogicalOperation(this, "AND" /* AND */, other);
40
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
+ }
41
53
  };
42
54
  var NoOperation = class extends Operation {
43
- andOp(other) {
55
+ and(other) {
44
56
  return other;
45
57
  }
58
+ /** No filter matches every row, so OR-ing anything with it still matches every row. */
59
+ or(_other) {
60
+ return this;
61
+ }
46
62
  };
47
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
+ }
48
76
  };
49
- var IntegerConstantOperation = class extends ConstantOperation {
77
+ var NumericConstantOperation = class extends ConstantOperation {
78
+ arithmeticOperand() {
79
+ return this;
80
+ }
81
+ };
82
+ var IntegerConstantOperation = class extends NumericConstantOperation {
50
83
  constructor(value) {
51
84
  super();
52
85
  this.value = value;
53
86
  }
54
87
  value;
55
88
  };
56
- var FloatConstantOperation = class extends ConstantOperation {
89
+ var FloatConstantOperation = class extends NumericConstantOperation {
57
90
  constructor(value) {
58
91
  super();
59
92
  this.value = value;
@@ -67,6 +100,9 @@ var StringConstantOperation = class extends ConstantOperation {
67
100
  }
68
101
  value;
69
102
  };
103
+ var DATE_ONLY_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
104
+ var DateOnly = class extends Date {
105
+ };
70
106
  var DateConstantOperation = class extends ConstantOperation {
71
107
  constructor(value) {
72
108
  super();
@@ -81,7 +117,7 @@ var DateTimeConstantOperation = class extends ConstantOperation {
81
117
  }
82
118
  value;
83
119
  };
84
- var DecimalConstantOperation = class extends ConstantOperation {
120
+ var DecimalConstantOperation = class extends NumericConstantOperation {
85
121
  constructor(value) {
86
122
  super();
87
123
  this.value = value;
@@ -115,6 +151,20 @@ var IsNullOperation = class extends UnaryOperation {
115
151
  };
116
152
  var IsNotNullOperation = class extends UnaryOperation {
117
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
+ };
118
168
  var ComparisonOperation = class extends BinaryOperation {
119
169
  constructor(left, operator, right) {
120
170
  super(left, right);
@@ -138,21 +188,41 @@ var WindowSpecification = class {
138
188
  orderBy;
139
189
  };
140
190
  var AggregateOperation = class _AggregateOperation extends UnaryOperation {
141
- constructor(element, operator, displayName, window) {
191
+ constructor(element, operator, displayName, window, separator, orderBy = []) {
142
192
  super(element);
143
193
  this.operator = operator;
144
194
  this.displayName = displayName;
145
195
  this.window = window;
196
+ this.separator = separator;
197
+ this.orderBy = orderBy;
146
198
  }
147
199
  operator;
148
200
  displayName;
149
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
+ }
150
218
  over(partitionBy, orderBy) {
151
219
  return new _AggregateOperation(
152
220
  this.element,
153
221
  this.operator,
154
222
  this.displayName,
155
- new WindowSpecification(partitionBy ?? [], orderBy ?? [])
223
+ new WindowSpecification(partitionBy ?? [], orderBy ?? []),
224
+ this.separator,
225
+ this.orderBy
156
226
  );
157
227
  }
158
228
  };
@@ -170,6 +240,14 @@ var WindowFunctionOperation = class _WindowFunctionOperation extends UnaryOperat
170
240
  secondArg;
171
241
  extraArgs;
172
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
+ }
173
251
  over(partitionBy, orderBy) {
174
252
  return new _WindowFunctionOperation(
175
253
  this.element,
@@ -193,7 +271,38 @@ var ScalarFunctionOperation = class extends UnaryOperation {
193
271
  displayName;
194
272
  secondArg;
195
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
+ }
196
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
+ ]);
197
306
  var DateExtractOperation = class extends UnaryOperation {
198
307
  constructor(element, part, displayName) {
199
308
  super(element);
@@ -202,6 +311,34 @@ var DateExtractOperation = class extends UnaryOperation {
202
311
  }
203
312
  part;
204
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
+ }
205
342
  };
206
343
  var DateArithmeticOperation = class extends UnaryOperation {
207
344
  constructor(element, n, unit, isAdd = true, displayName) {
@@ -215,6 +352,14 @@ var DateArithmeticOperation = class extends UnaryOperation {
215
352
  unit;
216
353
  isAdd;
217
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
+ }
218
363
  };
219
364
  var DateDiffOperation = class extends UnaryOperation {
220
365
  constructor(element, other, unit, displayName) {
@@ -226,20 +371,31 @@ var DateDiffOperation = class extends UnaryOperation {
226
371
  other;
227
372
  unit;
228
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
+ }
229
382
  };
230
383
  var Column = class extends RelationalOperationElement {
231
- constructor(name, type, owner, primaryKey = false) {
384
+ constructor(name, type, owner, primaryKey = false, nullable = true) {
232
385
  super();
233
386
  this.name = name;
234
387
  this.type = type;
235
388
  this.owner = owner;
236
389
  this.primaryKey = primaryKey;
390
+ this.nullable = nullable && !primaryKey;
237
391
  }
238
392
  name;
239
393
  type;
240
394
  owner;
241
395
  primaryKey;
242
396
  table;
397
+ /** Whether the column may hold NULL. As in Legend, a primary key is never null. */
398
+ nullable;
243
399
  };
244
400
  var Table = class {
245
401
  constructor(name, columns, schema) {
@@ -272,15 +428,24 @@ var ColumnWithJoin = class extends RelationalOperationElement {
272
428
  * `embedded` is an alternate reference to a flat (denormalized) column on the owning table that
273
429
  * holds the same value without the join; the SQL generator uses it when the join can be elided.
274
430
  */
275
- constructor(column, parent, embedded) {
431
+ constructor(column, parent, embedded, _optional = false) {
276
432
  super();
277
433
  this.column = column;
278
434
  this.parent = parent;
279
435
  this.embedded = embedded;
436
+ this._optional = _optional;
280
437
  }
281
438
  column;
282
439
  parent;
283
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
+ }
284
449
  };
285
450
  var SortOperation = class {
286
451
  constructor(column, direction) {
@@ -296,8 +461,23 @@ var CountAllOperation = class extends RelationalOperationElement {
296
461
  this.table = table;
297
462
  }
298
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
+ }
299
479
  };
300
- var Alias = class extends RelationalOperationElement {
480
+ var Alias = class _Alias extends RelationalOperationElement {
301
481
  constructor(element, name) {
302
482
  super();
303
483
  this.element = element;
@@ -305,6 +485,30 @@ var Alias = class extends RelationalOperationElement {
305
485
  }
306
486
  element;
307
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;
308
512
  };
309
513
  var ExistsOperation = class extends RelationalOperationElement {
310
514
  constructor(node) {
@@ -312,9 +516,16 @@ var ExistsOperation = class extends RelationalOperationElement {
312
516
  this.node = node;
313
517
  }
314
518
  node;
315
- andOp(other) {
519
+ /** Name this column in the result. */
520
+ alias(name) {
521
+ return new Alias(this, name);
522
+ }
523
+ and(other) {
316
524
  return new LogicalOperation(this, "AND" /* AND */, other);
317
525
  }
526
+ or(other) {
527
+ return new LogicalOperation(this, "OR" /* OR */, other);
528
+ }
318
529
  };
319
530
  var NotExistsOperation = class extends RelationalOperationElement {
320
531
  constructor(node) {
@@ -322,12 +533,19 @@ var NotExistsOperation = class extends RelationalOperationElement {
322
533
  this.node = node;
323
534
  }
324
535
  node;
325
- andOp(other) {
536
+ /** Name this column in the result. */
537
+ alias(name) {
538
+ return new Alias(this, name);
539
+ }
540
+ and(other) {
326
541
  return new LogicalOperation(this, "AND" /* AND */, other);
327
542
  }
543
+ or(other) {
544
+ return new LogicalOperation(this, "OR" /* OR */, other);
545
+ }
328
546
  };
329
547
  var SelectOperation = class {
330
- constructor(display, filter, orderBy = [], groupBy = [], limit, table, joinFilters = /* @__PURE__ */ new Map()) {
548
+ constructor(display, filter, orderBy = [], groupBy = [], limit, table, joinFilters = /* @__PURE__ */ new Map(), distinct = false, offset, having) {
331
549
  this.display = display;
332
550
  this.filter = filter;
333
551
  this.orderBy = orderBy;
@@ -335,6 +553,9 @@ var SelectOperation = class {
335
553
  this.limit = limit;
336
554
  this.table = table;
337
555
  this.joinFilters = joinFilters;
556
+ this.distinct = distinct;
557
+ this.offset = offset;
558
+ this.having = having;
338
559
  }
339
560
  display;
340
561
  filter;
@@ -343,7 +564,247 @@ var SelectOperation = class {
343
564
  limit;
344
565
  table;
345
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
+ }
346
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]);
347
808
 
348
809
  // src/datafinder/attribute.ts
349
810
  var Attribute = class _Attribute {
@@ -352,9 +813,11 @@ var Attribute = class _Attribute {
352
813
  _owner;
353
814
  _parent;
354
815
  _embedded;
355
- constructor(displayName, columnName, columnDbType, owner, parent, embedded) {
816
+ _optional;
817
+ constructor(displayName, columnName, columnDbType, owner, parent, embedded, nullable = true, optional = false) {
818
+ this._optional = optional;
356
819
  this._displayName = displayName;
357
- this._column = new Column(columnName, columnDbType, owner);
820
+ this._column = new Column(columnName, columnDbType, owner, false, nullable);
358
821
  this._owner = owner;
359
822
  this._parent = parent;
360
823
  this._embedded = embedded;
@@ -375,10 +838,22 @@ var Attribute = class _Attribute {
375
838
  embedded() {
376
839
  return this._embedded;
377
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
+ }
378
853
  cwj() {
379
- return new ColumnWithJoin(this._column, this._parent, this._embedded?.cwj());
854
+ return new ColumnWithJoin(this._column, this._parent, this._embedded?.cwj(), this._optional);
380
855
  }
381
- /** Count the number of non-null values. Use in a projection with `groupByOp` to aggregate per group. */
856
+ /** Count the number of non-null values. Use in a projection with `groupBy` to aggregate per group. */
382
857
  count() {
383
858
  return new AggregateOperation(
384
859
  this.cwj(),
@@ -386,11 +861,26 @@ var Attribute = class _Attribute {
386
861
  this._displayName + " Count"
387
862
  );
388
863
  }
389
- /** Sort this attribute in ascending order. Pass to `orderByOp` or a window function's `orderBy`. */
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`. */
390
880
  ascending() {
391
881
  return new SortOperation(this.cwj(), "ASC" /* ASC */);
392
882
  }
393
- /** Sort this attribute in descending order. Pass to `orderByOp` or a window function's `orderBy`. */
883
+ /** Sort this attribute in descending order. Pass to `orderBy` or a window function's `orderBy`. */
394
884
  descending() {
395
885
  return new SortOperation(this.cwj(), "DESC" /* DESC */);
396
886
  }
@@ -402,6 +892,23 @@ var Attribute = class _Attribute {
402
892
  isNotNull() {
403
893
  return new IsNotNullOperation(this.cwj());
404
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));
911
+ }
405
912
  _windowSpec(partitionBy, orderBy) {
406
913
  const pbArr = partitionBy === void 0 ? [] : Array.isArray(partitionBy) ? partitionBy : [partitionBy];
407
914
  const pb = pbArr.map((x) => x instanceof _Attribute ? x.cwj() : x);
@@ -587,16 +1094,215 @@ var MilestonedTable = class extends Table {
587
1094
  milestoningColumns;
588
1095
  };
589
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
+
590
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]);
591
1289
  function parseDate(value) {
592
1290
  if (value instanceof Date) return value;
593
1291
  const d = new Date(value);
594
1292
  if (isNaN(d.getTime())) throw new Error(`Cannot parse date: ${value}`);
595
1293
  return d;
596
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
+ }
597
1300
  var DateAttribute = class extends Attribute {
598
- constructor(displayName, columnName, columnDbType, owner, parent, embedded) {
599
- super(displayName, columnName, columnDbType, owner, parent, embedded);
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));
600
1306
  }
601
1307
  /** Filter to rows where this date equals `value`. */
602
1308
  eq(value) {
@@ -646,6 +1352,22 @@ var DateAttribute = class extends Attribute {
646
1352
  dayOfWeek() {
647
1353
  return new DateExtractOperation(this.cwj(), "DOW" /* DOW */, "Day Of Week " + this.displayName());
648
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
+ }
649
1371
  /** Add `n` days to this date. */
650
1372
  addDays(n) {
651
1373
  return new DateArithmeticOperation(this.cwj(), n, "DAY" /* DAY */, true, "Add Days " + this.displayName());
@@ -684,32 +1406,35 @@ var DateAttribute = class extends Attribute {
684
1406
  }
685
1407
  };
686
1408
  var DateTimeAttribute = class extends Attribute {
687
- constructor(displayName, columnName, columnDbType, owner, parent, embedded) {
688
- super(displayName, columnName, columnDbType, owner, parent, embedded);
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);
689
1414
  }
690
1415
  /** Filter to rows where this timestamp equals `value`. */
691
1416
  eq(value) {
692
- return new ComparisonOperation(this.cwj(), "EQUAL" /* EQUAL */, new DateTimeConstantOperation(parseDate(value)));
1417
+ return new ComparisonOperation(this.cwj(), "EQUAL" /* EQUAL */, temporalConstant(value));
693
1418
  }
694
1419
  /** Filter to rows where this timestamp does not equal `value`. */
695
1420
  ne(value) {
696
- 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));
697
1422
  }
698
1423
  /** Filter to rows where this timestamp is after `value`. */
699
1424
  gt(value) {
700
- 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));
701
1426
  }
702
1427
  /** Filter to rows where this timestamp is before `value`. */
703
1428
  lt(value) {
704
- 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));
705
1430
  }
706
1431
  /** Filter to rows where this timestamp is on or after `value`. */
707
1432
  gte(value) {
708
- 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));
709
1434
  }
710
1435
  /** Filter to rows where this timestamp is on or before `value`. */
711
1436
  lte(value) {
712
- 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));
713
1438
  }
714
1439
  /** Extract the four-digit calendar year. */
715
1440
  year() {
@@ -747,6 +1472,22 @@ var DateTimeAttribute = class extends Attribute {
747
1472
  dayOfWeek() {
748
1473
  return new DateExtractOperation(this.cwj(), "DOW" /* DOW */, "Day Of Week " + this.displayName());
749
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
+ }
750
1491
  /** Add `n` days to this timestamp. */
751
1492
  addDays(n) {
752
1493
  return new DateArithmeticOperation(this.cwj(), n, "DAY" /* DAY */, true, "Add Days " + this.displayName());
@@ -820,6 +1561,69 @@ var DateTimeAttribute = class extends Attribute {
820
1561
  return new DateDiffOperation(this.cwj(), other, "SECOND" /* SECOND */, "Diff Seconds " + this.displayName());
821
1562
  }
822
1563
  };
1564
+ applyMixins(DateAttribute, [MinMax]);
1565
+ applyMixins(DateTimeAttribute, [MinMax]);
1566
+
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(", ") : ""})`
1596
+ };
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(", ") + ")" : ""}`
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
+ }
823
1627
 
824
1628
  // src/datafinder/sql-generator.ts
825
1629
  var TableAlias = class {
@@ -848,6 +1652,12 @@ var Join = class {
848
1652
  source;
849
1653
  target;
850
1654
  filterOp;
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;
1660
+ }
851
1661
  };
852
1662
  function sqlFormatDatetime(value) {
853
1663
  const pad = (n, w = 2) => String(n).padStart(w, "0");
@@ -874,6 +1684,13 @@ var COMPARISON_OPERATOR_STR = {
874
1684
  var AGGREGATE_SQL_NAMES = {
875
1685
  ["AVERAGE" /* AVERAGE */]: "AVG"
876
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
+ ]);
877
1694
  var WINDOW_SQL_NAMES = {
878
1695
  ["ROW_NUMBER" /* ROW_NUMBER */]: "ROW_NUMBER",
879
1696
  ["RANK" /* RANK */]: "RANK",
@@ -905,31 +1722,63 @@ var SCALAR_SQL_NAMES = {
905
1722
  ["RIGHT" /* RIGHT */]: "RIGHT",
906
1723
  ["REPEAT" /* REPEAT */]: "REPEAT",
907
1724
  ["REPLACE" /* REPLACE */]: "REPLACE",
908
- ["SUBSTRING" /* SUBSTRING */]: "SUBSTRING"
1725
+ ["SUBSTRING" /* SUBSTRING */]: "SUBSTRING",
1726
+ ["COALESCE" /* COALESCE */]: "COALESCE"
909
1727
  };
910
- function constantValueString(op) {
911
- if (op instanceof StringConstantOperation) return "'" + op.value + "'";
1728
+ function constantValueString(op, d) {
1729
+ if (op instanceof StringConstantOperation) return d.stringLiteral(op.value);
912
1730
  if (op instanceof DateConstantOperation) return sqlFormatDate(op.value);
913
1731
  if (op instanceof DateTimeConstantOperation) return sqlFormatDatetime(op.value);
914
1732
  if (op instanceof IntegerConstantOperation) return String(op.value);
915
- 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);
916
1734
  if (op instanceof BooleanConstantOperation) return op.value ? "TRUE" : "FALSE";
917
1735
  if (op instanceof DecimalConstantOperation) return String(op.value);
918
1736
  throw new Error("Unknown constant operation");
919
1737
  }
920
- 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) {
921
1745
  const parts = [];
922
1746
  if (window.partitionBy.length > 0) {
923
- parts.push("PARTITION BY " + window.partitionBy.map((p) => sqlOperationToString(p)).join(", "));
1747
+ parts.push("PARTITION BY " + window.partitionBy.map((p) => sqlOperationToString(p, d)).join(", "));
924
1748
  }
925
1749
  if (window.orderBy.length > 0) {
926
1750
  parts.push("ORDER BY " + window.orderBy.map(
927
- (s) => sqlOperationToString(s.column) + (s.direction === "ASC" /* ASC */ ? " ASC" : " DESC")
1751
+ (s) => sqlOperationToString(s.column, d) + d.orderDirection(s.direction)
928
1752
  ).join(", "));
929
1753
  }
930
1754
  return "OVER (" + parts.join(" ") + ")";
931
1755
  }
932
- 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) {
933
1782
  if (operation instanceof TableAliasColumn) {
934
1783
  return operation.tableAlias.alias + "." + operation.column.name;
935
1784
  }
@@ -937,46 +1786,71 @@ function sqlOperationToString(operation) {
937
1786
  const fn = WINDOW_SQL_NAMES[operation.func];
938
1787
  const parts = [];
939
1788
  if (operation.element !== null && operation.element !== void 0) {
940
- parts.push(sqlOperationToString(operation.element));
1789
+ parts.push(sqlOperationToString(operation.element, d));
941
1790
  }
942
1791
  if (operation.secondArg !== void 0) parts.push(String(operation.secondArg));
943
1792
  for (const arg of operation.extraArgs) {
944
- parts.push(typeof arg === "string" ? "'" + arg + "'" : String(arg));
1793
+ parts.push(typeof arg === "string" ? d.stringLiteral(arg) : String(arg));
945
1794
  }
946
1795
  const sql = fn + "(" + parts.join(", ") + ")";
947
- 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;
948
1809
  }
949
1810
  if (operation instanceof AggregateOperation) {
950
1811
  const fn = AGGREGATE_SQL_NAMES[operation.operator] ?? operation.operator;
951
- let sql = fn + "(" + sqlOperationToString(operation.element) + ")";
1812
+ let sql = fn + "(" + sqlOperationToString(operation.element, d) + ")";
952
1813
  if (operation.window !== void 0) {
953
- sql += " " + windowSpecToString(operation.window);
1814
+ sql += " " + windowSpecToString(operation.window, d);
954
1815
  }
955
1816
  return sql;
956
1817
  }
957
1818
  if (operation instanceof ScalarFunctionOperation) {
958
1819
  const fn = SCALAR_SQL_NAMES[operation.func];
959
- const parts = [sqlOperationToString(operation.element)];
1820
+ const parts = [sqlOperationToString(operation.element, d)];
960
1821
  if (operation.secondArg !== void 0) parts.push(String(operation.secondArg));
961
1822
  for (const arg of operation.extraArgs) {
962
- parts.push(typeof arg === "string" ? "'" + arg + "'" : String(arg));
1823
+ parts.push(typeof arg === "string" ? d.stringLiteral(arg) : String(arg));
963
1824
  }
964
1825
  return fn + "(" + parts.join(", ") + ")";
965
1826
  }
966
1827
  if (operation instanceof DateExtractOperation) {
967
- 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)";
968
1833
  }
969
1834
  if (operation instanceof DateArithmeticOperation) {
970
1835
  const op = operation.isAdd ? "+" : "-";
971
- return sqlOperationToString(operation.element) + " " + op + " INTERVAL " + operation.n + " " + operation.unit;
1836
+ return sqlOperationToString(operation.element, d) + " " + op + " INTERVAL '" + operation.n + "' " + operation.unit;
972
1837
  }
973
1838
  if (operation instanceof DateDiffOperation) {
974
- const otherSql = sqlFormatDatetime(operation.other);
975
- return "DATE_DIFF('" + operation.unit.toLowerCase() + "', " + sqlOperationToString(operation.element) + ", " + otherSql + ")";
1839
+ return d.dateDiff(operation.unit, sqlOperationToString(operation.element, d), "TIMESTAMP " + sqlFormatDatetime(operation.other));
976
1840
  }
977
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
+ }
978
1852
  if (operation instanceof Alias) {
979
- return sqlOperationToString(operation.element) + ' AS "' + operation.name + '"';
1853
+ return sqlOperationToString(operation.element, d) + " AS " + d.quoteIdentifier(operation.name);
980
1854
  }
981
1855
  throw new TypeError("Unknown operation in sqlOperationToString: " + operation.constructor.name);
982
1856
  }
@@ -996,73 +1870,53 @@ function openEndClause(endAttr, value, infiniteDatetime) {
996
1870
  }
997
1871
  return gtOp;
998
1872
  }
999
- function buildMilestoningFilterOperation(businessDate, processingDatetime, table, joinNode) {
1000
- const mc = table.milestoningColumns;
1001
- const ops = [];
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;
1002
1886
  if (mc instanceof BiTemporalColumns) {
1003
- if (businessDate !== null) {
1004
- const dateFrom = new DateAttribute("business_date_from", mc.businessDateFromColumn.name, mc.businessDateFromColumn.type, mc.businessDateFromColumn.table.qualifiedName, joinNode);
1005
- const dateTo = new DateAttribute("business_date_to", mc.businessDateToColumn.name, mc.businessDateToColumn.type, mc.businessDateToColumn.table.qualifiedName, joinNode);
1006
- ops.push(new LogicalOperation(dateFrom.lte(businessDate), "AND" /* AND */, openEndClause(dateTo, businessDate, mc.infiniteDatetime)));
1007
- }
1008
- if (processingDatetime !== null) {
1009
- const startAt = new DateTimeAttribute("start_at", mc.startAtColumn.name, mc.startAtColumn.type, mc.startAtColumn.table.qualifiedName, joinNode);
1010
- const endAt = new DateTimeAttribute("end_at", mc.endAtColumn.name, mc.endAtColumn.type, mc.endAtColumn.table.qualifiedName, joinNode);
1011
- ops.push(new LogicalOperation(startAt.lte(processingDatetime), "AND" /* AND */, openEndClause(endAt, processingDatetime, mc.infiniteDatetime)));
1012
- }
1013
- } else if (mc instanceof BusinessDateAndProcessingTemporalColumns) {
1014
- if (businessDate !== null) {
1015
- const businessAtt = new DateAttribute("business_date", mc.businessDateColumn.name, mc.businessDateColumn.type, mc.businessDateColumn.table.qualifiedName, joinNode);
1016
- ops.push(businessAtt.eq(businessDate));
1017
- }
1018
- if (processingDatetime !== null) {
1019
- const startAt = new DateTimeAttribute("start_at", mc.startAtColumn.name, mc.startAtColumn.type, mc.startAtColumn.table.qualifiedName, joinNode);
1020
- const endAt = new DateTimeAttribute("end_at", mc.endAtColumn.name, mc.endAtColumn.type, mc.endAtColumn.table.qualifiedName, joinNode);
1021
- ops.push(new LogicalOperation(startAt.lte(processingDatetime), "AND" /* AND */, openEndClause(endAt, processingDatetime, mc.infiniteDatetime)));
1022
- }
1023
- } else if (mc instanceof ProcessingTemporalColumns && processingDatetime !== null) {
1024
- const startAt = new DateTimeAttribute("start_at", mc.startAtColumn.name, mc.startAtColumn.type, mc.startAtColumn.table.qualifiedName, joinNode);
1025
- const endAt = new DateTimeAttribute("end_at", mc.endAtColumn.name, mc.endAtColumn.type, mc.endAtColumn.table.qualifiedName, joinNode);
1026
- ops.push(new LogicalOperation(startAt.lte(processingDatetime), "AND" /* AND */, openEndClause(endAt, processingDatetime, mc.infiniteDatetime)));
1027
- } else if (mc instanceof SingleBusinessDateColumn && businessDate !== null) {
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) {
1028
1892
  const businessAtt = new DateAttribute("business_date", mc.businessDateColumn.name, mc.businessDateColumn.type, mc.businessDateColumn.table.qualifiedName, joinNode);
1029
- ops.push(businessAtt.eq(businessDate));
1893
+ return businessAtt.eq(businessDate);
1030
1894
  }
1031
- if (ops.length === 0) return null;
1032
- if (ops.length === 1) return ops[0];
1033
- return new LogicalOperation(ops[0], "AND" /* AND */, ops[1]);
1895
+ return null;
1034
1896
  }
1035
- function buildMilestoningFilterOperationForDateRange(businessDateFrom, businessDateTo, processingDatetime, table, joinNode) {
1036
- const mc = table.milestoningColumns;
1037
- const ops = [];
1038
- const hasRange = businessDateFrom !== null && businessDateTo !== null;
1039
- const processingOp = (m) => {
1040
- const startAt = new DateTimeAttribute("start_at", m.startAtColumn.name, m.startAtColumn.type, m.startAtColumn.table.qualifiedName, joinNode);
1041
- const endAt = new DateTimeAttribute("end_at", m.endAtColumn.name, m.endAtColumn.type, m.endAtColumn.table.qualifiedName, joinNode);
1042
- return new LogicalOperation(startAt.lte(processingDatetime), "AND" /* AND */, openEndClause(endAt, processingDatetime, m.infiniteDatetime));
1043
- };
1897
+ function businessRangePredicate(mc, businessDateFrom, businessDateTo, joinNode) {
1898
+ if (businessDateFrom === null || businessDateTo === null) return null;
1044
1899
  if (mc instanceof BiTemporalColumns) {
1045
- if (hasRange) {
1046
- const dateFrom = new DateAttribute("business_date_from", mc.businessDateFromColumn.name, mc.businessDateFromColumn.type, mc.businessDateFromColumn.table.qualifiedName, joinNode);
1047
- const dateTo = new DateAttribute("business_date_to", mc.businessDateToColumn.name, mc.businessDateToColumn.type, mc.businessDateToColumn.table.qualifiedName, joinNode);
1048
- ops.push(new LogicalOperation(dateFrom.lte(businessDateTo), "AND" /* AND */, openEndClause(dateTo, businessDateFrom, mc.infiniteDatetime)));
1049
- }
1050
- if (processingDatetime !== null) ops.push(processingOp(mc));
1051
- } else if (mc instanceof BusinessDateAndProcessingTemporalColumns) {
1052
- if (hasRange) {
1053
- const businessAtt = new DateAttribute("business_date", mc.businessDateColumn.name, mc.businessDateColumn.type, mc.businessDateColumn.table.qualifiedName, joinNode);
1054
- ops.push(new LogicalOperation(businessAtt.gte(businessDateFrom), "AND" /* AND */, businessAtt.lte(businessDateTo)));
1055
- }
1056
- if (processingDatetime !== null) ops.push(processingOp(mc));
1057
- } else if (mc instanceof ProcessingTemporalColumns) {
1058
- if (processingDatetime !== null) ops.push(processingOp(mc));
1059
- } else if (mc instanceof SingleBusinessDateColumn && hasRange) {
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) {
1060
1905
  const businessAtt = new DateAttribute("business_date", mc.businessDateColumn.name, mc.businessDateColumn.type, mc.businessDateColumn.table.qualifiedName, joinNode);
1061
- ops.push(new LogicalOperation(businessAtt.gte(businessDateFrom), "AND" /* AND */, businessAtt.lte(businessDateTo)));
1906
+ return new LogicalOperation(businessAtt.gte(businessDateFrom), "AND" /* AND */, businessAtt.lte(businessDateTo));
1062
1907
  }
1063
- if (ops.length === 0) return null;
1064
- if (ops.length === 1) return ops[0];
1065
- return new LogicalOperation(ops[0], "AND" /* AND */, ops[1]);
1908
+ return null;
1909
+ }
1910
+ function buildMilestoningFilterOperation(businessDate, processingDatetime, table, joinNode) {
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
+ ]);
1066
1920
  }
1067
1921
  function walkRefs(op, visit, visitExists) {
1068
1922
  if (op === null || op === void 0) return;
@@ -1073,11 +1927,23 @@ function walkRefs(op, visit, visitExists) {
1073
1927
  } else if (op instanceof AggregateOperation) {
1074
1928
  walkRefs(op.element, visit, visitExists);
1075
1929
  if (op.window) walkWindowSpec(op.window, visit, visitExists);
1930
+ for (const sort of op.orderBy) walkRefs(sort, visit, visitExists);
1076
1931
  } else if (op instanceof WindowFunctionOperation) {
1077
1932
  if (op.element) walkRefs(op.element, visit, visitExists);
1078
1933
  if (op.window) walkWindowSpec(op.window, visit, visitExists);
1079
- } else if (op instanceof ScalarFunctionOperation || op instanceof DateExtractOperation || op instanceof DateArithmeticOperation || op instanceof DateDiffOperation || op instanceof IsNullOperation || op instanceof IsNotNullOperation) {
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) {
1080
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);
1081
1947
  } else if (op instanceof SortOperation) {
1082
1948
  walkRefs(op.column, visit, visitExists);
1083
1949
  } else if (op instanceof LogicalOperation || op instanceof ComparisonOperation) {
@@ -1117,7 +1983,7 @@ function withAncestors(nodes) {
1117
1983
  for (const node of nodes) add(node);
1118
1984
  return [...ordered];
1119
1985
  }
1120
- function buildQueryOperation(businessDate, processingDatetime, columns, table, op, orderBy = [], groupBy = [], limit, businessDateTo = null) {
1986
+ function buildQueryOperation(businessDate, processingDatetime, columns, table, op, orderBy = [], groupBy = [], limit, businessDateTo = null, distinct = false, offset, having) {
1121
1987
  let filter = op;
1122
1988
  const milestoningFor = (t, node) => businessDateTo !== null ? buildMilestoningFilterOperationForDateRange(businessDate, businessDateTo, processingDatetime, t, node) : buildMilestoningFilterOperation(businessDate, processingDatetime, t, node);
1123
1989
  if (table instanceof MilestonedTable) {
@@ -1150,22 +2016,45 @@ function buildQueryOperation(businessDate, processingDatetime, columns, table, o
1150
2016
  }
1151
2017
  if (combined !== void 0) joinFilters.set(node, combined);
1152
2018
  }
1153
- return new SelectOperation(columns, filter, orderBy, groupBy, limit, table, joinFilters);
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;
1154
2035
  }
1155
2036
  var SQLQueryGenerator = class {
2037
+ constructor(_dialect) {
2038
+ this._dialect = _dialect;
2039
+ }
2040
+ _dialect;
1156
2041
  _select = [];
1157
2042
  _from = /* @__PURE__ */ new Set();
1158
2043
  _join = [];
1159
2044
  _groupByParts = [];
2045
+ _having = "";
1160
2046
  _orderByParts = [];
1161
2047
  _where = "";
1162
2048
  _limit;
2049
+ _distinct = false;
2050
+ _offset;
1163
2051
  _tableAliasIncr = 0;
1164
2052
  _tableAliasesByTable = /* @__PURE__ */ new Map();
1165
2053
  _addedJoinIds = /* @__PURE__ */ new Set();
1166
2054
  _rootTable;
1167
2055
  _elidableNodes = /* @__PURE__ */ new Set();
1168
2056
  _joinFilters = /* @__PURE__ */ new Map();
2057
+ _guardOptional = true;
1169
2058
  generate(select) {
1170
2059
  this._rootTable = select.table;
1171
2060
  this._elidableNodes = this._computeElidableNodes(select);
@@ -1182,11 +2071,52 @@ var SQLQueryGenerator = class {
1182
2071
  for (const node of groupByJoins.values()) {
1183
2072
  this._addJoin(node);
1184
2073
  }
1185
- 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
+ }
1186
2087
  this._orderByParts = select.orderBy.map(
1187
- (s) => this.buildFilter(s.column) + (s.direction === "ASC" /* ASC */ ? " ASC" : " DESC")
2088
+ (s) => this._sortExpression(s.column) + this._dialect.orderDirection(s.direction)
1188
2089
  );
1189
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;
1190
2120
  }
1191
2121
  _attrToColString(attr) {
1192
2122
  const { column, parent } = this._resolve(attr);
@@ -1233,6 +2163,13 @@ var SQLQueryGenerator = class {
1233
2163
  }
1234
2164
  return { column, parent };
1235
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);
2170
+ }
2171
+ return op.optional;
2172
+ }
1236
2173
  _rewriteWindowSpec(window) {
1237
2174
  const partitionBy = window.partitionBy.map((p) => this._rewriteOperation(p));
1238
2175
  const orderBy = window.orderBy.map((s) => new SortOperation(this._rewriteOperation(s.column), s.direction));
@@ -1252,9 +2189,17 @@ var SQLQueryGenerator = class {
1252
2189
  this._rewriteOperation(op.element),
1253
2190
  op.operator,
1254
2191
  op.displayName,
1255
- 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))
1256
2195
  );
1257
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
+ }
1258
2203
  if (op instanceof WindowFunctionOperation) {
1259
2204
  const el = op.element ? this._rewriteOperation(op.element) : null;
1260
2205
  return new WindowFunctionOperation(
@@ -1272,6 +2217,9 @@ var SQLQueryGenerator = class {
1272
2217
  if (op instanceof DateExtractOperation) {
1273
2218
  return new DateExtractOperation(this._rewriteOperation(op.element), op.part, op.displayName);
1274
2219
  }
2220
+ if (op instanceof DateTruncOperation) {
2221
+ return new DateTruncOperation(this._rewriteOperation(op.element), op.part, op.displayName);
2222
+ }
1275
2223
  if (op instanceof DateArithmeticOperation) {
1276
2224
  return new DateArithmeticOperation(this._rewriteOperation(op.element), op.n, op.unit, op.isAdd, op.displayName);
1277
2225
  }
@@ -1280,6 +2228,13 @@ var SQLQueryGenerator = class {
1280
2228
  }
1281
2229
  return op;
1282
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
+ }
1283
2238
  processSelect(cols) {
1284
2239
  const requiredJoins = /* @__PURE__ */ new Map();
1285
2240
  for (const col of cols) {
@@ -1288,88 +2243,96 @@ var SQLQueryGenerator = class {
1288
2243
  for (const node of requiredJoins.values()) {
1289
2244
  this._addJoin(node);
1290
2245
  }
1291
- for (const col of cols) {
1292
- if (col instanceof Attribute) {
1293
- const { column, parent: node } = this._resolve(col);
1294
- let ta;
1295
- if (node !== void 0) {
1296
- ta = this._tableAliasForTable(column.owner, node);
1297
- } else {
1298
- ta = this._tableAliasForTable(column.owner, column.owner);
1299
- this._from.add(ta);
1300
- }
1301
- this._select.push(new Alias(new TableAliasColumn(column, ta), col.displayName()));
1302
- } else if (col instanceof WindowFunctionOperation) {
1303
- const rewritten = this._rewriteOperation(col);
1304
- let alias;
1305
- if (col.displayName) {
1306
- alias = col.displayName;
1307
- } else if (["ROW_NUMBER" /* ROW_NUMBER */, "RANK" /* RANK */, "DENSE_RANK" /* DENSE_RANK */].includes(col.func)) {
1308
- alias = col.func.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
1309
- } else if (col.element) {
1310
- alias = col.func + " " + findColumn(col).column.name;
1311
- } else {
1312
- alias = col.func;
1313
- }
1314
- this._select.push(new Alias(rewritten, alias));
1315
- } else if (col instanceof AggregateOperation) {
1316
- const colNested = this._resolve(findColumn(col));
1317
- const node = colNested.parent;
1318
- let ta;
1319
- if (node !== void 0) {
1320
- ta = this._tableAliasForTable(colNested.column.owner, node);
1321
- } else {
1322
- ta = this._tableAliasForTable(colNested.column.owner, colNested.column.owner);
1323
- this._from.add(ta);
1324
- }
1325
- const alias = col.displayName ?? col.operator + " " + findColumn(col).column.name;
1326
- const rewrittenAgg = new AggregateOperation(
1327
- new TableAliasColumn(colNested.column, ta),
1328
- col.operator,
1329
- void 0,
1330
- col.window ? this._rewriteWindowSpec(col.window) : void 0
1331
- );
1332
- this._select.push(new Alias(rewrittenAgg, alias));
1333
- } else if (col instanceof ScalarFunctionOperation) {
1334
- const colNested = this._resolve(findColumn(col));
1335
- const node = colNested.parent;
1336
- let ta;
1337
- if (node !== void 0) {
1338
- ta = this._tableAliasForTable(colNested.column.owner, node);
1339
- } else {
1340
- ta = this._tableAliasForTable(colNested.column.owner, colNested.column.owner);
1341
- this._from.add(ta);
1342
- }
1343
- const alias = col.displayName ?? col.func + " " + findColumn(col).column.name;
1344
- this._select.push(new Alias(
1345
- new ScalarFunctionOperation(new TableAliasColumn(colNested.column, ta), col.func, void 0, col.secondArg, col.extraArgs),
1346
- alias
1347
- ));
1348
- } else if (col instanceof DateExtractOperation || col instanceof DateArithmeticOperation || col instanceof DateDiffOperation) {
1349
- const colNested = this._resolve(findColumn(col));
1350
- const node = colNested.parent;
1351
- let ta;
1352
- if (node !== void 0) {
1353
- ta = this._tableAliasForTable(colNested.column.owner, node);
1354
- } else {
1355
- ta = this._tableAliasForTable(colNested.column.owner, colNested.column.owner);
1356
- this._from.add(ta);
1357
- }
1358
- const alias = col.displayName ?? findColumn(col).column.name;
1359
- let aliasedOp;
1360
- if (col instanceof DateExtractOperation) {
1361
- aliasedOp = new DateExtractOperation(new TableAliasColumn(colNested.column, ta), col.part);
1362
- } else if (col instanceof DateArithmeticOperation) {
1363
- aliasedOp = new DateArithmeticOperation(new TableAliasColumn(colNested.column, ta), col.n, col.unit, col.isAdd);
1364
- } else {
1365
- aliasedOp = new DateDiffOperation(new TableAliasColumn(colNested.column, ta), col.other, col.unit);
1366
- }
1367
- this._select.push(new Alias(aliasedOp, alias));
1368
- } else if (col instanceof CountAllOperation) {
1369
- 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);
1370
2287
  this._from.add(ta);
1371
- this._select.push(new Alias(col, "Count"));
1372
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"));
1373
2336
  }
1374
2337
  }
1375
2338
  _addJoin(node) {
@@ -1397,19 +2360,43 @@ var SQLQueryGenerator = class {
1397
2360
  const col = op.node.join.right;
1398
2361
  return this.buildFilter(new ColumnWithJoin(col, op.node)) + " IS NULL";
1399
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
+ }
1400
2374
  if (op instanceof IsNotNullOperation) return this.buildFilter(op.element) + " IS NOT NULL";
1401
2375
  if (op instanceof IsNullOperation) return this.buildFilter(op.element) + " IS NULL";
1402
2376
  if (op instanceof LogicalOperation) {
1403
2377
  let left = this.buildFilter(op.left);
1404
2378
  let right = this.buildFilter(op.right);
1405
- if (op.left instanceof LogicalOperation) left = "(" + left + ")";
1406
- if (op.right instanceof LogicalOperation) right = "(" + right + ")";
2379
+ if (needsParentheses(op, op.left)) left = "(" + left + ")";
2380
+ if (needsParentheses(op, op.right)) right = "(" + right + ")";
1407
2381
  return left + LOGICAL_OPERATOR_STR[op.operator] + right;
1408
2382
  }
1409
2383
  if (op instanceof ComparisonOperation) {
1410
- 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);
1411
2399
  }
1412
- if (op instanceof ConstantOperation) return constantValueString(op);
1413
2400
  if (op instanceof ColumnWithJoin) {
1414
2401
  const { column, parent: node } = this._resolve(op);
1415
2402
  let ta;
@@ -1437,9 +2424,9 @@ var SQLQueryGenerator = class {
1437
2424
  }
1438
2425
  buildQueryString() {
1439
2426
  const joins = this._join.map(
1440
- (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) : "") + ")"
1441
2428
  );
1442
- 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();
1443
2430
  }
1444
2431
  _buildWhere() {
1445
2432
  return this._where.length > 0 ? " WHERE " + this._where : "";
@@ -1447,21 +2434,27 @@ var SQLQueryGenerator = class {
1447
2434
  _buildGroupBy() {
1448
2435
  return this._groupByParts.length > 0 ? " GROUP BY " + this._groupByParts.join(", ") : "";
1449
2436
  }
2437
+ _buildHaving() {
2438
+ return this._having.length > 0 ? " HAVING " + this._having : "";
2439
+ }
1450
2440
  _buildOrderBy() {
1451
2441
  return this._orderByParts.length > 0 ? " ORDER BY " + this._orderByParts.join(", ") : "";
1452
2442
  }
2443
+ _buildOffset() {
2444
+ return this._offset !== void 0 ? " OFFSET " + this._offset : "";
2445
+ }
1453
2446
  _buildLimit() {
1454
2447
  return this._limit !== void 0 ? " LIMIT " + this._limit : "";
1455
2448
  }
1456
2449
  };
1457
- function selectSqlToString(selectOperation) {
1458
- const qg = new SQLQueryGenerator();
2450
+ function selectSqlToString(selectOperation, dialect = "postgres") {
2451
+ const qg = new SQLQueryGenerator(dialectOf(dialect));
1459
2452
  qg.generate(selectOperation);
1460
2453
  return qg.buildQueryString();
1461
2454
  }
1462
- function toSql(businessDate, processingDatetime, columns, table, op, orderBy = [], groupBy = [], limit, businessDateTo = null) {
1463
- const selectOp = buildQueryOperation(businessDate, processingDatetime, columns, table, op, orderBy, groupBy, limit, businessDateTo);
1464
- 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);
1465
2458
  }
1466
2459
 
1467
2460
  // src/datafinder/runner.ts
@@ -1485,6 +2478,18 @@ function getRunner() {
1485
2478
  if (!_runner) throw new Error("No query runner registered. Call registerRunner() first.");
1486
2479
  return _runner;
1487
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
+ }
1488
2493
  var FinderResult = class {
1489
2494
  constructor(_businessDate, _processingDatetime, _columns, _table, _op, _businessDateTo = null) {
1490
2495
  this._businessDate = _businessDate;
@@ -1503,12 +2508,20 @@ var FinderResult = class {
1503
2508
  _orderBy = [];
1504
2509
  _groupBy = [];
1505
2510
  _limit;
2511
+ _distinct = false;
2512
+ _offset;
2513
+ _having;
1506
2514
  _timeoutMs = DEFAULT_TIMEOUT_MS;
1507
- orderByOp(...sorts) {
1508
- this._orderBy = sorts;
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)))];
1509
2522
  return this;
1510
2523
  }
1511
- groupByOp(...attrs) {
2524
+ groupBy(...attrs) {
1512
2525
  this._groupBy = attrs;
1513
2526
  return this;
1514
2527
  }
@@ -1516,12 +2529,40 @@ var FinderResult = class {
1516
2529
  this._limit = n;
1517
2530
  return this;
1518
2531
  }
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
+ }
1519
2559
  /** Abort the query and throw QueryTimeoutError if it runs longer than `milliseconds` (default 60000). */
1520
2560
  timeout(milliseconds) {
1521
2561
  this._timeoutMs = milliseconds;
1522
2562
  return this;
1523
2563
  }
1524
- toSql() {
2564
+ /** The query as SQL, in Postgres unless a dialect is given: `toSql('databricks')`. Nothing is run. */
2565
+ toSql(dialect = "postgres") {
1525
2566
  return toSql(
1526
2567
  this._businessDate,
1527
2568
  this._processingDatetime,
@@ -1531,7 +2572,11 @@ var FinderResult = class {
1531
2572
  this._orderBy,
1532
2573
  this._groupBy,
1533
2574
  this._limit,
1534
- this._businessDateTo
2575
+ this._businessDateTo,
2576
+ this._distinct,
2577
+ this._offset,
2578
+ this._having,
2579
+ dialect
1535
2580
  );
1536
2581
  }
1537
2582
  async toRows() {
@@ -1545,15 +2590,22 @@ var FinderResult = class {
1545
2590
  this._groupBy,
1546
2591
  this._limit,
1547
2592
  this._timeoutMs,
1548
- this._businessDateTo
2593
+ this._businessDateTo,
2594
+ this._distinct,
2595
+ this._offset,
2596
+ this._having
1549
2597
  );
1550
2598
  }
1551
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
+ }
1552
2604
  function convertDate(value) {
1553
2605
  if (value === null) return null;
1554
2606
  if (value instanceof Date) return value;
1555
2607
  let normalized = value;
1556
- if (/^\d{4}-\d{2}-\d{2}$/.test(value)) normalized = value + "T00:00:00Z";
2608
+ if (DATE_ONLY_PATTERN.test(value)) normalized = value + "T00:00:00Z";
1557
2609
  else if (/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}(:\d{2}(\.\d+)?)?$/.test(value)) normalized = value.replace(" ", "T") + "Z";
1558
2610
  const d = new Date(normalized);
1559
2611
  if (isNaN(d.getTime())) throw new Error(`Cannot parse date: ${value}`);
@@ -1562,7 +2614,7 @@ function convertDate(value) {
1562
2614
  function convertInputsAndSelect(businessDate, processingDatetime, columns, table, op = new NoOperation()) {
1563
2615
  return new FinderResult(
1564
2616
  convertDate(businessDate),
1565
- convertDate(processingDatetime),
2617
+ convertProcessingDate(processingDatetime),
1566
2618
  columns,
1567
2619
  table,
1568
2620
  op
@@ -1574,7 +2626,7 @@ function convertInputsAndSelectForDateRange(businessDateFrom, businessDateTo, pr
1574
2626
  if (processingDatetime == null) throw new Error("processingValidAt is required for findForDateRange");
1575
2627
  return new FinderResult(
1576
2628
  convertDate(businessDateFrom),
1577
- convertDate(processingDatetime),
2629
+ convertProcessingDate(processingDatetime),
1578
2630
  columns,
1579
2631
  table,
1580
2632
  op,