@sap/cds-compiler 4.5.0 → 4.6.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 (65) hide show
  1. package/CHANGELOG.md +50 -7
  2. package/bin/cdsc.js +13 -11
  3. package/doc/CHANGELOG_BETA.md +6 -0
  4. package/lib/api/main.js +256 -115
  5. package/lib/api/options.js +8 -0
  6. package/lib/base/message-registry.js +17 -4
  7. package/lib/base/messages.js +15 -3
  8. package/lib/base/model.js +1 -0
  9. package/lib/base/optionProcessorHelper.js +45 -176
  10. package/lib/checks/elements.js +32 -34
  11. package/lib/checks/enricher.js +39 -3
  12. package/lib/checks/validator.js +2 -3
  13. package/lib/compiler/assert-consistency.js +2 -1
  14. package/lib/compiler/builtins.js +20 -4
  15. package/lib/compiler/checks.js +30 -6
  16. package/lib/compiler/define.js +31 -9
  17. package/lib/compiler/populate.js +5 -1
  18. package/lib/compiler/resolve.js +26 -21
  19. package/lib/compiler/shared.js +19 -9
  20. package/lib/compiler/tweak-assocs.js +82 -107
  21. package/lib/compiler/utils.js +2 -1
  22. package/lib/edm/annotations/edmJson.js +23 -22
  23. package/lib/edm/annotations/genericTranslation.js +14 -4
  24. package/lib/edm/csn2edm.js +24 -10
  25. package/lib/edm/edmInboundChecks.js +1 -2
  26. package/lib/edm/edmPreprocessor.js +11 -9
  27. package/lib/edm/edmUtils.js +5 -2
  28. package/lib/gen/Dictionary.json +3 -1
  29. package/lib/gen/language.checksum +1 -1
  30. package/lib/gen/language.interp +4 -1
  31. package/lib/gen/language.tokens +1 -0
  32. package/lib/gen/languageParser.js +5253 -5214
  33. package/lib/json/to-csn.js +7 -1
  34. package/lib/language/antlrParser.js +19 -1
  35. package/lib/language/errorStrategy.js +21 -4
  36. package/lib/language/genericAntlrParser.js +9 -11
  37. package/lib/main.d.ts +28 -3
  38. package/lib/main.js +3 -0
  39. package/lib/model/csnRefs.js +4 -1
  40. package/lib/model/csnUtils.js +12 -7
  41. package/lib/optionProcessor.js +21 -19
  42. package/lib/render/manageConstraints.js +13 -29
  43. package/lib/render/toCdl.js +18 -15
  44. package/lib/render/toHdbcds.js +59 -28
  45. package/lib/render/toRename.js +6 -10
  46. package/lib/render/toSql.js +57 -82
  47. package/lib/render/utils/common.js +17 -0
  48. package/lib/transform/.eslintrc.json +9 -1
  49. package/lib/transform/addTenantFields.js +228 -0
  50. package/lib/transform/db/applyTransformations.js +27 -31
  51. package/lib/transform/db/assertUnique.js +4 -4
  52. package/lib/transform/db/cdsPersistence.js +1 -1
  53. package/lib/transform/db/flattening.js +68 -69
  54. package/lib/transform/db/temporal.js +1 -1
  55. package/lib/transform/draft/db.js +2 -16
  56. package/lib/transform/draft/odata.js +3 -3
  57. package/lib/transform/effective/associations.js +3 -5
  58. package/lib/transform/effective/main.js +6 -9
  59. package/lib/transform/forOdata.js +13 -9
  60. package/lib/transform/forRelationalDB.js +36 -17
  61. package/lib/transform/odata/toFinalBaseType.js +3 -3
  62. package/lib/transform/odata/typesExposure.js +14 -5
  63. package/lib/transform/transformUtils.js +47 -34
  64. package/lib/transform/translateAssocsToJoins.js +33 -8
  65. package/package.json +2 -2
@@ -378,6 +378,10 @@ function tweakAssocs( model ) {
378
378
  }
379
379
 
380
380
  function rewriteKeys( elem, assoc ) {
381
+ addConditionFromAssocPublishing( elem, assoc, null );
382
+ if (elem.on)
383
+ return; // foreign keys were transformed into ON-condition
384
+
381
385
  // TODO: split this function: create foreign keys without `targetElement`
382
386
  // already in Phase 2: redirectImplicitly()
383
387
  // console.log(message( null, elem.location, elem, {art:assoc,target:assoc.target},
@@ -399,8 +403,6 @@ function tweakAssocs( model ) {
399
403
  } );
400
404
  if (elem.foreignKeys) // Possibly no fk was set
401
405
  elem.foreignKeys[$inferred] = 'rewrite';
402
-
403
- addConditionFromAssocPublishing( elem, assoc );
404
406
  }
405
407
 
406
408
  // TODO: there is no need to rewrite the on condition of non-leading queries,
@@ -423,27 +425,24 @@ function tweakAssocs( model ) {
423
425
  const nav = (elem._main?.query && elem.value)
424
426
  ? pathNavigation( elem.value ) // redirected source elem or mixin
425
427
  : { navigation: assoc }; // redirected user-provided
426
- const cond = copyExpr( assoc.on,
428
+ elem.on = copyExpr( assoc.on,
427
429
  // replace location in ON except if from mixin element
428
- nav.tableAlias && elem.name.location );
429
- elem.on = cond;
430
- addConditionFromAssocPublishing( elem, assoc );
431
- // `cond` still points to the original condition; does not include possible assoc filter
430
+ nav.tableAlias && elem.name.location );
432
431
  elem.on.$inferred = 'copy';
433
432
 
434
433
  const { navigation } = nav;
435
434
  if (!navigation) // TODO: what about $projection.assoc as myAssoc ?
436
435
  return; // should not happen: $projection, $magic, or ref to const
436
+
437
437
  // Currently, having an unmanaged association inside a struct is not
438
438
  // supported by this function:
439
439
  if (navigation !== assoc && navigation._origin !== assoc) { // TODO: re-check
440
440
  // For "assoc1.assoc2" and "struct.elem1.assoc2"
441
441
  if (elem._redirected !== null) // null = already reported
442
442
  error( 'rewrite-not-supported', [ elem.target.location, elem ] );
443
- return;
444
443
  }
445
- if (!nav.tableAlias || nav.tableAlias.path) {
446
- traverseExpr( cond, 'rewrite-on', elem,
444
+ else if (!nav.tableAlias || nav.tableAlias.path) {
445
+ traverseExpr( elem.on, 'rewrite-on', elem,
447
446
  expr => rewriteExpr( expr, elem, nav.tableAlias ) );
448
447
  }
449
448
  else {
@@ -451,6 +450,12 @@ function tweakAssocs( model ) {
451
450
  error( null, [ elem.value.location, elem ], {},
452
451
  'Selecting unmanaged associations from a sub query is not supported' );
453
452
  }
453
+
454
+ // filter was copied in original element already
455
+ // TODO: not correct for e.g. composition-of-inline-aspect (#12223)
456
+ if (elem.$inferred !== 'include')
457
+ addConditionFromAssocPublishing( elem, assoc, nav );
458
+
454
459
  elem.on.$inferred = 'rewrite';
455
460
  }
456
461
 
@@ -462,70 +467,67 @@ function tweakAssocs( model ) {
462
467
  *
463
468
  * The added condition (filter) is already rewritten relative to `elem`.
464
469
  */
465
- function addConditionFromAssocPublishing( elem, assoc ) {
470
+ function addConditionFromAssocPublishing( elem, assoc, nav ) {
466
471
  const publishAssoc = (elem._main?.query || elem.$syntax === 'calc') &&
467
472
  elem.value?.path?.length > 0;
468
473
  if (!publishAssoc)
469
474
  return;
470
475
 
476
+ nav ??= (elem._main?.query && elem.value)
477
+ ? pathNavigation( elem.value ) // redirected source elem or mixin
478
+ : { navigation: assoc }; // redirected user-provided
479
+
471
480
  const { location } = elem.name;
472
- let isRestricted = false;
473
481
  const lastStep = elem.value.path[elem.value.path.length - 1];
474
- if (!lastStep)
482
+ if (!lastStep || !lastStep.where)
475
483
  return;
476
484
 
477
485
  if (lastStep.cardinality) {
478
- isRestricted = true;
479
- if (!elem.cardinality)
480
- elem.cardinality = { ...assoc.cardinality } || { location };
486
+ elem.cardinality ??= { ...assoc.cardinality };
487
+ elem.cardinality.location = location;
481
488
  for (const card of [ 'sourceMin', 'targetMin', 'targetMax' ]) {
482
489
  if (lastStep.cardinality[card])
483
490
  elem.cardinality[card] = copyExpr( lastStep.cardinality[card], location );
484
491
  }
485
492
  }
486
493
 
487
- if (lastStep.where) {
488
- // If there are foreign keys, transform them into an ON-condition first.
489
- if (assoc.foreignKeys) {
490
- const cond = foreignKeysToOnCondition( elem );
491
- if (cond) {
492
- elem.on = cond;
493
- elem.foreignKeys = undefined;
494
- }
495
- }
496
-
497
- if (elem.on) {
498
- isRestricted = true;
499
- elem.on = {
500
- op: { val: 'and', location },
501
- args: [
502
- // TODO: Get rid of $parens
503
- { ...elem.on, $parens: [ assoc.location ] },
504
- filterToCondition( lastStep, elem ),
505
- ],
506
- location,
507
- $inferred: 'copy',
508
- };
509
- }
510
- if (isRestricted) {
511
- elem.$filtered = {
512
- val: true,
513
- literal: 'boolean',
514
- location,
515
- $inferred: '$generated',
516
- };
494
+ // If there are foreign keys, transform them into an ON-condition first.
495
+ if (assoc.foreignKeys) {
496
+ const cond = foreignKeysToOnCondition( elem, assoc, nav );
497
+ if (cond) {
498
+ elem.on = cond;
499
+ elem.foreignKeys = undefined;
517
500
  }
518
501
  }
502
+
503
+ elem.on = {
504
+ op: { val: 'and', location },
505
+ args: [
506
+ // TODO: Get rid of $parens
507
+ { ...elem.on, $parens: [ assoc.location ] },
508
+ filterToCondition( lastStep, elem, nav ),
509
+ ],
510
+ location,
511
+ $inferred: 'copy',
512
+ };
513
+
514
+ elem.$filtered = {
515
+ val: true,
516
+ literal: 'boolean',
517
+ location,
518
+ $inferred: '$generated',
519
+ };
519
520
  }
520
521
 
521
522
  /**
522
- * Transform a filter on `assoc` into an ON-condition.
523
- * Paths inside the filter are rewritten relative to `elem`.
523
+ * Transform a filter on `assocPathStep` into an ON-condition.
524
+ * Paths inside the filter are rewritten relative to `assoc`, so they can be redirected
525
+ * using `rewriteExpr()` later on. `$self` paths remain unchanged.
524
526
  */
525
- function filterToCondition( assoc, elem ) {
526
- const cond = copyExpr( assoc.where );
527
+ function filterToCondition( assocPathStep, elem, nav ) {
528
+ const cond = copyExpr( assocPathStep.where );
527
529
  // TODO: Get rid of $parens
528
- cond.$parens = [ assoc.location ];
530
+ cond.$parens = [ assocPathStep.location ];
529
531
  traverseExpr( cond, 'rewrite-filter', elem, (expr) => {
530
532
  if (!expr.path || expr.path.length === 0)
531
533
  return;
@@ -539,15 +541,17 @@ function tweakAssocs( model ) {
539
541
  }
540
542
  else if (!root.builtin && root.kind !== 'builtin') {
541
543
  expr.path.unshift({
542
- id: elem.name.id,
544
+ id: assocPathStep.id,
543
545
  location: elem.name.location,
544
546
  });
545
- setLink( expr.path[0], '_artifact', elem );
546
- // _navigation link not necessary because this condition is not rewritten
547
- // inside the same view (would otherwise be needed for mixins).
548
-
549
- if (elem.name.id.charAt(0) === '$')
550
- prependSelfToPath( expr.path, elem );
547
+ setLink( expr.path[0], '_artifact', assocPathStep._artifact );
548
+ if (assocPathStep._navigation?.kind === 'mixin') {
549
+ // _navigation link necessary because condition is rewritten
550
+ // inside the same view (needed for mixins).
551
+ setLink( expr.path[0], '_navigation', assocPathStep._navigation );
552
+ }
553
+ // up to here, filter is relative to original association
554
+ rewriteExpr( expr, elem, nav?.tableAlias );
551
555
  }
552
556
  } );
553
557
 
@@ -556,15 +560,12 @@ function tweakAssocs( model ) {
556
560
  }
557
561
 
558
562
  // Caller must ensure ON-condition correctness via rewriteExpr()!
559
- function foreignKeysToOnCondition( elem ) {
560
- const nav = elem.$syntax === 'calc'
561
- ? calcPathNavigation( elem.value )
562
- : pathNavigation( elem.value );
563
+ function foreignKeysToOnCondition( elem, assoc, nav ) {
563
564
  if (model.options.testMode && !nav.tableAlias && !elem._pathHead && elem.$syntax !== 'calc')
564
565
  throw new CompilerAssertion('rewriting keys to cond: no tableAlias but not inline/calc');
565
566
 
566
567
  if ((!nav.tableAlias && elem.$syntax !== 'calc') || elem._parent?.kind === 'element' ||
567
- (nav && nav.item !== elem.value.path[elem.value.path.length - 1])) {
568
+ (nav && nav.item && nav.item !== elem.value.path[elem.value.path.length - 1])) {
568
569
  // - no nav.tableAlias for mixins or inside inline; mixins can't have managed assocs, though.
569
570
  // - _parent is element for expand
570
571
  // - nav.item is different for multi-path steps e.g. `sub.assoc`, which is not supported, yet
@@ -574,45 +575,40 @@ function tweakAssocs( model ) {
574
575
  }
575
576
 
576
577
  let cond = [];
577
- forEachInOrder( elem, 'foreignKeys', function keyToCond( fKey ) {
578
+ forEachInOrder( assoc, 'foreignKeys', function keyToCond( fKey ) {
578
579
  // Format: lhs = rhs
579
580
  // assoc.id = assoc_id
580
- // lhs and rhs look the same, but have different ids and _artifact links.
581
- // rhs is rewritten further down to ensure that the foreign key is projected.
581
+ // lhs and rhs look the same but are rewritten differently. We must ensure that
582
+ // the rhs is rewritten to a projected element (or it must remain the assoc's
583
+ // foreign key in case of calc elements).
582
584
  const lhs = {
583
585
  path: [
584
- { id: elem.name.id, location: elem.name.location },
586
+ { id: assoc.name.id, location: elem.name.location },
585
587
  ...copyExpr( fKey.targetElement.path ),
586
588
  ],
587
589
  location: elem.name.location,
588
590
  };
589
- setLink( lhs.path[0], '_artifact', elem ); // different to rhs!
590
- setLink( lhs, '_artifact', lhs.path[lhs.path.length - 1] );
591
+ setLink( lhs.path[0], '_artifact', assoc );
592
+ setLink( lhs, '_artifact', lhs.path[lhs.path.length - 1]._artifact );
591
593
 
592
- if (elem.name.id.charAt(0) === '$')
593
- prependSelfToPath( lhs.path, elem );
594
+ rewritePath( lhs, lhs.path[0], assoc, elem, elem.value.location ); // different to rhs!
594
595
 
595
- const elemOrigin = getOrigin( elem );
596
596
  const rhs = {
597
597
  path: [
598
598
  // use origin's name; elem could have alias
599
- { id: elemOrigin.name.id, location: elemOrigin.name.location },
599
+ { id: assoc.name.id, location: elem.name.location },
600
600
  ...copyExpr( fKey.targetElement.path ),
601
601
  ],
602
602
  location: elem.name.location,
603
603
  };
604
- setLink( rhs.path[0], '_artifact', elemOrigin ); // different to lhs!
605
- setLink( rhs, '_artifact', rhs.path[rhs.path.length - 1] );
604
+ setLink( rhs.path[0], '_artifact', assoc );
605
+ setLink( rhs, '_artifact', rhs.path[rhs.path.length - 1]._artifact );
606
606
 
607
- if (elem.$syntax !== 'calc') {
608
- // Can't use rewriteExpr as that would use `assoc[…]` itself as well.
607
+ if (elem.$syntax !== 'calc') { // different to lhs!
609
608
  const projectedFk = firstProjectionForPath( rhs.path, nav.tableAlias, elem );
610
609
  rewritePath( rhs, projectedFk.item, elem, projectedFk.elem, elem.value.location );
611
610
  }
612
- else {
613
- // TODO: calc elements: Do we need to rewrite? Shouldn't all elements exist, since
614
- // the calc element is right next to all others?
615
- }
611
+
616
612
  const fkCond = {
617
613
  op: { val: 'ixpr', location: elem.name.location },
618
614
  args: [
@@ -699,15 +695,10 @@ function tweakAssocs( model ) {
699
695
  return; // just $self
700
696
  // corresponding elem in including structure or…
701
697
  let elem = (assoc._main.items || assoc._main).elements[item.id];
702
- if (elem !== assoc && assoc.$syntax === 'calc') {
703
- // … special case for calc elements where "elem" points to the referenced
704
- // (possibly included) association. For _included_ calc elements of the form,
705
- // `calc = assoc[…]` it holds: `elem === assoc`
706
- // TODO: expr could be the transformed fkey->ON-condition, where this statement is not true
707
- // And for the reference to the _foreign key_, we must _not_ rewrite it.
708
- const calcRoot = assoc.value.path[0]?._navigation;
709
- if (assoc.value.path[calcRoot?.kind === '$self' ? 1 : 0]?._artifact === elem)
710
- elem = assoc;
698
+ if (assoc.$syntax === 'calc' && assoc._origin === elem) {
699
+ // … calc element where "elem" points to the referenced (possibly included)
700
+ // sibling element (association).
701
+ elem = assoc;
711
702
  }
712
703
  if (!elem)
713
704
  return; // See #11755
@@ -785,9 +776,9 @@ function tweakAssocs( model ) {
785
776
 
786
777
  function prependSelfToPath( path, elem ) {
787
778
  const root = { id: '$self', location: path[0].location };
788
- path.unshift( root );
789
779
  setLink( root, '_navigation', elem._parent.$tableAliases.$self );
790
780
  setArtifactLink( root, elem._parent );
781
+ path.unshift( root );
791
782
  }
792
783
 
793
784
  function rewriteItem( elem, item, name, assoc, forKeys ) {
@@ -931,20 +922,4 @@ function pathNavigation( ref ) {
931
922
  return { navigation: root.elements[item.id], item, tableAlias: root };
932
923
  }
933
924
 
934
- // TODO: Maybe unify with pathNavigation?
935
- function calcPathNavigation( ref ) {
936
- if (!ref._artifact)
937
- return {};
938
- let item = ref.path && ref.path[0];
939
- const root = item && item._artifact;
940
- if (!root)
941
- return {};
942
- if (root.kind === 'element')
943
- return { navigation: root, item, tableAlias: root._parent };
944
- item = ref.path[1];
945
- if (root.kind === '$self')
946
- return { item, tableAlias: root };
947
- return { navigation: root.elements[item.id], item, tableAlias: root };
948
- }
949
-
950
925
  module.exports = tweakAssocs;
@@ -29,7 +29,8 @@ function pushLink( obj, prop, value ) {
29
29
  // for annotations:
30
30
 
31
31
  function annotationVal( anno ) {
32
- return anno && (anno.val === undefined || anno.val); // XSN TODO: set val for anno short form
32
+ // XSN TODO: set val but no location for anno short form
33
+ return anno && (anno.val === undefined || anno.val);
33
34
  }
34
35
  function annotationIsFalse( anno ) { // falsy, but not null (unset)
35
36
  return anno && (anno.val === false || anno.val === 0 || anno.val === '');
@@ -7,9 +7,8 @@ const {
7
7
  EdmTypeFacetNames,
8
8
  EdmPrimitiveTypeMap,
9
9
  } = require('../EdmPrimitiveTypeDefinitions.js');
10
- const { isBuiltinType } = require('../../model/csnUtils');
10
+ const { isBuiltinType, isAnnotationExpression } = require('../../model/csnUtils');
11
11
  const { transformExpression } = require('../../transform/db/applyTransformations.js');
12
- const { xprInAnnoProperties } = require('../../compiler/builtins');
13
12
 
14
13
  /**
15
14
  * Translate a given token stream expression into an edmJson representation
@@ -80,7 +79,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
80
79
  // Error transformer
81
80
  const notADynExpr = (parent, op, xpr, csnPath, parentparent, parentprop, txt) => {
82
81
  error('odata-anno-xpr', location, {
83
- anno, op: txt ??= op, '#': 'notadynexpr',
82
+ anno, op: txt ?? op, '#': 'notadynexpr',
84
83
  });
85
84
  delete parent[op];
86
85
  };
@@ -108,13 +107,13 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
108
107
  // list is a $Collection => []
109
108
  transform.list = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
110
109
  parentparent[parentprop] = xpr.filter(a => a);
111
- transformExpression(parentparent, transform);
110
+ transformExpression(parentparent, parentprop, transform);
112
111
  };
113
112
  // XPR
114
113
  transform.xpr = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
115
114
  // eliminate 'xpr' node by pulling up xpr node to its parent
116
115
  parentparent[parentprop] = xpr;
117
- transformExpression(parentparent, transform);
116
+ transformExpression(parentparent, parentprop, transform);
118
117
  };
119
118
  //----------------------------------
120
119
  // CASE
@@ -145,9 +144,11 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
145
144
  curIf.$If.push(caseExpr[i]);
146
145
  parent.$If = edmIf.$If;
147
146
  delete parent.case;
148
- transformExpression(parent, transform);
147
+ transformExpression(parent, undefined, transform);
148
+ };
149
+ transform.$If = (_parent, _prop, expr) => {
150
+ transformExpression(expr, undefined, transform);
149
151
  };
150
- transform.$If = noOp;
151
152
  //----------------------------------
152
153
  // Cast => $Cast
153
154
  transform.cast = (parent, prop, castExpr, csnPath, parentparent, parentprop) => {
@@ -185,7 +186,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
185
186
 
186
187
 
187
188
  parentparent[parentprop] = castFunc;
188
- transformExpression(parentparent, transform);
189
+ transformExpression(parentparent, parentprop, transform);
189
190
  };
190
191
  //----------------------------------
191
192
  const evalArgs = (argDef, args, propName) => {
@@ -221,7 +222,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
221
222
  evalArgs({ exact }, xpr, prop);
222
223
  parent[opStr] = xpr;
223
224
  delete parent[prop];
224
- transformExpression(parent, transform);
225
+ transformExpression(parent, undefined, transform);
225
226
  };
226
227
  //----------------------------------
227
228
  // LOGICAL
@@ -250,12 +251,12 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
250
251
  evalArgs({ min: 1 }, xpr[1].list, prop);
251
252
  parent.$In = [ xpr[0], ...xpr[1].list ];
252
253
  delete parent[prop];
253
- transformExpression(parent, transform);
254
+ transformExpression(parent, undefined, transform);
254
255
  };
255
256
  transform.$In = noOp;
256
257
  transform.between = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
257
258
  evalArgs({ exact: 2 }, xpr.slice(1), prop);
258
- transformExpression(xpr, transform);
259
+ transformExpression(xpr, undefined, transform);
259
260
  delete parent[prop];
260
261
  parentparent[parentprop]
261
262
  = {
@@ -267,7 +268,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
267
268
  };
268
269
  transform['||'] = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
269
270
  evalArgs({ exact: 2 }, xpr, prop);
270
- transformExpression(xpr, transform);
271
+ transformExpression(xpr, undefined, transform);
271
272
  delete parent[prop];
272
273
  parentparent[parentprop].$Apply = [ { $Function: 'odata.concat' }, ...xpr ];
273
274
  };
@@ -280,7 +281,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
280
281
  else {
281
282
  delete parent[prop];
282
283
  parentparent[parentprop] = xpr;
283
- transformExpression(parentparent, transform);
284
+ transformExpression(parentparent, parentprop, transform);
284
285
  }
285
286
  };
286
287
  transform.$Add = noOp;
@@ -591,7 +592,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
591
592
  delete parent.args;
592
593
  };
593
594
 
594
- const exactArgs = (tgt = parent, x = xpr, count) => {
595
+ const exactArgs = (tgt = parent, x = xpr, count = undefined) => {
595
596
  standard(tgt, x);
596
597
  evalArgs({ exact: count }, tgt[x], xpr);
597
598
  };
@@ -695,7 +696,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
695
696
  // $Record ???
696
697
  $Collection: () => {
697
698
  standard(parentparent, parentprop);
698
- transformExpression(parentparent, transform);
699
+ transformExpression(parentparent, parentprop, transform);
699
700
  },
700
701
  $Path: () => {
701
702
  oneArg(parent, xpr);
@@ -705,7 +706,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
705
706
  anno, op: `${xpr}(…)`, meta: 'string', '#': 'wrongval_meta',
706
707
  });
707
708
  }
708
- transformExpression(parentparent, transform);
709
+ transformExpression(parentparent, parentprop, transform);
709
710
  },
710
711
  $Null: () => {
711
712
  parent[xpr] = true;
@@ -725,7 +726,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
725
726
  funcDef.forEach(f => f());
726
727
  else
727
728
  funcDef();
728
- transformExpression(parent, transform);
729
+ transformExpression(parent, undefined, transform);
729
730
  }
730
731
  else {
731
732
  const funcName = xpr.startsWith('odata.') ? xpr : `odata.${xpr}`;
@@ -741,7 +742,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
741
742
  parentparent[parentprop].$Apply = [ { $Function: funcName }, ...(parent.args || []) ];
742
743
  delete parentparent[parentprop].func;
743
744
  delete parentparent[parentprop].args;
744
- transformExpression(parentparent, transform);
745
+ transformExpression(parentparent, parentprop, transform);
745
746
  }
746
747
  }
747
748
  else {
@@ -753,14 +754,14 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
753
754
  delete parent[prop];
754
755
  };
755
756
 
756
- return transformExpression({ annoVal }, {
757
+ return transformExpression(carrier, anno, {
757
758
  '=': (parent, prop, xpr, csnPath, parentparent, parentprop) => {
758
- if (parent?.['='] !== undefined && xprInAnnoProperties.some(xProp => parent[xProp] !== undefined)) {
759
+ if (isAnnotationExpression(parent)) {
759
760
  delete parent['='];
760
- parentparent[parentprop] = transformExpression({ $edmJson: parseExpr( parent, { array: false }) }, transform);
761
+ parentparent[parentprop] = transformExpression({ $edmJson: parseExpr( parent, { array: false }) }, undefined, transform);
761
762
  }
762
763
  },
763
- }).annoVal;
764
+ });
764
765
  }
765
766
 
766
767
  // Not everything that can occur in OData annotations can be expressed with
@@ -305,7 +305,7 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
305
305
  if (isBetaEnabled(options, 'odataAnnotationExpressions')) {
306
306
  knownAnnos.forEach((knownAnno) => {
307
307
  if (knownAnno.search(/\.\$edmJson\./g) < 0)
308
- carrier[knownAnno] = xpr2edmJson(carrier, knownAnno, location, options, messageFunctions);
308
+ xpr2edmJson(carrier, knownAnno, location, options, messageFunctions);
309
309
  });
310
310
  }
311
311
 
@@ -676,7 +676,7 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
676
676
 
677
677
  // anno is the full <Annotation Term=...>
678
678
  const anno = handleTerm(fullTermName, prefixTree[voc][term], msg);
679
- if (anno !== undefined) {
679
+ if (!anno?.$isInvalid) {
680
680
  // addAnnotationFunc needs AppliesTo message from dictionary to decide where to put the anno
681
681
  const termName = fullTermName.replace(/#(\w+)$/g, ''); // remove qualifier
682
682
  const dictTerm = getDictTerm(termName, msg); // message for unknown term was already issued in handleTerm
@@ -835,6 +835,7 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
835
835
  }
836
836
  else if ( Object.keys(cAnnoValue).filter( x => x[0] !== '@' ).length === 0) {
837
837
  // object consists only of properties starting with "@", no $value
838
+ setProp(oTarget, '$isInvalid', true);
838
839
  message('odata-anno-value', msg.location,
839
840
  { anno: msg.anno(), str: 'base', '#': 'nested' } );
840
841
  }
@@ -1147,8 +1148,17 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
1147
1148
  actualTypeName = obj.$Type;
1148
1149
  if (!getDictType(actualTypeName)) {
1149
1150
  // this type doesn't exist
1150
- message('odata-anno-type', msg.location,
1151
- { anno: msg.anno(), type: actualTypeName, '#': 'unknown' });
1151
+ if (typeof actualTypeName !== 'string') {
1152
+ actualTypeName = JSON.stringify(obj.$Type);
1153
+ message('odata-anno-type', msg.location,
1154
+ {
1155
+ anno: msg.anno(), code: '$Type', rawvalue: actualTypeName, '#': 'literal',
1156
+ });
1157
+ }
1158
+ else {
1159
+ message('odata-anno-type', msg.location,
1160
+ { anno: msg.anno(), type: actualTypeName, '#': 'unknown' });
1161
+ }
1152
1162
  // explicitly mentioned type, render in XML and JSON
1153
1163
  newRecord.setXml({ Type: actualTypeName });
1154
1164
  // unknown dictionary type: can't fully qualify it
@@ -14,7 +14,6 @@ const {
14
14
  cloneCsnNonDict, isEdmPropertyRendered, isBuiltinType, getUtils,
15
15
  } = require('../model/csnUtils');
16
16
  const { checkCSNVersion } = require('../json/csnVersion');
17
- const { makeMessageFunction } = require('../base/messages');
18
17
  const {
19
18
  EdmTypeFacetMap,
20
19
  EdmTypeFacetNames,
@@ -23,21 +22,34 @@ const {
23
22
  const { getEdm } = require('./edm.js');
24
23
 
25
24
  /*
26
- OData V2 spec 06/01/2017 PDF version is available from here:
25
+ OData V2 spec 06/01/2017 PDF version is available here:
27
26
  https://msdn.microsoft.com/en-us/library/dd541474.aspx
28
27
  */
29
28
 
30
- function csn2edm( _csn, serviceName, _options ) {
31
- return csn2edmAll(_csn, _options, [ serviceName ])[serviceName];
29
+ /**
30
+ * @param {CSN.Model} _csn
31
+ * @param {string} serviceName
32
+ * @param {CSN.Options} _options
33
+ * @param {object} messageFunctions Message functions such as `error()`, `info()`, …
34
+ * @return {any}
35
+ */
36
+ function csn2edm( _csn, serviceName, _options, messageFunctions ) {
37
+ return csn2edmAll(_csn, _options, [ serviceName ], messageFunctions)[serviceName];
32
38
  }
33
39
 
34
- function csn2edmAll( _csn, _options, serviceNames = undefined ) {
40
+ /**
41
+ * @param {CSN.Model} _csn
42
+ * @param {CSN.Options} _options
43
+ * @param {string[]|undefined} serviceNames
44
+ * @param {object} messageFunctions Message functions such as `error()`, `info()`, …
45
+ * @return {any}
46
+ */
47
+ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
35
48
  // get us a fresh model copy that we can work with
36
49
  const csn = cloneCsnNonDict(_csn, _options);
37
50
  const special$self = !csn?.definitions?.$self && '$self';
51
+ messageFunctions.setModel(csn);
38
52
 
39
- // use original options for messages; cloned CSN for semantic location
40
- const messageFunctions = makeMessageFunction(csn, _options, 'to.edmx');
41
53
  const {
42
54
  info, warning, error, message, throwWithError,
43
55
  } = messageFunctions;
@@ -630,15 +642,17 @@ function csn2edmAll( _csn, _options, serviceNames = undefined ) {
630
642
  else if (isEdmPropertyRendered(elementCsn, options)) {
631
643
  // CDXCORE-CDXCORE-173
632
644
  // V2: filter @Core.MediaType
633
- if ( options.isV2() && elementCsn['@Core.MediaType']) {
645
+ if (options.isV2() && elementCsn['@Core.MediaType']) {
634
646
  hasStream = elementCsn['@Core.MediaType'];
635
- delete elementCsn['@Core.MediaType'];
647
+ elementCsn['@cds.api.ignore'] = true;
636
648
  // CDXCORE-CDXCORE-177:
637
649
  // V2: don't render element but add attribute 'm:HasStream="true' to EntityType
638
- // V4: render property type 'Edm.Stream'
639
650
  streamProps.push(elementName);
640
651
  }
641
652
  else {
653
+ // V4: render property type 'Edm.Stream' but don't add '@Core.IsURL'
654
+ if ( elementCsn['@Core.MediaType'])
655
+ delete elementCsn['@Core.IsURL'];
642
656
  collectUsedType(elementCsn);
643
657
  props.push(new Edm.Property(v, { Name: elementName }, elementCsn));
644
658
  }
@@ -71,10 +71,9 @@ function inboundQualificationChecks( csn, options, messageFunctions,
71
71
  type = elt;
72
72
  }
73
73
 
74
- if (type.type === 'cds.UUID') {
74
+ if (type.type === 'cds.UUID' && elt['@odata.foreignKey4'] == null) {
75
75
  if (path[1] === defName)
76
76
  assignAnnotation(elt, anno, true);
77
-
78
77
  else if (elt[anno] == null)
79
78
  warning('odata-key-uuid-default-anno', path, { type: type.type, anno, id: `${defName}:${eltPath.join('.')}` });
80
79
  }