data-finder-ts 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +17 -55
  2. package/dist/chunk-BGEX377Q.js +230 -0
  3. package/dist/chunk-BGEX377Q.js.map +1 -0
  4. package/dist/chunk-BKPWV3OF.js +962 -0
  5. package/dist/chunk-BKPWV3OF.js.map +1 -0
  6. package/dist/{chunk-EYXBYV2X.js → chunk-GPHWC533.js} +2 -2
  7. package/dist/chunk-KAK75RVS.js +198 -0
  8. package/dist/chunk-KAK75RVS.js.map +1 -0
  9. package/dist/chunk-MGEL5RWM.js +628 -0
  10. package/dist/chunk-MGEL5RWM.js.map +1 -0
  11. package/dist/chunk-PF6EJZHB.js +1015 -0
  12. package/dist/chunk-PF6EJZHB.js.map +1 -0
  13. package/dist/datafinder/attribute.cjs +595 -18
  14. package/dist/datafinder/attribute.cjs.map +1 -1
  15. package/dist/datafinder/attribute.d.cts +73 -10
  16. package/dist/datafinder/attribute.d.ts +73 -10
  17. package/dist/datafinder/attribute.js +2 -2
  18. package/dist/datafinder/runner.cjs +1589 -288
  19. package/dist/datafinder/runner.cjs.map +1 -1
  20. package/dist/datafinder/runner.d.cts +41 -7
  21. package/dist/datafinder/runner.d.ts +41 -7
  22. package/dist/datafinder/runner.js +12 -6
  23. package/dist/datafinder/sql-generator.cjs +1484 -281
  24. package/dist/datafinder/sql-generator.cjs.map +1 -1
  25. package/dist/datafinder/sql-generator.d.cts +19 -14
  26. package/dist/datafinder/sql-generator.d.ts +19 -14
  27. package/dist/datafinder/sql-generator.js +9 -7
  28. package/dist/datafinder/typed-attributes.cjs +856 -93
  29. package/dist/datafinder/typed-attributes.cjs.map +1 -1
  30. package/dist/datafinder/typed-attributes.d.cts +3 -135
  31. package/dist/datafinder/typed-attributes.d.ts +3 -135
  32. package/dist/datafinder/typed-attributes.js +3 -3
  33. package/dist/dialect-CogGPm_y.d.cts +38 -0
  34. package/dist/dialect-aaG1bDi5.d.ts +38 -0
  35. package/dist/index.cjs +2080 -480
  36. package/dist/index.cjs.map +1 -1
  37. package/dist/index.d.cts +119 -14
  38. package/dist/index.d.ts +119 -14
  39. package/dist/index.js +362 -12
  40. package/dist/index.js.map +1 -1
  41. package/dist/model/milestoning.cjs +529 -0
  42. package/dist/model/milestoning.cjs.map +1 -1
  43. package/dist/model/milestoning.js +2 -2
  44. package/dist/model/relational.cjs +608 -12
  45. package/dist/model/relational.cjs.map +1 -1
  46. package/dist/model/relational.d.cts +456 -47
  47. package/dist/model/relational.d.ts +456 -47
  48. package/dist/model/relational.js +61 -3
  49. package/dist/typed-attributes-CUTf_6Ru.d.ts +328 -0
  50. package/dist/typed-attributes-CdWXq_78.d.cts +328 -0
  51. package/package.json +5 -2
  52. package/dist/chunk-3SSWFES4.js +0 -448
  53. package/dist/chunk-3SSWFES4.js.map +0 -1
  54. package/dist/chunk-5K2Z3YDY.js +0 -393
  55. package/dist/chunk-5K2Z3YDY.js.map +0 -1
  56. package/dist/chunk-6QTCGJWX.js +0 -98
  57. package/dist/chunk-6QTCGJWX.js.map +0 -1
  58. package/dist/chunk-DFYZVBII.js +0 -652
  59. package/dist/chunk-DFYZVBII.js.map +0 -1
  60. package/dist/chunk-V647VNMY.js +0 -135
  61. package/dist/chunk-V647VNMY.js.map +0 -1
  62. /package/dist/{chunk-EYXBYV2X.js.map → chunk-GPHWC533.js.map} +0 -0
package/dist/index.cjs CHANGED
@@ -22,29 +22,46 @@ var src_exports = {};
22
22
  __export(src_exports, {
23
23
  AggregateOperation: () => AggregateOperation,
24
24
  AggregateOperator: () => AggregateOperator,
25
+ Aggregates: () => Aggregates,
26
+ Alias: () => Alias,
27
+ Arithmetic: () => Arithmetic,
28
+ ArithmeticOperation: () => ArithmeticOperation,
29
+ ArithmeticOperator: () => ArithmeticOperator,
25
30
  Attribute: () => Attribute,
26
31
  BiTemporalColumns: () => BiTemporalColumns,
27
32
  BinaryOperation: () => BinaryOperation,
33
+ BooleanAggregates: () => BooleanAggregates,
28
34
  BooleanAttribute: () => BooleanAttribute,
29
35
  BooleanConstantOperation: () => BooleanConstantOperation,
30
36
  BusinessDateAndProcessingTemporalColumns: () => BusinessDateAndProcessingTemporalColumns,
37
+ CaseOperation: () => CaseOperation,
31
38
  Column: () => Column,
32
39
  ColumnWithJoin: () => ColumnWithJoin,
40
+ Comparable: () => Comparable,
33
41
  ComparisonOperation: () => ComparisonOperation,
34
42
  ComparisonOperator: () => ComparisonOperator,
43
+ ConcatOperation: () => ConcatOperation,
35
44
  ConstantOperation: () => ConstantOperation,
36
45
  CountAllOperation: () => CountAllOperation,
46
+ DATABRICKS: () => DATABRICKS,
47
+ DATE_ONLY_PATTERN: () => DATE_ONLY_PATTERN,
48
+ DEFAULT_TIMEOUT_MS: () => DEFAULT_TIMEOUT_MS,
49
+ DUCKDB: () => DUCKDB,
37
50
  DataCatalog: () => DataCatalog,
38
51
  DataStore: () => DataStore,
39
52
  Database: () => Database,
53
+ DatabricksConnect: () => DatabricksConnect,
54
+ DatabricksQueryError: () => DatabricksQueryError,
40
55
  DateArithmeticOperation: () => DateArithmeticOperation,
41
56
  DateAttribute: () => DateAttribute,
42
57
  DateConstantOperation: () => DateConstantOperation,
43
58
  DateDiffOperation: () => DateDiffOperation,
44
59
  DateExtractOperation: () => DateExtractOperation,
60
+ DateOnly: () => DateOnly,
45
61
  DatePart: () => DatePart,
46
62
  DateTimeAttribute: () => DateTimeAttribute,
47
63
  DateTimeConstantOperation: () => DateTimeConstantOperation,
64
+ DateTruncOperation: () => DateTruncOperation,
48
65
  DecimalAttribute: () => DecimalAttribute,
49
66
  DecimalConstantOperation: () => DecimalConstantOperation,
50
67
  DoubleAttribute: () => DoubleAttribute,
@@ -53,44 +70,70 @@ __export(src_exports, {
53
70
  FloatAttribute: () => FloatAttribute,
54
71
  FloatConstantOperation: () => FloatConstantOperation,
55
72
  ForeignKey: () => ForeignKey,
73
+ InOperation: () => InOperation,
56
74
  IntegerAttribute: () => IntegerAttribute,
57
75
  IntegerConstantOperation: () => IntegerConstantOperation,
58
76
  IsNotNullOperation: () => IsNotNullOperation,
59
77
  IsNullOperation: () => IsNullOperation,
60
78
  JoinOperation: () => JoinOperation,
61
79
  JoinTreeNodeOperation: () => JoinTreeNodeOperation,
80
+ LIKE_ESCAPE: () => LIKE_ESCAPE,
62
81
  LogicalOperation: () => LogicalOperation,
63
82
  LogicalOperator: () => LogicalOperator,
64
83
  MilestonedTable: () => MilestonedTable,
65
84
  MilestoningColumns: () => MilestoningColumns,
85
+ NUMERIC_FUNCTIONS: () => NUMERIC_FUNCTIONS,
66
86
  NoOperation: () => NoOperation,
67
87
  NotExistsOperation: () => NotExistsOperation,
88
+ NotInOperation: () => NotInOperation,
68
89
  NumericAttribute: () => NumericAttribute,
90
+ NumericConstantOperation: () => NumericConstantOperation,
91
+ NumericFunctionOperation: () => NumericFunctionOperation,
69
92
  Operation: () => Operation,
93
+ POSTGRES: () => POSTGRES,
70
94
  ProcessingTemporalColumns: () => ProcessingTemporalColumns,
95
+ QueryTimeoutError: () => QueryTimeoutError,
71
96
  RelatedFinder: () => RelatedFinder,
72
97
  RelationalOperationElement: () => RelationalOperationElement,
73
98
  ScalarFunction: () => ScalarFunction,
74
99
  ScalarFunctionOperation: () => ScalarFunctionOperation,
75
100
  Schema: () => Schema,
101
+ SelectOperation: () => SelectOperation,
76
102
  SingleBusinessDateColumn: () => SingleBusinessDateColumn,
77
103
  SortDirection: () => SortDirection,
78
104
  SortOperation: () => SortOperation,
79
105
  StringAttribute: () => StringAttribute,
80
106
  StringConstantOperation: () => StringConstantOperation,
107
+ StringFunctionOperation: () => StringFunctionOperation,
108
+ StringPredicates: () => StringPredicates,
81
109
  Table: () => Table,
82
110
  UnaryOperation: () => UnaryOperation,
83
111
  WindowFunction: () => WindowFunction,
84
112
  WindowFunctionOperation: () => WindowFunctionOperation,
85
113
  WindowSpecification: () => WindowSpecification,
114
+ applyMixins: () => applyMixins,
86
115
  clearRunner: () => clearRunner,
116
+ concat: () => concat,
117
+ constant: () => constant,
118
+ containsAggregate: () => containsAggregate,
119
+ containsAggregateOrWindow: () => containsAggregateOrWindow,
87
120
  convertInputsAndSelect: () => convertInputsAndSelect,
121
+ convertInputsAndSelectForDateRange: () => convertInputsAndSelectForDateRange,
122
+ countAll: () => countAll,
123
+ dialectOf: () => dialectOf,
124
+ embeddedUnder: () => embeddedUnder,
125
+ escapeLike: () => escapeLike,
88
126
  getRelatedFinderClass: () => getRelatedFinderClass,
89
127
  getRunner: () => getRunner,
128
+ operandOf: () => operandOf,
90
129
  registerRelatedFinderClass: () => registerRelatedFinderClass,
91
130
  registerRunner: () => registerRunner,
131
+ sample: () => sample,
92
132
  selectSqlToString: () => selectSqlToString,
93
- toSql: () => toSql
133
+ titleCase: () => titleCase,
134
+ toSql: () => toSql,
135
+ valueOperand: () => valueOperand,
136
+ when: () => when
94
137
  });
95
138
  module.exports = __toCommonJS(src_exports);
96
139
 
@@ -106,6 +149,10 @@ var ComparisonOperator = /* @__PURE__ */ ((ComparisonOperator2) => {
106
149
  ComparisonOperator2["NOT_LIKE"] = "NOT_LIKE";
107
150
  return ComparisonOperator2;
108
151
  })(ComparisonOperator || {});
152
+ var LIKE_ESCAPE = "!";
153
+ function escapeLike(value2) {
154
+ return value2.replaceAll(LIKE_ESCAPE, LIKE_ESCAPE + LIKE_ESCAPE).replaceAll("%", LIKE_ESCAPE + "%").replaceAll("_", LIKE_ESCAPE + "_");
155
+ }
109
156
  var LogicalOperator = /* @__PURE__ */ ((LogicalOperator2) => {
110
157
  LogicalOperator2["AND"] = "AND";
111
158
  LogicalOperator2["OR"] = "OR";
@@ -117,29 +164,38 @@ var AggregateOperator = /* @__PURE__ */ ((AggregateOperator2) => {
117
164
  AggregateOperator2["MIN"] = "MIN";
118
165
  AggregateOperator2["MAX"] = "MAX";
119
166
  AggregateOperator2["AVERAGE"] = "AVERAGE";
167
+ AggregateOperator2["STRING_AGG"] = "STRING_AGG";
168
+ AggregateOperator2["COUNT_DISTINCT"] = "COUNT_DISTINCT";
169
+ AggregateOperator2["STDDEV_SAMP"] = "STDDEV_SAMP";
170
+ AggregateOperator2["STDDEV_POP"] = "STDDEV_POP";
171
+ AggregateOperator2["VAR_SAMP"] = "VAR_SAMP";
172
+ AggregateOperator2["VAR_POP"] = "VAR_POP";
173
+ AggregateOperator2["BOOL_AND"] = "BOOL_AND";
174
+ AggregateOperator2["BOOL_OR"] = "BOOL_OR";
120
175
  return AggregateOperator2;
121
176
  })(AggregateOperator || {});
122
- var ScalarFunction = /* @__PURE__ */ ((ScalarFunction2) => {
123
- ScalarFunction2["ABS"] = "ABS";
124
- ScalarFunction2["CEILING"] = "CEILING";
125
- ScalarFunction2["FLOOR"] = "FLOOR";
126
- ScalarFunction2["MOD"] = "MOD";
127
- ScalarFunction2["POWER"] = "POWER";
128
- ScalarFunction2["SQRT"] = "SQRT";
129
- ScalarFunction2["ROUND"] = "ROUND";
130
- ScalarFunction2["UPPER"] = "UPPER";
131
- ScalarFunction2["LOWER"] = "LOWER";
132
- ScalarFunction2["TRIM"] = "TRIM";
133
- ScalarFunction2["LTRIM"] = "LTRIM";
134
- ScalarFunction2["RTRIM"] = "RTRIM";
135
- ScalarFunction2["LENGTH"] = "LENGTH";
136
- ScalarFunction2["REVERSE"] = "REVERSE";
137
- ScalarFunction2["LEFT"] = "LEFT";
138
- ScalarFunction2["RIGHT"] = "RIGHT";
139
- ScalarFunction2["REPEAT"] = "REPEAT";
140
- ScalarFunction2["REPLACE"] = "REPLACE";
141
- ScalarFunction2["SUBSTRING"] = "SUBSTRING";
142
- return ScalarFunction2;
177
+ var ScalarFunction = /* @__PURE__ */ ((ScalarFunction3) => {
178
+ ScalarFunction3["ABS"] = "ABS";
179
+ ScalarFunction3["CEILING"] = "CEILING";
180
+ ScalarFunction3["FLOOR"] = "FLOOR";
181
+ ScalarFunction3["MOD"] = "MOD";
182
+ ScalarFunction3["POWER"] = "POWER";
183
+ ScalarFunction3["SQRT"] = "SQRT";
184
+ ScalarFunction3["ROUND"] = "ROUND";
185
+ ScalarFunction3["UPPER"] = "UPPER";
186
+ ScalarFunction3["LOWER"] = "LOWER";
187
+ ScalarFunction3["TRIM"] = "TRIM";
188
+ ScalarFunction3["LTRIM"] = "LTRIM";
189
+ ScalarFunction3["RTRIM"] = "RTRIM";
190
+ ScalarFunction3["LENGTH"] = "LENGTH";
191
+ ScalarFunction3["REVERSE"] = "REVERSE";
192
+ ScalarFunction3["LEFT"] = "LEFT";
193
+ ScalarFunction3["RIGHT"] = "RIGHT";
194
+ ScalarFunction3["REPEAT"] = "REPEAT";
195
+ ScalarFunction3["REPLACE"] = "REPLACE";
196
+ ScalarFunction3["SUBSTRING"] = "SUBSTRING";
197
+ ScalarFunction3["COALESCE"] = "COALESCE";
198
+ return ScalarFunction3;
143
199
  })(ScalarFunction || {});
144
200
  var DatePart = /* @__PURE__ */ ((DatePart2) => {
145
201
  DatePart2["YEAR"] = "YEAR";
@@ -153,71 +209,103 @@ var DatePart = /* @__PURE__ */ ((DatePart2) => {
153
209
  DatePart2["DOW"] = "DOW";
154
210
  return DatePart2;
155
211
  })(DatePart || {});
156
- var SortDirection = /* @__PURE__ */ ((SortDirection2) => {
157
- SortDirection2["ASC"] = "ASC";
158
- SortDirection2["DESC"] = "DESC";
159
- return SortDirection2;
212
+ var SortDirection = /* @__PURE__ */ ((SortDirection3) => {
213
+ SortDirection3["ASC"] = "ASC";
214
+ SortDirection3["DESC"] = "DESC";
215
+ return SortDirection3;
160
216
  })(SortDirection || {});
161
217
  var RelationalOperationElement = class {
162
218
  };
163
219
  var Operation = class extends RelationalOperationElement {
164
- andOp(other) {
220
+ /** Name this column in the result: `f.price().sum().alias('Total')`, `f.age().gt(30).alias('Adult')`. */
221
+ alias(name) {
222
+ return new Alias(this, name);
223
+ }
224
+ and(other) {
165
225
  return new LogicalOperation(this, "AND" /* AND */, other);
166
226
  }
227
+ /** Combine with `other` using OR. A chain reads left to right: `a.or(b).and(c)` is `(a OR b) AND c`. */
228
+ or(other) {
229
+ return new LogicalOperation(this, "OR" /* OR */, other);
230
+ }
167
231
  };
168
232
  var NoOperation = class extends Operation {
169
- andOp(other) {
233
+ and(other) {
170
234
  return other;
171
235
  }
236
+ /** No filter matches every row, so OR-ing anything with it still matches every row. */
237
+ or(_other) {
238
+ return this;
239
+ }
172
240
  };
173
241
  var ConstantOperation = class extends RelationalOperationElement {
242
+ /** Name this column in the result. */
243
+ alias(name) {
244
+ return new Alias(this, name);
245
+ }
246
+ /** Sort by this expression, smallest first. */
247
+ ascending() {
248
+ return new SortOperation(this, "ASC" /* ASC */);
249
+ }
250
+ /** Sort by this expression, largest first. */
251
+ descending() {
252
+ return new SortOperation(this, "DESC" /* DESC */);
253
+ }
254
+ };
255
+ var NumericConstantOperation = class extends ConstantOperation {
256
+ arithmeticOperand() {
257
+ return this;
258
+ }
174
259
  };
175
- var IntegerConstantOperation = class extends ConstantOperation {
176
- constructor(value) {
260
+ var IntegerConstantOperation = class extends NumericConstantOperation {
261
+ constructor(value2) {
177
262
  super();
178
- this.value = value;
263
+ this.value = value2;
179
264
  }
180
265
  value;
181
266
  };
182
- var FloatConstantOperation = class extends ConstantOperation {
183
- constructor(value) {
267
+ var FloatConstantOperation = class extends NumericConstantOperation {
268
+ constructor(value2) {
184
269
  super();
185
- this.value = value;
270
+ this.value = value2;
186
271
  }
187
272
  value;
188
273
  };
189
274
  var StringConstantOperation = class extends ConstantOperation {
190
- constructor(value) {
275
+ constructor(value2) {
191
276
  super();
192
- this.value = value;
277
+ this.value = value2;
193
278
  }
194
279
  value;
195
280
  };
281
+ var DATE_ONLY_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
282
+ var DateOnly = class extends Date {
283
+ };
196
284
  var DateConstantOperation = class extends ConstantOperation {
197
- constructor(value) {
285
+ constructor(value2) {
198
286
  super();
199
- this.value = value;
287
+ this.value = value2;
200
288
  }
201
289
  value;
202
290
  };
203
291
  var DateTimeConstantOperation = class extends ConstantOperation {
204
- constructor(value) {
292
+ constructor(value2) {
205
293
  super();
206
- this.value = value;
294
+ this.value = value2;
207
295
  }
208
296
  value;
209
297
  };
210
- var DecimalConstantOperation = class extends ConstantOperation {
211
- constructor(value) {
298
+ var DecimalConstantOperation = class extends NumericConstantOperation {
299
+ constructor(value2) {
212
300
  super();
213
- this.value = value;
301
+ this.value = value2;
214
302
  }
215
303
  value;
216
304
  };
217
305
  var BooleanConstantOperation = class extends ConstantOperation {
218
- constructor(value) {
306
+ constructor(value2) {
219
307
  super();
220
- this.value = value;
308
+ this.value = value2;
221
309
  }
222
310
  value;
223
311
  };
@@ -241,6 +329,20 @@ var IsNullOperation = class extends UnaryOperation {
241
329
  };
242
330
  var IsNotNullOperation = class extends UnaryOperation {
243
331
  };
332
+ var InOperation = class extends UnaryOperation {
333
+ constructor(element, values) {
334
+ super(element);
335
+ this.values = values;
336
+ }
337
+ values;
338
+ };
339
+ var NotInOperation = class extends UnaryOperation {
340
+ constructor(element, values) {
341
+ super(element);
342
+ this.values = values;
343
+ }
344
+ values;
345
+ };
244
346
  var ComparisonOperation = class extends BinaryOperation {
245
347
  constructor(left, operator, right) {
246
348
  super(left, right);
@@ -264,21 +366,41 @@ var WindowSpecification = class {
264
366
  orderBy;
265
367
  };
266
368
  var AggregateOperation = class _AggregateOperation extends UnaryOperation {
267
- constructor(element, operator, displayName, window) {
369
+ constructor(element, operator, displayName, window, separator, orderBy = []) {
268
370
  super(element);
269
371
  this.operator = operator;
270
372
  this.displayName = displayName;
271
373
  this.window = window;
374
+ this.separator = separator;
375
+ this.orderBy = orderBy;
272
376
  }
273
377
  operator;
274
378
  displayName;
275
379
  window;
380
+ separator;
381
+ orderBy;
382
+ /** Sort by this expression, smallest first. */
383
+ ascending() {
384
+ return new SortOperation(this, "ASC" /* ASC */);
385
+ }
386
+ /** Sort by this expression, largest first. */
387
+ descending() {
388
+ return new SortOperation(this, "DESC" /* DESC */);
389
+ }
390
+ arithmeticOperand() {
391
+ return this;
392
+ }
393
+ comparisonOperand() {
394
+ return this;
395
+ }
276
396
  over(partitionBy, orderBy) {
277
397
  return new _AggregateOperation(
278
398
  this.element,
279
399
  this.operator,
280
400
  this.displayName,
281
- new WindowSpecification(partitionBy ?? [], orderBy ?? [])
401
+ new WindowSpecification(partitionBy ?? [], orderBy ?? []),
402
+ this.separator,
403
+ this.orderBy
282
404
  );
283
405
  }
284
406
  };
@@ -309,6 +431,14 @@ var WindowFunctionOperation = class _WindowFunctionOperation extends UnaryOperat
309
431
  secondArg;
310
432
  extraArgs;
311
433
  window;
434
+ /** Sort by this expression, smallest first. */
435
+ ascending() {
436
+ return new SortOperation(this, "ASC" /* ASC */);
437
+ }
438
+ /** Sort by this expression, largest first. */
439
+ descending() {
440
+ return new SortOperation(this, "DESC" /* DESC */);
441
+ }
312
442
  over(partitionBy, orderBy) {
313
443
  return new _WindowFunctionOperation(
314
444
  this.element,
@@ -332,7 +462,38 @@ var ScalarFunctionOperation = class extends UnaryOperation {
332
462
  displayName;
333
463
  secondArg;
334
464
  extraArgs;
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
+ /** This expression as an operand of a comparison. */
474
+ comparisonOperand() {
475
+ return this;
476
+ }
477
+ arithmeticOperand() {
478
+ return this;
479
+ }
480
+ /** Only a function that gives a number can be used in arithmetic. */
481
+ assertNumeric() {
482
+ if (!NUMERIC_FUNCTIONS.has(this.func)) {
483
+ throw new TypeError(`${this.func} does not give a number, so it cannot be used in arithmetic`);
484
+ }
485
+ }
335
486
  };
487
+ var NUMERIC_FUNCTIONS = /* @__PURE__ */ new Set([
488
+ "ABS" /* ABS */,
489
+ "CEILING" /* CEILING */,
490
+ "FLOOR" /* FLOOR */,
491
+ "MOD" /* MOD */,
492
+ "POWER" /* POWER */,
493
+ "SQRT" /* SQRT */,
494
+ "ROUND" /* ROUND */,
495
+ "LENGTH" /* LENGTH */
496
+ ]);
336
497
  var DateExtractOperation = class extends UnaryOperation {
337
498
  constructor(element, part, displayName) {
338
499
  super(element);
@@ -341,6 +502,34 @@ var DateExtractOperation = class extends UnaryOperation {
341
502
  }
342
503
  part;
343
504
  displayName;
505
+ /** Sort by this expression, smallest first. */
506
+ ascending() {
507
+ return new SortOperation(this, "ASC" /* ASC */);
508
+ }
509
+ /** Sort by this expression, largest first. */
510
+ descending() {
511
+ return new SortOperation(this, "DESC" /* DESC */);
512
+ }
513
+ };
514
+ var DateTruncOperation = class extends UnaryOperation {
515
+ constructor(element, part, displayName) {
516
+ super(element);
517
+ this.part = part;
518
+ this.displayName = displayName;
519
+ }
520
+ part;
521
+ displayName;
522
+ /** Sort by this expression, earliest first. */
523
+ ascending() {
524
+ return new SortOperation(this, "ASC" /* ASC */);
525
+ }
526
+ /** Sort by this expression, latest first. */
527
+ descending() {
528
+ return new SortOperation(this, "DESC" /* DESC */);
529
+ }
530
+ comparisonOperand() {
531
+ return this;
532
+ }
344
533
  };
345
534
  var DateArithmeticOperation = class extends UnaryOperation {
346
535
  constructor(element, n, unit, isAdd = true, displayName) {
@@ -354,6 +543,14 @@ var DateArithmeticOperation = class extends UnaryOperation {
354
543
  unit;
355
544
  isAdd;
356
545
  displayName;
546
+ /** Sort by this expression, smallest first. */
547
+ ascending() {
548
+ return new SortOperation(this, "ASC" /* ASC */);
549
+ }
550
+ /** Sort by this expression, largest first. */
551
+ descending() {
552
+ return new SortOperation(this, "DESC" /* DESC */);
553
+ }
357
554
  };
358
555
  var DateDiffOperation = class extends UnaryOperation {
359
556
  constructor(element, other, unit, displayName) {
@@ -365,20 +562,31 @@ var DateDiffOperation = class extends UnaryOperation {
365
562
  other;
366
563
  unit;
367
564
  displayName;
565
+ /** Sort by this expression, smallest first. */
566
+ ascending() {
567
+ return new SortOperation(this, "ASC" /* ASC */);
568
+ }
569
+ /** Sort by this expression, largest first. */
570
+ descending() {
571
+ return new SortOperation(this, "DESC" /* DESC */);
572
+ }
368
573
  };
369
574
  var Column = class extends RelationalOperationElement {
370
- constructor(name, type, owner, primaryKey = false) {
575
+ constructor(name, type, owner, primaryKey = false, nullable = true) {
371
576
  super();
372
577
  this.name = name;
373
578
  this.type = type;
374
579
  this.owner = owner;
375
580
  this.primaryKey = primaryKey;
581
+ this.nullable = nullable && !primaryKey;
376
582
  }
377
583
  name;
378
584
  type;
379
585
  owner;
380
586
  primaryKey;
381
587
  table;
588
+ /** Whether the column may hold NULL. As in Legend, a primary key is never null. */
589
+ nullable;
382
590
  };
383
591
  var ForeignKey = class {
384
592
  constructor(column, references) {
@@ -462,23 +670,46 @@ var JoinOperation = class {
462
670
  left;
463
671
  right;
464
672
  filter;
673
+ /** Property-equality filters on the join target (property id -> value), set by a related finder given a filter. */
674
+ filterKwargs = {};
675
+ /** Maps property ids to [column, type, table] for `filterKwargs`. */
676
+ filterColumnMap = {};
465
677
  };
466
678
  var JoinTreeNodeOperation = class {
467
- constructor(join, parent) {
679
+ constructor(join, parent, many = false, optional = false) {
468
680
  this.join = join;
469
681
  this.parent = parent;
682
+ this.many = many;
683
+ this.optional = optional;
470
684
  }
471
685
  join;
472
686
  parent;
687
+ many;
688
+ optional;
473
689
  };
474
690
  var ColumnWithJoin = class extends RelationalOperationElement {
475
- constructor(column, parent) {
691
+ /**
692
+ * `embedded` is an alternate reference to a flat (denormalized) column on the owning table that
693
+ * holds the same value without the join; the SQL generator uses it when the join can be elided.
694
+ */
695
+ constructor(column, parent, embedded, _optional = false) {
476
696
  super();
477
697
  this.column = column;
478
698
  this.parent = parent;
699
+ this.embedded = embedded;
700
+ this._optional = _optional;
479
701
  }
480
702
  column;
481
703
  parent;
704
+ embedded;
705
+ _optional;
706
+ /** Whether the value can be missing: its property is optional, or a navigation on the way to it is. */
707
+ get optional() {
708
+ for (let node = this.parent; node !== void 0; node = node.parent) {
709
+ if (node.optional) return true;
710
+ }
711
+ return this._optional;
712
+ }
482
713
  };
483
714
  var SortOperation = class {
484
715
  constructor(column, direction) {
@@ -494,19 +725,373 @@ var CountAllOperation = class extends RelationalOperationElement {
494
725
  this.table = table;
495
726
  }
496
727
  table;
728
+ /** Name this column in the result. */
729
+ alias(name) {
730
+ return new Alias(this, name);
731
+ }
732
+ /** Sort by this expression, smallest first. */
733
+ ascending() {
734
+ return new SortOperation(this, "ASC" /* ASC */);
735
+ }
736
+ /** Sort by this expression, largest first. */
737
+ descending() {
738
+ return new SortOperation(this, "DESC" /* DESC */);
739
+ }
740
+ comparisonOperand() {
741
+ return this;
742
+ }
743
+ };
744
+ var Alias = class _Alias extends RelationalOperationElement {
745
+ constructor(element, name) {
746
+ super();
747
+ this.element = element;
748
+ this.name = name;
749
+ }
750
+ element;
751
+ name;
752
+ alias(name) {
753
+ return new _Alias(this.element, name);
754
+ }
755
+ /** Sorts by the column the alias names. */
756
+ ascending() {
757
+ return new SortOperation(this.element, "ASC" /* ASC */);
758
+ }
759
+ /** Sorts by the column the alias names. */
760
+ descending() {
761
+ return new SortOperation(this.element, "DESC" /* DESC */);
762
+ }
763
+ };
764
+ var CaseOperation = class extends Operation {
765
+ constructor(condition, then, fallback, displayName) {
766
+ super();
767
+ this.condition = condition;
768
+ this.then = then;
769
+ this.fallback = fallback;
770
+ this.displayName = displayName;
771
+ }
772
+ condition;
773
+ then;
774
+ fallback;
775
+ displayName;
776
+ };
777
+ var ExistsOperation = class extends RelationalOperationElement {
778
+ constructor(node) {
779
+ super();
780
+ this.node = node;
781
+ }
782
+ node;
783
+ /** Name this column in the result. */
784
+ alias(name) {
785
+ return new Alias(this, name);
786
+ }
787
+ and(other) {
788
+ return new LogicalOperation(this, "AND" /* AND */, other);
789
+ }
790
+ or(other) {
791
+ return new LogicalOperation(this, "OR" /* OR */, other);
792
+ }
793
+ };
794
+ var NotExistsOperation = class extends RelationalOperationElement {
795
+ constructor(node) {
796
+ super();
797
+ this.node = node;
798
+ }
799
+ node;
800
+ /** Name this column in the result. */
801
+ alias(name) {
802
+ return new Alias(this, name);
803
+ }
804
+ and(other) {
805
+ return new LogicalOperation(this, "AND" /* AND */, other);
806
+ }
807
+ or(other) {
808
+ return new LogicalOperation(this, "OR" /* OR */, other);
809
+ }
810
+ };
811
+ var SelectOperation = class {
812
+ constructor(display, filter, orderBy = [], groupBy = [], limit, table, joinFilters = /* @__PURE__ */ new Map(), distinct = false, offset, having) {
813
+ this.display = display;
814
+ this.filter = filter;
815
+ this.orderBy = orderBy;
816
+ this.groupBy = groupBy;
817
+ this.limit = limit;
818
+ this.table = table;
819
+ this.joinFilters = joinFilters;
820
+ this.distinct = distinct;
821
+ this.offset = offset;
822
+ this.having = having;
823
+ }
824
+ display;
825
+ filter;
826
+ orderBy;
827
+ groupBy;
828
+ limit;
829
+ table;
830
+ joinFilters;
831
+ distinct;
832
+ offset;
833
+ having;
834
+ };
835
+ function containsAggregateOrWindow(op) {
836
+ if (op instanceof AggregateOperation || op instanceof WindowFunctionOperation || op instanceof CountAllOperation) return true;
837
+ if (op instanceof BinaryOperation) return containsAggregateOrWindow(op.left) || containsAggregateOrWindow(op.right);
838
+ if (op instanceof UnaryOperation || op instanceof Alias) return containsAggregateOrWindow(op.element);
839
+ return false;
840
+ }
841
+ function containsAggregate(op) {
842
+ if (op instanceof AggregateOperation || op instanceof CountAllOperation) return true;
843
+ if (op instanceof BinaryOperation) return containsAggregate(op.left) || containsAggregate(op.right);
844
+ if (op instanceof UnaryOperation || op instanceof Alias) return containsAggregate(op.element);
845
+ return false;
846
+ }
847
+ function valueOperand(value2) {
848
+ if (typeof value2 === "boolean") return new BooleanConstantOperation(value2);
849
+ if (typeof value2 === "number" && Number.isFinite(value2)) {
850
+ return Number.isInteger(value2) ? new IntegerConstantOperation(value2) : new FloatConstantOperation(value2);
851
+ }
852
+ if (typeof value2 === "string") return new StringConstantOperation(value2);
853
+ if (value2 instanceof DateOnly) return new DateConstantOperation(value2);
854
+ if (value2 instanceof Date) return new DateTimeConstantOperation(value2);
855
+ throw new TypeError(`A value to compare with is a boolean, a finite number, a string or a date, not ${value2 === null ? "null" : typeof value2}`);
856
+ }
857
+ function operandOf(other) {
858
+ if (other instanceof RelationalOperationElement) return other;
859
+ if (typeof other?.cwj === "function") return other.cwj();
860
+ return valueOperand(other);
861
+ }
862
+ var Comparable = class {
863
+ _compared(operator, other) {
864
+ return new ComparisonOperation(this.comparisonOperand(), operator, operandOf(other));
865
+ }
866
+ /** Filter to rows where this expression equals `other`. */
867
+ eq(other) {
868
+ return this._compared("EQUAL" /* EQUAL */, other);
869
+ }
870
+ /** Filter to rows where this expression does not equal `other`. */
871
+ ne(other) {
872
+ return this._compared("NOT_EQUAL" /* NOT_EQUAL */, other);
873
+ }
874
+ /** Filter to rows where this expression is less than `other`. */
875
+ lt(other) {
876
+ return this._compared("LESS_THAN" /* LESS_THAN */, other);
877
+ }
878
+ /** Filter to rows where this expression is at most `other`. */
879
+ lte(other) {
880
+ return this._compared("LESS_THAN_OR_EQUAL_TO" /* LESS_THAN_OR_EQUAL_TO */, other);
881
+ }
882
+ /** Filter to rows where this expression is greater than `other`. */
883
+ gt(other) {
884
+ return this._compared("GREATER_THAN" /* GREATER_THAN */, other);
885
+ }
886
+ /** Filter to rows where this expression is at least `other`. */
887
+ gte(other) {
888
+ return this._compared("GREATER_THAN_OR_EQUAL_TO" /* GREATER_THAN_OR_EQUAL_TO */, other);
889
+ }
890
+ };
891
+ function sample(ddof) {
892
+ if (ddof !== 0 && ddof !== 1) throw new RangeError(`ddof is 0 (population) or 1 (sample), not ${ddof}`);
893
+ return ddof === 1;
894
+ }
895
+ var StringPredicates = class {
896
+ _like(pattern) {
897
+ return new ComparisonOperation(this.comparisonOperand(), "LIKE" /* LIKE */, new StringConstantOperation(pattern));
898
+ }
899
+ /** Filter to rows where this text contains `value`. */
900
+ contains(value2) {
901
+ return this._like(`%${escapeLike(value2)}%`);
902
+ }
903
+ /** Filter to rows where this text starts with `prefix`. */
904
+ startsWith(prefix) {
905
+ return this._like(`${escapeLike(prefix)}%`);
906
+ }
907
+ /** Filter to rows where this text ends with `suffix`. */
908
+ endsWith(suffix) {
909
+ return this._like(`%${escapeLike(suffix)}`);
910
+ }
911
+ };
912
+ var Aggregates = class {
913
+ _aggregate(operator, label) {
914
+ return new AggregateOperation(this, operator, label + " Expression");
915
+ }
916
+ /** Sum of the values in the group. */
917
+ sum() {
918
+ return this._aggregate("SUM" /* SUM */, "Sum");
919
+ }
920
+ /** Smallest value in the group. */
921
+ min() {
922
+ return this._aggregate("MIN" /* MIN */, "Min");
923
+ }
924
+ /** Largest value in the group. */
925
+ max() {
926
+ return this._aggregate("MAX" /* MAX */, "Max");
927
+ }
928
+ /** Arithmetic mean of the values in the group. */
929
+ average() {
930
+ return this._aggregate("AVERAGE" /* AVERAGE */, "Average");
931
+ }
932
+ /** Number of non-null values in the group. */
933
+ count() {
934
+ return this._aggregate("COUNT" /* COUNT */, "Count");
935
+ }
936
+ /** Standard deviation of the values in the group: the sample one by default, `ddof = 0` for the population one. */
937
+ std(ddof = 1) {
938
+ return this._aggregate(sample(ddof) ? "STDDEV_SAMP" /* STDDEV_SAMP */ : "STDDEV_POP" /* STDDEV_POP */, "Std");
939
+ }
940
+ /** Variance of the values in the group: the sample one by default, `ddof = 0` for the population one. */
941
+ var(ddof = 1) {
942
+ return this._aggregate(sample(ddof) ? "VAR_SAMP" /* VAR_SAMP */ : "VAR_POP" /* VAR_POP */, "Var");
943
+ }
944
+ };
945
+ var BooleanAggregates = class {
946
+ /** True for a group where every row satisfies the condition. */
947
+ all() {
948
+ return new AggregateOperation(this, "BOOL_AND" /* BOOL_AND */, "All Expression");
949
+ }
950
+ /** True for a group where at least one row satisfies the condition. */
951
+ any() {
952
+ return new AggregateOperation(this, "BOOL_OR" /* BOOL_OR */, "Any Expression");
953
+ }
954
+ };
955
+ function applyMixins(derived, bases) {
956
+ for (const base of bases) {
957
+ for (const name of Object.getOwnPropertyNames(base.prototype)) {
958
+ if (name === "constructor" || Object.prototype.hasOwnProperty.call(derived.prototype, name)) continue;
959
+ Object.defineProperty(derived.prototype, name, Object.getOwnPropertyDescriptor(base.prototype, name));
960
+ }
961
+ }
962
+ }
963
+ var ArithmeticOperator = /* @__PURE__ */ ((ArithmeticOperator2) => {
964
+ ArithmeticOperator2["ADD"] = "+";
965
+ ArithmeticOperator2["SUBTRACT"] = "-";
966
+ ArithmeticOperator2["MULTIPLY"] = "*";
967
+ ArithmeticOperator2["DIVIDE"] = "/";
968
+ return ArithmeticOperator2;
969
+ })(ArithmeticOperator || {});
970
+ var Arithmetic = class {
971
+ /** Raises if this expression is not a number. */
972
+ assertNumeric() {
973
+ }
974
+ _arithmetic(operator, other) {
975
+ this.assertNumeric();
976
+ let operand;
977
+ if (typeof other === "number") {
978
+ if (!Number.isFinite(other)) throw new TypeError("Arithmetic is between numbers and numeric columns, not a non-finite number");
979
+ operand = valueOperand(other);
980
+ } else if (other !== null && typeof other === "object" && typeof other.arithmeticOperand === "function") {
981
+ other.assertNumeric?.();
982
+ operand = other.arithmeticOperand();
983
+ } else {
984
+ throw new TypeError(`Arithmetic is between numbers and numeric columns, not ${other === null ? "null" : typeof other}`);
985
+ }
986
+ return new ArithmeticOperation(this.arithmeticOperand(), operator, operand);
987
+ }
988
+ /** `this + other`. */
989
+ plus(other) {
990
+ return this._arithmetic("+" /* ADD */, other);
991
+ }
992
+ /** `this - other`. */
993
+ minus(other) {
994
+ return this._arithmetic("-" /* SUBTRACT */, other);
995
+ }
996
+ /** `this * other`. */
997
+ times(other) {
998
+ return this._arithmetic("*" /* MULTIPLY */, other);
999
+ }
1000
+ /** `this / other`. */
1001
+ dividedBy(other) {
1002
+ return this._arithmetic("/" /* DIVIDE */, other);
1003
+ }
497
1004
  };
1005
+ var ArithmeticOperation = class extends BinaryOperation {
1006
+ constructor(left, operator, right, displayName) {
1007
+ super(left, right);
1008
+ this.operator = operator;
1009
+ this.displayName = displayName;
1010
+ }
1011
+ operator;
1012
+ displayName;
1013
+ /** Sort by this expression, smallest first. */
1014
+ ascending() {
1015
+ return new SortOperation(this, "ASC" /* ASC */);
1016
+ }
1017
+ /** Sort by this expression, largest first. */
1018
+ descending() {
1019
+ return new SortOperation(this, "DESC" /* DESC */);
1020
+ }
1021
+ arithmeticOperand() {
1022
+ return this;
1023
+ }
1024
+ comparisonOperand() {
1025
+ return this;
1026
+ }
1027
+ };
1028
+ var ConcatOperation = class extends Operation {
1029
+ constructor(parts, displayName) {
1030
+ super();
1031
+ this.parts = parts;
1032
+ this.displayName = displayName;
1033
+ }
1034
+ parts;
1035
+ displayName;
1036
+ /** Join more text on the end: `f.first().concat(' ', f.last())`. A concatenation joins into one. */
1037
+ concat(...more) {
1038
+ return concat(this, ...more);
1039
+ }
1040
+ /** Sort by this expression, smallest first. */
1041
+ ascending() {
1042
+ return new SortOperation(this, "ASC" /* ASC */);
1043
+ }
1044
+ /** Sort by this expression, largest first. */
1045
+ descending() {
1046
+ return new SortOperation(this, "DESC" /* DESC */);
1047
+ }
1048
+ comparisonOperand() {
1049
+ return this;
1050
+ }
1051
+ };
1052
+ function concat(...parts) {
1053
+ const partsOf = (part) => {
1054
+ if (part instanceof ConcatOperation) return part.parts;
1055
+ if (typeof part === "string") return [new StringConstantOperation(part)];
1056
+ if (part instanceof RelationalOperationElement) return [part];
1057
+ if (part !== null && typeof part === "object" && typeof part.functionInput === "function") return [part.functionInput()];
1058
+ throw new TypeError(`Text is joined with text, not ${part === null ? "null" : typeof part}`);
1059
+ };
1060
+ return new ConcatOperation(parts.flatMap(partsOf));
1061
+ }
1062
+ applyMixins(ConcatOperation, [Comparable, StringPredicates]);
1063
+ applyMixins(DateTruncOperation, [Comparable]);
1064
+ applyMixins(ArithmeticOperation, [Arithmetic, Comparable]);
1065
+ applyMixins(ScalarFunctionOperation, [Comparable, Arithmetic, StringPredicates]);
1066
+ applyMixins(AggregateOperation, [Arithmetic, Comparable, StringPredicates]);
1067
+ applyMixins(CountAllOperation, [Comparable]);
1068
+ applyMixins(NumericConstantOperation, [Arithmetic]);
1069
+ applyMixins(ConstantOperation, [Aggregates]);
1070
+ applyMixins(ArithmeticOperation, [Aggregates]);
1071
+ applyMixins(ScalarFunctionOperation, [Aggregates]);
1072
+ applyMixins(CaseOperation, [Aggregates]);
1073
+ applyMixins(IsNullOperation, [BooleanAggregates]);
1074
+ applyMixins(IsNotNullOperation, [BooleanAggregates]);
1075
+ applyMixins(InOperation, [BooleanAggregates]);
1076
+ applyMixins(NotInOperation, [BooleanAggregates]);
1077
+ applyMixins(ComparisonOperation, [BooleanAggregates]);
1078
+ applyMixins(LogicalOperation, [BooleanAggregates]);
498
1079
 
499
1080
  // src/datafinder/attribute.ts
500
- var Attribute = class {
1081
+ var Attribute = class _Attribute {
501
1082
  _displayName;
502
1083
  _column;
503
1084
  _owner;
504
1085
  _parent;
505
- constructor(displayName, columnName, columnDbType, owner, parent) {
1086
+ _embedded;
1087
+ _optional;
1088
+ constructor(displayName, columnName, columnDbType, owner, parent, embedded, nullable = true, optional = false) {
1089
+ this._optional = optional;
506
1090
  this._displayName = displayName;
507
- this._column = new Column(columnName, columnDbType, owner);
1091
+ this._column = new Column(columnName, columnDbType, owner, false, nullable);
508
1092
  this._owner = owner;
509
1093
  this._parent = parent;
1094
+ this._embedded = embedded;
510
1095
  }
511
1096
  column() {
512
1097
  return this._column;
@@ -520,33 +1105,102 @@ var Attribute = class {
520
1105
  displayName() {
521
1106
  return this._displayName;
522
1107
  }
1108
+ /** Alternate attribute on a flat column of the owning table, used in place of this one when its join is elided. */
1109
+ embedded() {
1110
+ return this._embedded;
1111
+ }
1112
+ /** @internal The expression a function of this attribute applies to: its column. */
1113
+ functionInput() {
1114
+ return this.cwj();
1115
+ }
1116
+ /** @internal What a function of this attribute is labelled after: its display name. */
1117
+ functionLabel() {
1118
+ return this._displayName;
1119
+ }
1120
+ /** @internal This attribute as an operand of arithmetic: its column. */
1121
+ arithmeticOperand() {
1122
+ return this.cwj();
1123
+ }
523
1124
  cwj() {
524
- return new ColumnWithJoin(this._column, this._parent);
1125
+ return new ColumnWithJoin(this._column, this._parent, this._embedded?.cwj(), this._optional);
525
1126
  }
1127
+ /** Count the number of non-null values. Use in a projection with `groupBy` to aggregate per group. */
526
1128
  count() {
527
1129
  return new AggregateOperation(
528
- new ColumnWithJoin(this._column, this._parent),
1130
+ this.cwj(),
529
1131
  "COUNT" /* COUNT */,
530
1132
  this._displayName + " Count"
531
1133
  );
532
1134
  }
1135
+ /** Name this column in the result: `f.price().sum().alias('Total')`. */
1136
+ alias(name) {
1137
+ return new Alias(this, name);
1138
+ }
1139
+ /** The number of different non-NULL values in the group: `COUNT(DISTINCT column)`. Use with `groupBy` to count per group. */
1140
+ countDistinct() {
1141
+ return new AggregateOperation(this.cwj(), "COUNT_DISTINCT" /* COUNT_DISTINCT */, this._displayName + " Count Distinct");
1142
+ }
1143
+ /** The value, or `defaultValue` when it is NULL: `name().orElse('N/A')`. Like `COALESCE` in SQL. */
1144
+ orElse(defaultValue) {
1145
+ if (!["string", "number", "boolean"].includes(typeof defaultValue)) {
1146
+ throw new TypeError(`An orElse default is a string, number or boolean, not ${defaultValue === null ? "null" : typeof defaultValue}`);
1147
+ }
1148
+ return new ScalarFunctionOperation(this.cwj(), "COALESCE" /* COALESCE */, "Or Else " + this._displayName, void 0, [defaultValue]);
1149
+ }
1150
+ /** Sort this attribute in ascending order. Pass to `orderBy` or a window function's `orderBy`. */
533
1151
  ascending() {
534
- return new SortOperation(new ColumnWithJoin(this._column, this._parent), "ASC" /* ASC */);
1152
+ return new SortOperation(this.cwj(), "ASC" /* ASC */);
535
1153
  }
1154
+ /** Sort this attribute in descending order. Pass to `orderBy` or a window function's `orderBy`. */
536
1155
  descending() {
537
- return new SortOperation(new ColumnWithJoin(this._column, this._parent), "DESC" /* DESC */);
1156
+ return new SortOperation(this.cwj(), "DESC" /* DESC */);
538
1157
  }
1158
+ /** Filter to rows where this attribute has no value. */
539
1159
  isNull() {
540
- return new IsNullOperation(new ColumnWithJoin(this._column, this._parent));
1160
+ return new IsNullOperation(this.cwj());
541
1161
  }
1162
+ /** Filter to rows where this attribute has a value. */
542
1163
  isNotNull() {
543
- return new IsNotNullOperation(new ColumnWithJoin(this._column, this._parent));
1164
+ return new IsNotNullOperation(this.cwj());
1165
+ }
1166
+ /** Wrap a value as a constant of this attribute's type. Overridden by the typed attributes. */
1167
+ toConstant(_value) {
1168
+ throw new TypeError(`${this.constructor.name} does not support isIn / isNotIn`);
1169
+ }
1170
+ _constants(values) {
1171
+ const constants = values.map((v) => this.toConstant(v));
1172
+ if (constants.length === 0) throw new Error("isIn / isNotIn need at least one value");
1173
+ return constants;
1174
+ }
1175
+ /** Filter to rows where this attribute equals any of `values`. */
1176
+ isIn(values) {
1177
+ return new InOperation(this.cwj(), this._constants(values));
1178
+ }
1179
+ /** Filter to rows where this attribute equals none of `values`. Rows with no value are excluded, as in SQL. */
1180
+ isNotIn(values) {
1181
+ return new NotInOperation(this.cwj(), this._constants(values));
544
1182
  }
545
1183
  _windowSpec(partitionBy, orderBy) {
546
- const pb = partitionBy === void 0 ? [] : Array.isArray(partitionBy) ? partitionBy : [partitionBy];
1184
+ const pbArr = partitionBy === void 0 ? [] : Array.isArray(partitionBy) ? partitionBy : [partitionBy];
1185
+ const pb = pbArr.map((x) => x instanceof _Attribute ? x.cwj() : x);
547
1186
  const ob = orderBy === void 0 ? [] : Array.isArray(orderBy) ? orderBy : [orderBy];
548
1187
  return new WindowSpecification(pb, ob);
549
1188
  }
1189
+ /**
1190
+ * Assign a rank to each row within the ordered window, following pandas `Series.rank` conventions.
1191
+ *
1192
+ * `method` controls tie-breaking (default `'min'`):
1193
+ * - `'min'` — tied rows share the lowest rank; the next rank skips.
1194
+ * - `'dense'` — like `'min'` but ranks are contiguous (no gaps).
1195
+ * - `'first'` — each row gets a unique sequential integer (no ties).
1196
+ * - `'max'` — only meaningful with `pct: true` (see below).
1197
+ *
1198
+ * When `pct: true` the result is a relative rank between 0 and 1:
1199
+ * - `method: 'min'` (default) → fraction of values strictly below this row (PERCENT_RANK).
1200
+ * - `method: 'max'` → fraction of values at or below this row (cumulative distribution).
1201
+ *
1202
+ * `partitionBy` restarts the ranking within each group of distinct values.
1203
+ */
550
1204
  rank(opts = {}) {
551
1205
  const { method = "min", pct = false, partitionBy, orderBy } = opts;
552
1206
  let func;
@@ -571,6 +1225,11 @@ var Attribute = class {
571
1225
  }
572
1226
  return new WindowFunctionOperation(null, func, name, void 0, [], this._windowSpec(partitionBy, orderBy));
573
1227
  }
1228
+ /**
1229
+ * Split rows into `buckets` equal-sized quantile bins ordered by `orderBy`.
1230
+ * Each row receives a bucket number from 1 (lowest) to `buckets` (highest).
1231
+ * Named after `pandas.qcut`.
1232
+ */
574
1233
  qcut(buckets, opts = {}) {
575
1234
  return new WindowFunctionOperation(
576
1235
  null,
@@ -581,12 +1240,17 @@ var Attribute = class {
581
1240
  this._windowSpec(opts.partitionBy, opts.orderBy)
582
1241
  );
583
1242
  }
1243
+ /**
1244
+ * Return the value of this attribute from `periods` rows earlier (positive) or later (negative)
1245
+ * within the ordered window, following `pandas.Series.shift`.
1246
+ * Rows with no neighbour at the given offset return `null` (or `fillValue` if provided).
1247
+ */
584
1248
  shift(periods = 1, opts = {}) {
585
1249
  const extraArgs = opts.fillValue !== void 0 ? [opts.fillValue] : [];
586
1250
  const func = periods >= 0 ? "LAG" /* LAG */ : "LEAD" /* LEAD */;
587
1251
  const label = periods >= 0 ? "Lag" : "Lead";
588
1252
  return new WindowFunctionOperation(
589
- new ColumnWithJoin(this._column, this._parent),
1253
+ this.cwj(),
590
1254
  func,
591
1255
  label + " " + this._displayName,
592
1256
  Math.abs(periods),
@@ -594,9 +1258,13 @@ var Attribute = class {
594
1258
  this._windowSpec(opts.partitionBy, opts.orderBy)
595
1259
  );
596
1260
  }
1261
+ /**
1262
+ * Return the first value of this attribute in the window frame.
1263
+ * Use `partitionBy` to restart the frame for each group.
1264
+ */
597
1265
  first(opts = {}) {
598
1266
  return new WindowFunctionOperation(
599
- new ColumnWithJoin(this._column, this._parent),
1267
+ this.cwj(),
600
1268
  "FIRST_VALUE" /* FIRST_VALUE */,
601
1269
  "First " + this._displayName,
602
1270
  void 0,
@@ -604,9 +1272,13 @@ var Attribute = class {
604
1272
  this._windowSpec(opts.partitionBy, opts.orderBy)
605
1273
  );
606
1274
  }
1275
+ /**
1276
+ * Return the last value of this attribute in the window frame.
1277
+ * Use `partitionBy` to restart the frame for each group.
1278
+ */
607
1279
  last(opts = {}) {
608
1280
  return new WindowFunctionOperation(
609
- new ColumnWithJoin(this._column, this._parent),
1281
+ this.cwj(),
610
1282
  "LAST_VALUE" /* LAST_VALUE */,
611
1283
  "Last " + this._displayName,
612
1284
  void 0,
@@ -625,367 +1297,669 @@ function getRelatedFinderClass(name) {
625
1297
  return _registry.get(name);
626
1298
  }
627
1299
 
628
- // src/datafinder/typed-attributes.ts
629
- var StringAttribute = class extends Attribute {
630
- constructor(displayName, columnName, columnDbType, owner, parent) {
631
- super(displayName, columnName, columnDbType, owner, parent);
1300
+ // src/datafinder/scalar-functions.ts
1301
+ var StringFunctionOperation = class extends ScalarFunctionOperation {
1302
+ functionInput() {
1303
+ return this;
1304
+ }
1305
+ functionLabel() {
1306
+ return labelOf(this);
1307
+ }
1308
+ };
1309
+ var NumericFunctionOperation = class extends ScalarFunctionOperation {
1310
+ functionInput() {
1311
+ return this;
1312
+ }
1313
+ functionLabel() {
1314
+ return labelOf(this);
632
1315
  }
633
- upper() {
634
- return new ScalarFunctionOperation(this.cwj(), "UPPER" /* UPPER */, "Upper " + this.displayName());
1316
+ };
1317
+ function labelOf(operation) {
1318
+ return operation.displayName ?? operation.func.charAt(0) + operation.func.slice(1).toLowerCase();
1319
+ }
1320
+ function stringFunction(element, func, label, secondArg, extraArgs = []) {
1321
+ return new StringFunctionOperation(element, func, label, secondArg, extraArgs);
1322
+ }
1323
+ function numberFunction(element, func, label, secondArg) {
1324
+ return new NumericFunctionOperation(element, func, label, secondArg);
1325
+ }
1326
+ var StringFunctions = class {
1327
+ /** Join text on the end: `f.first().concat(' ', f.last())`. A NULL part makes the whole NULL. */
1328
+ concat(...more) {
1329
+ return concat(this, ...more);
635
1330
  }
636
- lower() {
637
- return new ScalarFunctionOperation(this.cwj(), "LOWER" /* LOWER */, "Lower " + this.displayName());
1331
+ /** Convert every character to uppercase. */
1332
+ toUpperCase() {
1333
+ return stringFunction(this.functionInput(), "UPPER" /* UPPER */, "Upper " + this.functionLabel());
638
1334
  }
639
- strip() {
640
- return new ScalarFunctionOperation(this.cwj(), "TRIM" /* TRIM */, "Strip " + this.displayName());
1335
+ /** Convert every character to lowercase. */
1336
+ toLowerCase() {
1337
+ return stringFunction(this.functionInput(), "LOWER" /* LOWER */, "Lower " + this.functionLabel());
641
1338
  }
642
- lstrip() {
643
- return new ScalarFunctionOperation(this.cwj(), "LTRIM" /* LTRIM */, "LStrip " + this.displayName());
1339
+ /** Remove leading and trailing whitespace. */
1340
+ trim() {
1341
+ return stringFunction(this.functionInput(), "TRIM" /* TRIM */, "Trim " + this.functionLabel());
644
1342
  }
645
- rstrip() {
646
- return new ScalarFunctionOperation(this.cwj(), "RTRIM" /* RTRIM */, "RStrip " + this.displayName());
1343
+ /** Remove leading whitespace. */
1344
+ trimStart() {
1345
+ return stringFunction(this.functionInput(), "LTRIM" /* LTRIM */, "Trim Start " + this.functionLabel());
647
1346
  }
1347
+ /** Remove trailing whitespace. */
1348
+ trimEnd() {
1349
+ return stringFunction(this.functionInput(), "RTRIM" /* RTRIM */, "Trim End " + this.functionLabel());
1350
+ }
1351
+ /** Return the number of characters in the string. */
648
1352
  length() {
649
- return new ScalarFunctionOperation(this.cwj(), "LENGTH" /* LENGTH */, "Length " + this.displayName());
1353
+ return numberFunction(this.functionInput(), "LENGTH" /* LENGTH */, "Length " + this.functionLabel());
650
1354
  }
1355
+ /** Return the string with characters in reverse order. */
651
1356
  reverse() {
652
- return new ScalarFunctionOperation(this.cwj(), "REVERSE" /* REVERSE */, "Reverse " + this.displayName());
1357
+ return stringFunction(this.functionInput(), "REVERSE" /* REVERSE */, "Reverse " + this.functionLabel());
653
1358
  }
1359
+ /** Return the first `n` characters. */
654
1360
  left(n) {
655
- return new ScalarFunctionOperation(this.cwj(), "LEFT" /* LEFT */, "Left " + this.displayName(), n);
1361
+ return stringFunction(this.functionInput(), "LEFT" /* LEFT */, "Left " + this.functionLabel(), n);
656
1362
  }
1363
+ /** Return the last `n` characters. */
657
1364
  right(n) {
658
- return new ScalarFunctionOperation(this.cwj(), "RIGHT" /* RIGHT */, "Right " + this.displayName(), n);
1365
+ return stringFunction(this.functionInput(), "RIGHT" /* RIGHT */, "Right " + this.functionLabel(), n);
659
1366
  }
1367
+ /** Return the string repeated `n` times. */
660
1368
  repeat(n) {
661
- return new ScalarFunctionOperation(this.cwj(), "REPEAT" /* REPEAT */, "Repeat " + this.displayName(), n);
1369
+ return stringFunction(this.functionInput(), "REPEAT" /* REPEAT */, "Repeat " + this.functionLabel(), n);
662
1370
  }
1371
+ /** Replace all occurrences of `fromStr` with `toStr`. */
663
1372
  replace(fromStr, toStr) {
664
- return new ScalarFunctionOperation(this.cwj(), "REPLACE" /* REPLACE */, "Replace " + this.displayName(), void 0, [fromStr, toStr]);
1373
+ return stringFunction(this.functionInput(), "REPLACE" /* REPLACE */, "Replace " + this.functionLabel(), void 0, [fromStr, toStr]);
665
1374
  }
1375
+ /**
1376
+ * Extract a portion of the string.
1377
+ * `start` is zero-based (following Python convention); `length` is optional.
1378
+ */
666
1379
  substring(start, length) {
667
1380
  const args = length !== void 0 ? [start + 1, length] : [start + 1];
668
- return new ScalarFunctionOperation(this.cwj(), "SUBSTRING" /* SUBSTRING */, "Substring " + this.displayName(), void 0, args);
669
- }
670
- eq(value) {
671
- return new ComparisonOperation(this.cwj(), "EQUAL" /* EQUAL */, new StringConstantOperation(value));
1381
+ return stringFunction(this.functionInput(), "SUBSTRING" /* SUBSTRING */, "Substring " + this.functionLabel(), void 0, args);
672
1382
  }
673
- ne(value) {
674
- return new ComparisonOperation(this.cwj(), "NOT_EQUAL" /* NOT_EQUAL */, new StringConstantOperation(value));
1383
+ };
1384
+ var NumericFunctions = class {
1385
+ /** Absolute value (distance from zero, always non-negative). */
1386
+ abs() {
1387
+ return numberFunction(this.functionInput(), "ABS" /* ABS */, "Abs " + this.functionLabel());
675
1388
  }
676
- contains(value) {
677
- return new ComparisonOperation(this.cwj(), "LIKE" /* LIKE */, new StringConstantOperation(`%${value}%`));
1389
+ /** Round up to the nearest integer. */
1390
+ ceil() {
1391
+ return numberFunction(this.functionInput(), "CEILING" /* CEILING */, "Ceil " + this.functionLabel());
678
1392
  }
679
- startsWith(prefix) {
680
- return new ComparisonOperation(this.cwj(), "LIKE" /* LIKE */, new StringConstantOperation(`${prefix}%`));
1393
+ /** Round down to the nearest integer. */
1394
+ floor() {
1395
+ return numberFunction(this.functionInput(), "FLOOR" /* FLOOR */, "Floor " + this.functionLabel());
681
1396
  }
682
- endsWith(suffix) {
683
- return new ComparisonOperation(this.cwj(), "LIKE" /* LIKE */, new StringConstantOperation(`%${suffix}`));
1397
+ /** Square root. */
1398
+ sqrt() {
1399
+ return numberFunction(this.functionInput(), "SQRT" /* SQRT */, "Sqrt " + this.functionLabel());
684
1400
  }
685
- notContains(value) {
686
- return new ComparisonOperation(this.cwj(), "NOT_LIKE" /* NOT_LIKE */, new StringConstantOperation(`%${value}%`));
1401
+ /** Remainder after dividing by `n`. */
1402
+ mod(n) {
1403
+ return numberFunction(this.functionInput(), "MOD" /* MOD */, "Mod " + this.functionLabel(), n);
687
1404
  }
688
- };
689
- var NumericAttribute = class extends Attribute {
690
- constructor(displayName, columnName, columnDbType, owner, parent) {
691
- super(displayName, columnName, columnDbType, owner, parent);
1405
+ /** Raise to the power of `n`. */
1406
+ power(n) {
1407
+ return numberFunction(this.functionInput(), "POWER" /* POWER */, "Power " + this.functionLabel(), n);
692
1408
  }
693
- sum() {
694
- return new AggregateOperation(this.cwj(), "SUM" /* SUM */, this.displayName() + " Sum");
1409
+ /** Round to `d` decimal places, or to the nearest integer when `d` is omitted. */
1410
+ round(d) {
1411
+ return numberFunction(this.functionInput(), "ROUND" /* ROUND */, "Round " + this.functionLabel(), d);
695
1412
  }
1413
+ };
1414
+ applyMixins(StringFunctionOperation, [StringFunctions]);
1415
+ applyMixins(NumericFunctionOperation, [NumericFunctions]);
1416
+
1417
+ // src/datafinder/typed-attributes.ts
1418
+ var MinMax = class {
1419
+ /** Smallest value in the group. */
696
1420
  min() {
697
- return new AggregateOperation(this.cwj(), "MIN" /* MIN */, this.displayName() + " Min");
1421
+ return new AggregateOperation(this.cwj(), "MIN" /* MIN */, "Min " + this.displayName());
698
1422
  }
1423
+ /** Largest value in the group. */
699
1424
  max() {
700
- return new AggregateOperation(this.cwj(), "MAX" /* MAX */, this.displayName() + " Max");
1425
+ return new AggregateOperation(this.cwj(), "MAX" /* MAX */, "Max " + this.displayName());
701
1426
  }
702
- average() {
703
- return new AggregateOperation(this.cwj(), "AVERAGE" /* AVERAGE */, this.displayName() + " Average");
1427
+ };
1428
+ var StringAttribute = class extends Attribute {
1429
+ constructor(displayName, columnName, columnDbType, owner, parent, embedded, nullable = true, optional = false) {
1430
+ super(displayName, columnName, columnDbType, owner, parent, embedded, nullable, optional);
704
1431
  }
705
- abs() {
706
- return new ScalarFunctionOperation(this.cwj(), "ABS" /* ABS */, "Abs " + this.displayName());
1432
+ toConstant(value2) {
1433
+ return new StringConstantOperation(String(value2));
707
1434
  }
708
- ceil() {
709
- return new ScalarFunctionOperation(this.cwj(), "CEILING" /* CEILING */, "Ceil " + this.displayName());
1435
+ /**
1436
+ * Join the values of a group into one text with `separator` between them. `orderBy` is the order they are
1437
+ * joined in (`[f.name().ascending()]`); without it the order is not defined. Missing values are skipped.
1438
+ */
1439
+ join(separator = "", orderBy = []) {
1440
+ return new AggregateOperation(this.cwj(), "STRING_AGG" /* STRING_AGG */, "Join " + this.displayName(), void 0, separator, orderBy);
710
1441
  }
711
- floor() {
712
- return new ScalarFunctionOperation(this.cwj(), "FLOOR" /* FLOOR */, "Floor " + this.displayName());
1442
+ /** Filter to rows where this attribute equals `value`. */
1443
+ eq(value2) {
1444
+ return new ComparisonOperation(this.cwj(), "EQUAL" /* EQUAL */, new StringConstantOperation(value2));
713
1445
  }
714
- sqrt() {
715
- return new ScalarFunctionOperation(this.cwj(), "SQRT" /* SQRT */, "Sqrt " + this.displayName());
1446
+ /** Filter to rows where this attribute does not equal `value`. */
1447
+ ne(value2) {
1448
+ return new ComparisonOperation(this.cwj(), "NOT_EQUAL" /* NOT_EQUAL */, new StringConstantOperation(value2));
716
1449
  }
717
- mod(n) {
718
- return new ScalarFunctionOperation(this.cwj(), "MOD" /* MOD */, "Mod " + this.displayName(), n);
1450
+ /** Filter to rows where this attribute contains `value` as a substring. */
1451
+ contains(value2) {
1452
+ return new ComparisonOperation(this.cwj(), "LIKE" /* LIKE */, new StringConstantOperation(`%${escapeLike(value2)}%`));
719
1453
  }
720
- power(n) {
721
- return new ScalarFunctionOperation(this.cwj(), "POWER" /* POWER */, "Power " + this.displayName(), n);
1454
+ /** Filter to rows where this attribute begins with `prefix`. */
1455
+ startsWith(prefix) {
1456
+ return new ComparisonOperation(this.cwj(), "LIKE" /* LIKE */, new StringConstantOperation(`${escapeLike(prefix)}%`));
722
1457
  }
723
- round(d) {
724
- return new ScalarFunctionOperation(this.cwj(), "ROUND" /* ROUND */, "Round " + this.displayName(), d);
1458
+ /** Filter to rows where this attribute ends with `suffix`. */
1459
+ endsWith(suffix) {
1460
+ return new ComparisonOperation(this.cwj(), "LIKE" /* LIKE */, new StringConstantOperation(`%${escapeLike(suffix)}`));
1461
+ }
1462
+ /** Filter to rows where this attribute does not contain `value` as a substring. */
1463
+ notContains(value2) {
1464
+ return new ComparisonOperation(this.cwj(), "NOT_LIKE" /* NOT_LIKE */, new StringConstantOperation(`%${escapeLike(value2)}%`));
1465
+ }
1466
+ };
1467
+ applyMixins(StringAttribute, [MinMax, StringFunctions]);
1468
+ var NumericAttribute = class extends Attribute {
1469
+ constructor(displayName, columnName, columnDbType, owner, parent, embedded, nullable = true, optional = false) {
1470
+ super(displayName, columnName, columnDbType, owner, parent, embedded, nullable, optional);
1471
+ }
1472
+ /** Sum of all values in the group. */
1473
+ sum() {
1474
+ return new AggregateOperation(this.cwj(), "SUM" /* SUM */, "Sum " + this.displayName());
1475
+ }
1476
+ /** Arithmetic mean of all values in the group. */
1477
+ average() {
1478
+ return new AggregateOperation(this.cwj(), "AVERAGE" /* AVERAGE */, "Average " + this.displayName());
1479
+ }
1480
+ /** Standard deviation of the values in the group: the sample one by default, `ddof = 0` for the population one, as `Series.std`. */
1481
+ std(ddof = 1) {
1482
+ const operator = sample(ddof) ? "STDDEV_SAMP" /* STDDEV_SAMP */ : "STDDEV_POP" /* STDDEV_POP */;
1483
+ return new AggregateOperation(this.cwj(), operator, "Std " + this.displayName());
1484
+ }
1485
+ /** Variance of the values in the group: the sample one by default, `ddof = 0` for the population one, as `Series.var`. */
1486
+ var(ddof = 1) {
1487
+ const operator = sample(ddof) ? "VAR_SAMP" /* VAR_SAMP */ : "VAR_POP" /* VAR_POP */;
1488
+ return new AggregateOperation(this.cwj(), operator, "Var " + this.displayName());
725
1489
  }
726
1490
  };
1491
+ applyMixins(NumericAttribute, [MinMax, NumericFunctions, Arithmetic]);
727
1492
  var DoubleAttribute = class extends NumericAttribute {
728
- constructor(displayName, columnName, columnDbType, owner, parent) {
729
- super(displayName, columnName, columnDbType, owner, parent);
1493
+ constructor(displayName, columnName, columnDbType, owner, parent, embedded, nullable = true, optional = false) {
1494
+ super(displayName, columnName, columnDbType, owner, parent, embedded, nullable, optional);
1495
+ }
1496
+ toConstant(value2) {
1497
+ return new FloatConstantOperation(Number(value2));
730
1498
  }
731
- eq(value) {
732
- return new ComparisonOperation(this.cwj(), "EQUAL" /* EQUAL */, new FloatConstantOperation(value));
1499
+ /** Filter to rows where this attribute equals `value`. */
1500
+ eq(value2) {
1501
+ return new ComparisonOperation(this.cwj(), "EQUAL" /* EQUAL */, new FloatConstantOperation(value2));
733
1502
  }
734
- ne(value) {
735
- return new ComparisonOperation(this.cwj(), "NOT_EQUAL" /* NOT_EQUAL */, new FloatConstantOperation(value));
1503
+ /** Filter to rows where this attribute does not equal `value`. */
1504
+ ne(value2) {
1505
+ return new ComparisonOperation(this.cwj(), "NOT_EQUAL" /* NOT_EQUAL */, new FloatConstantOperation(value2));
736
1506
  }
737
- gt(value) {
738
- return new ComparisonOperation(this.cwj(), "GREATER_THAN" /* GREATER_THAN */, new FloatConstantOperation(value));
1507
+ /** Filter to rows where this attribute is greater than `value`. */
1508
+ gt(value2) {
1509
+ return new ComparisonOperation(this.cwj(), "GREATER_THAN" /* GREATER_THAN */, new FloatConstantOperation(value2));
739
1510
  }
740
- lt(value) {
741
- return new ComparisonOperation(this.cwj(), "LESS_THAN" /* LESS_THAN */, new FloatConstantOperation(value));
1511
+ /** Filter to rows where this attribute is less than `value`. */
1512
+ lt(value2) {
1513
+ return new ComparisonOperation(this.cwj(), "LESS_THAN" /* LESS_THAN */, new FloatConstantOperation(value2));
742
1514
  }
743
- gte(value) {
744
- return new ComparisonOperation(this.cwj(), "GREATER_THAN_OR_EQUAL_TO" /* GREATER_THAN_OR_EQUAL_TO */, new FloatConstantOperation(value));
1515
+ /** Filter to rows where this attribute is greater than or equal to `value`. */
1516
+ gte(value2) {
1517
+ return new ComparisonOperation(this.cwj(), "GREATER_THAN_OR_EQUAL_TO" /* GREATER_THAN_OR_EQUAL_TO */, new FloatConstantOperation(value2));
745
1518
  }
746
- lte(value) {
747
- return new ComparisonOperation(this.cwj(), "LESS_THAN_OR_EQUAL_TO" /* LESS_THAN_OR_EQUAL_TO */, new FloatConstantOperation(value));
1519
+ /** Filter to rows where this attribute is less than or equal to `value`. */
1520
+ lte(value2) {
1521
+ return new ComparisonOperation(this.cwj(), "LESS_THAN_OR_EQUAL_TO" /* LESS_THAN_OR_EQUAL_TO */, new FloatConstantOperation(value2));
748
1522
  }
749
1523
  };
750
1524
  var FloatAttribute = DoubleAttribute;
751
1525
  var DecimalAttribute = class extends NumericAttribute {
752
- constructor(displayName, columnName, columnDbType, owner, parent) {
753
- super(displayName, columnName, columnDbType, owner, parent);
1526
+ constructor(displayName, columnName, columnDbType, owner, parent, embedded, nullable = true, optional = false) {
1527
+ super(displayName, columnName, columnDbType, owner, parent, embedded, nullable, optional);
754
1528
  }
755
- eq(value) {
756
- return new ComparisonOperation(this.cwj(), "EQUAL" /* EQUAL */, new DecimalConstantOperation(value));
1529
+ toConstant(value2) {
1530
+ return new DecimalConstantOperation(Number(value2));
757
1531
  }
758
- ne(value) {
759
- return new ComparisonOperation(this.cwj(), "NOT_EQUAL" /* NOT_EQUAL */, new DecimalConstantOperation(value));
1532
+ /** Filter to rows where this attribute equals `value`. */
1533
+ eq(value2) {
1534
+ return new ComparisonOperation(this.cwj(), "EQUAL" /* EQUAL */, new DecimalConstantOperation(value2));
760
1535
  }
761
- gt(value) {
762
- return new ComparisonOperation(this.cwj(), "GREATER_THAN" /* GREATER_THAN */, new DecimalConstantOperation(value));
1536
+ /** Filter to rows where this attribute does not equal `value`. */
1537
+ ne(value2) {
1538
+ return new ComparisonOperation(this.cwj(), "NOT_EQUAL" /* NOT_EQUAL */, new DecimalConstantOperation(value2));
763
1539
  }
764
- lt(value) {
765
- return new ComparisonOperation(this.cwj(), "LESS_THAN" /* LESS_THAN */, new DecimalConstantOperation(value));
1540
+ /** Filter to rows where this attribute is greater than `value`. */
1541
+ gt(value2) {
1542
+ return new ComparisonOperation(this.cwj(), "GREATER_THAN" /* GREATER_THAN */, new DecimalConstantOperation(value2));
766
1543
  }
767
- gte(value) {
768
- return new ComparisonOperation(this.cwj(), "GREATER_THAN_OR_EQUAL_TO" /* GREATER_THAN_OR_EQUAL_TO */, new DecimalConstantOperation(value));
1544
+ /** Filter to rows where this attribute is less than `value`. */
1545
+ lt(value2) {
1546
+ return new ComparisonOperation(this.cwj(), "LESS_THAN" /* LESS_THAN */, new DecimalConstantOperation(value2));
769
1547
  }
770
- lte(value) {
771
- return new ComparisonOperation(this.cwj(), "LESS_THAN_OR_EQUAL_TO" /* LESS_THAN_OR_EQUAL_TO */, new DecimalConstantOperation(value));
1548
+ /** Filter to rows where this attribute is greater than or equal to `value`. */
1549
+ gte(value2) {
1550
+ return new ComparisonOperation(this.cwj(), "GREATER_THAN_OR_EQUAL_TO" /* GREATER_THAN_OR_EQUAL_TO */, new DecimalConstantOperation(value2));
1551
+ }
1552
+ /** Filter to rows where this attribute is less than or equal to `value`. */
1553
+ lte(value2) {
1554
+ return new ComparisonOperation(this.cwj(), "LESS_THAN_OR_EQUAL_TO" /* LESS_THAN_OR_EQUAL_TO */, new DecimalConstantOperation(value2));
772
1555
  }
773
1556
  };
774
1557
  var IntegerAttribute = class extends NumericAttribute {
775
- constructor(displayName, columnName, columnDbType, owner, parent) {
776
- super(displayName, columnName, columnDbType, owner, parent);
1558
+ constructor(displayName, columnName, columnDbType, owner, parent, embedded, nullable = true, optional = false) {
1559
+ super(displayName, columnName, columnDbType, owner, parent, embedded, nullable, optional);
1560
+ }
1561
+ toConstant(value2) {
1562
+ return new IntegerConstantOperation(Number(value2));
777
1563
  }
778
- eq(value) {
779
- return new ComparisonOperation(this.cwj(), "EQUAL" /* EQUAL */, new IntegerConstantOperation(value));
1564
+ /** Filter to rows where this attribute equals `value`. */
1565
+ eq(value2) {
1566
+ return new ComparisonOperation(this.cwj(), "EQUAL" /* EQUAL */, new IntegerConstantOperation(value2));
780
1567
  }
781
- ne(value) {
782
- return new ComparisonOperation(this.cwj(), "NOT_EQUAL" /* NOT_EQUAL */, new IntegerConstantOperation(value));
1568
+ /** Filter to rows where this attribute does not equal `value`. */
1569
+ ne(value2) {
1570
+ return new ComparisonOperation(this.cwj(), "NOT_EQUAL" /* NOT_EQUAL */, new IntegerConstantOperation(value2));
783
1571
  }
784
- gt(value) {
785
- return new ComparisonOperation(this.cwj(), "GREATER_THAN" /* GREATER_THAN */, new IntegerConstantOperation(value));
1572
+ /** Filter to rows where this attribute is greater than `value`. */
1573
+ gt(value2) {
1574
+ return new ComparisonOperation(this.cwj(), "GREATER_THAN" /* GREATER_THAN */, new IntegerConstantOperation(value2));
786
1575
  }
787
- lt(value) {
788
- return new ComparisonOperation(this.cwj(), "LESS_THAN" /* LESS_THAN */, new IntegerConstantOperation(value));
1576
+ /** Filter to rows where this attribute is less than `value`. */
1577
+ lt(value2) {
1578
+ return new ComparisonOperation(this.cwj(), "LESS_THAN" /* LESS_THAN */, new IntegerConstantOperation(value2));
789
1579
  }
790
- gte(value) {
791
- return new ComparisonOperation(this.cwj(), "GREATER_THAN_OR_EQUAL_TO" /* GREATER_THAN_OR_EQUAL_TO */, new IntegerConstantOperation(value));
1580
+ /** Filter to rows where this attribute is greater than or equal to `value`. */
1581
+ gte(value2) {
1582
+ return new ComparisonOperation(this.cwj(), "GREATER_THAN_OR_EQUAL_TO" /* GREATER_THAN_OR_EQUAL_TO */, new IntegerConstantOperation(value2));
792
1583
  }
793
- lte(value) {
794
- return new ComparisonOperation(this.cwj(), "LESS_THAN_OR_EQUAL_TO" /* LESS_THAN_OR_EQUAL_TO */, new IntegerConstantOperation(value));
1584
+ /** Filter to rows where this attribute is less than or equal to `value`. */
1585
+ lte(value2) {
1586
+ return new ComparisonOperation(this.cwj(), "LESS_THAN_OR_EQUAL_TO" /* LESS_THAN_OR_EQUAL_TO */, new IntegerConstantOperation(value2));
795
1587
  }
796
1588
  };
797
1589
  var BooleanAttribute = class extends Attribute {
798
- constructor(displayName, columnName, columnDbType, owner, parent) {
799
- super(displayName, columnName, columnDbType, owner, parent);
1590
+ constructor(displayName, columnName, columnDbType, owner, parent, embedded, nullable = true, optional = false) {
1591
+ super(displayName, columnName, columnDbType, owner, parent, embedded, nullable, optional);
800
1592
  }
801
- eq(value) {
802
- return new ComparisonOperation(this.cwj(), "EQUAL" /* EQUAL */, new BooleanConstantOperation(value));
1593
+ toConstant(value2) {
1594
+ return new BooleanConstantOperation(Boolean(value2));
803
1595
  }
1596
+ /** Filter to rows where this attribute equals `value`. */
1597
+ eq(value2) {
1598
+ return new ComparisonOperation(this.cwj(), "EQUAL" /* EQUAL */, new BooleanConstantOperation(value2));
1599
+ }
1600
+ /** Filter to rows where this attribute is true. */
804
1601
  isTrue() {
805
1602
  return new ComparisonOperation(this.cwj(), "EQUAL" /* EQUAL */, new BooleanConstantOperation(true));
806
1603
  }
1604
+ /** Filter to rows where this attribute is false. */
807
1605
  isFalse() {
808
1606
  return new ComparisonOperation(this.cwj(), "EQUAL" /* EQUAL */, new BooleanConstantOperation(false));
809
1607
  }
810
1608
  };
811
- function parseDate(value) {
812
- if (value instanceof Date) return value;
813
- const d = new Date(value);
814
- if (isNaN(d.getTime())) throw new Error(`Cannot parse date: ${value}`);
1609
+ function parseDate(value2) {
1610
+ if (value2 instanceof Date) return value2;
1611
+ const d = new Date(value2);
1612
+ if (isNaN(d.getTime())) throw new Error(`Cannot parse date: ${value2}`);
815
1613
  return d;
816
1614
  }
1615
+ function temporalConstant(value2) {
1616
+ if (typeof value2 === "string" && DATE_ONLY_PATTERN.test(value2)) return new DateConstantOperation(new DateOnly(value2));
1617
+ const date = parseDate(value2);
1618
+ return date instanceof DateOnly ? new DateConstantOperation(date) : new DateTimeConstantOperation(date);
1619
+ }
817
1620
  var DateAttribute = class extends Attribute {
818
- constructor(displayName, columnName, columnDbType, owner, parent) {
819
- super(displayName, columnName, columnDbType, owner, parent);
1621
+ constructor(displayName, columnName, columnDbType, owner, parent, embedded, nullable = true, optional = false) {
1622
+ super(displayName, columnName, columnDbType, owner, parent, embedded, nullable, optional);
820
1623
  }
821
- eq(value) {
822
- return new ComparisonOperation(this.cwj(), "EQUAL" /* EQUAL */, new DateConstantOperation(parseDate(value)));
1624
+ toConstant(value2) {
1625
+ return new DateConstantOperation(parseDate(value2));
823
1626
  }
824
- ne(value) {
825
- return new ComparisonOperation(this.cwj(), "NOT_EQUAL" /* NOT_EQUAL */, new DateConstantOperation(parseDate(value)));
1627
+ /** Filter to rows where this date equals `value`. */
1628
+ eq(value2) {
1629
+ return new ComparisonOperation(this.cwj(), "EQUAL" /* EQUAL */, new DateConstantOperation(parseDate(value2)));
826
1630
  }
827
- gt(value) {
828
- return new ComparisonOperation(this.cwj(), "GREATER_THAN" /* GREATER_THAN */, new DateConstantOperation(parseDate(value)));
1631
+ /** Filter to rows where this date does not equal `value`. */
1632
+ ne(value2) {
1633
+ return new ComparisonOperation(this.cwj(), "NOT_EQUAL" /* NOT_EQUAL */, new DateConstantOperation(parseDate(value2)));
829
1634
  }
830
- lt(value) {
831
- return new ComparisonOperation(this.cwj(), "LESS_THAN" /* LESS_THAN */, new DateConstantOperation(parseDate(value)));
1635
+ /** Filter to rows where this date is after `value`. */
1636
+ gt(value2) {
1637
+ return new ComparisonOperation(this.cwj(), "GREATER_THAN" /* GREATER_THAN */, new DateConstantOperation(parseDate(value2)));
832
1638
  }
833
- gte(value) {
834
- return new ComparisonOperation(this.cwj(), "GREATER_THAN_OR_EQUAL_TO" /* GREATER_THAN_OR_EQUAL_TO */, new DateConstantOperation(parseDate(value)));
1639
+ /** Filter to rows where this date is before `value`. */
1640
+ lt(value2) {
1641
+ return new ComparisonOperation(this.cwj(), "LESS_THAN" /* LESS_THAN */, new DateConstantOperation(parseDate(value2)));
835
1642
  }
836
- lte(value) {
837
- return new ComparisonOperation(this.cwj(), "LESS_THAN_OR_EQUAL_TO" /* LESS_THAN_OR_EQUAL_TO */, new DateConstantOperation(parseDate(value)));
1643
+ /** Filter to rows where this date is on or after `value`. */
1644
+ gte(value2) {
1645
+ return new ComparisonOperation(this.cwj(), "GREATER_THAN_OR_EQUAL_TO" /* GREATER_THAN_OR_EQUAL_TO */, new DateConstantOperation(parseDate(value2)));
838
1646
  }
1647
+ /** Filter to rows where this date is on or before `value`. */
1648
+ lte(value2) {
1649
+ return new ComparisonOperation(this.cwj(), "LESS_THAN_OR_EQUAL_TO" /* LESS_THAN_OR_EQUAL_TO */, new DateConstantOperation(parseDate(value2)));
1650
+ }
1651
+ /** Extract the four-digit calendar year. */
839
1652
  year() {
840
1653
  return new DateExtractOperation(this.cwj(), "YEAR" /* YEAR */, "Year " + this.displayName());
841
1654
  }
1655
+ /** Extract the month number (1–12). */
842
1656
  month() {
843
1657
  return new DateExtractOperation(this.cwj(), "MONTH" /* MONTH */, "Month " + this.displayName());
844
1658
  }
1659
+ /** Extract the day of the month (1–31). */
845
1660
  day() {
846
1661
  return new DateExtractOperation(this.cwj(), "DAY" /* DAY */, "Day " + this.displayName());
847
1662
  }
1663
+ /** Extract the quarter (1–4). */
848
1664
  quarter() {
849
1665
  return new DateExtractOperation(this.cwj(), "QUARTER" /* QUARTER */, "Quarter " + this.displayName());
850
1666
  }
1667
+ /** Extract the ISO week number (1–53). */
851
1668
  week() {
852
1669
  return new DateExtractOperation(this.cwj(), "WEEK" /* WEEK */, "Week " + this.displayName());
853
1670
  }
1671
+ /** Extract the day of the week (0 = Sunday through 6 = Saturday). */
854
1672
  dayOfWeek() {
855
1673
  return new DateExtractOperation(this.cwj(), "DOW" /* DOW */, "Day Of Week " + this.displayName());
856
1674
  }
1675
+ /** The date of the first day of the week the date falls in (a Monday). */
1676
+ firstDayOfWeek() {
1677
+ return new DateTruncOperation(this.cwj(), "WEEK" /* WEEK */, "First Day Of Week " + this.displayName());
1678
+ }
1679
+ /** The date of the first day of the month the date falls in. */
1680
+ firstDayOfMonth() {
1681
+ return new DateTruncOperation(this.cwj(), "MONTH" /* MONTH */, "First Day Of Month " + this.displayName());
1682
+ }
1683
+ /** The date of the first day of the quarter the date falls in. */
1684
+ firstDayOfQuarter() {
1685
+ return new DateTruncOperation(this.cwj(), "QUARTER" /* QUARTER */, "First Day Of Quarter " + this.displayName());
1686
+ }
1687
+ /** The date of the first day of the year the date falls in. */
1688
+ firstDayOfYear() {
1689
+ return new DateTruncOperation(this.cwj(), "YEAR" /* YEAR */, "First Day Of Year " + this.displayName());
1690
+ }
1691
+ /** Add `n` days to this date. */
857
1692
  addDays(n) {
858
1693
  return new DateArithmeticOperation(this.cwj(), n, "DAY" /* DAY */, true, "Add Days " + this.displayName());
859
1694
  }
1695
+ /** Add `n` months to this date. */
860
1696
  addMonths(n) {
861
1697
  return new DateArithmeticOperation(this.cwj(), n, "MONTH" /* MONTH */, true, "Add Months " + this.displayName());
862
1698
  }
1699
+ /** Add `n` years to this date. */
863
1700
  addYears(n) {
864
1701
  return new DateArithmeticOperation(this.cwj(), n, "YEAR" /* YEAR */, true, "Add Years " + this.displayName());
865
1702
  }
1703
+ /** Subtract `n` days from this date. */
866
1704
  subtractDays(n) {
867
1705
  return new DateArithmeticOperation(this.cwj(), n, "DAY" /* DAY */, false, "Subtract Days " + this.displayName());
868
1706
  }
1707
+ /** Subtract `n` months from this date. */
869
1708
  subtractMonths(n) {
870
1709
  return new DateArithmeticOperation(this.cwj(), n, "MONTH" /* MONTH */, false, "Subtract Months " + this.displayName());
871
1710
  }
1711
+ /** Subtract `n` years from this date. */
872
1712
  subtractYears(n) {
873
1713
  return new DateArithmeticOperation(this.cwj(), n, "YEAR" /* YEAR */, false, "Subtract Years " + this.displayName());
874
1714
  }
1715
+ /** Number of whole days between this date and `other`. */
875
1716
  diffDays(other) {
876
1717
  return new DateDiffOperation(this.cwj(), other, "DAY" /* DAY */, "Diff Days " + this.displayName());
877
1718
  }
1719
+ /** Number of whole months between this date and `other`. */
878
1720
  diffMonths(other) {
879
1721
  return new DateDiffOperation(this.cwj(), other, "MONTH" /* MONTH */, "Diff Months " + this.displayName());
880
1722
  }
1723
+ /** Number of whole years between this date and `other`. */
881
1724
  diffYears(other) {
882
1725
  return new DateDiffOperation(this.cwj(), other, "YEAR" /* YEAR */, "Diff Years " + this.displayName());
883
1726
  }
884
1727
  };
885
1728
  var DateTimeAttribute = class extends Attribute {
886
- constructor(displayName, columnName, columnDbType, owner, parent) {
887
- super(displayName, columnName, columnDbType, owner, parent);
1729
+ constructor(displayName, columnName, columnDbType, owner, parent, embedded, nullable = true, optional = false) {
1730
+ super(displayName, columnName, columnDbType, owner, parent, embedded, nullable, optional);
1731
+ }
1732
+ toConstant(value2) {
1733
+ return temporalConstant(value2);
888
1734
  }
889
- eq(value) {
890
- return new ComparisonOperation(this.cwj(), "EQUAL" /* EQUAL */, new DateTimeConstantOperation(parseDate(value)));
1735
+ /** Filter to rows where this timestamp equals `value`. */
1736
+ eq(value2) {
1737
+ return new ComparisonOperation(this.cwj(), "EQUAL" /* EQUAL */, temporalConstant(value2));
891
1738
  }
892
- ne(value) {
893
- return new ComparisonOperation(this.cwj(), "NOT_EQUAL" /* NOT_EQUAL */, new DateTimeConstantOperation(parseDate(value)));
1739
+ /** Filter to rows where this timestamp does not equal `value`. */
1740
+ ne(value2) {
1741
+ return new ComparisonOperation(this.cwj(), "NOT_EQUAL" /* NOT_EQUAL */, temporalConstant(value2));
894
1742
  }
895
- gt(value) {
896
- return new ComparisonOperation(this.cwj(), "GREATER_THAN" /* GREATER_THAN */, new DateTimeConstantOperation(parseDate(value)));
1743
+ /** Filter to rows where this timestamp is after `value`. */
1744
+ gt(value2) {
1745
+ return new ComparisonOperation(this.cwj(), "GREATER_THAN" /* GREATER_THAN */, temporalConstant(value2));
897
1746
  }
898
- lt(value) {
899
- return new ComparisonOperation(this.cwj(), "LESS_THAN" /* LESS_THAN */, new DateTimeConstantOperation(parseDate(value)));
1747
+ /** Filter to rows where this timestamp is before `value`. */
1748
+ lt(value2) {
1749
+ return new ComparisonOperation(this.cwj(), "LESS_THAN" /* LESS_THAN */, temporalConstant(value2));
900
1750
  }
901
- gte(value) {
902
- return new ComparisonOperation(this.cwj(), "GREATER_THAN_OR_EQUAL_TO" /* GREATER_THAN_OR_EQUAL_TO */, new DateTimeConstantOperation(parseDate(value)));
1751
+ /** Filter to rows where this timestamp is on or after `value`. */
1752
+ gte(value2) {
1753
+ return new ComparisonOperation(this.cwj(), "GREATER_THAN_OR_EQUAL_TO" /* GREATER_THAN_OR_EQUAL_TO */, temporalConstant(value2));
903
1754
  }
904
- lte(value) {
905
- return new ComparisonOperation(this.cwj(), "LESS_THAN_OR_EQUAL_TO" /* LESS_THAN_OR_EQUAL_TO */, new DateTimeConstantOperation(parseDate(value)));
1755
+ /** Filter to rows where this timestamp is on or before `value`. */
1756
+ lte(value2) {
1757
+ return new ComparisonOperation(this.cwj(), "LESS_THAN_OR_EQUAL_TO" /* LESS_THAN_OR_EQUAL_TO */, temporalConstant(value2));
906
1758
  }
1759
+ /** Extract the four-digit calendar year. */
907
1760
  year() {
908
1761
  return new DateExtractOperation(this.cwj(), "YEAR" /* YEAR */, "Year " + this.displayName());
909
1762
  }
1763
+ /** Extract the month number (1–12). */
910
1764
  month() {
911
1765
  return new DateExtractOperation(this.cwj(), "MONTH" /* MONTH */, "Month " + this.displayName());
912
1766
  }
1767
+ /** Extract the day of the month (1–31). */
913
1768
  day() {
914
1769
  return new DateExtractOperation(this.cwj(), "DAY" /* DAY */, "Day " + this.displayName());
915
1770
  }
1771
+ /** Extract the hour (0–23). */
916
1772
  hour() {
917
1773
  return new DateExtractOperation(this.cwj(), "HOUR" /* HOUR */, "Hour " + this.displayName());
918
1774
  }
1775
+ /** Extract the minute (0–59). */
919
1776
  minute() {
920
1777
  return new DateExtractOperation(this.cwj(), "MINUTE" /* MINUTE */, "Minute " + this.displayName());
921
1778
  }
1779
+ /** Extract the second (0–59). */
922
1780
  second() {
923
1781
  return new DateExtractOperation(this.cwj(), "SECOND" /* SECOND */, "Second " + this.displayName());
924
1782
  }
1783
+ /** Extract the quarter (1–4). */
925
1784
  quarter() {
926
1785
  return new DateExtractOperation(this.cwj(), "QUARTER" /* QUARTER */, "Quarter " + this.displayName());
927
1786
  }
1787
+ /** Extract the ISO week number (1–53). */
928
1788
  week() {
929
1789
  return new DateExtractOperation(this.cwj(), "WEEK" /* WEEK */, "Week " + this.displayName());
930
1790
  }
1791
+ /** Extract the day of the week (0 = Sunday through 6 = Saturday). */
931
1792
  dayOfWeek() {
932
1793
  return new DateExtractOperation(this.cwj(), "DOW" /* DOW */, "Day Of Week " + this.displayName());
933
1794
  }
1795
+ /** The date of the first day of the week the date falls in (a Monday). */
1796
+ firstDayOfWeek() {
1797
+ return new DateTruncOperation(this.cwj(), "WEEK" /* WEEK */, "First Day Of Week " + this.displayName());
1798
+ }
1799
+ /** The date of the first day of the month the date falls in. */
1800
+ firstDayOfMonth() {
1801
+ return new DateTruncOperation(this.cwj(), "MONTH" /* MONTH */, "First Day Of Month " + this.displayName());
1802
+ }
1803
+ /** The date of the first day of the quarter the date falls in. */
1804
+ firstDayOfQuarter() {
1805
+ return new DateTruncOperation(this.cwj(), "QUARTER" /* QUARTER */, "First Day Of Quarter " + this.displayName());
1806
+ }
1807
+ /** The date of the first day of the year the date falls in. */
1808
+ firstDayOfYear() {
1809
+ return new DateTruncOperation(this.cwj(), "YEAR" /* YEAR */, "First Day Of Year " + this.displayName());
1810
+ }
1811
+ /** Add `n` days to this timestamp. */
934
1812
  addDays(n) {
935
1813
  return new DateArithmeticOperation(this.cwj(), n, "DAY" /* DAY */, true, "Add Days " + this.displayName());
936
1814
  }
1815
+ /** Add `n` months to this timestamp. */
937
1816
  addMonths(n) {
938
1817
  return new DateArithmeticOperation(this.cwj(), n, "MONTH" /* MONTH */, true, "Add Months " + this.displayName());
939
1818
  }
1819
+ /** Add `n` years to this timestamp. */
940
1820
  addYears(n) {
941
1821
  return new DateArithmeticOperation(this.cwj(), n, "YEAR" /* YEAR */, true, "Add Years " + this.displayName());
942
1822
  }
1823
+ /** Add `n` hours to this timestamp. */
943
1824
  addHours(n) {
944
1825
  return new DateArithmeticOperation(this.cwj(), n, "HOUR" /* HOUR */, true, "Add Hours " + this.displayName());
945
1826
  }
1827
+ /** Add `n` minutes to this timestamp. */
946
1828
  addMinutes(n) {
947
1829
  return new DateArithmeticOperation(this.cwj(), n, "MINUTE" /* MINUTE */, true, "Add Minutes " + this.displayName());
948
1830
  }
1831
+ /** Add `n` seconds to this timestamp. */
949
1832
  addSeconds(n) {
950
1833
  return new DateArithmeticOperation(this.cwj(), n, "SECOND" /* SECOND */, true, "Add Seconds " + this.displayName());
951
1834
  }
1835
+ /** Subtract `n` days from this timestamp. */
952
1836
  subtractDays(n) {
953
1837
  return new DateArithmeticOperation(this.cwj(), n, "DAY" /* DAY */, false, "Subtract Days " + this.displayName());
954
1838
  }
1839
+ /** Subtract `n` months from this timestamp. */
955
1840
  subtractMonths(n) {
956
1841
  return new DateArithmeticOperation(this.cwj(), n, "MONTH" /* MONTH */, false, "Subtract Months " + this.displayName());
957
1842
  }
1843
+ /** Subtract `n` years from this timestamp. */
958
1844
  subtractYears(n) {
959
1845
  return new DateArithmeticOperation(this.cwj(), n, "YEAR" /* YEAR */, false, "Subtract Years " + this.displayName());
960
1846
  }
1847
+ /** Subtract `n` hours from this timestamp. */
961
1848
  subtractHours(n) {
962
1849
  return new DateArithmeticOperation(this.cwj(), n, "HOUR" /* HOUR */, false, "Subtract Hours " + this.displayName());
963
1850
  }
1851
+ /** Subtract `n` minutes from this timestamp. */
964
1852
  subtractMinutes(n) {
965
1853
  return new DateArithmeticOperation(this.cwj(), n, "MINUTE" /* MINUTE */, false, "Subtract Minutes " + this.displayName());
966
1854
  }
1855
+ /** Subtract `n` seconds from this timestamp. */
967
1856
  subtractSeconds(n) {
968
1857
  return new DateArithmeticOperation(this.cwj(), n, "SECOND" /* SECOND */, false, "Subtract Seconds " + this.displayName());
969
1858
  }
1859
+ /** Number of whole days between this timestamp and `other`. */
970
1860
  diffDays(other) {
971
1861
  return new DateDiffOperation(this.cwj(), other, "DAY" /* DAY */, "Diff Days " + this.displayName());
972
1862
  }
1863
+ /** Number of whole months between this timestamp and `other`. */
973
1864
  diffMonths(other) {
974
1865
  return new DateDiffOperation(this.cwj(), other, "MONTH" /* MONTH */, "Diff Months " + this.displayName());
975
1866
  }
1867
+ /** Number of whole years between this timestamp and `other`. */
976
1868
  diffYears(other) {
977
1869
  return new DateDiffOperation(this.cwj(), other, "YEAR" /* YEAR */, "Diff Years " + this.displayName());
978
1870
  }
1871
+ /** Number of whole hours between this timestamp and `other`. */
979
1872
  diffHours(other) {
980
1873
  return new DateDiffOperation(this.cwj(), other, "HOUR" /* HOUR */, "Diff Hours " + this.displayName());
981
1874
  }
1875
+ /** Number of whole minutes between this timestamp and `other`. */
982
1876
  diffMinutes(other) {
983
1877
  return new DateDiffOperation(this.cwj(), other, "MINUTE" /* MINUTE */, "Diff Minutes " + this.displayName());
984
1878
  }
1879
+ /** Number of whole seconds between this timestamp and `other`. */
985
1880
  diffSeconds(other) {
986
1881
  return new DateDiffOperation(this.cwj(), other, "SECOND" /* SECOND */, "Diff Seconds " + this.displayName());
987
1882
  }
988
1883
  };
1884
+ applyMixins(DateAttribute, [MinMax]);
1885
+ applyMixins(DateTimeAttribute, [MinMax]);
1886
+
1887
+ // src/datafinder/expressions.ts
1888
+ function constant(value2) {
1889
+ if (typeof value2 === "boolean") return new BooleanConstantOperation(value2);
1890
+ if (typeof value2 === "number" && Number.isFinite(value2)) {
1891
+ return Number.isInteger(value2) ? new IntegerConstantOperation(value2) : new FloatConstantOperation(value2);
1892
+ }
1893
+ if (typeof value2 === "string") return new StringConstantOperation(value2);
1894
+ if (value2 instanceof DateOnly) return new DateConstantOperation(value2);
1895
+ if (value2 instanceof Date) return new DateTimeConstantOperation(value2);
1896
+ throw new TypeError(`A constant column holds a boolean, a finite number, a string or a date, not ${value2 === null ? "null" : typeof value2}`);
1897
+ }
1898
+ function countAll() {
1899
+ return new CountAllOperation("");
1900
+ }
1901
+
1902
+ // src/datafinder/conditional.ts
1903
+ var CONDITIONS = [
1904
+ ComparisonOperation,
1905
+ LogicalOperation,
1906
+ IsNullOperation,
1907
+ IsNotNullOperation,
1908
+ InOperation,
1909
+ NotInOperation,
1910
+ ExistsOperation,
1911
+ NotExistsOperation
1912
+ ];
1913
+ function value(branch) {
1914
+ if (branch instanceof Attribute) return branch.cwj();
1915
+ if (branch instanceof RelationalOperationElement) return branch;
1916
+ try {
1917
+ return constant(branch);
1918
+ } catch {
1919
+ throw new TypeError(`A branch is a column, an expression or a value, not ${branch === null ? "null" : typeof branch}`);
1920
+ }
1921
+ }
1922
+ function checked(condition) {
1923
+ if (!CONDITIONS.some((c) => condition instanceof c)) {
1924
+ throw new TypeError("The condition is a comparison, a null check, a membership test, exists, or and/or of those");
1925
+ }
1926
+ return condition;
1927
+ }
1928
+ function chain(cases, fallback) {
1929
+ const [[condition, then], ...rest] = cases;
1930
+ return new CaseOperation(condition, then, rest.length > 0 ? chain(rest, fallback) : fallback);
1931
+ }
1932
+ var Then = class extends CaseOperation {
1933
+ constructor(cases) {
1934
+ const first = chain(cases);
1935
+ super(first.condition, first.then, first.fallback);
1936
+ this.cases = cases;
1937
+ }
1938
+ cases;
1939
+ /** Another case, tried when the ones before it do not hold. */
1940
+ when(condition) {
1941
+ return new When(checked(condition), this.cases);
1942
+ }
1943
+ /** The value when none of the cases hold. */
1944
+ otherwise(branch) {
1945
+ return chain(this.cases, value(branch));
1946
+ }
1947
+ };
1948
+ var When = class {
1949
+ constructor(condition, earlier = []) {
1950
+ this.condition = condition;
1951
+ this.earlier = earlier;
1952
+ }
1953
+ condition;
1954
+ earlier;
1955
+ /** The value where the condition holds. A value is a column, an expression or a plain value. */
1956
+ then(branch) {
1957
+ return new Then([...this.earlier, [this.condition, value(branch)]]);
1958
+ }
1959
+ };
1960
+ function when(condition) {
1961
+ return new When(checked(condition));
1962
+ }
989
1963
 
990
1964
  // src/model/milestoning.ts
991
1965
  var MilestoningColumns = class {
@@ -1064,49 +2038,68 @@ var MilestonedTable = class extends Table {
1064
2038
  milestoningColumns;
1065
2039
  };
1066
2040
 
1067
- // src/datafinder/finder.ts
1068
- var ExistsOperation = class extends RelationalOperationElement {
1069
- constructor(node) {
1070
- super();
1071
- this.node = node;
1072
- }
1073
- node;
1074
- andOp(other) {
1075
- return new LogicalOperation(this, "AND" /* AND */, other);
1076
- }
1077
- };
1078
- var NotExistsOperation = class extends RelationalOperationElement {
1079
- constructor(node) {
1080
- super();
1081
- this.node = node;
1082
- }
1083
- node;
1084
- andOp(other) {
1085
- return new LogicalOperation(this, "AND" /* AND */, other);
1086
- }
1087
- };
1088
- var RelatedFinder = class {
1089
- constructor(_node) {
1090
- this._node = _node;
1091
- }
1092
- _node;
1093
- exists() {
1094
- return new ExistsOperation(this._node);
1095
- }
1096
- notExists() {
1097
- return new NotExistsOperation(this._node);
1098
- }
1099
- };
2041
+ // src/datafinder/dialect.ts
2042
+ var doubleQuoted = (name) => '"' + name.replaceAll('"', '""') + '"';
2043
+ var quoted = (value2) => "'" + value2.replaceAll("'", "''") + "'";
2044
+ var SECONDS_PER = { ["HOUR" /* HOUR */]: 3600, ["MINUTE" /* MINUTE */]: 60, ["SECOND" /* SECOND */]: 1 };
2045
+ var POSTGRES = {
2046
+ name: "postgres",
2047
+ quoteIdentifier: doubleQuoted,
2048
+ stringLiteral: quoted,
2049
+ orderDirection: (direction) => direction === "ASC" /* ASC */ ? " ASC" : " DESC",
2050
+ doubleType: "DOUBLE PRECISION",
2051
+ dayOfWeek: (expression) => `EXTRACT(DOW FROM ${expression})`,
2052
+ dateDiff(unit, from, to) {
2053
+ const start = `CAST(${from} AS TIMESTAMP)`;
2054
+ const end = `CAST(${to} AS TIMESTAMP)`;
2055
+ switch (unit) {
2056
+ case "DAY" /* DAY */:
2057
+ return `(CAST(${to} AS DATE) - CAST(${from} AS DATE))`;
2058
+ case "MONTH" /* MONTH */:
2059
+ return `CAST(EXTRACT(YEAR FROM AGE(${end}, ${start})) * 12 + EXTRACT(MONTH FROM AGE(${end}, ${start})) AS BIGINT)`;
2060
+ case "YEAR" /* YEAR */:
2061
+ return `CAST(EXTRACT(YEAR FROM AGE(${end}, ${start})) AS BIGINT)`;
2062
+ default: {
2063
+ const seconds = SECONDS_PER[unit];
2064
+ if (seconds === void 0) throw new Error(`Cannot take the difference in ${unit}`);
2065
+ return `CAST(FLOOR(EXTRACT(EPOCH FROM (${end} - ${start})) / ${seconds}) AS BIGINT)`;
2066
+ }
2067
+ }
2068
+ },
2069
+ stringAgg: (expression, separator, orderBy) => `STRING_AGG(${expression}, ${separator}${orderBy.length > 0 ? " ORDER BY " + orderBy.join(", ") : ""})`
2070
+ };
2071
+ var DUCKDB = {
2072
+ ...POSTGRES,
2073
+ name: "duckdb",
2074
+ // DuckDB sorts nulls last whichever way the sort goes
2075
+ orderDirection: (direction) => direction === "ASC" /* ASC */ ? " ASC" : " DESC NULLS FIRST",
2076
+ doubleType: "DOUBLE",
2077
+ dateDiff: (unit, from, to) => `DATE_DIFF('${unit.toLowerCase()}', ${from}, ${to})`
2078
+ };
2079
+ var DATABRICKS = {
2080
+ name: "databricks",
2081
+ // a double-quoted token is a string in Databricks; identifiers take backticks
2082
+ quoteIdentifier: (name) => "`" + name.replaceAll("`", "``") + "`",
2083
+ // a backslash starts an escape in a Databricks string literal, and a doubled quote is not one: Spark reads
2084
+ // 'it''s' as the two literals 'it' and 's' and joins them, losing the quote. A quote is escaped with a backslash.
2085
+ stringLiteral: (value2) => "'" + value2.replaceAll("\\", "\\\\").replaceAll("'", "\\'") + "'",
2086
+ // Databricks sorts nulls first when ascending, last when descending
2087
+ orderDirection: (direction) => direction === "ASC" /* ASC */ ? " ASC NULLS LAST" : " DESC NULLS FIRST",
2088
+ doubleType: "DOUBLE",
2089
+ // DAYOFWEEK is 1 = Sunday through 7 = Saturday
2090
+ dayOfWeek: (expression) => `(DAYOFWEEK(${expression}) - 1)`,
2091
+ dateDiff: (unit, from, to) => `DATEDIFF(${unit}, ${from}, ${to})`,
2092
+ stringAgg: (expression, separator, orderBy) => `LISTAGG(${expression}, ${separator})${orderBy.length > 0 ? " WITHIN GROUP (ORDER BY " + orderBy.join(", ") + ")" : ""}`
2093
+ };
2094
+ var DIALECTS = { postgres: POSTGRES, duckdb: DUCKDB, databricks: DATABRICKS };
2095
+ function dialectOf(dialect) {
2096
+ if (typeof dialect !== "string") return dialect;
2097
+ const found = DIALECTS[dialect];
2098
+ if (found === void 0) throw new Error(`Unknown SQL dialect '${dialect}': one of ${Object.keys(DIALECTS).join(", ")}`);
2099
+ return found;
2100
+ }
1100
2101
 
1101
2102
  // src/datafinder/sql-generator.ts
1102
- var Alias = class {
1103
- constructor(element, name) {
1104
- this.element = element;
1105
- this.name = name;
1106
- }
1107
- element;
1108
- name;
1109
- };
1110
2103
  var TableAlias = class {
1111
2104
  constructor(table, alias) {
1112
2105
  this.table = table;
@@ -1133,30 +2126,20 @@ var Join = class {
1133
2126
  source;
1134
2127
  target;
1135
2128
  filterOp;
1136
- };
1137
- var SelectOperation = class {
1138
- constructor(display, filter, orderBy = [], groupBy = [], limit, table) {
1139
- this.display = display;
1140
- this.filter = filter;
1141
- this.orderBy = orderBy;
1142
- this.groupBy = groupBy;
1143
- this.limit = limit;
1144
- this.table = table;
2129
+ /** `source = target`, written with the key column first as Legend writes a join defined as key = foreign key. */
2130
+ condition() {
2131
+ let [first, second] = [this.source, this.target];
2132
+ if (second.column.primaryKey && !first.column.primaryKey) [first, second] = [second, first];
2133
+ return first.tableAlias.alias + "." + first.column.name + " = " + second.tableAlias.alias + "." + second.column.name;
1145
2134
  }
1146
- display;
1147
- filter;
1148
- orderBy;
1149
- groupBy;
1150
- limit;
1151
- table;
1152
2135
  };
1153
- function sqlFormatDatetime(value) {
2136
+ function sqlFormatDatetime(value2) {
1154
2137
  const pad = (n, w = 2) => String(n).padStart(w, "0");
1155
- return `'${value.getUTCFullYear()}-${pad(value.getUTCMonth() + 1)}-${pad(value.getUTCDate())} ${pad(value.getUTCHours())}:${pad(value.getUTCMinutes())}:${pad(value.getUTCSeconds())}'`;
2138
+ return `'${value2.getUTCFullYear()}-${pad(value2.getUTCMonth() + 1)}-${pad(value2.getUTCDate())} ${pad(value2.getUTCHours())}:${pad(value2.getUTCMinutes())}:${pad(value2.getUTCSeconds())}'`;
1156
2139
  }
1157
- function sqlFormatDate(value) {
2140
+ function sqlFormatDate(value2) {
1158
2141
  const pad = (n) => String(n).padStart(2, "0");
1159
- return `'${value.getUTCFullYear()}-${pad(value.getUTCMonth() + 1)}-${pad(value.getUTCDate())}'`;
2142
+ return `'${value2.getUTCFullYear()}-${pad(value2.getUTCMonth() + 1)}-${pad(value2.getUTCDate())}'`;
1160
2143
  }
1161
2144
  var LOGICAL_OPERATOR_STR = {
1162
2145
  ["AND" /* AND */]: " AND ",
@@ -1175,6 +2158,13 @@ var COMPARISON_OPERATOR_STR = {
1175
2158
  var AGGREGATE_SQL_NAMES = {
1176
2159
  ["AVERAGE" /* AVERAGE */]: "AVG"
1177
2160
  };
2161
+ var GUARDED_COMPARISONS = /* @__PURE__ */ new Set([
2162
+ "LESS_THAN" /* LESS_THAN */,
2163
+ "GREATER_THAN" /* GREATER_THAN */,
2164
+ "LESS_THAN_OR_EQUAL_TO" /* LESS_THAN_OR_EQUAL_TO */,
2165
+ "GREATER_THAN_OR_EQUAL_TO" /* GREATER_THAN_OR_EQUAL_TO */,
2166
+ "LIKE" /* LIKE */
2167
+ ]);
1178
2168
  var WINDOW_SQL_NAMES = {
1179
2169
  ["ROW_NUMBER" /* ROW_NUMBER */]: "ROW_NUMBER",
1180
2170
  ["RANK" /* RANK */]: "RANK",
@@ -1206,31 +2196,63 @@ var SCALAR_SQL_NAMES = {
1206
2196
  ["RIGHT" /* RIGHT */]: "RIGHT",
1207
2197
  ["REPEAT" /* REPEAT */]: "REPEAT",
1208
2198
  ["REPLACE" /* REPLACE */]: "REPLACE",
1209
- ["SUBSTRING" /* SUBSTRING */]: "SUBSTRING"
2199
+ ["SUBSTRING" /* SUBSTRING */]: "SUBSTRING",
2200
+ ["COALESCE" /* COALESCE */]: "COALESCE"
1210
2201
  };
1211
- function constantValueString(op) {
1212
- if (op instanceof StringConstantOperation) return "'" + op.value + "'";
2202
+ function constantValueString(op, d) {
2203
+ if (op instanceof StringConstantOperation) return d.stringLiteral(op.value);
1213
2204
  if (op instanceof DateConstantOperation) return sqlFormatDate(op.value);
1214
2205
  if (op instanceof DateTimeConstantOperation) return sqlFormatDatetime(op.value);
1215
2206
  if (op instanceof IntegerConstantOperation) return String(op.value);
1216
- if (op instanceof FloatConstantOperation) return String(op.value);
2207
+ if (op instanceof FloatConstantOperation) return Number.isInteger(op.value) && Math.abs(op.value) < 1e21 ? op.value.toFixed(1) : String(op.value);
1217
2208
  if (op instanceof BooleanConstantOperation) return op.value ? "TRUE" : "FALSE";
1218
2209
  if (op instanceof DecimalConstantOperation) return String(op.value);
1219
2210
  throw new Error("Unknown constant operation");
1220
2211
  }
1221
- function windowSpecToString(window) {
2212
+ function selectedConstantString(op, d) {
2213
+ if (op instanceof FloatConstantOperation) return "CAST(" + constantValueString(op, d) + " AS " + d.doubleType + ")";
2214
+ if (op instanceof DateTimeConstantOperation) return "TIMESTAMP " + constantValueString(op, d);
2215
+ if (op instanceof DateConstantOperation) return "DATE " + constantValueString(op, d);
2216
+ return constantValueString(op, d);
2217
+ }
2218
+ function windowSpecToString(window, d) {
1222
2219
  const parts = [];
1223
2220
  if (window.partitionBy.length > 0) {
1224
- parts.push("PARTITION BY " + window.partitionBy.map((p) => sqlOperationToString(p)).join(", "));
2221
+ parts.push("PARTITION BY " + window.partitionBy.map((p) => sqlOperationToString(p, d)).join(", "));
1225
2222
  }
1226
2223
  if (window.orderBy.length > 0) {
1227
2224
  parts.push("ORDER BY " + window.orderBy.map(
1228
- (s) => sqlOperationToString(s.column) + (s.direction === "ASC" /* ASC */ ? " ASC" : " DESC")
2225
+ (s) => sqlOperationToString(s.column, d) + d.orderDirection(s.direction)
1229
2226
  ).join(", "));
1230
2227
  }
1231
2228
  return "OVER (" + parts.join(" ") + ")";
1232
2229
  }
1233
- function sqlOperationToString(operation) {
2230
+ var ARITHMETIC_PRECEDENCE = {
2231
+ ["+" /* ADD */]: 1,
2232
+ ["-" /* SUBTRACT */]: 1,
2233
+ ["*" /* MULTIPLY */]: 2,
2234
+ ["/" /* DIVIDE */]: 2
2235
+ };
2236
+ function arithmeticNeedsParentheses(parent, child, onRight) {
2237
+ if (!(child instanceof ArithmeticOperation)) return false;
2238
+ const here = ARITHMETIC_PRECEDENCE[parent.operator];
2239
+ const there = ARITHMETIC_PRECEDENCE[child.operator];
2240
+ return there < here || there === here && onRight && (parent.operator === "-" /* SUBTRACT */ || parent.operator === "/" /* DIVIDE */);
2241
+ }
2242
+ function operandString(operation, d) {
2243
+ return operation instanceof ConstantOperation ? constantValueString(operation, d) : sqlOperationToString(operation, d);
2244
+ }
2245
+ var RenderedSql = class extends RelationalOperationElement {
2246
+ constructor(sql) {
2247
+ super();
2248
+ this.sql = sql;
2249
+ }
2250
+ sql;
2251
+ };
2252
+ function isCondition(op) {
2253
+ 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;
2254
+ }
2255
+ function sqlOperationToString(operation, d) {
1234
2256
  if (operation instanceof TableAliasColumn) {
1235
2257
  return operation.tableAlias.alias + "." + operation.column.name;
1236
2258
  }
@@ -1238,145 +2260,279 @@ function sqlOperationToString(operation) {
1238
2260
  const fn = WINDOW_SQL_NAMES[operation.func];
1239
2261
  const parts = [];
1240
2262
  if (operation.element !== null && operation.element !== void 0) {
1241
- parts.push(sqlOperationToString(operation.element));
2263
+ parts.push(sqlOperationToString(operation.element, d));
1242
2264
  }
1243
2265
  if (operation.secondArg !== void 0) parts.push(String(operation.secondArg));
1244
2266
  for (const arg of operation.extraArgs) {
1245
- parts.push(typeof arg === "string" ? "'" + arg + "'" : String(arg));
2267
+ parts.push(typeof arg === "string" ? d.stringLiteral(arg) : String(arg));
1246
2268
  }
1247
2269
  const sql = fn + "(" + parts.join(", ") + ")";
1248
- return sql + (operation.window !== void 0 ? " " + windowSpecToString(operation.window) : " OVER ()");
2270
+ return sql + (operation.window !== void 0 ? " " + windowSpecToString(operation.window, d) : " OVER ()");
2271
+ }
2272
+ if (operation instanceof AggregateOperation && operation.operator === "COUNT_DISTINCT" /* COUNT_DISTINCT */) {
2273
+ return "COUNT(DISTINCT " + sqlOperationToString(operation.element, d) + ")";
2274
+ }
2275
+ if (operation instanceof AggregateOperation && operation.operator === "STRING_AGG" /* STRING_AGG */) {
2276
+ let sql = d.stringAgg(
2277
+ sqlOperationToString(operation.element, d),
2278
+ d.stringLiteral(operation.separator ?? ""),
2279
+ operation.orderBy.map((s) => sqlOperationToString(s.column, d) + d.orderDirection(s.direction))
2280
+ );
2281
+ if (operation.window !== void 0) sql += " " + windowSpecToString(operation.window, d);
2282
+ return sql;
1249
2283
  }
1250
2284
  if (operation instanceof AggregateOperation) {
1251
2285
  const fn = AGGREGATE_SQL_NAMES[operation.operator] ?? operation.operator;
1252
- let sql = fn + "(" + sqlOperationToString(operation.element) + ")";
2286
+ let sql = fn + "(" + sqlOperationToString(operation.element, d) + ")";
1253
2287
  if (operation.window !== void 0) {
1254
- sql += " " + windowSpecToString(operation.window);
2288
+ sql += " " + windowSpecToString(operation.window, d);
1255
2289
  }
1256
2290
  return sql;
1257
2291
  }
1258
2292
  if (operation instanceof ScalarFunctionOperation) {
1259
2293
  const fn = SCALAR_SQL_NAMES[operation.func];
1260
- const parts = [sqlOperationToString(operation.element)];
2294
+ const parts = [sqlOperationToString(operation.element, d)];
1261
2295
  if (operation.secondArg !== void 0) parts.push(String(operation.secondArg));
1262
2296
  for (const arg of operation.extraArgs) {
1263
- parts.push(typeof arg === "string" ? "'" + arg + "'" : String(arg));
2297
+ parts.push(typeof arg === "string" ? d.stringLiteral(arg) : String(arg));
1264
2298
  }
1265
2299
  return fn + "(" + parts.join(", ") + ")";
1266
2300
  }
1267
2301
  if (operation instanceof DateExtractOperation) {
1268
- return "EXTRACT(" + operation.part + " FROM " + sqlOperationToString(operation.element) + ")";
2302
+ const element = sqlOperationToString(operation.element, d);
2303
+ return operation.part === "DOW" /* DOW */ ? d.dayOfWeek(element) : "EXTRACT(" + operation.part + " FROM " + element + ")";
2304
+ }
2305
+ if (operation instanceof DateTruncOperation) {
2306
+ return "CAST(DATE_TRUNC('" + operation.part.toLowerCase() + "', " + sqlOperationToString(operation.element, d) + ") AS DATE)";
1269
2307
  }
1270
2308
  if (operation instanceof DateArithmeticOperation) {
1271
2309
  const op = operation.isAdd ? "+" : "-";
1272
- return sqlOperationToString(operation.element) + " " + op + " INTERVAL " + operation.n + " " + operation.unit;
2310
+ return sqlOperationToString(operation.element, d) + " " + op + " INTERVAL '" + operation.n + "' " + operation.unit;
1273
2311
  }
1274
2312
  if (operation instanceof DateDiffOperation) {
1275
- const otherSql = sqlFormatDatetime(operation.other);
1276
- return "DATE_DIFF('" + operation.unit.toLowerCase() + "', " + sqlOperationToString(operation.element) + ", " + otherSql + ")";
2313
+ return d.dateDiff(operation.unit, sqlOperationToString(operation.element, d), "TIMESTAMP " + sqlFormatDatetime(operation.other));
1277
2314
  }
1278
2315
  if (operation instanceof CountAllOperation) return "COUNT(*)";
2316
+ if (operation instanceof ConstantOperation) return selectedConstantString(operation, d);
2317
+ if (operation instanceof RenderedSql) return operation.sql;
2318
+ if (operation instanceof ConcatOperation) return operation.parts.map((part) => operandString(part, d)).join(" || ");
2319
+ if (operation instanceof ArithmeticOperation) {
2320
+ let left = operandString(operation.left, d);
2321
+ let right = operandString(operation.right, d);
2322
+ if (arithmeticNeedsParentheses(operation, operation.left, false)) left = "(" + left + ")";
2323
+ if (arithmeticNeedsParentheses(operation, operation.right, true)) right = "(" + right + ")";
2324
+ return left + " " + operation.operator + " " + right;
2325
+ }
1279
2326
  if (operation instanceof Alias) {
1280
- return sqlOperationToString(operation.element) + ' AS "' + operation.name + '"';
2327
+ return sqlOperationToString(operation.element, d) + " AS " + d.quoteIdentifier(operation.name);
1281
2328
  }
1282
2329
  throw new TypeError("Unknown operation in sqlOperationToString: " + operation.constructor.name);
1283
2330
  }
2331
+ function embeddedOf(ref) {
2332
+ return ref instanceof Attribute ? ref.embedded() : ref.embedded;
2333
+ }
1284
2334
  function findColumn(operation) {
1285
2335
  if (operation instanceof UnaryOperation) return findColumn(operation.element);
1286
2336
  if (operation instanceof ColumnWithJoin) return operation;
1287
2337
  throw new TypeError("Cannot find ColumnWithJoin in: " + operation.constructor.name);
1288
2338
  }
1289
- function openEndClause(endAttr, value, infiniteDatetime) {
1290
- const gtOp = endAttr instanceof DateTimeAttribute ? endAttr.gt(value) : endAttr.gt(value);
2339
+ function openEndClause(endAttr, value2, infiniteDatetime) {
2340
+ const gtOp = endAttr instanceof DateTimeAttribute ? endAttr.gt(value2) : endAttr.gt(value2);
1291
2341
  if (infiniteDatetime === void 0) {
1292
2342
  const colRef = new ColumnWithJoin(endAttr.column(), endAttr.parent());
1293
2343
  return new LogicalOperation(gtOp, "OR" /* OR */, new IsNullOperation(colRef));
1294
2344
  }
1295
2345
  return gtOp;
1296
2346
  }
2347
+ function andAll(ops) {
2348
+ const present = ops.filter((op) => op !== null);
2349
+ if (present.length === 0) return null;
2350
+ return present.slice(1).reduce((left, right) => new LogicalOperation(left, "AND" /* AND */, right), present[0]);
2351
+ }
2352
+ function processingPredicate(mc, processingDatetime, joinNode) {
2353
+ if (!(mc instanceof ProcessingTemporalColumns || mc instanceof BusinessDateAndProcessingTemporalColumns || mc instanceof BiTemporalColumns) || processingDatetime === null) return null;
2354
+ const startAt = new DateTimeAttribute("start_at", mc.startAtColumn.name, mc.startAtColumn.type, mc.startAtColumn.table.qualifiedName, joinNode);
2355
+ const endAt = new DateTimeAttribute("end_at", mc.endAtColumn.name, mc.endAtColumn.type, mc.endAtColumn.table.qualifiedName, joinNode);
2356
+ return new LogicalOperation(startAt.lte(processingDatetime), "AND" /* AND */, openEndClause(endAt, processingDatetime, mc.infiniteDatetime));
2357
+ }
2358
+ function businessPredicate(mc, businessDate, joinNode) {
2359
+ if (businessDate === null) return null;
2360
+ if (mc instanceof BiTemporalColumns) {
2361
+ const dateFrom = new DateAttribute("business_date_from", mc.businessDateFromColumn.name, mc.businessDateFromColumn.type, mc.businessDateFromColumn.table.qualifiedName, joinNode);
2362
+ const dateTo = new DateAttribute("business_date_to", mc.businessDateToColumn.name, mc.businessDateToColumn.type, mc.businessDateToColumn.table.qualifiedName, joinNode);
2363
+ return new LogicalOperation(dateFrom.lte(businessDate), "AND" /* AND */, openEndClause(dateTo, businessDate, mc.infiniteDatetime));
2364
+ }
2365
+ if (mc instanceof BusinessDateAndProcessingTemporalColumns || mc instanceof SingleBusinessDateColumn) {
2366
+ const businessAtt = new DateAttribute("business_date", mc.businessDateColumn.name, mc.businessDateColumn.type, mc.businessDateColumn.table.qualifiedName, joinNode);
2367
+ return businessAtt.eq(businessDate);
2368
+ }
2369
+ return null;
2370
+ }
2371
+ function businessRangePredicate(mc, businessDateFrom, businessDateTo, joinNode) {
2372
+ if (businessDateFrom === null || businessDateTo === null) return null;
2373
+ if (mc instanceof BiTemporalColumns) {
2374
+ const dateFrom = new DateAttribute("business_date_from", mc.businessDateFromColumn.name, mc.businessDateFromColumn.type, mc.businessDateFromColumn.table.qualifiedName, joinNode);
2375
+ const dateTo = new DateAttribute("business_date_to", mc.businessDateToColumn.name, mc.businessDateToColumn.type, mc.businessDateToColumn.table.qualifiedName, joinNode);
2376
+ return new LogicalOperation(dateFrom.lte(businessDateTo), "AND" /* AND */, openEndClause(dateTo, businessDateFrom, mc.infiniteDatetime));
2377
+ }
2378
+ if (mc instanceof BusinessDateAndProcessingTemporalColumns || mc instanceof SingleBusinessDateColumn) {
2379
+ const businessAtt = new DateAttribute("business_date", mc.businessDateColumn.name, mc.businessDateColumn.type, mc.businessDateColumn.table.qualifiedName, joinNode);
2380
+ return new LogicalOperation(businessAtt.gte(businessDateFrom), "AND" /* AND */, businessAtt.lte(businessDateTo));
2381
+ }
2382
+ return null;
2383
+ }
1297
2384
  function buildMilestoningFilterOperation(businessDate, processingDatetime, table, joinNode) {
1298
2385
  const mc = table.milestoningColumns;
2386
+ return andAll([processingPredicate(mc, processingDatetime, joinNode), businessPredicate(mc, businessDate, joinNode)]);
2387
+ }
2388
+ function buildMilestoningFilterOperationForDateRange(businessDateFrom, businessDateTo, processingDatetime, table, joinNode) {
2389
+ const mc = table.milestoningColumns;
2390
+ return andAll([
2391
+ processingPredicate(mc, processingDatetime, joinNode),
2392
+ businessRangePredicate(mc, businessDateFrom, businessDateTo, joinNode)
2393
+ ]);
2394
+ }
2395
+ function walkRefs(op, visit, visitExists) {
2396
+ if (op === null || op === void 0) return;
2397
+ if (op instanceof Attribute || op instanceof ColumnWithJoin) {
2398
+ visit(op);
2399
+ } else if (op instanceof ExistsOperation || op instanceof NotExistsOperation) {
2400
+ visitExists?.(op.node);
2401
+ } else if (op instanceof AggregateOperation) {
2402
+ walkRefs(op.element, visit, visitExists);
2403
+ if (op.window) walkWindowSpec(op.window, visit, visitExists);
2404
+ for (const sort of op.orderBy) walkRefs(sort, visit, visitExists);
2405
+ } else if (op instanceof WindowFunctionOperation) {
2406
+ if (op.element) walkRefs(op.element, visit, visitExists);
2407
+ if (op.window) walkWindowSpec(op.window, visit, visitExists);
2408
+ } 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) {
2409
+ walkRefs(op.element, visit, visitExists);
2410
+ } else if (op instanceof Alias) {
2411
+ walkRefs(op.element, visit, visitExists);
2412
+ } else if (op instanceof ConcatOperation) {
2413
+ for (const part of op.parts) walkRefs(part, visit, visitExists);
2414
+ } else if (op instanceof ArithmeticOperation) {
2415
+ walkRefs(op.left, visit, visitExists);
2416
+ walkRefs(op.right, visit, visitExists);
2417
+ } else if (op instanceof CaseOperation) {
2418
+ walkRefs(op.condition, visit, visitExists);
2419
+ walkRefs(op.then, visit, visitExists);
2420
+ walkRefs(op.fallback, visit, visitExists);
2421
+ } else if (op instanceof SortOperation) {
2422
+ walkRefs(op.column, visit, visitExists);
2423
+ } else if (op instanceof LogicalOperation || op instanceof ComparisonOperation) {
2424
+ walkRefs(op.left, visit, visitExists);
2425
+ walkRefs(op.right, visit, visitExists);
2426
+ }
2427
+ }
2428
+ function walkWindowSpec(window, visit, visitExists) {
2429
+ for (const p of window.partitionBy) walkRefs(p, visit, visitExists);
2430
+ for (const s of window.orderBy) walkRefs(s.column, visit, visitExists);
2431
+ }
2432
+ function buildJoinFilterFromProperties(node, filter, columnMap) {
1299
2433
  const ops = [];
1300
- if (mc instanceof BiTemporalColumns) {
1301
- if (businessDate !== null) {
1302
- const dateFrom = new DateAttribute("business_date_from", mc.businessDateFromColumn.name, mc.businessDateFromColumn.type, mc.businessDateFromColumn.table.qualifiedName, joinNode);
1303
- const dateTo = new DateAttribute("business_date_to", mc.businessDateToColumn.name, mc.businessDateToColumn.type, mc.businessDateToColumn.table.qualifiedName, joinNode);
1304
- ops.push(new LogicalOperation(dateFrom.lte(businessDate), "AND" /* AND */, openEndClause(dateTo, businessDate, mc.infiniteDatetime)));
1305
- }
1306
- if (processingDatetime !== null) {
1307
- const startAt = new DateTimeAttribute("start_at", mc.startAtColumn.name, mc.startAtColumn.type, mc.startAtColumn.table.qualifiedName, joinNode);
1308
- const endAt = new DateTimeAttribute("end_at", mc.endAtColumn.name, mc.endAtColumn.type, mc.endAtColumn.table.qualifiedName, joinNode);
1309
- ops.push(new LogicalOperation(startAt.lte(processingDatetime), "AND" /* AND */, openEndClause(endAt, processingDatetime, mc.infiniteDatetime)));
2434
+ for (const [key, value2] of Object.entries(filter)) {
2435
+ const entry = columnMap[key];
2436
+ if (entry === void 0) {
2437
+ throw new Error(`Unknown property '${key}' \u2014 valid properties are: ${Object.keys(columnMap).sort().join(", ")}`);
1310
2438
  }
1311
- } else if (mc instanceof BusinessDateAndProcessingTemporalColumns) {
1312
- if (businessDate !== null) {
1313
- const businessAtt = new DateAttribute("business_date", mc.businessDateColumn.name, mc.businessDateColumn.type, mc.businessDateColumn.table.qualifiedName, joinNode);
1314
- ops.push(businessAtt.eq(businessDate));
1315
- }
1316
- if (processingDatetime !== null) {
1317
- const startAt = new DateTimeAttribute("start_at", mc.startAtColumn.name, mc.startAtColumn.type, mc.startAtColumn.table.qualifiedName, joinNode);
1318
- const endAt = new DateTimeAttribute("end_at", mc.endAtColumn.name, mc.endAtColumn.type, mc.endAtColumn.table.qualifiedName, joinNode);
1319
- ops.push(new LogicalOperation(startAt.lte(processingDatetime), "AND" /* AND */, openEndClause(endAt, processingDatetime, mc.infiniteDatetime)));
1320
- }
1321
- } else if (mc instanceof ProcessingTemporalColumns && processingDatetime !== null) {
1322
- const startAt = new DateTimeAttribute("start_at", mc.startAtColumn.name, mc.startAtColumn.type, mc.startAtColumn.table.qualifiedName, joinNode);
1323
- const endAt = new DateTimeAttribute("end_at", mc.endAtColumn.name, mc.endAtColumn.type, mc.endAtColumn.table.qualifiedName, joinNode);
1324
- ops.push(new LogicalOperation(startAt.lte(processingDatetime), "AND" /* AND */, openEndClause(endAt, processingDatetime, mc.infiniteDatetime)));
1325
- } else if (mc instanceof SingleBusinessDateColumn && businessDate !== null) {
1326
- const businessAtt = new DateAttribute("business_date", mc.businessDateColumn.name, mc.businessDateColumn.type, mc.businessDateColumn.table.qualifiedName, joinNode);
1327
- ops.push(businessAtt.eq(businessDate));
1328
- }
1329
- if (ops.length === 0) return null;
1330
- if (ops.length === 1) return ops[0];
1331
- return new LogicalOperation(ops[0], "AND" /* AND */, ops[1]);
2439
+ const [name, type, table] = entry;
2440
+ const col = new ColumnWithJoin(new Column(name, type, table), node);
2441
+ let constant2;
2442
+ if (typeof value2 === "boolean") constant2 = new BooleanConstantOperation(value2);
2443
+ else if (typeof value2 === "number") constant2 = Number.isInteger(value2) ? new IntegerConstantOperation(value2) : new FloatConstantOperation(value2);
2444
+ else if (value2 instanceof Date) constant2 = type.toUpperCase() === "DATE" ? new DateConstantOperation(value2) : new DateTimeConstantOperation(value2);
2445
+ else constant2 = new StringConstantOperation(String(value2));
2446
+ ops.push(new ComparisonOperation(col, "EQUAL" /* EQUAL */, constant2));
2447
+ }
2448
+ return ops.reduce((acc, op) => new LogicalOperation(acc, "AND" /* AND */, op));
1332
2449
  }
1333
- function buildQueryOperation(businessDate, processingDatetime, columns, table, op, orderBy = [], groupBy = [], limit) {
2450
+ function withAncestors(nodes) {
2451
+ const ordered = /* @__PURE__ */ new Set();
2452
+ const add = (node) => {
2453
+ if (ordered.has(node)) return;
2454
+ if (node.parent !== void 0) add(node.parent);
2455
+ ordered.add(node);
2456
+ };
2457
+ for (const node of nodes) add(node);
2458
+ return [...ordered];
2459
+ }
2460
+ function buildQueryOperation(businessDate, processingDatetime, columns, table, op, orderBy = [], groupBy = [], limit, businessDateTo = null, distinct = false, offset, having) {
1334
2461
  let filter = op;
2462
+ const milestoningFor = (t, node) => businessDateTo !== null ? buildMilestoningFilterOperationForDateRange(businessDate, businessDateTo, processingDatetime, t, node) : buildMilestoningFilterOperation(businessDate, processingDatetime, t, node);
1335
2463
  if (table instanceof MilestonedTable) {
1336
- const milestonedOp = buildMilestoningFilterOperation(businessDate, processingDatetime, table);
2464
+ const milestonedOp = milestoningFor(table);
1337
2465
  if (milestonedOp !== null) {
1338
2466
  filter = filter instanceof NoOperation ? milestonedOp : new LogicalOperation(filter, "AND" /* AND */, milestonedOp);
1339
2467
  }
1340
2468
  }
1341
- const requiredJoins = [];
1342
- const requiredJoinIds = /* @__PURE__ */ new Set();
1343
- for (const col of columns) {
1344
- if (col instanceof CountAllOperation) continue;
1345
- let node;
1346
- if (col instanceof Attribute) {
1347
- node = col.parent();
1348
- } else {
1349
- node = findColumn(col).parent;
2469
+ const requiredJoins = /* @__PURE__ */ new Set();
2470
+ const collect = (ref) => {
2471
+ const parent = ref instanceof Attribute ? ref.parent() : ref.parent;
2472
+ if (parent !== void 0) requiredJoins.add(parent);
2473
+ };
2474
+ const collectNode = (node) => requiredJoins.add(node);
2475
+ for (const col of columns) walkRefs(col, collect, collectNode);
2476
+ for (const expr of groupBy) walkRefs(expr, collect, collectNode);
2477
+ for (const sort of orderBy) walkRefs(sort, collect, collectNode);
2478
+ walkRefs(op, collect, collectNode);
2479
+ const joinFilters = /* @__PURE__ */ new Map();
2480
+ for (const node of withAncestors(requiredJoins)) {
2481
+ let combined;
2482
+ if (Object.keys(node.join.filterKwargs).length > 0) {
2483
+ combined = buildJoinFilterFromProperties(node, node.join.filterKwargs, node.join.filterColumnMap);
1350
2484
  }
1351
- if (node !== void 0 && !requiredJoinIds.has(node)) {
1352
- requiredJoinIds.add(node);
1353
- requiredJoins.push(node);
1354
- }
1355
- }
1356
- for (const node of requiredJoins) {
1357
2485
  if (node.join.target instanceof MilestonedTable) {
1358
- const milestonedOp = buildMilestoningFilterOperation(businessDate, processingDatetime, node.join.target, node);
2486
+ const milestonedOp = milestoningFor(node.join.target, node);
1359
2487
  if (milestonedOp !== null) {
1360
- node.join.filter = milestonedOp;
2488
+ combined = combined === void 0 ? milestonedOp : new LogicalOperation(combined, "AND" /* AND */, milestonedOp);
1361
2489
  }
1362
2490
  }
2491
+ if (combined !== void 0) joinFilters.set(node, combined);
1363
2492
  }
1364
- return new SelectOperation(columns, filter, orderBy, groupBy, limit, table);
2493
+ return new SelectOperation(columns, filter, orderBy, groupBy, limit, table, joinFilters, distinct, offset, having);
2494
+ }
2495
+ function needsParentheses(parent, child) {
2496
+ return child instanceof LogicalOperation && child.operator !== parent.operator;
2497
+ }
2498
+ function reachesToMany(op) {
2499
+ if (op instanceof ColumnWithJoin) {
2500
+ for (let node = op.parent; node !== void 0; node = node.parent) if (node.many) return true;
2501
+ return false;
2502
+ }
2503
+ if (op instanceof UnaryOperation || op instanceof Alias) return reachesToMany(op.element);
2504
+ if (op instanceof BinaryOperation) return reachesToMany(op.left) || reachesToMany(op.right);
2505
+ return false;
2506
+ }
2507
+ function unaliased(op) {
2508
+ return op instanceof Alias ? unaliased(op.element) : op;
1365
2509
  }
1366
2510
  var SQLQueryGenerator = class {
2511
+ constructor(_dialect) {
2512
+ this._dialect = _dialect;
2513
+ }
2514
+ _dialect;
1367
2515
  _select = [];
1368
2516
  _from = /* @__PURE__ */ new Set();
1369
2517
  _join = [];
1370
2518
  _groupByParts = [];
2519
+ _having = "";
1371
2520
  _orderByParts = [];
1372
2521
  _where = "";
1373
2522
  _limit;
2523
+ _distinct = false;
2524
+ _offset;
1374
2525
  _tableAliasIncr = 0;
1375
2526
  _tableAliasesByTable = /* @__PURE__ */ new Map();
1376
2527
  _addedJoinIds = /* @__PURE__ */ new Set();
1377
2528
  _rootTable;
2529
+ _elidableNodes = /* @__PURE__ */ new Set();
2530
+ _joinFilters = /* @__PURE__ */ new Map();
2531
+ _guardOptional = true;
1378
2532
  generate(select) {
1379
2533
  this._rootTable = select.table;
2534
+ this._elidableNodes = this._computeElidableNodes(select);
2535
+ this._joinFilters = select.joinFilters;
1380
2536
  if (this._rootTable !== void 0) {
1381
2537
  this._from.add(this._tableAliasForTable(this._rootTable.qualifiedName, this._rootTable.qualifiedName));
1382
2538
  }
@@ -1389,46 +2545,104 @@ var SQLQueryGenerator = class {
1389
2545
  for (const node of groupByJoins.values()) {
1390
2546
  this._addJoin(node);
1391
2547
  }
1392
- this._groupByParts = select.groupBy.map((a) => this._attrToColString(a));
2548
+ this._groupByParts = this._groupByPartsFor(select);
2549
+ this._having = "";
2550
+ if (select.having !== void 0) {
2551
+ const havingJoins = /* @__PURE__ */ new Map();
2552
+ this._collectRequiredJoins(select.having, havingJoins);
2553
+ for (const node of havingJoins.values()) this._addJoin(node);
2554
+ this._guardOptional = false;
2555
+ try {
2556
+ this._having = this.buildFilter(select.having);
2557
+ } finally {
2558
+ this._guardOptional = true;
2559
+ }
2560
+ }
1393
2561
  this._orderByParts = select.orderBy.map(
1394
- (s) => this.buildFilter(s.column) + (s.direction === "ASC" /* ASC */ ? " ASC" : " DESC")
2562
+ (s) => this._sortExpression(s.column) + this._dialect.orderDirection(s.direction)
1395
2563
  );
1396
2564
  this._limit = select.limit;
2565
+ this._distinct = select.distinct;
2566
+ this._offset = select.offset;
2567
+ }
2568
+ /**
2569
+ * The GROUP BY of a query: the grouping it was given, else the grouping its columns imply.
2570
+ *
2571
+ * Columns that are not aggregated are what the aggregates are computed for, so a query that selects both is
2572
+ * grouped by those columns (as a DataFrame's `groupby(columns).agg(...)` does). An aggregate read through a
2573
+ * to-many association is computed for each row of the root table, as Legend does, so the root's primary key
2574
+ * is grouped by too. A query that selects only aggregates over the root's own columns is not grouped: the
2575
+ * aggregate covers every row. A constant needs no grouping: a bare
2576
+ * `GROUP BY 7` would be a column position.
2577
+ */
2578
+ _groupByPartsFor(select) {
2579
+ if (select.groupBy.length > 0) return select.groupBy.map((a) => this._attrToColString(a));
2580
+ const aggregates = select.display.filter(containsAggregate);
2581
+ if (aggregates.length === 0) return [];
2582
+ const perRoot = select.table !== void 0 && aggregates.some(reachesToMany);
2583
+ const keys = perRoot ? select.table.columns.filter((c) => c.primaryKey).map((c) => this._columnToString(c)) : [];
2584
+ 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));
2585
+ return [.../* @__PURE__ */ new Set([...keys, ...grouped])];
2586
+ }
2587
+ /** What a sort orders by: a column, or an expression such as an aggregate. */
2588
+ _sortExpression(expression) {
2589
+ if (expression instanceof ColumnWithJoin || expression instanceof Attribute) return this.buildFilter(expression);
2590
+ return sqlOperationToString(this._rewriteOperation(expression), this._dialect);
2591
+ }
2592
+ _columnToString(column) {
2593
+ return this._tableAliasForTable(column.owner, column.owner).alias + "." + column.name;
1397
2594
  }
1398
2595
  _attrToColString(attr) {
1399
- const node = attr.parent();
1400
- const ta = this._tableAliasForTable(attr.owner(), node ?? attr.owner());
1401
- return ta.alias + "." + attr.column().name;
2596
+ const { column, parent } = this._resolve(attr);
2597
+ const ta = this._tableAliasForTable(column.owner, parent ?? column.owner);
2598
+ return ta.alias + "." + column.name;
1402
2599
  }
1403
2600
  _collectRequiredJoins(op, out) {
1404
- if (op === null || op === void 0) return;
1405
- if (op instanceof Attribute) {
1406
- const p = op.parent();
1407
- if (p !== void 0) out.set(p, p);
1408
- } else if (op instanceof ColumnWithJoin) {
1409
- if (op.parent !== void 0) out.set(op.parent, op.parent);
1410
- } else if (op instanceof AggregateOperation) {
1411
- this._collectRequiredJoins(op.element, out);
1412
- if (op.window) this._collectRequiredJoins(op.window, out);
1413
- } else if (op instanceof WindowFunctionOperation) {
1414
- if (op.element) this._collectRequiredJoins(op.element, out);
1415
- if (op.window) this._collectRequiredJoinsFromWindowSpec(op.window, out);
1416
- } else if (op instanceof ScalarFunctionOperation || op instanceof DateExtractOperation || op instanceof DateArithmeticOperation || op instanceof DateDiffOperation) {
1417
- this._collectRequiredJoins(op.element, out);
1418
- } else if (op instanceof SortOperation) {
1419
- this._collectRequiredJoins(op.column, out);
1420
- } else if (op instanceof LogicalOperation || op instanceof ComparisonOperation) {
1421
- this._collectRequiredJoins(op.left, out);
1422
- this._collectRequiredJoins(op.right, out);
2601
+ walkRefs(op, (ref) => {
2602
+ const { parent } = this._resolve(ref);
2603
+ if (parent !== void 0) out.set(parent, parent);
2604
+ });
2605
+ }
2606
+ /**
2607
+ * Determine upfront which join nodes can be satisfied entirely by embedded (join-less) alternates:
2608
+ * a node is elidable when every reference to it has an embedded alternate. A reference without one
2609
+ * (e.g. a milestoning column) keeps the join for every reference to that node.
2610
+ */
2611
+ _computeElidableNodes(select) {
2612
+ const refsByNode = /* @__PURE__ */ new Map();
2613
+ const record = (ref) => {
2614
+ const parent = ref instanceof Attribute ? ref.parent() : ref.parent;
2615
+ if (parent === void 0) return;
2616
+ const refs = refsByNode.get(parent);
2617
+ if (refs) refs.push(ref);
2618
+ else refsByNode.set(parent, [ref]);
2619
+ };
2620
+ for (const col of select.display) walkRefs(col, record);
2621
+ for (const expr of select.groupBy) walkRefs(expr, record);
2622
+ for (const sort of select.orderBy) walkRefs(sort, record);
2623
+ walkRefs(select.filter, record);
2624
+ const elidable = /* @__PURE__ */ new Set();
2625
+ for (const [node, refs] of refsByNode) {
2626
+ if (refs.every((ref) => embeddedOf(ref) !== void 0)) elidable.add(node);
1423
2627
  }
1424
- }
1425
- _collectRequiredJoinsFromWindowSpec(window, out) {
1426
- for (const p of window.partitionBy) {
1427
- this._collectRequiredJoins(p, out);
2628
+ return elidable;
2629
+ }
2630
+ /** The (column, parent) a reference should resolve to, substituting its embedded alternate when its join is elided. */
2631
+ _resolve(ref) {
2632
+ const column = ref instanceof Attribute ? ref.column() : ref.column;
2633
+ const parent = ref instanceof Attribute ? ref.parent() : ref.parent;
2634
+ if (parent !== void 0 && this._elidableNodes.has(parent)) {
2635
+ const embedded = embeddedOf(ref);
2636
+ if (embedded !== void 0) return this._resolve(embedded);
1428
2637
  }
1429
- for (const s of window.orderBy) {
1430
- this._collectRequiredJoins(s.column, out);
2638
+ return { column, parent };
2639
+ }
2640
+ /** Whether the value `op` reads can be missing, in the join or embedded form that is used for it. */
2641
+ _resolveOptional(op) {
2642
+ if (op.parent !== void 0 && this._elidableNodes.has(op.parent) && op.embedded !== void 0) {
2643
+ return this._resolveOptional(op.embedded);
1431
2644
  }
2645
+ return op.optional;
1432
2646
  }
1433
2647
  _rewriteWindowSpec(window) {
1434
2648
  const partitionBy = window.partitionBy.map((p) => this._rewriteOperation(p));
@@ -1436,13 +2650,9 @@ var SQLQueryGenerator = class {
1436
2650
  return new WindowSpecification(partitionBy, orderBy);
1437
2651
  }
1438
2652
  _rewriteOperation(op) {
1439
- if (op instanceof Attribute) {
1440
- const ta = this._tableAliasForTable(op.owner(), op.parent() ?? op.owner());
1441
- return new TableAliasColumn(op.column(), ta);
1442
- }
1443
- if (op instanceof ColumnWithJoin) {
1444
- const ta = op.parent !== void 0 ? this._tableAliasForTable(op.column.owner, op.parent) : this._tableAliasForTable(op.column.owner, op.column.owner);
1445
- return new TableAliasColumn(op.column, ta);
2653
+ if (op instanceof Attribute || op instanceof ColumnWithJoin) {
2654
+ const { column, parent } = this._resolve(op);
2655
+ return new TableAliasColumn(column, this._tableAliasForTable(column.owner, parent ?? column.owner));
1446
2656
  }
1447
2657
  if (op instanceof Column) {
1448
2658
  const ta = this._tableAliasForTable(op.owner, op.owner);
@@ -1453,9 +2663,17 @@ var SQLQueryGenerator = class {
1453
2663
  this._rewriteOperation(op.element),
1454
2664
  op.operator,
1455
2665
  op.displayName,
1456
- op.window ? this._rewriteWindowSpec(op.window) : void 0
2666
+ op.window ? this._rewriteWindowSpec(op.window) : void 0,
2667
+ op.separator,
2668
+ op.orderBy.map((s) => new SortOperation(this._rewriteOperation(s.column), s.direction))
1457
2669
  );
1458
2670
  }
2671
+ if (op instanceof CaseOperation) return new RenderedSql(this._caseSql(op));
2672
+ if (isCondition(op)) return new RenderedSql(this.buildFilter(op));
2673
+ if (op instanceof ConcatOperation) return new ConcatOperation(op.parts.map((part) => this._rewriteOperation(part)), op.displayName);
2674
+ if (op instanceof ArithmeticOperation) {
2675
+ return new ArithmeticOperation(this._rewriteOperation(op.left), op.operator, this._rewriteOperation(op.right), op.displayName);
2676
+ }
1459
2677
  if (op instanceof WindowFunctionOperation) {
1460
2678
  const el = op.element ? this._rewriteOperation(op.element) : null;
1461
2679
  return new WindowFunctionOperation(
@@ -1473,6 +2691,9 @@ var SQLQueryGenerator = class {
1473
2691
  if (op instanceof DateExtractOperation) {
1474
2692
  return new DateExtractOperation(this._rewriteOperation(op.element), op.part, op.displayName);
1475
2693
  }
2694
+ if (op instanceof DateTruncOperation) {
2695
+ return new DateTruncOperation(this._rewriteOperation(op.element), op.part, op.displayName);
2696
+ }
1476
2697
  if (op instanceof DateArithmeticOperation) {
1477
2698
  return new DateArithmeticOperation(this._rewriteOperation(op.element), op.n, op.unit, op.isAdd, op.displayName);
1478
2699
  }
@@ -1481,6 +2702,13 @@ var SQLQueryGenerator = class {
1481
2702
  }
1482
2703
  return op;
1483
2704
  }
2705
+ /** The CASE written out now: its condition can only be rendered while the query is being built. */
2706
+ _caseSql(op) {
2707
+ const branch = (value2) => value2 instanceof ConstantOperation ? constantValueString(value2, this._dialect) : sqlOperationToString(this._rewriteOperation(value2), this._dialect);
2708
+ let sql = "CASE WHEN " + this.buildFilter(op.condition) + " THEN " + branch(op.then);
2709
+ if (op.fallback !== void 0) sql += " ELSE " + branch(op.fallback);
2710
+ return sql + " END";
2711
+ }
1484
2712
  processSelect(cols) {
1485
2713
  const requiredJoins = /* @__PURE__ */ new Map();
1486
2714
  for (const col of cols) {
@@ -1489,88 +2717,96 @@ var SQLQueryGenerator = class {
1489
2717
  for (const node of requiredJoins.values()) {
1490
2718
  this._addJoin(node);
1491
2719
  }
1492
- for (const col of cols) {
1493
- if (col instanceof Attribute) {
1494
- const node = col.parent();
1495
- let ta;
1496
- if (node !== void 0) {
1497
- ta = this._tableAliasForTable(col.owner(), node);
1498
- } else {
1499
- ta = this._tableAliasForTable(col.owner(), col.owner());
1500
- this._from.add(ta);
1501
- }
1502
- this._select.push(new Alias(new TableAliasColumn(col.column(), ta), col.displayName()));
1503
- } else if (col instanceof WindowFunctionOperation) {
1504
- const rewritten = this._rewriteOperation(col);
1505
- let alias;
1506
- if (col.displayName) {
1507
- alias = col.displayName;
1508
- } else if (["ROW_NUMBER" /* ROW_NUMBER */, "RANK" /* RANK */, "DENSE_RANK" /* DENSE_RANK */].includes(col.func)) {
1509
- alias = col.func.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
1510
- } else if (col.element) {
1511
- alias = col.func + " " + findColumn(col).column.name;
1512
- } else {
1513
- alias = col.func;
1514
- }
1515
- this._select.push(new Alias(rewritten, alias));
1516
- } else if (col instanceof AggregateOperation) {
1517
- const colNested = findColumn(col);
1518
- const node = colNested.parent;
1519
- let ta;
1520
- if (node !== void 0) {
1521
- ta = this._tableAliasForTable(colNested.column.owner, node);
1522
- } else {
1523
- ta = this._tableAliasForTable(colNested.column.owner, colNested.column.owner);
1524
- this._from.add(ta);
1525
- }
1526
- const alias = col.displayName ?? col.operator + " " + colNested.column.name;
1527
- const rewrittenAgg = new AggregateOperation(
1528
- new TableAliasColumn(colNested.column, ta),
1529
- col.operator,
1530
- void 0,
1531
- col.window ? this._rewriteWindowSpec(col.window) : void 0
1532
- );
1533
- this._select.push(new Alias(rewrittenAgg, alias));
1534
- } else if (col instanceof ScalarFunctionOperation) {
1535
- const colNested = findColumn(col);
1536
- const node = colNested.parent;
1537
- let ta;
1538
- if (node !== void 0) {
1539
- ta = this._tableAliasForTable(colNested.column.owner, node);
1540
- } else {
1541
- ta = this._tableAliasForTable(colNested.column.owner, colNested.column.owner);
1542
- this._from.add(ta);
1543
- }
1544
- const alias = col.displayName ?? col.func + " " + colNested.column.name;
1545
- this._select.push(new Alias(
1546
- new ScalarFunctionOperation(new TableAliasColumn(colNested.column, ta), col.func, void 0, col.secondArg, col.extraArgs),
1547
- alias
1548
- ));
1549
- } else if (col instanceof DateExtractOperation || col instanceof DateArithmeticOperation || col instanceof DateDiffOperation) {
1550
- const colNested = findColumn(col);
1551
- const node = colNested.parent;
1552
- let ta;
1553
- if (node !== void 0) {
1554
- ta = this._tableAliasForTable(colNested.column.owner, node);
1555
- } else {
1556
- ta = this._tableAliasForTable(colNested.column.owner, colNested.column.owner);
1557
- this._from.add(ta);
1558
- }
1559
- const alias = col.displayName ?? colNested.column.name;
1560
- let aliasedOp;
1561
- if (col instanceof DateExtractOperation) {
1562
- aliasedOp = new DateExtractOperation(new TableAliasColumn(colNested.column, ta), col.part);
1563
- } else if (col instanceof DateArithmeticOperation) {
1564
- aliasedOp = new DateArithmeticOperation(new TableAliasColumn(colNested.column, ta), col.n, col.unit, col.isAdd);
1565
- } else {
1566
- aliasedOp = new DateDiffOperation(new TableAliasColumn(colNested.column, ta), col.other, col.unit);
1567
- }
1568
- this._select.push(new Alias(aliasedOp, alias));
1569
- } else if (col instanceof CountAllOperation) {
1570
- const ta = this._tableAliasForTable(col.table, col.table);
2720
+ for (const col of cols) this._selectColumn(col);
2721
+ }
2722
+ /** Add one column to the select list. An alias reads the column it wraps, then names the entry. */
2723
+ _selectColumn(col) {
2724
+ if (col instanceof Alias) {
2725
+ this._selectColumn(col.element);
2726
+ const read = this._select.pop();
2727
+ this._select.push(new Alias(read.element, col.name));
2728
+ } else if (col instanceof Attribute) {
2729
+ const { column, parent: node } = this._resolve(col);
2730
+ let ta;
2731
+ if (node !== void 0) {
2732
+ ta = this._tableAliasForTable(column.owner, node);
2733
+ } else {
2734
+ ta = this._tableAliasForTable(column.owner, column.owner);
1571
2735
  this._from.add(ta);
1572
- this._select.push(new Alias(col, "Count"));
1573
2736
  }
2737
+ this._select.push(new Alias(new TableAliasColumn(column, ta), col.displayName()));
2738
+ } else if (col instanceof WindowFunctionOperation) {
2739
+ const rewritten = this._rewriteOperation(col);
2740
+ let alias;
2741
+ if (col.displayName) {
2742
+ alias = col.displayName;
2743
+ } else if (["ROW_NUMBER" /* ROW_NUMBER */, "RANK" /* RANK */, "DENSE_RANK" /* DENSE_RANK */].includes(col.func)) {
2744
+ alias = col.func.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
2745
+ } else if (col.element) {
2746
+ alias = col.func + " " + findColumn(col).column.name;
2747
+ } else {
2748
+ alias = col.func;
2749
+ }
2750
+ this._select.push(new Alias(rewritten, alias));
2751
+ } else if (col instanceof AggregateOperation && !(col.element instanceof ColumnWithJoin)) {
2752
+ this._select.push(new Alias(this._rewriteOperation(col), col.displayName ?? "Expression"));
2753
+ } else if (col instanceof AggregateOperation) {
2754
+ const colNested = this._resolve(findColumn(col));
2755
+ const node = colNested.parent;
2756
+ let ta;
2757
+ if (node !== void 0) {
2758
+ ta = this._tableAliasForTable(colNested.column.owner, node);
2759
+ } else {
2760
+ ta = this._tableAliasForTable(colNested.column.owner, colNested.column.owner);
2761
+ this._from.add(ta);
2762
+ }
2763
+ const alias = col.displayName ?? col.operator + " " + findColumn(col).column.name;
2764
+ const rewrittenAgg = new AggregateOperation(
2765
+ new TableAliasColumn(colNested.column, ta),
2766
+ col.operator,
2767
+ void 0,
2768
+ col.window ? this._rewriteWindowSpec(col.window) : void 0,
2769
+ col.separator,
2770
+ col.orderBy.map((s) => new SortOperation(this._rewriteOperation(s.column), s.direction))
2771
+ );
2772
+ this._select.push(new Alias(rewrittenAgg, alias));
2773
+ } else if (col instanceof ScalarFunctionOperation) {
2774
+ this._select.push(new Alias(this._rewriteOperation(col), col.displayName ?? col.func + " " + findColumn(col).column.name));
2775
+ } else if (col instanceof DateExtractOperation || col instanceof DateArithmeticOperation || col instanceof DateDiffOperation) {
2776
+ const colNested = this._resolve(findColumn(col));
2777
+ const node = colNested.parent;
2778
+ let ta;
2779
+ if (node !== void 0) {
2780
+ ta = this._tableAliasForTable(colNested.column.owner, node);
2781
+ } else {
2782
+ ta = this._tableAliasForTable(colNested.column.owner, colNested.column.owner);
2783
+ this._from.add(ta);
2784
+ }
2785
+ const alias = col.displayName ?? findColumn(col).column.name;
2786
+ let aliasedOp;
2787
+ if (col instanceof DateExtractOperation) {
2788
+ aliasedOp = new DateExtractOperation(new TableAliasColumn(colNested.column, ta), col.part);
2789
+ } else if (col instanceof DateArithmeticOperation) {
2790
+ aliasedOp = new DateArithmeticOperation(new TableAliasColumn(colNested.column, ta), col.n, col.unit, col.isAdd);
2791
+ } else {
2792
+ aliasedOp = new DateDiffOperation(new TableAliasColumn(colNested.column, ta), col.other, col.unit);
2793
+ }
2794
+ this._select.push(new Alias(aliasedOp, alias));
2795
+ } else if (col instanceof CaseOperation) {
2796
+ this._select.push(new Alias(this._rewriteOperation(col), col.displayName ?? "Expression"));
2797
+ } else if (col instanceof ConcatOperation) {
2798
+ this._select.push(new Alias(this._rewriteOperation(col), col.displayName ?? "Expression"));
2799
+ } else if (col instanceof ArithmeticOperation) {
2800
+ this._select.push(new Alias(this._rewriteOperation(col), col.displayName ?? "Expression"));
2801
+ } else if (col instanceof DateTruncOperation) {
2802
+ this._select.push(new Alias(this._rewriteOperation(col), col.displayName ?? findColumn(col).column.name));
2803
+ } else if (isCondition(col)) {
2804
+ this._select.push(new Alias(new RenderedSql(this.buildFilter(col)), "Expression"));
2805
+ } else if (col instanceof ConstantOperation) {
2806
+ this._select.push(new Alias(col, "Constant"));
2807
+ } else if (col instanceof CountAllOperation) {
2808
+ if (col.table !== "") this._from.add(this._tableAliasForTable(col.table, col.table));
2809
+ this._select.push(new Alias(col, "Count"));
1574
2810
  }
1575
2811
  }
1576
2812
  _addJoin(node) {
@@ -1584,7 +2820,9 @@ var SQLQueryGenerator = class {
1584
2820
  const sc = new TableAliasColumn(left, srcTa);
1585
2821
  const right = join.right;
1586
2822
  const tc = new TableAliasColumn(right, this._tableAliasForTable(right.owner, node));
1587
- this._join.push(new Join(sc, tc, join.filter));
2823
+ const queryFilter = this._joinFilters.get(node);
2824
+ const filterOp = join.filter !== void 0 && queryFilter !== void 0 ? new LogicalOperation(join.filter, "AND" /* AND */, queryFilter) : join.filter ?? queryFilter;
2825
+ this._join.push(new Join(sc, tc, filterOp));
1588
2826
  }
1589
2827
  buildFilter(op) {
1590
2828
  if (op instanceof NoOperation) return "";
@@ -1596,29 +2834,53 @@ var SQLQueryGenerator = class {
1596
2834
  const col = op.node.join.right;
1597
2835
  return this.buildFilter(new ColumnWithJoin(col, op.node)) + " IS NULL";
1598
2836
  }
2837
+ if (op instanceof InOperation) {
2838
+ return this.buildFilter(op.element) + " IN (" + op.values.map((value2) => constantValueString(value2, this._dialect)).join(", ") + ")";
2839
+ }
2840
+ if (op instanceof NotInOperation) {
2841
+ const column = this.buildFilter(op.element);
2842
+ const sql = column + " NOT IN (" + op.values.map((value2) => constantValueString(value2, this._dialect)).join(", ") + ")";
2843
+ if (op.element instanceof ColumnWithJoin && this._resolve(op.element).column.nullable) {
2844
+ return "(" + sql + " OR " + column + " IS NULL)";
2845
+ }
2846
+ return sql;
2847
+ }
1599
2848
  if (op instanceof IsNotNullOperation) return this.buildFilter(op.element) + " IS NOT NULL";
1600
2849
  if (op instanceof IsNullOperation) return this.buildFilter(op.element) + " IS NULL";
1601
2850
  if (op instanceof LogicalOperation) {
1602
2851
  let left = this.buildFilter(op.left);
1603
2852
  let right = this.buildFilter(op.right);
1604
- if (op.left instanceof LogicalOperation) left = "(" + left + ")";
1605
- if (op.right instanceof LogicalOperation) right = "(" + right + ")";
2853
+ if (needsParentheses(op, op.left)) left = "(" + left + ")";
2854
+ if (needsParentheses(op, op.right)) right = "(" + right + ")";
1606
2855
  return left + LOGICAL_OPERATOR_STR[op.operator] + right;
1607
2856
  }
1608
2857
  if (op instanceof ComparisonOperation) {
1609
- return this.buildFilter(op.left) + COMPARISON_OPERATOR_STR[op.operator] + this.buildFilter(op.right);
2858
+ let sql = this.buildFilter(op.left) + COMPARISON_OPERATOR_STR[op.operator] + this.buildFilter(op.right);
2859
+ 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("\\"))) {
2860
+ sql += " ESCAPE '" + LIKE_ESCAPE + "'";
2861
+ }
2862
+ if (this._guardOptional && GUARDED_COMPARISONS.has(op.operator) && op.left instanceof ColumnWithJoin && this._resolveOptional(op.left)) {
2863
+ sql = "(" + this.buildFilter(op.left) + " IS NOT NULL AND " + sql + ")";
2864
+ }
2865
+ return sql;
2866
+ }
2867
+ if (op instanceof ConstantOperation) return constantValueString(op, this._dialect);
2868
+ if (op instanceof ScalarFunctionOperation || op instanceof ArithmeticOperation || op instanceof ConcatOperation || op instanceof DateTruncOperation || op instanceof AggregateOperation || op instanceof CountAllOperation) {
2869
+ const required = /* @__PURE__ */ new Map();
2870
+ this._collectRequiredJoins(op, required);
2871
+ for (const node of required.values()) this._addJoin(node);
2872
+ return sqlOperationToString(this._rewriteOperation(op), this._dialect);
1610
2873
  }
1611
- if (op instanceof ConstantOperation) return constantValueString(op);
1612
2874
  if (op instanceof ColumnWithJoin) {
1613
- const node = op.parent;
2875
+ const { column, parent: node } = this._resolve(op);
1614
2876
  let ta;
1615
2877
  if (node !== void 0) {
1616
- ta = this._tableAliasForTable(op.column.owner, node);
2878
+ ta = this._tableAliasForTable(column.owner, node);
1617
2879
  this._addJoin(node);
1618
2880
  } else {
1619
- ta = this._tableAliasForTable(op.column.owner, op.column.owner);
2881
+ ta = this._tableAliasForTable(column.owner, column.owner);
1620
2882
  }
1621
- return ta.alias + "." + op.column.name;
2883
+ return ta.alias + "." + column.name;
1622
2884
  }
1623
2885
  if (op instanceof Column) {
1624
2886
  const ta = this._tableAliasForTable(op.owner, op.owner);
@@ -1636,9 +2898,9 @@ var SQLQueryGenerator = class {
1636
2898
  }
1637
2899
  buildQueryString() {
1638
2900
  const joins = this._join.map(
1639
- (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) : "")
2901
+ (j) => " LEFT OUTER JOIN " + j.target.tableAlias.table + " AS " + j.target.tableAlias.alias + " ON (" + j.condition() + (j.filterOp ? " AND " + this.buildFilter(j.filterOp) : "") + ")"
1640
2902
  );
1641
- 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();
2903
+ 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();
1642
2904
  }
1643
2905
  _buildWhere() {
1644
2906
  return this._where.length > 0 ? " WHERE " + this._where : "";
@@ -1646,24 +2908,68 @@ var SQLQueryGenerator = class {
1646
2908
  _buildGroupBy() {
1647
2909
  return this._groupByParts.length > 0 ? " GROUP BY " + this._groupByParts.join(", ") : "";
1648
2910
  }
2911
+ _buildHaving() {
2912
+ return this._having.length > 0 ? " HAVING " + this._having : "";
2913
+ }
1649
2914
  _buildOrderBy() {
1650
2915
  return this._orderByParts.length > 0 ? " ORDER BY " + this._orderByParts.join(", ") : "";
1651
2916
  }
2917
+ _buildOffset() {
2918
+ return this._offset !== void 0 ? " OFFSET " + this._offset : "";
2919
+ }
1652
2920
  _buildLimit() {
1653
2921
  return this._limit !== void 0 ? " LIMIT " + this._limit : "";
1654
2922
  }
1655
2923
  };
1656
- function selectSqlToString(selectOperation) {
1657
- const qg = new SQLQueryGenerator();
2924
+ function selectSqlToString(selectOperation, dialect = "postgres") {
2925
+ const qg = new SQLQueryGenerator(dialectOf(dialect));
1658
2926
  qg.generate(selectOperation);
1659
2927
  return qg.buildQueryString();
1660
2928
  }
1661
- function toSql(businessDate, processingDatetime, columns, table, op, orderBy = [], groupBy = [], limit) {
1662
- const selectOp = buildQueryOperation(businessDate, processingDatetime, columns, table, op, orderBy, groupBy, limit);
1663
- return selectSqlToString(selectOp);
2929
+ function toSql(businessDate, processingDatetime, columns, table, op, orderBy = [], groupBy = [], limit, businessDateTo = null, distinct = false, offset, having, dialect = "postgres") {
2930
+ const selectOp = buildQueryOperation(businessDate, processingDatetime, columns, table, op, orderBy, groupBy, limit, businessDateTo, distinct, offset, having);
2931
+ return selectSqlToString(selectOp, dialect);
2932
+ }
2933
+
2934
+ // src/datafinder/naming.ts
2935
+ function titleCase(value2) {
2936
+ return value2.replace(/[A-Za-z]+/g, (w) => w[0].toUpperCase() + w.slice(1).toLowerCase());
2937
+ }
2938
+
2939
+ // src/datafinder/embedded.ts
2940
+ function embeddedUnder(embedded, prefix) {
2941
+ if (!embedded) return void 0;
2942
+ const result = {};
2943
+ for (const [key, attr] of Object.entries(embedded)) {
2944
+ if (key.startsWith(prefix)) result[key.slice(prefix.length)] = attr;
2945
+ }
2946
+ return Object.keys(result).length > 0 ? result : void 0;
1664
2947
  }
1665
2948
 
2949
+ // src/datafinder/finder.ts
2950
+ var RelatedFinder = class {
2951
+ constructor(_node) {
2952
+ this._node = _node;
2953
+ }
2954
+ _node;
2955
+ exists() {
2956
+ return new ExistsOperation(this._node);
2957
+ }
2958
+ notExists() {
2959
+ return new NotExistsOperation(this._node);
2960
+ }
2961
+ };
2962
+
1666
2963
  // src/datafinder/runner.ts
2964
+ var DEFAULT_TIMEOUT_MS = 6e4;
2965
+ var QueryTimeoutError = class extends Error {
2966
+ constructor(timeoutMs) {
2967
+ super(`Query exceeded ${timeoutMs}ms timeout`);
2968
+ this.timeoutMs = timeoutMs;
2969
+ this.name = "QueryTimeoutError";
2970
+ }
2971
+ timeoutMs;
2972
+ };
1667
2973
  var _runner = null;
1668
2974
  function registerRunner(runner) {
1669
2975
  _runner = runner;
@@ -1675,27 +2981,50 @@ function getRunner() {
1675
2981
  if (!_runner) throw new Error("No query runner registered. Call registerRunner() first.");
1676
2982
  return _runner;
1677
2983
  }
2984
+ var expressionIds = /* @__PURE__ */ new WeakMap();
2985
+ var nextExpressionId = 1;
2986
+ function sortTarget(sort) {
2987
+ const column = sort.column;
2988
+ if (!(column instanceof ColumnWithJoin)) {
2989
+ if (!expressionIds.has(column)) expressionIds.set(column, nextExpressionId++);
2990
+ return JSON.stringify(["expression", expressionIds.get(column)]);
2991
+ }
2992
+ const joins = [];
2993
+ for (let node = column.parent; node !== void 0; node = node.parent) joins.push(node.join.name);
2994
+ return JSON.stringify([column.column.owner, column.column.name, joins]);
2995
+ }
1678
2996
  var FinderResult = class {
1679
- constructor(_businessDate, _processingDatetime, _columns, _table, _op) {
2997
+ constructor(_businessDate, _processingDatetime, _columns, _table, _op, _businessDateTo = null) {
1680
2998
  this._businessDate = _businessDate;
1681
2999
  this._processingDatetime = _processingDatetime;
1682
3000
  this._columns = _columns;
1683
3001
  this._table = _table;
1684
3002
  this._op = _op;
3003
+ this._businessDateTo = _businessDateTo;
1685
3004
  }
1686
3005
  _businessDate;
1687
3006
  _processingDatetime;
1688
3007
  _columns;
1689
3008
  _table;
1690
3009
  _op;
3010
+ _businessDateTo;
1691
3011
  _orderBy = [];
1692
3012
  _groupBy = [];
1693
3013
  _limit;
1694
- orderByOp(...sorts) {
1695
- this._orderBy = sorts;
3014
+ _distinct = false;
3015
+ _offset;
3016
+ _having;
3017
+ _timeoutMs = DEFAULT_TIMEOUT_MS;
3018
+ /**
3019
+ * Order the rows. Calls accumulate: the sorts of the latest call take priority and earlier ones order the
3020
+ * rows it leaves tied, as in a chain of `sortBy` calls. Sorting a column again replaces its earlier direction.
3021
+ */
3022
+ orderBy(...sorts) {
3023
+ const resorted = new Set(sorts.map(sortTarget));
3024
+ this._orderBy = [...sorts, ...this._orderBy.filter((s) => !resorted.has(sortTarget(s)))];
1696
3025
  return this;
1697
3026
  }
1698
- groupByOp(...attrs) {
3027
+ groupBy(...attrs) {
1699
3028
  this._groupBy = attrs;
1700
3029
  return this;
1701
3030
  }
@@ -1703,7 +3032,40 @@ var FinderResult = class {
1703
3032
  this._limit = n;
1704
3033
  return this;
1705
3034
  }
1706
- toSql() {
3035
+ /** Skip the first `n` rows. Sort the rows first: without an order, which rows are first is not defined. */
3036
+ offset(n) {
3037
+ if (!Number.isInteger(n) || n < 0) throw new Error("offset cannot be negative");
3038
+ this._offset = n;
3039
+ return this;
3040
+ }
3041
+ /** Rows `start` up to but not including `stop`, counting from 0: `offset(start).limit(stop - start)`. */
3042
+ slice(start, stop) {
3043
+ if (!Number.isInteger(start) || !Number.isInteger(stop) || start < 0 || stop < start) {
3044
+ throw new Error("slice needs 0 <= start <= stop");
3045
+ }
3046
+ return this.offset(start).limit(stop - start);
3047
+ }
3048
+ /**
3049
+ * Keep the groups for which every condition holds: `HAVING`. A condition tests an aggregate or a grouping column:
3050
+ * `f.findAll(null, null, [f.category(), countAll()]).groupBy(f.category()).having(countAll().gt(1))`.
3051
+ * Calls accumulate, as `and` does.
3052
+ */
3053
+ having(...conditions) {
3054
+ for (const condition of conditions) this._having = this._having === void 0 ? condition : this._having.and(condition);
3055
+ return this;
3056
+ }
3057
+ /** Return each distinct row once: `SELECT DISTINCT`. A sort on a distinct query has to use a selected column. */
3058
+ distinct() {
3059
+ this._distinct = true;
3060
+ return this;
3061
+ }
3062
+ /** Abort the query and throw QueryTimeoutError if it runs longer than `milliseconds` (default 60000). */
3063
+ timeout(milliseconds) {
3064
+ this._timeoutMs = milliseconds;
3065
+ return this;
3066
+ }
3067
+ /** The query as SQL, in Postgres unless a dialect is given: `toSql('databricks')`. Nothing is run. */
3068
+ toSql(dialect = "postgres") {
1707
3069
  return toSql(
1708
3070
  this._businessDate,
1709
3071
  this._processingDatetime,
@@ -1712,7 +3074,12 @@ var FinderResult = class {
1712
3074
  this._op,
1713
3075
  this._orderBy,
1714
3076
  this._groupBy,
1715
- this._limit
3077
+ this._limit,
3078
+ this._businessDateTo,
3079
+ this._distinct,
3080
+ this._offset,
3081
+ this._having,
3082
+ dialect
1716
3083
  );
1717
3084
  }
1718
3085
  async toRows() {
@@ -1724,54 +3091,261 @@ var FinderResult = class {
1724
3091
  this._op,
1725
3092
  this._orderBy,
1726
3093
  this._groupBy,
1727
- this._limit
3094
+ this._limit,
3095
+ this._timeoutMs,
3096
+ this._businessDateTo,
3097
+ this._distinct,
3098
+ this._offset,
3099
+ this._having
1728
3100
  );
1729
3101
  }
1730
3102
  };
1731
- function convertDate(value) {
1732
- if (value === null) return null;
1733
- if (value instanceof Date) return value;
1734
- const normalized = /^\d{4}-\d{2}-\d{2}$/.test(value) ? value + "T00:00:00Z" : value;
3103
+ function convertProcessingDate(value2) {
3104
+ if (typeof value2 === "string" && DATE_ONLY_PATTERN.test(value2)) return new DateOnly(value2 + "T00:00:00Z");
3105
+ return convertDate(value2);
3106
+ }
3107
+ function convertDate(value2) {
3108
+ if (value2 === null) return null;
3109
+ if (value2 instanceof Date) return value2;
3110
+ let normalized = value2;
3111
+ if (DATE_ONLY_PATTERN.test(value2)) normalized = value2 + "T00:00:00Z";
3112
+ else if (/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}(:\d{2}(\.\d+)?)?$/.test(value2)) normalized = value2.replace(" ", "T") + "Z";
1735
3113
  const d = new Date(normalized);
1736
- if (isNaN(d.getTime())) throw new Error(`Cannot parse date: ${value}`);
3114
+ if (isNaN(d.getTime())) throw new Error(`Cannot parse date: ${value2}`);
1737
3115
  return d;
1738
3116
  }
1739
3117
  function convertInputsAndSelect(businessDate, processingDatetime, columns, table, op = new NoOperation()) {
1740
3118
  return new FinderResult(
1741
3119
  convertDate(businessDate),
1742
- convertDate(processingDatetime),
3120
+ convertProcessingDate(processingDatetime),
1743
3121
  columns,
1744
3122
  table,
1745
3123
  op
1746
3124
  );
1747
3125
  }
3126
+ function convertInputsAndSelectForDateRange(businessDateFrom, businessDateTo, processingDatetime, columns, table, op = new NoOperation()) {
3127
+ if (businessDateFrom == null) throw new Error("businessDateFrom is required for findForDateRange");
3128
+ if (businessDateTo == null) throw new Error("businessDateTo is required for findForDateRange");
3129
+ if (processingDatetime == null) throw new Error("processingValidAt is required for findForDateRange");
3130
+ return new FinderResult(
3131
+ convertDate(businessDateFrom),
3132
+ convertProcessingDate(processingDatetime),
3133
+ columns,
3134
+ table,
3135
+ op,
3136
+ convertDate(businessDateTo)
3137
+ );
3138
+ }
3139
+
3140
+ // src/datafinder/databricks-runner.ts
3141
+ var DatabricksQueryError = class extends Error {
3142
+ constructor(message, code, statementId) {
3143
+ super(message);
3144
+ this.code = code;
3145
+ this.statementId = statementId;
3146
+ this.name = "DatabricksQueryError";
3147
+ }
3148
+ code;
3149
+ statementId;
3150
+ };
3151
+ var API = "/api/2.0/sql/statements";
3152
+ var MAX_SAFE = BigInt(Number.MAX_SAFE_INTEGER);
3153
+ function convertValue(text, typeName) {
3154
+ if (text === null || text === void 0) return null;
3155
+ switch ((typeName ?? "").toUpperCase()) {
3156
+ case "BYTE":
3157
+ case "SHORT":
3158
+ case "INT":
3159
+ return Number(text);
3160
+ case "LONG": {
3161
+ const value2 = BigInt(text);
3162
+ return value2 >= -MAX_SAFE && value2 <= MAX_SAFE ? Number(value2) : value2;
3163
+ }
3164
+ case "FLOAT":
3165
+ case "DOUBLE":
3166
+ case "DECIMAL":
3167
+ return Number(text);
3168
+ case "BOOLEAN":
3169
+ return text.toLowerCase() === "true";
3170
+ case "DATE":
3171
+ return new DateOnly(text + "T00:00:00Z");
3172
+ case "TIMESTAMP":
3173
+ case "TIMESTAMP_NTZ": {
3174
+ const zoned = /(Z|[+-]\d{2}:?\d{2})$/.test(text);
3175
+ return new Date(zoned ? text : text.replace(" ", "T") + "Z");
3176
+ }
3177
+ default:
3178
+ return text;
3179
+ }
3180
+ }
3181
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
3182
+ var DatabricksConnect = class {
3183
+ constructor(config) {
3184
+ this.config = config;
3185
+ if (!config.host) throw new Error("DatabricksConnect needs the host of a workspace, or of a server in front of it");
3186
+ if (!config.warehouseId) throw new Error("DatabricksConnect needs the id of a SQL warehouse");
3187
+ }
3188
+ config;
3189
+ dialect = "databricks";
3190
+ /** Names of the columns of the last query that returned. */
3191
+ lastColumns = [];
3192
+ async select(businessDate, processingDatetime, columns, table, op, orderBy, groupBy, limit, timeoutMs = DEFAULT_TIMEOUT_MS, businessDateTo = null, distinct = false, offset, having) {
3193
+ const statement = toSql(
3194
+ businessDate,
3195
+ processingDatetime,
3196
+ columns,
3197
+ table,
3198
+ op,
3199
+ orderBy,
3200
+ groupBy,
3201
+ limit,
3202
+ businessDateTo,
3203
+ distinct,
3204
+ offset,
3205
+ having,
3206
+ this.dialect
3207
+ );
3208
+ const deadline = Date.now() + timeoutMs;
3209
+ const controller = new AbortController();
3210
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
3211
+ let statementId;
3212
+ try {
3213
+ let response = await this.call("POST", `${API}/`, controller.signal, {
3214
+ warehouse_id: this.config.warehouseId,
3215
+ statement,
3216
+ // the API holds the request for up to 50 s and answers at once if the statement is quicker; a shorter
3217
+ // wait than 5 s is not allowed except 0, so poll
3218
+ wait_timeout: timeoutMs >= 5e3 ? `${Math.min(50, Math.floor(timeoutMs / 1e3))}s` : "0s",
3219
+ on_wait_timeout: "CONTINUE",
3220
+ disposition: "INLINE",
3221
+ format: "JSON_ARRAY",
3222
+ ...this.config.catalog ? { catalog: this.config.catalog } : {},
3223
+ ...this.config.schema ? { schema: this.config.schema } : {}
3224
+ });
3225
+ statementId = response.statement_id;
3226
+ let delay = this.config.pollIntervalMs ?? 250;
3227
+ while (response.status.state === "PENDING" || response.status.state === "RUNNING") {
3228
+ if (Date.now() >= deadline) throw new QueryTimeoutError(timeoutMs);
3229
+ await sleep(Math.min(delay, Math.max(0, deadline - Date.now())));
3230
+ delay = Math.min(delay * 2, 2e3);
3231
+ response = await this.call("GET", `${API}/${statementId}`, controller.signal);
3232
+ }
3233
+ if (response.status.state !== "SUCCEEDED") {
3234
+ const error = response.status.error;
3235
+ throw new DatabricksQueryError(
3236
+ error?.message ?? `The statement ${response.status.state.toLowerCase()}`,
3237
+ error?.error_code,
3238
+ statementId
3239
+ );
3240
+ }
3241
+ return await this.rows(response, controller.signal);
3242
+ } catch (e) {
3243
+ const timedOut = controller.signal.aborted || e instanceof QueryTimeoutError;
3244
+ if (timedOut && statementId !== void 0) await this.cancel(statementId);
3245
+ if (controller.signal.aborted) throw new QueryTimeoutError(timeoutMs);
3246
+ throw e;
3247
+ } finally {
3248
+ clearTimeout(timer);
3249
+ }
3250
+ }
3251
+ /** The rows of a finished statement, following its chunks. */
3252
+ async rows(first, signal) {
3253
+ const info = first.manifest?.schema?.columns ?? [];
3254
+ this.lastColumns = info.map((c) => c.name);
3255
+ const rows = [];
3256
+ const take = (data) => {
3257
+ for (const row of data ?? []) rows.push(row.map((text, i) => convertValue(text, info[i]?.type_name)));
3258
+ };
3259
+ take(first.result?.data_array);
3260
+ let next = first.result?.next_chunk_index;
3261
+ while (next !== void 0 && next !== null) {
3262
+ const chunk = await this.call("GET", `${API}/${first.statement_id}/result/chunks/${next}`, signal);
3263
+ take(chunk.data_array);
3264
+ next = chunk.next_chunk_index;
3265
+ }
3266
+ return rows;
3267
+ }
3268
+ /** Ask Databricks to stop a statement. Best effort: the query is already given up on. */
3269
+ async cancel(statementId) {
3270
+ try {
3271
+ await this.call("POST", `${API}/${statementId}/cancel`, void 0, {});
3272
+ } catch {
3273
+ }
3274
+ }
3275
+ async call(method, path, signal, body) {
3276
+ const { host, token, headers, fetch: custom } = this.config;
3277
+ const bearer = typeof token === "function" ? await token() : token;
3278
+ const response = await (custom ?? fetch)(host.replace(/\/+$/, "") + path, {
3279
+ method,
3280
+ headers: {
3281
+ "Content-Type": "application/json",
3282
+ ...bearer ? { Authorization: `Bearer ${bearer}` } : {},
3283
+ ...headers
3284
+ },
3285
+ body: body === void 0 ? void 0 : JSON.stringify(body),
3286
+ signal
3287
+ });
3288
+ const text = await response.text();
3289
+ let parsed;
3290
+ try {
3291
+ parsed = text ? JSON.parse(text) : {};
3292
+ } catch {
3293
+ parsed = void 0;
3294
+ }
3295
+ if (!response.ok) {
3296
+ const detail = parsed;
3297
+ throw new DatabricksQueryError(
3298
+ detail?.message ?? `Databricks request failed: ${response.status} ${response.statusText}`,
3299
+ detail?.error_code
3300
+ );
3301
+ }
3302
+ return parsed;
3303
+ }
3304
+ };
1748
3305
  // Annotate the CommonJS export names for ESM import in node:
1749
3306
  0 && (module.exports = {
1750
3307
  AggregateOperation,
1751
3308
  AggregateOperator,
3309
+ Aggregates,
3310
+ Alias,
3311
+ Arithmetic,
3312
+ ArithmeticOperation,
3313
+ ArithmeticOperator,
1752
3314
  Attribute,
1753
3315
  BiTemporalColumns,
1754
3316
  BinaryOperation,
3317
+ BooleanAggregates,
1755
3318
  BooleanAttribute,
1756
3319
  BooleanConstantOperation,
1757
3320
  BusinessDateAndProcessingTemporalColumns,
3321
+ CaseOperation,
1758
3322
  Column,
1759
3323
  ColumnWithJoin,
3324
+ Comparable,
1760
3325
  ComparisonOperation,
1761
3326
  ComparisonOperator,
3327
+ ConcatOperation,
1762
3328
  ConstantOperation,
1763
3329
  CountAllOperation,
3330
+ DATABRICKS,
3331
+ DATE_ONLY_PATTERN,
3332
+ DEFAULT_TIMEOUT_MS,
3333
+ DUCKDB,
1764
3334
  DataCatalog,
1765
3335
  DataStore,
1766
3336
  Database,
3337
+ DatabricksConnect,
3338
+ DatabricksQueryError,
1767
3339
  DateArithmeticOperation,
1768
3340
  DateAttribute,
1769
3341
  DateConstantOperation,
1770
3342
  DateDiffOperation,
1771
3343
  DateExtractOperation,
3344
+ DateOnly,
1772
3345
  DatePart,
1773
3346
  DateTimeAttribute,
1774
3347
  DateTimeConstantOperation,
3348
+ DateTruncOperation,
1775
3349
  DecimalAttribute,
1776
3350
  DecimalConstantOperation,
1777
3351
  DoubleAttribute,
@@ -1780,43 +3354,69 @@ function convertInputsAndSelect(businessDate, processingDatetime, columns, table
1780
3354
  FloatAttribute,
1781
3355
  FloatConstantOperation,
1782
3356
  ForeignKey,
3357
+ InOperation,
1783
3358
  IntegerAttribute,
1784
3359
  IntegerConstantOperation,
1785
3360
  IsNotNullOperation,
1786
3361
  IsNullOperation,
1787
3362
  JoinOperation,
1788
3363
  JoinTreeNodeOperation,
3364
+ LIKE_ESCAPE,
1789
3365
  LogicalOperation,
1790
3366
  LogicalOperator,
1791
3367
  MilestonedTable,
1792
3368
  MilestoningColumns,
3369
+ NUMERIC_FUNCTIONS,
1793
3370
  NoOperation,
1794
3371
  NotExistsOperation,
3372
+ NotInOperation,
1795
3373
  NumericAttribute,
3374
+ NumericConstantOperation,
3375
+ NumericFunctionOperation,
1796
3376
  Operation,
3377
+ POSTGRES,
1797
3378
  ProcessingTemporalColumns,
3379
+ QueryTimeoutError,
1798
3380
  RelatedFinder,
1799
3381
  RelationalOperationElement,
1800
3382
  ScalarFunction,
1801
3383
  ScalarFunctionOperation,
1802
3384
  Schema,
3385
+ SelectOperation,
1803
3386
  SingleBusinessDateColumn,
1804
3387
  SortDirection,
1805
3388
  SortOperation,
1806
3389
  StringAttribute,
1807
3390
  StringConstantOperation,
3391
+ StringFunctionOperation,
3392
+ StringPredicates,
1808
3393
  Table,
1809
3394
  UnaryOperation,
1810
3395
  WindowFunction,
1811
3396
  WindowFunctionOperation,
1812
3397
  WindowSpecification,
3398
+ applyMixins,
1813
3399
  clearRunner,
3400
+ concat,
3401
+ constant,
3402
+ containsAggregate,
3403
+ containsAggregateOrWindow,
1814
3404
  convertInputsAndSelect,
3405
+ convertInputsAndSelectForDateRange,
3406
+ countAll,
3407
+ dialectOf,
3408
+ embeddedUnder,
3409
+ escapeLike,
1815
3410
  getRelatedFinderClass,
1816
3411
  getRunner,
3412
+ operandOf,
1817
3413
  registerRelatedFinderClass,
1818
3414
  registerRunner,
3415
+ sample,
1819
3416
  selectSqlToString,
1820
- toSql
3417
+ titleCase,
3418
+ toSql,
3419
+ valueOperand,
3420
+ when
1821
3421
  });
1822
3422
  //# sourceMappingURL=index.cjs.map