@sap/cds-compiler 2.4.4 → 2.10.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 (106) hide show
  1. package/CHANGELOG.md +241 -1
  2. package/bin/.eslintrc.json +17 -0
  3. package/bin/cds_update_identifiers.js +8 -7
  4. package/bin/cdsc.js +180 -132
  5. package/bin/cdshi.js +18 -11
  6. package/bin/cdsse.js +38 -32
  7. package/bin/cdsv2m.js +8 -7
  8. package/doc/CHANGELOG_BETA.md +36 -1
  9. package/lib/api/main.js +81 -100
  10. package/lib/api/options.js +17 -11
  11. package/lib/api/validate.js +12 -8
  12. package/lib/backends.js +0 -81
  13. package/lib/base/keywords.js +32 -2
  14. package/lib/base/location.js +2 -2
  15. package/lib/base/message-registry.js +66 -4
  16. package/lib/base/messages.js +84 -27
  17. package/lib/base/model.js +2 -61
  18. package/lib/checks/arrayOfs.js +0 -1
  19. package/lib/checks/defaultValues.js +27 -2
  20. package/lib/checks/elements.js +1 -6
  21. package/lib/checks/enricher.js +8 -2
  22. package/lib/checks/foreignKeys.js +0 -6
  23. package/lib/checks/managedWithoutKeys.js +17 -0
  24. package/lib/checks/nonexpandableStructured.js +38 -0
  25. package/lib/checks/onConditions.js +9 -45
  26. package/lib/checks/queryNoDbArtifacts.js +27 -9
  27. package/lib/checks/selectItems.js +25 -2
  28. package/lib/checks/types.js +26 -2
  29. package/lib/checks/unknownMagic.js +38 -0
  30. package/lib/checks/utils.js +61 -0
  31. package/lib/checks/validator.js +66 -13
  32. package/lib/compiler/assert-consistency.js +24 -12
  33. package/lib/compiler/builtins.js +2 -0
  34. package/lib/compiler/checks.js +6 -4
  35. package/lib/compiler/definer.js +101 -39
  36. package/lib/compiler/index.js +88 -59
  37. package/lib/compiler/resolver.js +455 -209
  38. package/lib/compiler/shared.js +57 -33
  39. package/lib/edm/annotations/genericTranslation.js +183 -187
  40. package/lib/edm/csn2edm.js +128 -99
  41. package/lib/edm/edm.js +18 -21
  42. package/lib/edm/edmPreprocessor.js +361 -127
  43. package/lib/edm/edmUtils.js +103 -33
  44. package/lib/gen/Dictionary.json +74 -28
  45. package/lib/gen/language.checksum +1 -1
  46. package/lib/gen/language.interp +18 -4
  47. package/lib/gen/language.tokens +124 -118
  48. package/lib/gen/languageLexer.interp +13 -1
  49. package/lib/gen/languageLexer.js +870 -839
  50. package/lib/gen/languageLexer.tokens +116 -111
  51. package/lib/gen/languageParser.js +5894 -5614
  52. package/lib/json/from-csn.js +152 -67
  53. package/lib/json/to-csn.js +334 -135
  54. package/lib/language/antlrParser.js +4 -3
  55. package/lib/language/errorStrategy.js +1 -0
  56. package/lib/language/genericAntlrParser.js +24 -14
  57. package/lib/language/language.g4 +188 -128
  58. package/lib/main.d.ts +435 -0
  59. package/lib/main.js +31 -7
  60. package/lib/model/api.js +78 -0
  61. package/lib/model/csnRefs.js +463 -187
  62. package/lib/model/csnUtils.js +280 -136
  63. package/lib/model/enrichCsn.js +75 -4
  64. package/lib/model/revealInternalProperties.js +2 -1
  65. package/lib/modelCompare/compare.js +70 -25
  66. package/lib/optionProcessor.js +13 -10
  67. package/lib/render/.eslintrc.json +4 -1
  68. package/lib/render/DuplicateChecker.js +8 -5
  69. package/lib/render/toCdl.js +123 -40
  70. package/lib/render/toHdbcds.js +156 -65
  71. package/lib/render/toSql.js +87 -11
  72. package/lib/render/utils/common.js +55 -9
  73. package/lib/render/utils/sql.js +3 -3
  74. package/lib/sql-identifier.js +6 -1
  75. package/lib/transform/{sql → db}/.eslintrc.json +0 -0
  76. package/lib/transform/{sql → db}/assertUnique.js +7 -8
  77. package/lib/transform/{sql → db}/constraints.js +35 -20
  78. package/lib/transform/db/draft.js +353 -0
  79. package/lib/transform/db/expansion.js +582 -0
  80. package/lib/transform/db/flattening.js +325 -0
  81. package/lib/transform/{sql → db}/groupByOrderBy.js +8 -16
  82. package/lib/transform/{sql → db}/helpers.js +0 -0
  83. package/lib/transform/{sql → db}/transformExists.js +256 -60
  84. package/lib/transform/forHanaNew.js +216 -765
  85. package/lib/transform/forOdataNew.js +60 -56
  86. package/lib/transform/localized.js +48 -26
  87. package/lib/transform/odata/attachPath.js +19 -4
  88. package/lib/transform/odata/expandStructKeysInAssociations.js +2 -2
  89. package/lib/transform/odata/generateForeignKeyElements.js +13 -12
  90. package/lib/transform/odata/referenceFlattener.js +60 -36
  91. package/lib/transform/odata/sortByAssociationDependency.js +4 -4
  92. package/lib/transform/odata/structuralPath.js +76 -0
  93. package/lib/transform/odata/structureFlattener.js +21 -22
  94. package/lib/transform/odata/toFinalBaseType.js +5 -5
  95. package/lib/transform/odata/typesExposure.js +27 -17
  96. package/lib/transform/odata/utils.js +2 -2
  97. package/lib/transform/transformUtilsNew.js +141 -77
  98. package/lib/transform/translateAssocsToJoins.js +17 -14
  99. package/lib/transform/universalCsnEnricher.js +67 -0
  100. package/lib/utils/file.js +0 -11
  101. package/lib/utils/moduleResolve.js +6 -8
  102. package/lib/utils/timetrace.js +6 -1
  103. package/package.json +2 -1
  104. package/lib/base/deepCopy.js +0 -66
  105. package/lib/json/walker.js +0 -26
  106. 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);
@@ -142,14 +140,18 @@ function initializeModel(csn, _options)
142
140
  // must be run before proxy exposure to avoid potential reference collisions
143
141
  convertExposedTypesOfOtherServicesIntoCrossReferences();
144
142
  // create association target proxies
145
- forEachDefinition(csn, exposeTargetsAsProxiesOrSchemaRefs);
143
+ // Decide if an entity set needs to be constructed or not
144
+ forEachDefinition(csn, [ exposeTargetsAsProxiesOrSchemaRefs, determineEntitySet ]);
145
+ if(options.isV4())
146
+ forEachDefinition(csn, initializeEdmNavPropBindingTargets);
146
147
 
147
148
  // Things that can be done in one pass
148
149
  // Create edmKeyRefPaths
149
- // Decide if an entity set needs to be constructed or not
150
+ // Create NavigationPropertyBindings, requires determineEntitySet
150
151
  // Map /** doc comments */ to @CoreDescription
151
152
  // Artifact identifier spec compliance check (should be run last)
152
- forEachDefinition(csn, [ initializeEdmKeyRefPaths, determineEntitySet, initializeEdmTypesAndDescription, checkArtifactIdentifierAndBoundActions ]);
153
+ forEachDefinition(csn, [ initializeEdmKeyRefPaths, initializeEdmNavPropBindingPaths,
154
+ initializeEdmTypesAndDescription, checkArtifactIdentifierAndBoundActions ]);
153
155
  }
154
156
  return [serviceRoots, schemas, whatsMyServiceRootName, options];
155
157
 
@@ -216,6 +218,9 @@ function initializeModel(csn, _options)
216
218
  if(member.target && dotEntityNameMap[member.target]) {
217
219
  member.target = dotEntityNameMap[member.target];
218
220
  }
221
+ if(member.$path && dotEntityNameMap[member.$path[1]]) {
222
+ member.$path[1] = dotEntityNameMap[member.$path[1]]
223
+ }
219
224
  _rewriteReferencesInActions(member);
220
225
  });
221
226
  // handle unbound action/function and params in views
@@ -334,33 +339,57 @@ function initializeModel(csn, _options)
334
339
  // entity set. Instead try to rewrite the annotation in such a way that it is effective
335
340
  // on the containment navigation property.
336
341
  function initializeContainments(container) {
337
- forEachMemberRecursively(container, (element, elementName) => {
338
- if(isAssociationOrComposition(element) && !element._ignore) {
339
- if(element['@odata.contained']) {
342
+ if(['entity', 'view'].includes(container.kind)) {
343
+ forEachMemberRecursively(container, initContainments,
344
+ [], true, { elementsOnly: true });
345
+ }
346
+
347
+ function initContainments(elt, eltName) {
348
+ if(isAssociationOrComposition(elt) && elt['@odata.contained'] && !elt._ignore) {
340
349
  // Let the containee know its container
341
350
  // (array because the contanee may contained more then once)
342
- let containee = element._target;
343
- if (!containee._containerEntity) {
344
- setProp(containee, '_containerEntity', []);
345
- }
351
+ let containee = elt._target;
352
+ if (!containee._containerEntity)
353
+ setProp(containee, '_containerEntity', []);
346
354
  // 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);
355
+ if (!containee._containerEntity.includes(container.name))
356
+ containee._containerEntity.push(container.name);
357
+ // Mark associations in the containee pointing to the container (i.e. to this entity)
358
+ forEachMemberRecursively(containee, markToContainer,
359
+ [], true, { elementsOnly: true });
360
+ rewriteContainmentAnnotations(container, containee, eltName);
361
+ }
362
+ else {
363
+ // try to find elements to drill down further
364
+ while(elt && !(isBuiltinType(elt.type) || elt.elements)) {
365
+ elt = csn.definitions[elt.type];
366
+ }
367
+ if(elt && elt.elements) {
368
+ forEachMemberRecursively(elt, initContainments,
369
+ [], true, { elementsOnly: true });
361
370
  }
362
371
  }
363
- });
372
+ }
373
+
374
+ function markToContainer(elt) {
375
+ if(elt._target && elt._target.name) {
376
+ // If this is an association that points to the container (but is not by itself contained,
377
+ // which would indicate the top role in a hierarchy) mark it with '_isToContainer'
378
+ if(elt._target.name === container.name && !elt['odata.contained']) {
379
+ setProp(elt, '_isToContainer', true);
380
+ }
381
+ }
382
+ else {
383
+ // try to find elements to drill down further
384
+ while(elt && !(isBuiltinType(elt.type) || elt.elements)) {
385
+ elt = csn.definitions[elt.type];
386
+ }
387
+ if(elt && elt.elements) {
388
+ forEachMemberRecursively(elt, markToContainer,
389
+ [], true, { elementsOnly: true });
390
+ }
391
+ }
392
+ }
364
393
  }
365
394
 
366
395
  // Split an entity with parameters into two entity types with their entity sets,
@@ -417,7 +446,6 @@ function initializeModel(csn, _options)
417
446
  assignProp(parameterCsn, '_SetAttributes',
418
447
  {'@sap.creatable': false, '@sap.updatable': false, '@sap.deletable': false, '@sap.pageable': false });
419
448
 
420
- assignProp(parameterCsn, '$keys', Object.create(null));
421
449
  setProp(parameterCsn, '$isParamEntity', true);
422
450
  setProp(parameterCsn, '$mySchemaName', entityCsn.$mySchemaName);
423
451
 
@@ -435,9 +463,10 @@ function initializeModel(csn, _options)
435
463
  elt.name = n;
436
464
  delete elt.kind;
437
465
  elt.key = true; // params become primary key in parameter entity
438
- parameterCsn.$keys[n] = parameterCsn.elements[n] = elt;
466
+ parameterCsn.elements[n] = elt;
439
467
  });
440
-
468
+ linkAssociationTarget(parameterCsn);
469
+ initializeContainments(parameterCsn);
441
470
  // add assoc to result set, FIXME: is the cardinality correct?
442
471
  parameterCsn.elements[parameterToOriginalAssocName] = {
443
472
  '@odata.contained': true,
@@ -447,6 +476,16 @@ function initializeModel(csn, _options)
447
476
  cardinality: { src: 1, min: 0, max: '*' }
448
477
  };
449
478
  setProp(parameterCsn.elements[parameterToOriginalAssocName], '_target', entityCsn);
479
+ setProp(parameterCsn.elements[parameterToOriginalAssocName], '$path',
480
+ [ 'definitions', parameterEntityName, 'elements', parameterToOriginalAssocName ] );
481
+
482
+ // rewrite $path
483
+ setProp(parameterCsn, '$path', [ 'definitions', parameterEntityName ]);
484
+ forEachMemberRecursively(parameterCsn, (member) => {
485
+ if(member.$path)
486
+ member.$path[1] = parameterEntityName;
487
+ });
488
+
450
489
 
451
490
  csn.definitions[parameterCsn.name] = parameterCsn;
452
491
  // modify the original parameter entity with backlink and new name
@@ -454,7 +493,6 @@ function initializeModel(csn, _options)
454
493
  delete csn.definitions[entityCsn.name];
455
494
  entityCsn.name = originalEntityName;
456
495
  setProp(entityCsn, '$entitySetName', originalEntitySetName);
457
-
458
496
  // add backlink association
459
497
  if(hasBacklink) {
460
498
  entityCsn.elements[backlinkAssocName] = {
@@ -465,6 +503,16 @@ function initializeModel(csn, _options)
465
503
  };
466
504
  setProp(entityCsn.elements[backlinkAssocName], '_selfReferences', []);
467
505
  setProp(entityCsn.elements[backlinkAssocName], '_target', parameterCsn);
506
+ setProp(entityCsn.elements[backlinkAssocName], '$path',
507
+ [ 'definitions', originalEntityName, 'elements', backlinkAssocName ] );
508
+
509
+ // rewrite $path
510
+ if(entityCsn.$path)
511
+ entityCsn.$path[1] = originalEntityName;
512
+ forEachMemberRecursively(entityCsn, (member) => {
513
+ if(member.$path)
514
+ member.$path[1] = originalEntityName;
515
+ });
468
516
  }
469
517
 
470
518
  /*
@@ -492,6 +540,24 @@ function initializeModel(csn, _options)
492
540
  setProp(element, '_parent', struct);
493
541
  }
494
542
 
543
+ // convert $path to path starting at main artifact
544
+ function $path2path(p) {
545
+ const path = [];
546
+ let env = csn;
547
+ for (let i = 0; p && env && i < p.length; i++) {
548
+ const ps = p[i];
549
+ env = env[ps];
550
+ if (env && env.constructor === Object) {
551
+ path.push(ps);
552
+ if(env.items)
553
+ env = env.items;
554
+ if(env.type && !isBuiltinType(env.type) && !env.elements)
555
+ env = csn.definitions[env.type];
556
+ }
557
+ }
558
+ return path;
559
+ }
560
+
495
561
  // Initialize a structured artifact
496
562
  function initializeStructure(def) {
497
563
 
@@ -503,16 +569,17 @@ function initializeModel(csn, _options)
503
569
  let keys = Object.create(null);
504
570
  let validFrom = [], validKey = [];
505
571
 
506
- let structParent = def.items || def;
507
-
508
572
  // Iterate all struct elements
509
- forEachGeneric(structParent, 'elements', (element, elementName) => {
510
- initElement(element, elementName, def);
573
+ forEachMemberRecursively(def.items || def, (element, elementName, prop, path = [], construct) => {
574
+ if(!['elements'].includes(prop))
575
+ return;
576
+
577
+ initElement(element, elementName, construct);
511
578
 
512
579
  if(!['event', 'aspect'].includes(def.kind)) {
513
580
  if(element._parent && element._parent.$mySchemaName) {
514
581
  if(!isODataSimpleIdentifier(elementName)) {
515
- signalIllegalIdentifier(elementName, ['definitions', def.name, 'elements', elementName]);
582
+ signalIllegalIdentifier(elementName, ['definitions', def.name].concat(path));
516
583
  } else if (options.isV2() && /^(_|[0-9])/.test(elementName) && ['view', 'entity'].includes(element._parent.kind)) {
517
584
  // FIXME: Rewrite signalIllegalIdentifier function to be more flexible
518
585
  error(null, ['definitions', def.name, 'elements', elementName], { prop: elementName[0] },
@@ -533,13 +600,17 @@ function initializeModel(csn, _options)
533
600
  // in case this is a forward assoc, store the backlink partners here, _selfReferences.length > 1 => error
534
601
  assignProp(element, '_selfReferences', []);
535
602
  assignProp(element._target, '$proxies', []);
603
+ // $abspath is used as partner path
604
+ assignProp(element, '$abspath', $path2path(element.$path));
536
605
 
537
606
  //forward annotations from managed association element to its foreign keys
538
607
  if(element.keys && options.isFlatFormat) {
608
+ const elements = construct.items && construct.items.elements || construct.elements;
539
609
  for(let fk of element.keys) {
540
610
  forAll(element, (attr, attrName) => {
541
- if(attrName[0] === '@')
542
- def.elements[fk.$generatedFieldName][attrName] = attr;
611
+ if(attrName[0] === '@' && fk.$generatedFieldName && elements && elements[fk.$generatedFieldName]) {
612
+ elements[fk.$generatedFieldName][attrName] = attr;
613
+ }
543
614
  });
544
615
  }
545
616
  }
@@ -552,7 +623,7 @@ function initializeModel(csn, _options)
552
623
  keys[elementName] = element;
553
624
  }
554
625
  applyAppSpecificLateCsnTransformationOnElement(options, element, def, error);
555
- });
626
+ }, [], true, { elementsOnly: true });
556
627
 
557
628
  if(!isDeprecatedEnabled(options, 'v1KeysForTemporal')) {
558
629
  // if artifact has a cds.valid.key mention it as @Core.AlternateKey
@@ -604,14 +675,14 @@ function initializeModel(csn, _options)
604
675
  if(!isStructuredArtifact(struct))
605
676
  return;
606
677
 
607
- forEachMember(struct, element => {
678
+ forEachMemberRecursively(struct.items || struct, (element) => {
608
679
  if (isAssociationOrComposition(element) && !element._ignore) {
609
680
  // setup the constraints object
610
681
  setProp(element, '_constraints', { constraints: Object.create(null), selfs: [], _origins: [], termCount: 0 });
611
682
  // and crack the ON condition
612
- resolveOnConditionAndPrepareConstraints(element, messageFunctions);
683
+ resolveOnConditionAndPrepareConstraints(csn, element, messageFunctions);
613
684
  }
614
- });
685
+ }, [], true, { elementsOnly: true });
615
686
  }
616
687
 
617
688
  /*
@@ -627,13 +698,16 @@ function initializeModel(csn, _options)
627
698
  4) All of this can be revoked with options.renderForeignKeys.
628
699
  */
629
700
  function ignoreProperties(struct) {
630
- forEachGeneric(struct, 'elements', (element) => {
701
+ if(!isStructuredArtifact(struct))
702
+ return;
703
+
704
+ forEachMemberRecursively(struct.items || struct, (element) => {
631
705
  if(!element.target) {
632
706
  if(element['@odata.foreignKey4']) {
633
707
  let isContainerAssoc = false;
634
- let elements = struct.elements;
708
+ let elements = (struct.items || struct).elements;
635
709
  let assoc = undefined;
636
- let paths = element['@odata.foreignKey4'].split('.')
710
+ const paths = element['@odata.foreignKey4'].split('.')
637
711
  for(let p of paths) {
638
712
  assoc = elements[p];
639
713
  if(assoc) // could be that the @odata.foreignKey4 was propagated...
@@ -672,7 +746,7 @@ function initializeModel(csn, _options)
672
746
  // ignore it if option odataContainment is true and no foreign keys should be rendered
673
747
  assignAnnotation(element, '@odata.navigable', false);
674
748
  }
675
- });
749
+ }, [], true, { elementsOnly: true });
676
750
  }
677
751
 
678
752
  /*
@@ -684,9 +758,9 @@ function initializeModel(csn, _options)
684
758
  if(!isStructuredArtifact(struct))
685
759
  return;
686
760
 
687
- forEachMember(struct, element => {
761
+ forEachMemberRecursively(struct.items || struct, (element) => {
688
762
  if (isAssociationOrComposition(element) && !element._ignore) {
689
- finalizeReferentialConstraints(element, options, warning);
763
+ finalizeReferentialConstraints(csn, element, options, info);
690
764
 
691
765
  if(element._constraints._partnerCsn && element.cardinality && element.cardinality.max) {
692
766
  // if this is a partnership and this assoc has a set target cardinality, assign it as source cardinality to the partner
@@ -711,7 +785,7 @@ function initializeModel(csn, _options)
711
785
  }
712
786
  }
713
787
  }
714
- });
788
+ }, [], true, { elementsOnly: true });
715
789
  }
716
790
 
717
791
  /*
@@ -774,7 +848,7 @@ function initializeModel(csn, _options)
774
848
  const globalSchemaPrefix = whatsMyServiceRootName(struct.$mySchemaName);
775
849
  // if this artifact is a service member check its associations
776
850
  if(globalSchemaPrefix) {
777
- forEachGeneric(struct, 'elements', element => {
851
+ forEachGeneric(struct.items || struct, 'elements', element => {
778
852
  if(!isAssociationOrComposition(element) || element._ignore || element['@odata.navigable'] === false)
779
853
  return;
780
854
  /*
@@ -832,7 +906,7 @@ function initializeModel(csn, _options)
832
906
  }
833
907
  else {
834
908
  // fake the target to be proxy
835
- element._target.$externalRef = true;
909
+ setProp(element._target, '$externalRef', true);
836
910
  }
837
911
  }
838
912
  else {
@@ -881,7 +955,6 @@ function initializeModel(csn, _options)
881
955
  setProp(proxy, '$keys', Object.create(null));
882
956
  setProp(proxy, '$hasEntitySet', false);
883
957
  setProp(proxy, '$exposedTypes', Object.create(null));
884
-
885
958
  // copy all annotations of the target to the proxy
886
959
  Object.entries(assoc._target).forEach(([k, v]) => {
887
960
  if(k[0] === '@')
@@ -1028,10 +1101,12 @@ function initializeModel(csn, _options)
1028
1101
  if(!elem.target) {
1029
1102
  type.elements[elemName] = Object.create(null);
1030
1103
  Object.keys(elem).forEach(prop => type.elements[elemName][prop] = elem[prop])
1104
+ type.elements[elemName].notNull = true;
1031
1105
  }
1032
1106
  else {
1033
1107
  type.elements[elemName] = createProxyOrSchemaRefForManagedAssoc(elem);
1034
1108
  }
1109
+ setProp(type.elements[elemName], 'name', elem.name);
1035
1110
  });
1036
1111
  return type;
1037
1112
  }
@@ -1058,6 +1133,7 @@ function initializeModel(csn, _options)
1058
1133
  // art is in the target side, clone it and remove key property
1059
1134
  let cloneArt = cloneCsn(art, options);
1060
1135
  setProp(cloneArt, 'name', art.name);
1136
+ cloneArt.notNull = true;
1061
1137
  delete cloneArt.key;
1062
1138
  newElt.elements[art.name] = cloneArt;
1063
1139
  });
@@ -1147,14 +1223,14 @@ function initializeModel(csn, _options)
1147
1223
  function registerProxy(proxy, element) {
1148
1224
  if(proxy === undefined)
1149
1225
  return undefined;
1150
- const proxyName = globalSchemaPrefix + '.' + proxy.name;
1151
- const schemaName = globalSchemaPrefix + '.' + proxy.$mySchemaName;
1226
+ const fqProxyName = globalSchemaPrefix + '.' + proxy.name;
1227
+ const fqSchemaName = globalSchemaPrefix + '.' + proxy.$mySchemaName;
1152
1228
 
1153
1229
  if(!element._target.$cachedProxy)
1154
1230
  assignProp(element._target, '$cachedProxy', Object.create(null));
1155
1231
  if(getProxyForTargetOf(element)) {
1156
1232
  info(null, ['definitions', struct.name, 'elements', element.name],
1157
- { name: proxyName }, 'Proxy EDM entity type $(NAME) has already been registered');
1233
+ { name: fqProxyName }, 'Proxy EDM entity type $(NAME) has already been registered');
1158
1234
  }
1159
1235
  else
1160
1236
  element._target.$cachedProxy[globalSchemaPrefix] = proxy;
@@ -1164,7 +1240,7 @@ function initializeModel(csn, _options)
1164
1240
  // (that may reside in another subcontext schema), but only once
1165
1241
  const schemaSet = new Set();
1166
1242
  // start with the schema name for the proxy
1167
- schemaSet.add(schemaName);
1243
+ schemaSet.add(fqSchemaName);
1168
1244
  // followed by all namespaces that are potentially exposed by the exposed types
1169
1245
  // don't forget to prepend the global namespace prefix
1170
1246
  // schemas are ordered in csn2edm.js for each service
@@ -1177,12 +1253,16 @@ function initializeModel(csn, _options)
1177
1253
  }
1178
1254
  });
1179
1255
  /** @type {object} */
1180
- const alreadyRegistered = csn.definitions[proxyName]
1256
+ const alreadyRegistered = csn.definitions[fqProxyName]
1181
1257
  if(!alreadyRegistered) {
1182
- csn.definitions[proxyName] = proxy;
1258
+ csn.definitions[fqProxyName] = proxy;
1259
+ setProp(proxy, '$path', ['definitions', fqProxyName]);
1183
1260
  Object.entries(proxy.$exposedTypes).forEach(([tn, v]) => {
1184
- if(csn.definitions[globalSchemaPrefix + '.' + tn] === undefined)
1185
- csn.definitions[globalSchemaPrefix + '.' + tn] = v;
1261
+ const fqtn = globalSchemaPrefix + '.' + tn;
1262
+ if(csn.definitions[fqtn] === undefined) {
1263
+ csn.definitions[fqtn] = v;
1264
+ setProp(v, '$path', ['definitions', fqtn]);
1265
+ }
1186
1266
  });
1187
1267
  info(null, ['definitions', element._parent.name, 'elements', element.name],
1188
1268
  { name: proxy.name }, 'Created proxy EDM entity type $(NAME)');
@@ -1190,16 +1270,16 @@ function initializeModel(csn, _options)
1190
1270
  else if(alreadyRegistered && !alreadyRegistered.$proxy &&
1191
1271
  !['entity', 'view'].includes(alreadyRegistered.kind)) {
1192
1272
  warning(null, ['definitions', element._parent.name, 'elements', element.name],
1193
- { name: proxyName, kind: alreadyRegistered.kind },
1273
+ { name: fqProxyName, kind: alreadyRegistered.kind },
1194
1274
  'No proxy EDM entity type created due to name collision with $(NAME) of kind $(KIND)');
1195
1275
  return undefined;
1196
1276
  }
1197
1277
  }
1198
1278
  else {
1199
1279
  // it's a service reference, just add that reference proxy
1200
- if(!schemas[schemaName]) {
1201
- schemas[schemaName] = proxy;
1202
- schemaNames.push(schemaName);
1280
+ if(!schemas[fqSchemaName]) {
1281
+ schemas[fqSchemaName] = proxy;
1282
+ schemaNames.push(fqSchemaName);
1203
1283
  info(null, ['definitions', struct.name, 'elements', element.name],
1204
1284
  { name: proxy.name }, 'Created EDM namespace reference $(NAME)');
1205
1285
  }
@@ -1244,11 +1324,8 @@ function initializeModel(csn, _options)
1244
1324
  else if(!k.target) {
1245
1325
  struct.$edmKeyPaths.push([kn]);
1246
1326
  }
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
- }
1327
+ // check toplevel key for spec violations
1328
+ checkKeySpecViolations(k, ['definitions', struct.name, 'elements', k.name]);
1252
1329
  }
1253
1330
  });
1254
1331
  }
@@ -1264,7 +1341,7 @@ function initializeModel(csn, _options)
1264
1341
  If element is of scalar type, return it as an array.
1265
1342
  */
1266
1343
  function produceKeyRefPaths(eltCsn, prefix) {
1267
- let keyPaths = [];
1344
+ const keyPaths = [];
1268
1345
  if(!isEdmPropertyRendered(eltCsn, options)) {
1269
1346
  // let annos = Object.keys(eltCsn).filter(a=>a[0]==='@').join(', ');
1270
1347
  // warning(null, ['definitions', struct.name, 'elements', eltCsn.name ],
@@ -1272,15 +1349,19 @@ function initializeModel(csn, _options)
1272
1349
  return keyPaths;
1273
1350
  }
1274
1351
  // 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;
1352
+
1353
+ const finalType = getFinalTypeDef(eltCsn.items && eltCsn.items.type || eltCsn.type);
1354
+ const elements = eltCsn.elements || eltCsn.items && eltCsn.items.elements|| 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 || 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
+ }
1424
+ }
1425
+ }
1426
+ }
1427
+ }
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));
1320
1485
  }
1321
- );
1486
+ }
1322
1487
  }
1323
1488
  }
1324
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
@@ -1372,7 +1605,7 @@ function initializeModel(csn, _options)
1372
1605
  //
1373
1606
 
1374
1607
  function inboundQualificationChecks() {
1375
- forEachDefinition(csn, [ checkChainedArray, checkODataV2Limitations ]);
1608
+ forEachDefinition(csn, [ checkChainedArray ]);
1376
1609
  checkNestedContextsAndServices();
1377
1610
  throwWithError();
1378
1611
 
@@ -1404,18 +1637,6 @@ function initializeModel(csn, _options)
1404
1637
  }
1405
1638
  }
1406
1639
 
1407
- function checkODataV2Limitations(def, defName) {
1408
- if (!options.isV2() || !whatsMyServiceRootName(defName))
1409
- return;
1410
-
1411
- forEachMemberRecursively(def, (member, _memberName, prop, path) => {
1412
- if (def.kind && def.kind === 'type' && isAssocOrComposition(member.type))
1413
- warning('odata-spec-violation-assoc', path, 'User-defined structured types must not contain associations for OData V2');
1414
- else if (prop === 'elements' && (member.items || (member.type && getFinalTypeDef(member.type).items)))
1415
- warning('odata-spec-violation-array-of', path, { keyword: 'many/array of' }, 'Predicate $(KEYWORD) not allowed on elements for OData V2');
1416
- }, ['definitions', defName]);
1417
- }
1418
-
1419
1640
  function checkNestedContextsAndServices() {
1420
1641
  !isBetaEnabled(options, 'nestedServices') && serviceRootNames.forEach(sn => {
1421
1642
  const parent = whatsMyServiceRootName(sn, false);
@@ -1544,38 +1765,51 @@ function initializeModel(csn, _options)
1544
1765
  // short cut annotation @readonly that gets expanded and can be safely remapped.
1545
1766
  function rewriteContainmentAnnotations(container, containee, assocName) {
1546
1767
  // rectify Restrictions to NavigationRestrictions
1547
- if(options.isV4() && container['@Capabilities.NavigationRestrictions'] === undefined) {
1548
- let navRestr = {
1549
- RestrictedProperties: [
1550
- {
1551
- NavigationProperty: assocName
1552
- }
1553
- ]
1554
- };
1555
- let hasRestrictions = false;
1556
- if(containee['@Capabilities.DeleteRestrictions.Deletable'] !== undefined) {
1557
- navRestr.RestrictedProperties[0].DeleteRestrictions =
1558
- { 'Deletable': containee['@Capabilities.DeleteRestrictions.Deletable'] };
1559
- 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;
1560
1778
  }
1561
- if(containee['@Capabilities.InsertRestrictions.Insertable'] !== undefined) {
1562
- navRestr.RestrictedProperties[0].InsertRestrictions =
1563
- { 'Insertable': containee['@Capabilities.InsertRestrictions.Insertable'] };
1564
- hasRestrictions = true;
1779
+ if(!navPropEntry) {
1780
+ navPropEntry = { NavigationProperty: { '=': assocName } };
1565
1781
  }
1566
- if(containee['@Capabilities.UpdateRestrictions.Updatable'] !== undefined) {
1567
- navRestr.RestrictedProperties[0].UpdateRestrictions =
1568
- { 'Updatable': containee['@Capabilities.UpdateRestrictions.Updatable'] };
1569
- 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
+ }
1570
1800
  }
1571
- //'@Capabilities.ReadRestrictions.Readable'
1572
- if(containee['@Capabilities.ReadRestrictions.Readable'] !== undefined) {
1573
- navRestr.RestrictedProperties[0].ReadRestrictions =
1574
- { 'Readable': containee['@Capabilities.ReadRestrictions.Readable'] };
1575
- 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
+ }
1576
1812
  }
1577
- if(hasRestrictions)
1578
- container['@Capabilities.NavigationRestrictions'] = navRestr;
1579
1813
  }
1580
1814
  }
1581
1815
 
@@ -1784,7 +2018,7 @@ function applyAppSpecificLateCsnTransformationOnStructure(options, struct, error
1784
2018
  let key = { name: keyName, key : true, type : 'cds.String', '@sap.sortable':false, '@sap.filterable':false, '@UI.Hidden': true };
1785
2019
  elements[keyName] = key;
1786
2020
  setProp(struct, '$keys',{ [keyName] : key } );
1787
- forEachGeneric(struct, 'elements', (e,n) =>
2021
+ forEachGeneric(struct.items || struct, 'elements', (e,n) =>
1788
2022
  {
1789
2023
  if(e.key) delete e.key;
1790
2024
  elements[n] = e;