@balena/abstract-sql-compiler 11.0.0-build-11-x-b2280608fff69d9959999c79db6245c4ad561bbc-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.
Files changed (53) hide show
  1. package/package.json +5 -2
  2. package/.github/workflows/flowzone.yml +0 -21
  3. package/.husky/pre-commit +0 -2
  4. package/.versionbot/CHANGELOG.yml +0 -10729
  5. package/CHANGELOG.md +0 -3515
  6. package/repo.yml +0 -12
  7. package/src/abstract-sql-compiler.ts +0 -1138
  8. package/src/abstract-sql-optimizer.ts +0 -1632
  9. package/src/abstract-sql-rules-to-sql.ts +0 -1730
  10. package/src/abstract-sql-schema-optimizer.ts +0 -172
  11. package/src/referenced-fields.ts +0 -600
  12. package/test/abstract-sql/aggregate-json.ts +0 -49
  13. package/test/abstract-sql/aggregate.ts +0 -161
  14. package/test/abstract-sql/and-or-boolean-optimisations.ts +0 -115
  15. package/test/abstract-sql/case-when-else.ts +0 -48
  16. package/test/abstract-sql/cast.ts +0 -25
  17. package/test/abstract-sql/coalesce.ts +0 -24
  18. package/test/abstract-sql/comparisons.ts +0 -360
  19. package/test/abstract-sql/dates.ts +0 -512
  20. package/test/abstract-sql/duration.ts +0 -56
  21. package/test/abstract-sql/empty-query-optimisations.ts +0 -54
  22. package/test/abstract-sql/functions-wrapper.ts +0 -70
  23. package/test/abstract-sql/get-referenced-fields.ts +0 -674
  24. package/test/abstract-sql/get-rule-referenced-fields.ts +0 -345
  25. package/test/abstract-sql/insert-query.ts +0 -22
  26. package/test/abstract-sql/is-distinct.ts +0 -102
  27. package/test/abstract-sql/joins.ts +0 -84
  28. package/test/abstract-sql/json.ts +0 -58
  29. package/test/abstract-sql/math.ts +0 -467
  30. package/test/abstract-sql/nested-in-optimisations.ts +0 -200
  31. package/test/abstract-sql/not-not-optimisations.ts +0 -15
  32. package/test/abstract-sql/schema-checks.ts +0 -168
  33. package/test/abstract-sql/schema-informative-reference.ts +0 -420
  34. package/test/abstract-sql/schema-rule-optimization.ts +0 -120
  35. package/test/abstract-sql/schema-rule-to-check.ts +0 -393
  36. package/test/abstract-sql/schema-views.ts +0 -73
  37. package/test/abstract-sql/test.ts +0 -192
  38. package/test/abstract-sql/text.ts +0 -168
  39. package/test/model.sbvr +0 -60
  40. package/test/odata/expand.ts +0 -674
  41. package/test/odata/fields.ts +0 -59
  42. package/test/odata/filterby.ts +0 -1517
  43. package/test/odata/orderby.ts +0 -96
  44. package/test/odata/paging.ts +0 -48
  45. package/test/odata/resource-parsing.ts +0 -568
  46. package/test/odata/select.ts +0 -119
  47. package/test/odata/stress.ts +0 -93
  48. package/test/odata/test.ts +0 -297
  49. package/test/sbvr/pilots.ts +0 -1097
  50. package/test/sbvr/reference-type.ts +0 -211
  51. package/test/sbvr/test.ts +0 -101
  52. package/tsconfig.build.json +0 -6
  53. package/tsconfig.json +0 -25
@@ -1,1517 +0,0 @@
1
- /*
2
- * decaffeinate suggestions:
3
- * DS205: Consider reworking code to avoid use of IIFEs
4
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
5
- */
6
- import { expect } from 'chai';
7
- import type { ExpectationSuccessFn } from './test.js';
8
- import test, { clientModel } from './test.js';
9
- import _ from 'lodash';
10
- import { odataNameToSqlName } from '@balena/odata-to-abstract-sql';
11
- import {
12
- pilotFields,
13
- teamFields,
14
- aliasPilotCanFlyPlaneFields,
15
- } from './fields.js';
16
- import type {
17
- Binding,
18
- DurationNode,
19
- SqlResult,
20
- } from '../../out/abstract-sql-compiler.js';
21
-
22
- const pilotFieldsStr = pilotFields.join(', ');
23
- const aliasPilotCanFlyPlaneFieldsStr = aliasPilotCanFlyPlaneFields.join(', ');
24
- const teamFieldsStr = teamFields.join(', ');
25
-
26
- type ParsedOperand = {
27
- odata: string | number | boolean;
28
- bindings: Binding[];
29
- sql: string;
30
- };
31
- type Operand =
32
- | string
33
- | number
34
- | boolean
35
- | Date
36
- | DurationNode[1]
37
- | ParsedOperand;
38
-
39
- let parseOperandFactory = function (defaultResource = 'pilot') {
40
- let bindNo = 0;
41
- const operandToOData = function (operand: Operand) {
42
- if (operand != null && typeof operand === 'object') {
43
- if ('odata' in operand) {
44
- return operand.odata;
45
- }
46
- if (_.isDate(operand)) {
47
- return "datetime'" + encodeURIComponent(operand.toISOString()) + "'";
48
- }
49
-
50
- const duration: Array<string | number> = [];
51
- let t = false;
52
- if (operand.negative) {
53
- duration.push('-');
54
- }
55
- duration.push('P');
56
- if (operand.day != null) {
57
- duration.push(operand.day, 'D');
58
- }
59
- if (operand.hour != null) {
60
- t = true;
61
- duration.push('T', operand.hour, 'H');
62
- }
63
- if (operand.minute != null) {
64
- if (!t) {
65
- t = true;
66
- duration.push('T');
67
- }
68
- duration.push(operand.minute, 'M');
69
- }
70
- if (operand.second != null) {
71
- if (!t) {
72
- t = true;
73
- duration.push('T');
74
- }
75
- duration.push(operand.second, 'S');
76
- }
77
- if (duration.length < 3) {
78
- throw new Error('Duration must contain at least 1 component');
79
- }
80
- return `duration'${duration.join('')}'`;
81
- }
82
- return operand;
83
- };
84
-
85
- const operandToBindings = function (operand: Operand): Binding[] {
86
- if (typeof operand === 'object' && 'bindings' in operand) {
87
- return operand.bindings;
88
- }
89
- if (
90
- typeof operand === 'boolean' ||
91
- typeof operand === 'number' ||
92
- _.isDate(operand) ||
93
- (typeof operand === 'string' && operand.startsWith("'"))
94
- ) {
95
- return [['Bind', bindNo++]];
96
- }
97
- return [];
98
- };
99
-
100
- const operandToSQL = function (
101
- operand: Operand,
102
- resource = defaultResource,
103
- ): string {
104
- if (typeof operand === 'object' && 'sql' in operand) {
105
- return operand.sql;
106
- }
107
- if (
108
- typeof operand === 'boolean' ||
109
- typeof operand === 'number' ||
110
- _.isDate(operand)
111
- ) {
112
- return '?';
113
- }
114
- if (typeof operand === 'string') {
115
- let mapping;
116
- if (operand === 'null') {
117
- return 'NULL';
118
- }
119
- if (operand.startsWith("'")) {
120
- return '?';
121
- }
122
- const fieldParts = operand.split('/');
123
- if (fieldParts.length > 1) {
124
- let alias = resource;
125
- let previousResource = resource;
126
- for (const resourceName of fieldParts.slice(0, -1)) {
127
- const sqlName = odataNameToSqlName(resourceName);
128
- const sqlNameParts = sqlName.split('-');
129
- mapping = _.get(
130
- clientModel.relationships[previousResource],
131
- sqlNameParts.join('.'),
132
- ).$;
133
- const refTable = mapping[1][0];
134
- if (sqlNameParts.length > 1 && !_.includes(refTable, '-')) {
135
- alias = `${alias}.${sqlNameParts[0]}-${refTable}`;
136
- } else {
137
- alias = `${alias}.${refTable}`;
138
- }
139
- previousResource = refTable;
140
- }
141
- mapping = [alias, _.last(fieldParts)];
142
- } else {
143
- mapping = [resource, odataNameToSqlName(operand)];
144
- }
145
- return '"' + mapping.join('"."') + '"';
146
- }
147
- if (operand != null && typeof operand === 'object') {
148
- const sign = operand.negative ? '-' : '';
149
- const day = operand.day ?? 0;
150
- const hour = operand.hour ?? 0;
151
- const minute = operand.minute ?? 0;
152
- const second = operand.second ?? 0;
153
- return `INTERVAL '${sign}${day} ${sign}${hour}:${minute}:${second}'`;
154
- }
155
- throw new Error(`Unknown operand type: ${operand}`);
156
- };
157
-
158
- return (operand: Operand): ParsedOperand => ({
159
- sql: operandToSQL(operand),
160
- bindings: operandToBindings(operand),
161
- odata: operandToOData(operand),
162
- });
163
- };
164
-
165
- let parseOperand: ReturnType<typeof parseOperandFactory> | null = null;
166
- const run = (function () {
167
- let running = false;
168
- return function (fn: () => void) {
169
- if (!running) {
170
- running = true;
171
- parseOperand = parseOperandFactory();
172
- fn();
173
- parseOperand = null;
174
- running = false;
175
- } else {
176
- fn();
177
- }
178
- };
179
- })();
180
-
181
- const sqlOps = {
182
- eq: ' =',
183
- ne: ' !=',
184
- gt: ' >',
185
- ge: ' >=',
186
- lt: ' <',
187
- le: ' <=',
188
- and: '\nAND',
189
- or: '\nOR',
190
- add: ' +',
191
- sub: ' -',
192
- mul: ' *',
193
- div: ' /',
194
- };
195
- const sqlOpBrackets = { or: true };
196
-
197
- const methodMaps = {
198
- TOUPPER: 'UPPER',
199
- TOLOWER: 'LOWER',
200
- };
201
-
202
- const createExpression = function (lhs: Operand, op?: Operand, rhs?: Operand) {
203
- let sql;
204
- if (lhs === 'not') {
205
- op = parseOperand!(op!);
206
- return {
207
- odata: 'not(' + op.odata + ')',
208
- sql: 'NOT (\n\t' + op.sql + '\n)',
209
- bindings: op.bindings,
210
- };
211
- }
212
- if (rhs == null) {
213
- lhs = parseOperand!(lhs);
214
- return {
215
- odata: '(' + lhs.odata + ')',
216
- sql: lhs.sql,
217
- bindings: lhs.bindings,
218
- };
219
- }
220
- lhs = parseOperand!(lhs);
221
- rhs = parseOperand!(rhs);
222
- const bindings = lhs.bindings.concat(rhs.bindings);
223
- if (op === 'eq' || op === 'ne') {
224
- if ([lhs.sql, rhs.sql].includes('NULL')) {
225
- const nullCheck = op === 'eq' ? ' IS NULL' : ' IS NOT NULL';
226
- if (lhs.sql === 'NULL') {
227
- sql = rhs.sql + nullCheck;
228
- } else {
229
- sql = lhs.sql + nullCheck;
230
- }
231
- } else {
232
- const isAtomic = /^([-." a-zA-Z]+|\?|\$[0-9]+)$/;
233
- const lhsSql = isAtomic.test(lhs.sql) ? lhs.sql : `(${lhs.sql})`;
234
- const rhsSql = isAtomic.test(rhs.sql) ? rhs.sql : `(${rhs.sql})`;
235
- const nullCheck = ' IS NOT NULL';
236
- const lhsNullCheck = lhs.sql === '?' ? '' : `${lhsSql}${nullCheck} AND `;
237
- const rhsNullCheck = rhs.sql === '?' ? '' : `${rhsSql}${nullCheck} AND `;
238
- const bothNullCheck =
239
- lhsNullCheck.length > 0 && rhsNullCheck.length > 0
240
- ? ` OR ${lhsSql} IS NULL AND ${rhsSql} IS NULL`
241
- : '';
242
-
243
- if (lhsNullCheck.length > 0 || rhsNullCheck.length > 0) {
244
- if (op === 'ne') {
245
- const mainCheck = `${lhsSql}${sqlOps.eq} ${rhsSql}`;
246
- sql = `NOT(${lhsNullCheck}${rhsNullCheck}${mainCheck}${bothNullCheck})`;
247
- } else {
248
- const mainCheck = `${lhsSql}${sqlOps[op]} ${rhsSql}`;
249
- sql = `${lhsNullCheck}${rhsNullCheck}${mainCheck}${bothNullCheck}`;
250
- }
251
- } else {
252
- const mainCheck = `${lhs.sql}${sqlOps[op]} ${rhs.sql}`;
253
- sql = `${lhsNullCheck}${rhsNullCheck}${mainCheck}${bothNullCheck}`;
254
- }
255
- }
256
- } else {
257
- sql = `${lhs.sql}${sqlOps[op as keyof typeof sqlOps]} ${rhs.sql}`;
258
- }
259
-
260
- if (sqlOpBrackets[op as keyof typeof sqlOpBrackets]) {
261
- sql = '(' + sql + ')';
262
- }
263
- return {
264
- odata: `${lhs.odata} ${op} ${rhs.odata}`,
265
- sql,
266
- bindings,
267
- };
268
- };
269
- const createMethodCall = function (method: string, ...args: Operand[]) {
270
- const parsedArgs = args.map((arg) => parseOperand!(arg));
271
- const odata =
272
- method + '(' + parsedArgs.map((arg) => arg.odata).join(',') + ')';
273
- method = method.toUpperCase();
274
- switch (method) {
275
- case 'CONTAINS':
276
- case 'SUBSTRINGOF':
277
- if (method === 'SUBSTRINGOF') {
278
- parsedArgs.reverse();
279
- }
280
- return {
281
- sql: `${parsedArgs[0].sql} LIKE ('%' || REPLACE(REPLACE(REPLACE(${parsedArgs[1].sql}, '\\', '\\\\'), '_', '\\_'), '%', '\\%') || '%')`,
282
- bindings: [...parsedArgs[0].bindings, ...parsedArgs[1].bindings],
283
- odata,
284
- };
285
- case 'STARTSWITH':
286
- return {
287
- sql: `STARTS_WITH(${parsedArgs[0].sql}, ${parsedArgs[1].sql})`,
288
- bindings: [...parsedArgs[0].bindings, ...parsedArgs[1].bindings],
289
- odata,
290
- };
291
- case 'ENDSWITH':
292
- return {
293
- sql: `${parsedArgs[0].sql} LIKE ('%' || REPLACE(REPLACE(REPLACE(${parsedArgs[1].sql}, '\\', '\\\\'), '_', '\\_'), '%', '\\%'))`,
294
- bindings: [...parsedArgs[0].bindings, ...parsedArgs[1].bindings],
295
- odata,
296
- };
297
- case 'CONCAT':
298
- return {
299
- sql: '(' + parsedArgs.map((arg) => arg.sql).join(' || ') + ')',
300
- bindings: _.flatten(parsedArgs.map((arg) => arg.bindings)),
301
- odata,
302
- };
303
- case 'INDEXOF':
304
- return {
305
- sql: 'STRPOS(' + parsedArgs.map((arg) => arg.sql).join(', ') + ') - 1',
306
- bindings: _.flatten(parsedArgs.map((arg) => arg.bindings)),
307
- odata,
308
- };
309
- case 'NOW':
310
- return {
311
- sql: 'CURRENT_TIMESTAMP',
312
- bindings: [],
313
- odata,
314
- };
315
- case 'YEAR':
316
- case 'MONTH':
317
- case 'DAY':
318
- case 'HOUR':
319
- case 'MINUTE':
320
- return {
321
- sql: `EXTRACT('${method}' FROM DATE_TRUNC('milliseconds', ${parsedArgs[0].sql}, 'UTC'))`,
322
- bindings: parsedArgs[0].bindings,
323
- odata,
324
- };
325
- case 'SECOND':
326
- return {
327
- sql: `FLOOR(EXTRACT('${method}' FROM DATE_TRUNC('milliseconds', ${parsedArgs[0].sql}, 'UTC')))`,
328
- bindings: parsedArgs[0].bindings,
329
- odata,
330
- };
331
- case 'FRACTIONALSECONDS':
332
- return {
333
- sql: `EXTRACT('SECOND' FROM DATE_TRUNC('milliseconds', ${parsedArgs[0].sql}, 'UTC')) - FLOOR(EXTRACT('SECOND' FROM DATE_TRUNC('milliseconds', ${parsedArgs[0].sql}, 'UTC')))`,
334
- bindings: parsedArgs[0].bindings,
335
- odata,
336
- };
337
- case 'TIME':
338
- return {
339
- sql: `CAST(DATE_TRUNC('milliseconds', ${parsedArgs[0].sql}, 'UTC') AS ${method})`,
340
- bindings: parsedArgs[0].bindings,
341
- odata,
342
- };
343
- case 'TOTALSECONDS':
344
- return {
345
- sql: `EXTRACT(EPOCH FROM ${parsedArgs[0].sql})`,
346
- bindings: parsedArgs[0].bindings,
347
- odata,
348
- };
349
- case 'DATE':
350
- return {
351
- sql: `DATE(DATE_TRUNC('milliseconds', ${parsedArgs[0].sql}, 'UTC'))`,
352
- bindings: parsedArgs[0].bindings,
353
- odata,
354
- };
355
- default: {
356
- if (Object.prototype.hasOwnProperty.call(methodMaps, method)) {
357
- method = methodMaps[method as keyof typeof methodMaps];
358
- }
359
- switch (method) {
360
- case 'SUBSTRING':
361
- parsedArgs[1].sql += ' + 1';
362
- break;
363
- }
364
- const sql =
365
- method + '(' + parsedArgs.map((arg) => arg.sql).join(', ') + ')';
366
- return {
367
- sql,
368
- bindings: _.flatten(parsedArgs.map((arg) => arg.bindings)),
369
- odata,
370
- };
371
- }
372
- }
373
- };
374
-
375
- const operandTest = (
376
- lhs: Operand,
377
- op?: Operand,
378
- rhs?: Operand,
379
- override?: Partial<ParsedOperand>,
380
- ) => {
381
- run(function () {
382
- let from;
383
- let { odata, sql, bindings } = createExpression(lhs, op, rhs);
384
- bindings = override?.bindings ?? bindings;
385
- sql = override?.sql ?? sql;
386
- if (_.includes(odata, '/')) {
387
- from = `\
388
- "pilot"
389
- LEFT JOIN "pilot" AS "pilot.trained-pilot" ON "pilot"."id" = "pilot.trained-pilot"."was trained by-pilot"`;
390
- } else {
391
- from = '"pilot"';
392
- }
393
-
394
- test(`/pilot?$filter=${odata}`, 'GET', bindings, (result, sqlEquals) => {
395
- it('should select from pilot where "' + odata + '"', () => {
396
- sqlEquals(
397
- result,
398
- `\
399
- SELECT ${pilotFieldsStr}
400
- FROM ${from}
401
- WHERE ${sql}`,
402
- );
403
- });
404
- });
405
- test(
406
- `/pilot/$count?$filter=${odata}`,
407
- 'GET',
408
- bindings,
409
- (result, sqlEquals) => {
410
- it('should select count(*) from pilot where "' + odata + '"', () => {
411
- sqlEquals(
412
- result,
413
- `\
414
- SELECT COUNT(*) AS "$count"
415
- FROM ${from}
416
- WHERE ${sql}`,
417
- );
418
- });
419
- },
420
- );
421
- });
422
- };
423
-
424
- const methodTest = (...args: [method: string, ...Operand[]]) => {
425
- run(function () {
426
- const { odata, sql, bindings } = createMethodCall(...args);
427
- test(`/pilot?$filter=${odata}`, 'GET', bindings, (result, sqlEquals) => {
428
- it('should select from pilot where "' + odata + '"', () => {
429
- sqlEquals(
430
- result,
431
- `\
432
- SELECT ${pilotFieldsStr}
433
- FROM "pilot"
434
- WHERE ` + sql,
435
- );
436
- });
437
- });
438
- test(
439
- `/pilot/$count?$filter=${odata}`,
440
- 'GET',
441
- bindings,
442
- (result, sqlEquals) => {
443
- it('should select count(*) from pilot where "' + odata + '"', () => {
444
- sqlEquals(
445
- result,
446
- `\
447
- SELECT COUNT(*) AS "$count"
448
- FROM "pilot"
449
- WHERE ` + sql,
450
- );
451
- });
452
- },
453
- );
454
- });
455
- };
456
-
457
- // Test each combination of operands and operations
458
- (function () {
459
- const operations = ['eq', 'ne', 'gt', 'ge', 'lt', 'le'];
460
- const nonNullableOperands = [
461
- 2,
462
- -2,
463
- 2.5,
464
- -2.5,
465
- "'bar'",
466
- new Date(),
467
- true,
468
- false,
469
- ];
470
- const nullableOperands = [
471
- 'name',
472
- 'trained__pilot/name',
473
- { negative: true, day: 3, hour: 4, minute: 5, second: 6.7 },
474
- // null is quoted as otherwise we hit issues with coffeescript defaulting values
475
- // 'null',
476
- ];
477
- operations.forEach((op) => {
478
- describe(op, () => {
479
- nonNullableOperands.forEach((lhs) => {
480
- [...nonNullableOperands, ...nullableOperands].forEach((rhs) => {
481
- run(() => {
482
- operandTest(lhs, op, rhs);
483
- });
484
- });
485
- run(() => {
486
- switch (op) {
487
- case 'eq':
488
- case 'ne':
489
- // eq/ne of non-nullable to null are automatically optimized away
490
- operandTest(lhs, op, 'null', {
491
- bindings: [],
492
- sql: op === 'eq' ? 'false' : 'true',
493
- });
494
- break;
495
-
496
- default:
497
- operandTest(lhs, op, 'null');
498
- break;
499
- }
500
- });
501
- });
502
- nullableOperands.forEach((lhs) => {
503
- [...nonNullableOperands, ...nullableOperands].forEach((rhs) => {
504
- run(() => {
505
- operandTest(lhs, op, rhs);
506
- });
507
- });
508
- run(() => {
509
- operandTest(lhs, op, 'null');
510
- });
511
- });
512
- });
513
- });
514
- })();
515
-
516
- run(function () {
517
- const left = createExpression('age', 'gt', 2);
518
- const right = createExpression('age', 'lt', 10);
519
- operandTest(left, 'and', right);
520
- operandTest(left, 'or', right);
521
- operandTest('is_experienced');
522
- operandTest('not', 'is_experienced');
523
- operandTest('not', left);
524
- });
525
-
526
- (function () {
527
- const mathOps = ['add', 'sub', 'mul', 'div'];
528
- mathOps.map((mathOp) => {
529
- run(() => {
530
- const mathOpExpr = createExpression('age', mathOp, 2);
531
- operandTest(mathOpExpr, 'gt', 10);
532
- });
533
- });
534
- })();
535
-
536
- run(function () {
537
- const odata = "name eq @name&@name='Pete'";
538
- test(
539
- `/pilot?$filter=${odata}`,
540
- 'GET',
541
- [['Bind', '@name']],
542
- (result, sqlEquals) => {
543
- it('should select from pilot where "' + odata + '"', () => {
544
- sqlEquals(
545
- result,
546
- `\
547
- SELECT ${pilotFieldsStr}
548
- FROM "pilot"
549
- WHERE "pilot"."name" IS NOT NULL AND "pilot"."name" = ?`,
550
- );
551
- });
552
- },
553
- );
554
- });
555
-
556
- run(function () {
557
- const odata = "name eq @x or favourite_colour eq @x&@x='Amber'";
558
- test(
559
- `/pilot?$filter=${odata}`,
560
- 'GET',
561
- [['Bind', '@x']],
562
- (result, sqlEquals) => {
563
- it('should select from pilot where "' + odata + '"', () => {
564
- sqlEquals(
565
- result,
566
- `\
567
- SELECT ${pilotFieldsStr}
568
- FROM "pilot"
569
- WHERE ("pilot"."name" IS NOT NULL AND "pilot"."name" = $1
570
- OR "pilot"."favourite colour" IS NOT NULL AND "pilot"."favourite colour" = $1)`,
571
- );
572
- });
573
- },
574
- );
575
- });
576
-
577
- run(function () {
578
- const { odata, bindings, sql } = createExpression(
579
- 'can_fly__plane/id',
580
- 'eq',
581
- 10,
582
- );
583
- test(`/pilot?$filter=${odata}`, 'GET', bindings, (result, sqlEquals) => {
584
- it('should select from pilot where "' + odata + '"', () => {
585
- sqlEquals(
586
- result,
587
- `\
588
- SELECT ${pilotFieldsStr}
589
- FROM "pilot"
590
- LEFT JOIN "pilot-can fly-plane" AS "pilot.pilot-can fly-plane" ON "pilot"."id" = "pilot.pilot-can fly-plane"."pilot"
591
- WHERE ${sql}`,
592
- );
593
- });
594
- });
595
- });
596
-
597
- run(function () {
598
- const { odata: keyOdata, bindings: keyBindings } = parseOperand!(1);
599
- const { odata, bindings } = createExpression('can_fly__plane/id', 'eq', 10);
600
- test(
601
- '/pilot(' + keyOdata + ')/can_fly__plane?$filter=' + odata,
602
- 'GET',
603
- bindings.concat(keyBindings),
604
- (result, sqlEquals) => {
605
- it(
606
- 'should select from pilot__can_fly__plane where "' + odata + '"',
607
- () => {
608
- sqlEquals(
609
- result,
610
- `\
611
- SELECT ${aliasPilotCanFlyPlaneFieldsStr}
612
- FROM "pilot",
613
- "pilot-can fly-plane" AS "pilot.pilot-can fly-plane"
614
- LEFT JOIN "plane" AS "pilot.pilot-can fly-plane.can fly-plane" ON "pilot.pilot-can fly-plane"."can fly-plane" = "pilot.pilot-can fly-plane.can fly-plane"."id"
615
- WHERE "pilot.pilot-can fly-plane.can fly-plane"."id" IS NOT NULL AND "pilot.pilot-can fly-plane.can fly-plane"."id" = ?
616
- AND "pilot"."id" = "pilot.pilot-can fly-plane"."pilot"
617
- AND "pilot"."id" IS NOT NULL AND "pilot"."id" = ?`,
618
- );
619
- },
620
- );
621
- },
622
- );
623
- });
624
-
625
- run(function () {
626
- const { odata, bindings, sql } = createExpression(
627
- 'can_fly__plane/plane/id',
628
- 'eq',
629
- 10,
630
- );
631
- const name = 'Peter';
632
- const bodyBindings = [['Bind', ['pilot', 'name']], ...bindings] as const;
633
- const insertTest: ExpectationSuccessFn = (result, sqlEquals) => {
634
- sqlEquals(
635
- result,
636
- `\
637
- INSERT INTO "pilot" ("name")
638
- SELECT "$insert"."name"
639
- FROM (
640
- SELECT CAST(NULL AS TIMESTAMPTZ) AS "created at", CAST(NULL AS TIMESTAMPTZ) AS "modified at", CAST(NULL AS INTEGER) AS "id", CAST(NULL AS INTEGER) AS "person", CAST(NULL AS BOOLEAN) AS "is experienced", CAST(? AS VARCHAR(255)) AS "name", CAST(NULL AS INTEGER) AS "age", CAST(NULL AS INTEGER) AS "favourite colour", CAST(NULL AS INTEGER) AS "is on-team", CAST(NULL AS INTEGER) AS "licence", CAST(NULL AS TIMESTAMPTZ) AS "hire date", CAST(NULL AS INTEGER) AS "was trained by-pilot"
641
- ) AS "$insert"
642
- WHERE EXISTS (
643
- SELECT 1
644
- FROM (
645
- SELECT "$insert".*
646
- ) AS "pilot"
647
- LEFT JOIN "pilot-can fly-plane" AS "pilot.pilot-can fly-plane" ON "pilot"."id" = "pilot.pilot-can fly-plane"."pilot"
648
- LEFT JOIN "plane" AS "pilot.pilot-can fly-plane.plane" ON "pilot.pilot-can fly-plane"."can fly-plane" = "pilot.pilot-can fly-plane.plane"."id"
649
- WHERE ${sql}
650
- )`,
651
- );
652
- };
653
- const updateWhere = `\
654
- WHERE "pilot"."id" IN ((
655
- SELECT "pilot"."id" AS "$modifyid"
656
- FROM "pilot"
657
- LEFT JOIN "pilot-can fly-plane" AS "pilot.pilot-can fly-plane" ON "pilot"."id" = "pilot.pilot-can fly-plane"."pilot"
658
- LEFT JOIN "plane" AS "pilot.pilot-can fly-plane.plane" ON "pilot.pilot-can fly-plane"."can fly-plane" = "pilot.pilot-can fly-plane.plane"."id"
659
- WHERE ${sql}
660
- ))`;
661
-
662
- test(`/pilot?$filter=${odata}`, 'GET', bindings, (result, sqlEquals) => {
663
- it(`should select from pilot where '${odata}'`, () => {
664
- sqlEquals(
665
- result,
666
- `\
667
- SELECT ${pilotFieldsStr}
668
- FROM "pilot"
669
- LEFT JOIN "pilot-can fly-plane" AS "pilot.pilot-can fly-plane" ON "pilot"."id" = "pilot.pilot-can fly-plane"."pilot"
670
- LEFT JOIN "plane" AS "pilot.pilot-can fly-plane.plane" ON "pilot.pilot-can fly-plane"."can fly-plane" = "pilot.pilot-can fly-plane.plane"."id"
671
- WHERE ${sql}`,
672
- );
673
- });
674
- });
675
-
676
- test(
677
- `/pilot?$filter=${odata}`,
678
- 'PATCH',
679
- bodyBindings,
680
- { name },
681
- (result, sqlEquals) => {
682
- it(`should update pilot where '${odata}'`, () => {
683
- sqlEquals(
684
- result,
685
- `\
686
- UPDATE "pilot"
687
- SET "name" = ?
688
- ${updateWhere}`,
689
- );
690
- });
691
- },
692
- );
693
-
694
- test(
695
- `/pilot?$filter=${odata}`,
696
- 'POST',
697
- bodyBindings,
698
- { name },
699
- (result, sqlEquals) => {
700
- it(`should insert pilot where '${odata}'`, () => {
701
- insertTest(result, sqlEquals);
702
- });
703
- },
704
- );
705
-
706
- test(
707
- `/pilot?$filter=${odata}`,
708
- 'PUT',
709
- bodyBindings,
710
- { name },
711
- (result, sqlEquals) => {
712
- describe('should upsert the pilot with id 1', function () {
713
- it('should be an upsert', () => {
714
- expect(result).to.be.an('array');
715
- });
716
- it('that inserts', () => {
717
- insertTest((result as SqlResult[])[0], sqlEquals);
718
- });
719
- it('and updates', () => {
720
- sqlEquals(
721
- (result as SqlResult[])[1],
722
- `\
723
- UPDATE "pilot"
724
- SET "created at" = DEFAULT,
725
- "modified at" = DEFAULT,
726
- "id" = DEFAULT,
727
- "person" = DEFAULT,
728
- "is experienced" = DEFAULT,
729
- "name" = ?,
730
- "age" = DEFAULT,
731
- "favourite colour" = DEFAULT,
732
- "is on-team" = DEFAULT,
733
- "licence" = DEFAULT,
734
- "hire date" = DEFAULT,
735
- "was trained by-pilot" = DEFAULT
736
- ${updateWhere}`,
737
- );
738
- });
739
- });
740
- },
741
- );
742
-
743
- test(`/pilot?$filter=${odata}`, 'DELETE', bindings, (result, sqlEquals) => {
744
- it('should delete from pilot where "' + odata + '"', () => {
745
- sqlEquals(
746
- result,
747
- `\
748
- DELETE FROM "pilot"
749
- WHERE "pilot"."id" IN ((
750
- SELECT "pilot"."id" AS "$modifyid"
751
- FROM "pilot"
752
- LEFT JOIN "pilot-can fly-plane" AS "pilot.pilot-can fly-plane" ON "pilot"."id" = "pilot.pilot-can fly-plane"."pilot"
753
- LEFT JOIN "plane" AS "pilot.pilot-can fly-plane.plane" ON "pilot.pilot-can fly-plane"."can fly-plane" = "pilot.pilot-can fly-plane.plane"."id"
754
- WHERE ${sql}
755
- ))`,
756
- );
757
- });
758
- });
759
- });
760
-
761
- run(function () {
762
- const name = 'Peter';
763
- const {
764
- odata,
765
- sql,
766
- bindings: exprBindings,
767
- } = createExpression('name', 'eq', `'${name}'`);
768
- test(
769
- `/pilot?$filter=${odata}`,
770
- 'POST',
771
- [['Bind', ['pilot', 'name']], ...exprBindings],
772
- { name },
773
- (result, sqlEquals) => {
774
- it(`should insert into pilot where '${odata}'`, () => {
775
- sqlEquals(
776
- result,
777
- `\
778
- INSERT INTO "pilot" ("name")
779
- SELECT "$insert"."name"
780
- FROM (
781
- SELECT CAST(NULL AS TIMESTAMPTZ) AS "created at", CAST(NULL AS TIMESTAMPTZ) AS "modified at", CAST(NULL AS INTEGER) AS "id", CAST(NULL AS INTEGER) AS "person", CAST(NULL AS BOOLEAN) AS "is experienced", CAST(? AS VARCHAR(255)) AS "name", CAST(NULL AS INTEGER) AS "age", CAST(NULL AS INTEGER) AS "favourite colour", CAST(NULL AS INTEGER) AS "is on-team", CAST(NULL AS INTEGER) AS "licence", CAST(NULL AS TIMESTAMPTZ) AS "hire date", CAST(NULL AS INTEGER) AS "was trained by-pilot"
782
- ) AS "$insert"
783
- WHERE EXISTS (
784
- SELECT 1
785
- FROM (
786
- SELECT "$insert".*
787
- ) AS "pilot"
788
- WHERE ${sql}
789
- )`,
790
- );
791
- });
792
- },
793
- );
794
- });
795
-
796
- run(function () {
797
- const name = 'Peter';
798
- const { odata: keyOdata, bindings: keyBindings } = parseOperand!(1);
799
- const {
800
- odata,
801
- sql,
802
- bindings: exprBindings,
803
- } = createExpression('name', 'eq', `'${name}'`);
804
- const bodyBindings = [['Bind', ['pilot', 'name']]] as const;
805
- const insertBindings = [
806
- ['Bind', ['pilot', 'id']],
807
- ...bodyBindings,
808
- ...exprBindings,
809
- ...keyBindings,
810
- ] as const;
811
- const updateBindings = [
812
- ...bodyBindings,
813
- ...keyBindings,
814
- ...exprBindings,
815
- ] as const;
816
- test(
817
- '/pilot(' + keyOdata + ')?$filter=' + odata,
818
- 'PATCH',
819
- updateBindings,
820
- { name },
821
- (result, sqlEquals) => {
822
- it('should update the pilot with id 1', () => {
823
- sqlEquals(
824
- result,
825
- `\
826
- UPDATE "pilot"
827
- SET "name" = ?
828
- WHERE "pilot"."id" IS NOT NULL AND "pilot"."id" = ?
829
- AND "pilot"."id" IN ((
830
- SELECT "pilot"."id" AS "$modifyid"
831
- FROM "pilot"
832
- WHERE ${sql}
833
- ))`,
834
- );
835
- });
836
- },
837
- );
838
-
839
- test(
840
- '/pilot(' + keyOdata + ')?$filter=' + odata,
841
- 'PUT',
842
- [insertBindings, [['Bind', ['pilot', 'id']], ...updateBindings]],
843
- { name },
844
- (result, sqlEquals) => {
845
- describe('should upsert the pilot with id 1', function () {
846
- it('should be an upsert', () => {
847
- expect(result).to.be.an('array');
848
- });
849
- it('that inserts', () => {
850
- sqlEquals(
851
- (result as SqlResult[])[0],
852
- `\
853
- INSERT INTO "pilot" ("id", "name")
854
- SELECT "$insert"."id", "$insert"."name"
855
- FROM (
856
- SELECT CAST(NULL AS TIMESTAMPTZ) AS "created at", CAST(NULL AS TIMESTAMPTZ) AS "modified at", CAST(? AS INTEGER) AS "id", CAST(NULL AS INTEGER) AS "person", CAST(NULL AS BOOLEAN) AS "is experienced", CAST(? AS VARCHAR(255)) AS "name", CAST(NULL AS INTEGER) AS "age", CAST(NULL AS INTEGER) AS "favourite colour", CAST(NULL AS INTEGER) AS "is on-team", CAST(NULL AS INTEGER) AS "licence", CAST(NULL AS TIMESTAMPTZ) AS "hire date", CAST(NULL AS INTEGER) AS "was trained by-pilot"
857
- ) AS "$insert"
858
- WHERE EXISTS (
859
- SELECT 1
860
- FROM (
861
- SELECT "$insert".*
862
- ) AS "pilot"
863
- WHERE ${sql}
864
- AND "pilot"."id" IS NOT NULL AND "pilot"."id" = ?
865
- )`,
866
- );
867
- });
868
- it('and updates', () => {
869
- sqlEquals(
870
- (result as SqlResult[])[1],
871
- `\
872
- UPDATE "pilot"
873
- SET "created at" = DEFAULT,
874
- "modified at" = DEFAULT,
875
- "id" = ?,
876
- "person" = DEFAULT,
877
- "is experienced" = DEFAULT,
878
- "name" = ?,
879
- "age" = DEFAULT,
880
- "favourite colour" = DEFAULT,
881
- "is on-team" = DEFAULT,
882
- "licence" = DEFAULT,
883
- "hire date" = DEFAULT,
884
- "was trained by-pilot" = DEFAULT
885
- WHERE "pilot"."id" IS NOT NULL AND "pilot"."id" = ?
886
- AND "pilot"."id" IN ((
887
- SELECT "pilot"."id" AS "$modifyid"
888
- FROM "pilot"
889
- WHERE ${sql}
890
- ))`,
891
- );
892
- });
893
- });
894
- },
895
- );
896
- });
897
-
898
- run(function () {
899
- const { odata: keyOdata, bindings: keyBindings } = parseOperand!(1);
900
- const { odata, bindings, sql } = createExpression(
901
- createExpression(1, 'eq', 1),
902
- 'or',
903
- createExpression(1, 'eq', 1),
904
- );
905
- test(
906
- '/pilot(' + keyOdata + ')/can_fly__plane?$filter=' + odata,
907
- 'GET',
908
- bindings.concat(keyBindings),
909
- (result, sqlEquals) => {
910
- it(
911
- 'should select from pilot__can_fly__plane where "' + odata + '"',
912
- () => {
913
- sqlEquals(
914
- result,
915
- `\
916
- SELECT ${aliasPilotCanFlyPlaneFieldsStr}
917
- FROM "pilot",
918
- "pilot-can fly-plane" AS "pilot.pilot-can fly-plane"
919
- WHERE ${sql}
920
- AND "pilot"."id" = "pilot.pilot-can fly-plane"."pilot"
921
- AND "pilot"."id" IS NOT NULL AND "pilot"."id" = ?`,
922
- );
923
- },
924
- );
925
- },
926
- );
927
- });
928
-
929
- methodTest('contains', 'name', "'et'");
930
- methodTest('endswith', 'name', "'ete'");
931
- methodTest('startswith', 'name', "'P'");
932
- run(() => {
933
- operandTest(createMethodCall('length', 'name'), 'eq', 4);
934
- });
935
-
936
- run(() => {
937
- operandTest(createMethodCall('indexof', 'name', "'Pe'"), 'eq', 0, {
938
- sql: '(STRPOS("pilot"."name", $1) - 1) IS NOT NULL AND (STRPOS("pilot"."name", $1) - 1) = $2',
939
- });
940
- });
941
- run(() => {
942
- operandTest(createMethodCall('substring', 'name', 1), 'eq', "'ete'", {
943
- sql: '(SUBSTRING("pilot"."name", $1 + 1)) IS NOT NULL AND (SUBSTRING("pilot"."name", $1 + 1)) = $2',
944
- });
945
- });
946
- run(() => {
947
- operandTest(createMethodCall('substring', 'name', 1, 2), 'eq', "'et'", {
948
- sql: '(SUBSTRING("pilot"."name", $1 + 1, $2)) IS NOT NULL AND (SUBSTRING("pilot"."name", $1 + 1, $2)) = $3',
949
- });
950
- });
951
- run(() => {
952
- operandTest(createMethodCall('tolower', 'name'), 'eq', "'pete'");
953
- });
954
- run(() => {
955
- operandTest(
956
- createMethodCall('tolower', 'trained__pilot/name'),
957
- 'eq',
958
- "'pete'",
959
- );
960
- });
961
- run(() => {
962
- operandTest(createMethodCall('toupper', 'name'), 'eq', "'PETE'");
963
- });
964
- run(function () {
965
- const concat = createMethodCall('concat', 'name', "'%20'");
966
- operandTest(createMethodCall('trim', concat), 'eq', "'Pete'", {
967
- sql: '(TRIM(("pilot"."name" || $1))) IS NOT NULL AND (TRIM(("pilot"."name" || $1))) = $2',
968
- });
969
- });
970
- run(function () {
971
- const concat = createMethodCall('concat', 'name', "'%20'");
972
- operandTest(concat, 'eq', "'Pete%20'", {
973
- sql: '(("pilot"."name" || $1)) IS NOT NULL AND (("pilot"."name" || $1)) = $2',
974
- });
975
- });
976
- run(() => {
977
- operandTest(createMethodCall('year', 'hire_date'), 'eq', 2011);
978
- });
979
- run(() => {
980
- operandTest(createMethodCall('month', 'hire_date'), 'eq', 10);
981
- });
982
- run(() => {
983
- operandTest(createMethodCall('day', 'hire_date'), 'eq', 3);
984
- });
985
- run(() => {
986
- operandTest(createMethodCall('hour', 'hire_date'), 'eq', 12);
987
- });
988
- run(() => {
989
- operandTest(createMethodCall('minute', 'hire_date'), 'eq', 10);
990
- });
991
- run(() => {
992
- operandTest(createMethodCall('second', 'hire_date'), 'eq', 25);
993
- });
994
- run(() => {
995
- operandTest(createMethodCall('fractionalseconds', 'hire_date'), 'eq', 0.222);
996
- });
997
- run(() => {
998
- operandTest(createMethodCall('date', 'hire_date'), 'eq', "'2011-10-03'");
999
- });
1000
- run(() => {
1001
- operandTest(createMethodCall('time', 'hire_date'), 'eq', "'12:10:25.222'");
1002
- });
1003
- run(() => {
1004
- operandTest(createMethodCall('now'), 'eq', new Date('2012-12-03T07:16:23Z'));
1005
- });
1006
- run(() => {
1007
- operandTest(
1008
- createMethodCall('totalseconds', {
1009
- negative: true,
1010
- day: 3,
1011
- hour: 4,
1012
- minute: 5,
1013
- second: 6.7,
1014
- }),
1015
- 'eq',
1016
- -273906.7,
1017
- );
1018
- });
1019
- run(() => {
1020
- operandTest(createMethodCall('round', 'age'), 'eq', 25);
1021
- });
1022
- run(() => {
1023
- operandTest(createMethodCall('floor', 'age'), 'eq', 25);
1024
- });
1025
- run(() => {
1026
- operandTest(createMethodCall('ceiling', 'age'), 'eq', 25);
1027
- });
1028
-
1029
- methodTest('substringof', "'Pete'", 'name');
1030
- run(() => {
1031
- operandTest(
1032
- createMethodCall('replace', 'name', "'ete'", "'at'"),
1033
- 'eq',
1034
- "'Pat'",
1035
- {
1036
- sql: '(REPLACE("pilot"."name", $1, $2)) IS NOT NULL AND (REPLACE("pilot"."name", $1, $2)) = $3',
1037
- },
1038
- );
1039
- });
1040
-
1041
- test(
1042
- "/pilot?$filter=can_fly__plane/any(d:d/plane/name eq 'Concorde')",
1043
- 'GET',
1044
- [['Bind', 0]],
1045
- (result, sqlEquals) => {
1046
- it('should select from pilot where ...', () => {
1047
- sqlEquals(
1048
- result,
1049
- `\
1050
- SELECT ${pilotFieldsStr}
1051
- FROM "pilot"
1052
- WHERE EXISTS (
1053
- SELECT 1
1054
- FROM "pilot-can fly-plane" AS "pilot.pilot-can fly-plane"
1055
- LEFT JOIN "plane" AS "pilot.pilot-can fly-plane.plane" ON "pilot.pilot-can fly-plane"."can fly-plane" = "pilot.pilot-can fly-plane.plane"."id"
1056
- WHERE "pilot"."id" = "pilot.pilot-can fly-plane"."pilot"
1057
- AND "pilot.pilot-can fly-plane.plane"."name" IS NOT NULL AND "pilot.pilot-can fly-plane.plane"."name" = ?
1058
- )`,
1059
- );
1060
- });
1061
- },
1062
- );
1063
-
1064
- test(
1065
- "/pilot/$count?$filter=can_fly__plane/any(d:d/plane/name eq 'Concorde')",
1066
- 'GET',
1067
- [['Bind', 0]],
1068
- (result, sqlEquals) => {
1069
- it('should select count(*) from pilot where ...', () => {
1070
- sqlEquals(
1071
- result,
1072
- `\
1073
- SELECT COUNT(*) AS "$count"
1074
- FROM "pilot"
1075
- WHERE EXISTS (
1076
- SELECT 1
1077
- FROM "pilot-can fly-plane" AS "pilot.pilot-can fly-plane"
1078
- LEFT JOIN "plane" AS "pilot.pilot-can fly-plane.plane" ON "pilot.pilot-can fly-plane"."can fly-plane" = "pilot.pilot-can fly-plane.plane"."id"
1079
- WHERE "pilot"."id" = "pilot.pilot-can fly-plane"."pilot"
1080
- AND "pilot.pilot-can fly-plane.plane"."name" IS NOT NULL AND "pilot.pilot-can fly-plane.plane"."name" = ?
1081
- )`,
1082
- );
1083
- });
1084
- },
1085
- );
1086
-
1087
- test(
1088
- "/pilot?$filter=can_fly__plane/any(d:d/plane/name eq 'Concorde') or (id eq 5 or id eq 10) or (name eq 'Peter' or name eq 'Harry')",
1089
- 'GET',
1090
- [
1091
- ['Bind', 0],
1092
- ['Bind', 1],
1093
- ['Bind', 2],
1094
- ['Bind', 3],
1095
- ['Bind', 4],
1096
- ],
1097
- (result, sqlEquals) => {
1098
- it('should select count(*) from pilot where id in (5,10)', () => {
1099
- sqlEquals(
1100
- result,
1101
- `\
1102
- SELECT ${pilotFieldsStr}
1103
- FROM "pilot"
1104
- WHERE (EXISTS (
1105
- SELECT 1
1106
- FROM "pilot-can fly-plane" AS "pilot.pilot-can fly-plane"
1107
- LEFT JOIN "plane" AS "pilot.pilot-can fly-plane.plane" ON "pilot.pilot-can fly-plane"."can fly-plane" = "pilot.pilot-can fly-plane.plane"."id"
1108
- WHERE "pilot"."id" = "pilot.pilot-can fly-plane"."pilot"
1109
- AND "pilot.pilot-can fly-plane.plane"."name" IS NOT NULL AND "pilot.pilot-can fly-plane.plane"."name" = ?
1110
- )
1111
- OR "pilot"."id" IS NOT NULL AND "pilot"."id" = ?
1112
- OR "pilot"."id" IS NOT NULL AND "pilot"."id" = ?
1113
- OR "pilot"."name" IS NOT NULL AND "pilot"."name" = ?
1114
- OR "pilot"."name" IS NOT NULL AND "pilot"."name" = ?)`,
1115
- );
1116
- });
1117
- },
1118
- );
1119
-
1120
- test(
1121
- "/pilot?$filter=not(can_fly__plane/any(d:d/plane/name eq 'Concorde') or (id eq 5 or id eq 10) or (name eq 'Peter' or name eq 'Harry'))",
1122
- 'GET',
1123
- [
1124
- ['Bind', 0],
1125
- ['Bind', 1],
1126
- ['Bind', 2],
1127
- ['Bind', 3],
1128
- ['Bind', 4],
1129
- ],
1130
- (result, sqlEquals) => {
1131
- it('should select count(*) from pilot where id in (5,10)', () => {
1132
- sqlEquals(
1133
- result,
1134
- `\
1135
- SELECT ${pilotFieldsStr}
1136
- FROM "pilot"
1137
- WHERE NOT (
1138
- (EXISTS (
1139
- SELECT 1
1140
- FROM "pilot-can fly-plane" AS "pilot.pilot-can fly-plane"
1141
- LEFT JOIN "plane" AS "pilot.pilot-can fly-plane.plane" ON "pilot.pilot-can fly-plane"."can fly-plane" = "pilot.pilot-can fly-plane.plane"."id"
1142
- WHERE "pilot"."id" = "pilot.pilot-can fly-plane"."pilot"
1143
- AND "pilot.pilot-can fly-plane.plane"."name" IS NOT NULL AND "pilot.pilot-can fly-plane.plane"."name" = ?
1144
- )
1145
- OR "pilot"."id" IS NOT NULL AND "pilot"."id" = ?
1146
- OR "pilot"."id" IS NOT NULL AND "pilot"."id" = ?
1147
- OR "pilot"."name" IS NOT NULL AND "pilot"."name" = ?
1148
- OR "pilot"."name" IS NOT NULL AND "pilot"."name" = ?)
1149
- )`,
1150
- );
1151
- });
1152
- },
1153
- );
1154
-
1155
- test(
1156
- "/pilot?$filter=can_fly__plane/all(d:d/plane/name eq 'Concorde')",
1157
- 'GET',
1158
- [['Bind', 0]],
1159
- (result, sqlEquals) => {
1160
- it('should select from pilot where ...', () => {
1161
- sqlEquals(
1162
- result,
1163
- `\
1164
- SELECT ${pilotFieldsStr}
1165
- FROM "pilot"
1166
- WHERE NOT EXISTS (
1167
- SELECT 1
1168
- FROM "pilot-can fly-plane" AS "pilot.pilot-can fly-plane"
1169
- LEFT JOIN "plane" AS "pilot.pilot-can fly-plane.plane" ON "pilot.pilot-can fly-plane"."can fly-plane" = "pilot.pilot-can fly-plane.plane"."id"
1170
- WHERE "pilot"."id" = "pilot.pilot-can fly-plane"."pilot"
1171
- AND NOT (
1172
- "pilot.pilot-can fly-plane.plane"."name" IS NOT NULL AND "pilot.pilot-can fly-plane.plane"."name" = ?
1173
- )
1174
- )`,
1175
- );
1176
- });
1177
- },
1178
- );
1179
-
1180
- test(
1181
- "/pilot/$count?$filter=can_fly__plane/all(d:d/plane/name eq 'Concorde')",
1182
- 'GET',
1183
- [['Bind', 0]],
1184
- (result, sqlEquals) => {
1185
- it('should select count(*) from pilot where ...', () => {
1186
- sqlEquals(
1187
- result,
1188
- `\
1189
- SELECT COUNT(*) AS "$count"
1190
- FROM "pilot"
1191
- WHERE NOT EXISTS (
1192
- SELECT 1
1193
- FROM "pilot-can fly-plane" AS "pilot.pilot-can fly-plane"
1194
- LEFT JOIN "plane" AS "pilot.pilot-can fly-plane.plane" ON "pilot.pilot-can fly-plane"."can fly-plane" = "pilot.pilot-can fly-plane.plane"."id"
1195
- WHERE "pilot"."id" = "pilot.pilot-can fly-plane"."pilot"
1196
- AND NOT (
1197
- "pilot.pilot-can fly-plane.plane"."name" IS NOT NULL AND "pilot.pilot-can fly-plane.plane"."name" = ?
1198
- )
1199
- )`,
1200
- );
1201
- });
1202
- },
1203
- );
1204
-
1205
- test(
1206
- "/pilot?$filter=can_fly__plane/plane/any(d:d/name eq 'Concorde')",
1207
- 'GET',
1208
- [['Bind', 0]],
1209
- (result, sqlEquals) => {
1210
- it('should select from pilot where ...', () => {
1211
- sqlEquals(
1212
- result,
1213
- `\
1214
- SELECT ${pilotFieldsStr}
1215
- FROM "pilot"
1216
- LEFT JOIN "pilot-can fly-plane" AS "pilot.pilot-can fly-plane" ON "pilot"."id" = "pilot.pilot-can fly-plane"."pilot"
1217
- WHERE EXISTS (
1218
- SELECT 1
1219
- FROM "plane" AS "pilot.pilot-can fly-plane.plane"
1220
- WHERE "pilot.pilot-can fly-plane"."can fly-plane" = "pilot.pilot-can fly-plane.plane"."id"
1221
- AND "pilot.pilot-can fly-plane.plane"."name" IS NOT NULL AND "pilot.pilot-can fly-plane.plane"."name" = ?
1222
- )`,
1223
- );
1224
- });
1225
- },
1226
- );
1227
-
1228
- test(
1229
- "/pilot/$count?$filter=can_fly__plane/plane/any(d:d/name eq 'Concorde')",
1230
- 'GET',
1231
- [['Bind', 0]],
1232
- (result, sqlEquals) => {
1233
- it('should select count(*) from pilot where ...', () => {
1234
- sqlEquals(
1235
- result,
1236
- `\
1237
- SELECT COUNT(*) AS "$count"
1238
- FROM "pilot"
1239
- LEFT JOIN "pilot-can fly-plane" AS "pilot.pilot-can fly-plane" ON "pilot"."id" = "pilot.pilot-can fly-plane"."pilot"
1240
- WHERE EXISTS (
1241
- SELECT 1
1242
- FROM "plane" AS "pilot.pilot-can fly-plane.plane"
1243
- WHERE "pilot.pilot-can fly-plane"."can fly-plane" = "pilot.pilot-can fly-plane.plane"."id"
1244
- AND "pilot.pilot-can fly-plane.plane"."name" IS NOT NULL AND "pilot.pilot-can fly-plane.plane"."name" = ?
1245
- )`,
1246
- );
1247
- });
1248
- },
1249
- );
1250
-
1251
- test(
1252
- "/pilot?$filter=can_fly__plane/plane/all(d:d/name eq 'Concorde')",
1253
- 'GET',
1254
- [['Bind', 0]],
1255
- (result, sqlEquals) => {
1256
- it('should select from pilot where ...', () => {
1257
- sqlEquals(
1258
- result,
1259
- `\
1260
- SELECT ${pilotFieldsStr}
1261
- FROM "pilot"
1262
- LEFT JOIN "pilot-can fly-plane" AS "pilot.pilot-can fly-plane" ON "pilot"."id" = "pilot.pilot-can fly-plane"."pilot"
1263
- WHERE NOT EXISTS (
1264
- SELECT 1
1265
- FROM "plane" AS "pilot.pilot-can fly-plane.plane"
1266
- WHERE "pilot.pilot-can fly-plane"."can fly-plane" = "pilot.pilot-can fly-plane.plane"."id"
1267
- AND NOT (
1268
- "pilot.pilot-can fly-plane.plane"."name" IS NOT NULL AND "pilot.pilot-can fly-plane.plane"."name" = ?
1269
- )
1270
- )`,
1271
- );
1272
- });
1273
- },
1274
- );
1275
-
1276
- test(
1277
- "/pilot/$count?$filter=can_fly__plane/plane/all(d:d/name eq 'Concorde')",
1278
- 'GET',
1279
- [['Bind', 0]],
1280
- (result, sqlEquals) => {
1281
- it('should select count(*) from pilot where ...', () => {
1282
- sqlEquals(
1283
- result,
1284
- `\
1285
- SELECT COUNT(*) AS "$count"
1286
- FROM "pilot"
1287
- LEFT JOIN "pilot-can fly-plane" AS "pilot.pilot-can fly-plane" ON "pilot"."id" = "pilot.pilot-can fly-plane"."pilot"
1288
- WHERE NOT EXISTS (
1289
- SELECT 1
1290
- FROM "plane" AS "pilot.pilot-can fly-plane.plane"
1291
- WHERE "pilot.pilot-can fly-plane"."can fly-plane" = "pilot.pilot-can fly-plane.plane"."id"
1292
- AND NOT (
1293
- "pilot.pilot-can fly-plane.plane"."name" IS NOT NULL AND "pilot.pilot-can fly-plane.plane"."name" = ?
1294
- )
1295
- )`,
1296
- );
1297
- });
1298
- },
1299
- );
1300
-
1301
- test(
1302
- "/pilot?$filter=can_fly__plane/any(d:d/plane/name eq 'Concorde') or (id eq 5 or name eq 'Peter') or (id eq 10 or name eq 'Harry')",
1303
- 'GET',
1304
- [
1305
- ['Bind', 0],
1306
- ['Bind', 1],
1307
- ['Bind', 2],
1308
- ['Bind', 3],
1309
- ['Bind', 4],
1310
- ],
1311
- (result, sqlEquals) => {
1312
- it('should select count(*) from pilot where id in (5,10)', () => {
1313
- sqlEquals(
1314
- result,
1315
- `\
1316
- SELECT ${pilotFieldsStr}
1317
- FROM "pilot"
1318
- WHERE (EXISTS (
1319
- SELECT 1
1320
- FROM "pilot-can fly-plane" AS "pilot.pilot-can fly-plane"
1321
- LEFT JOIN "plane" AS "pilot.pilot-can fly-plane.plane" ON "pilot.pilot-can fly-plane"."can fly-plane" = "pilot.pilot-can fly-plane.plane"."id"
1322
- WHERE "pilot"."id" = "pilot.pilot-can fly-plane"."pilot"
1323
- AND "pilot.pilot-can fly-plane.plane"."name" IS NOT NULL AND "pilot.pilot-can fly-plane.plane"."name" = ?
1324
- )
1325
- OR "pilot"."id" IS NOT NULL AND "pilot"."id" = ?
1326
- OR "pilot"."name" IS NOT NULL AND "pilot"."name" = ?
1327
- OR "pilot"."id" IS NOT NULL AND "pilot"."id" = ?
1328
- OR "pilot"."name" IS NOT NULL AND "pilot"."name" = ?)`,
1329
- );
1330
- });
1331
- },
1332
- );
1333
-
1334
- test(
1335
- "/pilot?$filter=can_fly__plane/any(d:d/plane/name eq 'Concorde') and (id ne 5 and id ne 10) and (name ne 'Peter' and name ne 'Harry')",
1336
- 'GET',
1337
- [
1338
- ['Bind', 0],
1339
- ['Bind', 1],
1340
- ['Bind', 2],
1341
- ['Bind', 3],
1342
- ['Bind', 4],
1343
- ],
1344
- (result, sqlEquals) => {
1345
- it('should select count(*) from pilot where id in (5,10)', () => {
1346
- sqlEquals(
1347
- result,
1348
- `\
1349
- SELECT ${pilotFieldsStr}
1350
- FROM "pilot"
1351
- WHERE EXISTS (
1352
- SELECT 1
1353
- FROM "pilot-can fly-plane" AS "pilot.pilot-can fly-plane"
1354
- LEFT JOIN "plane" AS "pilot.pilot-can fly-plane.plane" ON "pilot.pilot-can fly-plane"."can fly-plane" = "pilot.pilot-can fly-plane.plane"."id"
1355
- WHERE "pilot"."id" = "pilot.pilot-can fly-plane"."pilot"
1356
- AND "pilot.pilot-can fly-plane.plane"."name" IS NOT NULL AND "pilot.pilot-can fly-plane.plane"."name" = ?
1357
- )
1358
- AND NOT("pilot"."id" IS NOT NULL AND "pilot"."id" = ?)
1359
- AND NOT("pilot"."id" IS NOT NULL AND "pilot"."id" = ?)
1360
- AND NOT("pilot"."name" IS NOT NULL AND "pilot"."name" = ?)
1361
- AND NOT("pilot"."name" IS NOT NULL AND "pilot"."name" = ?)`,
1362
- );
1363
- });
1364
- },
1365
- );
1366
-
1367
- test(
1368
- "/pilot?$filter=can_fly__plane/any(d:d/plane/name eq 'Concorde') and (id ne 5 and name ne 'Peter') and (id ne 10 and name ne 'Harry')",
1369
- 'GET',
1370
- [
1371
- ['Bind', 0],
1372
- ['Bind', 1],
1373
- ['Bind', 2],
1374
- ['Bind', 3],
1375
- ['Bind', 4],
1376
- ],
1377
- (result, sqlEquals) => {
1378
- it('should select count(*) from pilot where id in (5,10)', () => {
1379
- sqlEquals(
1380
- result,
1381
- `\
1382
- SELECT ${pilotFieldsStr}
1383
- FROM "pilot"
1384
- WHERE EXISTS (
1385
- SELECT 1
1386
- FROM "pilot-can fly-plane" AS "pilot.pilot-can fly-plane"
1387
- LEFT JOIN "plane" AS "pilot.pilot-can fly-plane.plane" ON "pilot.pilot-can fly-plane"."can fly-plane" = "pilot.pilot-can fly-plane.plane"."id"
1388
- WHERE "pilot"."id" = "pilot.pilot-can fly-plane"."pilot"
1389
- AND "pilot.pilot-can fly-plane.plane"."name" IS NOT NULL AND "pilot.pilot-can fly-plane.plane"."name" = ?
1390
- )
1391
- AND NOT("pilot"."id" IS NOT NULL AND "pilot"."id" = ?)
1392
- AND NOT("pilot"."name" IS NOT NULL AND "pilot"."name" = ?)
1393
- AND NOT("pilot"."id" IS NOT NULL AND "pilot"."id" = ?)
1394
- AND NOT("pilot"."name" IS NOT NULL AND "pilot"."name" = ?)`,
1395
- );
1396
- });
1397
- },
1398
- );
1399
-
1400
- // Switch parseOperandFactory permanently to using 'team' as the resource,
1401
- // as we are switch to using that as our base resource from here on.
1402
- parseOperandFactory = _.partialRight(parseOperandFactory, 'team');
1403
- run(function () {
1404
- const favouriteColour = 'purple';
1405
- const { odata, sql, bindings } = createExpression(
1406
- 'favourite_colour',
1407
- 'eq',
1408
- `'${favouriteColour}'`,
1409
- );
1410
- test(
1411
- '/team?$filter=' + odata,
1412
- 'POST',
1413
- [['Bind', ['team', 'favourite_colour']], ...bindings],
1414
- { favourite_colour: favouriteColour },
1415
- (result, sqlEquals) => {
1416
- it('should insert into team where "' + odata + '"', () => {
1417
- sqlEquals(
1418
- result,
1419
- `\
1420
- INSERT INTO "team" ("favourite colour")
1421
- SELECT "$insert"."favourite colour"
1422
- FROM (
1423
- SELECT CAST(NULL AS TIMESTAMPTZ) AS "created at", CAST(NULL AS TIMESTAMPTZ) AS "modified at", CAST(? AS INTEGER) AS "favourite colour"
1424
- ) AS "$insert"
1425
- WHERE EXISTS (
1426
- SELECT 1
1427
- FROM (
1428
- SELECT "$insert".*
1429
- ) AS "team"
1430
- WHERE ${sql}
1431
- )`,
1432
- );
1433
- });
1434
- },
1435
- );
1436
- });
1437
-
1438
- run(function () {
1439
- const { odata, sql, bindings } = createExpression(
1440
- 'includes__pilot/can_fly__plane/plane/name',
1441
- 'eq',
1442
- "'Concorde'",
1443
- );
1444
- test('/team?$filter=' + odata, 'GET', bindings, (result, sqlEquals) => {
1445
- it('should select from team where "' + odata + '"', () => {
1446
- sqlEquals(
1447
- result,
1448
- `\
1449
- SELECT ${teamFieldsStr}
1450
- FROM "team"
1451
- LEFT JOIN "pilot" AS "team.includes-pilot" ON "team"."favourite colour" = "team.includes-pilot"."is on-team"
1452
- LEFT JOIN "pilot-can fly-plane" AS "team.includes-pilot.pilot-can fly-plane" ON "team.includes-pilot"."id" = "team.includes-pilot.pilot-can fly-plane"."pilot"
1453
- LEFT JOIN "plane" AS "team.includes-pilot.pilot-can fly-plane.plane" ON "team.includes-pilot.pilot-can fly-plane"."can fly-plane" = "team.includes-pilot.pilot-can fly-plane.plane"."id"
1454
- WHERE ${sql}`,
1455
- );
1456
- });
1457
- });
1458
- });
1459
-
1460
- run(function () {
1461
- const odata = "now() sub created_at lt duration'P1D'";
1462
- test(`/pilot?$filter=${odata}`, 'GET', [], (result, sqlEquals) => {
1463
- it('should select from pilot where "' + odata + '"', () => {
1464
- sqlEquals(
1465
- result,
1466
- `\
1467
- SELECT ${pilotFieldsStr}
1468
- FROM "pilot"
1469
- WHERE CURRENT_TIMESTAMP - DATE_TRUNC('milliseconds', "pilot"."created at", 'UTC') < INTERVAL '1 0:0:0.0'`,
1470
- );
1471
- });
1472
- });
1473
- });
1474
-
1475
- run(function () {
1476
- const odata = 'now() add now()';
1477
- test.fail(`/pilot?$filter=${odata}`, 'GET', [], (err) => {
1478
- it(
1479
- 'should fail to add current_timestamp to current_timestamp where "' +
1480
- odata +
1481
- '"',
1482
- () => {
1483
- expect(err).to.be.instanceOf(Error);
1484
- },
1485
- );
1486
- });
1487
- });
1488
-
1489
- run(function () {
1490
- const odata = "created_at add duration'P1D' gt now()";
1491
- test(`/pilot?$filter=${odata}`, 'GET', [], (result, sqlEquals) => {
1492
- it('should select from pilot where "' + odata + '"', () => {
1493
- sqlEquals(
1494
- result,
1495
- `\
1496
- SELECT ${pilotFieldsStr}
1497
- FROM "pilot"
1498
- WHERE DATE_TRUNC('milliseconds', "pilot"."created at", 'UTC') + INTERVAL '1 0:0:0.0' > CURRENT_TIMESTAMP`,
1499
- );
1500
- });
1501
- });
1502
- });
1503
-
1504
- run(function () {
1505
- const odata = "totalseconds(duration'P1D') gt 1";
1506
- test(`/pilot?$filter=${odata}`, 'GET', [['Bind', 0]], (result, sqlEquals) => {
1507
- it('should select from pilot where "' + odata + '"', () => {
1508
- sqlEquals(
1509
- result,
1510
- `\
1511
- SELECT ${pilotFieldsStr}
1512
- FROM "pilot"
1513
- WHERE EXTRACT(EPOCH FROM INTERVAL '1 0:0:0.0') > $1`,
1514
- );
1515
- });
1516
- });
1517
- });