@sap/cds-compiler 2.7.0 → 2.11.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 (87) hide show
  1. package/CHANGELOG.md +167 -0
  2. package/bin/cdsc.js +42 -25
  3. package/bin/cdsse.js +1 -0
  4. package/doc/CHANGELOG_BETA.md +10 -0
  5. package/lib/api/.eslintrc.json +2 -0
  6. package/lib/api/main.js +17 -33
  7. package/lib/api/options.js +25 -13
  8. package/lib/api/validate.js +33 -9
  9. package/lib/backends.js +9 -8
  10. package/lib/base/dictionaries.js +2 -1
  11. package/lib/base/keywords.js +32 -2
  12. package/lib/base/message-registry.js +26 -2
  13. package/lib/base/messages.js +25 -9
  14. package/lib/base/model.js +5 -3
  15. package/lib/base/optionProcessorHelper.js +56 -22
  16. package/lib/checks/onConditions.js +5 -0
  17. package/lib/checks/selectItems.js +4 -0
  18. package/lib/checks/types.js +26 -2
  19. package/lib/checks/unknownMagic.js +41 -0
  20. package/lib/checks/validator.js +7 -2
  21. package/lib/compiler/assert-consistency.js +18 -5
  22. package/lib/compiler/base.js +65 -0
  23. package/lib/compiler/builtins.js +30 -1
  24. package/lib/compiler/checks.js +5 -2
  25. package/lib/compiler/definer.js +145 -120
  26. package/lib/compiler/index.js +16 -4
  27. package/lib/compiler/propagator.js +5 -2
  28. package/lib/compiler/resolver.js +207 -47
  29. package/lib/compiler/shared.js +47 -200
  30. package/lib/compiler/utils.js +173 -0
  31. package/lib/edm/annotations/genericTranslation.js +183 -187
  32. package/lib/edm/csn2edm.js +94 -98
  33. package/lib/edm/edm.js +16 -20
  34. package/lib/edm/edmPreprocessor.js +302 -115
  35. package/lib/edm/edmUtils.js +31 -12
  36. package/lib/gen/language.checksum +1 -1
  37. package/lib/gen/language.interp +28 -1
  38. package/lib/gen/language.tokens +79 -69
  39. package/lib/gen/languageLexer.interp +28 -1
  40. package/lib/gen/languageLexer.js +879 -805
  41. package/lib/gen/languageLexer.tokens +71 -62
  42. package/lib/gen/languageParser.js +5308 -4308
  43. package/lib/json/from-csn.js +59 -30
  44. package/lib/json/to-csn.js +354 -105
  45. package/lib/language/antlrParser.js +11 -0
  46. package/lib/language/errorStrategy.js +1 -0
  47. package/lib/language/genericAntlrParser.js +81 -14
  48. package/lib/language/language.g4 +163 -31
  49. package/lib/main.d.ts +136 -17
  50. package/lib/main.js +7 -1
  51. package/lib/model/api.js +78 -0
  52. package/lib/model/csnRefs.js +115 -32
  53. package/lib/model/csnUtils.js +71 -33
  54. package/lib/model/enrichCsn.js +36 -9
  55. package/lib/model/revealInternalProperties.js +20 -4
  56. package/lib/modelCompare/compare.js +2 -1
  57. package/lib/optionProcessor.js +33 -16
  58. package/lib/render/.eslintrc.json +3 -1
  59. package/lib/render/DuplicateChecker.js +1 -1
  60. package/lib/render/toCdl.js +60 -17
  61. package/lib/render/toHdbcds.js +122 -74
  62. package/lib/render/toSql.js +57 -32
  63. package/lib/render/utils/common.js +6 -10
  64. package/lib/sql-identifier.js +6 -1
  65. package/lib/transform/db/constraints.js +273 -119
  66. package/lib/transform/db/draft.js +9 -6
  67. package/lib/transform/db/expansion.js +19 -7
  68. package/lib/transform/db/flattening.js +31 -7
  69. package/lib/transform/db/transformExists.js +344 -66
  70. package/lib/transform/db/views.js +438 -0
  71. package/lib/transform/forHanaNew.js +65 -436
  72. package/lib/transform/forOdataNew.js +21 -10
  73. package/lib/transform/localized.js +2 -0
  74. package/lib/transform/odata/attachPath.js +19 -4
  75. package/lib/transform/odata/generateForeignKeyElements.js +11 -10
  76. package/lib/transform/odata/referenceFlattener.js +44 -38
  77. package/lib/transform/odata/sortByAssociationDependency.js +2 -2
  78. package/lib/transform/odata/structuralPath.js +72 -0
  79. package/lib/transform/odata/structureFlattener.js +13 -10
  80. package/lib/transform/odata/typesExposure.js +22 -12
  81. package/lib/transform/transformUtilsNew.js +55 -9
  82. package/lib/transform/translateAssocsToJoins.js +11 -17
  83. package/lib/transform/universalCsnEnricher.js +67 -0
  84. package/lib/utils/file.js +5 -3
  85. package/lib/utils/term.js +65 -42
  86. package/lib/utils/timetrace.js +48 -26
  87. package/package.json +1 -1
@@ -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
 
@@ -337,33 +342,57 @@ function initializeModel(csn, _options)
337
342
  // entity set. Instead try to rewrite the annotation in such a way that it is effective
338
343
  // on the containment navigation property.
339
344
  function initializeContainments(container) {
340
- forEachMemberRecursively(container, (element, elementName) => {
341
- if(isAssociationOrComposition(element) && !element._ignore) {
342
- 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) {
343
352
  // Let the containee know its container
344
353
  // (array because the contanee may contained more then once)
345
- let containee = element._target;
346
- if (!containee._containerEntity) {
347
- setProp(containee, '_containerEntity', []);
348
- }
354
+ let containee = elt._target;
355
+ if (!containee._containerEntity)
356
+ setProp(containee, '_containerEntity', []);
349
357
  // add container only once per containee
350
- if (!containee._containerEntity.includes(container.name)) {
351
- containee._containerEntity.push(container.name);
352
- // Mark associations in the containee pointing to the container (i.e. to this entity)
353
- containee.elements && Object.values(containee.elements).forEach( containeeElement => {
354
- if (containeeElement._target && containeeElement._target.name) {
355
- // If this is an association that points to a container (but is not by itself contained,
356
- // which would indicate the top role in a hierarchy) mark it with '_isToContainer'
357
- if (containeeElement._target.name == container.name && !containeeElement['@odata.contained']) {
358
- setProp(containeeElement, '_isToContainer', true);
359
- }
360
- }
361
- });
362
- }
363
- 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 });
364
373
  }
365
374
  }
366
- });
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
+ }
367
396
  }
368
397
 
369
398
  // Split an entity with parameters into two entity types with their entity sets,
@@ -420,7 +449,6 @@ function initializeModel(csn, _options)
420
449
  assignProp(parameterCsn, '_SetAttributes',
421
450
  {'@sap.creatable': false, '@sap.updatable': false, '@sap.deletable': false, '@sap.pageable': false });
422
451
 
423
- assignProp(parameterCsn, '$keys', Object.create(null));
424
452
  setProp(parameterCsn, '$isParamEntity', true);
425
453
  setProp(parameterCsn, '$mySchemaName', entityCsn.$mySchemaName);
426
454
 
@@ -438,9 +466,10 @@ function initializeModel(csn, _options)
438
466
  elt.name = n;
439
467
  delete elt.kind;
440
468
  elt.key = true; // params become primary key in parameter entity
441
- parameterCsn.$keys[n] = parameterCsn.elements[n] = elt;
469
+ parameterCsn.elements[n] = elt;
442
470
  });
443
-
471
+ linkAssociationTarget(parameterCsn);
472
+ initializeContainments(parameterCsn);
444
473
  // add assoc to result set, FIXME: is the cardinality correct?
445
474
  parameterCsn.elements[parameterToOriginalAssocName] = {
446
475
  '@odata.contained': true,
@@ -543,16 +572,17 @@ function initializeModel(csn, _options)
543
572
  let keys = Object.create(null);
544
573
  let validFrom = [], validKey = [];
545
574
 
546
- let structParent = def.items || def;
547
-
548
575
  // Iterate all struct elements
549
- forEachGeneric(structParent, 'elements', (element, elementName) => {
550
- 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);
551
581
 
552
582
  if(!['event', 'aspect'].includes(def.kind)) {
553
583
  if(element._parent && element._parent.$mySchemaName) {
554
584
  if(!isODataSimpleIdentifier(elementName)) {
555
- signalIllegalIdentifier(elementName, ['definitions', def.name, 'elements', elementName]);
585
+ signalIllegalIdentifier(elementName, ['definitions', def.name].concat(path));
556
586
  } else if (options.isV2() && /^(_|[0-9])/.test(elementName) && ['view', 'entity'].includes(element._parent.kind)) {
557
587
  // FIXME: Rewrite signalIllegalIdentifier function to be more flexible
558
588
  error(null, ['definitions', def.name, 'elements', elementName], { prop: elementName[0] },
@@ -567,6 +597,16 @@ function initializeModel(csn, _options)
567
597
  if(element['@cds.valid.from']) {
568
598
  validFrom.push(element);
569
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);
570
610
 
571
611
  // initialize an association
572
612
  if(isAssociationOrComposition(element)) {
@@ -575,18 +615,6 @@ function initializeModel(csn, _options)
575
615
  assignProp(element._target, '$proxies', []);
576
616
  // $abspath is used as partner path
577
617
  assignProp(element, '$abspath', $path2path(element.$path));
578
-
579
- //forward annotations from managed association element to its foreign keys
580
- if(element.keys && options.isFlatFormat) {
581
- for(let fk of element.keys) {
582
- forAll(element, (attr, attrName) => {
583
- if(attrName[0] === '@' && fk.$generatedFieldName)
584
- def.elements[fk.$generatedFieldName][attrName] = attr;
585
- });
586
- }
587
- }
588
- // and afterwards eventually remove some :)
589
- setSAPSpecificV2AnnotationsToAssociation(options, element, def);
590
618
  }
591
619
 
592
620
  // Collect keys
@@ -594,7 +622,7 @@ function initializeModel(csn, _options)
594
622
  keys[elementName] = element;
595
623
  }
596
624
  applyAppSpecificLateCsnTransformationOnElement(options, element, def, error);
597
- });
625
+ }, [], true, { elementsOnly: true });
598
626
 
599
627
  if(!isDeprecatedEnabled(options, 'v1KeysForTemporal')) {
600
628
  // if artifact has a cds.valid.key mention it as @Core.AlternateKey
@@ -646,14 +674,14 @@ function initializeModel(csn, _options)
646
674
  if(!isStructuredArtifact(struct))
647
675
  return;
648
676
 
649
- forEachMember(struct, element => {
677
+ forEachMemberRecursively(struct.items || struct, (element) => {
650
678
  if (isAssociationOrComposition(element) && !element._ignore) {
651
679
  // setup the constraints object
652
680
  setProp(element, '_constraints', { constraints: Object.create(null), selfs: [], _origins: [], termCount: 0 });
653
681
  // and crack the ON condition
654
682
  resolveOnConditionAndPrepareConstraints(csn, element, messageFunctions);
655
683
  }
656
- });
684
+ }, [], true, { elementsOnly: true });
657
685
  }
658
686
 
659
687
  /*
@@ -669,13 +697,16 @@ function initializeModel(csn, _options)
669
697
  4) All of this can be revoked with options.renderForeignKeys.
670
698
  */
671
699
  function ignoreProperties(struct) {
672
- forEachGeneric(struct, 'elements', (element) => {
700
+ if(!isStructuredArtifact(struct))
701
+ return;
702
+
703
+ forEachMemberRecursively(struct.items || struct, (element) => {
673
704
  if(!element.target) {
674
705
  if(element['@odata.foreignKey4']) {
675
706
  let isContainerAssoc = false;
676
- let elements = struct.elements;
707
+ let elements = (struct.items || struct).elements;
677
708
  let assoc = undefined;
678
- let paths = element['@odata.foreignKey4'].split('.')
709
+ const paths = element['@odata.foreignKey4'].split('.')
679
710
  for(let p of paths) {
680
711
  assoc = elements[p];
681
712
  if(assoc) // could be that the @odata.foreignKey4 was propagated...
@@ -714,7 +745,7 @@ function initializeModel(csn, _options)
714
745
  // ignore it if option odataContainment is true and no foreign keys should be rendered
715
746
  assignAnnotation(element, '@odata.navigable', false);
716
747
  }
717
- });
748
+ }, [], true, { elementsOnly: true });
718
749
  }
719
750
 
720
751
  /*
@@ -726,7 +757,7 @@ function initializeModel(csn, _options)
726
757
  if(!isStructuredArtifact(struct))
727
758
  return;
728
759
 
729
- forEachMember(struct, element => {
760
+ forEachMemberRecursively(struct.items || struct, (element) => {
730
761
  if (isAssociationOrComposition(element) && !element._ignore) {
731
762
  finalizeReferentialConstraints(csn, element, options, info);
732
763
 
@@ -753,7 +784,7 @@ function initializeModel(csn, _options)
753
784
  }
754
785
  }
755
786
  }
756
- });
787
+ }, [], true, { elementsOnly: true });
757
788
  }
758
789
 
759
790
  /*
@@ -816,7 +847,7 @@ function initializeModel(csn, _options)
816
847
  const globalSchemaPrefix = whatsMyServiceRootName(struct.$mySchemaName);
817
848
  // if this artifact is a service member check its associations
818
849
  if(globalSchemaPrefix) {
819
- forEachGeneric(struct, 'elements', element => {
850
+ forEachGeneric(struct.items || struct, 'elements', element => {
820
851
  if(!isAssociationOrComposition(element) || element._ignore || element['@odata.navigable'] === false)
821
852
  return;
822
853
  /*
@@ -874,7 +905,7 @@ function initializeModel(csn, _options)
874
905
  }
875
906
  else {
876
907
  // fake the target to be proxy
877
- element._target.$externalRef = true;
908
+ setProp(element._target, '$externalRef', true);
878
909
  }
879
910
  }
880
911
  else {
@@ -923,7 +954,6 @@ function initializeModel(csn, _options)
923
954
  setProp(proxy, '$keys', Object.create(null));
924
955
  setProp(proxy, '$hasEntitySet', false);
925
956
  setProp(proxy, '$exposedTypes', Object.create(null));
926
-
927
957
  // copy all annotations of the target to the proxy
928
958
  Object.entries(assoc._target).forEach(([k, v]) => {
929
959
  if(k[0] === '@')
@@ -1070,10 +1100,12 @@ function initializeModel(csn, _options)
1070
1100
  if(!elem.target) {
1071
1101
  type.elements[elemName] = Object.create(null);
1072
1102
  Object.keys(elem).forEach(prop => type.elements[elemName][prop] = elem[prop])
1103
+ type.elements[elemName].notNull = true;
1073
1104
  }
1074
1105
  else {
1075
1106
  type.elements[elemName] = createProxyOrSchemaRefForManagedAssoc(elem);
1076
1107
  }
1108
+ setProp(type.elements[elemName], 'name', elem.name);
1077
1109
  });
1078
1110
  return type;
1079
1111
  }
@@ -1100,6 +1132,7 @@ function initializeModel(csn, _options)
1100
1132
  // art is in the target side, clone it and remove key property
1101
1133
  let cloneArt = cloneCsn(art, options);
1102
1134
  setProp(cloneArt, 'name', art.name);
1135
+ cloneArt.notNull = true;
1103
1136
  delete cloneArt.key;
1104
1137
  newElt.elements[art.name] = cloneArt;
1105
1138
  });
@@ -1189,14 +1222,14 @@ function initializeModel(csn, _options)
1189
1222
  function registerProxy(proxy, element) {
1190
1223
  if(proxy === undefined)
1191
1224
  return undefined;
1192
- const proxyName = globalSchemaPrefix + '.' + proxy.name;
1193
- const schemaName = globalSchemaPrefix + '.' + proxy.$mySchemaName;
1225
+ const fqProxyName = globalSchemaPrefix + '.' + proxy.name;
1226
+ const fqSchemaName = globalSchemaPrefix + '.' + proxy.$mySchemaName;
1194
1227
 
1195
1228
  if(!element._target.$cachedProxy)
1196
1229
  assignProp(element._target, '$cachedProxy', Object.create(null));
1197
1230
  if(getProxyForTargetOf(element)) {
1198
1231
  info(null, ['definitions', struct.name, 'elements', element.name],
1199
- { name: proxyName }, 'Proxy EDM entity type $(NAME) has already been registered');
1232
+ { name: fqProxyName }, 'Proxy EDM entity type $(NAME) has already been registered');
1200
1233
  }
1201
1234
  else
1202
1235
  element._target.$cachedProxy[globalSchemaPrefix] = proxy;
@@ -1206,7 +1239,7 @@ function initializeModel(csn, _options)
1206
1239
  // (that may reside in another subcontext schema), but only once
1207
1240
  const schemaSet = new Set();
1208
1241
  // start with the schema name for the proxy
1209
- schemaSet.add(schemaName);
1242
+ schemaSet.add(fqSchemaName);
1210
1243
  // followed by all namespaces that are potentially exposed by the exposed types
1211
1244
  // don't forget to prepend the global namespace prefix
1212
1245
  // schemas are ordered in csn2edm.js for each service
@@ -1219,12 +1252,16 @@ function initializeModel(csn, _options)
1219
1252
  }
1220
1253
  });
1221
1254
  /** @type {object} */
1222
- const alreadyRegistered = csn.definitions[proxyName]
1255
+ const alreadyRegistered = csn.definitions[fqProxyName]
1223
1256
  if(!alreadyRegistered) {
1224
- csn.definitions[proxyName] = proxy;
1257
+ csn.definitions[fqProxyName] = proxy;
1258
+ setProp(proxy, '$path', ['definitions', fqProxyName]);
1225
1259
  Object.entries(proxy.$exposedTypes).forEach(([tn, v]) => {
1226
- if(csn.definitions[globalSchemaPrefix + '.' + tn] === undefined)
1227
- 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
+ }
1228
1265
  });
1229
1266
  info(null, ['definitions', element._parent.name, 'elements', element.name],
1230
1267
  { name: proxy.name }, 'Created proxy EDM entity type $(NAME)');
@@ -1232,16 +1269,16 @@ function initializeModel(csn, _options)
1232
1269
  else if(alreadyRegistered && !alreadyRegistered.$proxy &&
1233
1270
  !['entity', 'view'].includes(alreadyRegistered.kind)) {
1234
1271
  warning(null, ['definitions', element._parent.name, 'elements', element.name],
1235
- { name: proxyName, kind: alreadyRegistered.kind },
1272
+ { name: fqProxyName, kind: alreadyRegistered.kind },
1236
1273
  'No proxy EDM entity type created due to name collision with $(NAME) of kind $(KIND)');
1237
1274
  return undefined;
1238
1275
  }
1239
1276
  }
1240
1277
  else {
1241
1278
  // it's a service reference, just add that reference proxy
1242
- if(!schemas[schemaName]) {
1243
- schemas[schemaName] = proxy;
1244
- schemaNames.push(schemaName);
1279
+ if(!schemas[fqSchemaName]) {
1280
+ schemas[fqSchemaName] = proxy;
1281
+ schemaNames.push(fqSchemaName);
1245
1282
  info(null, ['definitions', struct.name, 'elements', element.name],
1246
1283
  { name: proxy.name }, 'Created EDM namespace reference $(NAME)');
1247
1284
  }
@@ -1286,11 +1323,8 @@ function initializeModel(csn, _options)
1286
1323
  else if(!k.target) {
1287
1324
  struct.$edmKeyPaths.push([kn]);
1288
1325
  }
1289
- // complain about nullable keys in toplevel
1290
- if(k.notNull === false) {
1291
- const pathToElement = ['definitions', struct.name, 'elements', k.name];
1292
- signalErrorForNullableKey(pathToElement);
1293
- }
1326
+ // check toplevel key for spec violations
1327
+ checkKeySpecViolations(k, ['definitions', struct.name, 'elements', k.name]);
1294
1328
  }
1295
1329
  });
1296
1330
  }
@@ -1306,7 +1340,7 @@ function initializeModel(csn, _options)
1306
1340
  If element is of scalar type, return it as an array.
1307
1341
  */
1308
1342
  function produceKeyRefPaths(eltCsn, prefix) {
1309
- let keyPaths = [];
1343
+ const keyPaths = [];
1310
1344
  if(!isEdmPropertyRendered(eltCsn, options)) {
1311
1345
  // let annos = Object.keys(eltCsn).filter(a=>a[0]==='@').join(', ');
1312
1346
  // warning(null, ['definitions', struct.name, 'elements', eltCsn.name ],
@@ -1314,15 +1348,20 @@ function initializeModel(csn, _options)
1314
1348
  return keyPaths;
1315
1349
  }
1316
1350
  // OData requires all elements along the path to be nullable: false (that is either key or notNull)
1317
- 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));
1318
1355
  if(elements) {
1319
1356
  Object.entries(elements).forEach(([eltName, elt]) => {
1320
- keyPaths.push(...produceKeyRefPaths(elt, prefix + options.pathDelimiter + eltName));
1321
- if(elt.notNull === false && !isBuiltinType(elt)) {
1322
- 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}`;
1323
1362
  // we want to point to the element in the entity which is the first path step
1324
- const pathToMaliciousKey = struct.$path.concat(['elements']).concat(odataKeyPath.split('/')[0]);
1325
- signalErrorForNullableKey(pathToMaliciousKey, odataKeyPath);
1363
+ const location = struct.$path.concat(['elements']).concat(pathSegment.split('/')[0]);
1364
+ checkKeySpecViolations(elt, location, pathSegment);
1326
1365
  }
1327
1366
  });
1328
1367
  }
@@ -1353,17 +1392,169 @@ function initializeModel(csn, _options)
1353
1392
  return keyPaths;
1354
1393
  }
1355
1394
 
1356
- function signalErrorForNullableKey(pathToElement, keyElement) {
1357
- error(null, pathToElement,
1358
- {name: keyElement, '#': keyElement ? 'std' : 'scalar'},
1359
- {
1360
- std: 'Key element $(NAME) must not be nullable',
1361
- 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
+ }
1362
1424
  }
1363
- );
1425
+ }
1364
1426
  }
1365
1427
  }
1366
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
+ }
1367
1558
 
1368
1559
  function determineEntitySet(struct) {
1369
1560
  // if this is an entity or a view, determine if an entity set is required or not
@@ -1827,7 +2018,7 @@ function applyAppSpecificLateCsnTransformationOnStructure(options, struct, error
1827
2018
  let key = { name: keyName, key : true, type : 'cds.String', '@sap.sortable':false, '@sap.filterable':false, '@UI.Hidden': true };
1828
2019
  elements[keyName] = key;
1829
2020
  setProp(struct, '$keys',{ [keyName] : key } );
1830
- forEachGeneric(struct, 'elements', (e,n) =>
2021
+ forEachGeneric(struct.items || struct, 'elements', (e,n) =>
1831
2022
  {
1832
2023
  if(e.key) delete e.key;
1833
2024
  elements[n] = e;
@@ -1967,19 +2158,17 @@ function setSAPSpecificV2AnnotationsToEntitySet(options, carrier) {
1967
2158
  }
1968
2159
  }
1969
2160
 
1970
- function setSAPSpecificV2AnnotationsToAssociation(options, carrier, struct) {
1971
- if(!options.isV2())
1972
- return;
2161
+ function setSAPSpecificV2AnnotationsToAssociation(carrier) {
1973
2162
  // documented in https://wiki.scn.sap.com/wiki/display/EmTech/SAP+Annotations+for+OData+Version+2.0
1974
2163
  const SetAttributes = {
1975
2164
  // Applicable to NavProp and foreign keys, add to AssociationSet
1976
- '@sap.creatable' : (struct, c,pn, pv) => { addToSetAttr(struct, c, pn, pv, false); },
2165
+ '@sap.creatable' : (c, pn, pv) => { addToAssociationSet(c, pn, pv, false); },
1977
2166
  // Not applicable to NavProp, applicable to foreign keys, add to AssociationSet
1978
- '@sap.updatable' : addToSetAttr,
2167
+ '@sap.updatable' : addToAssociationSet,
1979
2168
  // Not applicable to NavProp, not applicable to foreign key, add to AssociationSet
1980
- '@sap.deletable': (struct, c, pn, pv) => {
1981
- addToSetAttr(struct, c, pn, pv);
1982
- removeFromForeignKey(struct, c, pn);
2169
+ '@sap.deletable': (c, pn, pv) => {
2170
+ addToAssociationSet(c, pn, pv);
2171
+ removeFromForeignKey(c, pn);
1983
2172
  },
1984
2173
  // applicable to NavProp, not applicable to foreign keys, not applicable to AssociationSet
1985
2174
  '@sap.creatable.path': removeFromForeignKey,
@@ -1987,24 +2176,22 @@ function setSAPSpecificV2AnnotationsToAssociation(options, carrier, struct) {
1987
2176
  };
1988
2177
 
1989
2178
  Object.entries(carrier).forEach(([p, v]) => {
1990
- (SetAttributes[p] || function() {/* no-op */})(struct, carrier, p, v);
2179
+ (SetAttributes[p] || function() {/* no-op */})(carrier, p, v);
1991
2180
  });
1992
2181
 
1993
- function addToSetAttr(struct, carrier, propName, propValue, removeFromType=true) {
1994
- assignProp(carrier, '_SetAttributes', Object.create(null));
1995
- assignAnnotation(carrier._SetAttributes, propName, propValue);
1996
- if(removeFromType) {
1997
- 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
+ }
1998
2189
  }
1999
2190
  }
2000
2191
 
2001
- function removeFromForeignKey(struct, carrier, propName) {
2002
- if(carrier.target && carrier.keys) {
2003
- struct.elements && Object.values(struct.elements).forEach(e => {
2004
- if(e['@odata.foreignKey4'] === carrier.name) {
2005
- delete e[propName];
2006
- }
2007
- });
2192
+ function removeFromForeignKey(carrier, propName) {
2193
+ if(carrier['@odata.foreignKey4'] && carrier[propName] !== undefined) {
2194
+ delete carrier[propName];
2008
2195
  }
2009
2196
  }
2010
2197
  }