data-finder-ts 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/{chunk-4JVW4TAB.js → chunk-BGEX377Q.js} +58 -8
  2. package/dist/chunk-BGEX377Q.js.map +1 -0
  3. package/dist/chunk-BKPWV3OF.js +962 -0
  4. package/dist/chunk-BKPWV3OF.js.map +1 -0
  5. package/dist/{chunk-PMPLCEWQ.js → chunk-GPHWC533.js} +2 -2
  6. package/dist/{chunk-FJYA2ASS.js → chunk-KAK75RVS.js} +74 -12
  7. package/dist/chunk-KAK75RVS.js.map +1 -0
  8. package/dist/{chunk-4LNZV6EN.js → chunk-MGEL5RWM.js} +206 -78
  9. package/dist/chunk-MGEL5RWM.js.map +1 -0
  10. package/dist/chunk-PF6EJZHB.js +1015 -0
  11. package/dist/chunk-PF6EJZHB.js.map +1 -0
  12. package/dist/datafinder/attribute.cjs +537 -11
  13. package/dist/datafinder/attribute.cjs.map +1 -1
  14. package/dist/datafinder/attribute.d.cts +29 -7
  15. package/dist/datafinder/attribute.d.ts +29 -7
  16. package/dist/datafinder/attribute.js +2 -2
  17. package/dist/datafinder/runner.cjs +1267 -215
  18. package/dist/datafinder/runner.cjs.map +1 -1
  19. package/dist/datafinder/runner.d.cts +28 -5
  20. package/dist/datafinder/runner.d.ts +28 -5
  21. package/dist/datafinder/runner.js +6 -6
  22. package/dist/datafinder/sql-generator.cjs +1203 -206
  23. package/dist/datafinder/sql-generator.cjs.map +1 -1
  24. package/dist/datafinder/sql-generator.d.cts +19 -5
  25. package/dist/datafinder/sql-generator.d.ts +19 -5
  26. package/dist/datafinder/sql-generator.js +9 -5
  27. package/dist/datafinder/typed-attributes.cjs +693 -88
  28. package/dist/datafinder/typed-attributes.cjs.map +1 -1
  29. package/dist/datafinder/typed-attributes.d.cts +3 -242
  30. package/dist/datafinder/typed-attributes.d.ts +3 -242
  31. package/dist/datafinder/typed-attributes.js +3 -3
  32. package/dist/dialect-CogGPm_y.d.cts +38 -0
  33. package/dist/dialect-aaG1bDi5.d.ts +38 -0
  34. package/dist/index.cjs +1677 -408
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +109 -5
  37. package/dist/index.d.ts +109 -5
  38. package/dist/index.js +321 -11
  39. package/dist/index.js.map +1 -1
  40. package/dist/model/milestoning.cjs +529 -0
  41. package/dist/model/milestoning.cjs.map +1 -1
  42. package/dist/model/milestoning.js +2 -2
  43. package/dist/model/relational.cjs +547 -16
  44. package/dist/model/relational.cjs.map +1 -1
  45. package/dist/model/relational.d.cts +421 -57
  46. package/dist/model/relational.d.ts +421 -57
  47. package/dist/model/relational.js +53 -3
  48. package/dist/typed-attributes-CUTf_6Ru.d.ts +328 -0
  49. package/dist/typed-attributes-CdWXq_78.d.cts +328 -0
  50. package/package.json +3 -2
  51. package/dist/chunk-4JVW4TAB.js.map +0 -1
  52. package/dist/chunk-4LNZV6EN.js.map +0 -1
  53. package/dist/chunk-FJYA2ASS.js.map +0 -1
  54. package/dist/chunk-MH4XMYVB.js +0 -696
  55. package/dist/chunk-MH4XMYVB.js.map +0 -1
  56. package/dist/chunk-UYMOE6YU.js +0 -509
  57. package/dist/chunk-UYMOE6YU.js.map +0 -1
  58. /package/dist/{chunk-PMPLCEWQ.js.map → chunk-GPHWC533.js.map} +0 -0
@@ -20,35 +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
+ arithmeticNeedsParentheses: () => arithmeticNeedsParentheses,
23
24
  buildQueryOperation: () => buildQueryOperation,
25
+ needsParentheses: () => needsParentheses,
24
26
  selectSqlToString: () => selectSqlToString,
25
27
  toSql: () => toSql
26
28
  });
27
29
  module.exports = __toCommonJS(sql_generator_exports);
28
30
 
29
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
+ }
30
36
  var RelationalOperationElement = class {
31
37
  };
32
38
  var Operation = class extends RelationalOperationElement {
33
- 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) {
34
44
  return new LogicalOperation(this, "AND" /* AND */, other);
35
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
+ }
36
50
  };
37
51
  var NoOperation = class extends Operation {
38
- andOp(other) {
52
+ and(other) {
39
53
  return other;
40
54
  }
55
+ /** No filter matches every row, so OR-ing anything with it still matches every row. */
56
+ or(_other) {
57
+ return this;
58
+ }
41
59
  };
42
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
+ }
73
+ };
74
+ var NumericConstantOperation = class extends ConstantOperation {
75
+ arithmeticOperand() {
76
+ return this;
77
+ }
43
78
  };
44
- var IntegerConstantOperation = class extends ConstantOperation {
79
+ var IntegerConstantOperation = class extends NumericConstantOperation {
45
80
  constructor(value) {
46
81
  super();
47
82
  this.value = value;
48
83
  }
49
84
  value;
50
85
  };
51
- var FloatConstantOperation = class extends ConstantOperation {
86
+ var FloatConstantOperation = class extends NumericConstantOperation {
52
87
  constructor(value) {
53
88
  super();
54
89
  this.value = value;
@@ -62,6 +97,9 @@ var StringConstantOperation = class extends ConstantOperation {
62
97
  }
63
98
  value;
64
99
  };
100
+ var DATE_ONLY_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
101
+ var DateOnly = class extends Date {
102
+ };
65
103
  var DateConstantOperation = class extends ConstantOperation {
66
104
  constructor(value) {
67
105
  super();
@@ -76,7 +114,7 @@ var DateTimeConstantOperation = class extends ConstantOperation {
76
114
  }
77
115
  value;
78
116
  };
79
- var DecimalConstantOperation = class extends ConstantOperation {
117
+ var DecimalConstantOperation = class extends NumericConstantOperation {
80
118
  constructor(value) {
81
119
  super();
82
120
  this.value = value;
@@ -110,6 +148,20 @@ var IsNullOperation = class extends UnaryOperation {
110
148
  };
111
149
  var IsNotNullOperation = class extends UnaryOperation {
112
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
+ };
113
165
  var ComparisonOperation = class extends BinaryOperation {
114
166
  constructor(left, operator, right) {
115
167
  super(left, right);
@@ -133,21 +185,41 @@ var WindowSpecification = class {
133
185
  orderBy;
134
186
  };
135
187
  var AggregateOperation = class _AggregateOperation extends UnaryOperation {
136
- constructor(element, operator, displayName, window) {
188
+ constructor(element, operator, displayName, window, separator, orderBy = []) {
137
189
  super(element);
138
190
  this.operator = operator;
139
191
  this.displayName = displayName;
140
192
  this.window = window;
193
+ this.separator = separator;
194
+ this.orderBy = orderBy;
141
195
  }
142
196
  operator;
143
197
  displayName;
144
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
+ }
145
215
  over(partitionBy, orderBy) {
146
216
  return new _AggregateOperation(
147
217
  this.element,
148
218
  this.operator,
149
219
  this.displayName,
150
- new WindowSpecification(partitionBy ?? [], orderBy ?? [])
220
+ new WindowSpecification(partitionBy ?? [], orderBy ?? []),
221
+ this.separator,
222
+ this.orderBy
151
223
  );
152
224
  }
153
225
  };
@@ -165,6 +237,14 @@ var WindowFunctionOperation = class _WindowFunctionOperation extends UnaryOperat
165
237
  secondArg;
166
238
  extraArgs;
167
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
+ }
168
248
  over(partitionBy, orderBy) {
169
249
  return new _WindowFunctionOperation(
170
250
  this.element,
@@ -188,7 +268,38 @@ var ScalarFunctionOperation = class extends UnaryOperation {
188
268
  displayName;
189
269
  secondArg;
190
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
+ }
191
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
+ ]);
192
303
  var DateExtractOperation = class extends UnaryOperation {
193
304
  constructor(element, part, displayName) {
194
305
  super(element);
@@ -197,6 +308,34 @@ var DateExtractOperation = class extends UnaryOperation {
197
308
  }
198
309
  part;
199
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
+ }
200
339
  };
201
340
  var DateArithmeticOperation = class extends UnaryOperation {
202
341
  constructor(element, n, unit, isAdd = true, displayName) {
@@ -210,6 +349,14 @@ var DateArithmeticOperation = class extends UnaryOperation {
210
349
  unit;
211
350
  isAdd;
212
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
+ }
213
360
  };
214
361
  var DateDiffOperation = class extends UnaryOperation {
215
362
  constructor(element, other, unit, displayName) {
@@ -221,20 +368,31 @@ var DateDiffOperation = class extends UnaryOperation {
221
368
  other;
222
369
  unit;
223
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
+ }
224
379
  };
225
380
  var Column = class extends RelationalOperationElement {
226
- constructor(name, type, owner, primaryKey = false) {
381
+ constructor(name, type, owner, primaryKey = false, nullable = true) {
227
382
  super();
228
383
  this.name = name;
229
384
  this.type = type;
230
385
  this.owner = owner;
231
386
  this.primaryKey = primaryKey;
387
+ this.nullable = nullable && !primaryKey;
232
388
  }
233
389
  name;
234
390
  type;
235
391
  owner;
236
392
  primaryKey;
237
393
  table;
394
+ /** Whether the column may hold NULL. As in Legend, a primary key is never null. */
395
+ nullable;
238
396
  };
239
397
  var Table = class {
240
398
  constructor(name, columns, schema) {
@@ -267,15 +425,24 @@ var ColumnWithJoin = class extends RelationalOperationElement {
267
425
  * `embedded` is an alternate reference to a flat (denormalized) column on the owning table that
268
426
  * holds the same value without the join; the SQL generator uses it when the join can be elided.
269
427
  */
270
- constructor(column, parent, embedded) {
428
+ constructor(column, parent, embedded, _optional = false) {
271
429
  super();
272
430
  this.column = column;
273
431
  this.parent = parent;
274
432
  this.embedded = embedded;
433
+ this._optional = _optional;
275
434
  }
276
435
  column;
277
436
  parent;
278
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
+ }
279
446
  };
280
447
  var SortOperation = class {
281
448
  constructor(column, direction) {
@@ -291,8 +458,23 @@ var CountAllOperation = class extends RelationalOperationElement {
291
458
  this.table = table;
292
459
  }
293
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
+ }
294
476
  };
295
- var Alias = class extends RelationalOperationElement {
477
+ var Alias = class _Alias extends RelationalOperationElement {
296
478
  constructor(element, name) {
297
479
  super();
298
480
  this.element = element;
@@ -300,6 +482,30 @@ var Alias = class extends RelationalOperationElement {
300
482
  }
301
483
  element;
302
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;
303
509
  };
304
510
  var ExistsOperation = class extends RelationalOperationElement {
305
511
  constructor(node) {
@@ -307,9 +513,16 @@ var ExistsOperation = class extends RelationalOperationElement {
307
513
  this.node = node;
308
514
  }
309
515
  node;
310
- andOp(other) {
516
+ /** Name this column in the result. */
517
+ alias(name) {
518
+ return new Alias(this, name);
519
+ }
520
+ and(other) {
311
521
  return new LogicalOperation(this, "AND" /* AND */, other);
312
522
  }
523
+ or(other) {
524
+ return new LogicalOperation(this, "OR" /* OR */, other);
525
+ }
313
526
  };
314
527
  var NotExistsOperation = class extends RelationalOperationElement {
315
528
  constructor(node) {
@@ -317,12 +530,19 @@ var NotExistsOperation = class extends RelationalOperationElement {
317
530
  this.node = node;
318
531
  }
319
532
  node;
320
- andOp(other) {
533
+ /** Name this column in the result. */
534
+ alias(name) {
535
+ return new Alias(this, name);
536
+ }
537
+ and(other) {
321
538
  return new LogicalOperation(this, "AND" /* AND */, other);
322
539
  }
540
+ or(other) {
541
+ return new LogicalOperation(this, "OR" /* OR */, other);
542
+ }
323
543
  };
324
544
  var SelectOperation = class {
325
- constructor(display, filter, orderBy = [], groupBy = [], limit, table, joinFilters = /* @__PURE__ */ new Map()) {
545
+ constructor(display, filter, orderBy = [], groupBy = [], limit, table, joinFilters = /* @__PURE__ */ new Map(), distinct = false, offset, having) {
326
546
  this.display = display;
327
547
  this.filter = filter;
328
548
  this.orderBy = orderBy;
@@ -330,6 +550,9 @@ var SelectOperation = class {
330
550
  this.limit = limit;
331
551
  this.table = table;
332
552
  this.joinFilters = joinFilters;
553
+ this.distinct = distinct;
554
+ this.offset = offset;
555
+ this.having = having;
333
556
  }
334
557
  display;
335
558
  filter;
@@ -338,7 +561,247 @@ var SelectOperation = class {
338
561
  limit;
339
562
  table;
340
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
+ }
341
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
+ }
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]);
342
805
 
343
806
  // src/datafinder/attribute.ts
344
807
  var Attribute = class _Attribute {
@@ -347,9 +810,11 @@ var Attribute = class _Attribute {
347
810
  _owner;
348
811
  _parent;
349
812
  _embedded;
350
- constructor(displayName, columnName, columnDbType, owner, parent, embedded) {
813
+ _optional;
814
+ constructor(displayName, columnName, columnDbType, owner, parent, embedded, nullable = true, optional = false) {
815
+ this._optional = optional;
351
816
  this._displayName = displayName;
352
- this._column = new Column(columnName, columnDbType, owner);
817
+ this._column = new Column(columnName, columnDbType, owner, false, nullable);
353
818
  this._owner = owner;
354
819
  this._parent = parent;
355
820
  this._embedded = embedded;
@@ -370,10 +835,22 @@ var Attribute = class _Attribute {
370
835
  embedded() {
371
836
  return this._embedded;
372
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
+ }
373
850
  cwj() {
374
- return new ColumnWithJoin(this._column, this._parent, this._embedded?.cwj());
851
+ return new ColumnWithJoin(this._column, this._parent, this._embedded?.cwj(), this._optional);
375
852
  }
376
- /** Count the number of non-null values. Use in a projection with `groupByOp` to aggregate per group. */
853
+ /** Count the number of non-null values. Use in a projection with `groupBy` to aggregate per group. */
377
854
  count() {
378
855
  return new AggregateOperation(
379
856
  this.cwj(),
@@ -381,11 +858,26 @@ var Attribute = class _Attribute {
381
858
  this._displayName + " Count"
382
859
  );
383
860
  }
384
- /** Sort this attribute in ascending order. Pass to `orderByOp` or a window function's `orderBy`. */
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`. */
385
877
  ascending() {
386
878
  return new SortOperation(this.cwj(), "ASC" /* ASC */);
387
879
  }
388
- /** Sort this attribute in descending order. Pass to `orderByOp` or a window function's `orderBy`. */
880
+ /** Sort this attribute in descending order. Pass to `orderBy` or a window function's `orderBy`. */
389
881
  descending() {
390
882
  return new SortOperation(this.cwj(), "DESC" /* DESC */);
391
883
  }
@@ -397,6 +889,23 @@ var Attribute = class _Attribute {
397
889
  isNotNull() {
398
890
  return new IsNotNullOperation(this.cwj());
399
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));
908
+ }
400
909
  _windowSpec(partitionBy, orderBy) {
401
910
  const pbArr = partitionBy === void 0 ? [] : Array.isArray(partitionBy) ? partitionBy : [partitionBy];
402
911
  const pb = pbArr.map((x) => x instanceof _Attribute ? x.cwj() : x);
@@ -582,16 +1091,215 @@ var MilestonedTable = class extends Table {
582
1091
  milestoningColumns;
583
1092
  };
584
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
+
585
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]);
586
1286
  function parseDate(value) {
587
1287
  if (value instanceof Date) return value;
588
1288
  const d = new Date(value);
589
1289
  if (isNaN(d.getTime())) throw new Error(`Cannot parse date: ${value}`);
590
1290
  return d;
591
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
+ }
592
1297
  var DateAttribute = class extends Attribute {
593
- constructor(displayName, columnName, columnDbType, owner, parent, embedded) {
594
- super(displayName, columnName, columnDbType, owner, parent, embedded);
1298
+ constructor(displayName, columnName, columnDbType, owner, parent, embedded, nullable = true, optional = false) {
1299
+ super(displayName, columnName, columnDbType, owner, parent, embedded, nullable, optional);
1300
+ }
1301
+ toConstant(value) {
1302
+ return new DateConstantOperation(parseDate(value));
595
1303
  }
596
1304
  /** Filter to rows where this date equals `value`. */
597
1305
  eq(value) {
@@ -641,6 +1349,22 @@ var DateAttribute = class extends Attribute {
641
1349
  dayOfWeek() {
642
1350
  return new DateExtractOperation(this.cwj(), "DOW" /* DOW */, "Day Of Week " + this.displayName());
643
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
+ }
644
1368
  /** Add `n` days to this date. */
645
1369
  addDays(n) {
646
1370
  return new DateArithmeticOperation(this.cwj(), n, "DAY" /* DAY */, true, "Add Days " + this.displayName());
@@ -679,32 +1403,35 @@ var DateAttribute = class extends Attribute {
679
1403
  }
680
1404
  };
681
1405
  var DateTimeAttribute = class extends Attribute {
682
- constructor(displayName, columnName, columnDbType, owner, parent, embedded) {
683
- super(displayName, columnName, columnDbType, owner, parent, embedded);
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);
684
1411
  }
685
1412
  /** Filter to rows where this timestamp equals `value`. */
686
1413
  eq(value) {
687
- return new ComparisonOperation(this.cwj(), "EQUAL" /* EQUAL */, new DateTimeConstantOperation(parseDate(value)));
1414
+ return new ComparisonOperation(this.cwj(), "EQUAL" /* EQUAL */, temporalConstant(value));
688
1415
  }
689
1416
  /** Filter to rows where this timestamp does not equal `value`. */
690
1417
  ne(value) {
691
- 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));
692
1419
  }
693
1420
  /** Filter to rows where this timestamp is after `value`. */
694
1421
  gt(value) {
695
- 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));
696
1423
  }
697
1424
  /** Filter to rows where this timestamp is before `value`. */
698
1425
  lt(value) {
699
- 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));
700
1427
  }
701
1428
  /** Filter to rows where this timestamp is on or after `value`. */
702
1429
  gte(value) {
703
- 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));
704
1431
  }
705
1432
  /** Filter to rows where this timestamp is on or before `value`. */
706
1433
  lte(value) {
707
- 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));
708
1435
  }
709
1436
  /** Extract the four-digit calendar year. */
710
1437
  year() {
@@ -742,6 +1469,22 @@ var DateTimeAttribute = class extends Attribute {
742
1469
  dayOfWeek() {
743
1470
  return new DateExtractOperation(this.cwj(), "DOW" /* DOW */, "Day Of Week " + this.displayName());
744
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
+ }
745
1488
  /** Add `n` days to this timestamp. */
746
1489
  addDays(n) {
747
1490
  return new DateArithmeticOperation(this.cwj(), n, "DAY" /* DAY */, true, "Add Days " + this.displayName());
@@ -815,6 +1558,69 @@ var DateTimeAttribute = class extends Attribute {
815
1558
  return new DateDiffOperation(this.cwj(), other, "SECOND" /* SECOND */, "Diff Seconds " + this.displayName());
816
1559
  }
817
1560
  };
1561
+ applyMixins(DateAttribute, [MinMax]);
1562
+ applyMixins(DateTimeAttribute, [MinMax]);
1563
+
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(", ") : ""})`
1593
+ };
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(", ") + ")" : ""}`
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
+ }
818
1624
 
819
1625
  // src/datafinder/sql-generator.ts
820
1626
  var TableAlias = class {
@@ -843,6 +1649,12 @@ var Join = class {
843
1649
  source;
844
1650
  target;
845
1651
  filterOp;
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;
1657
+ }
846
1658
  };
847
1659
  function sqlFormatDatetime(value) {
848
1660
  const pad = (n, w = 2) => String(n).padStart(w, "0");
@@ -869,6 +1681,13 @@ var COMPARISON_OPERATOR_STR = {
869
1681
  var AGGREGATE_SQL_NAMES = {
870
1682
  ["AVERAGE" /* AVERAGE */]: "AVG"
871
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
+ ]);
872
1691
  var WINDOW_SQL_NAMES = {
873
1692
  ["ROW_NUMBER" /* ROW_NUMBER */]: "ROW_NUMBER",
874
1693
  ["RANK" /* RANK */]: "RANK",
@@ -900,31 +1719,63 @@ var SCALAR_SQL_NAMES = {
900
1719
  ["RIGHT" /* RIGHT */]: "RIGHT",
901
1720
  ["REPEAT" /* REPEAT */]: "REPEAT",
902
1721
  ["REPLACE" /* REPLACE */]: "REPLACE",
903
- ["SUBSTRING" /* SUBSTRING */]: "SUBSTRING"
1722
+ ["SUBSTRING" /* SUBSTRING */]: "SUBSTRING",
1723
+ ["COALESCE" /* COALESCE */]: "COALESCE"
904
1724
  };
905
- function constantValueString(op) {
906
- if (op instanceof StringConstantOperation) return "'" + op.value + "'";
1725
+ function constantValueString(op, d) {
1726
+ if (op instanceof StringConstantOperation) return d.stringLiteral(op.value);
907
1727
  if (op instanceof DateConstantOperation) return sqlFormatDate(op.value);
908
1728
  if (op instanceof DateTimeConstantOperation) return sqlFormatDatetime(op.value);
909
1729
  if (op instanceof IntegerConstantOperation) return String(op.value);
910
- 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);
911
1731
  if (op instanceof BooleanConstantOperation) return op.value ? "TRUE" : "FALSE";
912
1732
  if (op instanceof DecimalConstantOperation) return String(op.value);
913
1733
  throw new Error("Unknown constant operation");
914
1734
  }
915
- 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) {
916
1742
  const parts = [];
917
1743
  if (window.partitionBy.length > 0) {
918
- parts.push("PARTITION BY " + window.partitionBy.map((p) => sqlOperationToString(p)).join(", "));
1744
+ parts.push("PARTITION BY " + window.partitionBy.map((p) => sqlOperationToString(p, d)).join(", "));
919
1745
  }
920
1746
  if (window.orderBy.length > 0) {
921
1747
  parts.push("ORDER BY " + window.orderBy.map(
922
- (s) => sqlOperationToString(s.column) + (s.direction === "ASC" /* ASC */ ? " ASC" : " DESC")
1748
+ (s) => sqlOperationToString(s.column, d) + d.orderDirection(s.direction)
923
1749
  ).join(", "));
924
1750
  }
925
1751
  return "OVER (" + parts.join(" ") + ")";
926
1752
  }
927
- 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) {
928
1779
  if (operation instanceof TableAliasColumn) {
929
1780
  return operation.tableAlias.alias + "." + operation.column.name;
930
1781
  }
@@ -932,46 +1783,71 @@ function sqlOperationToString(operation) {
932
1783
  const fn = WINDOW_SQL_NAMES[operation.func];
933
1784
  const parts = [];
934
1785
  if (operation.element !== null && operation.element !== void 0) {
935
- parts.push(sqlOperationToString(operation.element));
1786
+ parts.push(sqlOperationToString(operation.element, d));
936
1787
  }
937
1788
  if (operation.secondArg !== void 0) parts.push(String(operation.secondArg));
938
1789
  for (const arg of operation.extraArgs) {
939
- parts.push(typeof arg === "string" ? "'" + arg + "'" : String(arg));
1790
+ parts.push(typeof arg === "string" ? d.stringLiteral(arg) : String(arg));
940
1791
  }
941
1792
  const sql = fn + "(" + parts.join(", ") + ")";
942
- 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;
943
1806
  }
944
1807
  if (operation instanceof AggregateOperation) {
945
1808
  const fn = AGGREGATE_SQL_NAMES[operation.operator] ?? operation.operator;
946
- let sql = fn + "(" + sqlOperationToString(operation.element) + ")";
1809
+ let sql = fn + "(" + sqlOperationToString(operation.element, d) + ")";
947
1810
  if (operation.window !== void 0) {
948
- sql += " " + windowSpecToString(operation.window);
1811
+ sql += " " + windowSpecToString(operation.window, d);
949
1812
  }
950
1813
  return sql;
951
1814
  }
952
1815
  if (operation instanceof ScalarFunctionOperation) {
953
1816
  const fn = SCALAR_SQL_NAMES[operation.func];
954
- const parts = [sqlOperationToString(operation.element)];
1817
+ const parts = [sqlOperationToString(operation.element, d)];
955
1818
  if (operation.secondArg !== void 0) parts.push(String(operation.secondArg));
956
1819
  for (const arg of operation.extraArgs) {
957
- parts.push(typeof arg === "string" ? "'" + arg + "'" : String(arg));
1820
+ parts.push(typeof arg === "string" ? d.stringLiteral(arg) : String(arg));
958
1821
  }
959
1822
  return fn + "(" + parts.join(", ") + ")";
960
1823
  }
961
1824
  if (operation instanceof DateExtractOperation) {
962
- 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)";
963
1830
  }
964
1831
  if (operation instanceof DateArithmeticOperation) {
965
1832
  const op = operation.isAdd ? "+" : "-";
966
- return sqlOperationToString(operation.element) + " " + op + " INTERVAL " + operation.n + " " + operation.unit;
1833
+ return sqlOperationToString(operation.element, d) + " " + op + " INTERVAL '" + operation.n + "' " + operation.unit;
967
1834
  }
968
1835
  if (operation instanceof DateDiffOperation) {
969
- const otherSql = sqlFormatDatetime(operation.other);
970
- return "DATE_DIFF('" + operation.unit.toLowerCase() + "', " + sqlOperationToString(operation.element) + ", " + otherSql + ")";
1836
+ return d.dateDiff(operation.unit, sqlOperationToString(operation.element, d), "TIMESTAMP " + sqlFormatDatetime(operation.other));
971
1837
  }
972
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
+ }
973
1849
  if (operation instanceof Alias) {
974
- return sqlOperationToString(operation.element) + ' AS "' + operation.name + '"';
1850
+ return sqlOperationToString(operation.element, d) + " AS " + d.quoteIdentifier(operation.name);
975
1851
  }
976
1852
  throw new TypeError("Unknown operation in sqlOperationToString: " + operation.constructor.name);
977
1853
  }
@@ -991,73 +1867,53 @@ function openEndClause(endAttr, value, infiniteDatetime) {
991
1867
  }
992
1868
  return gtOp;
993
1869
  }
994
- function buildMilestoningFilterOperation(businessDate, processingDatetime, table, joinNode) {
995
- const mc = table.milestoningColumns;
996
- const ops = [];
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;
997
1883
  if (mc instanceof BiTemporalColumns) {
998
- if (businessDate !== null) {
999
- const dateFrom = new DateAttribute("business_date_from", mc.businessDateFromColumn.name, mc.businessDateFromColumn.type, mc.businessDateFromColumn.table.qualifiedName, joinNode);
1000
- const dateTo = new DateAttribute("business_date_to", mc.businessDateToColumn.name, mc.businessDateToColumn.type, mc.businessDateToColumn.table.qualifiedName, joinNode);
1001
- ops.push(new LogicalOperation(dateFrom.lte(businessDate), "AND" /* AND */, openEndClause(dateTo, businessDate, mc.infiniteDatetime)));
1002
- }
1003
- if (processingDatetime !== null) {
1004
- const startAt = new DateTimeAttribute("start_at", mc.startAtColumn.name, mc.startAtColumn.type, mc.startAtColumn.table.qualifiedName, joinNode);
1005
- const endAt = new DateTimeAttribute("end_at", mc.endAtColumn.name, mc.endAtColumn.type, mc.endAtColumn.table.qualifiedName, joinNode);
1006
- ops.push(new LogicalOperation(startAt.lte(processingDatetime), "AND" /* AND */, openEndClause(endAt, processingDatetime, mc.infiniteDatetime)));
1007
- }
1008
- } else if (mc instanceof BusinessDateAndProcessingTemporalColumns) {
1009
- if (businessDate !== null) {
1010
- const businessAtt = new DateAttribute("business_date", mc.businessDateColumn.name, mc.businessDateColumn.type, mc.businessDateColumn.table.qualifiedName, joinNode);
1011
- ops.push(businessAtt.eq(businessDate));
1012
- }
1013
- if (processingDatetime !== null) {
1014
- const startAt = new DateTimeAttribute("start_at", mc.startAtColumn.name, mc.startAtColumn.type, mc.startAtColumn.table.qualifiedName, joinNode);
1015
- const endAt = new DateTimeAttribute("end_at", mc.endAtColumn.name, mc.endAtColumn.type, mc.endAtColumn.table.qualifiedName, joinNode);
1016
- ops.push(new LogicalOperation(startAt.lte(processingDatetime), "AND" /* AND */, openEndClause(endAt, processingDatetime, mc.infiniteDatetime)));
1017
- }
1018
- } else if (mc instanceof ProcessingTemporalColumns && processingDatetime !== null) {
1019
- const startAt = new DateTimeAttribute("start_at", mc.startAtColumn.name, mc.startAtColumn.type, mc.startAtColumn.table.qualifiedName, joinNode);
1020
- const endAt = new DateTimeAttribute("end_at", mc.endAtColumn.name, mc.endAtColumn.type, mc.endAtColumn.table.qualifiedName, joinNode);
1021
- ops.push(new LogicalOperation(startAt.lte(processingDatetime), "AND" /* AND */, openEndClause(endAt, processingDatetime, mc.infiniteDatetime)));
1022
- } else if (mc instanceof SingleBusinessDateColumn && businessDate !== null) {
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) {
1023
1889
  const businessAtt = new DateAttribute("business_date", mc.businessDateColumn.name, mc.businessDateColumn.type, mc.businessDateColumn.table.qualifiedName, joinNode);
1024
- ops.push(businessAtt.eq(businessDate));
1890
+ return businessAtt.eq(businessDate);
1025
1891
  }
1026
- if (ops.length === 0) return null;
1027
- if (ops.length === 1) return ops[0];
1028
- return new LogicalOperation(ops[0], "AND" /* AND */, ops[1]);
1892
+ return null;
1029
1893
  }
1030
- function buildMilestoningFilterOperationForDateRange(businessDateFrom, businessDateTo, processingDatetime, table, joinNode) {
1031
- const mc = table.milestoningColumns;
1032
- const ops = [];
1033
- const hasRange = businessDateFrom !== null && businessDateTo !== null;
1034
- const processingOp = (m) => {
1035
- const startAt = new DateTimeAttribute("start_at", m.startAtColumn.name, m.startAtColumn.type, m.startAtColumn.table.qualifiedName, joinNode);
1036
- const endAt = new DateTimeAttribute("end_at", m.endAtColumn.name, m.endAtColumn.type, m.endAtColumn.table.qualifiedName, joinNode);
1037
- return new LogicalOperation(startAt.lte(processingDatetime), "AND" /* AND */, openEndClause(endAt, processingDatetime, m.infiniteDatetime));
1038
- };
1894
+ function businessRangePredicate(mc, businessDateFrom, businessDateTo, joinNode) {
1895
+ if (businessDateFrom === null || businessDateTo === null) return null;
1039
1896
  if (mc instanceof BiTemporalColumns) {
1040
- if (hasRange) {
1041
- const dateFrom = new DateAttribute("business_date_from", mc.businessDateFromColumn.name, mc.businessDateFromColumn.type, mc.businessDateFromColumn.table.qualifiedName, joinNode);
1042
- const dateTo = new DateAttribute("business_date_to", mc.businessDateToColumn.name, mc.businessDateToColumn.type, mc.businessDateToColumn.table.qualifiedName, joinNode);
1043
- ops.push(new LogicalOperation(dateFrom.lte(businessDateTo), "AND" /* AND */, openEndClause(dateTo, businessDateFrom, mc.infiniteDatetime)));
1044
- }
1045
- if (processingDatetime !== null) ops.push(processingOp(mc));
1046
- } else if (mc instanceof BusinessDateAndProcessingTemporalColumns) {
1047
- if (hasRange) {
1048
- const businessAtt = new DateAttribute("business_date", mc.businessDateColumn.name, mc.businessDateColumn.type, mc.businessDateColumn.table.qualifiedName, joinNode);
1049
- ops.push(new LogicalOperation(businessAtt.gte(businessDateFrom), "AND" /* AND */, businessAtt.lte(businessDateTo)));
1050
- }
1051
- if (processingDatetime !== null) ops.push(processingOp(mc));
1052
- } else if (mc instanceof ProcessingTemporalColumns) {
1053
- if (processingDatetime !== null) ops.push(processingOp(mc));
1054
- } else if (mc instanceof SingleBusinessDateColumn && hasRange) {
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) {
1055
1902
  const businessAtt = new DateAttribute("business_date", mc.businessDateColumn.name, mc.businessDateColumn.type, mc.businessDateColumn.table.qualifiedName, joinNode);
1056
- ops.push(new LogicalOperation(businessAtt.gte(businessDateFrom), "AND" /* AND */, businessAtt.lte(businessDateTo)));
1903
+ return new LogicalOperation(businessAtt.gte(businessDateFrom), "AND" /* AND */, businessAtt.lte(businessDateTo));
1057
1904
  }
1058
- if (ops.length === 0) return null;
1059
- if (ops.length === 1) return ops[0];
1060
- return new LogicalOperation(ops[0], "AND" /* AND */, ops[1]);
1905
+ return null;
1906
+ }
1907
+ function buildMilestoningFilterOperation(businessDate, processingDatetime, table, joinNode) {
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
+ ]);
1061
1917
  }
1062
1918
  function walkRefs(op, visit, visitExists) {
1063
1919
  if (op === null || op === void 0) return;
@@ -1068,11 +1924,23 @@ function walkRefs(op, visit, visitExists) {
1068
1924
  } else if (op instanceof AggregateOperation) {
1069
1925
  walkRefs(op.element, visit, visitExists);
1070
1926
  if (op.window) walkWindowSpec(op.window, visit, visitExists);
1927
+ for (const sort of op.orderBy) walkRefs(sort, visit, visitExists);
1071
1928
  } else if (op instanceof WindowFunctionOperation) {
1072
1929
  if (op.element) walkRefs(op.element, visit, visitExists);
1073
1930
  if (op.window) walkWindowSpec(op.window, visit, visitExists);
1074
- } else if (op instanceof ScalarFunctionOperation || op instanceof DateExtractOperation || op instanceof DateArithmeticOperation || op instanceof DateDiffOperation || op instanceof IsNullOperation || op instanceof IsNotNullOperation) {
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) {
1075
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);
1076
1944
  } else if (op instanceof SortOperation) {
1077
1945
  walkRefs(op.column, visit, visitExists);
1078
1946
  } else if (op instanceof LogicalOperation || op instanceof ComparisonOperation) {
@@ -1112,7 +1980,7 @@ function withAncestors(nodes) {
1112
1980
  for (const node of nodes) add(node);
1113
1981
  return [...ordered];
1114
1982
  }
1115
- function buildQueryOperation(businessDate, processingDatetime, columns, table, op, orderBy = [], groupBy = [], limit, businessDateTo = null) {
1983
+ function buildQueryOperation(businessDate, processingDatetime, columns, table, op, orderBy = [], groupBy = [], limit, businessDateTo = null, distinct = false, offset, having) {
1116
1984
  let filter = op;
1117
1985
  const milestoningFor = (t, node) => businessDateTo !== null ? buildMilestoningFilterOperationForDateRange(businessDate, businessDateTo, processingDatetime, t, node) : buildMilestoningFilterOperation(businessDate, processingDatetime, t, node);
1118
1986
  if (table instanceof MilestonedTable) {
@@ -1145,22 +2013,45 @@ function buildQueryOperation(businessDate, processingDatetime, columns, table, o
1145
2013
  }
1146
2014
  if (combined !== void 0) joinFilters.set(node, combined);
1147
2015
  }
1148
- return new SelectOperation(columns, filter, orderBy, groupBy, limit, table, joinFilters);
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;
1149
2032
  }
1150
2033
  var SQLQueryGenerator = class {
2034
+ constructor(_dialect) {
2035
+ this._dialect = _dialect;
2036
+ }
2037
+ _dialect;
1151
2038
  _select = [];
1152
2039
  _from = /* @__PURE__ */ new Set();
1153
2040
  _join = [];
1154
2041
  _groupByParts = [];
2042
+ _having = "";
1155
2043
  _orderByParts = [];
1156
2044
  _where = "";
1157
2045
  _limit;
2046
+ _distinct = false;
2047
+ _offset;
1158
2048
  _tableAliasIncr = 0;
1159
2049
  _tableAliasesByTable = /* @__PURE__ */ new Map();
1160
2050
  _addedJoinIds = /* @__PURE__ */ new Set();
1161
2051
  _rootTable;
1162
2052
  _elidableNodes = /* @__PURE__ */ new Set();
1163
2053
  _joinFilters = /* @__PURE__ */ new Map();
2054
+ _guardOptional = true;
1164
2055
  generate(select) {
1165
2056
  this._rootTable = select.table;
1166
2057
  this._elidableNodes = this._computeElidableNodes(select);
@@ -1177,11 +2068,52 @@ var SQLQueryGenerator = class {
1177
2068
  for (const node of groupByJoins.values()) {
1178
2069
  this._addJoin(node);
1179
2070
  }
1180
- 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
+ }
1181
2084
  this._orderByParts = select.orderBy.map(
1182
- (s) => this.buildFilter(s.column) + (s.direction === "ASC" /* ASC */ ? " ASC" : " DESC")
2085
+ (s) => this._sortExpression(s.column) + this._dialect.orderDirection(s.direction)
1183
2086
  );
1184
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;
1185
2117
  }
1186
2118
  _attrToColString(attr) {
1187
2119
  const { column, parent } = this._resolve(attr);
@@ -1228,6 +2160,13 @@ var SQLQueryGenerator = class {
1228
2160
  }
1229
2161
  return { column, parent };
1230
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);
2167
+ }
2168
+ return op.optional;
2169
+ }
1231
2170
  _rewriteWindowSpec(window) {
1232
2171
  const partitionBy = window.partitionBy.map((p) => this._rewriteOperation(p));
1233
2172
  const orderBy = window.orderBy.map((s) => new SortOperation(this._rewriteOperation(s.column), s.direction));
@@ -1247,9 +2186,17 @@ var SQLQueryGenerator = class {
1247
2186
  this._rewriteOperation(op.element),
1248
2187
  op.operator,
1249
2188
  op.displayName,
1250
- 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))
1251
2192
  );
1252
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
+ }
1253
2200
  if (op instanceof WindowFunctionOperation) {
1254
2201
  const el = op.element ? this._rewriteOperation(op.element) : null;
1255
2202
  return new WindowFunctionOperation(
@@ -1267,6 +2214,9 @@ var SQLQueryGenerator = class {
1267
2214
  if (op instanceof DateExtractOperation) {
1268
2215
  return new DateExtractOperation(this._rewriteOperation(op.element), op.part, op.displayName);
1269
2216
  }
2217
+ if (op instanceof DateTruncOperation) {
2218
+ return new DateTruncOperation(this._rewriteOperation(op.element), op.part, op.displayName);
2219
+ }
1270
2220
  if (op instanceof DateArithmeticOperation) {
1271
2221
  return new DateArithmeticOperation(this._rewriteOperation(op.element), op.n, op.unit, op.isAdd, op.displayName);
1272
2222
  }
@@ -1275,6 +2225,13 @@ var SQLQueryGenerator = class {
1275
2225
  }
1276
2226
  return op;
1277
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
+ }
1278
2235
  processSelect(cols) {
1279
2236
  const requiredJoins = /* @__PURE__ */ new Map();
1280
2237
  for (const col of cols) {
@@ -1283,88 +2240,96 @@ var SQLQueryGenerator = class {
1283
2240
  for (const node of requiredJoins.values()) {
1284
2241
  this._addJoin(node);
1285
2242
  }
1286
- for (const col of cols) {
1287
- if (col instanceof Attribute) {
1288
- const { column, parent: node } = this._resolve(col);
1289
- let ta;
1290
- if (node !== void 0) {
1291
- ta = this._tableAliasForTable(column.owner, node);
1292
- } else {
1293
- ta = this._tableAliasForTable(column.owner, column.owner);
1294
- this._from.add(ta);
1295
- }
1296
- this._select.push(new Alias(new TableAliasColumn(column, ta), col.displayName()));
1297
- } else if (col instanceof WindowFunctionOperation) {
1298
- const rewritten = this._rewriteOperation(col);
1299
- let alias;
1300
- if (col.displayName) {
1301
- alias = col.displayName;
1302
- } else if (["ROW_NUMBER" /* ROW_NUMBER */, "RANK" /* RANK */, "DENSE_RANK" /* DENSE_RANK */].includes(col.func)) {
1303
- alias = col.func.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
1304
- } else if (col.element) {
1305
- alias = col.func + " " + findColumn(col).column.name;
1306
- } else {
1307
- alias = col.func;
1308
- }
1309
- this._select.push(new Alias(rewritten, alias));
1310
- } else if (col instanceof AggregateOperation) {
1311
- const colNested = this._resolve(findColumn(col));
1312
- const node = colNested.parent;
1313
- let ta;
1314
- if (node !== void 0) {
1315
- ta = this._tableAliasForTable(colNested.column.owner, node);
1316
- } else {
1317
- ta = this._tableAliasForTable(colNested.column.owner, colNested.column.owner);
1318
- this._from.add(ta);
1319
- }
1320
- const alias = col.displayName ?? col.operator + " " + findColumn(col).column.name;
1321
- const rewrittenAgg = new AggregateOperation(
1322
- new TableAliasColumn(colNested.column, ta),
1323
- col.operator,
1324
- void 0,
1325
- col.window ? this._rewriteWindowSpec(col.window) : void 0
1326
- );
1327
- this._select.push(new Alias(rewrittenAgg, alias));
1328
- } else if (col instanceof ScalarFunctionOperation) {
1329
- const colNested = this._resolve(findColumn(col));
1330
- const node = colNested.parent;
1331
- let ta;
1332
- if (node !== void 0) {
1333
- ta = this._tableAliasForTable(colNested.column.owner, node);
1334
- } else {
1335
- ta = this._tableAliasForTable(colNested.column.owner, colNested.column.owner);
1336
- this._from.add(ta);
1337
- }
1338
- const alias = col.displayName ?? col.func + " " + findColumn(col).column.name;
1339
- this._select.push(new Alias(
1340
- new ScalarFunctionOperation(new TableAliasColumn(colNested.column, ta), col.func, void 0, col.secondArg, col.extraArgs),
1341
- alias
1342
- ));
1343
- } else if (col instanceof DateExtractOperation || col instanceof DateArithmeticOperation || col instanceof DateDiffOperation) {
1344
- const colNested = this._resolve(findColumn(col));
1345
- const node = colNested.parent;
1346
- let ta;
1347
- if (node !== void 0) {
1348
- ta = this._tableAliasForTable(colNested.column.owner, node);
1349
- } else {
1350
- ta = this._tableAliasForTable(colNested.column.owner, colNested.column.owner);
1351
- this._from.add(ta);
1352
- }
1353
- const alias = col.displayName ?? findColumn(col).column.name;
1354
- let aliasedOp;
1355
- if (col instanceof DateExtractOperation) {
1356
- aliasedOp = new DateExtractOperation(new TableAliasColumn(colNested.column, ta), col.part);
1357
- } else if (col instanceof DateArithmeticOperation) {
1358
- aliasedOp = new DateArithmeticOperation(new TableAliasColumn(colNested.column, ta), col.n, col.unit, col.isAdd);
1359
- } else {
1360
- aliasedOp = new DateDiffOperation(new TableAliasColumn(colNested.column, ta), col.other, col.unit);
1361
- }
1362
- this._select.push(new Alias(aliasedOp, alias));
1363
- } else if (col instanceof CountAllOperation) {
1364
- 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);
1365
2306
  this._from.add(ta);
1366
- this._select.push(new Alias(col, "Count"));
1367
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"));
1368
2333
  }
1369
2334
  }
1370
2335
  _addJoin(node) {
@@ -1392,19 +2357,43 @@ var SQLQueryGenerator = class {
1392
2357
  const col = op.node.join.right;
1393
2358
  return this.buildFilter(new ColumnWithJoin(col, op.node)) + " IS NULL";
1394
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
+ }
1395
2371
  if (op instanceof IsNotNullOperation) return this.buildFilter(op.element) + " IS NOT NULL";
1396
2372
  if (op instanceof IsNullOperation) return this.buildFilter(op.element) + " IS NULL";
1397
2373
  if (op instanceof LogicalOperation) {
1398
2374
  let left = this.buildFilter(op.left);
1399
2375
  let right = this.buildFilter(op.right);
1400
- if (op.left instanceof LogicalOperation) left = "(" + left + ")";
1401
- if (op.right instanceof LogicalOperation) right = "(" + right + ")";
2376
+ if (needsParentheses(op, op.left)) left = "(" + left + ")";
2377
+ if (needsParentheses(op, op.right)) right = "(" + right + ")";
1402
2378
  return left + LOGICAL_OPERATOR_STR[op.operator] + right;
1403
2379
  }
1404
2380
  if (op instanceof ComparisonOperation) {
1405
- 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);
1406
2396
  }
1407
- if (op instanceof ConstantOperation) return constantValueString(op);
1408
2397
  if (op instanceof ColumnWithJoin) {
1409
2398
  const { column, parent: node } = this._resolve(op);
1410
2399
  let ta;
@@ -1432,9 +2421,9 @@ var SQLQueryGenerator = class {
1432
2421
  }
1433
2422
  buildQueryString() {
1434
2423
  const joins = this._join.map(
1435
- (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) : "") + ")"
1436
2425
  );
1437
- 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();
1438
2427
  }
1439
2428
  _buildWhere() {
1440
2429
  return this._where.length > 0 ? " WHERE " + this._where : "";
@@ -1442,25 +2431,33 @@ var SQLQueryGenerator = class {
1442
2431
  _buildGroupBy() {
1443
2432
  return this._groupByParts.length > 0 ? " GROUP BY " + this._groupByParts.join(", ") : "";
1444
2433
  }
2434
+ _buildHaving() {
2435
+ return this._having.length > 0 ? " HAVING " + this._having : "";
2436
+ }
1445
2437
  _buildOrderBy() {
1446
2438
  return this._orderByParts.length > 0 ? " ORDER BY " + this._orderByParts.join(", ") : "";
1447
2439
  }
2440
+ _buildOffset() {
2441
+ return this._offset !== void 0 ? " OFFSET " + this._offset : "";
2442
+ }
1448
2443
  _buildLimit() {
1449
2444
  return this._limit !== void 0 ? " LIMIT " + this._limit : "";
1450
2445
  }
1451
2446
  };
1452
- function selectSqlToString(selectOperation) {
1453
- const qg = new SQLQueryGenerator();
2447
+ function selectSqlToString(selectOperation, dialect = "postgres") {
2448
+ const qg = new SQLQueryGenerator(dialectOf(dialect));
1454
2449
  qg.generate(selectOperation);
1455
2450
  return qg.buildQueryString();
1456
2451
  }
1457
- function toSql(businessDate, processingDatetime, columns, table, op, orderBy = [], groupBy = [], limit, businessDateTo = null) {
1458
- const selectOp = buildQueryOperation(businessDate, processingDatetime, columns, table, op, orderBy, groupBy, limit, businessDateTo);
1459
- 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);
1460
2455
  }
1461
2456
  // Annotate the CommonJS export names for ESM import in node:
1462
2457
  0 && (module.exports = {
2458
+ arithmeticNeedsParentheses,
1463
2459
  buildQueryOperation,
2460
+ needsParentheses,
1464
2461
  selectSqlToString,
1465
2462
  toSql
1466
2463
  });