@sap/cds-compiler 2.10.4 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/bin/.eslintrc.json +1 -2
  3. package/bin/cds_update_identifiers.js +10 -8
  4. package/bin/cdsc.js +58 -35
  5. package/bin/cdsse.js +1 -0
  6. package/bin/cdsv2m.js +3 -2
  7. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  8. package/doc/CHANGELOG_BETA.md +16 -0
  9. package/lib/api/.eslintrc.json +2 -0
  10. package/lib/api/main.js +10 -36
  11. package/lib/api/options.js +17 -8
  12. package/lib/api/validate.js +30 -3
  13. package/lib/backends.js +12 -13
  14. package/lib/base/dictionaries.js +2 -1
  15. package/lib/base/keywords.js +3 -2
  16. package/lib/base/message-registry.js +64 -11
  17. package/lib/base/messages.js +38 -18
  18. package/lib/base/model.js +6 -4
  19. package/lib/base/optionProcessorHelper.js +148 -86
  20. package/lib/checks/.eslintrc.json +2 -0
  21. package/lib/checks/actionsFunctions.js +2 -1
  22. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  23. package/lib/checks/foreignKeys.js +4 -4
  24. package/lib/checks/managedInType.js +4 -4
  25. package/lib/checks/queryNoDbArtifacts.js +1 -3
  26. package/lib/checks/selectItems.js +4 -0
  27. package/lib/checks/sql-snippets.js +93 -0
  28. package/lib/checks/unknownMagic.js +6 -3
  29. package/lib/checks/validator.js +8 -0
  30. package/lib/compiler/assert-consistency.js +14 -5
  31. package/lib/compiler/base.js +64 -0
  32. package/lib/compiler/builtins.js +62 -16
  33. package/lib/compiler/checks.js +34 -10
  34. package/lib/compiler/definer.js +91 -112
  35. package/lib/compiler/index.js +30 -30
  36. package/lib/compiler/propagator.js +8 -4
  37. package/lib/compiler/resolver.js +279 -63
  38. package/lib/compiler/shared.js +65 -230
  39. package/lib/compiler/utils.js +191 -0
  40. package/lib/edm/annotations/genericTranslation.js +35 -18
  41. package/lib/edm/annotations/preprocessAnnotations.js +1 -1
  42. package/lib/edm/csn2edm.js +4 -3
  43. package/lib/edm/edm.js +8 -8
  44. package/lib/edm/edmPreprocessor.js +61 -59
  45. package/lib/edm/edmUtils.js +14 -15
  46. package/lib/gen/Dictionary.json +82 -40
  47. package/lib/gen/language.checksum +1 -1
  48. package/lib/gen/language.interp +19 -1
  49. package/lib/gen/language.tokens +80 -73
  50. package/lib/gen/languageLexer.interp +27 -1
  51. package/lib/gen/languageLexer.js +925 -826
  52. package/lib/gen/languageLexer.tokens +72 -65
  53. package/lib/gen/languageParser.js +4817 -4102
  54. package/lib/json/from-csn.js +57 -26
  55. package/lib/json/to-csn.js +244 -51
  56. package/lib/language/antlrParser.js +12 -1
  57. package/lib/language/docCommentParser.js +1 -1
  58. package/lib/language/errorStrategy.js +26 -8
  59. package/lib/language/genericAntlrParser.js +106 -30
  60. package/lib/language/language.g4 +200 -70
  61. package/lib/language/multiLineStringParser.js +536 -0
  62. package/lib/main.d.ts +220 -21
  63. package/lib/main.js +6 -3
  64. package/lib/model/api.js +2 -2
  65. package/lib/model/csnRefs.js +218 -86
  66. package/lib/model/csnUtils.js +99 -178
  67. package/lib/model/enrichCsn.js +84 -43
  68. package/lib/model/revealInternalProperties.js +25 -8
  69. package/lib/model/sortViews.js +8 -1
  70. package/lib/modelCompare/compare.js +2 -1
  71. package/lib/optionProcessor.js +33 -18
  72. package/lib/render/.eslintrc.json +1 -2
  73. package/lib/render/DuplicateChecker.js +2 -2
  74. package/lib/render/manageConstraints.js +1 -1
  75. package/lib/render/toCdl.js +202 -82
  76. package/lib/render/toHdbcds.js +194 -135
  77. package/lib/render/toRename.js +7 -10
  78. package/lib/render/toSql.js +91 -51
  79. package/lib/render/utils/common.js +24 -5
  80. package/lib/render/utils/sql.js +6 -4
  81. package/lib/transform/braceExpression.js +4 -2
  82. package/lib/transform/db/applyTransformations.js +189 -0
  83. package/lib/transform/db/associations.js +389 -0
  84. package/lib/transform/db/cdsPersistence.js +150 -0
  85. package/lib/transform/db/constraints.js +275 -119
  86. package/lib/transform/db/draft.js +6 -4
  87. package/lib/transform/db/expansion.js +10 -9
  88. package/lib/transform/db/flattening.js +23 -8
  89. package/lib/transform/db/temporal.js +236 -0
  90. package/lib/transform/db/transformExists.js +106 -25
  91. package/lib/transform/db/views.js +485 -0
  92. package/lib/transform/forHanaNew.js +90 -1036
  93. package/lib/transform/forOdataNew.js +11 -3
  94. package/lib/transform/localized.js +5 -14
  95. package/lib/transform/odata/generateForeignKeyElements.js +2 -2
  96. package/lib/transform/transformUtilsNew.js +34 -20
  97. package/lib/transform/translateAssocsToJoins.js +15 -23
  98. package/lib/transform/universalCsnEnricher.js +217 -47
  99. package/lib/utils/file.js +13 -6
  100. package/lib/utils/term.js +65 -42
  101. package/lib/utils/timetrace.js +55 -27
  102. package/package.json +1 -1
  103. package/lib/transform/db/helpers.js +0 -58
@@ -98,6 +98,8 @@ function assertConsistency( model, stage ) {
98
98
  '$blocks',
99
99
  '$newfeatures',
100
100
  '$messageFunctions',
101
+ '$functions',
102
+ '$volatileFunctions',
101
103
  ],
102
104
  },
103
105
  ':parser': { // top-level from parser
@@ -247,7 +249,7 @@ function assertConsistency( model, stage ) {
247
249
  'name', '$parens', 'quantifier', 'mixin', 'excludingDict', 'columns', 'elements', '_deps',
248
250
  'where', 'groupBy', 'having', 'orderBy', '$orderBy', 'limit',
249
251
  '_projections', '_block', '_parent', '_main', '_effectiveType', '$expand',
250
- '$tableAliases', 'kind', '_$next', '_combined', '$inlines',
252
+ '$tableAliases', 'kind', '_$next', '_combined', '$inlines', '_status',
251
253
  ],
252
254
  },
253
255
  none: { optional: () => true }, // parse error
@@ -422,9 +424,12 @@ function assertConsistency( model, stage ) {
422
424
  val: {
423
425
  test: isVal, // the following for array/struct value
424
426
  requires: [ 'location' ],
425
- optional: [ 'literal', 'val', 'sym', 'struct', 'variant', 'path', 'name', '$duplicate' ],
427
+ optional: [
428
+ 'literal', 'val', 'sym', 'struct', 'variant', 'path', 'name', '$duplicate', 'upTo',
429
+ ],
426
430
  // TODO: restrict path to #simplePath
427
431
  },
432
+ upTo: { test: TODO },
428
433
  struct: { inherits: 'val', test: isDictionary( definition ) }, // def because double @
429
434
  args: {
430
435
  inherits: 'value',
@@ -479,6 +484,7 @@ function assertConsistency( model, stage ) {
479
484
  },
480
485
  items: {
481
486
  kind: true,
487
+ also: [ 0 ], // 0 for cyclic expansions
482
488
  requires: [ 'location' ],
483
489
  optional: [
484
490
  'enum',
@@ -539,7 +545,7 @@ function assertConsistency( model, stage ) {
539
545
  // query specific
540
546
  'where', 'columns', 'mixin', 'quantifier', 'offset',
541
547
  'orderBy', '$orderBy', 'groupBy', 'excludingDict', 'having',
542
- 'limit',
548
+ 'limit', '_status',
543
549
  ],
544
550
  },
545
551
  _leadingQuery: { kind: true, test: TODO },
@@ -588,6 +594,8 @@ function assertConsistency( model, stage ) {
588
594
  $sources: { parser: true, test: isArray( isString ) },
589
595
  $expected: { parser: true, test: isString },
590
596
  $messageFunctions: { test: TODO },
597
+ $functions: { test: TODO },
598
+ $volatileFunctions: { test: TODO },
591
599
  };
592
600
  let _noSyntaxErrors = null;
593
601
  assertProp( model, null, stageParser ? ':parser' : ':model', null, true );
@@ -659,6 +667,8 @@ function assertConsistency( model, stage ) {
659
667
  }
660
668
 
661
669
  function standard( node, parent, prop, spec, name ) {
670
+ if (spec.also && spec.also.includes( node ))
671
+ return;
662
672
  isObject( node, parent, prop, spec, name );
663
673
 
664
674
  const names = Object.getOwnPropertyNames( node );
@@ -785,8 +795,7 @@ function assertConsistency( model, stage ) {
785
795
  }
786
796
 
787
797
  function at( nodes, prop, name ) {
788
- // eslint-disable-next-line no-nested-ternary
789
- const n = name ? (typeof name === 'number' ? ` for index ${ name }` : ` for "${ name }"`) : '';
798
+ const n = name && (typeof name === 'number' ? ` for index ${ name }` : ` for "${ name }"`) || '';
790
799
  const loc = nodes.find( o => o && typeof o === 'object' && (o.location || o.start) );
791
800
  const f = (prop) ? `${ n } in property '${ prop }'` : n;
792
801
  const l = locationString( loc && loc.location || loc || model.location );
@@ -0,0 +1,64 @@
1
+ // Base Definitions for the Core Compiler
2
+
3
+ 'use strict';
4
+
5
+ const dictKinds = {
6
+ definitions: 'absolute',
7
+ elements: 'element',
8
+ enum: 'enum',
9
+ foreignKeys: 'key',
10
+ actions: 'action',
11
+ params: 'param',
12
+ };
13
+
14
+ const kindProperties = {
15
+ // TODO: also foreignKeys ?
16
+ namespace: { artifacts: true }, // on-the-fly context
17
+ context: { artifacts: true, normalized: 'namespace' },
18
+ service: { artifacts: true, normalized: 'namespace' },
19
+ entity: { elements: true, actions: true, params: () => false },
20
+ select: { normalized: 'select', elements: true },
21
+ $join: { normalized: 'select' },
22
+ $tableAlias: { normalized: 'alias' }, // table alias in select
23
+ $self: { normalized: 'alias' }, // table alias in select
24
+ $navElement: { normalized: 'element' },
25
+ $inline: { normalized: 'element' }, // column with inline property
26
+ event: { elements: true },
27
+ type: { elements: propExists, enum: propExists },
28
+ aspect: { elements: propExists },
29
+ annotation: { elements: propExists, enum: propExists },
30
+ enum: { normalized: 'element' },
31
+ element: { elements: propExists, enum: propExists, dict: 'elements' },
32
+ mixin: { normalized: 'alias' },
33
+ action: {
34
+ params: () => false, elements: () => false, enum: () => false, dict: 'actions',
35
+ }, // no extend params, only annotate
36
+ function: {
37
+ params: () => false, elements: () => false, enum: () => false, normalized: 'action',
38
+ }, // no extend params, only annotate
39
+ key: { normalized: 'element' },
40
+ param: { elements: () => false, enum: () => false, dict: 'params' },
41
+ source: { artifacts: true }, // TODO -> $source
42
+ using: {},
43
+ extend: {
44
+ isExtension: true,
45
+ noDep: 'special',
46
+ elements: true, /* only for parse-cdl */
47
+ actions: true, /* only for parse-cdl */
48
+ },
49
+ annotate: {
50
+ isExtension: true, noDep: 'special', elements: true, enum: true, actions: true, params: true,
51
+ },
52
+ builtin: {}, // = CURRENT_DATE, TODO: improve
53
+ $parameters: {}, // $parameters in query entities
54
+ };
55
+
56
+ function propExists( prop, parent ) {
57
+ const obj = parent.returns || parent;
58
+ return (obj.items || obj.targetAspect || obj)[prop];
59
+ }
60
+
61
+ module.exports = {
62
+ dictKinds,
63
+ kindProperties,
64
+ };
@@ -2,9 +2,8 @@
2
2
 
3
3
  'use strict';
4
4
 
5
- const { forEachInDict } = require('../base/dictionaries');
6
5
  const { builtinLocation } = require('../base/location');
7
- const { setProp } = require('../base/model');
6
+ const { setProp } = require('./utils');
8
7
 
9
8
  const core = {
10
9
  String: { parameters: [ 'length' ], category: 'string' },
@@ -82,18 +81,19 @@ const specialFunctions = {
82
81
  */
83
82
  const magicVariables = {
84
83
  $user: {
84
+ // id and locale are always available
85
85
  elements: { id: {}, locale: {} },
86
86
  // Allow $user.<any>
87
87
  $uncheckedElements: true,
88
88
  // Allow shortcut in CDL: `$user` becomes `$user.id` in CSN.
89
89
  $autoElement: 'id',
90
- }, // CDS-specific, not part of SQL
91
- $at: {
90
+ },
91
+ $at: { // CDS-specific, not part of SQL
92
92
  elements: {
93
93
  from: {}, to: {},
94
94
  },
95
95
  },
96
- $now: {}, // Dito
96
+ $now: {}, // Dito
97
97
  $session: {
98
98
  // In ABAP CDS session variables are accessed in a generic way via
99
99
  // the pseudo variable $session.
@@ -167,6 +167,31 @@ function isRelationTypeName(typeName) {
167
167
  return typeCategories.relation.includes(typeName);
168
168
  }
169
169
 
170
+ /**
171
+ * Checks whether the given absolute path is inside a reserved namespace.
172
+ *
173
+ * @param {string} absolute
174
+ * @returns {boolean}
175
+ */
176
+ function isInReservedNamespace(absolute) {
177
+ return absolute.startsWith( 'cds.') &&
178
+ !absolute.match(/^cds\.foundation(\.|$)/) &&
179
+ !absolute.match(/^cds\.outbox(\.|$)/); // Requested by Node runtime
180
+ }
181
+
182
+ /**
183
+ * Tell if a type is (directly) a builtin type
184
+ * Note that in CSN builtins are not in the definition of the model, so we can only
185
+ * check against their absolute names. Builtin types are "cds.<something>", i.e. they
186
+ * are directly in 'cds', but not for example in 'cds.foundation'.
187
+ *
188
+ * @param {string} type
189
+ * @returns {boolean}
190
+ */
191
+ function isBuiltinType(type) {
192
+ return typeof type === 'string' && isInReservedNamespace(type);
193
+ }
194
+
170
195
  /**
171
196
  * Add CDS builtins like the `cds` namespace with types like `cds.Integer` to
172
197
  * `definitions` of the XSN model as well as to `$builtins`.
@@ -174,6 +199,7 @@ function isRelationTypeName(typeName) {
174
199
  * @param {XSN.Model} model XSN model without CDS builtins
175
200
  */
176
201
  function initBuiltins( model ) {
202
+ const { options } = model;
177
203
  setMagicVariables( magicVariables );
178
204
  // namespace:"cds" stores the builtins ---
179
205
  const cds = createNamespace( 'cds', 'reserved' );
@@ -241,27 +267,45 @@ function initBuiltins( model ) {
241
267
  for (const name in builtins) {
242
268
  const magic = builtins[name];
243
269
  // TODO: rename to $builtinFunction
244
- const art = { kind: 'builtin', name: { id: name, element: name } };
270
+ const art = { kind: 'builtin', name: { element: name, id: name } };
245
271
  artifacts[name] = art;
246
- if (magic.elements)
247
- art.elements = forEachInDict( magic.elements, (e, n) => magicElement( e, n, art ));
272
+
248
273
  if (magic.$autoElement)
249
274
  art.$autoElement = magic.$autoElement;
250
275
  if (magic.$uncheckedElements)
251
276
  art.$uncheckedElements = magic.$uncheckedElements;
277
+
278
+ createMagicElements( art, magic.elements );
279
+ if (options.variableReplacements)
280
+ createMagicElements( art, options.variableReplacements[name] );
252
281
  // setProp( art, '_effectiveType', art );
253
282
  }
254
283
  model.$magicVariables = { kind: '$magicVariables', artifacts };
255
284
  }
256
285
 
257
- function magicElement( spec, name, parent ) {
258
- const magic = {
259
- kind: 'builtin',
260
- name: { id: name, element: `${ parent.name.element }.${ name }` },
261
- };
262
- setProp( magic, '_parent', parent );
263
- // setProp( magic, '_effectiveType', magic );
264
- return magic;
286
+ function createMagicElements( art, elements ) {
287
+ if (!elements)
288
+ return;
289
+
290
+ const names = Object.keys(elements);
291
+ if (names.length > 0 && !art.elements)
292
+ art.elements = Object.create(null);
293
+
294
+ for (const n of names) {
295
+ const magic = {
296
+ kind: 'builtin',
297
+ name: { id: n, element: `${ art.name.element }.${ n }` },
298
+ };
299
+ // Propagate this property so that it is available for sub-elements.
300
+ if (art.$uncheckedElements)
301
+ magic.$uncheckedElements = art.$uncheckedElements;
302
+ setProp( magic, '_parent', art );
303
+ // setProp( magic, '_effectiveType', magic );
304
+ if (elements[n] && typeof elements[n] === 'object')
305
+ createMagicElements(magic, elements[n]);
306
+
307
+ art.elements[n] = magic;
308
+ }
265
309
  }
266
310
  }
267
311
 
@@ -269,6 +313,8 @@ module.exports = {
269
313
  functionsWithoutParens,
270
314
  specialFunctions,
271
315
  initBuiltins,
316
+ isInReservedNamespace,
317
+ isBuiltinType,
272
318
  isIntegerTypeName,
273
319
  isDecimalTypeName,
274
320
  isNumericTypeName,
@@ -87,6 +87,17 @@ function check( model ) { // = XSN
87
87
  'Keyword “localized” may only be used in combination with string types');
88
88
  }
89
89
  }
90
+ // "key" keyword at localized element in SELECT list.
91
+ // TODO: This check should be moved to localized.js
92
+ if (elem.key && elem.key.val && elem._main && elem._main.query) {
93
+ // original element is localized but not key, as that would have
94
+ // already resulted in a warning
95
+ if (elem._origin && elem._origin.localized && elem._origin.localized.val &&
96
+ ( !elem._origin.key || !elem._origin.key.val)) {
97
+ warning('localized-key', [ elem.key.location, elem ], { keyword: 'localized' },
98
+ 'Keyword $(KEYWORD) is ignored for primary keys');
99
+ }
100
+ }
90
101
  }
91
102
 
92
103
  function checkQuery( query ) {
@@ -334,11 +345,17 @@ function check( model ) { // = XSN
334
345
  // Max cardinalities must be a positive number or '*'
335
346
  for (const prop of [ 'sourceMax', 'targetMax' ]) {
336
347
  if (elem.cardinality[prop]) {
337
- if (!(elem.cardinality[prop].literal === 'number' && elem.cardinality[prop].val > 0 ||
338
- elem.cardinality[prop].literal === 'string' && elem.cardinality[prop].val === '*')) {
339
- error(null, [ elem.cardinality[prop].location, elem ],
340
- { code: elem.cardinality[prop].val },
341
- 'Illegal value $(CODE) for max cardinality (must be a positive number or "*")');
348
+ const { literal, val, location } = elem.cardinality[prop];
349
+ if (!(literal === 'number' && val > 0 ||
350
+ literal === 'string' && val === '*')) {
351
+ error('invalid-cardinality', [ location, elem ], { '#': prop, code: val }, {
352
+ // eslint-disable-next-line max-len
353
+ std: 'Value $(CODE) is invalid for maximum cardinality, expecting a positive number or ‘*’',
354
+ // eslint-disable-next-line max-len
355
+ sourceMax: 'Value $(CODE) is invalid for maximum source cardinality, expecting a positive number or ‘*’',
356
+ // eslint-disable-next-line max-len
357
+ targetMax: 'Value $(CODE) is invalid for maximum target cardinality, expecting a positive number or ‘*’',
358
+ });
342
359
  }
343
360
  }
344
361
  }
@@ -348,10 +365,16 @@ function check( model ) { // = XSN
348
365
  // from-csn.json (expected non-negative number)
349
366
  for (const prop of [ 'sourceMin', 'targetMin' ]) {
350
367
  if (elem.cardinality[prop]) {
351
- if (!(elem.cardinality[prop].literal === 'number' && elem.cardinality[prop].val >= 0)) {
352
- error(null, [ elem.cardinality[prop].location, elem ],
353
- { code: elem.cardinality[prop].val },
354
- 'Illegal value $(CODE) for min cardinality (must be a non-negative number)');
368
+ const { literal, val, location } = elem.cardinality[prop];
369
+ if (!(literal === 'number' && val >= 0)) {
370
+ error('invalid-cardinality', [ location, elem ], { '#': prop, code: val }, {
371
+ // eslint-disable-next-line max-len
372
+ std: 'Value $(CODE) is invalid for minimum cardinality, expecting a non-negative number',
373
+ // eslint-disable-next-line max-len
374
+ targetMin: 'Value $(CODE) is invalid for minimum target cardinality, expecting a non-negative number',
375
+ // eslint-disable-next-line max-len
376
+ sourceMin: 'Value $(CODE) is invalid for minimum source cardinality, expecting a non-negative number',
377
+ });
355
378
  }
356
379
  }
357
380
  }
@@ -632,8 +655,9 @@ function check( model ) { // = XSN
632
655
  * @returns {void}
633
656
  */
634
657
  function checkTokenStreamExpression(xpr, allowAssocTail) {
658
+ const args = Array.isArray(xpr.args) ? xpr.args : Object.values(xpr.args || {});
635
659
  // Check for illegal argument usage within the expression
636
- for (const arg of xpr.args || []) {
660
+ for (const arg of args) {
637
661
  if (isVirtualElement(arg))
638
662
  error(null, arg.location, 'Virtual elements can\'t be used in an expression');
639
663