data-finder-ts 0.1.3 → 0.1.5

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