@sap/cds-compiler 4.0.2 → 4.2.2

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 (101) hide show
  1. package/CHANGELOG.md +200 -5
  2. package/bin/cdsc.js +18 -15
  3. package/doc/CHANGELOG_BETA.md +16 -0
  4. package/doc/CHANGELOG_DEPRECATED.md +15 -0
  5. package/lib/api/main.js +33 -13
  6. package/lib/api/options.js +2 -2
  7. package/lib/api/validate.js +25 -25
  8. package/lib/base/location.js +6 -7
  9. package/lib/base/message-registry.js +123 -42
  10. package/lib/base/messages.js +18 -10
  11. package/lib/base/model.js +43 -10
  12. package/lib/checks/defaultValues.js +6 -6
  13. package/lib/checks/elements.js +11 -10
  14. package/lib/checks/foreignKeys.js +0 -5
  15. package/lib/checks/manyNavigations.js +33 -0
  16. package/lib/checks/onConditions.js +22 -14
  17. package/lib/checks/queryNoDbArtifacts.js +132 -73
  18. package/lib/checks/selectItems.js +4 -55
  19. package/lib/checks/sql-snippets.js +15 -4
  20. package/lib/checks/types.js +3 -3
  21. package/lib/checks/utils.js +4 -3
  22. package/lib/checks/validator.js +3 -1
  23. package/lib/compiler/.eslintrc.json +2 -1
  24. package/lib/compiler/assert-consistency.js +71 -40
  25. package/lib/compiler/base.js +7 -2
  26. package/lib/compiler/builtins.js +40 -41
  27. package/lib/compiler/checks.js +415 -367
  28. package/lib/compiler/classes.js +62 -0
  29. package/lib/compiler/cycle-detector.js +9 -9
  30. package/lib/compiler/define.js +124 -90
  31. package/lib/compiler/extend.js +115 -88
  32. package/lib/compiler/finalize-parse-cdl.js +26 -25
  33. package/lib/compiler/generate.js +57 -49
  34. package/lib/compiler/index.js +56 -56
  35. package/lib/compiler/kick-start.js +10 -7
  36. package/lib/compiler/moduleLayers.js +1 -1
  37. package/lib/compiler/populate.js +180 -144
  38. package/lib/compiler/propagator.js +10 -9
  39. package/lib/compiler/resolve.js +321 -246
  40. package/lib/compiler/shared.js +812 -433
  41. package/lib/compiler/tweak-assocs.js +114 -50
  42. package/lib/compiler/utils.js +241 -46
  43. package/lib/edm/.eslintrc.json +40 -1
  44. package/lib/edm/annotations/genericTranslation.js +721 -707
  45. package/lib/edm/annotations/preprocessAnnotations.js +88 -77
  46. package/lib/edm/csn2edm.js +389 -378
  47. package/lib/edm/edm.js +679 -770
  48. package/lib/edm/edmAnnoPreprocessor.js +132 -146
  49. package/lib/edm/edmInboundChecks.js +29 -27
  50. package/lib/edm/edmPreprocessor.js +689 -648
  51. package/lib/edm/edmUtils.js +279 -300
  52. package/lib/gen/Dictionary.json +34 -10
  53. package/lib/gen/language.checksum +1 -1
  54. package/lib/gen/language.interp +1 -1
  55. package/lib/gen/languageParser.js +2857 -2856
  56. package/lib/json/from-csn.js +77 -51
  57. package/lib/json/to-csn.js +15 -15
  58. package/lib/language/antlrParser.js +2 -1
  59. package/lib/language/genericAntlrParser.js +52 -43
  60. package/lib/language/language.g4 +61 -64
  61. package/lib/language/multiLineStringParser.js +2 -0
  62. package/lib/main.d.ts +65 -0
  63. package/lib/model/csnRefs.js +37 -19
  64. package/lib/model/csnUtils.js +51 -18
  65. package/lib/model/revealInternalProperties.js +30 -22
  66. package/lib/modelCompare/compare.js +149 -41
  67. package/lib/modelCompare/utils/filter.js +55 -25
  68. package/lib/optionProcessor.js +21 -9
  69. package/lib/render/manageConstraints.js +20 -17
  70. package/lib/render/toCdl.js +63 -23
  71. package/lib/render/toHdbcds.js +2 -2
  72. package/lib/render/toRename.js +4 -9
  73. package/lib/render/toSql.js +82 -35
  74. package/lib/render/utils/common.js +11 -9
  75. package/lib/render/utils/unique.js +52 -0
  76. package/lib/transform/db/applyTransformations.js +62 -21
  77. package/lib/transform/db/assertUnique.js +7 -8
  78. package/lib/transform/db/associations.js +2 -2
  79. package/lib/transform/db/cdsPersistence.js +9 -9
  80. package/lib/transform/db/constraints.js +47 -17
  81. package/lib/transform/db/expansion.js +138 -68
  82. package/lib/transform/db/flattening.js +98 -30
  83. package/lib/transform/db/rewriteCalculatedElements.js +20 -14
  84. package/lib/transform/db/temporal.js +1 -1
  85. package/lib/transform/db/transformExists.js +8 -7
  86. package/lib/transform/db/views.js +73 -33
  87. package/lib/transform/draft/db.js +11 -9
  88. package/lib/transform/draft/odata.js +1 -1
  89. package/lib/transform/{forOdataNew.js → forOdata.js} +10 -7
  90. package/lib/transform/forRelationalDB.js +148 -136
  91. package/lib/transform/localized.js +92 -54
  92. package/lib/transform/odata/toFinalBaseType.js +3 -3
  93. package/lib/transform/{transformUtilsNew.js → transformUtils.js} +13 -111
  94. package/lib/transform/translateAssocsToJoins.js +14 -28
  95. package/lib/utils/file.js +7 -7
  96. package/lib/utils/moduleResolve.js +210 -121
  97. package/lib/utils/objectUtils.js +1 -1
  98. package/package.json +5 -5
  99. package/share/messages/check-proper-type-of.md +1 -1
  100. package/share/messages/{check-proper-type.md → def-missing-type.md} +3 -5
  101. package/share/messages/message-explanations.json +1 -1
@@ -14,14 +14,16 @@ const {
14
14
  setArtifactLink,
15
15
  linkToOrigin,
16
16
  copyExpr,
17
+ forEachUserArtifact,
18
+ forEachQueryExpr,
17
19
  traverseQueryPost,
18
20
  traverseQueryExtra,
19
21
  setExpandStatus,
20
- traverseExpr,
21
22
  } = require('./utils');
23
+ const { CsnLocation } = require('./classes');
22
24
 
23
- const $location = Symbol.for('cds.$location');
24
- const $inferred = Symbol.for('cds.$inferred');
25
+ const $location = Symbol.for( 'cds.$location' );
26
+ const $inferred = Symbol.for( 'cds.$inferred' );
25
27
 
26
28
  // Export function of this file.
27
29
  function tweakAssocs( model ) {
@@ -30,9 +32,11 @@ function tweakAssocs( model ) {
30
32
  info, warning, error,
31
33
  } = model.$messageFunctions;
32
34
  const {
35
+ traverseExpr,
36
+ checkExpr,
37
+ checkOnCondition,
33
38
  effectiveType,
34
39
  getOrigin,
35
- navigationEnv,
36
40
  } = model.$functions;
37
41
 
38
42
  // Phase 5: rewrite associations
@@ -40,6 +44,13 @@ function tweakAssocs( model ) {
40
44
  // Think hard whether an on condition rewrite can lead to a new cyclic
41
45
  // dependency. If so, we need other messages anyway. TODO: probably do
42
46
  // another cyclic check with testMode.js
47
+ forEachUserArtifact( model, 'definitions', function check( art ) {
48
+ checkOnCondition( art.on, (art.kind !== 'mixin' ? 'on' : 'mixin-on'), art );
49
+ checkExpr( art.value, (art.$syntax === 'calc' ? 'calc' : 'column'), art );
50
+
51
+ if (art.kind === 'select')
52
+ forEachQueryExpr( art, checkExpr );
53
+ } );
43
54
  return;
44
55
 
45
56
 
@@ -107,7 +118,7 @@ function tweakAssocs( model ) {
107
118
  }
108
119
 
109
120
  function rewriteAssociationCheck( element ) {
110
- const elem = element.items || element; // TODO v2: nested items
121
+ const elem = element.items || element; // TODO v5: nested items
111
122
  if (elem.elements)
112
123
  forEachGeneric( elem, 'elements', rewriteAssociationCheck );
113
124
  if (!elem.target)
@@ -166,13 +177,9 @@ function tweakAssocs( model ) {
166
177
  }
167
178
  if (names.length) {
168
179
  const loc = otherAssoc.foreignKeys[$location] || dictLocation( otherAssoc.foreignKeys );
169
- const location = loc && (!loc.endCol ? loc : {
170
- file: loc.file,
171
- line: loc.endLine,
172
- col: loc.endCol - 1,
173
- endLine: loc.endLine,
174
- endCol: loc.endCol,
175
- } );
180
+ const location = loc && (!loc.endCol
181
+ ? loc
182
+ : new CsnLocation( loc.file, loc.endLine, loc.endCol - 1, loc.endLine, loc.endCol ));
176
183
  const baseAssoc = assocWithExplicitSpec( thisAssoc );
177
184
  if (inferredForeignKeys( baseAssoc.foreignKeys )) { // still inferred = via target keys
178
185
  error( 'rewrite-key-not-covered-implicit', [ location, otherAssoc ],
@@ -194,7 +201,7 @@ function tweakAssocs( model ) {
194
201
  }
195
202
 
196
203
  function assocWithExplicitSpec( assoc ) {
197
- while (assoc.foreignKeys && inferredForeignKeys( assoc.foreignKeys, 'keys') ||
204
+ while (assoc.foreignKeys && inferredForeignKeys( assoc.foreignKeys, 'keys' ) ||
198
205
  assoc.on && assoc.on.$inferred)
199
206
  assoc = getOrigin( assoc );
200
207
  return assoc;
@@ -257,7 +264,7 @@ function tweakAssocs( model ) {
257
264
  fk.$inferred = 'rewrite'; // Override existing value; TODO: other $inferred value?
258
265
  // TODO: re-check for case that foreign key is managed association
259
266
  if (orig._effectiveType !== undefined)
260
- setLink( fk, '_effectiveType', orig._effectiveType);
267
+ setLink( fk, '_effectiveType', orig._effectiveType );
261
268
  const te = copyExpr( orig.targetElement, elem.location );
262
269
  if (elem._redirected) {
263
270
  const i = te.path[0]; // TODO: or also follow path like for ON?
@@ -266,7 +273,7 @@ function tweakAssocs( model ) {
266
273
  setArtifactLink( te, state );
267
274
  }
268
275
  fk.targetElement = te;
269
- });
276
+ } );
270
277
  if (elem.foreignKeys) // Possibly no fk was set
271
278
  elem.foreignKeys[$inferred] = 'rewrite';
272
279
  }
@@ -281,14 +288,14 @@ function tweakAssocs( model ) {
281
288
  // same (TODO later: set status whether rewrite changes anything),
282
289
  // especially problematic are refs starting with $self:
283
290
  setExpandStatus( elem, 'target' );
284
- if (elem._parent && elem._parent.kind === 'element') {
291
+ if (elem._parent?.kind === 'element') {
285
292
  // managed association as sub element not supported yet
286
293
  error( null, [ elem.location, elem ], {},
287
294
  // eslint-disable-next-line max-len
288
295
  'Rewriting the ON-condition of unmanaged association in sub element is not supported' );
289
296
  return;
290
297
  }
291
- const nav = (elem._main && elem._main.query && elem.value)
298
+ const nav = (elem._main?.query && elem.value)
292
299
  ? pathNavigation( elem.value ) // redirected source elem or mixin
293
300
  : { navigation: assoc }; // redirected user-provided
294
301
  const cond = copyExpr( assoc.on,
@@ -340,15 +347,13 @@ function tweakAssocs( model ) {
340
347
  if (tableAlias) { // from ON cond of element in source ref/d by table alias
341
348
  const source = tableAlias._origin;
342
349
  const root = expr.path[0]._navigation || expr.path[0]._artifact;
343
- // console.log( info(null, [assoc.name.location, assoc],
344
- // { names: expr.path.map(i=>i.id), art: root }, 'TA').toString())
350
+ // console.log( info(null, [ assoc.name.location, assoc ],
351
+ // { names: expr.path.map(i => i.id), art: root }, 'TA').toString());
345
352
  if (!root || root._main !== source)
346
353
  return; // not $self or source element
347
354
  if (expr.scope === 'param' || root.kind === '$parameters')
348
355
  return; // are not allowed anyway - there was an error before
349
- const item = expr.path[root.kind === '$self' ? 1 : 0];
350
- // console.log('YE', assoc.name, item, root.name, expr.path)
351
- const elem = navProjection( item && tableAlias.elements[item.id], assoc );
356
+ const { item, elem } = firstProjectionForPath( expr.path, tableAlias, assoc );
352
357
  rewritePath( expr, item, assoc, elem, assoc.value.location );
353
358
  }
354
359
  else if (assoc._main.query) { // from ON cond of mixin element in query
@@ -379,7 +384,7 @@ function tweakAssocs( model ) {
379
384
  return; // just $self
380
385
  // corresponding elem in including structure
381
386
  const elem = (assoc._main.items || assoc._main).elements[item.id];
382
- if (!(Array.isArray(elem) || // no msg for redefs
387
+ if (!(Array.isArray( elem ) || // no msg for redefs
383
388
  elem === item._artifact || // redirection for explicit def
384
389
  elem._origin === item._artifact)) {
385
390
  const art = assoc._origin;
@@ -392,7 +397,7 @@ function tweakAssocs( model ) {
392
397
  element: 'This element is not originally referred to in the ON-condition of association $(MEMBER) of $(ART)',
393
398
  } );
394
399
  }
395
- rewritePath( expr, item, assoc, (Array.isArray(elem) ? false : elem), null );
400
+ rewritePath( expr, item, assoc, (Array.isArray( elem ) ? false : elem), null );
396
401
  }
397
402
  }
398
403
 
@@ -401,13 +406,12 @@ function tweakAssocs( model ) {
401
406
  let root = path[0];
402
407
  if (!elem) {
403
408
  if (location) {
404
- error( 'rewrite-not-projected', [ location, assoc ],
405
- { name: assoc.name.id, art: item._artifact }, {
406
- // eslint-disable-next-line max-len
407
- std: 'Projected association $(NAME) uses non-projected element $(ART)',
408
- // eslint-disable-next-line max-len
409
- element: 'Projected association $(NAME) uses non-projected element $(MEMBER) of $(ART)',
410
- } );
409
+ error( 'rewrite-not-projected', [ location, assoc ], {
410
+ name: assoc.name.id, art: item._artifact, elemref: { ref: path },
411
+ }, {
412
+ std: 'Projected association $(NAME) uses non-projected element $(ELEMREF)',
413
+ element: 'Projected association $(NAME) uses non-projected element $(MEMBER) of $(ART)',
414
+ } );
411
415
  }
412
416
  delete root._navigation;
413
417
  setArtifactLink( root, elem );
@@ -415,9 +419,15 @@ function tweakAssocs( model ) {
415
419
  return;
416
420
  }
417
421
  if (item !== root) {
422
+ // e.g. mixin ON-condition: Base.foo -> $self.foo or multi-path projection,
423
+ // $projection -> $self
418
424
  root.id = '$self';
419
425
  setLink( root, '_navigation', assoc._parent.$tableAliases.$self );
420
426
  setArtifactLink( root, assoc._parent );
427
+ if (item) {
428
+ const i = path.indexOf(item);
429
+ ref.path = [ root, ...path.slice( i, path.length ) ];
430
+ }
421
431
  }
422
432
  else if (elem.name.id.charAt(0) === '$') {
423
433
  root = { id: '$self', location: item.location };
@@ -463,14 +473,13 @@ function tweakAssocs( model ) {
463
473
  // consider intermediate "preferred" elements - not just `assoc`,
464
474
  // but its origins, too.
465
475
  const proj = navProjection( alias.elements[name], assoc );
466
- name = proj && proj.name && proj.name.id;
476
+ name = proj?.name?.id;
467
477
  if (!name) {
468
478
  if (!forKeys)
469
479
  break;
470
480
  setArtifactLink( item, null );
471
481
  const culprit = elem.target && !elem.target.$inferred && elem.target ||
472
- (elem.value && elem.value.path &&
473
- elem.value.path[elem.value.path.length - 1]) ||
482
+ elem.value?.path?.[elem.value.path.length - 1] ||
474
483
  elem;
475
484
  // TODO: probably better to collect the non-projected foreign keys
476
485
  // and have one message for all
@@ -482,9 +491,13 @@ function tweakAssocs( model ) {
482
491
  item.id = name;
483
492
  }
484
493
  }
485
- const env = name && navigationEnv(elem);
494
+ let env = name && elem._effectiveType; // should have been computed
495
+ // refs in ON cannot navigate along `items`, no need to consider `items` here
496
+ if (env?.target)
497
+ env = env.target._artifact?._effectiveType;
486
498
  elem = setArtifactLink( item, env?.elements?.[name] );
487
- if (elem && !Array.isArray(elem))
499
+
500
+ if (elem && !Array.isArray( elem ))
488
501
  return elem;
489
502
  // TODO: better (extra message), TODO: do it
490
503
  error( 'query-undefined-element', [ item.location, assoc ],
@@ -505,20 +518,71 @@ function navProjection( navigation, preferred ) {
505
518
  : navigation._projections[0] || null;
506
519
  }
507
520
 
508
- // Return condensed info about reference in select item
509
- // - tableAlias.elem -> { navigation: navElem, item: path[1], tableAlias }
510
- // - sourceElem (in query) -> { navigation: navElem, item: path[0], tableAlias }
511
- // - mixinElem -> { navigation: mixinElement, item: path[0] }
512
- // - $projection.elem -> also $self.item -> { item: path[1], tableAlias: $self }
513
- // - $self -> { item: undefined, tableAlias: $self }
514
- // - $parameters.P, :P -> {}
515
- // - $now, current_date -> {}
516
- // - undef, redef -> {}
517
- // With 'navigation': store that navigation._artifact is projected
518
- // With 'navigation': rewrite its ON condition
519
- // With navigation: Do KEY propagation
520
- //
521
- // TODO: re-think this function, copied in populate.js and tweak-assocs.js
521
+
522
+ /**
523
+ * For a path `a.b.c.d`, return a projection for the first path item that is projected.
524
+ * For example, if a query has multiple projections such as `a.b, a, a.b.c`, the
525
+ * _first_ possible projection will be used and the caller can rewrite `a.b.c.d` to `b.c.d`.
526
+ * This avoids that `extend`s affect the ON-condition.
527
+ *
528
+ * The returned object `ret` has `ret.item`, which is the path item that is projected.
529
+ * `ret.elem` is the element projection.
530
+ *
531
+ * @param {any[]} path
532
+ * @param {object} tableAlias
533
+ * @param {object} assoc Preferred association that should be used if projected.
534
+ * @return {{elem: object, item: object}|null}
535
+ */
536
+ function firstProjectionForPath( path, tableAlias, assoc ) {
537
+ const viaSelf = (path[0]._navigation || path[0]._artifact).kind === '$self';
538
+ const root = viaSelf ? 1 : 0;
539
+ if (root >= path.length) // e.g. just `$self` path item
540
+ return { item: undefined, elem: {} };
541
+
542
+ // We want to use the _first_ valid projection that is written by the user (if the preferred
543
+ // `assoc` is not directly projected). To achieve that, look into the table alias' elements.
544
+ const selectedElements = Object.values(tableAlias._parent.elements);
545
+ const proj = [];
546
+ let navItem = tableAlias;
547
+ for (const item of path.slice(root)) {
548
+ navItem = item?.id && navItem.elements?.[item.id];
549
+ if (!navItem) {
550
+ break;
551
+ }
552
+ else if (navItem._projections) {
553
+ const elem = navProjection( navItem, assoc );
554
+ if (elem && elem === assoc) {
555
+ // in case the specified association is found, _always_ use it.
556
+ return { item, elem };
557
+ }
558
+ else if (elem) {
559
+ const index = selectedElements.indexOf(elem);
560
+ proj.push({ item, elem, index });
561
+ }
562
+ }
563
+ }
564
+
565
+ return (proj.length === 0)
566
+ ? { item: path[root], elem: null }
567
+ : proj.reduce( (acc, curr) => (acc.index > curr.index ? curr : acc), proj[0] ); // first
568
+ }
569
+
570
+ /**
571
+ * Return condensed info about reference in select item
572
+ * - tableAlias.elem -> { navigation: navElem, item: path[1], tableAlias }
573
+ * - sourceElem (in query) -> { navigation: navElem, item: path[0], tableAlias }
574
+ * - mixinElem -> { navigation: mixinElement, item: path[0] }
575
+ * - $projection.elem -> also $self.item -> { item: path[1], tableAlias: $self }
576
+ * - $self -> { item: undefined, tableAlias: $self }
577
+ * - $parameters.P, :P -> {}
578
+ * - $now, current_date -> {}
579
+ * - undef, redef -> {}
580
+ * With 'navigation': store that navigation._artifact is projected
581
+ * With 'navigation': rewrite its ON condition
582
+ * With navigation: Do KEY propagation
583
+ *
584
+ * TODO: re-think this function, copied in populate.js and tweak-assocs.js
585
+ */
522
586
  function pathNavigation( ref ) {
523
587
  // currently, indirectly projectable elements are not included - we might
524
588
  // keep it this way! If we want them to be included - be aware: cycles