@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.
- package/package.json +5 -2
- package/.github/workflows/flowzone.yml +0 -21
- package/.husky/pre-commit +0 -2
- package/.versionbot/CHANGELOG.yml +0 -10729
- package/CHANGELOG.md +0 -3515
- package/repo.yml +0 -12
- package/src/abstract-sql-compiler.ts +0 -1138
- package/src/abstract-sql-optimizer.ts +0 -1632
- package/src/abstract-sql-rules-to-sql.ts +0 -1730
- package/src/abstract-sql-schema-optimizer.ts +0 -172
- package/src/referenced-fields.ts +0 -600
- package/test/abstract-sql/aggregate-json.ts +0 -49
- package/test/abstract-sql/aggregate.ts +0 -161
- package/test/abstract-sql/and-or-boolean-optimisations.ts +0 -115
- package/test/abstract-sql/case-when-else.ts +0 -48
- package/test/abstract-sql/cast.ts +0 -25
- package/test/abstract-sql/coalesce.ts +0 -24
- package/test/abstract-sql/comparisons.ts +0 -360
- package/test/abstract-sql/dates.ts +0 -512
- package/test/abstract-sql/duration.ts +0 -56
- package/test/abstract-sql/empty-query-optimisations.ts +0 -54
- package/test/abstract-sql/functions-wrapper.ts +0 -70
- package/test/abstract-sql/get-referenced-fields.ts +0 -674
- package/test/abstract-sql/get-rule-referenced-fields.ts +0 -345
- package/test/abstract-sql/insert-query.ts +0 -22
- package/test/abstract-sql/is-distinct.ts +0 -102
- package/test/abstract-sql/joins.ts +0 -84
- package/test/abstract-sql/json.ts +0 -58
- package/test/abstract-sql/math.ts +0 -467
- package/test/abstract-sql/nested-in-optimisations.ts +0 -200
- package/test/abstract-sql/not-not-optimisations.ts +0 -15
- package/test/abstract-sql/schema-checks.ts +0 -168
- package/test/abstract-sql/schema-informative-reference.ts +0 -420
- package/test/abstract-sql/schema-rule-optimization.ts +0 -120
- package/test/abstract-sql/schema-rule-to-check.ts +0 -393
- package/test/abstract-sql/schema-views.ts +0 -73
- package/test/abstract-sql/test.ts +0 -192
- package/test/abstract-sql/text.ts +0 -168
- package/test/model.sbvr +0 -60
- package/test/odata/expand.ts +0 -674
- package/test/odata/fields.ts +0 -59
- package/test/odata/filterby.ts +0 -1517
- package/test/odata/orderby.ts +0 -96
- package/test/odata/paging.ts +0 -48
- package/test/odata/resource-parsing.ts +0 -568
- package/test/odata/select.ts +0 -119
- package/test/odata/stress.ts +0 -93
- package/test/odata/test.ts +0 -297
- package/test/sbvr/pilots.ts +0 -1097
- package/test/sbvr/reference-type.ts +0 -211
- package/test/sbvr/test.ts +0 -101
- package/tsconfig.build.json +0 -6
- 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
|
-
}
|