@balena/abstract-sql-compiler 11.0.0-build-11-x-45529f014aa1c181f338c0f7348767f2990a9084-1 → 11.0.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.
Files changed (53) hide show
  1. package/package.json +5 -2
  2. package/.github/workflows/flowzone.yml +0 -21
  3. package/.husky/pre-commit +0 -2
  4. package/.versionbot/CHANGELOG.yml +0 -10729
  5. package/CHANGELOG.md +0 -3515
  6. package/repo.yml +0 -12
  7. package/src/abstract-sql-compiler.ts +0 -1138
  8. package/src/abstract-sql-optimizer.ts +0 -1632
  9. package/src/abstract-sql-rules-to-sql.ts +0 -1730
  10. package/src/abstract-sql-schema-optimizer.ts +0 -172
  11. package/src/referenced-fields.ts +0 -600
  12. package/test/abstract-sql/aggregate-json.ts +0 -49
  13. package/test/abstract-sql/aggregate.ts +0 -161
  14. package/test/abstract-sql/and-or-boolean-optimisations.ts +0 -115
  15. package/test/abstract-sql/case-when-else.ts +0 -48
  16. package/test/abstract-sql/cast.ts +0 -25
  17. package/test/abstract-sql/coalesce.ts +0 -24
  18. package/test/abstract-sql/comparisons.ts +0 -360
  19. package/test/abstract-sql/dates.ts +0 -512
  20. package/test/abstract-sql/duration.ts +0 -56
  21. package/test/abstract-sql/empty-query-optimisations.ts +0 -54
  22. package/test/abstract-sql/functions-wrapper.ts +0 -70
  23. package/test/abstract-sql/get-referenced-fields.ts +0 -674
  24. package/test/abstract-sql/get-rule-referenced-fields.ts +0 -345
  25. package/test/abstract-sql/insert-query.ts +0 -22
  26. package/test/abstract-sql/is-distinct.ts +0 -102
  27. package/test/abstract-sql/joins.ts +0 -84
  28. package/test/abstract-sql/json.ts +0 -58
  29. package/test/abstract-sql/math.ts +0 -467
  30. package/test/abstract-sql/nested-in-optimisations.ts +0 -200
  31. package/test/abstract-sql/not-not-optimisations.ts +0 -15
  32. package/test/abstract-sql/schema-checks.ts +0 -168
  33. package/test/abstract-sql/schema-informative-reference.ts +0 -420
  34. package/test/abstract-sql/schema-rule-optimization.ts +0 -120
  35. package/test/abstract-sql/schema-rule-to-check.ts +0 -393
  36. package/test/abstract-sql/schema-views.ts +0 -73
  37. package/test/abstract-sql/test.ts +0 -192
  38. package/test/abstract-sql/text.ts +0 -168
  39. package/test/model.sbvr +0 -60
  40. package/test/odata/expand.ts +0 -674
  41. package/test/odata/fields.ts +0 -59
  42. package/test/odata/filterby.ts +0 -1517
  43. package/test/odata/orderby.ts +0 -96
  44. package/test/odata/paging.ts +0 -48
  45. package/test/odata/resource-parsing.ts +0 -568
  46. package/test/odata/select.ts +0 -119
  47. package/test/odata/stress.ts +0 -93
  48. package/test/odata/test.ts +0 -297
  49. package/test/sbvr/pilots.ts +0 -1097
  50. package/test/sbvr/reference-type.ts +0 -211
  51. package/test/sbvr/test.ts +0 -101
  52. package/tsconfig.build.json +0 -6
  53. package/tsconfig.json +0 -25
@@ -1,1632 +0,0 @@
1
- import type {
2
- AbstractSqlQuery,
3
- AbstractSqlType,
4
- AddDateDurationNode,
5
- AddDateNumberNode,
6
- AddNode,
7
- AggregateJSONNode,
8
- AliasNode,
9
- AndNode,
10
- AnyNode,
11
- AnyTypeNodes,
12
- BetweenNode,
13
- BindNode,
14
- BitwiseAndNode,
15
- BitwiseShiftRightNode,
16
- BooleanNode,
17
- BooleanTypeNodes,
18
- CaseNode,
19
- CastNode,
20
- CeilingNode,
21
- CharacterLengthNode,
22
- CoalesceNode,
23
- ConcatenateNode,
24
- ConcatenateWithSeparatorNode,
25
- CrossJoinNode,
26
- CurrentDateNode,
27
- CurrentTimestampNode,
28
- DeleteQueryNode,
29
- DivideNode,
30
- DurationNode,
31
- ElseNode,
32
- EqualsNode,
33
- ExistsNode,
34
- ExtractJSONPathAsTextNode,
35
- ExtractNumericDateTypeNodes,
36
- FieldNode,
37
- FieldsNode,
38
- FloorNode,
39
- FromNode,
40
- FullJoinNode,
41
- GreaterThanNode,
42
- GreaterThanOrEqualNode,
43
- GroupByNode,
44
- HavingNode,
45
- InNode,
46
- InnerJoinNode,
47
- InsertQueryNode,
48
- IntegerNode,
49
- IsDistinctFromNode,
50
- IsNotDistinctFromNode,
51
- JSONTypeNodes,
52
- JoinTypeNodes,
53
- LeftJoinNode,
54
- LessThanNode,
55
- LessThanOrEqualNode,
56
- LikeNode,
57
- LimitNode,
58
- LowerNode,
59
- MultiplyNode,
60
- NotEqualsNode,
61
- NotExistsNode,
62
- NotNode,
63
- NullNode,
64
- NumberNode,
65
- NumberTypeNodes,
66
- OffsetNode,
67
- OrNode,
68
- OrderByNode,
69
- RealNode,
70
- ReferencedFieldNode,
71
- ReplaceNode,
72
- StrictBooleanTypeNodes,
73
- StrictNumberTypeNodes,
74
- StrictTextTypeNodes,
75
- RightJoinNode,
76
- RightNode,
77
- RoundNode,
78
- SelectNode,
79
- SelectQueryNode,
80
- StrPosNode,
81
- SubtractDateDateNode,
82
- SubtractDateDurationNode,
83
- SubtractDateNumberNode,
84
- SubtractNode,
85
- TableNode,
86
- TextArrayTypeNodes,
87
- TextNode,
88
- TextTypeNodes,
89
- ToDateNode,
90
- ToJSONNode,
91
- ToTimeNode,
92
- TrimNode,
93
- UnionQueryNode,
94
- UnknownTypeNodes,
95
- UpdateQueryNode,
96
- UpperNode,
97
- ValuesNode,
98
- ValuesNodeTypes,
99
- WhenNode,
100
- WhereNode,
101
- FromTypeNode,
102
- StartsWithNode,
103
- EscapeForLikeNode,
104
- EqualsAnyNode,
105
- NotInNode,
106
- } from './abstract-sql-compiler.js';
107
- import { isFieldTypeNode } from './abstract-sql-compiler.js';
108
- import * as AbstractSQLRules2SQL from './abstract-sql-rules-to-sql.js';
109
-
110
- const {
111
- isAbstractSqlQuery,
112
- getAbstractSqlQuery,
113
- checkArgs,
114
- checkMinArgs,
115
- isNotNullable,
116
- } = AbstractSQLRules2SQL;
117
-
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;
123
-
124
- const identity = <T>(x: T): T => x;
125
-
126
- let helped = false;
127
- let noBinds = false;
128
- const Helper = <F extends (...args: any[]) => any>(fn: F) => {
129
- return (...args: Parameters<F>): ReturnType<F> => {
130
- const result = fn(...args);
131
- if (result !== false) {
132
- helped = true;
133
- }
134
- return result;
135
- };
136
- };
137
-
138
- const isEmptySelectQuery = (query: AnyTypeNodes): boolean => {
139
- const [type, ...rest] = query;
140
- switch (type) {
141
- case 'SelectQuery':
142
- for (const arg of rest as SelectQueryNode) {
143
- if (arg[0] === 'Where') {
144
- const maybeBool = arg[1];
145
- if (maybeBool[0] === 'Boolean') {
146
- if (maybeBool[1] === false) {
147
- return true;
148
- }
149
- }
150
- }
151
- }
152
- }
153
- return false;
154
- };
155
-
156
- const rewriteMatch =
157
- <I extends AnyTypeNodes, O extends AnyTypeNodes>(
158
- name: I[0],
159
- matchers: Array<(args: AbstractSqlType) => AbstractSqlType>,
160
- rewriteFn: MatchFn<O>,
161
- ): MatchFn<O> =>
162
- (args) => {
163
- checkArgs(name, args, matchers.length);
164
- return rewriteFn(
165
- args.map((arg, index) => {
166
- return matchers[index](arg);
167
- }),
168
- );
169
- };
170
-
171
- const matchArgs = <T extends AnyTypeNodes>(
172
- name: T[0],
173
- ...matchers: Array<(args: AbstractSqlType) => AbstractSqlType>
174
- ): MatchFn<T> => rewriteMatch(name, matchers, (args) => [name, ...args] as T);
175
-
176
- const tryMatches = <T extends AnyTypeNodes>(
177
- ...matchers: Array<OptimisationMatchFn<T>>
178
- ): MatchFn<T> => {
179
- return (args): T => {
180
- let err;
181
- for (const matcher of matchers) {
182
- try {
183
- const result = matcher(args);
184
- if (result !== false) {
185
- return result;
186
- }
187
- } catch (e) {
188
- err = e;
189
- }
190
- }
191
- throw err;
192
- };
193
- };
194
-
195
- const AnyValue: MetaMatchFn<AnyTypeNodes> = (args) => {
196
- const [type, ...rest] = args;
197
-
198
- for (const matcher of [
199
- isJSONValue,
200
- isDateValue,
201
- isTextValue,
202
- isNumericValue,
203
- isBooleanValue,
204
- isDurationValue,
205
- ]) {
206
- if (matcher(type)) {
207
- return typeRules[type](rest);
208
- }
209
- }
210
-
211
- return UnknownValue(args);
212
- };
213
- const UnknownValue: MetaMatchFn<UnknownTypeNodes> = (args) => {
214
- const [type, ...rest] = args;
215
- switch (type) {
216
- case 'Null':
217
- case 'Field':
218
- case 'ReferencedField':
219
- case 'Bind':
220
- case 'Cast':
221
- case 'Case':
222
- case 'Coalesce':
223
- case 'Any':
224
- return typeRules[type](rest);
225
- case 'SelectQuery':
226
- case 'UnionQuery':
227
- return typeRules[type](rest);
228
- default:
229
- throw new Error(`Invalid "UnknownValue" type: ${type}`);
230
- }
231
- };
232
- const MatchValue =
233
- <T extends AnyTypeNodes>(
234
- matcher: (type: unknown) => type is T[0],
235
- ): MetaMatchFn<T | UnknownTypeNodes> =>
236
- (args) => {
237
- const [type, ...rest] = args;
238
- if (matcher(type)) {
239
- return typeRules[type as keyof typeof typeRules](rest) as T;
240
- }
241
- return UnknownValue(args);
242
- };
243
-
244
- const isTextValue = (
245
- type: unknown,
246
- ): type is
247
- | 'Value'
248
- | 'Concat'
249
- | 'Tolower'
250
- | 'ToLower'
251
- | 'Toupper'
252
- | 'ToUpper'
253
- | StrictTextTypeNodes[0] => {
254
- return (
255
- type === 'Value' ||
256
- type === 'Concat' ||
257
- type === 'Tolower' ||
258
- type === 'ToLower' ||
259
- type === 'Toupper' ||
260
- type === 'ToUpper' ||
261
- AbstractSQLRules2SQL.isTextValue(type)
262
- );
263
- };
264
- const TextValue = MatchValue<TextTypeNodes>(
265
- isTextValue as typeof AbstractSQLRules2SQL.isTextValue,
266
- );
267
-
268
- const isNumericValue = (
269
- type: unknown,
270
- ): type is 'IndexOf' | 'Indexof' | StrictNumberTypeNodes[0] => {
271
- return (
272
- type === 'IndexOf' ||
273
- type === 'Indexof' ||
274
- AbstractSQLRules2SQL.isNumericValue(type)
275
- );
276
- };
277
- const NumericValue = MatchValue(isNumericValue);
278
-
279
- const isBooleanValue = (
280
- type: unknown,
281
- ): type is
282
- | 'Contains'
283
- | 'Substringof'
284
- | 'Startswith'
285
- | 'Endswith'
286
- | StrictBooleanTypeNodes[0] => {
287
- return (
288
- type === 'Contains' ||
289
- type === 'Substringof' ||
290
- type === 'Startswith' ||
291
- type === 'Endswith' ||
292
- AbstractSQLRules2SQL.isBooleanValue(type)
293
- );
294
- };
295
- const BooleanValue = MatchValue<BooleanTypeNodes>(
296
- isBooleanValue as typeof AbstractSQLRules2SQL.isBooleanValue,
297
- );
298
-
299
- const { isDateValue } = AbstractSQLRules2SQL;
300
- const DateValue = MatchValue(isDateValue);
301
-
302
- const { isJSONValue } = AbstractSQLRules2SQL;
303
- const JSONValue = MatchValue<JSONTypeNodes>(isJSONValue);
304
-
305
- const { isDurationValue } = AbstractSQLRules2SQL;
306
- const DurationValue = MatchValue(isDurationValue);
307
-
308
- const { isArrayValue } = AbstractSQLRules2SQL;
309
- const ArrayValue = MatchValue<TextArrayTypeNodes>(isArrayValue);
310
-
311
- const Field: MetaMatchFn<FieldNode | ReferencedFieldNode> = (args) => {
312
- if (isFieldTypeNode(args)) {
313
- const [type, ...rest] = args;
314
- return typeRules[type](rest);
315
- } else {
316
- throw new SyntaxError(`Invalid field type: ${args[0]}`);
317
- }
318
- };
319
-
320
- const AnyNotNullValue = (args: any): boolean => {
321
- return args != null && args !== 'Null' && args[0] !== 'Null';
322
- };
323
-
324
- const FieldOp = <T extends string>(type: T) =>
325
- ((args) => {
326
- if (
327
- AnyNotNullValue(args[0]) === false ||
328
- AnyNotNullValue(args[1]) === false
329
- ) {
330
- return false;
331
- }
332
- if (isFieldTypeNode(args[0])) {
333
- return [type, args[0], args[1]];
334
- } else if (isFieldTypeNode(args[1])) {
335
- return [type, args[1], args[0]];
336
- } else {
337
- return false;
338
- }
339
- }) satisfies OptimisationMatchFn<AnyTypeNodes>;
340
- const FieldEquals = FieldOp('Equals');
341
- const FieldNotEquals = FieldOp('NotEquals');
342
-
343
- const Comparison = <T extends AnyTypeNodes>(
344
- comparison: keyof typeof AbstractSQLRules2SQL.comparisons,
345
- ): MatchFn<T> => {
346
- return matchArgs<T>(comparison, AnyValue, AnyValue);
347
- };
348
- const NumberMatch = <T extends AnyTypeNodes>(type: T[0]): MatchFn<T> => {
349
- return matchArgs(type, (arg) => {
350
- if (typeof arg !== 'number') {
351
- throw new SyntaxError(`${type} expected number but got ${typeof arg}`);
352
- }
353
- return arg;
354
- });
355
- };
356
-
357
- type ExtractNodeType<T, U> = T extends any[]
358
- ? T[0] extends U
359
- ? T
360
- : never
361
- : never;
362
- const MathOp = <
363
- T extends ExtractNodeType<NumberTypeNodes, AbstractSQLRules2SQL.MathOps>,
364
- >(
365
- type: T[0],
366
- ): MatchFn<T> => {
367
- return matchArgs(type, NumericValue, NumericValue);
368
- };
369
-
370
- const ExtractNumericDatePart = <T extends ExtractNumericDateTypeNodes>(
371
- type: T[0],
372
- ): MatchFn<T> => {
373
- return matchArgs(type, DateValue);
374
- };
375
-
376
- const Concatenate: MatchFn<ConcatenateNode> = (args) => {
377
- checkMinArgs('Concatenate', args, 1);
378
- return [
379
- 'Concatenate',
380
- ...(args.map((arg) => {
381
- if (!isAbstractSqlQuery(arg)) {
382
- throw new SyntaxError(
383
- `Expected AbstractSqlQuery array but got ${typeof arg}`,
384
- );
385
- }
386
- return TextValue(arg);
387
- }) as [
388
- ReturnType<typeof TextValue>,
389
- ...Array<ReturnType<typeof TextValue>>,
390
- ]),
391
- ];
392
- };
393
-
394
- const ConcatenateWithSeparator: MatchFn<ConcatenateWithSeparatorNode> = (
395
- args,
396
- ) => {
397
- checkMinArgs('ConcatenateWithSeparator', args, 2);
398
- return [
399
- 'ConcatenateWithSeparator',
400
- ...(args.map((arg) => {
401
- if (!isAbstractSqlQuery(arg)) {
402
- throw new SyntaxError(
403
- `Expected AbstractSqlQuery array but got ${typeof arg}`,
404
- );
405
- }
406
- return TextValue(arg);
407
- }) as [
408
- ReturnType<typeof TextValue>,
409
- ReturnType<typeof TextValue>,
410
- ...Array<ReturnType<typeof TextValue>>,
411
- ]),
412
- ];
413
- };
414
-
415
- const Text = matchArgs<TextNode>('Text', identity);
416
-
417
- const Value = (arg: string | AbstractSqlQuery): ValuesNodeTypes => {
418
- switch (arg) {
419
- case 'Default':
420
- return arg;
421
- default: {
422
- const [type, ...rest] = arg;
423
- switch (type) {
424
- case 'Null':
425
- case 'Bind':
426
- case 'Value':
427
- case 'Text':
428
- case 'Number':
429
- case 'Real':
430
- case 'Integer':
431
- case 'Boolean':
432
- return typeRules[type](rest) as
433
- | NullNode
434
- | BindNode
435
- | TextNode
436
- | NumberNode
437
- | RealNode
438
- | IntegerNode;
439
- default:
440
- throw new SyntaxError(`Invalid type for Value ${type}`);
441
- }
442
- }
443
- }
444
- };
445
-
446
- const FromMatch: MetaMatchFn<FromTypeNode[keyof FromTypeNode]> = (args) => {
447
- const [type, ...rest] = args;
448
- switch (type) {
449
- case 'SelectQuery':
450
- case 'UnionQuery':
451
- return typeRules[type](rest);
452
- case 'Table':
453
- checkArgs('Table', rest, 1);
454
- return ['Table', rest[0] as TableNode[1]];
455
- default:
456
- throw new SyntaxError(`From does not support ${type}`);
457
- }
458
- };
459
-
460
- const MaybeAlias = <T extends AnyTypeNodes>(
461
- args: AbstractSqlQuery,
462
- matchFn: MetaMatchFn<T>,
463
- ): T | AliasNode<T> => {
464
- const [type, ...rest] = args;
465
- switch (type) {
466
- case 'Alias':
467
- checkArgs('Alias', rest, 2);
468
- return [
469
- 'Alias',
470
- matchFn(getAbstractSqlQuery(rest, 0)),
471
- rest[1] as AliasNode<T>[2],
472
- ];
473
- default:
474
- return matchFn(args);
475
- }
476
- };
477
-
478
- const Lower = matchArgs<LowerNode>('Lower', TextValue);
479
- const Upper = matchArgs<UpperNode>('Upper', TextValue);
480
-
481
- const JoinMatch =
482
- <T extends JoinTypeNodes>(joinType: T[0]): MatchFn<T> =>
483
- (args) => {
484
- if (args.length !== 1 && args.length !== 2) {
485
- throw new SyntaxError(`"${joinType}" requires 1/2 arg(s)`);
486
- }
487
- const from = MaybeAlias(getAbstractSqlQuery(args, 0), FromMatch);
488
- if (args.length === 1) {
489
- return [joinType, from] as T;
490
- }
491
- const [type, ...rest] = getAbstractSqlQuery(args, 1);
492
- switch (type) {
493
- case 'On':
494
- if (joinType !== 'CrossJoin') {
495
- checkArgs('On', rest, 1);
496
- const ruleBody = BooleanValue(getAbstractSqlQuery(rest, 0));
497
- return [joinType, from, ['On', ruleBody]] as unknown as T;
498
- }
499
- // eslint-disable-next-line no-fallthrough
500
- default:
501
- throw new SyntaxError(
502
- `'${joinType}' clause does not support '${type}' clause`,
503
- );
504
- }
505
- };
506
-
507
- const AddDateMatcher = tryMatches(
508
- matchArgs('AddDateDuration', DateValue, DurationValue),
509
- matchArgs('AddDateNumber', DateValue, NumericValue),
510
- );
511
-
512
- const SubtractDateMatcher = tryMatches<
513
- SubtractDateDateNode | SubtractDateDurationNode | SubtractDateNumberNode
514
- >(
515
- matchArgs<SubtractDateDateNode>('SubtractDateDate', DateValue, DateValue),
516
- matchArgs<SubtractDateDurationNode>(
517
- 'SubtractDateDuration',
518
- DateValue,
519
- DurationValue,
520
- ),
521
- matchArgs<SubtractDateNumberNode>(
522
- 'SubtractDateNumber',
523
- DateValue,
524
- NumericValue,
525
- ),
526
- );
527
-
528
- const EndsWithMatcher = rewriteMatch(
529
- 'Endswith',
530
- [TextValue, TextValue],
531
- Helper<MatchFn<LikeNode>>(
532
- ([haystack, needle]: [TextTypeNodes, TextTypeNodes]) => [
533
- 'Like',
534
- haystack,
535
- ['Concatenate', ['EmbeddedText', '%'], ['EscapeForLike', needle]],
536
- ],
537
- ),
538
- );
539
-
540
- const typeRules = {
541
- UnionQuery: (args): UnionQueryNode => {
542
- checkMinArgs('UnionQuery', args, 2);
543
- return [
544
- 'UnionQuery',
545
- ...args.map((arg) => {
546
- if (!isAbstractSqlQuery(arg)) {
547
- throw new SyntaxError(
548
- `Expected AbstractSqlQuery array but got ${typeof arg}`,
549
- );
550
- }
551
- const [type, ...rest] = arg;
552
- switch (type) {
553
- case 'SelectQuery':
554
- case 'UnionQuery':
555
- return typeRules[type](rest);
556
- default:
557
- throw new SyntaxError(`UnionQuery does not support ${type}`);
558
- }
559
- }),
560
- ];
561
- },
562
- SelectQuery: (args): SelectQueryNode => {
563
- const tables: Array<
564
- | FromNode
565
- | InnerJoinNode
566
- | LeftJoinNode
567
- | RightJoinNode
568
- | FullJoinNode
569
- | CrossJoinNode
570
- > = [];
571
- let select: SelectNode[] = [];
572
- const groups = {
573
- Where: [] as WhereNode[],
574
- GroupBy: [] as GroupByNode[],
575
- Having: [] as HavingNode[],
576
- OrderBy: [] as OrderByNode[],
577
- Limit: [] as LimitNode[],
578
- Offset: [] as OffsetNode[],
579
- };
580
- for (const arg of args) {
581
- if (!isAbstractSqlQuery(arg)) {
582
- throw new SyntaxError('`SelectQuery` args must all be arrays');
583
- }
584
- const [type, ...rest] = arg;
585
- switch (type) {
586
- case 'Select':
587
- if (select.length !== 0) {
588
- throw new SyntaxError(
589
- `'SelectQuery' can only accept one '${type}'`,
590
- );
591
- }
592
- select = [typeRules[type](rest)];
593
- break;
594
- case 'From':
595
- case 'Join':
596
- case 'LeftJoin':
597
- case 'RightJoin':
598
- case 'FullJoin':
599
- case 'CrossJoin':
600
- tables.push(typeRules[type](rest));
601
- break;
602
- case 'Where':
603
- case 'GroupBy':
604
- case 'Having':
605
- case 'OrderBy':
606
- case 'Limit':
607
- case 'Offset':
608
- if (groups[type].length !== 0) {
609
- throw new SyntaxError(
610
- `'SelectQuery' can only accept one '${type}'`,
611
- );
612
- }
613
- groups[type] = [
614
- typeRules[type](rest) as
615
- | WhereNode
616
- | GroupByNode
617
- | HavingNode
618
- | OrderByNode
619
- | LimitNode
620
- // 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
621
- | OffsetNode as any,
622
- ];
623
- break;
624
- default:
625
- throw new SyntaxError(`'SelectQuery' does not support '${type}'`);
626
- }
627
- }
628
-
629
- return [
630
- 'SelectQuery',
631
- ...select,
632
- ...tables,
633
- ...groups.Where,
634
- ...groups.GroupBy,
635
- ...groups.Having,
636
- ...groups.OrderBy,
637
- ...groups.Limit,
638
- ...groups.Offset,
639
- ];
640
- },
641
- Select: (args): SelectNode => {
642
- checkArgs('Select', args, 1);
643
- return [
644
- 'Select',
645
- getAbstractSqlQuery(args, 0).map((arg) => {
646
- if (!isAbstractSqlQuery(arg)) {
647
- throw new SyntaxError(
648
- `Expected AbstractSqlQuery array but got ${typeof arg}`,
649
- );
650
- }
651
- return MaybeAlias(arg, AnyValue);
652
- }),
653
- ];
654
- },
655
- From: (args): FromNode => {
656
- checkArgs('From', args, 1);
657
- return ['From', MaybeAlias(getAbstractSqlQuery(args, 0), FromMatch)];
658
- },
659
- Join: JoinMatch('Join'),
660
- LeftJoin: JoinMatch('LeftJoin'),
661
- RightJoin: JoinMatch('RightJoin'),
662
- FullJoin: JoinMatch('FullJoin'),
663
- CrossJoin: (args) => {
664
- checkArgs('CrossJoin', args, 1);
665
- const from = MaybeAlias(getAbstractSqlQuery(args, 0), FromMatch);
666
- return ['CrossJoin', from];
667
- },
668
- Where: matchArgs<WhereNode>('Where', BooleanValue),
669
- GroupBy: (args) => {
670
- checkArgs('GroupBy', args, 1);
671
- const groups = getAbstractSqlQuery(args, 0);
672
- checkMinArgs('GroupBy groups', groups, 1);
673
- return ['GroupBy', groups.map(AnyValue)] as GroupByNode;
674
- },
675
- Having: matchArgs('Having', BooleanValue),
676
- OrderBy: (args) => {
677
- checkMinArgs('OrderBy', args, 1);
678
- return [
679
- 'OrderBy',
680
- ...args.map((arg: AbstractSqlQuery) => {
681
- checkMinArgs('OrderBy ordering', arg, 2);
682
- const order = arg[0];
683
- if (order !== 'ASC' && order !== 'DESC') {
684
- throw new SyntaxError(`Can only order by "ASC" or "DESC"`);
685
- }
686
- const value = AnyValue(getAbstractSqlQuery(arg, 1));
687
- return [order, value];
688
- }),
689
- ] as OrderByNode;
690
- },
691
- Limit: matchArgs('Limit', NumericValue),
692
- Offset: matchArgs('Offset', NumericValue),
693
- Count: (args) => {
694
- checkArgs('Count', args, 1);
695
- if (args[0] !== '*') {
696
- throw new SyntaxError('"Count" only supports "*"');
697
- }
698
- return ['Count', args[0]];
699
- },
700
- Average: matchArgs('Average', NumericValue),
701
- Sum: matchArgs('Sum', NumericValue),
702
- Field: matchArgs<FieldNode>('Field', identity),
703
- ReferencedField: matchArgs<ReferencedFieldNode>(
704
- 'ReferencedField',
705
- identity,
706
- identity,
707
- ),
708
- Cast: matchArgs<CastNode>('Cast', AnyValue, identity),
709
- // eslint-disable-next-line id-denylist
710
- Number: NumberMatch('Number'),
711
- Real: NumberMatch('Real'),
712
- Integer: NumberMatch('Integer'),
713
- // eslint-disable-next-line id-denylist
714
- Boolean: matchArgs<BooleanNode>('Boolean', identity),
715
- EmbeddedText: matchArgs('EmbeddedText', identity),
716
- Null: matchArgs<NullNode>('Null'),
717
- CurrentTimestamp: matchArgs<CurrentTimestampNode>('CurrentTimestamp'),
718
- CurrentDate: matchArgs<CurrentDateNode>('CurrentDate'),
719
- AggregateJSON: matchArgs<AggregateJSONNode>('AggregateJSON', Field),
720
- Equals: tryMatches<NotExistsNode | EqualsNode>(
721
- Helper<OptimisationMatchFn<NotExistsNode>>((args) => {
722
- checkArgs('Equals', args, 2);
723
- let valueIndex;
724
- if (args[0][0] === 'Null') {
725
- valueIndex = 1;
726
- } else if (args[1][0] === 'Null') {
727
- valueIndex = 0;
728
- } else {
729
- return false;
730
- }
731
-
732
- return ['NotExists', getAbstractSqlQuery(args, valueIndex)];
733
- }),
734
- Comparison<EqualsNode>('Equals'),
735
- ),
736
- EqualsAny: matchArgs<EqualsAnyNode>('EqualsAny', AnyValue, AnyValue),
737
- NotEquals: tryMatches<NotEqualsNode | ExistsNode>(
738
- Helper<OptimisationMatchFn<ExistsNode>>((args) => {
739
- checkArgs('NotEquals', args, 2);
740
- let valueIndex;
741
- if (args[0][0] === 'Null') {
742
- valueIndex = 1;
743
- } else if (args[1][0] === 'Null') {
744
- valueIndex = 0;
745
- } else {
746
- return false;
747
- }
748
-
749
- return ['Exists', getAbstractSqlQuery(args, valueIndex)];
750
- }),
751
- Comparison<NotEqualsNode>('NotEquals'),
752
- ),
753
- GreaterThan: Comparison<GreaterThanNode>('GreaterThan'),
754
- GreaterThanOrEqual: Comparison<GreaterThanOrEqualNode>('GreaterThanOrEqual'),
755
- LessThan: Comparison<LessThanNode>('LessThan'),
756
- LessThanOrEqual: Comparison<LessThanOrEqualNode>('LessThanOrEqual'),
757
- Like: Comparison<LikeNode>('Like'),
758
- IsNotDistinctFrom: tryMatches(
759
- Helper<OptimisationMatchFn<NotExistsNode>>((args) => {
760
- checkArgs('IsNotDistinctFrom', args, 2);
761
- let valueIndex;
762
- if (args[0][0] === 'Null') {
763
- valueIndex = 1;
764
- } else if (args[1][0] === 'Null') {
765
- valueIndex = 0;
766
- } else {
767
- return false;
768
- }
769
-
770
- return ['NotExists', getAbstractSqlQuery(args, valueIndex)];
771
- }),
772
- Helper<OptimisationMatchFn<EqualsNode>>((args) => {
773
- checkArgs('IsNotDistinctFrom', args, 2);
774
- const a = getAbstractSqlQuery(args, 0);
775
- const b = getAbstractSqlQuery(args, 1);
776
- if (isNotNullable(a) && isNotNullable(b)) {
777
- return ['Equals', a, b];
778
- }
779
- return false;
780
- }),
781
- matchArgs<IsNotDistinctFromNode>('IsNotDistinctFrom', AnyValue, AnyValue),
782
- ),
783
- IsDistinctFrom: tryMatches(
784
- Helper<OptimisationMatchFn<ExistsNode>>((args) => {
785
- checkArgs('IsDistinctFrom', args, 2);
786
- let valueIndex;
787
- if (args[0][0] === 'Null') {
788
- valueIndex = 1;
789
- } else if (args[1][0] === 'Null') {
790
- valueIndex = 0;
791
- } else {
792
- return false;
793
- }
794
-
795
- return ['Exists', getAbstractSqlQuery(args, valueIndex)];
796
- }),
797
- Helper<OptimisationMatchFn<NotEqualsNode>>((args) => {
798
- checkArgs('IsDistinctFrom', args, 2);
799
- const a = getAbstractSqlQuery(args, 0);
800
- const b = getAbstractSqlQuery(args, 1);
801
- if (isNotNullable(a) && isNotNullable(b)) {
802
- return ['NotEquals', a, b];
803
- }
804
- return false;
805
- }),
806
- matchArgs<IsDistinctFromNode>('IsDistinctFrom', AnyValue, AnyValue),
807
- ),
808
- Between: matchArgs<BetweenNode>('Between', AnyValue, AnyValue, AnyValue),
809
- Add: tryMatches(MathOp<AddNode>('Add'), Helper(AddDateMatcher)),
810
- Subtract: tryMatches<
811
- | SubtractNode
812
- | SubtractDateDateNode
813
- | SubtractDateNumberNode
814
- | SubtractDateDurationNode
815
- >(MathOp<SubtractNode>('Subtract'), Helper(SubtractDateMatcher)),
816
- SubtractDateDate: matchArgs<SubtractDateDateNode>(
817
- 'SubtractDateDate',
818
- DateValue,
819
- DateValue,
820
- ),
821
- SubtractDateNumber: matchArgs<SubtractDateNumberNode>(
822
- 'SubtractDateNumber',
823
- DateValue,
824
- NumericValue,
825
- ),
826
- SubtractDateDuration: matchArgs<SubtractDateDurationNode>(
827
- 'SubtractDateDuration',
828
- DateValue,
829
- DurationValue,
830
- ),
831
- AddDateDuration: matchArgs<AddDateDurationNode>(
832
- 'AddDateDuration',
833
- DateValue,
834
- DurationValue,
835
- ),
836
- AddDateNumber: matchArgs<AddDateNumberNode>(
837
- 'AddDateNumber',
838
- DateValue,
839
- NumericValue,
840
- ),
841
- Multiply: MathOp<MultiplyNode>('Multiply'),
842
- Divide: MathOp<DivideNode>('Divide'),
843
- BitwiseAnd: MathOp<BitwiseAndNode>('BitwiseAnd'),
844
- BitwiseShiftRight: MathOp<BitwiseShiftRightNode>('BitwiseShiftRight'),
845
- Year: ExtractNumericDatePart('Year'),
846
- Month: ExtractNumericDatePart('Month'),
847
- Day: ExtractNumericDatePart('Day'),
848
- Hour: ExtractNumericDatePart('Hour'),
849
- Minute: ExtractNumericDatePart('Minute'),
850
- Second: ExtractNumericDatePart('Second'),
851
- Fractionalseconds: ExtractNumericDatePart('Fractionalseconds'),
852
- Totalseconds: matchArgs('Totalseconds', DurationValue),
853
- Concat: Concatenate,
854
- Concatenate,
855
- ConcatenateWithSeparator,
856
- Replace: matchArgs<ReplaceNode>('Replace', TextValue, TextValue, TextValue),
857
- CharacterLength: matchArgs<CharacterLengthNode>('CharacterLength', TextValue),
858
- StrPos: matchArgs<StrPosNode>('StrPos', TextValue, TextValue),
859
- StartsWith: matchArgs<StartsWithNode>('StartsWith', TextValue, TextValue),
860
- Substring: (args) => {
861
- checkMinArgs('Substring', args, 2);
862
- const str = TextValue(getAbstractSqlQuery(args, 0));
863
- const nums = args.slice(1).map((arg) => {
864
- if (!isAbstractSqlQuery(arg)) {
865
- throw new SyntaxError(
866
- `Expected AbstractSqlQuery array but got ${typeof arg}`,
867
- );
868
- }
869
- return NumericValue(arg);
870
- });
871
- return ['Substring', str, ...nums];
872
- },
873
- Right: matchArgs<RightNode>('Right', TextValue, NumericValue),
874
- Tolower: Lower,
875
- ToLower: Lower,
876
- Lower,
877
- Toupper: Upper,
878
- ToUpper: Upper,
879
- Upper,
880
- Trim: matchArgs<TrimNode>('Trim', TextValue),
881
- Round: matchArgs<RoundNode>('Round', NumericValue),
882
- Floor: matchArgs<FloorNode>('Floor', NumericValue),
883
- Ceiling: matchArgs<CeilingNode>('Ceiling', NumericValue),
884
- ToDate: matchArgs<ToDateNode>('ToDate', DateValue),
885
- DateTrunc: (args) => {
886
- checkMinArgs('DateTrunc', args, 2);
887
- const precision = TextValue(getAbstractSqlQuery(args, 0));
888
- const date = DateValue(getAbstractSqlQuery(args, 1));
889
- const timeZone =
890
- args.length === 3 ? TextValue(getAbstractSqlQuery(args, 2)) : undefined;
891
- return timeZone
892
- ? ['DateTrunc', precision, date, timeZone]
893
- : ['DateTrunc', precision, date];
894
- },
895
- ToTime: matchArgs<ToTimeNode>('ToTime', DateValue),
896
- ExtractJSONPathAsText: (args): ExtractJSONPathAsTextNode => {
897
- checkMinArgs('ExtractJSONPathAsText', args, 1);
898
- const json = JSONValue(getAbstractSqlQuery(args, 0));
899
- const path = ArrayValue(getAbstractSqlQuery(args, 1));
900
- return ['ExtractJSONPathAsText', json, path];
901
- },
902
- TextArray: (args) => {
903
- // Allow for populated and empty arrays
904
- return ['TextArray', ...args.map(TextValue)];
905
- },
906
- ToJSON: matchArgs<ToJSONNode>('ToJSON', AnyValue),
907
- Any: matchArgs<AnyNode>('Any', AnyValue, identity),
908
- Coalesce: (args): CoalesceNode => {
909
- checkMinArgs('Coalesce', args, 2);
910
- return [
911
- 'Coalesce',
912
- ...(args.map(AnyValue) as [
913
- AnyTypeNodes,
914
- AnyTypeNodes,
915
- ...AnyTypeNodes[],
916
- ]),
917
- ];
918
- },
919
- Case: (args) => {
920
- checkMinArgs('Case', args, 1);
921
- return [
922
- 'Case',
923
- ...args.map((arg, index): WhenNode | ElseNode => {
924
- if (!isAbstractSqlQuery(arg)) {
925
- throw new SyntaxError(
926
- `Expected AbstractSqlQuery array but got ${typeof arg}`,
927
- );
928
- }
929
- const [type, ...rest] = arg;
930
- switch (type) {
931
- case 'When': {
932
- checkArgs('When', rest, 2);
933
- const matches = BooleanValue(getAbstractSqlQuery(rest, 0));
934
- const resultValue = AnyValue(getAbstractSqlQuery(rest, 1));
935
- return ['When', matches, resultValue];
936
- }
937
- case 'Else':
938
- if (index !== args.length - 1) {
939
- throw new SyntaxError('Else must be the last element of a Case');
940
- }
941
- checkArgs('Else', rest, 1);
942
- return ['Else', AnyValue(getAbstractSqlQuery(rest, 0))];
943
- default:
944
- throw new SyntaxError('Case can only contain When/Else');
945
- }
946
- }),
947
- ] as CaseNode;
948
- },
949
- And: tryMatches(
950
- Helper<OptimisationMatchFn<AnyTypeNodes>>((args) => {
951
- if (args.length !== 1) {
952
- return false;
953
- }
954
- return getAbstractSqlQuery(args, 0);
955
- }),
956
- Helper<OptimisationMatchFn<AndNode>>((args) => {
957
- checkMinArgs('And', args, 2);
958
- // Collapse nested ANDs.
959
- let maybeHelped = false;
960
- const conditions = args.flatMap((arg) => {
961
- if (!isAbstractSqlQuery(arg)) {
962
- throw new SyntaxError(
963
- `Expected AbstractSqlQuery array but got ${typeof arg}`,
964
- );
965
- }
966
- if (arg[0] === 'And') {
967
- maybeHelped = true;
968
- return arg.slice(1);
969
- }
970
- return [arg];
971
- });
972
- if (!maybeHelped) {
973
- // Make sure we actually hit an optimisation case
974
- return false;
975
- }
976
-
977
- return ['And', ...conditions] as AndNode;
978
- }),
979
- Helper<OptimisationMatchFn<BooleanNode | AndNode>>((args) => {
980
- checkMinArgs('And', args, 2);
981
- // Reduce any booleans
982
- let maybeHelped = false;
983
- let containsFalse = false;
984
- const conditions = args.filter((arg) => {
985
- if (arg[0] === 'Boolean') {
986
- if (arg[1] === true) {
987
- maybeHelped = true;
988
- return false;
989
- } else if (arg[1] === false) {
990
- containsFalse = true;
991
- }
992
- }
993
- return true;
994
- });
995
- if (containsFalse) {
996
- return ['Boolean', false];
997
- }
998
- if (maybeHelped) {
999
- return ['And', ...conditions] as AndNode;
1000
- }
1001
- return false;
1002
- }),
1003
- Helper<OptimisationMatchFn<AndNode>>((args) => {
1004
- checkMinArgs('And', args, 2);
1005
- // Optimise id != 1 AND id != 2 AND id != 3 -> id NOT IN [1, 2, 3]
1006
- const fieldBuckets: Record<
1007
- string,
1008
- Array<
1009
- [
1010
- 'NotEquals' | 'NotIn',
1011
- FieldNode | ReferencedFieldNode,
1012
- AbstractSqlType,
1013
- ...AbstractSqlType[],
1014
- ]
1015
- >
1016
- > = {};
1017
- const others: AnyTypeNodes[] = [];
1018
- let maybeHelped = false;
1019
- args.map((arg) => {
1020
- if (!isAbstractSqlQuery(arg)) {
1021
- throw new SyntaxError(
1022
- `Expected AbstractSqlQuery array but got ${typeof arg}`,
1023
- );
1024
- }
1025
- if (arg[0] === 'NotEquals') {
1026
- const fieldBool = FieldNotEquals(arg.slice(1));
1027
- if (fieldBool !== false) {
1028
- const fieldRef = `${fieldBool[1]}`;
1029
- if (fieldBuckets[fieldRef] == null) {
1030
- fieldBuckets[fieldRef] = [fieldBool];
1031
- } else {
1032
- // We're adding a second match, so that means we can optimise
1033
- maybeHelped = true;
1034
- fieldBuckets[fieldRef].push(fieldBool);
1035
- }
1036
- return;
1037
- }
1038
- } else if (arg[0] === 'NotIn') {
1039
- const fieldRef = `${arg[1]}`;
1040
- if (fieldBuckets[fieldRef] == null) {
1041
- fieldBuckets[fieldRef] = [arg as NotInNode];
1042
- } else {
1043
- // We're adding a second match, so that means we can optimise
1044
- maybeHelped = true;
1045
- fieldBuckets[fieldRef].push(arg as NotInNode);
1046
- }
1047
- return;
1048
- }
1049
- others.push(arg);
1050
- });
1051
- // Make sure we have at least some fields entries that can be optimised
1052
- if (!maybeHelped) {
1053
- return false;
1054
- }
1055
- const fields = Object.keys(fieldBuckets).map((fieldRef) => {
1056
- const fieldBucket = fieldBuckets[fieldRef];
1057
- if (fieldBucket.length === 1) {
1058
- return fieldBucket[0];
1059
- } else {
1060
- return [
1061
- 'NotIn',
1062
- fieldBucket[0][1],
1063
- ...fieldBucket.flatMap((field) => field.slice(2)),
1064
- ];
1065
- }
1066
- });
1067
- return ['And', ...fields, ...others] as AndNode;
1068
- }),
1069
- (args) => {
1070
- checkMinArgs('And', args, 2);
1071
- return [
1072
- 'And',
1073
- ...args.map((arg) => {
1074
- if (!isAbstractSqlQuery(arg)) {
1075
- throw new SyntaxError(
1076
- `Expected AbstractSqlQuery array but got ${typeof arg}`,
1077
- );
1078
- }
1079
- return BooleanValue(arg);
1080
- }),
1081
- ];
1082
- },
1083
- ),
1084
- Or: tryMatches(
1085
- Helper<OptimisationMatchFn<AnyTypeNodes>>((args) => {
1086
- if (args.length !== 1) {
1087
- return false;
1088
- }
1089
- return getAbstractSqlQuery(args, 0);
1090
- }),
1091
- Helper<OptimisationMatchFn<OrNode>>((args) => {
1092
- checkMinArgs('Or', args, 2);
1093
- // Collapse nested ORs.
1094
- let maybeHelped = false;
1095
- const conditions = args.flatMap((arg) => {
1096
- if (!isAbstractSqlQuery(arg)) {
1097
- throw new SyntaxError(
1098
- `Expected AbstractSqlQuery array but got ${typeof arg}`,
1099
- );
1100
- }
1101
- if (arg[0] === 'Or') {
1102
- maybeHelped = true;
1103
- return arg.slice(1);
1104
- }
1105
- return [arg];
1106
- });
1107
- if (!maybeHelped) {
1108
- // Make sure we actually hit an optimisation case
1109
- return false;
1110
- }
1111
-
1112
- return ['Or', ...conditions] as OrNode;
1113
- }),
1114
- Helper<OptimisationMatchFn<BooleanNode | OrNode>>((args) => {
1115
- checkMinArgs('Or', args, 2);
1116
- // Reduce any booleans
1117
- let maybeHelped = false;
1118
- let containsTrue = false;
1119
- const conditions = args.filter((arg) => {
1120
- if (arg[0] === 'Boolean') {
1121
- if (arg[1] === false) {
1122
- maybeHelped = true;
1123
- return false;
1124
- } else if (arg[1] === true) {
1125
- containsTrue = true;
1126
- }
1127
- }
1128
- return true;
1129
- });
1130
- if (containsTrue) {
1131
- return ['Boolean', true];
1132
- }
1133
- if (maybeHelped) {
1134
- return ['Or', ...conditions] as OrNode;
1135
- }
1136
- return false;
1137
- }),
1138
- Helper<OptimisationMatchFn<InNode | OrNode>>((args) => {
1139
- checkMinArgs('Or', args, 2);
1140
- // Optimise id = 1 OR id = 2 OR id = 3 -> id IN [1, 2, 3]
1141
- const fieldBuckets: Record<
1142
- string,
1143
- Array<
1144
- [
1145
- 'Equals' | 'In',
1146
- FieldNode | ReferencedFieldNode,
1147
- AbstractSqlType,
1148
- ...AbstractSqlType[],
1149
- ]
1150
- >
1151
- > = {};
1152
- const others: AnyTypeNodes[] = [];
1153
- let maybeHelped = false;
1154
- args.map((arg) => {
1155
- if (!isAbstractSqlQuery(arg)) {
1156
- throw new SyntaxError(
1157
- `Expected AbstractSqlQuery array but got ${typeof arg}`,
1158
- );
1159
- }
1160
- if (arg[0] === 'Equals') {
1161
- const fieldBool = FieldEquals(arg.slice(1));
1162
- if (fieldBool !== false) {
1163
- const fieldRef = `${fieldBool[1]}`;
1164
- if (fieldBuckets[fieldRef] == null) {
1165
- fieldBuckets[fieldRef] = [fieldBool];
1166
- } else {
1167
- // We're adding a second match, so that means we can optimise
1168
- maybeHelped = true;
1169
- fieldBuckets[fieldRef].push(fieldBool);
1170
- }
1171
- return;
1172
- }
1173
- } else if (arg[0] === 'In') {
1174
- const fieldRef = `${arg[1]}`;
1175
- if (fieldBuckets[fieldRef] == null) {
1176
- fieldBuckets[fieldRef] = [arg as InNode];
1177
- } else {
1178
- // We're adding a second match, so that means we can optimise
1179
- maybeHelped = true;
1180
- fieldBuckets[fieldRef].push(arg as InNode);
1181
- }
1182
- return;
1183
- }
1184
- others.push(arg);
1185
- });
1186
- // Make sure we have at least some fields entries that can be optimised
1187
- if (!maybeHelped) {
1188
- return false;
1189
- }
1190
- const fields = Object.keys(fieldBuckets).map((fieldRef) => {
1191
- const fieldBucket = fieldBuckets[fieldRef];
1192
- if (fieldBucket.length === 1) {
1193
- return fieldBucket[0];
1194
- } else {
1195
- return [
1196
- 'In',
1197
- fieldBucket[0][1],
1198
- ...fieldBucket.flatMap((field) => field.slice(2)),
1199
- ];
1200
- }
1201
- });
1202
- return ['Or', ...fields, ...others] as OrNode;
1203
- }),
1204
- (args) => {
1205
- checkMinArgs('Or', args, 2);
1206
- return [
1207
- 'Or',
1208
- ...args.map((arg) => {
1209
- if (!isAbstractSqlQuery(arg)) {
1210
- throw new SyntaxError(
1211
- `Expected AbstractSqlQuery array but got ${typeof arg}`,
1212
- );
1213
- }
1214
- return BooleanValue(arg);
1215
- }),
1216
- ];
1217
- },
1218
- ),
1219
- Bind: (args) => {
1220
- if (noBinds) {
1221
- throw new SyntaxError('Cannot use a bind whilst they are disabled');
1222
- }
1223
- checkArgs('Bind', args, 1);
1224
- return ['Bind', ...args] as BindNode;
1225
- },
1226
- Text,
1227
- Value: Text,
1228
- Date: matchArgs('Date', identity),
1229
- Duration: (args): DurationNode => {
1230
- checkArgs('Duration', args, 1);
1231
-
1232
- const duration = args[0] as DurationNode[1];
1233
- if (duration == null || typeof duration !== 'object') {
1234
- throw new SyntaxError(
1235
- `Duration must be an object, got ${typeof duration}`,
1236
- );
1237
- }
1238
- const { negative, day, hour, minute, second } = duration;
1239
- if (day == null && hour == null && minute == null && second == null) {
1240
- throw new SyntaxError('Invalid duration');
1241
- }
1242
- return ['Duration', { negative, day, hour, minute, second }];
1243
- },
1244
- Exists: tryMatches<ExistsNode | BooleanNode>(
1245
- Helper<OptimisationMatchFn<BooleanNode>>((args) => {
1246
- checkArgs('Exists', args, 1);
1247
- const arg = getAbstractSqlQuery(args, 0);
1248
- if (isNotNullable(arg)) {
1249
- return ['Boolean', true];
1250
- }
1251
- return false;
1252
- }),
1253
- Helper<OptimisationMatchFn<BooleanNode>>((args) => {
1254
- checkArgs('Exists', args, 1);
1255
- const arg = getAbstractSqlQuery(args, 0);
1256
- if (isEmptySelectQuery(arg)) {
1257
- return ['Boolean', false];
1258
- }
1259
- return false;
1260
- }),
1261
- (args): ExistsNode => {
1262
- checkArgs('Exists', args, 1);
1263
- const arg = getAbstractSqlQuery(args, 0);
1264
- const [type, ...rest] = arg;
1265
- switch (type) {
1266
- case 'SelectQuery':
1267
- case 'UnionQuery':
1268
- return ['Exists', typeRules[type](rest)];
1269
- default:
1270
- return ['Exists', AnyValue(arg)];
1271
- }
1272
- },
1273
- ),
1274
- NotExists: tryMatches<BooleanNode | NotExistsNode>(
1275
- Helper<OptimisationMatchFn<BooleanNode>>((args) => {
1276
- checkArgs('Exists', args, 1);
1277
- const arg = getAbstractSqlQuery(args, 0);
1278
- if (isNotNullable(arg)) {
1279
- return ['Boolean', false];
1280
- }
1281
- return false;
1282
- }),
1283
- Helper<OptimisationMatchFn<BooleanNode>>((args) => {
1284
- checkArgs('Exists', args, 1);
1285
- const arg = getAbstractSqlQuery(args, 0);
1286
- if (isEmptySelectQuery(arg)) {
1287
- return ['Boolean', true];
1288
- }
1289
- return false;
1290
- }),
1291
- (args): NotExistsNode => {
1292
- checkArgs('NotExists', args, 1);
1293
- const arg = getAbstractSqlQuery(args, 0);
1294
- const [type, ...rest] = arg;
1295
- switch (type) {
1296
- case 'SelectQuery':
1297
- case 'UnionQuery':
1298
- return ['NotExists', typeRules[type](rest)];
1299
- default:
1300
- return ['NotExists', AnyValue(arg)];
1301
- }
1302
- },
1303
- ),
1304
- Not: tryMatches<NotNode | BooleanTypeNodes | NotEqualsNode | ExistsNode>(
1305
- Helper<OptimisationMatchFn<BooleanTypeNodes | NotEqualsNode | ExistsNode>>(
1306
- (args): BooleanTypeNodes | NotEqualsNode | ExistsNode | false => {
1307
- checkArgs('Not', args, 1);
1308
- const [type, ...rest] = getAbstractSqlQuery(args, 0);
1309
- switch (type) {
1310
- case 'Not':
1311
- return BooleanValue(rest[0] as AbstractSqlQuery);
1312
- case 'Equals':
1313
- return typeRules.NotEquals(rest);
1314
- case 'NotEquals':
1315
- return typeRules.Equals(rest);
1316
- case 'In':
1317
- return typeRules.NotIn(rest);
1318
- case 'NotIn':
1319
- return typeRules.In(rest);
1320
- case 'Exists':
1321
- return typeRules.NotExists(rest);
1322
- case 'NotExists':
1323
- return typeRules.Exists(rest);
1324
- default:
1325
- return false;
1326
- }
1327
- },
1328
- ),
1329
- matchArgs<NotNode>('Not', BooleanValue),
1330
- ),
1331
- In: (args) => {
1332
- checkMinArgs('In', args, 2);
1333
- const field = Field(getAbstractSqlQuery(args, 0));
1334
- const vals = args.slice(1).map((arg) => {
1335
- if (!isAbstractSqlQuery(arg)) {
1336
- throw new SyntaxError(
1337
- `Expected AbstractSqlQuery array but got ${typeof arg}`,
1338
- );
1339
- }
1340
- return AnyValue(arg);
1341
- }) as [AnyTypeNodes, ...AnyTypeNodes[]];
1342
- return ['In', field, ...vals];
1343
- },
1344
- NotIn: (args) => {
1345
- checkMinArgs('NotIn', args, 2);
1346
- const field = Field(getAbstractSqlQuery(args, 0));
1347
- const vals = args.slice(1).map((arg) => {
1348
- if (!isAbstractSqlQuery(arg)) {
1349
- throw new SyntaxError(
1350
- `Expected AbstractSqlQuery array but got ${typeof arg}`,
1351
- );
1352
- }
1353
- return AnyValue(arg);
1354
- }) as [AnyTypeNodes, ...AnyTypeNodes[]];
1355
- return ['NotIn', field, ...vals];
1356
- },
1357
- InsertQuery: (args): InsertQueryNode => {
1358
- const tables: FromNode[] = [];
1359
- let fields: FieldsNode[] = [];
1360
- let values: ValuesNode[] = [];
1361
- const where: WhereNode[] = [];
1362
- for (const arg of args) {
1363
- if (!isAbstractSqlQuery(arg)) {
1364
- throw new SyntaxError('`InsertQuery` args must all be arrays');
1365
- }
1366
- const [type, ...rest] = arg;
1367
- switch (type) {
1368
- case 'Fields':
1369
- if (fields.length !== 0) {
1370
- throw new SyntaxError(
1371
- `'InsertQuery' can only accept one '${type}'`,
1372
- );
1373
- }
1374
- checkMinArgs('Update fields', rest, 1);
1375
- fields = [arg as FieldsNode];
1376
- break;
1377
- case 'Values': {
1378
- if (values.length !== 0) {
1379
- throw new SyntaxError(
1380
- `'InsertQuery' can only accept one '${type}'`,
1381
- );
1382
- }
1383
- const valuesArray = getAbstractSqlQuery(rest, 0);
1384
- if (valuesArray.length > 0) {
1385
- const [valuesType, ...valuesRest] = valuesArray;
1386
- switch (valuesType) {
1387
- case 'SelectQuery':
1388
- case 'UnionQuery':
1389
- values = [['Values', typeRules[valuesType](valuesRest)]];
1390
- break;
1391
- default:
1392
- values = [['Values', valuesArray.map(Value)] as ValuesNode];
1393
- }
1394
- }
1395
- break;
1396
- }
1397
- case 'From':
1398
- tables.push(typeRules[type](rest));
1399
- break;
1400
- case 'Where':
1401
- // We ignore `Where` in insert queries
1402
- break;
1403
- default:
1404
- throw new SyntaxError(`'InsertQuery' does not support '${type}'`);
1405
- }
1406
- }
1407
- if (tables.length === 0) {
1408
- throw new SyntaxError("'InsertQuery' must have a From component");
1409
- }
1410
- if (fields.length === 0) {
1411
- throw new SyntaxError("'InsertQuery' requires a Fields component");
1412
- }
1413
- if (values.length === 0 && fields[0][1].length !== 0) {
1414
- throw new SyntaxError(
1415
- "'InsertQuery' requires Values component to be present if Fields are provided ",
1416
- );
1417
- }
1418
- if (
1419
- fields.length !== 0 &&
1420
- values.length !== 0 &&
1421
- !['SelectQuery', 'UnionQuery'].includes(values[0][0]) &&
1422
- fields[0].length !== values[0].length
1423
- ) {
1424
- throw new SyntaxError(
1425
- "'InsertQuery' requires Fields and Values components to have the same length or use a query for Values",
1426
- );
1427
- }
1428
- return ['InsertQuery', ...tables, ...fields, ...values, ...where];
1429
- },
1430
- UpdateQuery: (args): UpdateQueryNode => {
1431
- const tables: FromNode[] = [];
1432
- let fields: FieldsNode[] = [];
1433
- let values: ValuesNode[] = [];
1434
- let where: WhereNode[] = [];
1435
- for (const arg of args) {
1436
- if (!isAbstractSqlQuery(arg)) {
1437
- throw new SyntaxError('`UpdateQuery` args must all be arrays');
1438
- }
1439
- const [type, ...rest] = arg;
1440
- switch (type) {
1441
- case 'Fields':
1442
- if (fields.length !== 0) {
1443
- throw new SyntaxError(
1444
- `'UpdateQuery' can only accept one '${type}'`,
1445
- );
1446
- }
1447
- checkMinArgs('Update fields', rest, 1);
1448
- fields = [arg as FieldsNode];
1449
- break;
1450
- case 'Values': {
1451
- if (values.length !== 0) {
1452
- throw new SyntaxError(
1453
- `'UpdateQuery' can only accept one '${type}'`,
1454
- );
1455
- }
1456
- checkArgs('Update values', rest, 1);
1457
- const valuesArray = getAbstractSqlQuery(rest, 0);
1458
- checkMinArgs('Update values array', valuesArray, 1);
1459
- values = [['Values', valuesArray.map(Value)]];
1460
- break;
1461
- }
1462
- case 'From':
1463
- tables.push(typeRules[type](rest));
1464
- break;
1465
- case 'Where':
1466
- if (where.length !== 0) {
1467
- throw new SyntaxError(
1468
- `'UpdateQuery' can only accept one '${type}'`,
1469
- );
1470
- }
1471
- where = [typeRules[type](rest)];
1472
- break;
1473
- default:
1474
- throw new SyntaxError(`'UpdateQuery' does not support '${type}'`);
1475
- }
1476
- }
1477
- if (tables.length === 0) {
1478
- throw new SyntaxError("'UpdateQuery' must have a From component");
1479
- }
1480
- if (fields.length === 0) {
1481
- throw new SyntaxError("'UpdateQuery' requires a Fields component");
1482
- }
1483
- if (values.length === 0) {
1484
- throw new SyntaxError("'UpdateQuery' requires a Values component");
1485
- }
1486
-
1487
- return ['UpdateQuery', ...tables, ...fields, ...values, ...where];
1488
- },
1489
- DeleteQuery: (args): DeleteQueryNode => {
1490
- const tables: FromNode[] = [];
1491
- let where: WhereNode[] = [];
1492
- for (const arg of args) {
1493
- if (!isAbstractSqlQuery(arg)) {
1494
- throw new SyntaxError('`DeleteQuery` args must all be arrays');
1495
- }
1496
- const [type, ...rest] = arg;
1497
- switch (type) {
1498
- case 'From':
1499
- tables.push(typeRules[type](rest));
1500
- break;
1501
- case 'Where':
1502
- if (where.length !== 0) {
1503
- throw new SyntaxError(
1504
- `'DeleteQuery' can only accept one '${type}'`,
1505
- );
1506
- }
1507
- where = [typeRules[type](rest)];
1508
- break;
1509
- default:
1510
- throw new SyntaxError(`'DeleteQuery' does not support '${type}'`);
1511
- }
1512
- }
1513
- if (tables.length === 0) {
1514
- throw new SyntaxError("'DeleteQuery' must have a From component");
1515
- }
1516
-
1517
- return ['DeleteQuery', ...tables, ...where];
1518
- },
1519
-
1520
- EscapeForLike: matchArgs<EscapeForLikeNode>('EscapeForLike', TextValue),
1521
-
1522
- // Virtual functions
1523
- Contains: rewriteMatch(
1524
- 'Contains',
1525
- [TextValue, TextValue],
1526
- Helper<MatchFn<LikeNode>>(
1527
- ([haystack, needle]: [TextTypeNodes, TextTypeNodes]) => [
1528
- 'Like',
1529
- haystack,
1530
- [
1531
- 'Concatenate',
1532
- ['EmbeddedText', '%'],
1533
- ['EscapeForLike', needle],
1534
- ['EmbeddedText', '%'],
1535
- ],
1536
- ],
1537
- ),
1538
- ),
1539
- Substringof: rewriteMatch(
1540
- 'Substringof',
1541
- [TextValue, TextValue],
1542
- Helper<MatchFn<LikeNode>>(
1543
- ([needle, haystack]: [TextTypeNodes, TextTypeNodes]) => [
1544
- 'Like',
1545
- haystack,
1546
- [
1547
- 'Concatenate',
1548
- ['EmbeddedText', '%'],
1549
- ['EscapeForLike', needle],
1550
- ['EmbeddedText', '%'],
1551
- ],
1552
- ],
1553
- ),
1554
- ),
1555
- Startswith: rewriteMatch(
1556
- 'Startswith',
1557
- [TextValue, TextValue],
1558
- Helper<MatchFn<StartsWithNode>>(
1559
- ([haystack, needle]: [TextTypeNodes, TextTypeNodes]) => [
1560
- 'StartsWith',
1561
- haystack,
1562
- needle,
1563
- ],
1564
- ),
1565
- ),
1566
- Endswith: EndsWithMatcher,
1567
- EndsWith: EndsWithMatcher,
1568
- IndexOf: rewriteMatch(
1569
- 'IndexOf',
1570
- [TextValue, TextValue],
1571
- Helper<MatchFn<SubtractNode>>(
1572
- ([haystack, needle]: [TextTypeNodes, TextTypeNodes]) => [
1573
- 'Subtract',
1574
- ['StrPos', haystack, needle],
1575
- ['Number', 1],
1576
- ],
1577
- ),
1578
- ),
1579
- Indexof: rewriteMatch(
1580
- 'Indexof',
1581
- [TextValue, TextValue],
1582
- Helper<MatchFn<SubtractNode>>(
1583
- ([haystack, needle]: [TextTypeNodes, TextTypeNodes]) => [
1584
- 'Subtract',
1585
- ['StrPos', haystack, needle],
1586
- ['Number', 1],
1587
- ],
1588
- ),
1589
- ),
1590
- } satisfies Record<string, MatchFn<AnyTypeNodes>>;
1591
-
1592
- export const AbstractSQLOptimizer = (
1593
- abstractSQL: AbstractSqlQuery,
1594
- $noBinds = false,
1595
- ): AbstractSqlQuery => {
1596
- noBinds = $noBinds;
1597
- do {
1598
- helped = false;
1599
- const [type, ...rest] = abstractSQL;
1600
- switch (type) {
1601
- case 'SelectQuery':
1602
- case 'UnionQuery':
1603
- case 'InsertQuery':
1604
- case 'UpdateQuery':
1605
- case 'DeleteQuery':
1606
- abstractSQL = typeRules[type](rest);
1607
- break;
1608
- case 'UpsertQuery': {
1609
- checkArgs('UpsertQuery', rest, 2);
1610
- const insertQuery = getAbstractSqlQuery(rest, 0);
1611
- const updateQuery = getAbstractSqlQuery(rest, 1);
1612
- if (
1613
- insertQuery[0] !== 'InsertQuery' ||
1614
- updateQuery[0] !== 'UpdateQuery'
1615
- ) {
1616
- throw new SyntaxError(
1617
- 'UpsertQuery must have [InsertQuery, UpdateQuery] provided',
1618
- );
1619
- }
1620
- abstractSQL = [
1621
- 'UpsertQuery',
1622
- typeRules.InsertQuery(insertQuery.slice(1)),
1623
- typeRules.UpdateQuery(updateQuery.slice(1)),
1624
- ];
1625
- break;
1626
- }
1627
- default:
1628
- abstractSQL = AnyValue(abstractSQL) as AbstractSqlQuery;
1629
- }
1630
- } while (helped);
1631
- return abstractSQL;
1632
- };