@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,1138 +0,0 @@
1
- export const enum Engines {
2
- /* eslint-disable @typescript-eslint/no-shadow -- this is fine since we only assign plain string values to the enum items */
3
- postgres = 'postgres',
4
- mysql = 'mysql',
5
- websql = 'websql',
6
- /* eslint-enable @typescript-eslint/no-shadow */
7
- }
8
-
9
- import { AbstractSQLOptimizer } from './abstract-sql-optimizer.js';
10
- import type { Binding, SqlResult } from './abstract-sql-rules-to-sql.js';
11
- import { AbstractSQLRules2SQL } from './abstract-sql-rules-to-sql.js';
12
- export { Binding, SqlResult } from './abstract-sql-rules-to-sql.js';
13
- import type { SbvrType } from '@balena/sbvr-types';
14
- import $sbvrTypes from '@balena/sbvr-types';
15
- const { default: sbvrTypes } = $sbvrTypes;
16
- import {
17
- optimizeSchema,
18
- generateRuleSlug,
19
- } from './abstract-sql-schema-optimizer.js';
20
- import type {
21
- ReferencedFields,
22
- RuleReferencedFields,
23
- ModifiedFields,
24
- } from './referenced-fields.js';
25
- import {
26
- getReferencedFields,
27
- getRuleReferencedFields,
28
- getModifiedFields,
29
- insertAffectedIdsBinds,
30
- } from './referenced-fields.js';
31
-
32
- export type { ReferencedFields, RuleReferencedFields, ModifiedFields };
33
-
34
- export type NullNode = ['Null'];
35
- export type DateNode = ['Date', Date | number | string];
36
- export type DurationNode = [
37
- 'Duration',
38
- {
39
- negative?: boolean | undefined;
40
- day?: number | undefined;
41
- hour?: number | undefined;
42
- minute?: number | undefined;
43
- second?: number | undefined;
44
- },
45
- ];
46
- export type StrictDurationTypeNodes = DurationNode;
47
- export type DurationTypeNodes = StrictDurationTypeNodes | UnknownTypeNodes;
48
-
49
- export type BooleanNode = ['Boolean', boolean];
50
- export type EqualsNode = ['Equals', AnyTypeNodes, AnyTypeNodes];
51
- export type EqualsAnyNode = ['EqualsAny', AnyTypeNodes, BindNode];
52
- export type NotEqualsNode = ['NotEquals', AnyTypeNodes, AnyTypeNodes];
53
- export type IsDistinctFromNode = ['IsDistinctFrom', AnyTypeNodes, AnyTypeNodes];
54
- export type IsNotDistinctFromNode = [
55
- 'IsNotDistinctFrom',
56
- AnyTypeNodes,
57
- AnyTypeNodes,
58
- ];
59
- export type GreaterThanNode = ['GreaterThan', AnyTypeNodes, AnyTypeNodes];
60
- export type GreaterThanOrEqualNode = [
61
- 'GreaterThanOrEqual',
62
- AnyTypeNodes,
63
- AnyTypeNodes,
64
- ];
65
- export type LessThanNode = ['LessThan', AnyTypeNodes, AnyTypeNodes];
66
- export type LessThanOrEqualNode = [
67
- 'LessThanOrEqual',
68
- AnyTypeNodes,
69
- AnyTypeNodes,
70
- ];
71
- export type BetweenNode = ['Between', AnyTypeNodes, AnyTypeNodes, AnyTypeNodes];
72
- export type LikeNode = ['Like', AnyTypeNodes, AnyTypeNodes];
73
- export type InNode = [
74
- 'In',
75
- FieldNode | ReferencedFieldNode,
76
- AnyTypeNodes,
77
- ...AnyTypeNodes[],
78
- ];
79
- export type NotInNode = [
80
- 'NotIn',
81
- FieldNode | ReferencedFieldNode,
82
- AnyTypeNodes,
83
- ...AnyTypeNodes[],
84
- ];
85
- export type NotExistsNode = ['NotExists', AnyTypeNodes];
86
- export type ExistsNode = ['Exists', AnyTypeNodes];
87
- export type NotNode = ['Not', BooleanTypeNodes];
88
- export type AndNode = ['And', ...BooleanTypeNodes[]];
89
- export type OrNode = ['Or', ...BooleanTypeNodes[]];
90
- export type StartsWithNode = ['StartsWith', TextTypeNodes, TextTypeNodes];
91
- export type EndsWithNode = ['EndsWith', TextTypeNodes, TextTypeNodes];
92
- export type ContainsNode = ['Contains', TextTypeNodes, TextTypeNodes];
93
- export type StrictBooleanTypeNodes =
94
- | BooleanNode
95
- | EqualsNode
96
- | EqualsAnyNode
97
- | NotEqualsNode
98
- | IsDistinctFromNode
99
- | IsNotDistinctFromNode
100
- | GreaterThanNode
101
- | GreaterThanOrEqualNode
102
- | LessThanNode
103
- | LessThanOrEqualNode
104
- | BetweenNode
105
- | LikeNode
106
- | InNode
107
- | NotInNode
108
- | ExistsNode
109
- | NotExistsNode
110
- | NotNode
111
- | AndNode
112
- | OrNode
113
- | StartsWithNode
114
- | EndsWithNode
115
- | ContainsNode;
116
- export type BooleanTypeNodes = StrictBooleanTypeNodes | UnknownTypeNodes;
117
-
118
- export type YearNode = ['Year', DateTypeNodes];
119
- export type MonthNode = ['Month', DateTypeNodes];
120
- export type DayNode = ['Day', DateTypeNodes];
121
- export type HourNode = ['Hour', DateTypeNodes];
122
- export type MinuteNode = ['Minute', DateTypeNodes];
123
- export type SecondNode = ['Second', DateTypeNodes];
124
- export type FractionalsecondsNode = ['Fractionalseconds', DateTypeNodes];
125
- export type ExtractNumericDateTypeNodes =
126
- | YearNode
127
- | MonthNode
128
- | DayNode
129
- | HourNode
130
- | MinuteNode
131
- | SecondNode
132
- | FractionalsecondsNode;
133
- export type TotalsecondsNode = ['Totalseconds', DurationTypeNodes];
134
-
135
- export type IntegerNode = ['Integer', number];
136
- export type RealNode = ['Real', number];
137
- export type NumberNode = ['Number', number];
138
- export type AddNode = ['Add', NumberTypeNodes, NumberTypeNodes];
139
- export type SubtractNode = ['Subtract', NumberTypeNodes, NumberTypeNodes];
140
- export type MultiplyNode = ['Multiply', NumberTypeNodes, NumberTypeNodes];
141
- export type DivideNode = ['Divide', NumberTypeNodes, NumberTypeNodes];
142
- export type StrPosNode = ['StrPos', TextTypeNodes, TextTypeNodes];
143
- export type RoundNode = ['Round', NumberTypeNodes];
144
- export type FloorNode = ['Floor', NumberTypeNodes];
145
- export type CeilingNode = ['Ceiling', NumberTypeNodes];
146
- export type CountNode = ['Count', '*'];
147
- export type AverageNode = ['Average', NumberTypeNodes];
148
- export type SumNode = ['Sum', NumberTypeNodes];
149
- export type CharacterLengthNode = ['CharacterLength', TextTypeNodes];
150
- export type BitwiseAndNode = ['BitwiseAnd', NumberTypeNodes, NumberTypeNodes];
151
- export type BitwiseShiftRightNode = [
152
- 'BitwiseShiftRight',
153
- NumberTypeNodes,
154
- NumberTypeNodes,
155
- ];
156
- export type StrictNumberTypeNodes =
157
- | NumberNode
158
- | IntegerNode
159
- | RealNode
160
- | AddNode
161
- | SubtractNode
162
- | MultiplyNode
163
- | DivideNode
164
- | StrPosNode
165
- | ExtractNumericDateTypeNodes
166
- | TotalsecondsNode
167
- | RoundNode
168
- | FloorNode
169
- | CeilingNode
170
- | CountNode
171
- | AverageNode
172
- | SumNode
173
- | BitwiseAndNode
174
- | BitwiseShiftRightNode
175
- | CharacterLengthNode
176
- | SubtractDateDateNode;
177
- export type NumberTypeNodes = StrictNumberTypeNodes | UnknownTypeNodes;
178
-
179
- export type FieldNode = ['Field', string];
180
- export type ReferencedFieldNode = ['ReferencedField', string, string];
181
- export type DateTruncNode =
182
- | ['DateTrunc', TextTypeNodes, DateTypeNodes]
183
- | ['DateTrunc', TextTypeNodes, DateTypeNodes, TextTypeNodes];
184
- export type ToDateNode = ['ToDate', DateTypeNodes];
185
- export type ToTimeNode = ['ToTime', DateTypeNodes];
186
- export type CurrentTimestampNode = ['CurrentTimestamp'];
187
- export type CurrentDateNode = ['CurrentDate'];
188
- export type StrictDateTypeNodes =
189
- | DateNode
190
- | ToDateNode
191
- | ToTimeNode
192
- | CurrentTimestampNode
193
- | CurrentDateNode
194
- | DateTruncNode
195
- | SubtractDateNumberNode
196
- | SubtractDateDurationNode
197
- | AddDateTypeNodes;
198
- export type DateTypeNodes = StrictDateTypeNodes | UnknownTypeNodes;
199
-
200
- // Date operations return different types dependent on the operand types
201
- // here we explicitly type the different nodes by the input types
202
- // timestamp, datetime, date are use synonymous here and all are simplified under date node
203
- // returns integer
204
- export type SubtractDateDateNode = [
205
- 'SubtractDateDate',
206
- DateTypeNodes,
207
- DateTypeNodes,
208
- ];
209
- // returns date
210
- export type SubtractDateNumberNode = [
211
- 'SubtractDateNumber',
212
- DateTypeNodes,
213
- NumberTypeNodes,
214
- ];
215
- // returns date (timestamp)
216
- export type SubtractDateDurationNode = [
217
- 'SubtractDateDuration',
218
- DateTypeNodes,
219
- DurationNode,
220
- ];
221
- export type AddDateTypeNodes = AddDateNumberNode | AddDateDurationNode;
222
- // returns date
223
- export type AddDateNumberNode = [
224
- 'AddDateNumber',
225
- DateTypeNodes,
226
- NumberTypeNodes,
227
- ];
228
- // return date
229
- export type AddDateDurationNode = [
230
- 'AddDateDuration',
231
- DateTypeNodes,
232
- DurationNode,
233
- ];
234
-
235
- export type WhenNode = ['When', BooleanTypeNodes, AnyTypeNodes];
236
- export type ElseNode = ['Else', AnyTypeNodes];
237
- export type CaseNode =
238
- | ['Case', ...WhenNode[]]
239
- | ['Case', ...WhenNode[], ElseNode];
240
-
241
- export type BindNode = ['Bind', number | string] | ['Bind', [string, string]];
242
- export type CastNode = ['Cast', AnyTypeNodes, string];
243
- export type CoalesceNode = [
244
- 'Coalesce',
245
- AnyTypeNodes,
246
- AnyTypeNodes,
247
- ...AnyTypeNodes[],
248
- ];
249
- export type AnyNode = ['Any', AnyTypeNodes, string];
250
- export type UnknownTypeNodes =
251
- | SelectQueryNode
252
- | UnionQueryNode
253
- | NullNode
254
- | FieldNode
255
- | ReferencedFieldNode
256
- | BindNode
257
- | CastNode
258
- | CaseNode
259
- | CoalesceNode
260
- | AnyNode;
261
-
262
- export type ToJSONNode = ['ToJSON', AnyTypeNodes];
263
- export type AggregateJSONNode = [
264
- 'AggregateJSON',
265
- FieldNode | ReferencedFieldNode,
266
- ];
267
- export type StrictJSONTypeNodes = AggregateJSONNode | ToJSONNode;
268
- export type JSONTypeNodes = StrictJSONTypeNodes | UnknownTypeNodes;
269
-
270
- export type EmbeddedTextNode = ['EmbeddedText', string];
271
- export type TextNode = ['Text', string];
272
- export type ConcatenateNode = [
273
- 'Concatenate',
274
- TextTypeNodes,
275
- ...TextTypeNodes[],
276
- ];
277
- export type ConcatenateWithSeparatorNode = [
278
- 'ConcatenateWithSeparator',
279
- TextTypeNodes,
280
- TextTypeNodes,
281
- ...TextTypeNodes[],
282
- ];
283
- export type LowerNode = ['Lower', TextTypeNodes];
284
- export type UpperNode = ['Upper', TextTypeNodes];
285
- export type TrimNode = ['Trim', TextTypeNodes];
286
- export type SubstringNode = [
287
- 'Substring',
288
- TextTypeNodes,
289
- NumberTypeNodes,
290
- NumberTypeNodes?,
291
- ];
292
- export type RightNode = ['Right', TextTypeNodes, NumberTypeNodes];
293
- export type ReplaceNode = [
294
- 'Replace',
295
- TextTypeNodes,
296
- TextTypeNodes,
297
- TextTypeNodes,
298
- ];
299
- export type ExtractJSONPathAsTextNode = [
300
- 'ExtractJSONPathAsText',
301
- JSONTypeNodes,
302
- TextArrayTypeNodes,
303
- ];
304
- export type EscapeForLikeNode = ['EscapeForLike', TextTypeNodes];
305
- export type StrictTextArrayTypeNodes = TextArrayNode;
306
- export type TextArrayTypeNodes = StrictTextArrayTypeNodes | UnknownTypeNodes;
307
- export type TextArrayNode = ['TextArray', ...TextTypeNodes[]];
308
- export type StrictTextTypeNodes =
309
- | TextNode
310
- | EmbeddedTextNode
311
- | ConcatenateNode
312
- | ConcatenateWithSeparatorNode
313
- | LowerNode
314
- | UpperNode
315
- | TrimNode
316
- | SubstringNode
317
- | RightNode
318
- | ReplaceNode
319
- | ExtractJSONPathAsTextNode
320
- | EscapeForLikeNode;
321
- export type TextTypeNodes = StrictTextTypeNodes | UnknownTypeNodes;
322
-
323
- export type JoinTypeNodes =
324
- | InnerJoinNode
325
- | LeftJoinNode
326
- | RightJoinNode
327
- | FullJoinNode
328
- | CrossJoinNode;
329
-
330
- export type SelectQueryStatementNode =
331
- | SelectNode
332
- | FromNode
333
- | JoinTypeNodes
334
- | WhereNode
335
- | GroupByNode
336
- | HavingNode
337
- | OrderByNode
338
- | LimitNode
339
- | OffsetNode;
340
- export type SelectQueryNode = ['SelectQuery', ...SelectQueryStatementNode[]];
341
- export type UnionQueryNode = [
342
- 'UnionQuery',
343
- // eslint-disable-next-line @typescript-eslint/array-type -- Typescript fails on a circular reference when prettier changes this to an `Array<T>` form
344
- ...(UnionQueryNode | SelectQueryNode)[],
345
- ];
346
- export type InsertQueryNode = [
347
- 'InsertQuery',
348
- ...Array<FromNode | FieldsNode | ValuesNode | WhereNode>,
349
- ];
350
- export type UpdateQueryNode = [
351
- 'UpdateQuery',
352
- ...Array<FromNode | FieldsNode | ValuesNode | WhereNode>,
353
- ];
354
- export type DeleteQueryNode = ['DeleteQuery', ...Array<FromNode | WhereNode>];
355
- export type UpsertQueryNode = ['UpsertQuery', InsertQueryNode, UpdateQueryNode];
356
-
357
- /**
358
- * This interface allows adding to the valid set of FromTypeNodes using interface merging, eg
359
- * declare module '@balena/abstract-sql-compiler' {
360
- * interface FromTypeNode {
361
- * MyNode: MyNode;
362
- * }
363
- * }
364
- */
365
- export interface FromTypeNode {
366
- SelectQueryNode: SelectQueryNode;
367
- UnionQueryNode: UnionQueryNode;
368
- TableNode: TableNode;
369
- ResourceNode: ResourceNode;
370
- }
371
- /**
372
- * This is not currently understood by the abstract-sql-compiler but is a placeholder for future support
373
- */
374
- export type ResourceNode = ['Resource', string];
375
-
376
- export type FromTypeNodes =
377
- | FromTypeNode[keyof FromTypeNode]
378
- | AliasNode<FromTypeNode[keyof FromTypeNode]>;
379
-
380
- export type SelectNode = ['Select', AnyTypeNodes[]];
381
- export type FromNode = ['From', FromTypeNodes];
382
- export type InnerJoinNode = ['Join', FromTypeNodes, OnNode?];
383
- export type LeftJoinNode = ['LeftJoin', FromTypeNodes, OnNode?];
384
- export type RightJoinNode = ['RightJoin', FromTypeNodes, OnNode?];
385
- export type FullJoinNode = ['FullJoin', FromTypeNodes, OnNode?];
386
- export type CrossJoinNode = ['CrossJoin', FromTypeNodes];
387
- export type OnNode = ['On', BooleanTypeNodes];
388
- export type TableNode = ['Table', string];
389
- export type WhereNode = ['Where', BooleanTypeNodes];
390
- export type GroupByNode = ['GroupBy', Array<FieldNode | ReferencedFieldNode>];
391
- export type HavingNode = ['Having', BooleanTypeNodes];
392
- type OrderBy = ['ASC' | 'DESC', AnyTypeNodes];
393
- export type OrderByNode = ['OrderBy', ...OrderBy[]];
394
- export type LimitNode = ['Limit', NumberTypeNodes];
395
- export type OffsetNode = ['Offset', NumberTypeNodes];
396
- export type FieldsNode = ['Fields', string[]];
397
- export type ValuesNode = [
398
- 'Values',
399
- SelectQueryNode | UnionQueryNode | ValuesNodeTypes[],
400
- ];
401
- export type ValuesNodeTypes =
402
- | 'Default'
403
- | NullNode
404
- | BindNode
405
- | TextNode
406
- | EmbeddedTextNode
407
- | NumberNode
408
- | IntegerNode
409
- | RealNode
410
- | BooleanNode;
411
-
412
- export type AliasNode<T> = ['Alias', T, string];
413
-
414
- export type AnyTypeNodes =
415
- | DateTypeNodes
416
- | BooleanTypeNodes
417
- | NumberTypeNodes
418
- | TextTypeNodes
419
- | JSONTypeNodes
420
- | TextArrayTypeNodes
421
- | UnknownTypeNodes
422
- | DurationNode
423
- | InsertQueryNode
424
- | UpdateQueryNode
425
- | DeleteQueryNode
426
- | UpsertQueryNode
427
- | ValuesNode
428
- | SelectQueryStatementNode
429
- | FromTypeNodes
430
- | UnknownNode;
431
-
432
- export type AbstractSqlType = string | string[] | AnyTypeNodes;
433
-
434
- export type UnknownNode = AbstractSqlQuery;
435
- export interface AbstractSqlQuery extends Array<AbstractSqlType> {
436
- 0: string;
437
- }
438
-
439
- export interface AbstractSqlField {
440
- fieldName: string;
441
- dataType: string;
442
- required?: boolean;
443
- index?: string;
444
- references?: {
445
- resourceName: string;
446
- fieldName: string;
447
- type?: string;
448
- };
449
- defaultValue?: string;
450
- computed?: AnyTypeNodes | true;
451
- checks?: BooleanTypeNodes[];
452
- }
453
- export interface Trigger {
454
- operation: 'INSERT' | 'UPDATE' | 'DELETE' | 'TRUNCATE';
455
- fnName: string;
456
- level: 'ROW' | 'STATEMENT';
457
- when: 'BEFORE' | 'AFTER' | 'INSTEAD OF';
458
- }
459
- export interface Index {
460
- type: string;
461
- fields: string[];
462
- name?: string;
463
- /** For rules converted to partial unique indexes this holds the actual rule expression */
464
- description?: string;
465
- distinctNulls?: boolean;
466
- predicate?: BooleanTypeNodes;
467
- }
468
- export interface Check {
469
- description?: string;
470
- name?: string;
471
- abstractSql: BooleanTypeNodes;
472
- }
473
- export interface BindVars extends Array<any> {
474
- [key: string]: any;
475
- }
476
- export interface Definition {
477
- binds?: BindVars;
478
- abstractSql: FromTypeNodes;
479
- }
480
- export interface AbstractSqlTable {
481
- name: string;
482
- resourceName: string;
483
- idField: string;
484
- fields: AbstractSqlField[];
485
- indexes: Index[];
486
- primitive: false | string;
487
- triggers?: Trigger[];
488
- checks?: Check[];
489
- viewDefinition?: Omit<Definition, 'binds'>;
490
- definition?: Definition;
491
- modifyFields?: AbstractSqlTable['fields'];
492
- modifyName?: AbstractSqlTable['name'];
493
- }
494
- export interface SqlRule {
495
- sql: string;
496
- bindings: Binding[];
497
- structuredEnglish: string;
498
- referencedFields?: ReferencedFields | undefined;
499
- ruleReferencedFields?: RuleReferencedFields | undefined;
500
- }
501
- /**
502
- * The RelationshipMapping can either describe a relationship to another term, or
503
- * a relationship to a local term (since simple terms are also defined and referenced).
504
- * A local term basically describes the fields of the term that are available.
505
- *
506
- * - RelationshipMapping[0] is the local field
507
- *
508
- * If this relationship points to a foreign term (a different table)
509
- *
510
- * - RelationshipMapping[1] is the reference to the other resource, that joins this resource
511
- * - RelationshipMapping[1][0] is the name of the other resource (or the other table)
512
- * - RelationshipMapping[1][1] is the name of the field on the other resource
513
- */
514
- export type RelationshipMapping = [string, [string, string]?];
515
- export interface RelationshipLeafNode {
516
- $: RelationshipMapping;
517
- }
518
- export interface RelationshipInternalNode {
519
- [resourceName: string]: Relationship;
520
- }
521
- export type Relationship = RelationshipLeafNode | RelationshipInternalNode;
522
- export interface AbstractSqlModel {
523
- synonyms: {
524
- [synonym: string]: string;
525
- };
526
- relationships: {
527
- [resourceName: string]: RelationshipInternalNode;
528
- };
529
- tables: {
530
- [resourceName: string]: AbstractSqlTable;
531
- };
532
- rules: Array<
533
- ['Rule', ['Body', AbstractSqlQuery], ['StructuredEnglish', string]]
534
- >;
535
- functions?: Record<
536
- string,
537
- {
538
- type: 'trigger';
539
- body: string;
540
- language: 'plpgsql';
541
- }
542
- >;
543
- lfInfo: {
544
- rules: {
545
- [key: string]: LfRuleInfo;
546
- };
547
- };
548
- }
549
- export interface LfRuleInfo {
550
- root: {
551
- table: string;
552
- alias: string;
553
- };
554
- }
555
- export interface SqlModel {
556
- synonyms: {
557
- [synonym: string]: string;
558
- };
559
- relationships: {
560
- [resourceName: string]: Relationship;
561
- };
562
- tables: {
563
- [resourceName: string]: AbstractSqlTable;
564
- };
565
- rules: SqlRule[];
566
- createSchema: string[];
567
- dropSchema: string[];
568
- }
569
-
570
- export interface EngineInstance {
571
- compileSchema: (abstractSqlModel: AbstractSqlModel) => SqlModel;
572
- compileRule: (
573
- abstractSQL: AbstractSqlQuery,
574
- ) => SqlResult | [SqlResult, SqlResult];
575
- dataTypeValidate: (
576
- value: any,
577
- field: Pick<AbstractSqlField, 'dataType' | 'required'>,
578
- ) => Promise<any>;
579
- getReferencedFields: (ruleBody: AbstractSqlQuery) => ReferencedFields;
580
- /**
581
- * This gets referenced fields for a query that is expected to always return true and only return fields that could change it to false
582
- */
583
- getRuleReferencedFields: (ruleBody: AbstractSqlQuery) => RuleReferencedFields;
584
- getModifiedFields: (
585
- abstractSqlQuery: AbstractSqlQuery,
586
- ) => undefined | ModifiedFields | Array<undefined | ModifiedFields>;
587
- }
588
-
589
- type ValidateTypes = {
590
- [key in keyof typeof sbvrTypes]: (typeof sbvrTypes)[key]['validate'];
591
- };
592
- const validateTypes: ValidateTypes = (() => {
593
- const result: Partial<ValidateTypes> = {};
594
- for (const key of Object.keys(sbvrTypes) as Array<keyof typeof sbvrTypes>) {
595
- result[key] = sbvrTypes[key].validate as any;
596
- }
597
- return result as ValidateTypes;
598
- })();
599
- const dataTypeValidate: EngineInstance['dataTypeValidate'] = async (
600
- value,
601
- field,
602
- ) => {
603
- // In case one of the validation types throws an error.
604
- const { dataType, required = false } = field;
605
- // Without the `: SbvrType['validate']` widening TS complains that
606
- // "none of those signatures are compatible with each other".
607
- const validateFn: SbvrType['validate'] =
608
- validateTypes[dataType as keyof typeof sbvrTypes];
609
- if (validateFn != null) {
610
- return validateFn(value, required);
611
- } else {
612
- return new Error('is an unsupported type: ' + dataType);
613
- }
614
- };
615
-
616
- const dataTypeGen = (
617
- engine: Engines,
618
- { dataType, required, index, defaultValue, checks }: AbstractSqlField,
619
- ): string => {
620
- let requiredStr;
621
- if (required) {
622
- requiredStr = ' NOT NULL';
623
- } else {
624
- requiredStr = ' NULL';
625
- }
626
- if (defaultValue != null) {
627
- defaultValue = ` DEFAULT ${defaultValue}`;
628
- } else {
629
- defaultValue = '';
630
- }
631
- let checksString = '';
632
- if (checks != null) {
633
- checksString = checks
634
- .map((check) => {
635
- return ` CHECK (${compileRule(
636
- check as AbstractSqlQuery,
637
- engine,
638
- true,
639
- )})`;
640
- })
641
- .join('');
642
- }
643
- if (index == null) {
644
- index = '';
645
- } else if (index !== '') {
646
- index = ' ' + index;
647
- }
648
- const dbType =
649
- sbvrTypes?.[dataType as keyof typeof sbvrTypes]?.types?.[engine];
650
- if (dbType != null) {
651
- if (typeof dbType === 'function') {
652
- return dbType(requiredStr, index);
653
- }
654
- return dbType + defaultValue + requiredStr + checksString + index;
655
- } else {
656
- throw new Error(`Unknown data type '${dataType}' for engine: ${engine}`);
657
- }
658
- };
659
-
660
- export const isAliasNode = <T>(
661
- n: AliasNode<T> | AbstractSqlType,
662
- ): n is AliasNode<T> => n[0] === 'Alias';
663
- export const isFromNode = (n: AbstractSqlType): n is FromNode =>
664
- n[0] === 'From';
665
- export const isTableNode = (n: AbstractSqlType): n is TableNode =>
666
- n[0] === 'Table';
667
- export const isResourceNode = (n: AbstractSqlType): n is ResourceNode =>
668
- n[0] === 'Resource';
669
- export const isSelectQueryNode = (n: AbstractSqlType): n is SelectQueryNode =>
670
- n[0] === 'SelectQuery';
671
- export const isSelectNode = (n: AbstractSqlType): n is SelectNode =>
672
- n[0] === 'Select';
673
- export const isWhereNode = (n: AbstractSqlType): n is WhereNode =>
674
- n[0] === 'Where';
675
- export const isFieldTypeNode = (
676
- n: AbstractSqlType,
677
- ): n is FieldNode | ReferencedFieldNode =>
678
- n[0] === 'ReferencedField' || n[0] === 'Field';
679
-
680
- /**
681
- *
682
- * @param n The abstract sql to check
683
- * @param checkNodeTypeFn A function that checks if a given node is the correct type
684
- */
685
- export const abstractSqlContainsNode = (
686
- n: AnyTypeNodes,
687
- checkNodeTypeFn: (n: AnyTypeNodes) => boolean,
688
- ): boolean => {
689
- if (checkNodeTypeFn(n)) {
690
- return true;
691
- }
692
- for (const p of n) {
693
- if (
694
- Array.isArray(p) &&
695
- abstractSqlContainsNode(p as AnyTypeNodes, checkNodeTypeFn)
696
- ) {
697
- return true;
698
- }
699
- }
700
- return false;
701
- };
702
-
703
- export function compileRule(
704
- abstractSQL: UpsertQueryNode,
705
- engine: Engines,
706
- noBinds: true,
707
- ): [string, string];
708
- export function compileRule(
709
- abstractSQL: AbstractSqlQuery,
710
- engine: Engines,
711
- noBinds: true,
712
- ): string;
713
- export function compileRule(
714
- abstractSQL: UpsertQueryNode,
715
- engine: Engines,
716
- noBinds?: false,
717
- ): [SqlResult, SqlResult];
718
- export function compileRule(
719
- abstractSQL:
720
- | SelectQueryNode
721
- | UnionQueryNode
722
- | InsertQueryNode
723
- | UpdateQueryNode
724
- | DeleteQueryNode,
725
- engine: Engines,
726
- noBinds?: false,
727
- ): SqlResult;
728
- export function compileRule(
729
- abstractSQL: AbstractSqlQuery,
730
- engine: Engines,
731
- noBinds?: false,
732
- ): SqlResult | [SqlResult, SqlResult];
733
- export function compileRule(
734
- abstractSQL: AbstractSqlQuery,
735
- engine: Engines,
736
- noBinds?: boolean,
737
- ): SqlResult | [SqlResult, SqlResult] | string;
738
- export function compileRule(
739
- abstractSQL: AbstractSqlQuery,
740
- engine: Engines,
741
- noBinds = false,
742
- ): SqlResult | [SqlResult, SqlResult] | string | [string, string] {
743
- abstractSQL = AbstractSQLOptimizer(abstractSQL, noBinds);
744
- return AbstractSQLRules2SQL(abstractSQL, engine, noBinds);
745
- }
746
-
747
- const compileSchema = (
748
- abstractSqlModel: AbstractSqlModel,
749
- engine: Engines,
750
- ifNotExists: boolean,
751
- ): SqlModel => {
752
- abstractSqlModel = optimizeSchema(abstractSqlModel, {
753
- createCheckConstraints: false,
754
- });
755
-
756
- let ifNotExistsStr = '';
757
- let orReplaceStr = '';
758
- if (ifNotExists) {
759
- ifNotExistsStr = 'IF NOT EXISTS ';
760
- orReplaceStr = 'OR REPLACE ';
761
- }
762
-
763
- const createSchemaStatements: string[] = [];
764
- const alterSchemaStatements: string[] = [];
765
- let dropSchemaStatements: string[] = [];
766
-
767
- const fns: Record<string, true> = {};
768
- if (abstractSqlModel.functions) {
769
- for (const fnName of Object.keys(abstractSqlModel.functions)) {
770
- const fnDefinition = abstractSqlModel.functions[fnName];
771
- if (engine !== Engines.postgres) {
772
- throw new Error('Functions are only supported on postgres currently');
773
- }
774
- if (fnDefinition.language !== 'plpgsql') {
775
- throw new Error('Only plpgsql functions currently supported');
776
- }
777
- if (fnDefinition.type !== 'trigger') {
778
- throw new Error('Only trigger functions currently supported');
779
- }
780
- fns[fnName] = true;
781
- createSchemaStatements.push(`\
782
- DO $$
783
- BEGIN
784
- CREATE FUNCTION "${fnName}"()
785
- RETURNS TRIGGER AS $fn$
786
- BEGIN
787
- ${fnDefinition.body}
788
- END;
789
- $fn$ LANGUAGE ${fnDefinition.language};
790
- EXCEPTION WHEN duplicate_function THEN
791
- NULL;
792
- END;
793
- $$;`);
794
- dropSchemaStatements.push(`DROP FUNCTION "${fnName}"();`);
795
- }
796
- }
797
-
798
- const hasDependants: {
799
- [dependant: string]: true;
800
- } = {};
801
- const schemaDependencyMap: {
802
- [resourceName: string]: {
803
- resourceName: string;
804
- primitive: AbstractSqlTable['primitive'];
805
- createSQL: string[];
806
- dropSQL: string[];
807
- depends: string[];
808
- };
809
- } = {};
810
- Object.keys(abstractSqlModel.tables).forEach((resourceName) => {
811
- const table = abstractSqlModel.tables[resourceName];
812
- if (typeof table === 'string') {
813
- return;
814
- }
815
- const { definition, viewDefinition } = table;
816
- if (viewDefinition != null && definition != null) {
817
- throw new Error('Cannot have both a definition and a viewDefinition');
818
- }
819
- if (definition != null || viewDefinition != null) {
820
- if (
821
- table.fields.some(
822
- ({ computed }) => computed != null && computed !== true,
823
- )
824
- ) {
825
- throw new Error(
826
- `Using computed fields alongside a custom table definition is unsupported, found for table resourceName: '${table.resourceName}', name: '${table.name}'`,
827
- );
828
- }
829
- }
830
- if (viewDefinition != null) {
831
- let definitionAbstractSql = viewDefinition.abstractSql;
832
- // If there are any resource nodes then it's a dynamic definition and cannot become a view
833
- if (abstractSqlContainsNode(definitionAbstractSql, isResourceNode)) {
834
- throw new Error(
835
- `Cannot create a view from a dynamic viewDefinition, found for table resourceName: '${table.resourceName}', name: '${table.name}'`,
836
- );
837
- }
838
- if (isTableNode(definitionAbstractSql)) {
839
- // If the definition is a table node we need to wrap it in a select query for the view creation
840
- definitionAbstractSql = [
841
- 'SelectQuery',
842
- ['Select', [['Field', '*']]],
843
- ['From', definitionAbstractSql],
844
- ];
845
- }
846
- schemaDependencyMap[table.resourceName] = {
847
- resourceName,
848
- primitive: table.primitive,
849
- createSQL: [
850
- `\
851
- CREATE ${orReplaceStr}VIEW "${table.name}" AS (
852
- ${compileRule(definitionAbstractSql as AbstractSqlQuery, engine, true).replace(
853
- /^/gm,
854
- ' ',
855
- )}
856
- );`,
857
- ],
858
- dropSQL: [`DROP VIEW "${table.name}";`],
859
- depends: [],
860
- };
861
- return;
862
- }
863
- const foreignKeys: string[] = [];
864
- const depends: string[] = [];
865
- const createSqlElements: string[] = [];
866
- const createIndexes: string[] = [];
867
-
868
- for (const field of table.fields) {
869
- const { fieldName, references, dataType, computed } = field;
870
- if (!computed) {
871
- createSqlElements.push(
872
- '"' + fieldName + '" ' + dataTypeGen(engine, field),
873
- );
874
- if (
875
- ['ForeignKey', 'ConceptType'].includes(dataType) &&
876
- references != null
877
- ) {
878
- const referencedTable =
879
- abstractSqlModel.tables[references.resourceName];
880
- const fkDefinition = `FOREIGN KEY ("${fieldName}") REFERENCES "${referencedTable.name}" ("${references.fieldName}")`;
881
-
882
- const schemaInfo = schemaDependencyMap[references.resourceName];
883
- if (schemaInfo?.depends.includes(table.resourceName)) {
884
- if (engine !== Engines.postgres) {
885
- throw new Error(
886
- 'Circular dependencies are only supported on postgres currently',
887
- );
888
- }
889
- // It's a simple circular dependency so we switch it to an ALTER TABLE
890
- alterSchemaStatements.push(`\
891
- DO $$
892
- BEGIN
893
- IF NOT EXISTS (
894
- SELECT 1
895
- FROM information_schema.table_constraints tc
896
- JOIN information_schema.key_column_usage kcu USING (constraint_catalog, constraint_schema, constraint_name)
897
- JOIN information_schema.constraint_column_usage ccu USING (constraint_catalog, constraint_schema, constraint_name)
898
- WHERE constraint_type = 'FOREIGN KEY'
899
- AND tc.table_schema = CURRENT_SCHEMA()
900
- AND tc.table_name = '${table.name}'
901
- AND kcu.column_name = '${fieldName}'
902
- AND ccu.table_schema = CURRENT_SCHEMA()
903
- AND ccu.table_name = '${referencedTable.name}'
904
- AND ccu.column_name = '${references.fieldName}'
905
- ) THEN
906
- ALTER TABLE "${table.name}"
907
- ADD CONSTRAINT "${table.name}_${fieldName}_fkey"
908
- ${fkDefinition};
909
- END IF;
910
- END;
911
- $$;`);
912
- } else {
913
- if (references.type !== 'informative') {
914
- foreignKeys.push(fkDefinition);
915
- }
916
- depends.push(references.resourceName);
917
- hasDependants[references.resourceName] = true;
918
- }
919
- }
920
- }
921
- }
922
-
923
- createSqlElements.push(...foreignKeys);
924
- for (const index of table.indexes) {
925
- let nullsSql = '';
926
- if (index.distinctNulls != null) {
927
- nullsSql =
928
- index.distinctNulls === false
929
- ? ` NULLS NOT DISTINCT`
930
- : ` NULLS DISTINCT`;
931
- }
932
- // Non-partial indexes are added directly to the CREATE TABLE statement
933
- if (index.predicate == null) {
934
- createSqlElements.push(
935
- index.type + nullsSql + '("' + index.fields.join('", "') + '")',
936
- );
937
- continue;
938
- }
939
- if (index.name == null) {
940
- throw new Error('No name provided for partial index');
941
- }
942
- const comment = index.description
943
- ? `-- ${index.description.replaceAll(/\r?\n/g, '\n-- ')}\n`
944
- : '';
945
- const whereSql = compileRule(
946
- index.predicate as AbstractSqlQuery,
947
- engine,
948
- true,
949
- );
950
- createIndexes.push(`\
951
- ${comment}\
952
- CREATE ${index.type} INDEX IF NOT EXISTS "${index.name}"
953
- ON "${table.name}" ("${index.fields.join('", "')}")${nullsSql}
954
- WHERE (${whereSql});`);
955
- }
956
-
957
- if (table.checks) {
958
- for (const check of table.checks) {
959
- const comment = check.description
960
- ? `-- ${check.description.split(/\r?\n/).join('\n-- ')}\n`
961
- : '';
962
- const constraintName = check.name ? `CONSTRAINT "${check.name}" ` : '';
963
- const sql = compileRule(
964
- check.abstractSql as AbstractSqlQuery,
965
- engine,
966
- true,
967
- );
968
- createSqlElements.push(`\
969
- ${comment}${constraintName}CHECK (${sql})`);
970
- }
971
- }
972
-
973
- const createTriggers: string[] = [];
974
- const dropTriggers: string[] = [];
975
- if (table.triggers) {
976
- for (const trigger of table.triggers) {
977
- if (!fns[trigger.fnName]) {
978
- throw new Error(`No such function '${trigger.fnName}' declared`);
979
- }
980
- // Trim the trigger name to a max of 63 characters
981
- const triggerName = `${table.name}_${trigger.fnName}`.slice(0, 63);
982
- createTriggers.push(`\
983
- DO
984
- $$
985
- BEGIN
986
- IF NOT EXISTS(
987
- SELECT 1
988
- FROM "information_schema"."triggers"
989
- WHERE "event_object_table" = '${table.name}'
990
- AND "trigger_name" = '${triggerName}'
991
- ) THEN
992
- CREATE TRIGGER "${triggerName}"
993
- ${trigger.when} ${trigger.operation} ON "${table.name}"
994
- FOR EACH ${trigger.level}
995
- EXECUTE PROCEDURE "${trigger.fnName}"();
996
- END IF;
997
- END;
998
- $$;`);
999
- dropTriggers.push(`DROP TRIGGER "${triggerName}";`);
1000
- }
1001
- }
1002
-
1003
- schemaDependencyMap[table.resourceName] = {
1004
- resourceName,
1005
- primitive: table.primitive,
1006
- createSQL: [
1007
- `\
1008
- CREATE TABLE ${ifNotExistsStr}"${table.name}" (
1009
- ${createSqlElements.join('\n,\t')}
1010
- );`,
1011
- ...createIndexes,
1012
- ...createTriggers,
1013
- ],
1014
- dropSQL: [...dropTriggers, `DROP TABLE "${table.name}";`],
1015
- depends,
1016
- };
1017
- });
1018
-
1019
- let resourceNames: string[] = [];
1020
- while (
1021
- resourceNames.length !==
1022
- (resourceNames = Object.keys(schemaDependencyMap)).length &&
1023
- resourceNames.length > 0
1024
- ) {
1025
- for (const resourceName of resourceNames) {
1026
- const schemaInfo = schemaDependencyMap[resourceName];
1027
- let unsolvedDependency = false;
1028
- for (const dependency of schemaInfo.depends) {
1029
- // Self-dependencies are ok.
1030
- if (
1031
- dependency !== resourceName &&
1032
- Object.prototype.hasOwnProperty.call(schemaDependencyMap, dependency)
1033
- ) {
1034
- unsolvedDependency = true;
1035
- break;
1036
- }
1037
- }
1038
- if (unsolvedDependency === false) {
1039
- if (
1040
- schemaInfo.primitive === false ||
1041
- hasDependants[resourceName] != null
1042
- ) {
1043
- if (schemaInfo.primitive !== false) {
1044
- console.warn(
1045
- "We're adding a primitive table??",
1046
- schemaInfo.resourceName,
1047
- );
1048
- }
1049
- createSchemaStatements.push(...schemaInfo.createSQL);
1050
- dropSchemaStatements.push(...schemaInfo.dropSQL);
1051
- }
1052
- delete schemaDependencyMap[resourceName];
1053
- }
1054
- }
1055
- }
1056
- if (Object.keys(schemaDependencyMap).length > 0) {
1057
- console.error(
1058
- 'Failed to resolve all schema dependencies',
1059
- schemaDependencyMap,
1060
- );
1061
- throw new Error('Failed to resolve all schema dependencies');
1062
- }
1063
- createSchemaStatements.push(...alterSchemaStatements);
1064
- dropSchemaStatements = dropSchemaStatements.reverse();
1065
-
1066
- const ruleStatements: SqlRule[] = abstractSqlModel.rules.map(
1067
- (rule): SqlRule => {
1068
- const [, ruleBodyNode, ruleSENode] = rule;
1069
- if (ruleBodyNode == null || ruleBodyNode[0] !== 'Body') {
1070
- throw new Error('Invalid rule');
1071
- }
1072
- const ruleBody = ruleBodyNode[1];
1073
- if (typeof ruleBody === 'string') {
1074
- throw new Error('Invalid rule');
1075
- }
1076
- if (ruleSENode == null || ruleSENode[0] !== 'StructuredEnglish') {
1077
- throw new Error('Invalid structured English');
1078
- }
1079
- const ruleSE = ruleSENode[1];
1080
- if (typeof ruleSE !== 'string') {
1081
- throw new Error('Invalid structured English');
1082
- }
1083
- insertAffectedIdsBinds(ruleBody, abstractSqlModel.lfInfo.rules[ruleSE]);
1084
- const { query: ruleSQL, bindings: ruleBindings } = compileRule(
1085
- ruleBody,
1086
- engine,
1087
- ) as SqlResult;
1088
- let referencedFields: ReferencedFields | undefined;
1089
- try {
1090
- referencedFields = getReferencedFields(ruleBody);
1091
- } catch (e) {
1092
- console.warn('Error fetching referenced fields', e);
1093
- }
1094
- let ruleReferencedFields: RuleReferencedFields | undefined;
1095
- try {
1096
- ruleReferencedFields = getRuleReferencedFields(ruleBody);
1097
- } catch (e) {
1098
- console.warn('Error fetching rule referenced fields', e);
1099
- }
1100
-
1101
- return {
1102
- structuredEnglish: ruleSE,
1103
- sql: ruleSQL,
1104
- bindings: ruleBindings,
1105
- referencedFields,
1106
- ruleReferencedFields,
1107
- };
1108
- },
1109
- );
1110
-
1111
- return {
1112
- synonyms: abstractSqlModel.synonyms,
1113
- relationships: abstractSqlModel.relationships,
1114
- tables: abstractSqlModel.tables,
1115
- createSchema: createSchemaStatements,
1116
- dropSchema: dropSchemaStatements,
1117
- rules: ruleStatements,
1118
- };
1119
- };
1120
-
1121
- const generateExport = (engine: Engines, ifNotExists: boolean) => {
1122
- return {
1123
- optimizeSchema,
1124
- generateRuleSlug,
1125
- compileSchema: (abstractSqlModel: AbstractSqlModel) =>
1126
- compileSchema(abstractSqlModel, engine, ifNotExists),
1127
- compileRule: (abstractSQL: AbstractSqlQuery) =>
1128
- compileRule(abstractSQL, engine, false),
1129
- dataTypeValidate,
1130
- getReferencedFields,
1131
- getRuleReferencedFields,
1132
- getModifiedFields,
1133
- };
1134
- };
1135
- export { optimizeSchema };
1136
- export const postgres = generateExport(Engines.postgres, true);
1137
- export const mysql = generateExport(Engines.mysql, true);
1138
- export const websql = generateExport(Engines.websql, false);