@sap/cds-compiler 2.5.2 → 2.11.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 (102) hide show
  1. package/CHANGELOG.md +235 -9
  2. package/bin/cdsc.js +44 -27
  3. package/bin/cdsse.js +1 -0
  4. package/doc/CHANGELOG_BETA.md +37 -3
  5. package/lib/api/.eslintrc.json +2 -0
  6. package/lib/api/main.js +37 -123
  7. package/lib/api/options.js +27 -15
  8. package/lib/api/validate.js +34 -9
  9. package/lib/backends.js +9 -89
  10. package/lib/base/dictionaries.js +2 -1
  11. package/lib/base/keywords.js +32 -2
  12. package/lib/base/message-registry.js +73 -11
  13. package/lib/base/messages.js +86 -30
  14. package/lib/base/model.js +6 -6
  15. package/lib/base/optionProcessorHelper.js +56 -22
  16. package/lib/checks/defaultValues.js +27 -2
  17. package/lib/checks/elements.js +1 -6
  18. package/lib/checks/foreignKeys.js +0 -6
  19. package/lib/checks/managedWithoutKeys.js +17 -0
  20. package/lib/checks/nonexpandableStructured.js +38 -0
  21. package/lib/checks/onConditions.js +9 -45
  22. package/lib/checks/queryNoDbArtifacts.js +25 -7
  23. package/lib/checks/selectItems.js +29 -2
  24. package/lib/checks/types.js +26 -2
  25. package/lib/checks/unknownMagic.js +41 -0
  26. package/lib/checks/utils.js +61 -0
  27. package/lib/checks/validator.js +60 -7
  28. package/lib/compiler/assert-consistency.js +23 -7
  29. package/lib/compiler/base.js +65 -0
  30. package/lib/compiler/builtins.js +30 -1
  31. package/lib/compiler/checks.js +8 -5
  32. package/lib/compiler/definer.js +157 -133
  33. package/lib/compiler/index.js +89 -31
  34. package/lib/compiler/propagator.js +5 -2
  35. package/lib/compiler/resolver.js +375 -185
  36. package/lib/compiler/shared.js +49 -202
  37. package/lib/compiler/utils.js +173 -0
  38. package/lib/edm/annotations/genericTranslation.js +183 -187
  39. package/lib/edm/csn2edm.js +104 -108
  40. package/lib/edm/edm.js +18 -21
  41. package/lib/edm/edmPreprocessor.js +388 -146
  42. package/lib/edm/edmUtils.js +104 -34
  43. package/lib/gen/Dictionary.json +22 -0
  44. package/lib/gen/language.checksum +1 -1
  45. package/lib/gen/language.interp +28 -1
  46. package/lib/gen/language.tokens +79 -69
  47. package/lib/gen/languageLexer.interp +28 -1
  48. package/lib/gen/languageLexer.js +879 -805
  49. package/lib/gen/languageLexer.tokens +71 -62
  50. package/lib/gen/languageParser.js +5330 -4300
  51. package/lib/json/from-csn.js +110 -52
  52. package/lib/json/to-csn.js +434 -120
  53. package/lib/language/antlrParser.js +15 -3
  54. package/lib/language/errorStrategy.js +1 -0
  55. package/lib/language/genericAntlrParser.js +93 -26
  56. package/lib/language/language.g4 +172 -31
  57. package/lib/main.d.ts +216 -19
  58. package/lib/main.js +32 -7
  59. package/lib/model/api.js +78 -0
  60. package/lib/model/csnRefs.js +413 -149
  61. package/lib/model/csnUtils.js +286 -75
  62. package/lib/model/enrichCsn.js +50 -6
  63. package/lib/model/revealInternalProperties.js +22 -5
  64. package/lib/modelCompare/compare.js +39 -21
  65. package/lib/optionProcessor.js +35 -18
  66. package/lib/render/.eslintrc.json +4 -1
  67. package/lib/render/DuplicateChecker.js +9 -6
  68. package/lib/render/toCdl.js +121 -36
  69. package/lib/render/toHdbcds.js +148 -98
  70. package/lib/render/toSql.js +114 -43
  71. package/lib/render/utils/common.js +8 -13
  72. package/lib/render/utils/sql.js +3 -3
  73. package/lib/sql-identifier.js +6 -1
  74. package/lib/transform/db/assertUnique.js +5 -6
  75. package/lib/transform/db/constraints.js +281 -106
  76. package/lib/transform/db/draft.js +11 -8
  77. package/lib/transform/db/expansion.js +584 -0
  78. package/lib/transform/db/flattening.js +341 -0
  79. package/lib/transform/db/groupByOrderBy.js +2 -2
  80. package/lib/transform/db/transformExists.js +345 -65
  81. package/lib/transform/db/views.js +438 -0
  82. package/lib/transform/forHanaNew.js +131 -793
  83. package/lib/transform/forOdataNew.js +30 -24
  84. package/lib/transform/localized.js +39 -10
  85. package/lib/transform/odata/attachPath.js +19 -4
  86. package/lib/transform/odata/generateForeignKeyElements.js +11 -10
  87. package/lib/transform/odata/referenceFlattener.js +60 -39
  88. package/lib/transform/odata/sortByAssociationDependency.js +2 -2
  89. package/lib/transform/odata/structuralPath.js +72 -0
  90. package/lib/transform/odata/structureFlattener.js +19 -18
  91. package/lib/transform/odata/typesExposure.js +22 -12
  92. package/lib/transform/transformUtilsNew.js +144 -78
  93. package/lib/transform/translateAssocsToJoins.js +22 -27
  94. package/lib/transform/universalCsnEnricher.js +67 -0
  95. package/lib/utils/file.js +5 -14
  96. package/lib/utils/moduleResolve.js +6 -8
  97. package/lib/utils/term.js +65 -42
  98. package/lib/utils/timetrace.js +48 -26
  99. package/package.json +1 -1
  100. package/lib/json/walker.js +0 -26
  101. package/lib/transform/sqlite +0 -0
  102. package/lib/utils/string.js +0 -17
@@ -1,9 +1,8 @@
1
1
  'use strict';
2
2
  /* eslint max-statements-per-line:off */
3
3
  const { setProp, isDeprecatedEnabled, isBetaEnabled } = require('../base/model');
4
- const { forEachDefinition, forEachGeneric, forEachMember, forEachMemberRecursively,
4
+ const { forEachDefinition, forEachGeneric, forEachMemberRecursively,
5
5
  isEdmPropertyRendered, getUtils, cloneCsn, isBuiltinType } = require('../model/csnUtils');
6
- const { makeMessageFunction } = require('../base/messages');
7
6
  const edmUtils = require('./edmUtils.js');
8
7
  const typesExposure = require('../transform/odata/typesExposure');
9
8
  const expandCSNToFinalBaseType = require('../transform/odata/toFinalBaseType');
@@ -35,12 +34,11 @@ const {
35
34
  * @param {CSN.Model} csn
36
35
  * @param {object} _options
37
36
  */
38
- function initializeModel(csn, _options)
37
+ function initializeModel(csn, _options, messageFunctions)
39
38
  {
40
39
  if (!_options)
41
40
  throw Error('Please debug me: initializeModel must be invoked with options');
42
41
 
43
- const messageFunctions = makeMessageFunction(csn, _options);
44
42
  const { info, warning, error, throwWithError } = messageFunctions;
45
43
 
46
44
  const csnUtils = getUtils(csn);
@@ -56,7 +54,7 @@ function initializeModel(csn, _options)
56
54
  let options = validateOptions(_options);
57
55
 
58
56
  // Fetch service definitions
59
- const serviceRoots = Object.keys(csn.definitions).reduce((serviceRoots, artName) => {
57
+ const serviceRoots = Object.keys(csn.definitions || {}).reduce((serviceRoots, artName) => {
60
58
  const art = csn.definitions[artName];
61
59
  if(art.kind === 'service') {
62
60
  serviceRoots[artName] = Object.assign(art, { name: artName });
@@ -69,6 +67,9 @@ function initializeModel(csn, _options)
69
67
  function whatsMyServiceRootName(n, self=true) {
70
68
  return serviceRootNames.reduce((rc, sn) => !rc && n && n.startsWith(sn + '.') || (n === sn && self) ? sn : rc, undefined);
71
69
  }
70
+ if(serviceRootNames.length === 0) {
71
+ return [serviceRoots, Object.create(null), whatsMyServiceRootName, options];
72
+ }
72
73
 
73
74
  // Structural CSN inbound QA checks
74
75
  inboundQualificationChecks();
@@ -142,14 +143,18 @@ function initializeModel(csn, _options)
142
143
  // must be run before proxy exposure to avoid potential reference collisions
143
144
  convertExposedTypesOfOtherServicesIntoCrossReferences();
144
145
  // create association target proxies
145
- forEachDefinition(csn, exposeTargetsAsProxiesOrSchemaRefs);
146
+ // Decide if an entity set needs to be constructed or not
147
+ forEachDefinition(csn, [ exposeTargetsAsProxiesOrSchemaRefs, determineEntitySet ]);
148
+ if(options.isV4())
149
+ forEachDefinition(csn, initializeEdmNavPropBindingTargets);
146
150
 
147
151
  // Things that can be done in one pass
148
152
  // Create edmKeyRefPaths
149
- // Decide if an entity set needs to be constructed or not
153
+ // Create NavigationPropertyBindings, requires determineEntitySet
150
154
  // Map /** doc comments */ to @CoreDescription
151
155
  // Artifact identifier spec compliance check (should be run last)
152
- forEachDefinition(csn, [ initializeEdmKeyRefPaths, determineEntitySet, initializeEdmTypesAndDescription, checkArtifactIdentifierAndBoundActions ]);
156
+ forEachDefinition(csn, [ initializeEdmKeyRefPaths, initializeEdmNavPropBindingPaths,
157
+ initializeEdmTypesAndDescription, checkArtifactIdentifierAndBoundActions ]);
153
158
  }
154
159
  return [serviceRoots, schemas, whatsMyServiceRootName, options];
155
160
 
@@ -216,6 +221,9 @@ function initializeModel(csn, _options)
216
221
  if(member.target && dotEntityNameMap[member.target]) {
217
222
  member.target = dotEntityNameMap[member.target];
218
223
  }
224
+ if(member.$path && dotEntityNameMap[member.$path[1]]) {
225
+ member.$path[1] = dotEntityNameMap[member.$path[1]]
226
+ }
219
227
  _rewriteReferencesInActions(member);
220
228
  });
221
229
  // handle unbound action/function and params in views
@@ -334,33 +342,57 @@ function initializeModel(csn, _options)
334
342
  // entity set. Instead try to rewrite the annotation in such a way that it is effective
335
343
  // on the containment navigation property.
336
344
  function initializeContainments(container) {
337
- forEachMemberRecursively(container, (element, elementName) => {
338
- if(isAssociationOrComposition(element) && !element._ignore) {
339
- if(element['@odata.contained']) {
345
+ if(['entity', 'view'].includes(container.kind)) {
346
+ forEachMemberRecursively(container, initContainments,
347
+ [], true, { elementsOnly: true });
348
+ }
349
+
350
+ function initContainments(elt, eltName) {
351
+ if(isAssociationOrComposition(elt) && elt['@odata.contained'] && !elt._ignore) {
340
352
  // Let the containee know its container
341
353
  // (array because the contanee may contained more then once)
342
- let containee = element._target;
343
- if (!containee._containerEntity) {
344
- setProp(containee, '_containerEntity', []);
345
- }
354
+ let containee = elt._target;
355
+ if (!containee._containerEntity)
356
+ setProp(containee, '_containerEntity', []);
346
357
  // add container only once per containee
347
- if (!containee._containerEntity.includes(container.name)) {
348
- containee._containerEntity.push(container.name);
349
- // Mark associations in the containee pointing to the container (i.e. to this entity)
350
- containee.elements && Object.values(containee.elements).forEach( containeeElement => {
351
- if (containeeElement._target && containeeElement._target.name) {
352
- // If this is an association that points to a container (but is not by itself contained,
353
- // which would indicate the top role in a hierarchy) mark it with '_isToContainer'
354
- if (containeeElement._target.name == container.name && !containeeElement['@odata.contained']) {
355
- setProp(containeeElement, '_isToContainer', true);
356
- }
357
- }
358
- });
359
- }
360
- rewriteContainmentAnnotations(container, containee, elementName);
358
+ if (!containee._containerEntity.includes(container.name))
359
+ containee._containerEntity.push(container.name);
360
+ // Mark associations in the containee pointing to the container (i.e. to this entity)
361
+ forEachMemberRecursively(containee, markToContainer,
362
+ [], true, { elementsOnly: true });
363
+ rewriteContainmentAnnotations(container, containee, eltName);
364
+ }
365
+ else {
366
+ // try to find elements to drill down further
367
+ while(elt && !(isBuiltinType(elt.type) || elt.elements)) {
368
+ elt = csn.definitions[elt.type];
369
+ }
370
+ if(elt && elt.elements) {
371
+ forEachMemberRecursively(elt, initContainments,
372
+ [], true, { elementsOnly: true });
361
373
  }
362
374
  }
363
- });
375
+ }
376
+
377
+ function markToContainer(elt) {
378
+ if(elt._target && elt._target.name) {
379
+ // If this is an association that points to the container (but is not by itself contained,
380
+ // which would indicate the top role in a hierarchy) mark it with '_isToContainer'
381
+ if(elt._target.name === container.name && !elt['odata.contained']) {
382
+ setProp(elt, '_isToContainer', true);
383
+ }
384
+ }
385
+ else {
386
+ // try to find elements to drill down further
387
+ while(elt && !(isBuiltinType(elt.type) || elt.elements)) {
388
+ elt = csn.definitions[elt.type];
389
+ }
390
+ if(elt && elt.elements) {
391
+ forEachMemberRecursively(elt, markToContainer,
392
+ [], true, { elementsOnly: true });
393
+ }
394
+ }
395
+ }
364
396
  }
365
397
 
366
398
  // Split an entity with parameters into two entity types with their entity sets,
@@ -417,7 +449,6 @@ function initializeModel(csn, _options)
417
449
  assignProp(parameterCsn, '_SetAttributes',
418
450
  {'@sap.creatable': false, '@sap.updatable': false, '@sap.deletable': false, '@sap.pageable': false });
419
451
 
420
- assignProp(parameterCsn, '$keys', Object.create(null));
421
452
  setProp(parameterCsn, '$isParamEntity', true);
422
453
  setProp(parameterCsn, '$mySchemaName', entityCsn.$mySchemaName);
423
454
 
@@ -435,9 +466,10 @@ function initializeModel(csn, _options)
435
466
  elt.name = n;
436
467
  delete elt.kind;
437
468
  elt.key = true; // params become primary key in parameter entity
438
- parameterCsn.$keys[n] = parameterCsn.elements[n] = elt;
469
+ parameterCsn.elements[n] = elt;
439
470
  });
440
-
471
+ linkAssociationTarget(parameterCsn);
472
+ initializeContainments(parameterCsn);
441
473
  // add assoc to result set, FIXME: is the cardinality correct?
442
474
  parameterCsn.elements[parameterToOriginalAssocName] = {
443
475
  '@odata.contained': true,
@@ -447,6 +479,16 @@ function initializeModel(csn, _options)
447
479
  cardinality: { src: 1, min: 0, max: '*' }
448
480
  };
449
481
  setProp(parameterCsn.elements[parameterToOriginalAssocName], '_target', entityCsn);
482
+ setProp(parameterCsn.elements[parameterToOriginalAssocName], '$path',
483
+ [ 'definitions', parameterEntityName, 'elements', parameterToOriginalAssocName ] );
484
+
485
+ // rewrite $path
486
+ setProp(parameterCsn, '$path', [ 'definitions', parameterEntityName ]);
487
+ forEachMemberRecursively(parameterCsn, (member) => {
488
+ if(member.$path)
489
+ member.$path[1] = parameterEntityName;
490
+ });
491
+
450
492
 
451
493
  csn.definitions[parameterCsn.name] = parameterCsn;
452
494
  // modify the original parameter entity with backlink and new name
@@ -454,7 +496,6 @@ function initializeModel(csn, _options)
454
496
  delete csn.definitions[entityCsn.name];
455
497
  entityCsn.name = originalEntityName;
456
498
  setProp(entityCsn, '$entitySetName', originalEntitySetName);
457
-
458
499
  // add backlink association
459
500
  if(hasBacklink) {
460
501
  entityCsn.elements[backlinkAssocName] = {
@@ -465,6 +506,16 @@ function initializeModel(csn, _options)
465
506
  };
466
507
  setProp(entityCsn.elements[backlinkAssocName], '_selfReferences', []);
467
508
  setProp(entityCsn.elements[backlinkAssocName], '_target', parameterCsn);
509
+ setProp(entityCsn.elements[backlinkAssocName], '$path',
510
+ [ 'definitions', originalEntityName, 'elements', backlinkAssocName ] );
511
+
512
+ // rewrite $path
513
+ if(entityCsn.$path)
514
+ entityCsn.$path[1] = originalEntityName;
515
+ forEachMemberRecursively(entityCsn, (member) => {
516
+ if(member.$path)
517
+ member.$path[1] = originalEntityName;
518
+ });
468
519
  }
469
520
 
470
521
  /*
@@ -492,6 +543,24 @@ function initializeModel(csn, _options)
492
543
  setProp(element, '_parent', struct);
493
544
  }
494
545
 
546
+ // convert $path to path starting at main artifact
547
+ function $path2path(p) {
548
+ const path = [];
549
+ let env = csn;
550
+ for (let i = 0; p && env && i < p.length; i++) {
551
+ const ps = p[i];
552
+ env = env[ps];
553
+ if (env && env.constructor === Object) {
554
+ path.push(ps);
555
+ if(env.items)
556
+ env = env.items;
557
+ if(env.type && !isBuiltinType(env.type) && !env.elements)
558
+ env = csn.definitions[env.type];
559
+ }
560
+ }
561
+ return path;
562
+ }
563
+
495
564
  // Initialize a structured artifact
496
565
  function initializeStructure(def) {
497
566
 
@@ -503,16 +572,17 @@ function initializeModel(csn, _options)
503
572
  let keys = Object.create(null);
504
573
  let validFrom = [], validKey = [];
505
574
 
506
- let structParent = def.items || def;
507
-
508
575
  // Iterate all struct elements
509
- forEachGeneric(structParent, 'elements', (element, elementName) => {
510
- initElement(element, elementName, def);
576
+ forEachMemberRecursively(def.items || def, (element, elementName, prop, path = [], construct) => {
577
+ if(!['elements'].includes(prop))
578
+ return;
579
+
580
+ initElement(element, elementName, construct);
511
581
 
512
582
  if(!['event', 'aspect'].includes(def.kind)) {
513
583
  if(element._parent && element._parent.$mySchemaName) {
514
584
  if(!isODataSimpleIdentifier(elementName)) {
515
- signalIllegalIdentifier(elementName, ['definitions', def.name, 'elements', elementName]);
585
+ signalIllegalIdentifier(elementName, ['definitions', def.name].concat(path));
516
586
  } else if (options.isV2() && /^(_|[0-9])/.test(elementName) && ['view', 'entity'].includes(element._parent.kind)) {
517
587
  // FIXME: Rewrite signalIllegalIdentifier function to be more flexible
518
588
  error(null, ['definitions', def.name, 'elements', elementName], { prop: elementName[0] },
@@ -527,24 +597,24 @@ function initializeModel(csn, _options)
527
597
  if(element['@cds.valid.from']) {
528
598
  validFrom.push(element);
529
599
  }
600
+ //forward annotations from managed association element to its foreign keys
601
+ const elements = construct.items && construct.items.elements || construct.elements;
602
+ forAll(elements[element['@odata.foreignKey4']], (attr, attrName) => {
603
+ if(attrName[0] === '@') {
604
+ element[attrName] = attr;
605
+ }
606
+ });
607
+ // and eventually remove some afterwards:)
608
+ if(options.isV2())
609
+ setSAPSpecificV2AnnotationsToAssociation(element);
530
610
 
531
611
  // initialize an association
532
612
  if(isAssociationOrComposition(element)) {
533
613
  // in case this is a forward assoc, store the backlink partners here, _selfReferences.length > 1 => error
534
614
  assignProp(element, '_selfReferences', []);
535
615
  assignProp(element._target, '$proxies', []);
536
-
537
- //forward annotations from managed association element to its foreign keys
538
- if(element.keys && options.isFlatFormat) {
539
- for(let fk of element.keys) {
540
- forAll(element, (attr, attrName) => {
541
- if(attrName[0] === '@' && fk.$generatedFieldName)
542
- def.elements[fk.$generatedFieldName][attrName] = attr;
543
- });
544
- }
545
- }
546
- // and afterwards eventually remove some :)
547
- setSAPSpecificV2AnnotationsToAssociation(options, element, def);
616
+ // $abspath is used as partner path
617
+ assignProp(element, '$abspath', $path2path(element.$path));
548
618
  }
549
619
 
550
620
  // Collect keys
@@ -552,7 +622,7 @@ function initializeModel(csn, _options)
552
622
  keys[elementName] = element;
553
623
  }
554
624
  applyAppSpecificLateCsnTransformationOnElement(options, element, def, error);
555
- });
625
+ }, [], true, { elementsOnly: true });
556
626
 
557
627
  if(!isDeprecatedEnabled(options, 'v1KeysForTemporal')) {
558
628
  // if artifact has a cds.valid.key mention it as @Core.AlternateKey
@@ -604,14 +674,14 @@ function initializeModel(csn, _options)
604
674
  if(!isStructuredArtifact(struct))
605
675
  return;
606
676
 
607
- forEachMember(struct, element => {
677
+ forEachMemberRecursively(struct.items || struct, (element) => {
608
678
  if (isAssociationOrComposition(element) && !element._ignore) {
609
679
  // setup the constraints object
610
680
  setProp(element, '_constraints', { constraints: Object.create(null), selfs: [], _origins: [], termCount: 0 });
611
681
  // and crack the ON condition
612
- resolveOnConditionAndPrepareConstraints(element, messageFunctions);
682
+ resolveOnConditionAndPrepareConstraints(csn, element, messageFunctions);
613
683
  }
614
- });
684
+ }, [], true, { elementsOnly: true });
615
685
  }
616
686
 
617
687
  /*
@@ -627,13 +697,16 @@ function initializeModel(csn, _options)
627
697
  4) All of this can be revoked with options.renderForeignKeys.
628
698
  */
629
699
  function ignoreProperties(struct) {
630
- forEachGeneric(struct, 'elements', (element) => {
700
+ if(!isStructuredArtifact(struct))
701
+ return;
702
+
703
+ forEachMemberRecursively(struct.items || struct, (element) => {
631
704
  if(!element.target) {
632
705
  if(element['@odata.foreignKey4']) {
633
706
  let isContainerAssoc = false;
634
- let elements = struct.elements;
707
+ let elements = (struct.items || struct).elements;
635
708
  let assoc = undefined;
636
- let paths = element['@odata.foreignKey4'].split('.')
709
+ const paths = element['@odata.foreignKey4'].split('.')
637
710
  for(let p of paths) {
638
711
  assoc = elements[p];
639
712
  if(assoc) // could be that the @odata.foreignKey4 was propagated...
@@ -672,7 +745,7 @@ function initializeModel(csn, _options)
672
745
  // ignore it if option odataContainment is true and no foreign keys should be rendered
673
746
  assignAnnotation(element, '@odata.navigable', false);
674
747
  }
675
- });
748
+ }, [], true, { elementsOnly: true });
676
749
  }
677
750
 
678
751
  /*
@@ -684,9 +757,9 @@ function initializeModel(csn, _options)
684
757
  if(!isStructuredArtifact(struct))
685
758
  return;
686
759
 
687
- forEachMember(struct, element => {
760
+ forEachMemberRecursively(struct.items || struct, (element) => {
688
761
  if (isAssociationOrComposition(element) && !element._ignore) {
689
- finalizeReferentialConstraints(element, options, warning);
762
+ finalizeReferentialConstraints(csn, element, options, info);
690
763
 
691
764
  if(element._constraints._partnerCsn && element.cardinality && element.cardinality.max) {
692
765
  // if this is a partnership and this assoc has a set target cardinality, assign it as source cardinality to the partner
@@ -711,7 +784,7 @@ function initializeModel(csn, _options)
711
784
  }
712
785
  }
713
786
  }
714
- });
787
+ }, [], true, { elementsOnly: true });
715
788
  }
716
789
 
717
790
  /*
@@ -774,7 +847,7 @@ function initializeModel(csn, _options)
774
847
  const globalSchemaPrefix = whatsMyServiceRootName(struct.$mySchemaName);
775
848
  // if this artifact is a service member check its associations
776
849
  if(globalSchemaPrefix) {
777
- forEachGeneric(struct, 'elements', element => {
850
+ forEachGeneric(struct.items || struct, 'elements', element => {
778
851
  if(!isAssociationOrComposition(element) || element._ignore || element['@odata.navigable'] === false)
779
852
  return;
780
853
  /*
@@ -832,7 +905,7 @@ function initializeModel(csn, _options)
832
905
  }
833
906
  else {
834
907
  // fake the target to be proxy
835
- element._target.$externalRef = true;
908
+ setProp(element._target, '$externalRef', true);
836
909
  }
837
910
  }
838
911
  else {
@@ -881,7 +954,6 @@ function initializeModel(csn, _options)
881
954
  setProp(proxy, '$keys', Object.create(null));
882
955
  setProp(proxy, '$hasEntitySet', false);
883
956
  setProp(proxy, '$exposedTypes', Object.create(null));
884
-
885
957
  // copy all annotations of the target to the proxy
886
958
  Object.entries(assoc._target).forEach(([k, v]) => {
887
959
  if(k[0] === '@')
@@ -1028,10 +1100,12 @@ function initializeModel(csn, _options)
1028
1100
  if(!elem.target) {
1029
1101
  type.elements[elemName] = Object.create(null);
1030
1102
  Object.keys(elem).forEach(prop => type.elements[elemName][prop] = elem[prop])
1103
+ type.elements[elemName].notNull = true;
1031
1104
  }
1032
1105
  else {
1033
1106
  type.elements[elemName] = createProxyOrSchemaRefForManagedAssoc(elem);
1034
1107
  }
1108
+ setProp(type.elements[elemName], 'name', elem.name);
1035
1109
  });
1036
1110
  return type;
1037
1111
  }
@@ -1058,6 +1132,7 @@ function initializeModel(csn, _options)
1058
1132
  // art is in the target side, clone it and remove key property
1059
1133
  let cloneArt = cloneCsn(art, options);
1060
1134
  setProp(cloneArt, 'name', art.name);
1135
+ cloneArt.notNull = true;
1061
1136
  delete cloneArt.key;
1062
1137
  newElt.elements[art.name] = cloneArt;
1063
1138
  });
@@ -1147,14 +1222,14 @@ function initializeModel(csn, _options)
1147
1222
  function registerProxy(proxy, element) {
1148
1223
  if(proxy === undefined)
1149
1224
  return undefined;
1150
- const proxyName = globalSchemaPrefix + '.' + proxy.name;
1151
- const schemaName = globalSchemaPrefix + '.' + proxy.$mySchemaName;
1225
+ const fqProxyName = globalSchemaPrefix + '.' + proxy.name;
1226
+ const fqSchemaName = globalSchemaPrefix + '.' + proxy.$mySchemaName;
1152
1227
 
1153
1228
  if(!element._target.$cachedProxy)
1154
1229
  assignProp(element._target, '$cachedProxy', Object.create(null));
1155
1230
  if(getProxyForTargetOf(element)) {
1156
1231
  info(null, ['definitions', struct.name, 'elements', element.name],
1157
- { name: proxyName }, 'Proxy EDM entity type $(NAME) has already been registered');
1232
+ { name: fqProxyName }, 'Proxy EDM entity type $(NAME) has already been registered');
1158
1233
  }
1159
1234
  else
1160
1235
  element._target.$cachedProxy[globalSchemaPrefix] = proxy;
@@ -1164,7 +1239,7 @@ function initializeModel(csn, _options)
1164
1239
  // (that may reside in another subcontext schema), but only once
1165
1240
  const schemaSet = new Set();
1166
1241
  // start with the schema name for the proxy
1167
- schemaSet.add(schemaName);
1242
+ schemaSet.add(fqSchemaName);
1168
1243
  // followed by all namespaces that are potentially exposed by the exposed types
1169
1244
  // don't forget to prepend the global namespace prefix
1170
1245
  // schemas are ordered in csn2edm.js for each service
@@ -1177,12 +1252,16 @@ function initializeModel(csn, _options)
1177
1252
  }
1178
1253
  });
1179
1254
  /** @type {object} */
1180
- const alreadyRegistered = csn.definitions[proxyName]
1255
+ const alreadyRegistered = csn.definitions[fqProxyName]
1181
1256
  if(!alreadyRegistered) {
1182
- csn.definitions[proxyName] = proxy;
1257
+ csn.definitions[fqProxyName] = proxy;
1258
+ setProp(proxy, '$path', ['definitions', fqProxyName]);
1183
1259
  Object.entries(proxy.$exposedTypes).forEach(([tn, v]) => {
1184
- if(csn.definitions[globalSchemaPrefix + '.' + tn] === undefined)
1185
- csn.definitions[globalSchemaPrefix + '.' + tn] = v;
1260
+ const fqtn = globalSchemaPrefix + '.' + tn;
1261
+ if(csn.definitions[fqtn] === undefined) {
1262
+ csn.definitions[fqtn] = v;
1263
+ setProp(v, '$path', ['definitions', fqtn]);
1264
+ }
1186
1265
  });
1187
1266
  info(null, ['definitions', element._parent.name, 'elements', element.name],
1188
1267
  { name: proxy.name }, 'Created proxy EDM entity type $(NAME)');
@@ -1190,16 +1269,16 @@ function initializeModel(csn, _options)
1190
1269
  else if(alreadyRegistered && !alreadyRegistered.$proxy &&
1191
1270
  !['entity', 'view'].includes(alreadyRegistered.kind)) {
1192
1271
  warning(null, ['definitions', element._parent.name, 'elements', element.name],
1193
- { name: proxyName, kind: alreadyRegistered.kind },
1272
+ { name: fqProxyName, kind: alreadyRegistered.kind },
1194
1273
  'No proxy EDM entity type created due to name collision with $(NAME) of kind $(KIND)');
1195
1274
  return undefined;
1196
1275
  }
1197
1276
  }
1198
1277
  else {
1199
1278
  // it's a service reference, just add that reference proxy
1200
- if(!schemas[schemaName]) {
1201
- schemas[schemaName] = proxy;
1202
- schemaNames.push(schemaName);
1279
+ if(!schemas[fqSchemaName]) {
1280
+ schemas[fqSchemaName] = proxy;
1281
+ schemaNames.push(fqSchemaName);
1203
1282
  info(null, ['definitions', struct.name, 'elements', element.name],
1204
1283
  { name: proxy.name }, 'Created EDM namespace reference $(NAME)');
1205
1284
  }
@@ -1244,11 +1323,8 @@ function initializeModel(csn, _options)
1244
1323
  else if(!k.target) {
1245
1324
  struct.$edmKeyPaths.push([kn]);
1246
1325
  }
1247
- // complain about nullable keys in toplevel
1248
- if(k.notNull === false) {
1249
- const pathToElement = ['definitions', struct.name, 'elements', k.name];
1250
- signalErrorForNullableKey(pathToElement);
1251
- }
1326
+ // check toplevel key for spec violations
1327
+ checkKeySpecViolations(k, ['definitions', struct.name, 'elements', k.name]);
1252
1328
  }
1253
1329
  });
1254
1330
  }
@@ -1264,7 +1340,7 @@ function initializeModel(csn, _options)
1264
1340
  If element is of scalar type, return it as an array.
1265
1341
  */
1266
1342
  function produceKeyRefPaths(eltCsn, prefix) {
1267
- let keyPaths = [];
1343
+ const keyPaths = [];
1268
1344
  if(!isEdmPropertyRendered(eltCsn, options)) {
1269
1345
  // let annos = Object.keys(eltCsn).filter(a=>a[0]==='@').join(', ');
1270
1346
  // warning(null, ['definitions', struct.name, 'elements', eltCsn.name ],
@@ -1272,15 +1348,20 @@ function initializeModel(csn, _options)
1272
1348
  return keyPaths;
1273
1349
  }
1274
1350
  // OData requires all elements along the path to be nullable: false (that is either key or notNull)
1275
- let elements = eltCsn.elements || getFinalTypeDef(eltCsn.items && eltCsn.items.type || eltCsn.type).elements;
1351
+
1352
+ const finalType = getFinalTypeDef(eltCsn.items && eltCsn.items.type || eltCsn.type);
1353
+ const elements = eltCsn.elements || eltCsn.items && eltCsn.items.elements ||
1354
+ (finalType && (finalType.elements || finalType.items && finalType.items.elements));
1276
1355
  if(elements) {
1277
1356
  Object.entries(elements).forEach(([eltName, elt]) => {
1278
- keyPaths.push(...produceKeyRefPaths(elt, prefix + options.pathDelimiter + eltName));
1279
- if(elt.notNull === false && !isBuiltinType(elt)) {
1280
- const odataKeyPath = `${prefix}/${eltName}`;
1357
+ const newRefs = produceKeyRefPaths(elt, prefix + options.pathDelimiter + eltName);
1358
+ if(newRefs.length) {
1359
+ keyPaths.push(...newRefs);
1360
+ // check path step key for spec violations
1361
+ const pathSegment = `${prefix}/${eltName}`;
1281
1362
  // we want to point to the element in the entity which is the first path step
1282
- const pathToMaliciousKey = struct.$path.concat(['elements']).concat(odataKeyPath.split('/')[0]);
1283
- signalErrorForNullableKey(pathToMaliciousKey, odataKeyPath);
1363
+ const location = struct.$path.concat(['elements']).concat(pathSegment.split('/')[0]);
1364
+ checkKeySpecViolations(elt, location, pathSegment);
1284
1365
  }
1285
1366
  });
1286
1367
  }
@@ -1311,17 +1392,169 @@ function initializeModel(csn, _options)
1311
1392
  return keyPaths;
1312
1393
  }
1313
1394
 
1314
- function signalErrorForNullableKey(pathToElement, keyElement) {
1315
- error(null, pathToElement,
1316
- {name: keyElement, '#': keyElement ? 'std' : 'scalar'},
1317
- {
1318
- std: 'Key element $(NAME) must not be nullable',
1319
- scalar: 'Key element must not be nullable'
1395
+ function checkKeySpecViolations(elt, location, pathSegment) {
1396
+ // Nullability
1397
+ if((!elt.key && (elt.notNull === undefined || elt.notNull === false)) ||
1398
+ elt.key && (elt.notNull !== undefined && elt.notNull === false)) {
1399
+ error('odata-spec-violation-key-null', location,
1400
+ {name: pathSegment, '#': pathSegment ? 'std' : 'scalar'});
1401
+ }
1402
+ // many
1403
+ let type = elt.items || elt.type && !isBuiltinType(elt.type) && getFinalTypeDef(elt.type).items;
1404
+ if(type) {
1405
+ error('odata-spec-violation-key-array', location,
1406
+ {name: pathSegment, '#': pathSegment ? 'std' : 'scalar'});
1407
+ }
1408
+ // type
1409
+ if(!elt.elements) {
1410
+ if(!type)
1411
+ type = isBuiltinType(elt.type) ? elt : csn.definitions[elt.type];
1412
+
1413
+ // check for legal scalar types, proxy exposed structured types are not resolvable in CSN
1414
+ // V2 allows any Edm.PrimitiveType (even Double and Binary), V4 is more specific:
1415
+ if(options.isV4() && type && !isAssociationOrComposition(type) && isBuiltinType(type.type)) {
1416
+ const edmType = edmUtils.mapCdsToEdmType(type);
1417
+ const legalEdmTypes = [
1418
+ 'Edm.Boolean', 'Edm.Byte', 'Edm.Date', 'Edm.DateTimeOffset', 'Edm.Decimal', 'Edm.Duration',
1419
+ 'Edm.Guid', 'Edm.Int16', 'Edm.Int32', 'Edm.Int64', 'Edm.SByte', 'Edm.String', 'Edm.TimeOfDay' ];
1420
+ if(!legalEdmTypes.includes(edmType)) {
1421
+ warning('odata-spec-violation-key-type', location,
1422
+ {name: pathSegment, type: type.type, id: edmType, '#': pathSegment ? 'std' : 'scalar'});
1423
+ }
1320
1424
  }
1321
- );
1425
+ }
1322
1426
  }
1323
1427
  }
1324
1428
 
1429
+ /*
1430
+ Calculate all reachable entity set paths for a given navigation start point
1431
+
1432
+ Rule: First non-containment association terminates Path, if association is
1433
+ containment enabling assoc, Target is own Struct/ plus the path down to the
1434
+ n-2nd path segment (which is the path to the n-1st implicit entity set).
1435
+
1436
+ Example:
1437
+ entity Header {
1438
+ items: composition of many {
1439
+ toF: association to F;
1440
+ subitems: composition of many {
1441
+ toG: association to G;
1442
+ subitems: composition of many {
1443
+ toG: association to G;
1444
+ };
1445
+ }
1446
+ }
1447
+ }
1448
+ Must produce:
1449
+ Path="items/up_" Target="Header"/>
1450
+ Path="items/toF" Target="F"/>
1451
+ Path="items/subitems/up_" Target="Header/items"/>
1452
+ Path="items/subitems/toG" Target="G"/>
1453
+ Path="items/subitems/subitems/up_" Target="Header/items/subitems"/>
1454
+ Path="items/subitems/subitems/toG" Target="G"/>
1455
+ */
1456
+ function initializeEdmNavPropBindingTargets(struct) {
1457
+ if(options.isV4() && struct.$mySchemaName && struct.$hasEntitySet) {
1458
+ forEachGeneric(struct.items || struct, 'elements', (element) => {
1459
+ produceTargetPath([edmUtils.getBaseName(struct.name)], element, struct);
1460
+ });
1461
+ }
1462
+
1463
+ function produceTargetPath(prefix, elt, curDef) {
1464
+ const newPrefix = [...prefix, elt.name];
1465
+ if(isEdmPropertyRendered(elt, options)) {
1466
+ // Assoc can never be a derived TypeDefinition, no need to
1467
+ // unroll derived type chains for assocs
1468
+ if(isAssociationOrComposition(elt) && !elt.$touched) {
1469
+ if(!elt._target.$edmTgtPaths)
1470
+ setProp(elt._target, '$edmTgtPaths', []);
1471
+ if(!elt._target.$hasEntitySet && !elt._isToContainer && curDef !== elt._target) {
1472
+ // follow elements in the target but avoid cycles
1473
+ setProp(elt, '$touched', true);
1474
+ elt._target.$edmTgtPaths.push(newPrefix);
1475
+ Object.values(elt._target.elements).forEach(e => produceTargetPath(newPrefix, e, elt._target));
1476
+ delete elt.$touched;
1477
+ }
1478
+ }
1479
+ else {
1480
+ // try to find elements to drill down further
1481
+ while(elt && !(isBuiltinType(elt.type) || elt.elements)) {
1482
+ elt = csn.definitions[elt.type];
1483
+ }
1484
+ elt && elt.elements && Object.values(elt.elements).forEach(e => produceTargetPath(newPrefix, e, curDef));
1485
+ }
1486
+ }
1487
+ }
1488
+ }
1489
+
1490
+ function initializeEdmNavPropBindingPaths(struct) {
1491
+ if(options.isV4() && struct.$mySchemaName && struct.$hasEntitySet) {
1492
+ let npbs = [];
1493
+ forEachGeneric(struct.items || struct, 'elements', (element) => {
1494
+ npbs = npbs.concat(produceNavigationPath(element, struct));
1495
+ });
1496
+ setProp(struct, '$edmNPBs', npbs);
1497
+ }
1498
+
1499
+ // collect all paths originating from this element that end up in an entity set
1500
+ function produceNavigationPath(elt, curDef) {
1501
+ let npbs = [];
1502
+ const prefix = elt.name;
1503
+ if(isEdmPropertyRendered(elt, options)) {
1504
+ // Assoc can never be a derived TypeDefinition, no need to
1505
+ // unroll derived type chains for assocs
1506
+ if(isAssociationOrComposition(elt) && !elt.$touched) {
1507
+ // drill into target only if
1508
+ // 1) target has no entity set and this assoc is not going to the container
1509
+ // 2) current definition and target are the same (cycle)
1510
+ if(!elt._target.$hasEntitySet && !elt._isToContainer && curDef !== elt._target) {
1511
+ // follow elements in the target but avoid cycles
1512
+ setProp(elt, '$touched', true);
1513
+ Object.values(elt._target.elements).forEach(e => npbs = npbs.concat(produceNavigationPath(e, elt._target)));
1514
+ delete elt.$touched;
1515
+ }
1516
+ else if(!(options.odataContainment && options.isV4() && elt['@odata.contained'])) {
1517
+ // end point reached but must not be an external reference nor a proxy nor a composition itself
1518
+ // last assoc step must not be to-n and target a singleton
1519
+ let p = undefined;
1520
+ if (!elt._target.$externalRef &&
1521
+ !(edmUtils.isToMany(elt) && edmUtils.isSingleton(elt._target, options.isV4()))) {
1522
+ if(elt._target.$edmTgtPaths && elt._target.$edmTgtPaths.length) {
1523
+ p = elt._target.$edmTgtPaths.find(p => p[0] === edmUtils.getBaseName(struct.name)) || elt._target.$edmTgtPaths[0];
1524
+ }
1525
+ else if(elt._target.$hasEntitySet) {
1526
+ const baseName = edmUtils.getBaseName(elt._target.$entitySetName || elt._target.name);
1527
+ // if own struct and target have a set they either are in the same $mySchemaName or not
1528
+ // if target is in another schema, target the full qualified entity set
1529
+ p = (elt._target.$mySchemaName === struct.$mySchemaName) ?
1530
+ [ baseName ] : [elt._target.$mySchemaName + '.EntityContainer', baseName];
1531
+ }
1532
+ if(p) {
1533
+ // if own struct and target have a set they either are in the same $mySchemaName or not
1534
+ // if target is in another schema, target the full qualified entity set
1535
+ const npb = {
1536
+ Path: elt.name,
1537
+ Target: p.join('/')
1538
+ };
1539
+ npbs.push( npb );
1540
+ }
1541
+ }
1542
+ // Do not prepend prefix here!
1543
+ return npbs;
1544
+ }
1545
+ }
1546
+ else {
1547
+ // try to find elements to drill down further
1548
+ while(elt && !(isBuiltinType(elt.type) || elt.elements)) {
1549
+ elt = csn.definitions[elt.type];
1550
+ }
1551
+ elt && elt.elements && Object.values(elt.elements).forEach(e => npbs = npbs.concat(produceNavigationPath(e, curDef)));
1552
+ }
1553
+ }
1554
+ npbs.forEach(p => p.Path = prefix + '/' + p.Path );
1555
+ return npbs;
1556
+ }
1557
+ }
1325
1558
 
1326
1559
  function determineEntitySet(struct) {
1327
1560
  // if this is an entity or a view, determine if an entity set is required or not
@@ -1532,38 +1765,51 @@ function initializeModel(csn, _options)
1532
1765
  // short cut annotation @readonly that gets expanded and can be safely remapped.
1533
1766
  function rewriteContainmentAnnotations(container, containee, assocName) {
1534
1767
  // rectify Restrictions to NavigationRestrictions
1535
- if(options.isV4() && container['@Capabilities.NavigationRestrictions'] === undefined) {
1536
- let navRestr = {
1537
- RestrictedProperties: [
1538
- {
1539
- NavigationProperty: assocName
1540
- }
1541
- ]
1542
- };
1543
- let hasRestrictions = false;
1544
- if(containee['@Capabilities.DeleteRestrictions.Deletable'] !== undefined) {
1545
- navRestr.RestrictedProperties[0].DeleteRestrictions =
1546
- { 'Deletable': containee['@Capabilities.DeleteRestrictions.Deletable'] };
1547
- hasRestrictions = true;
1768
+ if(options.isV4()) {
1769
+ let navPropEntry;
1770
+ let hasEntry = false;
1771
+ let newEntry = false;
1772
+ const anno = '@Capabilities.NavigationRestrictions.RestrictedProperties';
1773
+ let resProps = container[anno];
1774
+ // merge into existing anno, if available
1775
+ if(resProps) {
1776
+ navPropEntry = resProps.find(p => p.NavigationProperty && p.NavigationProperty['='] === assocName);
1777
+ hasEntry = !!navPropEntry;
1548
1778
  }
1549
- if(containee['@Capabilities.InsertRestrictions.Insertable'] !== undefined) {
1550
- navRestr.RestrictedProperties[0].InsertRestrictions =
1551
- { 'Insertable': containee['@Capabilities.InsertRestrictions.Insertable'] };
1552
- hasRestrictions = true;
1779
+ if(!navPropEntry) {
1780
+ navPropEntry = { NavigationProperty: { '=': assocName } };
1553
1781
  }
1554
- if(containee['@Capabilities.UpdateRestrictions.Updatable'] !== undefined) {
1555
- navRestr.RestrictedProperties[0].UpdateRestrictions =
1556
- { 'Updatable': containee['@Capabilities.UpdateRestrictions.Updatable'] };
1557
- hasRestrictions = true;
1782
+
1783
+ const props = Object.entries(containee);
1784
+
1785
+ const merge = (prefix) => {
1786
+ const prop = prefix.split('.')[1];
1787
+ // don't overwrite existing restrictions
1788
+ if(!navPropEntry[prop]) {
1789
+ // Filter properties with prefix and reduce them into a new dictionary
1790
+ const o = props.filter(p => p[0].startsWith(prefix+'.')).reduce((a,c) => {
1791
+ a[c[0].replace(prefix+'.', '')] = c[1];
1792
+ return a;
1793
+ }, { });
1794
+ // if dictionary has entries, add them to navPropEnty
1795
+ if(Object.keys(o).length) {
1796
+ navPropEntry[prop] = o;
1797
+ newEntry = true;
1798
+ }
1799
+ }
1558
1800
  }
1559
- //'@Capabilities.ReadRestrictions.Readable'
1560
- if(containee['@Capabilities.ReadRestrictions.Readable'] !== undefined) {
1561
- navRestr.RestrictedProperties[0].ReadRestrictions =
1562
- { 'Readable': containee['@Capabilities.ReadRestrictions.Readable'] };
1563
- hasRestrictions = true;
1801
+ merge('@Capabilities.DeleteRestrictions');
1802
+ merge('@Capabilities.InsertRestrictions');
1803
+ merge('@Capabilities.UpdateRestrictions');
1804
+ merge('@Capabilities.ReadRestrictions');
1805
+
1806
+ if(newEntry) {
1807
+ if(!hasEntry) {
1808
+ if(!resProps)
1809
+ resProps = container[anno] = [ ];
1810
+ resProps.push(navPropEntry);
1811
+ }
1564
1812
  }
1565
- if(hasRestrictions)
1566
- container['@Capabilities.NavigationRestrictions'] = navRestr;
1567
1813
  }
1568
1814
  }
1569
1815
 
@@ -1772,7 +2018,7 @@ function applyAppSpecificLateCsnTransformationOnStructure(options, struct, error
1772
2018
  let key = { name: keyName, key : true, type : 'cds.String', '@sap.sortable':false, '@sap.filterable':false, '@UI.Hidden': true };
1773
2019
  elements[keyName] = key;
1774
2020
  setProp(struct, '$keys',{ [keyName] : key } );
1775
- forEachGeneric(struct, 'elements', (e,n) =>
2021
+ forEachGeneric(struct.items || struct, 'elements', (e,n) =>
1776
2022
  {
1777
2023
  if(e.key) delete e.key;
1778
2024
  elements[n] = e;
@@ -1912,19 +2158,17 @@ function setSAPSpecificV2AnnotationsToEntitySet(options, carrier) {
1912
2158
  }
1913
2159
  }
1914
2160
 
1915
- function setSAPSpecificV2AnnotationsToAssociation(options, carrier, struct) {
1916
- if(!options.isV2())
1917
- return;
2161
+ function setSAPSpecificV2AnnotationsToAssociation(carrier) {
1918
2162
  // documented in https://wiki.scn.sap.com/wiki/display/EmTech/SAP+Annotations+for+OData+Version+2.0
1919
2163
  const SetAttributes = {
1920
2164
  // Applicable to NavProp and foreign keys, add to AssociationSet
1921
- '@sap.creatable' : (struct, c,pn, pv) => { addToSetAttr(struct, c, pn, pv, false); },
2165
+ '@sap.creatable' : (c, pn, pv) => { addToAssociationSet(c, pn, pv, false); },
1922
2166
  // Not applicable to NavProp, applicable to foreign keys, add to AssociationSet
1923
- '@sap.updatable' : addToSetAttr,
2167
+ '@sap.updatable' : addToAssociationSet,
1924
2168
  // Not applicable to NavProp, not applicable to foreign key, add to AssociationSet
1925
- '@sap.deletable': (struct, c, pn, pv) => {
1926
- addToSetAttr(struct, c, pn, pv);
1927
- removeFromForeignKey(struct, c, pn);
2169
+ '@sap.deletable': (c, pn, pv) => {
2170
+ addToAssociationSet(c, pn, pv);
2171
+ removeFromForeignKey(c, pn);
1928
2172
  },
1929
2173
  // applicable to NavProp, not applicable to foreign keys, not applicable to AssociationSet
1930
2174
  '@sap.creatable.path': removeFromForeignKey,
@@ -1932,24 +2176,22 @@ function setSAPSpecificV2AnnotationsToAssociation(options, carrier, struct) {
1932
2176
  };
1933
2177
 
1934
2178
  Object.entries(carrier).forEach(([p, v]) => {
1935
- (SetAttributes[p] || function() {/* no-op */})(struct, carrier, p, v);
2179
+ (SetAttributes[p] || function() {/* no-op */})(carrier, p, v);
1936
2180
  });
1937
2181
 
1938
- function addToSetAttr(struct, carrier, propName, propValue, removeFromType=true) {
1939
- assignProp(carrier, '_SetAttributes', Object.create(null));
1940
- assignAnnotation(carrier._SetAttributes, propName, propValue);
1941
- if(removeFromType) {
1942
- delete carrier[propName];
2182
+ function addToAssociationSet(carrier, propName, propValue, removeFromType=true) {
2183
+ if(isAssociationOrComposition(carrier)) {
2184
+ assignProp(carrier, '_SetAttributes', Object.create(null));
2185
+ assignAnnotation(carrier._SetAttributes, propName, propValue);
2186
+ if(removeFromType) {
2187
+ delete carrier[propName];
2188
+ }
1943
2189
  }
1944
2190
  }
1945
2191
 
1946
- function removeFromForeignKey(struct, carrier, propName) {
1947
- if(carrier.target && carrier.keys) {
1948
- struct.elements && Object.values(struct.elements).forEach(e => {
1949
- if(e['@odata.foreignKey4'] === carrier.name) {
1950
- delete e[propName];
1951
- }
1952
- });
2192
+ function removeFromForeignKey(carrier, propName) {
2193
+ if(carrier['@odata.foreignKey4'] && carrier[propName] !== undefined) {
2194
+ delete carrier[propName];
1953
2195
  }
1954
2196
  }
1955
2197
  }