@balena/abstract-sql-compiler 8.4.0 → 8.4.1

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.
@@ -4,26 +4,106 @@ import { Dictionary } from 'lodash';
4
4
  import {
5
5
  AbstractSqlQuery,
6
6
  AbstractSqlType,
7
+ AddDateDurationNode,
8
+ AddDateNumberNode,
9
+ AddNode,
10
+ AggregateJSONNode,
11
+ AliasNode,
7
12
  AndNode,
13
+ AnyNode,
8
14
  AnyTypeNodes,
15
+ BetweenNode,
9
16
  BindNode,
17
+ BitwiseAndNode,
18
+ BitwiseShiftRightNode,
19
+ BooleanNode,
20
+ BooleanTypeNodes,
10
21
  CaseNode,
22
+ CastNode,
23
+ CeilingNode,
24
+ CharacterLengthNode,
25
+ CoalesceNode,
26
+ ConcatenateNode,
27
+ ConcatenateWithSeparatorNode,
28
+ CrossJoinNode,
29
+ CurrentDateNode,
30
+ CurrentTimestampNode,
31
+ DateTruncNode,
32
+ DeleteQueryNode,
33
+ DivideNode,
11
34
  DurationNode,
35
+ ElseNode,
36
+ EqualsNode,
37
+ ExistsNode,
38
+ ExtractJSONPathAsTextNode,
39
+ ExtractNumericDateTypeNodes,
40
+ FieldNode,
41
+ FieldsNode,
42
+ FloorNode,
43
+ FromNode,
44
+ FullJoinNode,
45
+ GreaterThanNode,
46
+ GreaterThanOrEqualNode,
47
+ GroupByNode,
48
+ HavingNode,
49
+ InNode,
50
+ InnerJoinNode,
51
+ InsertQueryNode,
12
52
  IntegerNode,
53
+ IsDistinctFromNode,
54
+ IsNotDistinctFromNode,
55
+ JSONTypeNodes,
56
+ JoinTypeNodes,
57
+ LeftJoinNode,
58
+ LessThanNode,
59
+ LessThanOrEqualNode,
60
+ LikeNode,
61
+ LimitNode,
62
+ LowerNode,
63
+ MultiplyNode,
64
+ NotEqualsNode,
65
+ NotExistsNode,
66
+ NotNode,
13
67
  NullNode,
14
68
  NumberNode,
69
+ NumberTypeNodes,
70
+ OffsetNode,
15
71
  OrNode,
16
72
  OrderByNode,
17
73
  RealNode,
74
+ ReferencedFieldNode,
18
75
  ReplaceNode,
19
- SelectQueryNode,
20
76
  StrictBooleanTypeNodes,
21
77
  StrictDateTypeNodes,
22
78
  StrictNumberTypeNodes,
23
79
  StrictTextTypeNodes,
80
+ RightJoinNode,
81
+ RightNode,
82
+ RoundNode,
83
+ SelectNode,
84
+ SelectQueryNode,
85
+ StrPosNode,
86
+ SubtractDateDateNode,
87
+ SubtractDateDurationNode,
88
+ SubtractDateNumberNode,
89
+ SubtractNode,
90
+ TableNode,
91
+ TextArrayTypeNodes,
24
92
  TextNode,
25
93
  TextTypeNodes,
94
+ ToDateNode,
95
+ ToJSONNode,
96
+ ToTimeNode,
97
+ TrimNode,
98
+ UnionQueryNode,
99
+ UnknownTypeNodes,
100
+ UpdateQueryNode,
101
+ UpperNode,
102
+ ValuesNode,
26
103
  ValuesNodeTypes,
104
+ WhenNode,
105
+ WhereNode,
106
+ FromTypeNode,
27
107
  } from './AbstractSQLCompiler';
28
108
  import * as AbstractSQLRules2SQL from './AbstractSQLRules2SQL';
29
109
 
@@ -35,11 +115,11 @@ const {
35
115
  isNotNullable,
36
116
  } = AbstractSQLRules2SQL;
37
117
 
38
- type OptimisationMatchFn = (
39
- args: AbstractSqlType[],
40
- ) => AbstractSqlQuery | false;
41
- type MetaMatchFn = (args: AbstractSqlQuery) => AbstractSqlQuery;
42
- type MatchFn = (args: AbstractSqlType[]) => AbstractSqlQuery;
118
+ type OptimisationMatchFn<T extends AnyTypeNodes> =
119
+ | ((args: AbstractSqlType[]) => T | false)
120
+ | ((args: AbstractSqlType[]) => T);
121
+ type MetaMatchFn<T extends AnyTypeNodes> = (args: AbstractSqlQuery) => T;
122
+ type MatchFn<T extends AnyTypeNodes> = (args: AbstractSqlType[]) => T;
43
123
 
44
124
  const deprecated = (() => {
45
125
  const deprecationMessages = {
@@ -114,28 +194,29 @@ const isEmptySelectQuery = (query: AnyTypeNodes): boolean => {
114
194
  };
115
195
 
116
196
  const rewriteMatch =
117
- (
118
- name: string,
197
+ <I extends AnyTypeNodes, O extends AnyTypeNodes>(
198
+ name: I[0],
119
199
  matchers: Array<(args: AbstractSqlType) => AbstractSqlType>,
120
- rewriteFn: MatchFn,
121
- ): MatchFn =>
200
+ rewriteFn: MatchFn<O>,
201
+ ): MatchFn<O> =>
122
202
  (args) => {
123
203
  checkArgs(name, args, matchers.length);
124
204
  return rewriteFn(
125
205
  args.map((arg, index) => {
126
- // Type cast because of cases where not all nodes are arrays, but we do handle them correctly where they can occur
127
- return matchers[index](arg as AbstractSqlType);
206
+ return matchers[index](arg);
128
207
  }),
129
208
  );
130
209
  };
131
210
 
132
- const matchArgs = (
133
- name: string,
211
+ const matchArgs = <T extends AnyTypeNodes>(
212
+ name: T[0],
134
213
  ...matchers: Array<(args: AbstractSqlType) => AbstractSqlType>
135
- ): MatchFn => rewriteMatch(name, matchers, (args) => [name, ...args]);
214
+ ): MatchFn<T> => rewriteMatch(name, matchers, (args) => [name, ...args] as T);
136
215
 
137
- const tryMatches = (...matchers: OptimisationMatchFn[]): MatchFn => {
138
- return (args) => {
216
+ const tryMatches = <T extends AnyTypeNodes>(
217
+ ...matchers: Array<OptimisationMatchFn<T>>
218
+ ): MatchFn<T> => {
219
+ return (args): T => {
139
220
  let err;
140
221
  for (const matcher of matchers) {
141
222
  try {
@@ -151,7 +232,7 @@ const tryMatches = (...matchers: OptimisationMatchFn[]): MatchFn => {
151
232
  };
152
233
  };
153
234
 
154
- const AnyValue: MetaMatchFn = (args) => {
235
+ const AnyValue: MetaMatchFn<AnyTypeNodes> = (args) => {
155
236
  const [type, ...rest] = args;
156
237
  if (type === 'Case') {
157
238
  return typeRules[type](rest);
@@ -172,7 +253,7 @@ const AnyValue: MetaMatchFn = (args) => {
172
253
 
173
254
  return UnknownValue(args);
174
255
  };
175
- const UnknownValue: MetaMatchFn = (args) => {
256
+ const UnknownValue: MetaMatchFn<UnknownTypeNodes> = (args) => {
176
257
  if (args === null) {
177
258
  helped = true;
178
259
  deprecated.legacyNull();
@@ -201,11 +282,13 @@ const UnknownValue: MetaMatchFn = (args) => {
201
282
  }
202
283
  };
203
284
  const MatchValue =
204
- (matcher: (type: string | AbstractSqlQuery) => type is string): MetaMatchFn =>
285
+ <T extends AnyTypeNodes>(
286
+ matcher: (type: unknown) => type is T[0],
287
+ ): MetaMatchFn<T | UnknownTypeNodes> =>
205
288
  (args) => {
206
289
  const [type, ...rest] = args;
207
290
  if (matcher(type)) {
208
- return typeRules[type](rest);
291
+ return typeRules[type as keyof typeof typeRules](rest) as T;
209
292
  }
210
293
  return UnknownValue(args);
211
294
  };
@@ -230,7 +313,9 @@ const isTextValue = (
230
313
  AbstractSQLRules2SQL.isTextValue(type)
231
314
  );
232
315
  };
233
- const TextValue = MatchValue(isTextValue);
316
+ const TextValue = MatchValue<TextTypeNodes>(
317
+ isTextValue as typeof AbstractSQLRules2SQL.isTextValue,
318
+ );
234
319
 
235
320
  const isNumericValue = (
236
321
  type: unknown,
@@ -259,7 +344,9 @@ const isBooleanValue = (
259
344
  AbstractSQLRules2SQL.isBooleanValue(type)
260
345
  );
261
346
  };
262
- const BooleanValue = MatchValue(isBooleanValue);
347
+ const BooleanValue = MatchValue<BooleanTypeNodes>(
348
+ isBooleanValue as typeof AbstractSQLRules2SQL.isBooleanValue,
349
+ );
263
350
 
264
351
  const isDateValue = (type: unknown): type is 'Now' | StrictDateTypeNodes[0] => {
265
352
  return type === 'Now' || AbstractSQLRules2SQL.isDateValue(type);
@@ -267,16 +354,16 @@ const isDateValue = (type: unknown): type is 'Now' | StrictDateTypeNodes[0] => {
267
354
  const DateValue = MatchValue(isDateValue);
268
355
 
269
356
  const { isJSONValue } = AbstractSQLRules2SQL;
270
- const JSONValue = MatchValue(isJSONValue);
357
+ const JSONValue = MatchValue<JSONTypeNodes>(isJSONValue);
271
358
 
272
359
  const { isDurationValue } = AbstractSQLRules2SQL;
273
360
  const DurationValue = MatchValue(isDurationValue);
274
361
 
275
362
  const { isArrayValue } = AbstractSQLRules2SQL;
276
- const ArrayValue = MatchValue(isArrayValue);
363
+ const ArrayValue = MatchValue<TextArrayTypeNodes>(isArrayValue);
277
364
 
278
365
  const { isFieldValue } = AbstractSQLRules2SQL;
279
- const Field: MetaMatchFn = (args) => {
366
+ const Field: MetaMatchFn<FieldNode | ReferencedFieldNode> = (args) => {
280
367
  const [type, ...rest] = args;
281
368
  if (isFieldValue(type)) {
282
369
  return typeRules[type](rest);
@@ -290,7 +377,7 @@ const AnyNotNullValue = (args: any): boolean => {
290
377
  };
291
378
 
292
379
  const FieldOp =
293
- (type: string): OptimisationMatchFn =>
380
+ (type: string): OptimisationMatchFn<AnyTypeNodes> =>
294
381
  (args) => {
295
382
  if (
296
383
  AnyNotNullValue(args[0]) === false ||
@@ -309,12 +396,12 @@ const FieldOp =
309
396
  const FieldEquals = FieldOp('Equals');
310
397
  const FieldNotEquals = FieldOp('NotEquals');
311
398
 
312
- const Comparison = (
399
+ const Comparison = <T extends AnyTypeNodes>(
313
400
  comparison: keyof typeof AbstractSQLRules2SQL.comparisons,
314
- ): MatchFn => {
315
- return matchArgs(comparison, AnyValue, AnyValue);
401
+ ): MatchFn<T> => {
402
+ return matchArgs<T>(comparison, AnyValue, AnyValue);
316
403
  };
317
- const NumberMatch = (type: string): MatchFn => {
404
+ const NumberMatch = <T extends AnyTypeNodes>(type: T[0]): MatchFn<T> => {
318
405
  return matchArgs(type, (arg) => {
319
406
  if (typeof arg !== 'number') {
320
407
  throw new SyntaxError(`${type} expected number but got ${typeof arg}`);
@@ -323,45 +410,65 @@ const NumberMatch = (type: string): MatchFn => {
323
410
  });
324
411
  };
325
412
 
326
- const MathOp = (type: AbstractSQLRules2SQL.MathOps): MatchFn => {
413
+ type ExtractNodeType<T, U> = T extends any[]
414
+ ? T[0] extends U
415
+ ? T
416
+ : never
417
+ : never;
418
+ const MathOp = <
419
+ T extends ExtractNodeType<NumberTypeNodes, AbstractSQLRules2SQL.MathOps>,
420
+ >(
421
+ type: T[0],
422
+ ): MatchFn<T> => {
327
423
  return matchArgs(type, NumericValue, NumericValue);
328
424
  };
329
425
 
330
- const ExtractNumericDatePart = (type: string): MatchFn => {
426
+ const ExtractNumericDatePart = <T extends ExtractNumericDateTypeNodes>(
427
+ type: T[0],
428
+ ): MatchFn<T> => {
331
429
  return matchArgs(type, DateValue);
332
430
  };
333
431
 
334
- const Concatenate: MatchFn = (args) => {
432
+ const Concatenate: MatchFn<ConcatenateNode> = (args) => {
335
433
  checkMinArgs('Concatenate', args, 1);
336
434
  return [
337
435
  'Concatenate',
338
- ...args.map((arg) => {
436
+ ...(args.map((arg) => {
339
437
  if (!isAbstractSqlQuery(arg)) {
340
438
  throw new SyntaxError(
341
439
  `Expected AbstractSqlQuery array but got ${typeof arg}`,
342
440
  );
343
441
  }
344
442
  return TextValue(arg);
345
- }),
443
+ }) as [
444
+ ReturnType<typeof TextValue>,
445
+ ...Array<ReturnType<typeof TextValue>>,
446
+ ]),
346
447
  ];
347
448
  };
348
449
 
349
- const ConcatenateWithSeparator: MatchFn = (args) => {
450
+ const ConcatenateWithSeparator: MatchFn<ConcatenateWithSeparatorNode> = (
451
+ args,
452
+ ) => {
350
453
  checkMinArgs('ConcatenateWithSeparator', args, 2);
351
454
  return [
352
455
  'ConcatenateWithSeparator',
353
- ...args.map((arg) => {
456
+ ...(args.map((arg) => {
354
457
  if (!isAbstractSqlQuery(arg)) {
355
458
  throw new SyntaxError(
356
459
  `Expected AbstractSqlQuery array but got ${typeof arg}`,
357
460
  );
358
461
  }
359
462
  return TextValue(arg);
360
- }),
463
+ }) as [
464
+ ReturnType<typeof TextValue>,
465
+ ReturnType<typeof TextValue>,
466
+ ...Array<ReturnType<typeof TextValue>>,
467
+ ]),
361
468
  ];
362
469
  };
363
470
 
364
- const Text: MatchFn = matchArgs('Text', _.identity);
471
+ const Text = matchArgs<TextNode>('Text', _.identity);
365
472
 
366
473
  const Value = (arg: string): ValuesNodeTypes => {
367
474
  const $arg = arg as boolean | string;
@@ -396,7 +503,7 @@ const Value = (arg: string): ValuesNodeTypes => {
396
503
  }
397
504
  };
398
505
 
399
- const FromMatch: MetaMatchFn = (args) => {
506
+ const FromMatch: MetaMatchFn<FromTypeNode[keyof FromTypeNode]> = (args) => {
400
507
  if (typeof args === 'string') {
401
508
  deprecated.legacyTable();
402
509
  return ['Table', args];
@@ -408,16 +515,16 @@ const FromMatch: MetaMatchFn = (args) => {
408
515
  return typeRules[type](rest);
409
516
  case 'Table':
410
517
  checkArgs('Table', rest, 1);
411
- return ['Table', rest[0]];
518
+ return ['Table', rest[0] as TableNode[1]];
412
519
  default:
413
520
  throw new SyntaxError(`From does not support ${type}`);
414
521
  }
415
522
  };
416
523
 
417
- const MaybeAlias = (
524
+ const MaybeAlias = <T extends AnyTypeNodes>(
418
525
  args: AbstractSqlQuery,
419
- matchFn: MetaMatchFn,
420
- ): AbstractSqlQuery => {
526
+ matchFn: MetaMatchFn<T>,
527
+ ): T | AliasNode<T> => {
421
528
  if (
422
529
  args.length === 2 &&
423
530
  args[0] !== 'Table' &&
@@ -433,24 +540,28 @@ const MaybeAlias = (
433
540
  switch (type) {
434
541
  case 'Alias':
435
542
  checkArgs('Alias', rest, 2);
436
- return ['Alias', matchFn(getAbstractSqlQuery(rest, 0)), rest[1]];
543
+ return [
544
+ 'Alias',
545
+ matchFn(getAbstractSqlQuery(rest, 0)),
546
+ rest[1] as AliasNode<T>[2],
547
+ ];
437
548
  default:
438
549
  return matchFn(args);
439
550
  }
440
551
  };
441
552
 
442
- const Lower = matchArgs('Lower', TextValue);
443
- const Upper = matchArgs('Upper', TextValue);
553
+ const Lower = matchArgs<LowerNode>('Lower', TextValue);
554
+ const Upper = matchArgs<UpperNode>('Upper', TextValue);
444
555
 
445
556
  const JoinMatch =
446
- (joinType: string): MatchFn =>
557
+ <T extends JoinTypeNodes>(joinType: T[0]): MatchFn<T> =>
447
558
  (args) => {
448
559
  if (args.length !== 1 && args.length !== 2) {
449
560
  throw new SyntaxError(`"${joinType}" requires 1/2 arg(s)`);
450
561
  }
451
562
  const from = MaybeAlias(getAbstractSqlQuery(args, 0), FromMatch);
452
563
  if (args.length === 1) {
453
- return [joinType, from];
564
+ return [joinType, from] as T;
454
565
  }
455
566
  const [type, ...rest] = getAbstractSqlQuery(args, 1);
456
567
  switch (type) {
@@ -458,7 +569,7 @@ const JoinMatch =
458
569
  if (joinType !== 'CrossJoin') {
459
570
  checkArgs('On', rest, 1);
460
571
  const ruleBody = BooleanValue(getAbstractSqlQuery(rest, 0));
461
- return [joinType, from, ['On', ruleBody]];
572
+ return [joinType, from, ['On', ruleBody]] as unknown as T;
462
573
  }
463
574
  default:
464
575
  throw new SyntaxError(
@@ -472,14 +583,24 @@ const AddDateMatcher = tryMatches(
472
583
  matchArgs('AddDateNumber', DateValue, NumericValue),
473
584
  );
474
585
 
475
- const SubtractDateMatcher = tryMatches(
476
- matchArgs('SubtractDateDate', DateValue, DateValue),
477
- matchArgs('SubtractDateDuration', DateValue, DurationValue),
478
- matchArgs('SubtractDateNumber', DateValue, NumericValue),
586
+ const SubtractDateMatcher = tryMatches<
587
+ SubtractDateDateNode | SubtractDateDurationNode | SubtractDateNumberNode
588
+ >(
589
+ matchArgs<SubtractDateDateNode>('SubtractDateDate', DateValue, DateValue),
590
+ matchArgs<SubtractDateDurationNode>(
591
+ 'SubtractDateDuration',
592
+ DateValue,
593
+ DurationValue,
594
+ ),
595
+ matchArgs<SubtractDateNumberNode>(
596
+ 'SubtractDateNumber',
597
+ DateValue,
598
+ NumericValue,
599
+ ),
479
600
  );
480
601
 
481
- const typeRules: Dictionary<MatchFn> = {
482
- UnionQuery: (args) => {
602
+ const typeRules = {
603
+ UnionQuery: (args): UnionQueryNode => {
483
604
  checkMinArgs('UnionQuery', args, 2);
484
605
  return [
485
606
  'UnionQuery',
@@ -500,16 +621,23 @@ const typeRules: Dictionary<MatchFn> = {
500
621
  }),
501
622
  ];
502
623
  },
503
- SelectQuery: (args) => {
504
- const tables: AbstractSqlQuery[] = [];
505
- let select: AbstractSqlQuery[] = [];
624
+ SelectQuery: (args): SelectQueryNode => {
625
+ const tables: Array<
626
+ | FromNode
627
+ | InnerJoinNode
628
+ | LeftJoinNode
629
+ | RightJoinNode
630
+ | FullJoinNode
631
+ | CrossJoinNode
632
+ > = [];
633
+ let select: SelectNode[] = [];
506
634
  const groups = {
507
- Where: [] as AbstractSqlQuery[],
508
- GroupBy: [] as AbstractSqlQuery[],
509
- Having: [] as AbstractSqlQuery[],
510
- OrderBy: [] as AbstractSqlQuery[],
511
- Limit: [] as AbstractSqlQuery[],
512
- Offset: [] as AbstractSqlQuery[],
635
+ Where: [] as WhereNode[],
636
+ GroupBy: [] as GroupByNode[],
637
+ Having: [] as HavingNode[],
638
+ OrderBy: [] as OrderByNode[],
639
+ Limit: [] as LimitNode[],
640
+ Offset: [] as OffsetNode[],
513
641
  };
514
642
  for (const arg of args) {
515
643
  if (!isAbstractSqlQuery(arg)) {
@@ -531,7 +659,15 @@ const typeRules: Dictionary<MatchFn> = {
531
659
  case 'RightJoin':
532
660
  case 'FullJoin':
533
661
  case 'CrossJoin':
534
- tables.push(typeRules[type](rest));
662
+ tables.push(
663
+ typeRules[type](rest) as
664
+ | FromNode
665
+ | InnerJoinNode
666
+ | LeftJoinNode
667
+ | RightJoinNode
668
+ | FullJoinNode
669
+ | CrossJoinNode,
670
+ );
535
671
  break;
536
672
  case 'Where':
537
673
  case 'GroupBy':
@@ -544,7 +680,16 @@ const typeRules: Dictionary<MatchFn> = {
544
680
  `'SelectQuery' can only accept one '${type}'`,
545
681
  );
546
682
  }
547
- groups[type] = [typeRules[type](rest)];
683
+ groups[type] = [
684
+ typeRules[type](rest) as
685
+ | WhereNode
686
+ | GroupByNode
687
+ | HavingNode
688
+ | OrderByNode
689
+ | LimitNode
690
+ // The cast as any is because I couldn't find a way to automatically match up the correct type based upon the group we're assigning
691
+ | OffsetNode as any,
692
+ ];
548
693
  break;
549
694
  default:
550
695
  throw new SyntaxError(`'SelectQuery' does not support '${type}'`);
@@ -563,7 +708,7 @@ const typeRules: Dictionary<MatchFn> = {
563
708
  ...groups.Offset,
564
709
  ];
565
710
  },
566
- Select: (args) => {
711
+ Select: (args): SelectNode => {
567
712
  checkArgs('Select', args, 1);
568
713
  return [
569
714
  'Select',
@@ -575,9 +720,9 @@ const typeRules: Dictionary<MatchFn> = {
575
720
  }
576
721
  return MaybeAlias(arg, AnyValue);
577
722
  }),
578
- ] as AbstractSqlQuery;
723
+ ];
579
724
  },
580
- From: (args) => {
725
+ From: (args): FromNode => {
581
726
  checkArgs('From', args, 1);
582
727
  return ['From', MaybeAlias(args[0] as AbstractSqlQuery, FromMatch)];
583
728
  },
@@ -590,12 +735,12 @@ const typeRules: Dictionary<MatchFn> = {
590
735
  const from = MaybeAlias(getAbstractSqlQuery(args, 0), FromMatch);
591
736
  return ['CrossJoin', from];
592
737
  },
593
- Where: matchArgs('Where', BooleanValue),
738
+ Where: matchArgs<WhereNode>('Where', BooleanValue),
594
739
  GroupBy: (args) => {
595
740
  checkArgs('GroupBy', args, 1);
596
741
  const groups = getAbstractSqlQuery(args, 0);
597
742
  checkMinArgs('GroupBy groups', groups, 1);
598
- return ['GroupBy', groups.map(AnyValue)] as AbstractSqlQuery;
743
+ return ['GroupBy', groups.map(AnyValue)] as GroupByNode;
599
744
  },
600
745
  Having: matchArgs('Having', BooleanValue),
601
746
  OrderBy: (args) => {
@@ -624,31 +769,38 @@ const typeRules: Dictionary<MatchFn> = {
624
769
  },
625
770
  Average: matchArgs('Average', NumericValue),
626
771
  Sum: matchArgs('Sum', NumericValue),
627
- Field: matchArgs('Field', _.identity),
628
- ReferencedField: matchArgs('ReferencedField', _.identity, _.identity),
629
- Cast: matchArgs('Cast', AnyValue, _.identity),
772
+ Field: matchArgs<FieldNode>('Field', _.identity),
773
+ ReferencedField: matchArgs<ReferencedFieldNode>(
774
+ 'ReferencedField',
775
+ _.identity,
776
+ _.identity,
777
+ ),
778
+ Cast: matchArgs<CastNode>('Cast', AnyValue, _.identity),
630
779
  Number: NumberMatch('Number'),
631
780
  Real: NumberMatch('Real'),
632
781
  Integer: NumberMatch('Integer'),
633
- Boolean: matchArgs('Boolean', _.identity),
782
+ Boolean: matchArgs<BooleanNode>('Boolean', _.identity),
634
783
  EmbeddedText: matchArgs('EmbeddedText', _.identity),
635
- Null: matchArgs('Null'),
636
- CurrentTimestamp: matchArgs('CurrentTimestamp'),
637
- CurrentDate: matchArgs('CurrentDate'),
784
+ Null: matchArgs<NullNode>('Null'),
785
+ CurrentTimestamp: matchArgs<CurrentTimestampNode>('CurrentTimestamp'),
786
+ CurrentDate: matchArgs<CurrentDateNode>('CurrentDate'),
638
787
  AggregateJSON: tryMatches(
639
- Helper<OptimisationMatchFn>((args) => {
788
+ Helper<OptimisationMatchFn<AggregateJSONNode>>((args) => {
640
789
  checkArgs('AggregateJSON', args, 1);
641
790
  const fieldArg = getAbstractSqlQuery(args, 0);
642
791
  if (!isFieldValue(fieldArg[0])) {
643
792
  deprecated.legacyAggregateJSON();
644
- return ['AggregateJSON', ['ReferencedField', ...fieldArg]];
793
+ return [
794
+ 'AggregateJSON',
795
+ ['ReferencedField', ...fieldArg] as ReferencedFieldNode,
796
+ ];
645
797
  }
646
798
  return false;
647
799
  }),
648
- matchArgs('AggregateJSON', Field),
800
+ matchArgs<AggregateJSONNode>('AggregateJSON', Field),
649
801
  ),
650
- Equals: tryMatches(
651
- Helper<OptimisationMatchFn>((args) => {
802
+ Equals: tryMatches<NotExistsNode | EqualsNode>(
803
+ Helper<OptimisationMatchFn<NotExistsNode>>((args) => {
652
804
  checkArgs('Equals', args, 2);
653
805
  let valueIndex;
654
806
  if (args[0][0] === 'Null') {
@@ -661,10 +813,10 @@ const typeRules: Dictionary<MatchFn> = {
661
813
 
662
814
  return ['NotExists', getAbstractSqlQuery(args, valueIndex)];
663
815
  }),
664
- Comparison('Equals'),
816
+ Comparison<EqualsNode>('Equals'),
665
817
  ),
666
- NotEquals: tryMatches(
667
- Helper<OptimisationMatchFn>((args) => {
818
+ NotEquals: tryMatches<NotEqualsNode | ExistsNode>(
819
+ Helper<OptimisationMatchFn<ExistsNode>>((args) => {
668
820
  checkArgs('NotEquals', args, 2);
669
821
  let valueIndex;
670
822
  if (args[0][0] === 'Null') {
@@ -677,15 +829,15 @@ const typeRules: Dictionary<MatchFn> = {
677
829
 
678
830
  return ['Exists', getAbstractSqlQuery(args, valueIndex)];
679
831
  }),
680
- Comparison('NotEquals'),
832
+ Comparison<NotEqualsNode>('NotEquals'),
681
833
  ),
682
- GreaterThan: Comparison('GreaterThan'),
683
- GreaterThanOrEqual: Comparison('GreaterThanOrEqual'),
684
- LessThan: Comparison('LessThan'),
685
- LessThanOrEqual: Comparison('LessThanOrEqual'),
686
- Like: Comparison('Like'),
834
+ GreaterThan: Comparison<GreaterThanNode>('GreaterThan'),
835
+ GreaterThanOrEqual: Comparison<GreaterThanOrEqualNode>('GreaterThanOrEqual'),
836
+ LessThan: Comparison<LessThanNode>('LessThan'),
837
+ LessThanOrEqual: Comparison<LessThanOrEqualNode>('LessThanOrEqual'),
838
+ Like: Comparison<LikeNode>('Like'),
687
839
  IsNotDistinctFrom: tryMatches(
688
- Helper<OptimisationMatchFn>((args) => {
840
+ Helper<OptimisationMatchFn<NotExistsNode>>((args) => {
689
841
  checkArgs('IsNotDistinctFrom', args, 2);
690
842
  let valueIndex;
691
843
  if (args[0][0] === 'Null') {
@@ -698,20 +850,19 @@ const typeRules: Dictionary<MatchFn> = {
698
850
 
699
851
  return ['NotExists', getAbstractSqlQuery(args, valueIndex)];
700
852
  }),
701
- Helper<OptimisationMatchFn>((args) => {
702
- checkArgs('IsDistinctFrom', args, 2);
703
- if (
704
- isNotNullable(getAbstractSqlQuery(args, 0)) &&
705
- isNotNullable(getAbstractSqlQuery(args, 1))
706
- ) {
707
- return ['Equals', ...args];
853
+ Helper<OptimisationMatchFn<EqualsNode>>((args) => {
854
+ checkArgs('IsNotDistinctFrom', args, 2);
855
+ const a = getAbstractSqlQuery(args, 0);
856
+ const b = getAbstractSqlQuery(args, 1);
857
+ if (isNotNullable(a) && isNotNullable(b)) {
858
+ return ['Equals', a, b];
708
859
  }
709
860
  return false;
710
861
  }),
711
- matchArgs('IsNotDistinctFrom', AnyValue, AnyValue),
862
+ matchArgs<IsNotDistinctFromNode>('IsNotDistinctFrom', AnyValue, AnyValue),
712
863
  ),
713
864
  IsDistinctFrom: tryMatches(
714
- Helper<OptimisationMatchFn>((args) => {
865
+ Helper<OptimisationMatchFn<ExistsNode>>((args) => {
715
866
  checkArgs('IsDistinctFrom', args, 2);
716
867
  let valueIndex;
717
868
  if (args[0][0] === 'Null') {
@@ -724,34 +875,54 @@ const typeRules: Dictionary<MatchFn> = {
724
875
 
725
876
  return ['Exists', getAbstractSqlQuery(args, valueIndex)];
726
877
  }),
727
- Helper<OptimisationMatchFn>((args) => {
878
+ Helper<OptimisationMatchFn<NotEqualsNode>>((args) => {
728
879
  checkArgs('IsDistinctFrom', args, 2);
729
- if (
730
- isNotNullable(getAbstractSqlQuery(args, 0)) &&
731
- isNotNullable(getAbstractSqlQuery(args, 1))
732
- ) {
733
- return ['NotEquals', ...args];
880
+ const a = getAbstractSqlQuery(args, 0);
881
+ const b = getAbstractSqlQuery(args, 1);
882
+ if (isNotNullable(a) && isNotNullable(b)) {
883
+ return ['NotEquals', a, b];
734
884
  }
735
885
  return false;
736
886
  }),
737
- matchArgs('IsDistinctFrom', AnyValue, AnyValue),
887
+ matchArgs<IsDistinctFromNode>('IsDistinctFrom', AnyValue, AnyValue),
738
888
  ),
739
- Between: matchArgs('Between', AnyValue, AnyValue, AnyValue),
740
- Add: tryMatches(MathOp('Add'), Helper(AddDateMatcher)),
741
- Subtract: tryMatches(MathOp('Subtract'), Helper(SubtractDateMatcher)),
742
- SubtractDateDate: matchArgs('SubtractDateDate', DateValue, DateValue),
743
- SubtractDateNumber: matchArgs('SubtractDateNumber', DateValue, NumericValue),
744
- SubtractDateDuration: matchArgs(
889
+ Between: matchArgs<BetweenNode>('Between', AnyValue, AnyValue, AnyValue),
890
+ Add: tryMatches(MathOp<AddNode>('Add'), Helper(AddDateMatcher)),
891
+ Subtract: tryMatches<
892
+ | SubtractNode
893
+ | SubtractDateDateNode
894
+ | SubtractDateNumberNode
895
+ | SubtractDateDurationNode
896
+ >(MathOp<SubtractNode>('Subtract'), Helper(SubtractDateMatcher)),
897
+ SubtractDateDate: matchArgs<SubtractDateDateNode>(
898
+ 'SubtractDateDate',
899
+ DateValue,
900
+ DateValue,
901
+ ),
902
+ SubtractDateNumber: matchArgs<SubtractDateNumberNode>(
903
+ 'SubtractDateNumber',
904
+ DateValue,
905
+ NumericValue,
906
+ ),
907
+ SubtractDateDuration: matchArgs<SubtractDateDurationNode>(
745
908
  'SubtractDateDuration',
746
909
  DateValue,
747
910
  DurationValue,
748
911
  ),
749
- AddDateDuration: matchArgs('AddDateDuration', DateValue, DurationValue),
750
- AddDateNumber: matchArgs('AddDateNumber', DateValue, NumericValue),
751
- Multiply: MathOp('Multiply'),
752
- Divide: MathOp('Divide'),
753
- BitwiseAnd: MathOp('BitwiseAnd'),
754
- BitwiseShiftRight: MathOp('BitwiseShiftRight'),
912
+ AddDateDuration: matchArgs<AddDateDurationNode>(
913
+ 'AddDateDuration',
914
+ DateValue,
915
+ DurationValue,
916
+ ),
917
+ AddDateNumber: matchArgs<AddDateNumberNode>(
918
+ 'AddDateNumber',
919
+ DateValue,
920
+ NumericValue,
921
+ ),
922
+ Multiply: MathOp<MultiplyNode>('Multiply'),
923
+ Divide: MathOp<DivideNode>('Divide'),
924
+ BitwiseAnd: MathOp<BitwiseAndNode>('BitwiseAnd'),
925
+ BitwiseShiftRight: MathOp<BitwiseShiftRightNode>('BitwiseShiftRight'),
755
926
  Year: ExtractNumericDatePart('Year'),
756
927
  Month: ExtractNumericDatePart('Month'),
757
928
  Day: ExtractNumericDatePart('Day'),
@@ -763,9 +934,9 @@ const typeRules: Dictionary<MatchFn> = {
763
934
  Concat: Concatenate,
764
935
  Concatenate,
765
936
  ConcatenateWithSeparator,
766
- Replace: matchArgs('Replace', TextValue, TextValue, TextValue),
767
- CharacterLength: matchArgs('CharacterLength', TextValue),
768
- StrPos: matchArgs('StrPos', TextValue, TextValue),
937
+ Replace: matchArgs<ReplaceNode>('Replace', TextValue, TextValue, TextValue),
938
+ CharacterLength: matchArgs<CharacterLengthNode>('CharacterLength', TextValue),
939
+ StrPos: matchArgs<StrPosNode>('StrPos', TextValue, TextValue),
769
940
  Substring: (args) => {
770
941
  checkMinArgs('Substring', args, 2);
771
942
  const str = TextValue(getAbstractSqlQuery(args, 0));
@@ -779,21 +950,21 @@ const typeRules: Dictionary<MatchFn> = {
779
950
  });
780
951
  return ['Substring', str, ...nums];
781
952
  },
782
- Right: matchArgs('Right', TextValue, NumericValue),
953
+ Right: matchArgs<RightNode>('Right', TextValue, NumericValue),
783
954
  Tolower: Lower,
784
955
  ToLower: Lower,
785
956
  Lower,
786
957
  Toupper: Upper,
787
958
  ToUpper: Upper,
788
959
  Upper,
789
- Trim: matchArgs('Trim', TextValue),
790
- Round: matchArgs('Round', NumericValue),
791
- Floor: matchArgs('Floor', NumericValue),
792
- Ceiling: matchArgs('Ceiling', NumericValue),
793
- ToDate: matchArgs('ToDate', DateValue),
794
- DateTrunc: matchArgs('DateTrunc', TextValue, DateValue),
795
- ToTime: matchArgs('ToTime', DateValue),
796
- ExtractJSONPathAsText: (args) => {
960
+ Trim: matchArgs<TrimNode>('Trim', TextValue),
961
+ Round: matchArgs<RoundNode>('Round', NumericValue),
962
+ Floor: matchArgs<FloorNode>('Floor', NumericValue),
963
+ Ceiling: matchArgs<CeilingNode>('Ceiling', NumericValue),
964
+ ToDate: matchArgs<ToDateNode>('ToDate', DateValue),
965
+ DateTrunc: matchArgs<DateTruncNode>('DateTrunc', TextValue, DateValue),
966
+ ToTime: matchArgs<ToTimeNode>('ToTime', DateValue),
967
+ ExtractJSONPathAsText: (args): ExtractJSONPathAsTextNode => {
797
968
  checkMinArgs('ExtractJSONPathAsText', args, 1);
798
969
  const json = JSONValue(getAbstractSqlQuery(args, 0));
799
970
  const path = ArrayValue(getAbstractSqlQuery(args, 1));
@@ -803,17 +974,24 @@ const typeRules: Dictionary<MatchFn> = {
803
974
  // Allow for populated and empty arrays
804
975
  return ['TextArray', ...args.map(TextValue)];
805
976
  },
806
- ToJSON: matchArgs('ToJSON', AnyValue),
807
- Any: matchArgs('Any', AnyValue, _.identity),
808
- Coalesce: (args) => {
977
+ ToJSON: matchArgs<ToJSONNode>('ToJSON', AnyValue),
978
+ Any: matchArgs<AnyNode>('Any', AnyValue, _.identity),
979
+ Coalesce: (args): CoalesceNode => {
809
980
  checkMinArgs('Coalesce', args, 2);
810
- return ['Coalesce', ...args.map(AnyValue)];
981
+ return [
982
+ 'Coalesce',
983
+ ...(args.map(AnyValue) as [
984
+ AnyTypeNodes,
985
+ AnyTypeNodes,
986
+ ...AnyTypeNodes[],
987
+ ]),
988
+ ];
811
989
  },
812
990
  Case: (args) => {
813
991
  checkMinArgs('Case', args, 1);
814
992
  return [
815
993
  'Case',
816
- ...args.map((arg, index) => {
994
+ ...args.map((arg, index): WhenNode | ElseNode => {
817
995
  if (!isAbstractSqlQuery(arg)) {
818
996
  throw new SyntaxError(
819
997
  `Expected AbstractSqlQuery array but got ${typeof arg}`,
@@ -839,13 +1017,13 @@ const typeRules: Dictionary<MatchFn> = {
839
1017
  ] as CaseNode;
840
1018
  },
841
1019
  And: tryMatches(
842
- Helper<OptimisationMatchFn>((args) => {
1020
+ Helper<OptimisationMatchFn<AnyTypeNodes>>((args) => {
843
1021
  if (args.length !== 1) {
844
1022
  return false;
845
1023
  }
846
1024
  return getAbstractSqlQuery(args, 0);
847
1025
  }),
848
- Helper<OptimisationMatchFn>((args) => {
1026
+ Helper<OptimisationMatchFn<AndNode>>((args) => {
849
1027
  checkMinArgs('And', args, 2);
850
1028
  // Collapse nested ANDs.
851
1029
  let maybeHelped = false;
@@ -868,7 +1046,7 @@ const typeRules: Dictionary<MatchFn> = {
868
1046
 
869
1047
  return ['And', ...conditions] as AndNode;
870
1048
  }),
871
- Helper<OptimisationMatchFn>((args) => {
1049
+ Helper<OptimisationMatchFn<BooleanNode | AndNode>>((args) => {
872
1050
  checkMinArgs('And', args, 2);
873
1051
  // Reduce any booleans
874
1052
  let maybeHelped = false;
@@ -885,14 +1063,14 @@ const typeRules: Dictionary<MatchFn> = {
885
1063
  return true;
886
1064
  });
887
1065
  if (containsFalse) {
888
- return ['Boolean', false] as AbstractSqlQuery;
1066
+ return ['Boolean', false];
889
1067
  }
890
1068
  if (maybeHelped) {
891
1069
  return ['And', ...conditions] as AndNode;
892
1070
  }
893
1071
  return false;
894
1072
  }),
895
- Helper<OptimisationMatchFn>((args) => {
1073
+ Helper<OptimisationMatchFn<AndNode>>((args) => {
896
1074
  checkMinArgs('And', args, 2);
897
1075
  // Optimise id != 1 AND id != 2 AND id != 3 -> id NOT IN [1, 2, 3]
898
1076
  const fieldBuckets: Dictionary<AnyTypeNodes[]> = {};
@@ -964,13 +1142,13 @@ const typeRules: Dictionary<MatchFn> = {
964
1142
  },
965
1143
  ),
966
1144
  Or: tryMatches(
967
- Helper<OptimisationMatchFn>((args) => {
1145
+ Helper<OptimisationMatchFn<AnyTypeNodes>>((args) => {
968
1146
  if (args.length !== 1) {
969
1147
  return false;
970
1148
  }
971
1149
  return getAbstractSqlQuery(args, 0);
972
1150
  }),
973
- Helper<OptimisationMatchFn>((args) => {
1151
+ Helper<OptimisationMatchFn<OrNode>>((args) => {
974
1152
  checkMinArgs('Or', args, 2);
975
1153
  // Collapse nested ORs.
976
1154
  let maybeHelped = false;
@@ -993,7 +1171,7 @@ const typeRules: Dictionary<MatchFn> = {
993
1171
 
994
1172
  return ['Or', ...conditions] as OrNode;
995
1173
  }),
996
- Helper<OptimisationMatchFn>((args) => {
1174
+ Helper<OptimisationMatchFn<BooleanNode | OrNode>>((args) => {
997
1175
  checkMinArgs('Or', args, 2);
998
1176
  // Reduce any booleans
999
1177
  let maybeHelped = false;
@@ -1010,14 +1188,14 @@ const typeRules: Dictionary<MatchFn> = {
1010
1188
  return true;
1011
1189
  });
1012
1190
  if (containsTrue) {
1013
- return ['Boolean', true] as AbstractSqlQuery;
1191
+ return ['Boolean', true];
1014
1192
  }
1015
1193
  if (maybeHelped) {
1016
1194
  return ['Or', ...conditions] as OrNode;
1017
1195
  }
1018
1196
  return false;
1019
1197
  }),
1020
- Helper<OptimisationMatchFn>((args) => {
1198
+ Helper<OptimisationMatchFn<InNode | OrNode>>((args) => {
1021
1199
  checkMinArgs('Or', args, 2);
1022
1200
  // Optimise id = 1 OR id = 2 OR id = 3 -> id IN [1, 2, 3]
1023
1201
  const fieldBuckets: Dictionary<AnyTypeNodes[]> = {};
@@ -1095,12 +1273,12 @@ const typeRules: Dictionary<MatchFn> = {
1095
1273
  if (args.length !== 1 && args.length !== 2) {
1096
1274
  throw new SyntaxError(`"Bind" requires 1/2 arg(s)`);
1097
1275
  }
1098
- return ['Bind', ...args];
1276
+ return ['Bind', ...args] as BindNode;
1099
1277
  },
1100
1278
  Text,
1101
1279
  Value: Text,
1102
1280
  Date: matchArgs('Date', _.identity),
1103
- Duration: (args) => {
1281
+ Duration: (args): DurationNode => {
1104
1282
  checkArgs('Duration', args, 1);
1105
1283
 
1106
1284
  let duration = args[0] as DurationNode[1];
@@ -1116,26 +1294,26 @@ const typeRules: Dictionary<MatchFn> = {
1116
1294
  if (_(duration).omit('negative').isEmpty()) {
1117
1295
  throw new SyntaxError('Invalid duration');
1118
1296
  }
1119
- return ['Duration', duration] as AbstractSqlQuery;
1297
+ return ['Duration', duration];
1120
1298
  },
1121
- Exists: tryMatches(
1122
- Helper<OptimisationMatchFn>((args) => {
1299
+ Exists: tryMatches<ExistsNode | BooleanNode>(
1300
+ Helper<OptimisationMatchFn<BooleanNode>>((args) => {
1123
1301
  checkArgs('Exists', args, 1);
1124
1302
  const arg = getAbstractSqlQuery(args, 0);
1125
1303
  if (isNotNullable(arg)) {
1126
- return ['Boolean', true] as AbstractSqlQuery;
1304
+ return ['Boolean', true];
1127
1305
  }
1128
1306
  return false;
1129
1307
  }),
1130
- Helper<OptimisationMatchFn>((args) => {
1308
+ Helper<OptimisationMatchFn<BooleanNode>>((args) => {
1131
1309
  checkArgs('Exists', args, 1);
1132
1310
  const arg = getAbstractSqlQuery(args, 0);
1133
1311
  if (isEmptySelectQuery(arg)) {
1134
- return ['Boolean', false] as AbstractSqlQuery;
1312
+ return ['Boolean', false];
1135
1313
  }
1136
1314
  return false;
1137
1315
  }),
1138
- (args) => {
1316
+ (args): ExistsNode => {
1139
1317
  checkArgs('Exists', args, 1);
1140
1318
  const arg = getAbstractSqlQuery(args, 0);
1141
1319
  const [type, ...rest] = arg;
@@ -1148,24 +1326,24 @@ const typeRules: Dictionary<MatchFn> = {
1148
1326
  }
1149
1327
  },
1150
1328
  ),
1151
- NotExists: tryMatches(
1152
- Helper<OptimisationMatchFn>((args) => {
1329
+ NotExists: tryMatches<BooleanNode | NotExistsNode>(
1330
+ Helper<OptimisationMatchFn<BooleanNode>>((args) => {
1153
1331
  checkArgs('Exists', args, 1);
1154
1332
  const arg = getAbstractSqlQuery(args, 0);
1155
1333
  if (isNotNullable(arg)) {
1156
- return ['Boolean', false] as AbstractSqlQuery;
1334
+ return ['Boolean', false];
1157
1335
  }
1158
1336
  return false;
1159
1337
  }),
1160
- Helper<OptimisationMatchFn>((args) => {
1338
+ Helper<OptimisationMatchFn<BooleanNode>>((args) => {
1161
1339
  checkArgs('Exists', args, 1);
1162
1340
  const arg = getAbstractSqlQuery(args, 0);
1163
1341
  if (isEmptySelectQuery(arg)) {
1164
- return ['Boolean', true] as AbstractSqlQuery;
1342
+ return ['Boolean', true];
1165
1343
  }
1166
1344
  return false;
1167
1345
  }),
1168
- (args) => {
1346
+ (args): NotExistsNode => {
1169
1347
  checkArgs('NotExists', args, 1);
1170
1348
  const arg = getAbstractSqlQuery(args, 0);
1171
1349
  const [type, ...rest] = arg;
@@ -1178,30 +1356,32 @@ const typeRules: Dictionary<MatchFn> = {
1178
1356
  }
1179
1357
  },
1180
1358
  ),
1181
- Not: tryMatches(
1182
- Helper<OptimisationMatchFn>((args) => {
1183
- checkArgs('Not', args, 1);
1184
- const [type, ...rest] = getAbstractSqlQuery(args, 0);
1185
- switch (type) {
1186
- case 'Not':
1187
- return BooleanValue(rest[0] as AbstractSqlQuery);
1188
- case 'Equals':
1189
- return typeRules.NotEquals(rest);
1190
- case 'NotEquals':
1191
- return typeRules.Equals(rest);
1192
- case 'In':
1193
- return typeRules.NotIn(rest);
1194
- case 'NotIn':
1195
- return typeRules.In(rest);
1196
- case 'Exists':
1197
- return typeRules.NotExists(rest);
1198
- case 'NotExists':
1199
- return typeRules.Exists(rest);
1200
- default:
1201
- return false;
1202
- }
1203
- }),
1204
- matchArgs('Not', BooleanValue),
1359
+ Not: tryMatches<NotNode | BooleanTypeNodes | NotEqualsNode | ExistsNode>(
1360
+ Helper<OptimisationMatchFn<BooleanTypeNodes | NotEqualsNode | ExistsNode>>(
1361
+ (args): BooleanTypeNodes | NotEqualsNode | ExistsNode | false => {
1362
+ checkArgs('Not', args, 1);
1363
+ const [type, ...rest] = getAbstractSqlQuery(args, 0);
1364
+ switch (type) {
1365
+ case 'Not':
1366
+ return BooleanValue(rest[0] as AbstractSqlQuery);
1367
+ case 'Equals':
1368
+ return typeRules.NotEquals(rest);
1369
+ case 'NotEquals':
1370
+ return typeRules.Equals(rest);
1371
+ case 'In':
1372
+ return typeRules.NotIn(rest);
1373
+ case 'NotIn':
1374
+ return typeRules.In(rest);
1375
+ case 'Exists':
1376
+ return typeRules.NotExists(rest);
1377
+ case 'NotExists':
1378
+ return typeRules.Exists(rest);
1379
+ default:
1380
+ return false;
1381
+ }
1382
+ },
1383
+ ),
1384
+ matchArgs<NotNode>('Not', BooleanValue),
1205
1385
  ),
1206
1386
  In: (args) => {
1207
1387
  checkMinArgs('In', args, 2);
@@ -1213,7 +1393,7 @@ const typeRules: Dictionary<MatchFn> = {
1213
1393
  );
1214
1394
  }
1215
1395
  return AnyValue(arg);
1216
- });
1396
+ }) as [AnyTypeNodes, ...AnyTypeNodes[]];
1217
1397
  return ['In', field, ...vals];
1218
1398
  },
1219
1399
  NotIn: (args) => {
@@ -1226,14 +1406,14 @@ const typeRules: Dictionary<MatchFn> = {
1226
1406
  );
1227
1407
  }
1228
1408
  return AnyValue(arg);
1229
- });
1409
+ }) as [AnyTypeNodes, ...AnyTypeNodes[]];
1230
1410
  return ['NotIn', field, ...vals];
1231
1411
  },
1232
- InsertQuery: (args) => {
1233
- const tables: AbstractSqlQuery[] = [];
1234
- let fields: AbstractSqlQuery[] = [];
1235
- let values: AbstractSqlQuery[] = [];
1236
- const where: AbstractSqlQuery[] = [];
1412
+ InsertQuery: (args): InsertQueryNode => {
1413
+ const tables: FromNode[] = [];
1414
+ let fields: FieldsNode[] = [];
1415
+ let values: ValuesNode[] = [];
1416
+ const where: WhereNode[] = [];
1237
1417
  for (const arg of args) {
1238
1418
  if (!isAbstractSqlQuery(arg)) {
1239
1419
  throw new SyntaxError('`InsertQuery` args must all be arrays');
@@ -1247,7 +1427,7 @@ const typeRules: Dictionary<MatchFn> = {
1247
1427
  );
1248
1428
  }
1249
1429
  checkMinArgs('Update fields', rest, 1);
1250
- fields = [arg];
1430
+ fields = [arg as FieldsNode];
1251
1431
  break;
1252
1432
  case 'Values':
1253
1433
  if (values.length !== 0) {
@@ -1261,12 +1441,17 @@ const typeRules: Dictionary<MatchFn> = {
1261
1441
  switch (valuesType) {
1262
1442
  case 'SelectQuery':
1263
1443
  case 'UnionQuery':
1264
- values = [['Values', typeRules[valuesType](valuesRest)]];
1265
- break;
1266
- default:
1267
1444
  values = [
1268
- ['Values', valuesArray.map(Value)] as AbstractSqlQuery,
1445
+ [
1446
+ 'Values',
1447
+ typeRules[valuesType](valuesRest) as
1448
+ | SelectQueryNode
1449
+ | UnionQueryNode,
1450
+ ],
1269
1451
  ];
1452
+ break;
1453
+ default:
1454
+ values = [['Values', valuesArray.map(Value)] as ValuesNode];
1270
1455
  }
1271
1456
  }
1272
1457
  break;
@@ -1294,26 +1479,20 @@ const typeRules: Dictionary<MatchFn> = {
1294
1479
  if (
1295
1480
  fields.length !== 0 &&
1296
1481
  values.length !== 0 &&
1297
- !['SelectQuery', 'UnionQuery'].includes(values[0][0] as string) &&
1482
+ !['SelectQuery', 'UnionQuery'].includes(values[0][0]) &&
1298
1483
  fields[0].length !== values[0].length
1299
1484
  ) {
1300
1485
  throw new SyntaxError(
1301
1486
  "'InsertQuery' requires Fields and Values components to have the same length or use a query for Values",
1302
1487
  );
1303
1488
  }
1304
- return [
1305
- 'InsertQuery',
1306
- ...tables,
1307
- ...fields,
1308
- ...values,
1309
- ...where,
1310
- ] as AbstractSqlQuery;
1489
+ return ['InsertQuery', ...tables, ...fields, ...values, ...where];
1311
1490
  },
1312
- UpdateQuery: (args) => {
1313
- const tables: AbstractSqlQuery[] = [];
1314
- let fields: AbstractSqlQuery[] = [];
1315
- let values: AbstractSqlQuery[] = [];
1316
- let where: AbstractSqlQuery[] = [];
1491
+ UpdateQuery: (args): UpdateQueryNode => {
1492
+ const tables: FromNode[] = [];
1493
+ let fields: FieldsNode[] = [];
1494
+ let values: ValuesNode[] = [];
1495
+ let where: WhereNode[] = [];
1317
1496
  for (const arg of args) {
1318
1497
  if (!isAbstractSqlQuery(arg)) {
1319
1498
  throw new SyntaxError('`UpdateQuery` args must all be arrays');
@@ -1327,7 +1506,7 @@ const typeRules: Dictionary<MatchFn> = {
1327
1506
  );
1328
1507
  }
1329
1508
  checkMinArgs('Update fields', rest, 1);
1330
- fields = [arg];
1509
+ fields = [arg as FieldsNode];
1331
1510
  break;
1332
1511
  case 'Values':
1333
1512
  if (values.length !== 0) {
@@ -1338,7 +1517,7 @@ const typeRules: Dictionary<MatchFn> = {
1338
1517
  checkArgs('Update values', rest, 1);
1339
1518
  const valuesArray = getAbstractSqlQuery(rest, 0);
1340
1519
  checkMinArgs('Update values array', valuesArray, 1);
1341
- values = [['Values', valuesArray.map(Value)] as AbstractSqlQuery];
1520
+ values = [['Values', valuesArray.map(Value)]];
1342
1521
  break;
1343
1522
  case 'From':
1344
1523
  tables.push(typeRules[type](rest));
@@ -1365,17 +1544,11 @@ const typeRules: Dictionary<MatchFn> = {
1365
1544
  throw new SyntaxError("'UpdateQuery' requires a Values component");
1366
1545
  }
1367
1546
 
1368
- return [
1369
- 'UpdateQuery',
1370
- ...tables,
1371
- ...fields,
1372
- ...values,
1373
- ...where,
1374
- ] as AbstractSqlQuery;
1547
+ return ['UpdateQuery', ...tables, ...fields, ...values, ...where];
1375
1548
  },
1376
- DeleteQuery: (args) => {
1377
- const tables: AbstractSqlQuery[] = [];
1378
- let where: AbstractSqlQuery[] = [];
1549
+ DeleteQuery: (args): DeleteQueryNode => {
1550
+ const tables: FromNode[] = [];
1551
+ let where: WhereNode[] = [];
1379
1552
  for (const arg of args) {
1380
1553
  if (!isAbstractSqlQuery(arg)) {
1381
1554
  throw new SyntaxError('`DeleteQuery` args must all be arrays');
@@ -1401,80 +1574,92 @@ const typeRules: Dictionary<MatchFn> = {
1401
1574
  throw new SyntaxError("'DeleteQuery' must have a From component");
1402
1575
  }
1403
1576
 
1404
- return ['DeleteQuery', ...tables, ...where] as AbstractSqlQuery;
1577
+ return ['DeleteQuery', ...tables, ...where];
1405
1578
  },
1406
1579
 
1407
1580
  // Virtual functions
1408
1581
  Now: rewriteMatch(
1409
1582
  'Now',
1410
1583
  [],
1411
- Helper<MatchFn>(([]) => ['CurrentTimestamp']),
1584
+ Helper<MatchFn<CurrentTimestampNode>>(() => ['CurrentTimestamp']),
1412
1585
  ),
1413
1586
  Contains: rewriteMatch(
1414
1587
  'Contains',
1415
1588
  [TextValue, TextValue],
1416
- Helper<MatchFn>(([haystack, needle]: [TextTypeNodes, TextTypeNodes]) => [
1417
- 'Like',
1418
- haystack,
1419
- [
1420
- 'Concatenate',
1421
- ['EmbeddedText', '%'],
1422
- escapeForLike(needle),
1423
- ['EmbeddedText', '%'],
1589
+ Helper<MatchFn<LikeNode>>(
1590
+ ([haystack, needle]: [TextTypeNodes, TextTypeNodes]) => [
1591
+ 'Like',
1592
+ haystack,
1593
+ [
1594
+ 'Concatenate',
1595
+ ['EmbeddedText', '%'],
1596
+ escapeForLike(needle),
1597
+ ['EmbeddedText', '%'],
1598
+ ],
1424
1599
  ],
1425
- ]),
1600
+ ),
1426
1601
  ),
1427
1602
  Substringof: rewriteMatch(
1428
1603
  'Substringof',
1429
1604
  [TextValue, TextValue],
1430
- Helper<MatchFn>(([needle, haystack]: [TextTypeNodes, TextTypeNodes]) => [
1431
- 'Like',
1432
- haystack,
1433
- [
1434
- 'Concatenate',
1435
- ['EmbeddedText', '%'],
1436
- escapeForLike(needle),
1437
- ['EmbeddedText', '%'],
1605
+ Helper<MatchFn<LikeNode>>(
1606
+ ([needle, haystack]: [TextTypeNodes, TextTypeNodes]) => [
1607
+ 'Like',
1608
+ haystack,
1609
+ [
1610
+ 'Concatenate',
1611
+ ['EmbeddedText', '%'],
1612
+ escapeForLike(needle),
1613
+ ['EmbeddedText', '%'],
1614
+ ],
1438
1615
  ],
1439
- ]),
1616
+ ),
1440
1617
  ),
1441
1618
  Startswith: rewriteMatch(
1442
1619
  'Startswith',
1443
1620
  [TextValue, TextValue],
1444
- Helper<MatchFn>(([haystack, needle]: [TextTypeNodes, TextTypeNodes]) => [
1445
- 'Like',
1446
- haystack,
1447
- ['Concatenate', escapeForLike(needle), ['EmbeddedText', '%']],
1448
- ]),
1621
+ Helper<MatchFn<LikeNode>>(
1622
+ ([haystack, needle]: [TextTypeNodes, TextTypeNodes]) => [
1623
+ 'Like',
1624
+ haystack,
1625
+ ['Concatenate', escapeForLike(needle), ['EmbeddedText', '%']],
1626
+ ],
1627
+ ),
1449
1628
  ),
1450
1629
  Endswith: rewriteMatch(
1451
1630
  'Endswith',
1452
1631
  [TextValue, TextValue],
1453
- Helper<MatchFn>(([haystack, needle]: [TextTypeNodes, TextTypeNodes]) => [
1454
- 'Like',
1455
- haystack,
1456
- ['Concatenate', ['EmbeddedText', '%'], escapeForLike(needle)],
1457
- ]),
1632
+ Helper<MatchFn<LikeNode>>(
1633
+ ([haystack, needle]: [TextTypeNodes, TextTypeNodes]) => [
1634
+ 'Like',
1635
+ haystack,
1636
+ ['Concatenate', ['EmbeddedText', '%'], escapeForLike(needle)],
1637
+ ],
1638
+ ),
1458
1639
  ),
1459
1640
  IndexOf: rewriteMatch(
1460
1641
  'IndexOf',
1461
1642
  [TextValue, TextValue],
1462
- Helper<MatchFn>(([haystack, needle]) => [
1463
- 'Subtract',
1464
- ['StrPos', haystack, needle],
1465
- ['Number', 1],
1466
- ]),
1643
+ Helper<MatchFn<SubtractNode>>(
1644
+ ([haystack, needle]: [TextTypeNodes, TextTypeNodes]) => [
1645
+ 'Subtract',
1646
+ ['StrPos', haystack, needle],
1647
+ ['Number', 1],
1648
+ ],
1649
+ ),
1467
1650
  ),
1468
1651
  Indexof: rewriteMatch(
1469
1652
  'Indexof',
1470
1653
  [TextValue, TextValue],
1471
- Helper<MatchFn>(([haystack, needle]) => [
1472
- 'Subtract',
1473
- ['StrPos', haystack, needle],
1474
- ['Number', 1],
1475
- ]),
1654
+ Helper<MatchFn<SubtractNode>>(
1655
+ ([haystack, needle]: [TextTypeNodes, TextTypeNodes]) => [
1656
+ 'Subtract',
1657
+ ['StrPos', haystack, needle],
1658
+ ['Number', 1],
1659
+ ],
1660
+ ),
1476
1661
  ),
1477
- };
1662
+ } satisfies Dictionary<MatchFn<AnyTypeNodes>>;
1478
1663
 
1479
1664
  export const AbstractSQLOptimiser = (
1480
1665
  abstractSQL: AbstractSqlQuery,
@@ -1511,7 +1696,7 @@ export const AbstractSQLOptimiser = (
1511
1696
  ];
1512
1697
  break;
1513
1698
  default:
1514
- abstractSQL = AnyValue(abstractSQL);
1699
+ abstractSQL = AnyValue(abstractSQL) as AbstractSqlQuery;
1515
1700
  }
1516
1701
  } while (helped);
1517
1702
  return abstractSQL;