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