@sap/cds-compiler 2.5.0 → 2.10.4

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 (92) hide show
  1. package/CHANGELOG.md +191 -9
  2. package/bin/cdsc.js +2 -2
  3. package/doc/CHANGELOG_BETA.md +33 -3
  4. package/lib/api/main.js +29 -101
  5. package/lib/api/options.js +15 -11
  6. package/lib/api/validate.js +12 -8
  7. package/lib/backends.js +0 -81
  8. package/lib/base/keywords.js +32 -2
  9. package/lib/base/message-registry.js +63 -9
  10. package/lib/base/messages.js +63 -21
  11. package/lib/base/model.js +2 -3
  12. package/lib/checks/defaultValues.js +27 -2
  13. package/lib/checks/elements.js +1 -6
  14. package/lib/checks/foreignKeys.js +0 -6
  15. package/lib/checks/managedWithoutKeys.js +17 -0
  16. package/lib/checks/nonexpandableStructured.js +38 -0
  17. package/lib/checks/onConditions.js +9 -45
  18. package/lib/checks/queryNoDbArtifacts.js +25 -7
  19. package/lib/checks/selectItems.js +25 -2
  20. package/lib/checks/types.js +26 -2
  21. package/lib/checks/unknownMagic.js +38 -0
  22. package/lib/checks/utils.js +61 -0
  23. package/lib/checks/validator.js +60 -7
  24. package/lib/compiler/assert-consistency.js +16 -7
  25. package/lib/compiler/builtins.js +2 -0
  26. package/lib/compiler/checks.js +6 -4
  27. package/lib/compiler/definer.js +99 -42
  28. package/lib/compiler/index.js +73 -27
  29. package/lib/compiler/resolver.js +288 -157
  30. package/lib/compiler/shared.js +31 -11
  31. package/lib/edm/annotations/genericTranslation.js +182 -186
  32. package/lib/edm/csn2edm.js +103 -108
  33. package/lib/edm/edm.js +18 -21
  34. package/lib/edm/edmPreprocessor.js +361 -114
  35. package/lib/edm/edmUtils.js +103 -33
  36. package/lib/gen/Dictionary.json +22 -0
  37. package/lib/gen/language.checksum +1 -1
  38. package/lib/gen/language.interp +12 -1
  39. package/lib/gen/language.tokens +57 -53
  40. package/lib/gen/languageLexer.interp +10 -1
  41. package/lib/gen/languageLexer.js +770 -744
  42. package/lib/gen/languageLexer.tokens +49 -46
  43. package/lib/gen/languageParser.js +4713 -4279
  44. package/lib/json/from-csn.js +103 -45
  45. package/lib/json/to-csn.js +296 -117
  46. package/lib/language/antlrParser.js +4 -3
  47. package/lib/language/errorStrategy.js +1 -0
  48. package/lib/language/genericAntlrParser.js +21 -12
  49. package/lib/language/language.g4 +99 -31
  50. package/lib/main.d.ts +81 -3
  51. package/lib/main.js +30 -7
  52. package/lib/model/api.js +78 -0
  53. package/lib/model/csnRefs.js +329 -142
  54. package/lib/model/csnUtils.js +235 -58
  55. package/lib/model/enrichCsn.js +18 -1
  56. package/lib/model/revealInternalProperties.js +2 -1
  57. package/lib/modelCompare/compare.js +37 -20
  58. package/lib/optionProcessor.js +9 -3
  59. package/lib/render/.eslintrc.json +4 -1
  60. package/lib/render/DuplicateChecker.js +8 -5
  61. package/lib/render/toCdl.js +112 -33
  62. package/lib/render/toHdbcds.js +134 -64
  63. package/lib/render/toSql.js +91 -38
  64. package/lib/render/utils/common.js +8 -13
  65. package/lib/render/utils/sql.js +3 -3
  66. package/lib/sql-identifier.js +6 -1
  67. package/lib/transform/db/assertUnique.js +5 -6
  68. package/lib/transform/db/constraints.js +29 -13
  69. package/lib/transform/db/draft.js +8 -6
  70. package/lib/transform/db/expansion.js +582 -0
  71. package/lib/transform/db/flattening.js +325 -0
  72. package/lib/transform/db/groupByOrderBy.js +2 -2
  73. package/lib/transform/db/transformExists.js +284 -63
  74. package/lib/transform/forHanaNew.js +98 -381
  75. package/lib/transform/forOdataNew.js +21 -22
  76. package/lib/transform/localized.js +37 -10
  77. package/lib/transform/odata/attachPath.js +19 -4
  78. package/lib/transform/odata/generateForeignKeyElements.js +11 -10
  79. package/lib/transform/odata/referenceFlattener.js +60 -39
  80. package/lib/transform/odata/sortByAssociationDependency.js +2 -2
  81. package/lib/transform/odata/structuralPath.js +72 -0
  82. package/lib/transform/odata/structureFlattener.js +19 -18
  83. package/lib/transform/odata/typesExposure.js +22 -12
  84. package/lib/transform/transformUtilsNew.js +134 -78
  85. package/lib/transform/translateAssocsToJoins.js +17 -14
  86. package/lib/transform/universalCsnEnricher.js +67 -0
  87. package/lib/utils/file.js +0 -11
  88. package/lib/utils/moduleResolve.js +6 -8
  89. package/package.json +1 -1
  90. package/lib/json/walker.js +0 -26
  91. package/lib/transform/sqlite +0 -0
  92. 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] === '@' && fk.$generatedFieldName)
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,20 @@ 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 ||
1355
+ (finalType && (finalType.elements || finalType.items && finalType.items.elements));
1276
1356
  if(elements) {
1277
1357
  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}`;
1358
+ const newRefs = produceKeyRefPaths(elt, prefix + options.pathDelimiter + eltName);
1359
+ if(newRefs.length) {
1360
+ keyPaths.push(...newRefs);
1361
+ // check path step key for spec violations
1362
+ const pathSegment = `${prefix}/${eltName}`;
1281
1363
  // 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);
1364
+ const location = struct.$path.concat(['elements']).concat(pathSegment.split('/')[0]);
1365
+ checkKeySpecViolations(elt, location, pathSegment);
1284
1366
  }
1285
1367
  });
1286
1368
  }
@@ -1311,17 +1393,169 @@ function initializeModel(csn, _options)
1311
1393
  return keyPaths;
1312
1394
  }
1313
1395
 
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'
1396
+ function checkKeySpecViolations(elt, location, pathSegment) {
1397
+ // Nullability
1398
+ if((!elt.key && (elt.notNull === undefined || elt.notNull === false)) ||
1399
+ elt.key && (elt.notNull !== undefined && elt.notNull === false)) {
1400
+ error('odata-spec-violation-key-null', location,
1401
+ {name: pathSegment, '#': pathSegment ? 'std' : 'scalar'});
1402
+ }
1403
+ // many
1404
+ let type = elt.items || elt.type && !isBuiltinType(elt.type) && getFinalTypeDef(elt.type).items;
1405
+ if(type) {
1406
+ error('odata-spec-violation-key-array', location,
1407
+ {name: pathSegment, '#': pathSegment ? 'std' : 'scalar'});
1408
+ }
1409
+ // type
1410
+ if(!elt.elements) {
1411
+ if(!type)
1412
+ type = isBuiltinType(elt.type) ? elt : csn.definitions[elt.type];
1413
+
1414
+ // check for legal scalar types, proxy exposed structured types are not resolvable in CSN
1415
+ // V2 allows any Edm.PrimitiveType (even Double and Binary), V4 is more specific:
1416
+ if(options.isV4() && type && !isAssociationOrComposition(type) && isBuiltinType(type.type)) {
1417
+ const edmType = edmUtils.mapCdsToEdmType(type);
1418
+ const legalEdmTypes = [
1419
+ 'Edm.Boolean', 'Edm.Byte', 'Edm.Date', 'Edm.DateTimeOffset', 'Edm.Decimal', 'Edm.Duration',
1420
+ 'Edm.Guid', 'Edm.Int16', 'Edm.Int32', 'Edm.Int64', 'Edm.SByte', 'Edm.String', 'Edm.TimeOfDay' ];
1421
+ if(!legalEdmTypes.includes(edmType)) {
1422
+ warning('odata-spec-violation-key-type', location,
1423
+ {name: pathSegment, type: type.type, id: edmType, '#': pathSegment ? 'std' : 'scalar'});
1424
+ }
1320
1425
  }
1321
- );
1426
+ }
1322
1427
  }
1323
1428
  }
1324
1429
 
1430
+ /*
1431
+ Calculate all reachable entity set paths for a given navigation start point
1432
+
1433
+ Rule: First non-containment association terminates Path, if association is
1434
+ containment enabling assoc, Target is own Struct/ plus the path down to the
1435
+ n-2nd path segment (which is the path to the n-1st implicit entity set).
1436
+
1437
+ Example:
1438
+ entity Header {
1439
+ items: composition of many {
1440
+ toF: association to F;
1441
+ subitems: composition of many {
1442
+ toG: association to G;
1443
+ subitems: composition of many {
1444
+ toG: association to G;
1445
+ };
1446
+ }
1447
+ }
1448
+ }
1449
+ Must produce:
1450
+ Path="items/up_" Target="Header"/>
1451
+ Path="items/toF" Target="F"/>
1452
+ Path="items/subitems/up_" Target="Header/items"/>
1453
+ Path="items/subitems/toG" Target="G"/>
1454
+ Path="items/subitems/subitems/up_" Target="Header/items/subitems"/>
1455
+ Path="items/subitems/subitems/toG" Target="G"/>
1456
+ */
1457
+ function initializeEdmNavPropBindingTargets(struct) {
1458
+ if(options.isV4() && struct.$mySchemaName && struct.$hasEntitySet) {
1459
+ forEachGeneric(struct.items || struct, 'elements', (element) => {
1460
+ produceTargetPath([edmUtils.getBaseName(struct.name)], element, struct);
1461
+ });
1462
+ }
1463
+
1464
+ function produceTargetPath(prefix, elt, curDef) {
1465
+ const newPrefix = [...prefix, elt.name];
1466
+ if(isEdmPropertyRendered(elt, options)) {
1467
+ // Assoc can never be a derived TypeDefinition, no need to
1468
+ // unroll derived type chains for assocs
1469
+ if(isAssociationOrComposition(elt) && !elt.$touched) {
1470
+ if(!elt._target.$edmTgtPaths)
1471
+ setProp(elt._target, '$edmTgtPaths', []);
1472
+ if(!elt._target.$hasEntitySet && !elt._isToContainer && curDef !== elt._target) {
1473
+ // follow elements in the target but avoid cycles
1474
+ setProp(elt, '$touched', true);
1475
+ elt._target.$edmTgtPaths.push(newPrefix);
1476
+ Object.values(elt._target.elements).forEach(e => produceTargetPath(newPrefix, e, elt._target));
1477
+ delete elt.$touched;
1478
+ }
1479
+ }
1480
+ else {
1481
+ // try to find elements to drill down further
1482
+ while(elt && !(isBuiltinType(elt.type) || elt.elements)) {
1483
+ elt = csn.definitions[elt.type];
1484
+ }
1485
+ elt && elt.elements && Object.values(elt.elements).forEach(e => produceTargetPath(newPrefix, e, curDef));
1486
+ }
1487
+ }
1488
+ }
1489
+ }
1490
+
1491
+ function initializeEdmNavPropBindingPaths(struct) {
1492
+ if(options.isV4() && struct.$mySchemaName && struct.$hasEntitySet) {
1493
+ let npbs = [];
1494
+ forEachGeneric(struct.items || struct, 'elements', (element) => {
1495
+ npbs = npbs.concat(produceNavigationPath(element, struct));
1496
+ });
1497
+ setProp(struct, '$edmNPBs', npbs);
1498
+ }
1499
+
1500
+ // collect all paths originating from this element that end up in an entity set
1501
+ function produceNavigationPath(elt, curDef) {
1502
+ let npbs = [];
1503
+ const prefix = elt.name;
1504
+ if(isEdmPropertyRendered(elt, options)) {
1505
+ // Assoc can never be a derived TypeDefinition, no need to
1506
+ // unroll derived type chains for assocs
1507
+ if(isAssociationOrComposition(elt) && !elt.$touched) {
1508
+ // drill into target only if
1509
+ // 1) target has no entity set and this assoc is not going to the container
1510
+ // 2) current definition and target are the same (cycle)
1511
+ if(!elt._target.$hasEntitySet && !elt._isToContainer && curDef !== elt._target) {
1512
+ // follow elements in the target but avoid cycles
1513
+ setProp(elt, '$touched', true);
1514
+ Object.values(elt._target.elements).forEach(e => npbs = npbs.concat(produceNavigationPath(e, elt._target)));
1515
+ delete elt.$touched;
1516
+ }
1517
+ else if(!(options.odataContainment && options.isV4() && elt['@odata.contained'])) {
1518
+ // end point reached but must not be an external reference nor a proxy nor a composition itself
1519
+ // last assoc step must not be to-n and target a singleton
1520
+ let p = undefined;
1521
+ if (!elt._target.$externalRef &&
1522
+ !(edmUtils.isToMany(elt) && edmUtils.isSingleton(elt._target, options.isV4()))) {
1523
+ if(elt._target.$edmTgtPaths && elt._target.$edmTgtPaths.length) {
1524
+ p = elt._target.$edmTgtPaths.find(p => p[0] === edmUtils.getBaseName(struct.name)) || elt._target.$edmTgtPaths[0];
1525
+ }
1526
+ else if(elt._target.$hasEntitySet) {
1527
+ const baseName = edmUtils.getBaseName(elt._target.$entitySetName || elt._target.name);
1528
+ // if own struct and target have a set they either are in the same $mySchemaName or not
1529
+ // if target is in another schema, target the full qualified entity set
1530
+ p = (elt._target.$mySchemaName === struct.$mySchemaName) ?
1531
+ [ baseName ] : [elt._target.$mySchemaName + '.EntityContainer', baseName];
1532
+ }
1533
+ if(p) {
1534
+ // if own struct and target have a set they either are in the same $mySchemaName or not
1535
+ // if target is in another schema, target the full qualified entity set
1536
+ const npb = {
1537
+ Path: elt.name,
1538
+ Target: p.join('/')
1539
+ };
1540
+ npbs.push( npb );
1541
+ }
1542
+ }
1543
+ // Do not prepend prefix here!
1544
+ return npbs;
1545
+ }
1546
+ }
1547
+ else {
1548
+ // try to find elements to drill down further
1549
+ while(elt && !(isBuiltinType(elt.type) || elt.elements)) {
1550
+ elt = csn.definitions[elt.type];
1551
+ }
1552
+ elt && elt.elements && Object.values(elt.elements).forEach(e => npbs = npbs.concat(produceNavigationPath(e, curDef)));
1553
+ }
1554
+ }
1555
+ npbs.forEach(p => p.Path = prefix + '/' + p.Path );
1556
+ return npbs;
1557
+ }
1558
+ }
1325
1559
 
1326
1560
  function determineEntitySet(struct) {
1327
1561
  // if this is an entity or a view, determine if an entity set is required or not
@@ -1532,38 +1766,51 @@ function initializeModel(csn, _options)
1532
1766
  // short cut annotation @readonly that gets expanded and can be safely remapped.
1533
1767
  function rewriteContainmentAnnotations(container, containee, assocName) {
1534
1768
  // 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;
1769
+ if(options.isV4()) {
1770
+ let navPropEntry;
1771
+ let hasEntry = false;
1772
+ let newEntry = false;
1773
+ const anno = '@Capabilities.NavigationRestrictions.RestrictedProperties';
1774
+ let resProps = container[anno];
1775
+ // merge into existing anno, if available
1776
+ if(resProps) {
1777
+ navPropEntry = resProps.find(p => p.NavigationProperty && p.NavigationProperty['='] === assocName);
1778
+ hasEntry = !!navPropEntry;
1548
1779
  }
1549
- if(containee['@Capabilities.InsertRestrictions.Insertable'] !== undefined) {
1550
- navRestr.RestrictedProperties[0].InsertRestrictions =
1551
- { 'Insertable': containee['@Capabilities.InsertRestrictions.Insertable'] };
1552
- hasRestrictions = true;
1780
+ if(!navPropEntry) {
1781
+ navPropEntry = { NavigationProperty: { '=': assocName } };
1553
1782
  }
1554
- if(containee['@Capabilities.UpdateRestrictions.Updatable'] !== undefined) {
1555
- navRestr.RestrictedProperties[0].UpdateRestrictions =
1556
- { 'Updatable': containee['@Capabilities.UpdateRestrictions.Updatable'] };
1557
- hasRestrictions = true;
1783
+
1784
+ const props = Object.entries(containee);
1785
+
1786
+ const merge = (prefix) => {
1787
+ const prop = prefix.split('.')[1];
1788
+ // don't overwrite existing restrictions
1789
+ if(!navPropEntry[prop]) {
1790
+ // Filter properties with prefix and reduce them into a new dictionary
1791
+ const o = props.filter(p => p[0].startsWith(prefix+'.')).reduce((a,c) => {
1792
+ a[c[0].replace(prefix+'.', '')] = c[1];
1793
+ return a;
1794
+ }, { });
1795
+ // if dictionary has entries, add them to navPropEnty
1796
+ if(Object.keys(o).length) {
1797
+ navPropEntry[prop] = o;
1798
+ newEntry = true;
1799
+ }
1800
+ }
1558
1801
  }
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;
1802
+ merge('@Capabilities.DeleteRestrictions');
1803
+ merge('@Capabilities.InsertRestrictions');
1804
+ merge('@Capabilities.UpdateRestrictions');
1805
+ merge('@Capabilities.ReadRestrictions');
1806
+
1807
+ if(newEntry) {
1808
+ if(!hasEntry) {
1809
+ if(!resProps)
1810
+ resProps = container[anno] = [ ];
1811
+ resProps.push(navPropEntry);
1812
+ }
1564
1813
  }
1565
- if(hasRestrictions)
1566
- container['@Capabilities.NavigationRestrictions'] = navRestr;
1567
1814
  }
1568
1815
  }
1569
1816
 
@@ -1772,7 +2019,7 @@ function applyAppSpecificLateCsnTransformationOnStructure(options, struct, error
1772
2019
  let key = { name: keyName, key : true, type : 'cds.String', '@sap.sortable':false, '@sap.filterable':false, '@UI.Hidden': true };
1773
2020
  elements[keyName] = key;
1774
2021
  setProp(struct, '$keys',{ [keyName] : key } );
1775
- forEachGeneric(struct, 'elements', (e,n) =>
2022
+ forEachGeneric(struct.items || struct, 'elements', (e,n) =>
1776
2023
  {
1777
2024
  if(e.key) delete e.key;
1778
2025
  elements[n] = e;