@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,1730 +0,0 @@
1
- import $sbvrTypes from '@balena/sbvr-types';
2
- const { default: sbvrTypes } = $sbvrTypes;
3
- import type {
4
- AbstractSqlQuery,
5
- AbstractSqlType,
6
- InsertQueryNode,
7
- SelectQueryNode,
8
- UnionQueryNode,
9
- UpdateQueryNode,
10
- DeleteQueryNode,
11
- UpsertQueryNode,
12
- CoalesceNode,
13
- DurationNode,
14
- StrictTextTypeNodes,
15
- StrictNumberTypeNodes,
16
- StrictBooleanTypeNodes,
17
- StrictDateTypeNodes,
18
- StrictDurationTypeNodes,
19
- StrictTextArrayTypeNodes,
20
- StrictJSONTypeNodes,
21
- } from './abstract-sql-compiler.js';
22
- import { Engines, isFieldTypeNode } from './abstract-sql-compiler.js';
23
-
24
- export type Binding =
25
- | [string, any]
26
- | ['Bind', number | string | [string, string]];
27
- export interface SqlResult {
28
- query: string;
29
- bindings: Binding[];
30
- }
31
-
32
- type MetaMatchFn = (args: AbstractSqlQuery, indent: string) => string;
33
- type MatchFn = (args: AbstractSqlType[], indent: string) => string;
34
-
35
- let fieldOrderings: Binding[] = [];
36
- let fieldOrderingsLookup: Record<string, number> = {};
37
- let engine: Engines = Engines.postgres;
38
- let noBinds = false;
39
-
40
- export const comparisons = {
41
- Equals: ' = ',
42
- GreaterThan: ' > ',
43
- GreaterThanOrEqual: ' >= ',
44
- LessThan: ' < ',
45
- LessThanOrEqual: ' <= ',
46
- NotEquals: ' != ',
47
- Like: ' LIKE ',
48
- };
49
-
50
- const NestedIndent = (indent: string): string => indent + '\t';
51
-
52
- const escapeField = (field: string | AbstractSqlQuery) =>
53
- field === '*' ? '*' : `"${field}"`;
54
-
55
- const AnyValue: MetaMatchFn = (args, indent) => {
56
- const [type, ...rest] = args;
57
-
58
- for (const matcher of [
59
- isJSONValue,
60
- isDateValue,
61
- isTextValue,
62
- isNumericValue,
63
- isBooleanValue,
64
- isDurationValue,
65
- ]) {
66
- if (matcher(type)) {
67
- return typeRules[type](rest, indent);
68
- }
69
- }
70
-
71
- return UnknownValue(args, indent);
72
- };
73
- const UnknownValue: MetaMatchFn = (args, indent) => {
74
- const [type, ...rest] = args;
75
- switch (type) {
76
- case 'Null':
77
- case 'Field':
78
- case 'ReferencedField':
79
- case 'Bind':
80
- case 'Cast':
81
- case 'Case':
82
- case 'Coalesce':
83
- case 'ToJSON':
84
- case 'Any':
85
- case 'TextArray':
86
- return typeRules[type](rest, indent);
87
- case 'SelectQuery':
88
- case 'UnionQuery': {
89
- const nestedIndent = NestedIndent(indent);
90
- const query = typeRules[type](rest, nestedIndent);
91
- return '(' + nestedIndent + query + indent + ')';
92
- }
93
- default:
94
- throw new Error(`Invalid "UnknownValue" type: ${type}`);
95
- }
96
- };
97
- const MatchValue =
98
- (matcher: (type: unknown) => type is string): MetaMatchFn =>
99
- (args, indent) => {
100
- const [type, ...rest] = args;
101
- if (matcher(type)) {
102
- return typeRules[type](rest, indent);
103
- }
104
- return UnknownValue(args, indent);
105
- };
106
- export const isTextValue = (type: unknown): type is StrictTextTypeNodes[0] => {
107
- return (
108
- type === 'Text' ||
109
- type === 'EmbeddedText' ||
110
- type === 'Concatenate' ||
111
- type === 'ConcatenateWithSeparator' ||
112
- type === 'Lower' ||
113
- type === 'Upper' ||
114
- type === 'Trim' ||
115
- type === 'Replace' ||
116
- type === 'ExtractJSONPathAsText' ||
117
- type === 'Substring' ||
118
- type === 'Right' ||
119
- type === 'EscapeForLike'
120
- );
121
- };
122
- const TextValue = MatchValue(isTextValue);
123
- export const isNumericValue = (
124
- type: unknown,
125
- ): type is StrictNumberTypeNodes[0] => {
126
- return (
127
- type === 'Number' ||
128
- type === 'Real' ||
129
- type === 'Integer' ||
130
- type === 'Add' ||
131
- type === 'Subtract' ||
132
- type === 'Multiply' ||
133
- type === 'Divide' ||
134
- type === 'BitwiseAnd' ||
135
- type === 'BitwiseShiftRight' ||
136
- type === 'CharacterLength' ||
137
- type === 'StrPos' ||
138
- type === 'Year' ||
139
- type === 'Month' ||
140
- type === 'Day' ||
141
- type === 'Hour' ||
142
- type === 'Minute' ||
143
- type === 'Second' ||
144
- type === 'Fractionalseconds' ||
145
- type === 'Totalseconds' ||
146
- type === 'Round' ||
147
- type === 'Floor' ||
148
- type === 'Ceiling' ||
149
- type === 'Count' ||
150
- type === 'Average' ||
151
- type === 'Sum' ||
152
- type === 'SubtractDateDate'
153
- );
154
- };
155
- const NumericValue = MatchValue(isNumericValue);
156
- export const isBooleanValue = (
157
- type: unknown,
158
- ): type is StrictBooleanTypeNodes[0] => {
159
- return (
160
- type === 'Boolean' ||
161
- type === 'Not' ||
162
- type === 'And' ||
163
- type === 'Or' ||
164
- type === 'Exists' ||
165
- type === 'NotExists' ||
166
- type === 'Between' ||
167
- type === 'In' ||
168
- type === 'NotIn' ||
169
- type === 'Equals' ||
170
- type === 'GreaterThan' ||
171
- type === 'GreaterThanOrEqual' ||
172
- type === 'LessThan' ||
173
- type === 'LessThanOrEqual' ||
174
- type === 'NotEquals' ||
175
- type === 'Like' ||
176
- type === 'IsNotDistinctFrom' ||
177
- type === 'IsDistinctFrom' ||
178
- type === 'StartsWith' ||
179
- type === 'EqualsAny'
180
- );
181
- };
182
- const BooleanValue = MatchValue(isBooleanValue);
183
- export const isDateValue = (type: unknown): type is StrictDateTypeNodes[0] => {
184
- return (
185
- type === 'Date' ||
186
- type === 'ToDate' ||
187
- type === 'ToTime' ||
188
- type === 'CurrentTimestamp' ||
189
- type === 'CurrentDate' ||
190
- type === 'DateTrunc' ||
191
- type === 'AddDateNumber' ||
192
- type === 'AddDateDuration' ||
193
- type === 'SubtractDateDuration' ||
194
- type === 'SubtractDateNumber'
195
- );
196
- };
197
- const DateValue = MatchValue(isDateValue);
198
- export const isArrayValue = (
199
- type: unknown,
200
- ): type is StrictTextArrayTypeNodes[0] => {
201
- return type === 'TextArray';
202
- };
203
-
204
- export const isJSONValue = (type: unknown): type is StrictJSONTypeNodes[0] => {
205
- return type === 'AggregateJSON' || type === 'ToJSON';
206
- };
207
- const JSONValue = MatchValue(isJSONValue);
208
-
209
- export const isDurationValue = (
210
- type: unknown,
211
- ): type is StrictDurationTypeNodes[0] => {
212
- return type === 'Duration';
213
- };
214
- const DurationValue = MatchValue(isDurationValue);
215
-
216
- const Field: MetaMatchFn = (args, indent) => {
217
- if (isFieldTypeNode(args)) {
218
- const [type, ...rest] = args;
219
- return typeRules[type](rest, indent);
220
- } else {
221
- throw new SyntaxError(`Invalid field type: ${args[0]}`);
222
- }
223
- };
224
-
225
- export const isNotNullable = (node: AbstractSqlType): boolean => {
226
- switch (node[0]) {
227
- case 'EmbeddedText':
228
- case 'Boolean':
229
- // We don't support null binds so we can avoid checking them for null-ness
230
- // and avoid issues with postgres type inference
231
- case 'Bind':
232
- case 'Value':
233
- case 'Text':
234
- case 'Date':
235
- case 'Number':
236
- case 'Real':
237
- case 'Integer':
238
- case 'IsDistinctFrom':
239
- case 'IsNotDistinctFrom':
240
- case 'EqualsAny':
241
- case 'Exists':
242
- case 'NotExists':
243
- return true;
244
- case 'Coalesce':
245
- return (node as CoalesceNode).slice(1).some((n) => isNotNullable(n));
246
- case 'Not':
247
- return isNotNullable(node[1]);
248
- }
249
- return false;
250
- };
251
-
252
- const isAtomicNode = (n: AbstractSqlType): boolean =>
253
- isFieldTypeNode(n) ||
254
- n[0] === 'Bind' ||
255
- n[0] === 'Null' ||
256
- n[0] === 'Value' ||
257
- n[0] === 'Text' ||
258
- n[0] === 'Number' ||
259
- n[0] === 'Real' ||
260
- n[0] === 'Integer' ||
261
- n[0] === 'Boolean';
262
- const isNotDistinctFrom: MatchFn = (args, indent) => {
263
- const a = getAbstractSqlQuery(args, 0);
264
- const b = getAbstractSqlQuery(args, 1);
265
-
266
- let aSql = AnyValue(a, indent);
267
- let bSql = AnyValue(b, indent);
268
- // We can omit the parens if the value is a atomic type node, for slightly smaller/more readable sql
269
- if (!isAtomicNode(a)) {
270
- aSql = `(${aSql})`;
271
- }
272
- if (!isAtomicNode(b)) {
273
- bSql = `(${bSql})`;
274
- }
275
-
276
- if (engine === Engines.postgres) {
277
- const aIsNotNullable = isNotNullable(a);
278
- const bIsNotNullable = isNotNullable(b);
279
- if (aIsNotNullable && bIsNotNullable) {
280
- return `${aSql} = ${bSql}`;
281
- }
282
- const isNotNullChecks: string[] = [];
283
- if (!aIsNotNullable) {
284
- isNotNullChecks.push(`${aSql} IS NOT NULL`);
285
- }
286
- if (!bIsNotNullable) {
287
- isNotNullChecks.push(`${bSql} IS NOT NULL`);
288
- }
289
- const orBothNull =
290
- !aIsNotNullable && !bIsNotNullable
291
- ? ` OR ${aSql} IS NULL AND ${bSql} IS NULL`
292
- : '';
293
- return `${isNotNullChecks.join(
294
- ' AND ',
295
- )} AND ${aSql} = ${bSql}${orBothNull}`;
296
- } else if (engine === Engines.mysql) {
297
- return aSql + ' <=> ' + bSql;
298
- } else if (engine === Engines.websql) {
299
- return aSql + ' IS ' + bSql;
300
- } else {
301
- throw new SyntaxError(
302
- `IsDistinctFrom/IsNotDistinctFrom not supported on: ${engine}`,
303
- );
304
- }
305
- };
306
-
307
- export const isAbstractSqlQuery = (
308
- x: AbstractSqlType,
309
- ): x is AbstractSqlQuery => {
310
- return Array.isArray(x);
311
- };
312
- export const getAbstractSqlQuery = (
313
- args: AbstractSqlType[],
314
- index: number,
315
- ): AbstractSqlQuery => {
316
- const abstractSqlQuery = args[index];
317
- if (!isAbstractSqlQuery(abstractSqlQuery)) {
318
- throw new SyntaxError(
319
- `Expected AbstractSqlQuery array but got ${typeof abstractSqlQuery}`,
320
- );
321
- }
322
- return abstractSqlQuery;
323
- };
324
-
325
- const Comparison = (comparison: keyof typeof comparisons): MatchFn => {
326
- return (args, indent) => {
327
- checkArgs(comparison, args, 2);
328
- const a = precedenceSafeOpValue(comparison, AnyValue, args, 0, indent);
329
- const b = precedenceSafeOpValue(comparison, AnyValue, args, 1, indent);
330
- return a + comparisons[comparison] + b;
331
- };
332
- };
333
- const NumberMatch = (type: string): MatchFn => {
334
- return (args) => {
335
- checkArgs(type, args, 1);
336
- const n = args[0];
337
- if (typeof n !== 'number') {
338
- throw new SyntaxError(`${type} expected number but got ${typeof n}`);
339
- }
340
- return `${n}`;
341
- };
342
- };
343
- const JoinMatch = (joinType: string): MatchFn => {
344
- let sqlJoinType: string;
345
- switch (joinType) {
346
- case 'Join':
347
- sqlJoinType = 'JOIN ';
348
- break;
349
- case 'LeftJoin':
350
- sqlJoinType = 'LEFT JOIN ';
351
- break;
352
- case 'RightJoin':
353
- sqlJoinType = 'RIGHT JOIN ';
354
- break;
355
- case 'FullJoin':
356
- sqlJoinType = 'FULL JOIN ';
357
- break;
358
- case 'CrossJoin':
359
- sqlJoinType = 'CROSS JOIN ';
360
- break;
361
- default:
362
- throw new Error(`Unknown join type: '${joinType}'`);
363
- }
364
- return (args, indent) => {
365
- if (args.length !== 1 && args.length !== 2) {
366
- throw new SyntaxError(`"${joinType}" requires 1/2 arg(s)`);
367
- }
368
- const from = MaybeAlias(getAbstractSqlQuery(args, 0), indent, FromMatch);
369
- if (args.length === 1) {
370
- return sqlJoinType + from;
371
- }
372
- const [type, ...rest] = getAbstractSqlQuery(args, 1);
373
- switch (type) {
374
- case 'On': {
375
- checkArgs('On', rest, 1);
376
- const ruleBody = BooleanValue(
377
- getAbstractSqlQuery(rest, 0),
378
- NestedIndent(indent),
379
- );
380
- return sqlJoinType + from + ' ON ' + ruleBody;
381
- }
382
- default:
383
- throw new SyntaxError(
384
- `'${joinType}' clause does not support '${type}' clause`,
385
- );
386
- }
387
- };
388
- };
389
- const mathOps = {
390
- Add: '+',
391
- Subtract: '-',
392
- Multiply: '*',
393
- Divide: '/',
394
- BitwiseAnd: '&',
395
- BitwiseShiftRight: '>>',
396
- };
397
- export type MathOps = keyof typeof mathOps;
398
-
399
- const mathOperatorNodeTypes = new Set([
400
- ...Object.keys(mathOps),
401
- 'AddDateDuration',
402
- 'AddDateNumber',
403
- 'SubtractDateDate',
404
- 'SubtractDateDuration',
405
- 'SubtractDateNumber',
406
- ]);
407
-
408
- const precedenceSafeOpValue = (
409
- parentNodeType: string,
410
- valueMatchFn: MetaMatchFn,
411
- args: AbstractSqlType[],
412
- index: number,
413
- indent: string,
414
- ) => {
415
- const operandAbstractSql = getAbstractSqlQuery(args, index);
416
- const valueExpr = valueMatchFn(operandAbstractSql, indent);
417
- const [childNodeType] = operandAbstractSql;
418
- if (
419
- (mathOperatorNodeTypes.has(parentNodeType) &&
420
- mathOperatorNodeTypes.has(childNodeType)) ||
421
- // We need parenthesis for chained boolean comparisons, otherwise PostgreSQL complains.
422
- (parentNodeType in comparisons && childNodeType in comparisons)
423
- ) {
424
- return `(${valueExpr})`;
425
- }
426
- return valueExpr;
427
- };
428
-
429
- const MathOp = (type: keyof typeof mathOps): MatchFn => {
430
- return (args, indent) => {
431
- checkArgs(type, args, 2);
432
- const a = precedenceSafeOpValue(type, NumericValue, args, 0, indent);
433
- const b = precedenceSafeOpValue(type, NumericValue, args, 1, indent);
434
- return `${a} ${mathOps[type]} ${b}`;
435
- };
436
- };
437
-
438
- const fractionalSecondsFormat = function (date: string) {
439
- return this['Totalseconds'](date) + ' - ' + this['Second'](date);
440
- };
441
- const websqlBasicDateFormat = (format: string) => {
442
- return (date: string) => `STRFTIME('${format}', ${date})`;
443
- };
444
- const websqlDateFormats = {
445
- Year: websqlBasicDateFormat('%Y'),
446
- Month: websqlBasicDateFormat('%m'),
447
- Day: websqlBasicDateFormat('%d'),
448
- Hour: websqlBasicDateFormat('%H'),
449
- Minute: websqlBasicDateFormat('%M'),
450
- Second: websqlBasicDateFormat('%S'),
451
- Fractionalseconds: fractionalSecondsFormat,
452
- Totalseconds: websqlBasicDateFormat('%f'),
453
- };
454
-
455
- const basicDateFormat = function (part: string) {
456
- return (date: string) => `EXTRACT('${part}' FROM ${date})`;
457
- };
458
- const dateFormats = {
459
- Year: basicDateFormat('YEAR'),
460
- Month: basicDateFormat('MONTH'),
461
- Day: basicDateFormat('DAY'),
462
- Hour: basicDateFormat('HOUR'),
463
- Minute: basicDateFormat('MINUTE'),
464
- Second: (date: string) => `FLOOR(${dateFormats['Totalseconds'](date)})`,
465
- Fractionalseconds: fractionalSecondsFormat,
466
- Totalseconds: basicDateFormat('SECOND'),
467
- };
468
- const ExtractNumericDatePart = (type: keyof typeof dateFormats): MatchFn => {
469
- return (args, indent) => {
470
- checkArgs(type, args, 1);
471
- const date = DateValue(getAbstractSqlQuery(args, 0), indent);
472
- if (engine === Engines.websql) {
473
- return websqlDateFormats[type](date);
474
- } else {
475
- return dateFormats[type](date);
476
- }
477
- };
478
- };
479
-
480
- const Text: MatchFn = (args) => {
481
- checkArgs('Text', args, 1);
482
- if (noBinds) {
483
- return `'${args[0]}'`;
484
- } else {
485
- return AddBind(['Text', args[0]]);
486
- }
487
- };
488
-
489
- export const checkArgs = (matchName: string, args: any[], num: number) => {
490
- if (args.length !== num) {
491
- throw new SyntaxError(`"${matchName}" requires ${num} arg(s)`);
492
- }
493
- };
494
- export const checkMinArgs = (matchName: string, args: any[], num: number) => {
495
- if (args.length < num) {
496
- throw new SyntaxError(`"${matchName}" requires at least ${num} arg(s)`);
497
- }
498
- };
499
-
500
- const AddDateNumber: MatchFn = (args, indent) => {
501
- checkArgs('AddDateNumber', args, 2);
502
- const a = precedenceSafeOpValue('AddDateNumber', DateValue, args, 0, indent);
503
- const b = precedenceSafeOpValue(
504
- 'AddDateNumber',
505
- NumericValue,
506
- args,
507
- 1,
508
- indent,
509
- );
510
-
511
- if (engine === Engines.postgres) {
512
- return `${a} + ${b}`;
513
- } else if (engine === Engines.mysql) {
514
- return `ADDDATE(${a}, ${b})`;
515
- } else {
516
- throw new SyntaxError('AddDateNumber not supported on: ' + engine);
517
- }
518
- };
519
-
520
- const AddDateDuration: MatchFn = (args, indent) => {
521
- checkArgs('AddDateDuration', args, 2);
522
- const a = precedenceSafeOpValue(
523
- 'AddDateDuration',
524
- DateValue,
525
- args,
526
- 0,
527
- indent,
528
- );
529
- const b = precedenceSafeOpValue(
530
- 'AddDateDuration',
531
- DurationValue,
532
- args,
533
- 1,
534
- indent,
535
- );
536
-
537
- if (engine === Engines.postgres) {
538
- return `${a} + ${b}`;
539
- } else if (engine === Engines.mysql) {
540
- return `DATE_ADD(${a}, ${b})`;
541
- } else {
542
- throw new SyntaxError('AddDateDuration not supported on: ' + engine);
543
- }
544
- };
545
-
546
- const SubtractDateDuration: MatchFn = (args, indent) => {
547
- checkArgs('SubtractDateDuration', args, 2);
548
- const a = precedenceSafeOpValue(
549
- 'SubtractDateDuration',
550
- DateValue,
551
- args,
552
- 0,
553
- indent,
554
- );
555
- const b = precedenceSafeOpValue(
556
- 'SubtractDateDuration',
557
- DurationValue,
558
- args,
559
- 1,
560
- indent,
561
- );
562
-
563
- if (engine === Engines.postgres) {
564
- return `${a} - ${b}`;
565
- } else if (engine === Engines.mysql) {
566
- return `DATE_SUB(${a}, ${b})`;
567
- } else {
568
- throw new SyntaxError('SubtractDateDuration not supported on: ' + engine);
569
- }
570
- };
571
-
572
- const SubtractDateNumber: MatchFn = (args, indent) => {
573
- checkArgs('SubtractDateNumber', args, 2);
574
- const a = precedenceSafeOpValue(
575
- 'SubtractDateNumber',
576
- DateValue,
577
- args,
578
- 0,
579
- indent,
580
- );
581
- const b = precedenceSafeOpValue(
582
- 'SubtractDateNumber',
583
- NumericValue,
584
- args,
585
- 1,
586
- indent,
587
- );
588
-
589
- if (engine === Engines.postgres) {
590
- return `${a} - ${b}`;
591
- } else if (engine === Engines.mysql) {
592
- return `SUBDATE(${a}, ${b})`;
593
- } else {
594
- throw new SyntaxError('SubtractDateNumber not supported on: ' + engine);
595
- }
596
- };
597
-
598
- const SubtractDateDate: MatchFn = (args, indent) => {
599
- checkArgs('SubtractDateDate', args, 2);
600
- const a = precedenceSafeOpValue(
601
- 'SubtractDateDate',
602
- DateValue,
603
- args,
604
- 0,
605
- indent,
606
- );
607
- const b = precedenceSafeOpValue(
608
- 'SubtractDateDate',
609
- DateValue,
610
- args,
611
- 1,
612
- indent,
613
- );
614
- if (engine === Engines.postgres) {
615
- return `${a} - ${b}`;
616
- } else if (engine === Engines.mysql) {
617
- return `DATEDIFF(${a}, ${b})`;
618
- } else {
619
- throw new SyntaxError('SubtractDateDate not supported on: ' + engine);
620
- }
621
- };
622
-
623
- const Value = (arg: any, indent: string): string => {
624
- switch (arg) {
625
- case 'Default':
626
- return 'DEFAULT';
627
- default: {
628
- const [type, ...rest] = arg;
629
- switch (type) {
630
- case 'Null':
631
- case 'Bind':
632
- case 'Value':
633
- case 'Text':
634
- case 'Number':
635
- case 'Real':
636
- case 'Integer':
637
- case 'Boolean':
638
- return typeRules[type](rest, indent);
639
- default:
640
- throw new SyntaxError(`Invalid type for Value ${type}`);
641
- }
642
- }
643
- }
644
- };
645
-
646
- const SelectMatch: MetaMatchFn = (args, indent) => {
647
- const [type, ...rest] = args;
648
- switch (type) {
649
- case 'Count':
650
- return typeRules[type](rest, indent);
651
- default:
652
- return AnyValue(args, indent);
653
- }
654
- };
655
- const FromMatch: MetaMatchFn = (args, indent) => {
656
- const [type, ...rest] = args;
657
- switch (type) {
658
- case 'SelectQuery':
659
- case 'UnionQuery': {
660
- const nestedindent = NestedIndent(indent);
661
- const query = typeRules[type](rest, nestedindent);
662
- return '(' + nestedindent + query + indent + ')';
663
- }
664
- case 'Table': {
665
- checkArgs('Table', rest, 1);
666
- const [table] = rest;
667
- if (typeof table !== 'string') {
668
- throw new SyntaxError('`Table` table must be a string');
669
- }
670
- return escapeField(table);
671
- }
672
- default:
673
- throw new SyntaxError(`From does not support ${type}`);
674
- }
675
- };
676
-
677
- const MaybeAlias = (
678
- args: AbstractSqlQuery,
679
- indent: string,
680
- matchFn: MatchFn,
681
- ): string => {
682
- const [type, ...rest] = args;
683
- switch (type) {
684
- case 'Alias': {
685
- checkArgs('Alias', rest, 2);
686
- const field = matchFn(getAbstractSqlQuery(rest, 0), indent);
687
- return `${field} AS "${rest[1]}"`;
688
- }
689
- default:
690
- return matchFn(args, indent);
691
- }
692
- };
693
-
694
- const AddBind = (bind: Binding): string => {
695
- if (noBinds) {
696
- throw new SyntaxError('Cannot use a bind whilst they are disabled');
697
- }
698
- if (engine === Engines.postgres) {
699
- if (bind[0] === 'Bind') {
700
- const key = JSON.stringify(bind[1]);
701
- const existingBindIndex = fieldOrderingsLookup[key];
702
- if (existingBindIndex != null) {
703
- // Reuse the existing bind if there is one
704
- return '$' + existingBindIndex;
705
- }
706
- const nextID = fieldOrderings.push(bind);
707
- fieldOrderingsLookup[key] = nextID;
708
- return '$' + nextID;
709
- }
710
- return '$' + fieldOrderings.push(bind);
711
- } else {
712
- fieldOrderings.push(bind);
713
- return '?';
714
- }
715
- };
716
-
717
- const typeRules: Record<string, MatchFn> = {
718
- UnionQuery: (args, indent) => {
719
- checkMinArgs('UnionQuery', args, 2);
720
- return args
721
- .map((arg) => {
722
- if (!isAbstractSqlQuery(arg)) {
723
- throw new SyntaxError(
724
- `Expected AbstractSqlQuery array but got ${typeof arg}`,
725
- );
726
- }
727
- const [type, ...rest] = arg;
728
- switch (type) {
729
- case 'SelectQuery':
730
- case 'UnionQuery':
731
- return typeRules[type](rest, indent);
732
- default:
733
- throw new SyntaxError(`UnionQuery does not support ${type}`);
734
- }
735
- })
736
- .join(indent + 'UNION' + indent);
737
- },
738
- SelectQuery: (args, indent) => {
739
- const tables: string[] = [];
740
- const joins: string[] = [];
741
- let select = '';
742
- const groups = {
743
- Where: '',
744
- GroupBy: '',
745
- Having: '',
746
- OrderBy: '',
747
- Limit: '',
748
- Offset: '',
749
- };
750
- for (const arg of args) {
751
- if (!isAbstractSqlQuery(arg)) {
752
- throw new SyntaxError('`SelectQuery` args must all be arrays');
753
- }
754
- const [type, ...rest] = arg;
755
- switch (type) {
756
- case 'Select':
757
- if (select !== '') {
758
- throw new SyntaxError(
759
- `'SelectQuery' can only accept one '${type}'`,
760
- );
761
- }
762
- select = typeRules[type](rest, indent);
763
- break;
764
- case 'From':
765
- tables.push(typeRules[type](rest, indent));
766
- break;
767
- case 'Join':
768
- case 'LeftJoin':
769
- case 'RightJoin':
770
- case 'FullJoin':
771
- case 'CrossJoin':
772
- joins.push(typeRules[type](rest, indent));
773
- break;
774
- case 'Where':
775
- case 'GroupBy':
776
- case 'Having':
777
- case 'OrderBy':
778
- case 'Limit':
779
- case 'Offset':
780
- if (groups[type] !== '') {
781
- throw new SyntaxError(
782
- `'SelectQuery' can only accept one '${type}'`,
783
- );
784
- }
785
- groups[type] = indent + typeRules[type](rest, indent);
786
- break;
787
- default:
788
- throw new SyntaxError(`'SelectQuery' does not support '${type}'`);
789
- }
790
- }
791
-
792
- if (tables.length === 0 && joins.length > 0) {
793
- throw new SyntaxError(
794
- 'Must have at least one From node in order to use Join nodes',
795
- );
796
- }
797
-
798
- const from =
799
- tables.length > 0
800
- ? indent + 'FROM ' + tables.join(',' + NestedIndent(indent))
801
- : '';
802
-
803
- const joinStr = joins.length > 0 ? indent + joins.join(indent) : '';
804
-
805
- return (
806
- 'SELECT ' +
807
- select +
808
- from +
809
- joinStr +
810
- groups.Where +
811
- groups.GroupBy +
812
- groups.Having +
813
- groups.OrderBy +
814
- groups.Limit +
815
- groups.Offset
816
- );
817
- },
818
- Select: (args, indent) => {
819
- checkArgs('Select', args, 1);
820
- args = getAbstractSqlQuery(args, 0);
821
- if (args.length === 0) {
822
- // Empty select fields are converted to `SELECT 1`
823
- return '1';
824
- }
825
- return args
826
- .map((arg) => {
827
- if (!isAbstractSqlQuery(arg)) {
828
- throw new SyntaxError(
829
- `Expected AbstractSqlQuery array but got ${typeof arg}`,
830
- );
831
- }
832
- return MaybeAlias(arg, indent, SelectMatch);
833
- })
834
- .join(', ');
835
- },
836
- From: (args, indent) => {
837
- checkArgs('From', args, 1);
838
- return MaybeAlias(getAbstractSqlQuery(args, 0), indent, FromMatch);
839
- },
840
- Join: JoinMatch('Join'),
841
- LeftJoin: JoinMatch('LeftJoin'),
842
- RightJoin: JoinMatch('RightJoin'),
843
- FullJoin: JoinMatch('FullJoin'),
844
- CrossJoin: (args, indent) => {
845
- checkArgs('CrossJoin', args, 1);
846
- const from = MaybeAlias(getAbstractSqlQuery(args, 0), indent, FromMatch);
847
- return `CROSS JOIN ${from}`;
848
- },
849
- Where: (args, indent) => {
850
- checkArgs('Where', args, 1);
851
- const boolNode = getAbstractSqlQuery(args, 0);
852
- if (boolNode[0] === 'Boolean') {
853
- // This is designed to avoid cases of `WHERE 0`/`WHERE 1` which are invalid, ideally
854
- // we need to convert booleans to always use true/false but that's a major change
855
- return `WHERE ${boolNode[1] ? 'true' : 'false'}`;
856
- }
857
- const ruleBody = BooleanValue(boolNode, indent);
858
- return 'WHERE ' + ruleBody;
859
- },
860
- GroupBy: (args, indent) => {
861
- checkArgs('GroupBy', args, 1);
862
- const groups = getAbstractSqlQuery(args, 0);
863
- checkMinArgs('GroupBy groups', groups, 1);
864
- return (
865
- 'GROUP BY ' +
866
- groups.map((arg: AbstractSqlQuery) => AnyValue(arg, indent)).join(', ')
867
- );
868
- },
869
- Having: (args, indent) => {
870
- checkArgs('Having', args, 1);
871
- const havingBody = BooleanValue(getAbstractSqlQuery(args, 0), indent);
872
- return `HAVING ${havingBody}`;
873
- },
874
- OrderBy: (args, indent) => {
875
- checkMinArgs('OrderBy', args, 1);
876
- return (
877
- 'ORDER BY ' +
878
- args
879
- .map((arg: AbstractSqlQuery) => {
880
- checkMinArgs('OrderBy ordering', arg, 2);
881
- const order = arg[0];
882
- if (order !== 'ASC' && order !== 'DESC') {
883
- throw new SyntaxError(`Can only order by "ASC" or "DESC"`);
884
- }
885
- const value = AnyValue(getAbstractSqlQuery(arg, 1), indent);
886
- return `${value} ${order}`;
887
- })
888
- .join(',' + NestedIndent(indent))
889
- );
890
- },
891
- Limit: (args, indent) => {
892
- checkArgs('Limit', args, 1);
893
- const num = NumericValue(getAbstractSqlQuery(args, 0), indent);
894
- return `LIMIT ${num}`;
895
- },
896
- Offset: (args, indent) => {
897
- checkArgs('Offset', args, 1);
898
- const num = NumericValue(getAbstractSqlQuery(args, 0), indent);
899
- return `OFFSET ${num}`;
900
- },
901
- Count: (args) => {
902
- checkArgs('Count', args, 1);
903
- if (args[0] !== '*') {
904
- throw new SyntaxError('"Count" only supports "*"');
905
- }
906
- return 'COUNT(*)';
907
- },
908
- Average: (args, indent) => {
909
- checkArgs('Average', args, 1);
910
- const num = NumericValue(getAbstractSqlQuery(args, 0), indent);
911
- return `AVG(${num})`;
912
- },
913
- Sum: (args, indent) => {
914
- checkArgs('Sum', args, 1);
915
- const num = NumericValue(getAbstractSqlQuery(args, 0), indent);
916
- return `SUM(${num})`;
917
- },
918
- Field: (args) => {
919
- checkArgs('Field', args, 1);
920
- const [field] = args;
921
- if (typeof field !== 'string') {
922
- throw new SyntaxError('`Field` field must be a string');
923
- }
924
- return escapeField(field);
925
- },
926
- ReferencedField: (args) => {
927
- checkArgs('ReferencedField', args, 2);
928
- const [table, field] = args;
929
- if (typeof table !== 'string') {
930
- throw new SyntaxError('`ReferencedField` table must be a string');
931
- }
932
- if (typeof field !== 'string') {
933
- throw new SyntaxError('`ReferencedField` field must be a string');
934
- }
935
- return `"${table}".${escapeField(field)}`;
936
- },
937
- Cast: (args, indent) => {
938
- checkArgs('Cast', args, 2);
939
- const value = AnyValue(getAbstractSqlQuery(args, 0), indent);
940
- const typeName = args[1] as keyof typeof sbvrTypes;
941
- if (!sbvrTypes[typeName]?.types[engine]) {
942
- throw new SyntaxError(`Invalid cast type: ${typeName}`);
943
- }
944
- let type: string;
945
- const dbType = sbvrTypes[typeName].types[engine];
946
- if (typeof dbType === 'function') {
947
- type = dbType.castType;
948
- } else if (dbType.toUpperCase() === 'SERIAL') {
949
- // HACK: SERIAL type in postgres is really an INTEGER with automatic sequence,
950
- // so it's not actually possible to cast to SERIAL, instead you have to cast to INTEGER.
951
- type = 'INTEGER';
952
- } else if (dbType.toUpperCase() === 'BIGSERIAL') {
953
- // HACK: BIGSERIAL type in postgres is really a BIGINT with automatic sequence,
954
- // so it's not actually possible to cast to BIGSERIAL, instead you have to cast to BIGINT.
955
- type = 'BIGINT';
956
- } else {
957
- type = dbType;
958
- }
959
- return `CAST(${value} AS ${type})`;
960
- },
961
- // eslint-disable-next-line id-denylist
962
- Number: NumberMatch('Number'),
963
- Real: NumberMatch('Real'),
964
- Integer: NumberMatch('Integer'),
965
- // eslint-disable-next-line id-denylist
966
- Boolean: (args) => {
967
- checkArgs('Boolean', args, 1);
968
- const b = args[0];
969
- if (typeof b !== 'boolean') {
970
- throw new SyntaxError(`Boolean expected boolean but got ${typeof b}`);
971
- }
972
- return b ? 'TRUE' : 'FALSE';
973
- },
974
- EmbeddedText: (args) => {
975
- checkArgs('EmbeddedText', args, 1);
976
- return `'${args[0]}'`;
977
- },
978
- TextArray: (args, indent) => {
979
- const values = args.map((arg) => {
980
- if (!isAbstractSqlQuery(arg)) {
981
- throw new SyntaxError(
982
- `Expected AbstractSqlQuery array but got ${typeof arg}`,
983
- );
984
- }
985
- return TextValue(arg, indent);
986
- });
987
- return values.length
988
- ? `ARRAY[${values.join(', ')}]`
989
- : 'CAST(ARRAY[] as TEXT[])';
990
- },
991
- Null: (args) => {
992
- checkArgs('Null', args, 0);
993
- return 'NULL';
994
- },
995
- CurrentTimestamp: (args) => {
996
- checkArgs('CurrentTimestamp', args, 0);
997
- return 'CURRENT_TIMESTAMP';
998
- },
999
- CurrentDate: (args) => {
1000
- checkArgs('CurrentDate', args, 0);
1001
- return 'CURRENT_DATE';
1002
- },
1003
- AggregateJSON: (args, indent) => {
1004
- checkArgs('AggregateJSON', args, 1);
1005
- if (engine !== Engines.postgres) {
1006
- throw new SyntaxError('AggregateJSON not supported on: ' + engine);
1007
- }
1008
- const field = Field(getAbstractSqlQuery(args, 0), indent);
1009
- return `COALESCE(JSON_AGG(${field}), '[]')`;
1010
- },
1011
- Equals: Comparison('Equals'),
1012
- EqualsAny: (args, indent) => {
1013
- checkArgs('EqualsAny', args, 2);
1014
- return `${AnyValue(getAbstractSqlQuery(args, 0), indent)} = ANY(${AnyValue(getAbstractSqlQuery(args, 1), indent)})`;
1015
- },
1016
- GreaterThan: Comparison('GreaterThan'),
1017
- GreaterThanOrEqual: Comparison('GreaterThanOrEqual'),
1018
- LessThan: Comparison('LessThan'),
1019
- LessThanOrEqual: Comparison('LessThanOrEqual'),
1020
- NotEquals: Comparison('NotEquals'),
1021
- Like: Comparison('Like'),
1022
- IsNotDistinctFrom: (args, indent) => {
1023
- checkArgs('IsNotDistinctFrom', args, 2);
1024
- return isNotDistinctFrom(args, indent);
1025
- },
1026
- IsDistinctFrom: (args, indent) => {
1027
- checkArgs('IsDistinctFrom', args, 2);
1028
- return 'NOT(' + isNotDistinctFrom(args, indent) + ')';
1029
- },
1030
- Between: (args, indent) => {
1031
- checkArgs('Between', args, 3);
1032
- const v = AnyValue(getAbstractSqlQuery(args, 0), indent);
1033
- const a = AnyValue(getAbstractSqlQuery(args, 1), indent);
1034
- const b = AnyValue(getAbstractSqlQuery(args, 2), indent);
1035
- return `${v} BETWEEN ${a} AND (${b})`;
1036
- },
1037
- Add: MathOp('Add'),
1038
- Subtract: MathOp('Subtract'),
1039
- Multiply: MathOp('Multiply'),
1040
- Divide: MathOp('Divide'),
1041
- BitwiseAnd: MathOp('BitwiseAnd'),
1042
- BitwiseShiftRight: MathOp('BitwiseShiftRight'),
1043
- AddDateNumber, // returns date
1044
- AddDateDuration, // returns date
1045
- SubtractDateDate, // returns integer
1046
- SubtractDateNumber, // returns date
1047
- SubtractDateDuration, // returns date
1048
- Year: ExtractNumericDatePart('Year'),
1049
- Month: ExtractNumericDatePart('Month'),
1050
- Day: ExtractNumericDatePart('Day'),
1051
- Hour: ExtractNumericDatePart('Hour'),
1052
- Minute: ExtractNumericDatePart('Minute'),
1053
- Second: ExtractNumericDatePart('Second'),
1054
- Fractionalseconds: ExtractNumericDatePart('Fractionalseconds'),
1055
- Totalseconds: (args, indent) => {
1056
- checkArgs('Totalseconds', args, 1);
1057
- const duration = DurationValue(getAbstractSqlQuery(args, 0), indent);
1058
- if (engine === Engines.postgres) {
1059
- return `EXTRACT(EPOCH FROM ${duration})`;
1060
- } else if (engine === Engines.mysql) {
1061
- return `(TIMESTAMPDIFF(MICROSECOND, FROM_UNIXTIME(0), FROM_UNIXTIME(0) + ${duration}) / 1000000)`;
1062
- } else {
1063
- throw new SyntaxError('TotalSeconds not supported on: ' + engine);
1064
- }
1065
- },
1066
- Concatenate: (args, indent) => {
1067
- checkMinArgs('Concatenate', args, 1);
1068
- const comparators = args.map((arg) => {
1069
- if (!isAbstractSqlQuery(arg)) {
1070
- throw new SyntaxError(
1071
- `Expected AbstractSqlQuery array but got ${typeof arg}`,
1072
- );
1073
- }
1074
- return TextValue(arg, indent);
1075
- });
1076
- if (engine === Engines.mysql) {
1077
- return 'CONCAT(' + comparators.join(', ') + ')';
1078
- } else {
1079
- return '(' + comparators.join(' || ') + ')';
1080
- }
1081
- },
1082
- ConcatenateWithSeparator: (args, indent) => {
1083
- checkMinArgs('ConcatenateWithSeparator', args, 2);
1084
- const textParts = args.map((arg) => {
1085
- if (!isAbstractSqlQuery(arg)) {
1086
- throw new SyntaxError(
1087
- `Expected AbstractSqlQuery array but got ${typeof arg}`,
1088
- );
1089
- }
1090
- return TextValue(arg, indent);
1091
- });
1092
- if (engine === Engines.websql) {
1093
- throw new SyntaxError(
1094
- 'ConcatenateWithSeparator not supported on: ' + engine,
1095
- );
1096
- }
1097
- return `CONCAT_WS(${textParts.join(', ')})`;
1098
- },
1099
- Replace: (args, indent) => {
1100
- checkArgs('Replace', args, 3);
1101
- const str = TextValue(getAbstractSqlQuery(args, 0), indent);
1102
- const find = TextValue(getAbstractSqlQuery(args, 1), indent);
1103
- const replacement = TextValue(getAbstractSqlQuery(args, 2), indent);
1104
- return `REPLACE(${str}, ${find}, ${replacement})`;
1105
- },
1106
- ExtractJSONPathAsText: (args, indent) => {
1107
- checkMinArgs('ExtractJSONPathAsText', args, 1);
1108
- if (engine !== Engines.postgres) {
1109
- throw new SyntaxError(
1110
- 'ExtractJSONPathAsText not supported on: ' + engine,
1111
- );
1112
- }
1113
- const json = JSONValue(getAbstractSqlQuery(args, 0), indent);
1114
- const path = TextValue(getAbstractSqlQuery(args, 1), indent);
1115
- return `${json} #>> ${path}`;
1116
- },
1117
- CharacterLength: (args, indent) => {
1118
- checkArgs('CharacterLength', args, 1);
1119
- const text = TextValue(getAbstractSqlQuery(args, 0), indent);
1120
- if (engine === Engines.mysql) {
1121
- return `CHAR_LENGTH(${text})`;
1122
- } else {
1123
- return `LENGTH(${text})`;
1124
- }
1125
- },
1126
- StrPos: (args, indent) => {
1127
- checkArgs('StrPos', args, 2);
1128
- const haystack = TextValue(getAbstractSqlQuery(args, 0), indent);
1129
- const needle = TextValue(getAbstractSqlQuery(args, 1), indent);
1130
- if (engine === Engines.postgres) {
1131
- return `STRPOS(${haystack}, ${needle})`;
1132
- } else {
1133
- return `INSTR(${haystack}, ${needle})`;
1134
- }
1135
- },
1136
- StartsWith: (args, indent) => {
1137
- checkArgs('StartsWith', args, 2);
1138
- const haystack = TextValue(getAbstractSqlQuery(args, 0), indent);
1139
- const needle = TextValue(getAbstractSqlQuery(args, 1), indent);
1140
- if (engine === Engines.postgres) {
1141
- return `STARTS_WITH(${haystack}, ${needle})`;
1142
- } else {
1143
- return typeRules.Like(
1144
- [
1145
- haystack,
1146
- ['Concatenate', ['EscapeForLike', needle], ['EmbeddedText', '%']],
1147
- ],
1148
- indent,
1149
- );
1150
- }
1151
- },
1152
- Substring: (args, indent) => {
1153
- checkMinArgs('Substring', args, 2);
1154
- const str = TextValue(getAbstractSqlQuery(args, 0), indent);
1155
- const nums = args.slice(1).map((arg) => {
1156
- if (!isAbstractSqlQuery(arg)) {
1157
- throw new SyntaxError(
1158
- `Expected AbstractSqlQuery array but got ${typeof arg}`,
1159
- );
1160
- }
1161
- return NumericValue(arg, indent);
1162
- });
1163
- return `SUBSTRING(${[str, ...nums].join(', ')})`;
1164
- },
1165
- Right: (args, indent) => {
1166
- checkArgs('Right', args, 2);
1167
- const str = TextValue(getAbstractSqlQuery(args, 0), indent);
1168
- const n = NumericValue(getAbstractSqlQuery(args, 1), indent);
1169
- if (engine === Engines.websql) {
1170
- return `SUBSTRING(${str}, -${n})`;
1171
- } else {
1172
- return `RIGHT(${str}, ${n})`;
1173
- }
1174
- },
1175
- Lower: (args, indent) => {
1176
- checkArgs('Lower', args, 1);
1177
- const str = TextValue(getAbstractSqlQuery(args, 0), indent);
1178
- return `LOWER(${str})`;
1179
- },
1180
- Upper: (args, indent) => {
1181
- checkArgs('Upper', args, 1);
1182
- const str = TextValue(getAbstractSqlQuery(args, 0), indent);
1183
- return `UPPER(${str})`;
1184
- },
1185
- Trim: (args, indent) => {
1186
- checkArgs('Trim', args, 1);
1187
- const str = TextValue(getAbstractSqlQuery(args, 0), indent);
1188
- return `TRIM(${str})`;
1189
- },
1190
- Round: (args, indent) => {
1191
- checkArgs('Round', args, 1);
1192
- const num = NumericValue(getAbstractSqlQuery(args, 0), indent);
1193
- return `ROUND(${num})`;
1194
- },
1195
- Floor: (args, indent) => {
1196
- checkArgs('Floor', args, 1);
1197
- const num = NumericValue(getAbstractSqlQuery(args, 0), indent);
1198
- return `FLOOR(${num})`;
1199
- },
1200
- Ceiling: (args, indent) => {
1201
- checkArgs('Ceiling', args, 1);
1202
- const num = NumericValue(getAbstractSqlQuery(args, 0), indent);
1203
- return `CEILING(${num})`;
1204
- },
1205
- ToDate: (args, indent) => {
1206
- checkArgs('ToDate', args, 1);
1207
- const date = DateValue(getAbstractSqlQuery(args, 0), indent);
1208
- return `DATE(${date})`;
1209
- },
1210
- DateTrunc: (args, indent) => {
1211
- checkMinArgs('DateTrunc', args, 2);
1212
- const precision = TextValue(getAbstractSqlQuery(args, 0), indent);
1213
- const date = DateValue(getAbstractSqlQuery(args, 1), indent);
1214
- // Postgres generated timestamps have a microseconds precision
1215
- // these timestamps will fail on comparisons: eq, ne, gt, lt with
1216
- // js timestamps that have only milliseconds precision
1217
- // thus supporting for truncating to a given precision
1218
- if (engine === Engines.postgres) {
1219
- const timeZone =
1220
- args.length === 3
1221
- ? TextValue(getAbstractSqlQuery(args, 2), indent)
1222
- : undefined;
1223
- return timeZone
1224
- ? `DATE_TRUNC(${precision}, ${date}, ${timeZone})`
1225
- : `DATE_TRUNC(${precision}, ${date})`;
1226
- } else if (
1227
- // not postgresql ==> no need to truncate ==> return timestamp as is (milliseconds precision)
1228
- precision === "'milliseconds'" ||
1229
- precision === "'microseconds'"
1230
- ) {
1231
- return date;
1232
- } else {
1233
- // not postgresql ==> no truncate functionality ==>
1234
- throw new SyntaxError('DateTrunc is not supported on: ' + engine);
1235
- }
1236
- },
1237
- ToTime: (args, indent) => {
1238
- checkArgs('ToTime', args, 1);
1239
- const date = DateValue(getAbstractSqlQuery(args, 0), indent);
1240
- if (engine === Engines.postgres) {
1241
- return `CAST(${date} AS TIME)`;
1242
- } else {
1243
- return `TIME(${date})`;
1244
- }
1245
- },
1246
- ToJSON: (args, indent) => {
1247
- checkMinArgs('ToJSON', args, 1);
1248
- if (engine !== Engines.postgres) {
1249
- throw new SyntaxError('ToJSON not supported on: ' + engine);
1250
- }
1251
- const value = AnyValue(getAbstractSqlQuery(args, 0), indent);
1252
- return `TO_JSON(${value})`;
1253
- },
1254
- Any: (args, indent) => {
1255
- checkArgs('Any', args, 2);
1256
- if (engine !== Engines.postgres) {
1257
- throw new SyntaxError('Any not supported on: ' + engine);
1258
- }
1259
- const value = AnyValue(getAbstractSqlQuery(args, 0), indent);
1260
- const innerType =
1261
- sbvrTypes[args[1] as keyof typeof sbvrTypes].types[engine];
1262
- return `ANY(CAST(${value} AS ${innerType}[]))`;
1263
- },
1264
- Coalesce: (args, indent) => {
1265
- checkMinArgs('Coalesce', args, 2);
1266
- const comparators = args.map((arg) => {
1267
- if (!isAbstractSqlQuery(arg)) {
1268
- throw new SyntaxError(
1269
- `Expected AbstractSqlQuery array but got ${typeof arg}`,
1270
- );
1271
- }
1272
- return AnyValue(arg, indent);
1273
- });
1274
- return 'COALESCE(' + comparators.join(', ') + ')';
1275
- },
1276
- Case: (args, indent) => {
1277
- checkMinArgs('Case', args, 1);
1278
- const nestedIndent = NestedIndent(indent);
1279
- const clauses = args
1280
- .map((arg, index) => {
1281
- if (!isAbstractSqlQuery(arg)) {
1282
- throw new SyntaxError(
1283
- `Expected AbstractSqlQuery array but got ${typeof arg}`,
1284
- );
1285
- }
1286
- const [type, ...rest] = arg;
1287
- switch (type) {
1288
- case 'When': {
1289
- checkArgs('When', rest, 2);
1290
- const matches = BooleanValue(
1291
- getAbstractSqlQuery(rest, 0),
1292
- NestedIndent(nestedIndent),
1293
- );
1294
- const resultValue = AnyValue(
1295
- getAbstractSqlQuery(rest, 1),
1296
- nestedIndent,
1297
- );
1298
- return 'WHEN ' + matches + ' THEN ' + resultValue;
1299
- }
1300
- case 'Else':
1301
- if (index !== args.length - 1) {
1302
- throw new SyntaxError('Else must be the last element of a Case');
1303
- }
1304
- checkArgs('Else', rest, 1);
1305
- return (
1306
- 'ELSE ' + AnyValue(getAbstractSqlQuery(rest, 0), nestedIndent)
1307
- );
1308
- default:
1309
- throw new SyntaxError('Case can only contain When/Else');
1310
- }
1311
- })
1312
- .join(nestedIndent);
1313
- return 'CASE' + nestedIndent + clauses + indent + 'END';
1314
- },
1315
- And: (args, indent) => {
1316
- checkMinArgs('And', args, 2);
1317
- return args
1318
- .map((arg) => {
1319
- if (!isAbstractSqlQuery(arg)) {
1320
- throw new SyntaxError(
1321
- `Expected AbstractSqlQuery array but got ${typeof arg}`,
1322
- );
1323
- }
1324
- return BooleanValue(arg, indent);
1325
- })
1326
- .join(indent + 'AND ');
1327
- },
1328
- Or: (args, indent) => {
1329
- checkMinArgs('Or', args, 2);
1330
- return (
1331
- '(' +
1332
- args
1333
- .map((arg) => {
1334
- if (!isAbstractSqlQuery(arg)) {
1335
- throw new SyntaxError(
1336
- `Expected AbstractSqlQuery array but got ${typeof arg}`,
1337
- );
1338
- }
1339
- return BooleanValue(arg, indent);
1340
- })
1341
- .join(indent + 'OR ') +
1342
- ')'
1343
- );
1344
- },
1345
- Bind: (args) => {
1346
- checkArgs('Bind', args, 1);
1347
- const bind = args[0];
1348
- return AddBind(['Bind', bind]);
1349
- },
1350
- Text,
1351
- Date: (args) => {
1352
- checkArgs('Date', args, 1);
1353
- return AddBind(['Date', args[0]]);
1354
- },
1355
- Duration: (args) => {
1356
- checkArgs('Duration', args, 1);
1357
- if (engine === Engines.websql) {
1358
- throw new SyntaxError('Durations not supported on: ' + engine);
1359
- }
1360
- // TODO: The abstract sql type should accommodate this
1361
- const duration = args[0] as DurationNode[1];
1362
- if (duration == null || typeof duration !== 'object') {
1363
- throw new SyntaxError(
1364
- `Duration must be an object, got ${typeof duration}`,
1365
- );
1366
- }
1367
- const { negative, day, hour, minute, second } = duration;
1368
- if (day == null && hour == null && minute == null && second == null) {
1369
- throw new SyntaxError('Invalid duration');
1370
- }
1371
- return (
1372
- "INTERVAL '" +
1373
- (negative ? '-' : '') +
1374
- (day ?? '0') +
1375
- ' ' +
1376
- (negative ? '-' : '') +
1377
- (hour ?? '0') +
1378
- ':' +
1379
- (minute ?? '0') +
1380
- ':' +
1381
- // Force seconds to be at least 0.0 - required for mysql
1382
- Number(second ?? 0).toLocaleString('en', {
1383
- minimumFractionDigits: 1,
1384
- }) +
1385
- "'" +
1386
- (engine === Engines.mysql ? ' DAY_MICROSECOND' : '')
1387
- );
1388
- },
1389
- Exists: (args, indent) => {
1390
- checkArgs('Exists', args, 1);
1391
- const arg = getAbstractSqlQuery(args, 0);
1392
- const [type, ...rest] = arg;
1393
- switch (type) {
1394
- case 'SelectQuery':
1395
- case 'UnionQuery': {
1396
- const nestedIndent = NestedIndent(indent);
1397
- const query = typeRules[type](rest, nestedIndent);
1398
- return 'EXISTS (' + nestedIndent + query + indent + ')';
1399
- }
1400
- default:
1401
- return AnyValue(arg, indent) + ' IS NOT NULL';
1402
- }
1403
- },
1404
- NotExists: (args, indent) => {
1405
- checkArgs('NotExists', args, 1);
1406
- const arg = getAbstractSqlQuery(args, 0);
1407
- const [type, ...rest] = arg;
1408
- switch (type) {
1409
- case 'SelectQuery':
1410
- case 'UnionQuery': {
1411
- const nestedIndent = NestedIndent(indent);
1412
- const query = typeRules[type](rest, nestedIndent);
1413
- return 'NOT EXISTS (' + nestedIndent + query + indent + ')';
1414
- }
1415
- default:
1416
- return AnyValue(arg, indent) + ' IS NULL';
1417
- }
1418
- },
1419
- Not: (args, indent) => {
1420
- checkArgs('Not', args, 1);
1421
- const nestedIndent = NestedIndent(indent);
1422
- const bool = BooleanValue(getAbstractSqlQuery(args, 0), nestedIndent);
1423
- return 'NOT (' + nestedIndent + bool + indent + ')';
1424
- },
1425
- In: (args, indent) => {
1426
- checkMinArgs('In', args, 2);
1427
- const field = Field(getAbstractSqlQuery(args, 0), indent);
1428
- const vals = args.slice(1).map((arg) => {
1429
- if (!isAbstractSqlQuery(arg)) {
1430
- throw new SyntaxError(
1431
- `Expected AbstractSqlQuery array but got ${typeof arg}`,
1432
- );
1433
- }
1434
- return AnyValue(arg, indent);
1435
- });
1436
- return field + ' IN (' + vals.join(', ') + ')';
1437
- },
1438
- NotIn: (args, indent) => {
1439
- checkMinArgs('NotIn', args, 2);
1440
- const field = Field(getAbstractSqlQuery(args, 0), indent);
1441
- const vals = args.slice(1).map((arg) => {
1442
- if (!isAbstractSqlQuery(arg)) {
1443
- throw new SyntaxError(
1444
- `Expected AbstractSqlQuery array but got ${typeof arg}`,
1445
- );
1446
- }
1447
- return AnyValue(arg, indent);
1448
- });
1449
- return field + ' NOT IN (' + vals.join(', ') + ')';
1450
- },
1451
- InsertQuery: (args, indent) => {
1452
- const tables: string[] = [];
1453
- let fields: string[] = [];
1454
- let values: string | string[] = [];
1455
- for (const arg of args) {
1456
- if (!isAbstractSqlQuery(arg)) {
1457
- throw new SyntaxError('`InsertQuery` args must all be arrays');
1458
- }
1459
- const [type, ...rest] = arg;
1460
- switch (type) {
1461
- case 'Fields':
1462
- if (fields.length !== 0) {
1463
- throw new SyntaxError(
1464
- `'InsertQuery' can only accept one '${type}'`,
1465
- );
1466
- }
1467
- checkMinArgs('Update fields', rest, 1);
1468
- fields = getAbstractSqlQuery(rest, 0).map(escapeField);
1469
- break;
1470
- case 'Values': {
1471
- if (values.length !== 0) {
1472
- throw new SyntaxError(
1473
- `'InsertQuery' can only accept one '${type}'`,
1474
- );
1475
- }
1476
- const valuesArray = getAbstractSqlQuery(rest, 0);
1477
- if (valuesArray.length > 0) {
1478
- const [valuesType, ...valuesRest] = valuesArray;
1479
- switch (valuesType) {
1480
- case 'SelectQuery':
1481
- case 'UnionQuery':
1482
- values = typeRules[valuesType](valuesRest, indent);
1483
- break;
1484
- default:
1485
- values = valuesArray.map((v) => Value(v, indent));
1486
- }
1487
- }
1488
- break;
1489
- }
1490
- case 'From':
1491
- tables.push(typeRules[type](rest, indent));
1492
- break;
1493
- default:
1494
- throw new SyntaxError(`'InsertQuery' does not support '${type}'`);
1495
- }
1496
- }
1497
- if (typeof values !== 'string' && fields.length !== values.length) {
1498
- throw new SyntaxError(
1499
- 'Fields and Values must have the same length or use a query',
1500
- );
1501
- }
1502
-
1503
- if (fields.length > 0) {
1504
- if (Array.isArray(values)) {
1505
- values = 'VALUES (' + values.join(', ') + ')';
1506
- }
1507
- return (
1508
- 'INSERT INTO ' +
1509
- tables.join(', ') +
1510
- ' (' +
1511
- fields.join(', ') +
1512
- ')' +
1513
- indent +
1514
- values
1515
- );
1516
- } else {
1517
- return 'INSERT INTO ' + tables.join(', ') + ' DEFAULT VALUES';
1518
- }
1519
- },
1520
- UpdateQuery: (args, indent) => {
1521
- const tables: string[] = [];
1522
- let fields: string[] = [];
1523
- let values: string[] = [];
1524
- let where = '';
1525
- for (const arg of args) {
1526
- if (!isAbstractSqlQuery(arg)) {
1527
- throw new SyntaxError('`UpdateQuery` args must all be arrays');
1528
- }
1529
- const [type, ...rest] = arg;
1530
- switch (type) {
1531
- case 'Fields':
1532
- if (fields.length !== 0) {
1533
- throw new SyntaxError(
1534
- `'UpdateQuery' can only accept one '${type}'`,
1535
- );
1536
- }
1537
- checkMinArgs('Update fields', rest, 1);
1538
- fields = getAbstractSqlQuery(rest, 0).map(escapeField);
1539
- break;
1540
- case 'Values': {
1541
- if (values.length !== 0) {
1542
- throw new SyntaxError(
1543
- `'UpdateQuery' can only accept one '${type}'`,
1544
- );
1545
- }
1546
- checkArgs('Update values', rest, 1);
1547
- const valuesArray = getAbstractSqlQuery(rest, 0);
1548
- checkMinArgs('Update values array', valuesArray, 1);
1549
- values = valuesArray.map((v) => Value(v, indent));
1550
- break;
1551
- }
1552
- case 'From':
1553
- tables.push(typeRules[type](rest, indent));
1554
- break;
1555
- case 'Where':
1556
- if (where !== '') {
1557
- throw new SyntaxError(
1558
- `'UpdateQuery' can only accept one '${type}'`,
1559
- );
1560
- }
1561
- where = indent + typeRules[type](rest, indent);
1562
- break;
1563
- default:
1564
- throw new SyntaxError(`'UpdateQuery' does not support '${type}'`);
1565
- }
1566
- }
1567
- if (fields.length !== values.length) {
1568
- throw new SyntaxError('Fields and Values must have the same length');
1569
- }
1570
- const sets = fields.map((field, i) => field + ' = ' + values[i]);
1571
-
1572
- return (
1573
- 'UPDATE ' +
1574
- tables.join(', ') +
1575
- indent +
1576
- 'SET ' +
1577
- sets.join(',' + NestedIndent(indent)) +
1578
- where
1579
- );
1580
- },
1581
- DeleteQuery: (args, indent) => {
1582
- const tables: string[] = [];
1583
- let where = '';
1584
- for (const arg of args) {
1585
- if (!isAbstractSqlQuery(arg)) {
1586
- throw new SyntaxError('`DeleteQuery` args must all be arrays');
1587
- }
1588
- const [type, ...rest] = arg;
1589
- switch (type) {
1590
- case 'From':
1591
- tables.push(typeRules[type](rest, indent));
1592
- break;
1593
- case 'Where':
1594
- if (where !== '') {
1595
- throw new SyntaxError(
1596
- `'DeleteQuery' can only accept one '${type}'`,
1597
- );
1598
- }
1599
- where = indent + typeRules[type](rest, indent);
1600
- break;
1601
- default:
1602
- throw new SyntaxError(`'DeleteQuery' does not support '${type}'`);
1603
- }
1604
- }
1605
-
1606
- return 'DELETE FROM ' + tables.join(', ') + where;
1607
- },
1608
- EscapeForLike: (args, indent) => {
1609
- checkArgs('EscapeForLike', args, 1);
1610
- const textTypeNode = getAbstractSqlQuery(args, 0);
1611
- return typeRules.Replace(
1612
- [
1613
- [
1614
- 'Replace',
1615
- [
1616
- 'Replace',
1617
- textTypeNode,
1618
- ['EmbeddedText', '\\'],
1619
- ['EmbeddedText', '\\\\'],
1620
- ],
1621
- ['EmbeddedText', '_'],
1622
- ['EmbeddedText', '\\_'],
1623
- ],
1624
- ['EmbeddedText', '%'],
1625
- ['EmbeddedText', '\\%'],
1626
- ],
1627
- indent,
1628
- );
1629
- },
1630
- };
1631
-
1632
- const toSqlResult = (query: string): SqlResult | string => {
1633
- if (noBinds) {
1634
- return query;
1635
- }
1636
- return {
1637
- query,
1638
- bindings: fieldOrderings,
1639
- };
1640
- };
1641
-
1642
- export function AbstractSQLRules2SQL(
1643
- abstractSQL: UpsertQueryNode,
1644
- $engine: Engines,
1645
- $noBinds: true,
1646
- ): [string, string];
1647
- export function AbstractSQLRules2SQL(
1648
- abstractSQL: AbstractSqlQuery,
1649
- $engine: Engines,
1650
- $noBinds: true,
1651
- ): string;
1652
- export function AbstractSQLRules2SQL(
1653
- abstractSQL: UpsertQueryNode,
1654
- $engine: Engines,
1655
- $noBinds?: false,
1656
- ): [SqlResult, SqlResult];
1657
- export function AbstractSQLRules2SQL(
1658
- abstractSQL:
1659
- | SelectQueryNode
1660
- | UnionQueryNode
1661
- | InsertQueryNode
1662
- | UpdateQueryNode
1663
- | DeleteQueryNode,
1664
- $engine: Engines,
1665
- $noBinds?: false,
1666
- ): SqlResult;
1667
- export function AbstractSQLRules2SQL(
1668
- abstractSQL: AbstractSqlQuery,
1669
- $engine: Engines,
1670
- $noBinds?: false,
1671
- ): SqlResult | [SqlResult, SqlResult];
1672
- export function AbstractSQLRules2SQL(
1673
- abstractSQL: AbstractSqlQuery,
1674
- $engine: Engines,
1675
- $noBinds?: boolean,
1676
- ): SqlResult | [SqlResult, SqlResult] | string;
1677
- export function AbstractSQLRules2SQL(
1678
- abstractSQL: AbstractSqlQuery,
1679
- $engine: Engines,
1680
- $noBinds = false,
1681
- ): SqlResult | [SqlResult, SqlResult] | string | [string, string] {
1682
- engine = $engine;
1683
- noBinds = $noBinds;
1684
- fieldOrderings = [];
1685
- fieldOrderingsLookup = {};
1686
-
1687
- const indent = '\n';
1688
- const [type, ...rest] = abstractSQL;
1689
- switch (type) {
1690
- case 'SelectQuery':
1691
- case 'UnionQuery':
1692
- case 'InsertQuery':
1693
- case 'UpdateQuery':
1694
- case 'DeleteQuery': {
1695
- const query = typeRules[type](rest, indent);
1696
- return toSqlResult(query);
1697
- }
1698
- case 'UpsertQuery': {
1699
- checkArgs('UpsertQuery', rest, 2);
1700
- const insertQuery = getAbstractSqlQuery(rest, 0);
1701
- const updateQuery = getAbstractSqlQuery(rest, 1);
1702
- if (
1703
- insertQuery[0] !== 'InsertQuery' ||
1704
- updateQuery[0] !== 'UpdateQuery'
1705
- ) {
1706
- throw new SyntaxError(
1707
- 'UpsertQuery must have [InsertQuery, UpdateQuery] provided',
1708
- );
1709
- }
1710
- const insertSql = typeRules.InsertQuery(insertQuery.slice(1), indent);
1711
- const insert = toSqlResult(insertSql);
1712
- // Reset fieldOrderings for the second query
1713
- fieldOrderings = [];
1714
- fieldOrderingsLookup = {};
1715
- const updateSql = typeRules.UpdateQuery(updateQuery.slice(1), indent);
1716
- const update = toSqlResult(updateSql);
1717
- return [insert, update] as [string, string] | [SqlResult, SqlResult];
1718
- }
1719
- default: {
1720
- const value = AnyValue(abstractSQL, indent);
1721
- if (noBinds) {
1722
- return value;
1723
- }
1724
- return {
1725
- query: `SELECT ${value} AS "result";`,
1726
- bindings: fieldOrderings,
1727
- };
1728
- }
1729
- }
1730
- }