@milaboratories/ptabler-expression-js 1.1.0

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.
@@ -0,0 +1,944 @@
1
+ /**
2
+ * Base expressionImpl classes and interfaces for PTabler JavaScript implementation
3
+ */
4
+
5
+ import type {
6
+ AggregationType,
7
+ BinaryArithmeticExpression,
8
+ BinaryArithmeticOperator,
9
+ BooleanLogicExpression,
10
+ ColumnReferenceExpression,
11
+ ComparisonExpression,
12
+ ComparisonOperator,
13
+ ConstantValueExpression,
14
+ CumsumExpression,
15
+ Expression,
16
+ ExtendedUnaryStringExpression,
17
+ FillNaNExpression,
18
+ FillNullExpression,
19
+ FuzzyFilterDistanceMetric,
20
+ FuzzyStringFilterExpression,
21
+ MinMaxExpression,
22
+ MinMaxOperator,
23
+ NotExpression,
24
+ RankExpression,
25
+ StringContainsExpression,
26
+ StringDistanceExpression,
27
+ StringDistanceMetric,
28
+ StringJoinExpression,
29
+ StringReplaceExpression,
30
+ SubstringExpression,
31
+ UnaryArithmeticExpression,
32
+ UnaryArithmeticOperator,
33
+ WhenThenOtherwiseExpression,
34
+ WindowExpression,
35
+ } from './types';
36
+
37
+ /**
38
+ * Base abstract class for all expressions
39
+ */
40
+ export abstract class ExpressionImpl {
41
+ protected _alias?: string;
42
+
43
+ /**
44
+ * Convert the expressionImpl to JSON format compatible with PTabler schema
45
+ */
46
+ abstract toJSON(): Expression;
47
+
48
+ /**
49
+ * Get the alias for this expressionImpl (defaults to a generated name)
50
+ */
51
+ abstract getAlias(): string;
52
+
53
+ /**
54
+ * Set an alias for this expression
55
+ */
56
+ alias(name: string): ExpressionImpl {
57
+ const cloned = this.clone();
58
+ cloned._alias = name;
59
+ return cloned;
60
+ }
61
+
62
+ /**
63
+ * Clone this expression
64
+ */
65
+ protected abstract clone(): ExpressionImpl;
66
+
67
+ // Arithmetic operations
68
+ plus(other: ExpressionImpl | number | string): ArithmeticExpressionImpl {
69
+ return new ArithmeticExpressionImpl('plus', this, coerceToExpression(other));
70
+ }
71
+
72
+ minus(other: ExpressionImpl | number | string): ArithmeticExpressionImpl {
73
+ return new ArithmeticExpressionImpl('minus', this, coerceToExpression(other));
74
+ }
75
+
76
+ multiply(other: ExpressionImpl | number | string): ArithmeticExpressionImpl {
77
+ return new ArithmeticExpressionImpl('multiply', this, coerceToExpression(other));
78
+ }
79
+
80
+ truediv(other: ExpressionImpl | number | string): ArithmeticExpressionImpl {
81
+ return new ArithmeticExpressionImpl('truediv', this, coerceToExpression(other));
82
+ }
83
+
84
+ floordiv(other: ExpressionImpl | number | string): ArithmeticExpressionImpl {
85
+ return new ArithmeticExpressionImpl('floordiv', this, coerceToExpression(other));
86
+ }
87
+
88
+ // Comparison operations
89
+ gt(other: ExpressionImpl | number | string): ComparisonExpressionImpl {
90
+ return new ComparisonExpressionImpl('gt', this, coerceToExpression(other));
91
+ }
92
+
93
+ ge(other: ExpressionImpl | number | string): ComparisonExpressionImpl {
94
+ return new ComparisonExpressionImpl('ge', this, coerceToExpression(other));
95
+ }
96
+
97
+ eq(other: ExpressionImpl | number | string): ComparisonExpressionImpl {
98
+ return new ComparisonExpressionImpl('eq', this, coerceToExpression(other));
99
+ }
100
+
101
+ lt(other: ExpressionImpl | number | string): ComparisonExpressionImpl {
102
+ return new ComparisonExpressionImpl('lt', this, coerceToExpression(other));
103
+ }
104
+
105
+ le(other: ExpressionImpl | number | string): ComparisonExpressionImpl {
106
+ return new ComparisonExpressionImpl('le', this, coerceToExpression(other));
107
+ }
108
+
109
+ neq(other: ExpressionImpl | number | string): ComparisonExpressionImpl {
110
+ return new ComparisonExpressionImpl('neq', this, coerceToExpression(other));
111
+ }
112
+
113
+ // Logical operations
114
+ and(...others: ExpressionImpl[]): LogicalExpressionImpl {
115
+ return new LogicalExpressionImpl('and', [this, ...others]);
116
+ }
117
+
118
+ or(...others: ExpressionImpl[]): LogicalExpressionImpl {
119
+ return new LogicalExpressionImpl('or', [this, ...others]);
120
+ }
121
+
122
+ not(): LogicalExpressionImpl {
123
+ return new LogicalExpressionImpl('not', [this]);
124
+ }
125
+
126
+ // Unary arithmetic operations
127
+ abs(): UnaryArithmeticExpressionImpl {
128
+ return new UnaryArithmeticExpressionImpl('abs', this);
129
+ }
130
+
131
+ sqrt(): UnaryArithmeticExpressionImpl {
132
+ return new UnaryArithmeticExpressionImpl('sqrt', this);
133
+ }
134
+
135
+ log(): UnaryArithmeticExpressionImpl {
136
+ return new UnaryArithmeticExpressionImpl('log', this);
137
+ }
138
+
139
+ log10(): UnaryArithmeticExpressionImpl {
140
+ return new UnaryArithmeticExpressionImpl('log10', this);
141
+ }
142
+
143
+ log2(): UnaryArithmeticExpressionImpl {
144
+ return new UnaryArithmeticExpressionImpl('log2', this);
145
+ }
146
+
147
+ floor(): UnaryArithmeticExpressionImpl {
148
+ return new UnaryArithmeticExpressionImpl('floor', this);
149
+ }
150
+
151
+ ceil(): UnaryArithmeticExpressionImpl {
152
+ return new UnaryArithmeticExpressionImpl('ceil', this);
153
+ }
154
+
155
+ round(): UnaryArithmeticExpressionImpl {
156
+ return new UnaryArithmeticExpressionImpl('round', this);
157
+ }
158
+
159
+ negate(): UnaryArithmeticExpressionImpl {
160
+ return new UnaryArithmeticExpressionImpl('negate', this);
161
+ }
162
+
163
+ // Null checks
164
+ isNull(): NullCheckExpressionImpl {
165
+ return new NullCheckExpressionImpl('is_na', this);
166
+ }
167
+
168
+ isNotNull(): NullCheckExpressionImpl {
169
+ return new NullCheckExpressionImpl('is_not_na', this);
170
+ }
171
+
172
+ // Fill null/NaN
173
+ fillNull(value: ExpressionImpl): FillNullExpressionImpl {
174
+ return new FillNullExpressionImpl(this, coerceToExpression(value));
175
+ }
176
+
177
+ fillNaN(value: ExpressionImpl): FillNaNExpressionImpl {
178
+ return new FillNaNExpressionImpl(this, coerceToExpression(value));
179
+ }
180
+
181
+ // String operations
182
+ strConcat(...others: (ExpressionImpl | string)[]): StringConcatExpressionImpl {
183
+ return new StringConcatExpressionImpl([this, ...others.map(coerceToExpression)]);
184
+ }
185
+
186
+ substring(start: number, length?: number): SubstringExpressionImpl {
187
+ return new SubstringExpressionImpl(this, start, length);
188
+ }
189
+
190
+ strReplace(pattern: string, value: string, options?: Pick<StringReplaceExpression, 'replaceAll' | 'literal'>): StringReplaceExpressionImpl {
191
+ return new StringReplaceExpressionImpl(this, pattern, value, options);
192
+ }
193
+
194
+ strContains(pattern: ExpressionImpl | string, literal?: boolean, strict?: boolean): StringContainsExpressionImpl {
195
+ return new StringContainsExpressionImpl(this, coerceToExpression(pattern), literal, strict);
196
+ }
197
+
198
+ strToUpper(): StringCaseExpressionImpl {
199
+ return new StringCaseExpressionImpl('to_upper', this);
200
+ }
201
+
202
+ strToLower(): StringCaseExpressionImpl {
203
+ return new StringCaseExpressionImpl('to_lower', this);
204
+ }
205
+
206
+ strStartsWith(pattern: ExpressionImpl | string): StringStartsWithExpressionImpl {
207
+ return new StringStartsWithExpressionImpl(this, coerceToExpression(pattern));
208
+ }
209
+
210
+ strEndsWith(pattern: ExpressionImpl | string): StringEndsWithExpressionImpl {
211
+ return new StringEndsWithExpressionImpl(this, coerceToExpression(pattern));
212
+ }
213
+
214
+ // Aggregation operations
215
+ sum(): AggregationExpressionImpl {
216
+ return new AggregationExpressionImpl('sum', this);
217
+ }
218
+
219
+ mean(): AggregationExpressionImpl {
220
+ return new AggregationExpressionImpl('mean', this);
221
+ }
222
+
223
+ count(): AggregationExpressionImpl {
224
+ return new AggregationExpressionImpl('count', this);
225
+ }
226
+
227
+ min(): AggregationExpressionImpl {
228
+ return new AggregationExpressionImpl('min', this);
229
+ }
230
+
231
+ max(): AggregationExpressionImpl {
232
+ return new AggregationExpressionImpl('max', this);
233
+ }
234
+
235
+ first(): AggregationExpressionImpl {
236
+ return new AggregationExpressionImpl('first', this);
237
+ }
238
+
239
+ last(): AggregationExpressionImpl {
240
+ return new AggregationExpressionImpl('last', this);
241
+ }
242
+
243
+ cumsum(): CumsumExpressionImpl {
244
+ return new CumsumExpressionImpl(this);
245
+ }
246
+
247
+ // Fuzzy operations
248
+ stringDistance(other: ExpressionImpl | string, metric: StringDistanceMetric, returnSimilarity?: boolean): StringDistanceExpressionImpl {
249
+ return new StringDistanceExpressionImpl(
250
+ this,
251
+ coerceToExpression(other),
252
+ metric,
253
+ returnSimilarity,
254
+ );
255
+ }
256
+
257
+ fuzzyStringFilter(pattern: ExpressionImpl | string, metric: FuzzyFilterDistanceMetric, bound: number): FuzzyStringFilterExpressionImpl {
258
+ return new FuzzyStringFilterExpressionImpl(
259
+ this,
260
+ coerceToExpression(pattern),
261
+ metric,
262
+ bound,
263
+ );
264
+ }
265
+ }
266
+
267
+ export class ColumnExpressionImpl extends ExpressionImpl {
268
+ constructor(private readonly columnName: string) {
269
+ super();
270
+ }
271
+
272
+ toJSON(): ColumnReferenceExpression {
273
+ return {
274
+ type: 'col',
275
+ name: this.columnName,
276
+ };
277
+ }
278
+
279
+ getAlias(): string {
280
+ return this._alias || this.columnName;
281
+ }
282
+
283
+ protected clone(): ExpressionImpl {
284
+ const cloned = new ColumnExpressionImpl(this.columnName);
285
+ cloned._alias = this._alias;
286
+ return cloned;
287
+ }
288
+
289
+ /**
290
+ * Get the column name
291
+ */
292
+ getColumnName(): string {
293
+ return this.columnName;
294
+ }
295
+ }
296
+
297
+ /**
298
+ * Helper function to coerce values to expressions
299
+ */
300
+ export function coerceToExpression(value: ExpressionImpl | LiteralValue): ExpressionImpl {
301
+ if (value instanceof ExpressionImpl) {
302
+ return value;
303
+ }
304
+ return new LiteralExpressionImpl(value);
305
+ }
306
+
307
+ export class MinMaxExpressionImpl extends ExpressionImpl {
308
+ constructor(private readonly op: MinMaxOperator, private readonly ops: ExpressionImpl[]) {
309
+ super();
310
+ }
311
+
312
+ toJSON(): MinMaxExpression {
313
+ return {
314
+ type: this.op,
315
+ operands: this.ops.map((o) => o.toJSON()),
316
+ };
317
+ }
318
+
319
+ getAlias(): string {
320
+ return this._alias || `${this.op}_${this.ops.map((o) => o.getAlias()).join('_')}`;
321
+ }
322
+
323
+ protected clone(): ExpressionImpl {
324
+ const cloned = new MinMaxExpressionImpl(this.op, this.ops);
325
+ cloned._alias = this._alias;
326
+ return cloned;
327
+ }
328
+ }
329
+
330
+ // Forward declarations for concrete expressionImpl classes
331
+ // These will be imported from their respective modules
332
+ export class ArithmeticExpressionImpl extends ExpressionImpl {
333
+ constructor(
334
+ private readonly operator: BinaryArithmeticOperator,
335
+ private readonly lhs: ExpressionImpl,
336
+ private readonly rhs: ExpressionImpl,
337
+ ) {
338
+ super();
339
+ }
340
+
341
+ toJSON(): BinaryArithmeticExpression {
342
+ return {
343
+ type: this.operator,
344
+ lhs: this.lhs.toJSON(),
345
+ rhs: this.rhs.toJSON(),
346
+ };
347
+ }
348
+
349
+ getAlias(): string {
350
+ return this._alias || `${this.lhs.getAlias()}_${this.operator}_${this.rhs.getAlias()}`;
351
+ }
352
+
353
+ protected clone(): ExpressionImpl {
354
+ const cloned = new ArithmeticExpressionImpl(this.operator, this.lhs, this.rhs);
355
+ cloned._alias = this._alias;
356
+ return cloned;
357
+ }
358
+ }
359
+
360
+ export class ComparisonExpressionImpl extends ExpressionImpl {
361
+ constructor(
362
+ private readonly operator: ComparisonOperator,
363
+ private readonly lhs: ExpressionImpl,
364
+ private readonly rhs: ExpressionImpl,
365
+ ) {
366
+ super();
367
+ }
368
+
369
+ toJSON(): ComparisonExpression {
370
+ return {
371
+ type: this.operator,
372
+ lhs: this.lhs.toJSON(),
373
+ rhs: this.rhs.toJSON(),
374
+ };
375
+ }
376
+
377
+ getAlias(): string {
378
+ return this._alias || `${this.lhs.getAlias()}_${this.operator}_${this.rhs.getAlias()}`;
379
+ }
380
+
381
+ protected clone(): ComparisonExpressionImpl {
382
+ const cloned = new ComparisonExpressionImpl(this.operator, this.lhs, this.rhs);
383
+ cloned._alias = this._alias;
384
+ return cloned;
385
+ }
386
+ }
387
+
388
+ export class LogicalExpressionImpl extends ExpressionImpl {
389
+ constructor(
390
+ private readonly operator: 'and' | 'or' | 'not',
391
+ private readonly operands: ExpressionImpl[],
392
+ ) {
393
+ super();
394
+ }
395
+
396
+ toJSON(): NotExpression | BooleanLogicExpression {
397
+ if (this.operator === 'not') {
398
+ return {
399
+ type: 'not',
400
+ value: this.operands[0].toJSON(),
401
+ };
402
+ }
403
+ return {
404
+ type: this.operator,
405
+ operands: this.operands.map((op) => op.toJSON()),
406
+ };
407
+ }
408
+
409
+ getAlias(): string {
410
+ if (this._alias) return this._alias;
411
+ if (this.operator === 'not') {
412
+ return `not_${this.operands[0].getAlias()}`;
413
+ }
414
+ return this.operands.map((op) => op.getAlias()).join(`_${this.operator}_`);
415
+ }
416
+
417
+ protected clone(): ExpressionImpl {
418
+ const cloned = new LogicalExpressionImpl(this.operator, this.operands);
419
+ cloned._alias = this._alias;
420
+ return cloned;
421
+ }
422
+ }
423
+
424
+ export class UnaryArithmeticExpressionImpl extends ExpressionImpl {
425
+ constructor(
426
+ private readonly operator: UnaryArithmeticOperator,
427
+ private readonly value: ExpressionImpl,
428
+ ) {
429
+ super();
430
+ }
431
+
432
+ toJSON(): UnaryArithmeticExpression {
433
+ return {
434
+ type: this.operator,
435
+ value: this.value.toJSON(),
436
+ };
437
+ }
438
+
439
+ getAlias(): string {
440
+ return this._alias || `${this.operator}_${this.value.getAlias()}`;
441
+ }
442
+
443
+ protected clone(): ExpressionImpl {
444
+ const cloned = new UnaryArithmeticExpressionImpl(this.operator, this.value);
445
+ cloned._alias = this._alias;
446
+ return cloned;
447
+ }
448
+ }
449
+
450
+ export class NullCheckExpressionImpl extends ExpressionImpl {
451
+ constructor(
452
+ private readonly operator: 'is_na' | 'is_not_na',
453
+ private readonly value: ExpressionImpl,
454
+ ) {
455
+ super();
456
+ }
457
+
458
+ toJSON(): Expression {
459
+ return {
460
+ type: this.operator,
461
+ value: this.value.toJSON(),
462
+ };
463
+ }
464
+
465
+ getAlias(): string {
466
+ return this._alias || `${this.value.getAlias()}_${this.operator}`;
467
+ }
468
+
469
+ protected clone(): ExpressionImpl {
470
+ const cloned = new NullCheckExpressionImpl(this.operator, this.value);
471
+ cloned._alias = this._alias;
472
+ return cloned;
473
+ }
474
+ }
475
+
476
+ export type LiteralValue = string | number | boolean | null;
477
+
478
+ export class LiteralExpressionImpl extends ExpressionImpl {
479
+ constructor(private readonly value: LiteralValue) {
480
+ super();
481
+ }
482
+
483
+ toJSON(): ConstantValueExpression {
484
+ return {
485
+ type: 'const',
486
+ value: this.value,
487
+ };
488
+ }
489
+
490
+ getAlias(): string {
491
+ return this._alias || this.generateDefaultAlias();
492
+ }
493
+
494
+ protected clone(): ExpressionImpl {
495
+ const cloned = new LiteralExpressionImpl(this.value);
496
+ cloned._alias = this._alias;
497
+ return cloned;
498
+ }
499
+
500
+ /**
501
+ * Get the literal value
502
+ */
503
+ getValue(): string | number | boolean | null {
504
+ return this.value;
505
+ }
506
+
507
+ /**
508
+ * Generate a default alias based on the value
509
+ */
510
+ private generateDefaultAlias(): string {
511
+ if (this.value === null || this.value === undefined) {
512
+ return 'null';
513
+ }
514
+ if (typeof this.value === 'string') {
515
+ // For string values, truncate if too long and make safe for column names
516
+ const safe = this.value.replace(/[^a-zA-Z0-9_]/g, '_');
517
+ return safe.length > 20 ? safe.substring(0, 17) + '...' : safe;
518
+ }
519
+ if (typeof this.value === 'boolean') {
520
+ return this.value ? 'true' : 'false';
521
+ }
522
+ if (typeof this.value === 'number') {
523
+ return String(this.value);
524
+ }
525
+ if (Array.isArray(this.value)) {
526
+ return 'array';
527
+ }
528
+ if (typeof this.value === 'object') {
529
+ return 'object';
530
+ }
531
+ return 'literal';
532
+ }
533
+ }
534
+
535
+ export class FillNullExpressionImpl extends ExpressionImpl {
536
+ constructor(private readonly expr: ExpressionImpl, private readonly fillValue: ExpressionImpl) {
537
+ super();
538
+ }
539
+
540
+ toJSON(): FillNullExpression {
541
+ return {
542
+ type: 'fill_null',
543
+ input: this.expr.toJSON(),
544
+ fillValue: this.fillValue.toJSON(),
545
+ };
546
+ }
547
+
548
+ getAlias(): string {
549
+ return this._alias || `${this.expr.getAlias()}_fill_null`;
550
+ }
551
+
552
+ protected clone(): ExpressionImpl {
553
+ const cloned = new FillNullExpressionImpl(this.expr, this.fillValue);
554
+ cloned._alias = this._alias;
555
+ return cloned;
556
+ }
557
+ }
558
+
559
+ export class FillNaNExpressionImpl extends ExpressionImpl {
560
+ constructor(private readonly expr: ExpressionImpl, private readonly fillValue: ExpressionImpl) {
561
+ super();
562
+ }
563
+
564
+ toJSON(): FillNaNExpression {
565
+ return {
566
+ type: 'fill_nan',
567
+ input: this.expr.toJSON(),
568
+ fillValue: this.fillValue.toJSON(),
569
+ };
570
+ }
571
+
572
+ getAlias(): string {
573
+ return this._alias || `${this.expr.getAlias()}_fill_nan`;
574
+ }
575
+
576
+ protected clone(): ExpressionImpl {
577
+ const cloned = new FillNaNExpressionImpl(this.expr, this.fillValue);
578
+ cloned._alias = this._alias;
579
+ return cloned;
580
+ }
581
+ }
582
+
583
+ export class StringConcatExpressionImpl extends ExpressionImpl {
584
+ constructor(private readonly operands: ExpressionImpl[], private readonly delimiter: string = '') {
585
+ super();
586
+ }
587
+
588
+ toJSON(): StringJoinExpression {
589
+ return {
590
+ type: 'str_join',
591
+ operands: this.operands.map((o) => o.toJSON()),
592
+ delimiter: this.delimiter,
593
+ };
594
+ }
595
+
596
+ getAlias(): string {
597
+ return this._alias || this.operands.map((o) => o.getAlias()).join('_');
598
+ }
599
+
600
+ protected clone(): ExpressionImpl {
601
+ const cloned = new StringConcatExpressionImpl(this.operands);
602
+ cloned._alias = this._alias;
603
+ return cloned;
604
+ }
605
+ }
606
+
607
+ export class SubstringExpressionImpl extends ExpressionImpl {
608
+ constructor(private readonly expr: ExpressionImpl, private readonly start: number, private readonly length?: number) {
609
+ super();
610
+ }
611
+
612
+ toJSON(): SubstringExpression {
613
+ return {
614
+ type: 'substring',
615
+ value: this.expr.toJSON(),
616
+ start: { type: 'const', value: this.start },
617
+ length: this.length !== undefined ? { type: 'const', value: this.length } : undefined,
618
+ };
619
+ }
620
+
621
+ getAlias(): string {
622
+ return this._alias || `${this.expr.getAlias()}_substring`;
623
+ }
624
+
625
+ protected clone(): ExpressionImpl {
626
+ const cloned = new SubstringExpressionImpl(this.expr, this.start, this.length);
627
+ cloned._alias = this._alias;
628
+ return cloned;
629
+ }
630
+ }
631
+
632
+ export class StringReplaceExpressionImpl extends ExpressionImpl {
633
+ constructor(private readonly expr: ExpressionImpl, private readonly pattern: string, private readonly value: string, private readonly options?: Pick<StringReplaceExpression, 'replaceAll' | 'literal'>) {
634
+ super();
635
+ }
636
+
637
+ toJSON(): StringReplaceExpression {
638
+ return {
639
+ type: 'str_replace',
640
+ value: this.expr.toJSON(),
641
+ pattern: this.pattern,
642
+ replacement: this.value,
643
+ replaceAll: this.options?.replaceAll || false,
644
+ literal: this.options?.literal || false,
645
+ };
646
+ }
647
+
648
+ getAlias(): string {
649
+ return this._alias || `${this.expr.getAlias()}_replace`;
650
+ }
651
+
652
+ protected clone(): ExpressionImpl {
653
+ const cloned = new StringReplaceExpressionImpl(this.expr, this.pattern, this.value, this.options);
654
+ cloned._alias = this._alias;
655
+ return cloned;
656
+ }
657
+ }
658
+
659
+ export class StringContainsExpressionImpl extends ExpressionImpl {
660
+ constructor(private readonly expr: ExpressionImpl, private readonly pattern: ExpressionImpl, private readonly literal?: boolean, private readonly strict?: boolean) {
661
+ super();
662
+ }
663
+
664
+ toJSON(): StringContainsExpression {
665
+ return {
666
+ type: 'str_contains',
667
+ value: this.expr.toJSON(),
668
+ pattern: this.pattern.toJSON(),
669
+ literal: this.literal || false,
670
+ strict: this.strict !== undefined ? this.strict : true,
671
+ };
672
+ }
673
+
674
+ getAlias(): string {
675
+ return this._alias || `${this.expr.getAlias()}_contains`;
676
+ }
677
+
678
+ protected clone(): ExpressionImpl {
679
+ const cloned = new StringContainsExpressionImpl(this.expr, this.pattern, this.literal, this.strict);
680
+ cloned._alias = this._alias;
681
+ return cloned;
682
+ }
683
+ }
684
+
685
+ export class StringCaseExpressionImpl extends ExpressionImpl {
686
+ constructor(private readonly operation: 'to_upper' | 'to_lower', private readonly expr: ExpressionImpl) {
687
+ super();
688
+ }
689
+
690
+ toJSON(): ExtendedUnaryStringExpression {
691
+ return {
692
+ type: this.operation,
693
+ value: this.expr.toJSON(),
694
+ };
695
+ }
696
+
697
+ getAlias(): string {
698
+ return this._alias || `${this.expr.getAlias()}_${this.operation}`;
699
+ }
700
+
701
+ protected clone(): ExpressionImpl {
702
+ const cloned = new StringCaseExpressionImpl(this.operation, this.expr);
703
+ cloned._alias = this._alias;
704
+ return cloned;
705
+ }
706
+ }
707
+
708
+ export class StringStartsWithExpressionImpl extends ExpressionImpl {
709
+ constructor(private readonly expr: ExpressionImpl, private readonly pattern: ExpressionImpl) {
710
+ super();
711
+ }
712
+
713
+ toJSON(): Expression {
714
+ return {
715
+ type: 'str_starts_with',
716
+ value: this.expr.toJSON(),
717
+ prefix: this.pattern.toJSON(),
718
+ };
719
+ }
720
+
721
+ getAlias(): string {
722
+ return this._alias || `${this.expr.getAlias()}_starts_with`;
723
+ }
724
+
725
+ protected clone(): ExpressionImpl {
726
+ const cloned = new StringStartsWithExpressionImpl(this.expr, this.pattern);
727
+ cloned._alias = this._alias;
728
+ return cloned;
729
+ }
730
+ }
731
+
732
+ export class StringEndsWithExpressionImpl extends ExpressionImpl {
733
+ constructor(private readonly expr: ExpressionImpl, private readonly pattern: ExpressionImpl) {
734
+ super();
735
+ }
736
+
737
+ toJSON(): Expression {
738
+ return {
739
+ type: 'str_ends_with',
740
+ value: this.expr.toJSON(),
741
+ suffix: this.pattern.toJSON(),
742
+ };
743
+ }
744
+
745
+ getAlias(): string {
746
+ return this._alias || `${this.expr.getAlias()}_ends_with`;
747
+ }
748
+
749
+ protected clone(): ExpressionImpl {
750
+ const cloned = new StringEndsWithExpressionImpl(this.expr, this.pattern);
751
+ cloned._alias = this._alias;
752
+ return cloned;
753
+ }
754
+ }
755
+
756
+ export class CumsumExpressionImpl extends ExpressionImpl {
757
+ constructor(private readonly value: ExpressionImpl, private readonly additionalOrderBy: ExpressionImpl[] = [], private readonly partitionBy: ExpressionImpl[] = [], private readonly descending?: boolean) {
758
+ super();
759
+ }
760
+
761
+ toJSON(): CumsumExpression {
762
+ return {
763
+ type: 'cumsum',
764
+ value: this.value.toJSON(),
765
+ additionalOrderBy: this.additionalOrderBy.map((expr) => expr.toJSON()),
766
+ partitionBy: this.partitionBy.map((expr) => expr.toJSON()),
767
+ descending: this.descending,
768
+ };
769
+ }
770
+
771
+ getAlias(): string {
772
+ return this._alias || `cumsum_${this.value.getAlias()}`;
773
+ }
774
+
775
+ protected clone(): ExpressionImpl {
776
+ const cloned = new CumsumExpressionImpl(this.value, this.additionalOrderBy, this.partitionBy, this.descending);
777
+ cloned._alias = this._alias;
778
+ return cloned;
779
+ }
780
+ }
781
+
782
+ export class AggregationExpressionImpl extends ExpressionImpl {
783
+ constructor(public operation: AggregationType, public expr?: ExpressionImpl) {
784
+ super();
785
+ }
786
+
787
+ toJSON(): WindowExpression {
788
+ return {
789
+ type: 'aggregate',
790
+ aggregation: this.operation,
791
+ value: this.expr?.toJSON() || { type: 'const', value: 1 },
792
+ partitionBy: [],
793
+ };
794
+ }
795
+
796
+ getAlias(): string {
797
+ return this._alias || `${this.operation}${this.expr ? '_' + this.expr.getAlias() : ''}`;
798
+ }
799
+
800
+ protected clone(): ExpressionImpl {
801
+ const cloned = new AggregationExpressionImpl(this.operation, this.expr);
802
+ cloned._alias = this._alias;
803
+ return cloned;
804
+ }
805
+ }
806
+
807
+ export class WindowExpressionImpl extends ExpressionImpl {
808
+ constructor(private readonly expr: ExpressionImpl, private readonly aggregation: AggregationType, private readonly partitionBy: ExpressionImpl[]) {
809
+ super();
810
+ }
811
+
812
+ toJSON(): WindowExpression {
813
+ return {
814
+ type: 'aggregate',
815
+ aggregation: this.aggregation,
816
+ value: this.expr.toJSON(),
817
+ partitionBy: this.partitionBy.map((expr) => expr.toJSON()),
818
+ };
819
+ }
820
+
821
+ getAlias(): string {
822
+ return this._alias || `${this.expr.getAlias()}_window`;
823
+ }
824
+
825
+ protected clone(): ExpressionImpl {
826
+ const cloned = new WindowExpressionImpl(this.expr, this.aggregation, this.partitionBy);
827
+ cloned._alias = this._alias;
828
+ return cloned;
829
+ }
830
+ }
831
+
832
+ export class StringDistanceExpressionImpl extends ExpressionImpl {
833
+ constructor(private readonly string1: ExpressionImpl, private readonly string2: ExpressionImpl, private readonly metric: StringDistanceMetric, private readonly returnSimilarity?: boolean) {
834
+ super();
835
+ }
836
+
837
+ toJSON(): StringDistanceExpression {
838
+ return {
839
+ type: 'string_distance',
840
+ metric: this.metric,
841
+ string1: this.string1.toJSON(),
842
+ string2: this.string2.toJSON(),
843
+ returnSimilarity: this.returnSimilarity || false,
844
+ };
845
+ }
846
+
847
+ getAlias(): string {
848
+ return this._alias || `${this.string1.getAlias()}_distance_${this.string2.getAlias()}`;
849
+ }
850
+
851
+ protected clone(): ExpressionImpl {
852
+ const cloned = new StringDistanceExpressionImpl(this.string1, this.string2, this.metric, this.returnSimilarity);
853
+ cloned._alias = this._alias;
854
+ return cloned;
855
+ }
856
+ }
857
+
858
+ export class FuzzyStringFilterExpressionImpl extends ExpressionImpl {
859
+ constructor(private readonly expr: ExpressionImpl, private readonly pattern: ExpressionImpl, private readonly metric: FuzzyFilterDistanceMetric, private readonly bound: number) {
860
+ super();
861
+ }
862
+
863
+ toJSON(): FuzzyStringFilterExpression {
864
+ return {
865
+ type: 'fuzzy_string_filter',
866
+ metric: this.metric,
867
+ value: this.expr.toJSON(),
868
+ pattern: this.pattern.toJSON(),
869
+ bound: this.bound,
870
+ };
871
+ }
872
+
873
+ getAlias(): string {
874
+ return this._alias || `${this.expr.getAlias()}_fuzzy_filter`;
875
+ }
876
+
877
+ protected clone(): ExpressionImpl {
878
+ const cloned = new FuzzyStringFilterExpressionImpl(this.expr, this.pattern, this.metric, this.bound);
879
+ cloned._alias = this._alias;
880
+ return cloned;
881
+ }
882
+ }
883
+
884
+ export class RankExpressionImpl extends ExpressionImpl {
885
+ constructor(
886
+ private readonly orderBy: ExpressionImpl[],
887
+ private readonly partitionBy: ExpressionImpl[],
888
+ private readonly descending: boolean,
889
+ ) {
890
+ super();
891
+ }
892
+
893
+ toJSON(): RankExpression {
894
+ return {
895
+ type: 'rank',
896
+ orderBy: this.orderBy.map((e) => e.toJSON()),
897
+ partitionBy: this.partitionBy.map((e) => e.toJSON()),
898
+ descending: this.descending || undefined,
899
+ };
900
+ }
901
+
902
+ getAlias(): string {
903
+ const order = this.orderBy.map((e) => e.getAlias()).join('_');
904
+ const part = this.partitionBy.map((e) => e.getAlias()).join('_');
905
+ const dir = this.descending ? 'desc' : 'asc';
906
+ return this._alias || `rank_${order}${part ? `_over_${part}` : ''}_${dir}`;
907
+ }
908
+
909
+ protected clone(): ExpressionImpl {
910
+ const cloned = new RankExpressionImpl(this.orderBy, this.partitionBy, this.descending);
911
+ cloned._alias = this._alias;
912
+ return cloned;
913
+ }
914
+ }
915
+
916
+ export class WhenThenOtherwiseExpressionImpl extends ExpressionImpl {
917
+ constructor(
918
+ private readonly conditions: Array<{ when: ExpressionImpl; then: ExpressionImpl }>,
919
+ private readonly otherwiseValue: ExpressionImpl,
920
+ ) {
921
+ super();
922
+ }
923
+
924
+ toJSON(): WhenThenOtherwiseExpression {
925
+ return {
926
+ type: 'when_then_otherwise',
927
+ conditions: this.conditions.map((clause) => ({
928
+ when: clause.when.toJSON(),
929
+ then: clause.then.toJSON(),
930
+ })),
931
+ otherwise: this.otherwiseValue.toJSON(),
932
+ };
933
+ }
934
+
935
+ getAlias(): string {
936
+ return this._alias || 'conditional';
937
+ }
938
+
939
+ protected clone(): ExpressionImpl {
940
+ const cloned = new WhenThenOtherwiseExpressionImpl(this.conditions, this.otherwiseValue);
941
+ cloned._alias = this._alias;
942
+ return cloned;
943
+ }
944
+ }