@sap/cds-compiler 2.11.2 → 2.13.6

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 (140) hide show
  1. package/CHANGELOG.md +175 -2
  2. package/bin/.eslintrc.json +1 -2
  3. package/bin/cds_update_identifiers.js +10 -8
  4. package/bin/cdsc.js +23 -17
  5. package/bin/cdsse.js +2 -2
  6. package/bin/cdsv2m.js +3 -2
  7. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  8. package/doc/CHANGELOG_BETA.md +25 -6
  9. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  10. package/doc/NameResolution.md +21 -16
  11. package/lib/api/main.js +32 -79
  12. package/lib/api/options.js +3 -2
  13. package/lib/api/validate.js +2 -1
  14. package/lib/backends.js +16 -26
  15. package/lib/base/dictionaries.js +0 -8
  16. package/lib/base/error.js +26 -0
  17. package/lib/base/keywords.js +10 -19
  18. package/lib/base/location.js +9 -4
  19. package/lib/base/message-registry.js +75 -9
  20. package/lib/base/messages.js +31 -35
  21. package/lib/base/model.js +2 -62
  22. package/lib/base/optionProcessorHelper.js +246 -183
  23. package/lib/checks/.eslintrc.json +2 -0
  24. package/lib/checks/actionsFunctions.js +2 -1
  25. package/lib/checks/annotationsOData.js +1 -1
  26. package/lib/checks/cdsPersistence.js +2 -1
  27. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  28. package/lib/checks/enricher.js +17 -1
  29. package/lib/checks/foreignKeys.js +4 -4
  30. package/lib/checks/invalidTarget.js +3 -1
  31. package/lib/checks/managedInType.js +4 -4
  32. package/lib/checks/managedWithoutKeys.js +3 -1
  33. package/lib/checks/queryNoDbArtifacts.js +1 -3
  34. package/lib/checks/selectItems.js +4 -4
  35. package/lib/checks/sql-snippets.js +94 -0
  36. package/lib/checks/types.js +1 -1
  37. package/lib/checks/unknownMagic.js +1 -1
  38. package/lib/checks/validator.js +12 -7
  39. package/lib/compiler/assert-consistency.js +12 -8
  40. package/lib/compiler/base.js +0 -1
  41. package/lib/compiler/builtins.js +42 -21
  42. package/lib/compiler/checks.js +46 -12
  43. package/lib/compiler/cycle-detector.js +1 -1
  44. package/lib/compiler/define.js +1103 -0
  45. package/lib/compiler/extend.js +983 -0
  46. package/lib/compiler/finalize-parse-cdl.js +231 -0
  47. package/lib/compiler/index.js +46 -39
  48. package/lib/compiler/kick-start.js +190 -0
  49. package/lib/compiler/moduleLayers.js +4 -4
  50. package/lib/compiler/populate.js +1226 -0
  51. package/lib/compiler/propagator.js +113 -47
  52. package/lib/compiler/resolve.js +1433 -0
  53. package/lib/compiler/shared.js +100 -65
  54. package/lib/compiler/tweak-assocs.js +529 -0
  55. package/lib/compiler/utils.js +215 -33
  56. package/lib/edm/.eslintrc.json +5 -0
  57. package/lib/edm/annotations/genericTranslation.js +38 -25
  58. package/lib/edm/annotations/preprocessAnnotations.js +3 -3
  59. package/lib/edm/csn2edm.js +10 -9
  60. package/lib/edm/edm.js +19 -20
  61. package/lib/edm/edmPreprocessor.js +166 -95
  62. package/lib/edm/edmUtils.js +127 -34
  63. package/lib/gen/Dictionary.json +92 -43
  64. package/lib/gen/language.checksum +1 -1
  65. package/lib/gen/language.interp +11 -1
  66. package/lib/gen/language.tokens +86 -82
  67. package/lib/gen/languageLexer.interp +18 -1
  68. package/lib/gen/languageLexer.js +925 -847
  69. package/lib/gen/languageLexer.tokens +78 -74
  70. package/lib/gen/languageParser.js +5434 -4298
  71. package/lib/json/from-csn.js +59 -17
  72. package/lib/json/to-csn.js +189 -71
  73. package/lib/language/antlrParser.js +3 -3
  74. package/lib/language/docCommentParser.js +3 -3
  75. package/lib/language/errorStrategy.js +26 -8
  76. package/lib/language/genericAntlrParser.js +144 -53
  77. package/lib/language/language.g4 +424 -200
  78. package/lib/language/multiLineStringParser.js +536 -0
  79. package/lib/main.d.ts +550 -61
  80. package/lib/main.js +38 -11
  81. package/lib/model/api.js +3 -1
  82. package/lib/model/csnRefs.js +322 -198
  83. package/lib/model/csnUtils.js +226 -370
  84. package/lib/model/enrichCsn.js +124 -69
  85. package/lib/model/revealInternalProperties.js +29 -7
  86. package/lib/model/sortViews.js +10 -2
  87. package/lib/modelCompare/compare.js +17 -12
  88. package/lib/optionProcessor.js +8 -3
  89. package/lib/render/.eslintrc.json +1 -2
  90. package/lib/render/DuplicateChecker.js +1 -1
  91. package/lib/render/manageConstraints.js +36 -33
  92. package/lib/render/toCdl.js +174 -275
  93. package/lib/render/toHdbcds.js +203 -122
  94. package/lib/render/toRename.js +7 -10
  95. package/lib/render/toSql.js +161 -82
  96. package/lib/render/utils/common.js +22 -8
  97. package/lib/render/utils/sql.js +10 -7
  98. package/lib/render/utils/stringEscapes.js +111 -0
  99. package/lib/sql-identifier.js +1 -1
  100. package/lib/transform/.eslintrc.json +5 -0
  101. package/lib/transform/braceExpression.js +4 -2
  102. package/lib/transform/db/.eslintrc.json +2 -0
  103. package/lib/transform/db/applyTransformations.js +212 -0
  104. package/lib/transform/db/assertUnique.js +1 -1
  105. package/lib/transform/db/associations.js +187 -0
  106. package/lib/transform/db/cdsPersistence.js +150 -0
  107. package/lib/transform/db/constraints.js +61 -56
  108. package/lib/transform/db/expansion.js +50 -29
  109. package/lib/transform/db/flattening.js +556 -106
  110. package/lib/transform/db/groupByOrderBy.js +3 -1
  111. package/lib/transform/db/temporal.js +236 -0
  112. package/lib/transform/db/transformExists.js +103 -28
  113. package/lib/transform/db/views.js +92 -44
  114. package/lib/transform/draft/.eslintrc.json +38 -0
  115. package/lib/transform/{db/draft.js → draft/db.js} +9 -7
  116. package/lib/transform/draft/odata.js +227 -0
  117. package/lib/transform/forHanaNew.js +98 -783
  118. package/lib/transform/forOdataNew.js +22 -175
  119. package/lib/transform/localized.js +36 -32
  120. package/lib/transform/odata/generateForeignKeyElements.js +3 -3
  121. package/lib/transform/odata/referenceFlattener.js +95 -89
  122. package/lib/transform/odata/structureFlattener.js +1 -1
  123. package/lib/transform/odata/toFinalBaseType.js +86 -12
  124. package/lib/transform/odata/typesExposure.js +5 -5
  125. package/lib/transform/odata/utils.js +2 -2
  126. package/lib/transform/transformUtilsNew.js +47 -33
  127. package/lib/transform/translateAssocsToJoins.js +13 -30
  128. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  129. package/lib/transform/universalCsn/coreComputed.js +170 -0
  130. package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
  131. package/lib/transform/universalCsn/utils.js +63 -0
  132. package/lib/utils/file.js +8 -3
  133. package/lib/utils/objectUtils.js +30 -0
  134. package/lib/utils/timetrace.js +8 -2
  135. package/package.json +1 -1
  136. package/share/messages/README.md +26 -0
  137. package/lib/compiler/definer.js +0 -2349
  138. package/lib/compiler/resolver.js +0 -2922
  139. package/lib/transform/db/helpers.js +0 -58
  140. package/lib/transform/universalCsnEnricher.js +0 -67
@@ -39,23 +39,27 @@ function initializeModel(csn, _options, messageFunctions)
39
39
  if (!_options)
40
40
  throw Error('Please debug me: initializeModel must be invoked with options');
41
41
 
42
- const { info, warning, error, throwWithError } = messageFunctions;
42
+ const { info, warning, error, message, throwWithError } = messageFunctions;
43
43
 
44
44
  const csnUtils = getUtils(csn);
45
45
  const {
46
+ inspectRef,
46
47
  getCsnDef,
47
48
  getFinalTypeDef,
48
49
  isStructured,
49
50
  isAssocOrComposition,
50
51
  } = getUtils(csn);
51
52
 
53
+ // proxies are merged into the final model after all proxy elements are collected
54
+ const proxyCache = [];
52
55
 
53
56
  // make sure options are complete
54
57
  let options = validateOptions(_options);
55
58
 
56
59
  // Fetch service definitions
57
- const serviceRoots = Object.keys(csn.definitions || {}).reduce((serviceRoots, artName) => {
58
- const art = csn.definitions[artName];
60
+ const allDefs = Object.entries(csn.definitions||{});
61
+
62
+ const serviceRoots = allDefs.reduce((serviceRoots, [artName, art]) => {
59
63
  if(art.kind === 'service') {
60
64
  serviceRoots[artName] = Object.assign(art, { name: artName });
61
65
  }
@@ -64,11 +68,24 @@ function initializeModel(csn, _options, messageFunctions)
64
68
 
65
69
  // first of all we need to know about all 'real' user defined services
66
70
  const serviceRootNames = Object.keys(serviceRoots).sort((a,b)=>b.length-a.length);
71
+
67
72
  function whatsMyServiceRootName(n, self=true) {
68
73
  return serviceRootNames.reduce((rc, sn) => !rc && n && n.startsWith(sn + '.') || (n === sn && self) ? sn : rc, undefined);
69
74
  }
75
+
76
+ // find a globally unambiguous schema name to collect all top level 'root' types
77
+ // TODO: work on service basis (this requires post exposure renaming)
78
+ let autoexposeSchemaName = 'root';
79
+ let i = 1;
80
+ while (allDefs.some(([artName, _art]) => {
81
+ const p = artName.split('.');
82
+ return p.length === 2 && p[0] === autoexposeSchemaName;
83
+ })) {
84
+ autoexposeSchemaName = 'root' + i++;
85
+ }
86
+
70
87
  if(serviceRootNames.length === 0) {
71
- return [serviceRoots, Object.create(null), whatsMyServiceRootName, options];
88
+ return [serviceRoots, Object.create(null), whatsMyServiceRootName, autoexposeSchemaName, options];
72
89
  }
73
90
 
74
91
  // Structural CSN inbound QA checks
@@ -103,7 +120,7 @@ function initializeModel(csn, _options, messageFunctions)
103
120
  service), the namespace (including (sub-)contexts) or as last resort (if the type name
104
121
  has no prefix path) a 'root' namespace.
105
122
  */
106
- const schemas = typesExposure(csn, whatsMyServiceRootName, options, csnUtils, { error });
123
+ const schemas = typesExposure(csn, whatsMyServiceRootName, autoexposeSchemaName, options, csnUtils, { error });
107
124
 
108
125
  // First attach names to all definitions (and actions/params) in the model
109
126
  // elements are done in initializeStruct
@@ -145,6 +162,9 @@ function initializeModel(csn, _options, messageFunctions)
145
162
  // create association target proxies
146
163
  // Decide if an entity set needs to be constructed or not
147
164
  forEachDefinition(csn, [ exposeTargetsAsProxiesOrSchemaRefs, determineEntitySet ]);
165
+ // finalize proxy creation
166
+ mergeProxiesIntoModel();
167
+
148
168
  if(options.isV4())
149
169
  forEachDefinition(csn, initializeEdmNavPropBindingTargets);
150
170
 
@@ -156,7 +176,7 @@ function initializeModel(csn, _options, messageFunctions)
156
176
  forEachDefinition(csn, [ initializeEdmKeyRefPaths, initializeEdmNavPropBindingPaths,
157
177
  initializeEdmTypesAndDescription, checkArtifactIdentifierAndBoundActions ]);
158
178
  }
159
- return [serviceRoots, schemas, whatsMyServiceRootName, options];
179
+ return [serviceRoots, schemas, whatsMyServiceRootName, autoexposeSchemaName, options];
160
180
 
161
181
  //////////////////////////////////////////////////////////////////////
162
182
  //
@@ -186,16 +206,16 @@ function initializeModel(csn, _options, messageFunctions)
186
206
  const dotEntityNameMap = Object.create(null);
187
207
  const dotTypeNameMap = Object.create(null);
188
208
  forEachDefinition(csn, (def, defName) => {
189
- if(['entity', 'view', 'type', 'action', 'function'].includes(def.kind)) {
209
+ if(['entity', 'type', 'action', 'function'].includes(def.kind)) {
190
210
  const rootDef = getRootDef(defName);
191
211
  // if this definition has a root def and the root def is not the service/schema name
192
212
  // => service C { type D.E }, replace the prefix dots with underscores
193
213
  if(rootDef && defName !== rootDef && rootDef !== getSchemaPrefix(defName)) {
194
214
  let newDefName = rootDef + '.' + defName.replace(rootDef + '.', '').replace(/\./g, '_');
195
215
  // store renamed types in correlation maps for later renaming
196
- if(['entity', 'view'].includes(def.kind))
216
+ if(def.kind === 'entity')
197
217
  dotEntityNameMap[defName] = newDefName;
198
- if(['type'].includes(def.kind))
218
+ if(def.kind === 'type')
199
219
  dotTypeNameMap[defName] = newDefName;
200
220
  // rename in csn.definitions
201
221
  const art = csn.definitions[newDefName];
@@ -252,7 +272,7 @@ function initializeModel(csn, _options, messageFunctions)
252
272
  */
253
273
  function splitDottedDefinitionsIntoSeparateServices() {
254
274
  forEachDefinition(csn, (def, defName) => {
255
- if(![ 'service' ].includes(def.kind)) {
275
+ if(def.kind !== 'service') {
256
276
  const myServiceRoot = whatsMyServiceRootName(defName);
257
277
  const mySchemaPrefix = getSchemaPrefix(defName);
258
278
  if(myServiceRoot && options.isV4() &&
@@ -342,7 +362,7 @@ function initializeModel(csn, _options, messageFunctions)
342
362
  // entity set. Instead try to rewrite the annotation in such a way that it is effective
343
363
  // on the containment navigation property.
344
364
  function initializeContainments(container) {
345
- if(['entity', 'view'].includes(container.kind)) {
365
+ if(container.kind === 'entity') {
346
366
  forEachMemberRecursively(container, initContainments,
347
367
  [], true, { elementsOnly: true });
348
368
  }
@@ -455,8 +475,8 @@ function initializeModel(csn, _options, messageFunctions)
455
475
  // propagate containment information, if containment is recursive, use parameterCsn.name as _containerEntity
456
476
  if(entityCsn._containerEntity) {
457
477
  setProp(parameterCsn, '_containerEntity', []);
458
- for(let c of entityCsn._containerEntity) {
459
- parameterCsn._containerEntity.push((c==entityCsn.name)?parameterCsn.name:c);
478
+ for(const c of entityCsn._containerEntity) {
479
+ parameterCsn._containerEntity.push((c === entityCsn.name) ? parameterCsn.name : c);
460
480
  }
461
481
  }
462
482
  entityCsn._containerEntity = [ parameterCsn ];
@@ -466,6 +486,17 @@ function initializeModel(csn, _options, messageFunctions)
466
486
  elt.name = n;
467
487
  delete elt.kind;
468
488
  elt.key = true; // params become primary key in parameter entity
489
+ /*
490
+ Spec meeting decision 28.02.22:
491
+ Annotation @sap.parameter allows two values "mandatory"/"optional".
492
+ Question was how to deal with incompatible "optional".
493
+ Only "mandatory" is allowed because in RAP all parameters are NOT NULL
494
+ and so they are in CAP (all view parameters become primary keys which are not null).
495
+ */
496
+ if(options.isV2())
497
+ assignAnnotation(elt, '@sap.parameter', 'mandatory');
498
+ else
499
+ assignAnnotation(elt, '@Common.FieldControl', { '#': 'Mandatory' });
469
500
  parameterCsn.elements[n] = elt;
470
501
  });
471
502
  linkAssociationTarget(parameterCsn);
@@ -489,12 +520,18 @@ function initializeModel(csn, _options, messageFunctions)
489
520
  member.$path[1] = parameterEntityName;
490
521
  });
491
522
 
492
-
493
- csn.definitions[parameterCsn.name] = parameterCsn;
523
+ if(csn.definitions[parameterCsn.name])
524
+ error('odata-definition-exists', [ 'definitions', entityName ], { '#': 'std', name: parameterCsn.name });
525
+ else
526
+ csn.definitions[parameterCsn.name] = parameterCsn;
494
527
  // modify the original parameter entity with backlink and new name
495
- csn.definitions[originalEntityName] = entityCsn;
496
- delete csn.definitions[entityCsn.name];
497
- entityCsn.name = originalEntityName;
528
+ if(csn.definitions[originalEntityName])
529
+ error('odata-definition-exists', [ 'definitions', entityName ], { '#': 'std', name: originalEntityName });
530
+ else {
531
+ csn.definitions[originalEntityName] = entityCsn;
532
+ delete csn.definitions[entityCsn.name];
533
+ entityCsn.name = originalEntityName;
534
+ }
498
535
  setProp(entityCsn, '$entitySetName', originalEntitySetName);
499
536
  // add backlink association
500
537
  if(hasBacklink) {
@@ -546,6 +583,7 @@ function initializeModel(csn, _options, messageFunctions)
546
583
  // convert $path to path starting at main artifact
547
584
  function $path2path(p) {
548
585
  const path = [];
586
+ /** @type {object} */
549
587
  let env = csn;
550
588
  for (let i = 0; p && env && i < p.length; i++) {
551
589
  const ps = p[i];
@@ -574,16 +612,16 @@ function initializeModel(csn, _options, messageFunctions)
574
612
 
575
613
  // Iterate all struct elements
576
614
  forEachMemberRecursively(def.items || def, (element, elementName, prop, path = [], construct) => {
577
- if(!['elements'].includes(prop))
615
+ if(prop !== 'elements')
578
616
  return;
579
617
 
580
618
  initElement(element, elementName, construct);
581
619
 
582
- if(!['event', 'aspect'].includes(def.kind)) {
620
+ if(def.kind !== 'event' && def.kind !== 'aspect') {
583
621
  if(element._parent && element._parent.$mySchemaName) {
584
622
  if(!isODataSimpleIdentifier(elementName)) {
585
623
  signalIllegalIdentifier(elementName, ['definitions', def.name].concat(path));
586
- } else if (options.isV2() && /^(_|[0-9])/.test(elementName) && ['view', 'entity'].includes(element._parent.kind)) {
624
+ } else if (options.isV2() && /^(_|[0-9])/.test(elementName) && element._parent.kind === 'entity') {
587
625
  // FIXME: Rewrite signalIllegalIdentifier function to be more flexible
588
626
  error(null, ['definitions', def.name, 'elements', elementName], { prop: elementName[0] },
589
627
  'Element names must not start with $(PROP) for OData V2');
@@ -769,7 +807,7 @@ function initializeModel(csn, _options, messageFunctions)
769
807
  if(element._constraints._partnerCsn.cardinality.src) {
770
808
  let srcMult = (element._constraints._partnerCsn.cardinality.src == 1) ? '0..1' : '*';
771
809
  let newMult = (element.cardinality.max > 1) ? '*' : '0..1';
772
- if(options.isV2() && srcMult != newMult) {
810
+ if(options.isV2() && srcMult !== newMult) {
773
811
  // Association 'E_toF': Multiplicity of Role='E' defined to '*', conflicting with target multiplicity '0..1' from
774
812
  warning(null, null, `Source cardinality "${element._constraints._partnerCsn.cardinality.src}" of "${element._constraints._partnerCsn._parent.name}/${element._constraints._partnerCsn.name}" conflicts with target cardinality "${element.cardinality.max}" of association "${element._parent.name}/${element.name}"`);
775
813
  }
@@ -838,7 +876,7 @@ function initializeModel(csn, _options, messageFunctions)
838
876
  proxies.
839
877
  */
840
878
  function exposeTargetsAsProxiesOrSchemaRefs(struct) {
841
- if([ 'context', 'service' ].includes(struct.kind) || struct.$proxy)
879
+ if(struct.kind === 'context' || struct.kind === 'service' || struct.$proxy)
842
880
  return;
843
881
 
844
882
  // globalSchemaPrefix is the prefix for all proxy registrations and must not change
@@ -873,6 +911,7 @@ function initializeModel(csn, _options, messageFunctions)
873
911
  // If the target is in another schema, check if both the source and the target share the same service name.
874
912
  // If they share the same service name, then it is just a cross schema navigation within the same EDM, no
875
913
  // proxy required.
914
+ // association must be managed and not unmanaged
876
915
 
877
916
  // odataProxies (P) and odataXServiceRefs (X) are evalutated as follows:
878
917
  // P | X | Action
@@ -883,7 +922,10 @@ function initializeModel(csn, _options, messageFunctions)
883
922
 
884
923
  const targetSchemaName = element._target.$mySchemaName;
885
924
  if(isProxyRequired(element)) {
886
- if(options.isV4() && (options.toOdata.odataProxies || options.toOdata.odataXServiceRefs)) {
925
+ if(options.isV4() &&
926
+ (options.toOdata.odataProxies || options.toOdata.odataXServiceRefs)
927
+ // must be a managed association with keys and no ON condition
928
+ /* && element.keys && !element.on */) {
887
929
  // reuse proxy if available
888
930
  let proxy = getProxyForTargetOf(element);
889
931
  if(!proxy) {
@@ -902,6 +944,7 @@ function initializeModel(csn, _options, messageFunctions)
902
944
  element._constraints.constraints = Object.create(null);
903
945
  if(proxy.kind === 'entity') {
904
946
  element._target = proxy;
947
+ populateProxyElements(element, proxy, getForeignKeyDefinitions(element, ['definitions', struct.name, 'elements', element.name]));
905
948
  }
906
949
  else {
907
950
  // fake the target to be proxy
@@ -926,7 +969,7 @@ function initializeModel(csn, _options, messageFunctions)
926
969
 
927
970
  function noNavPropMsg(elt) {
928
971
  warning(null, ['definitions', struct.name, 'elements', elt.name],
929
- { target: elt._target.name }, 'No OData navigation property generated, target $(TARGET) is outside any service');
972
+ { target: elt._target.name, service: globalSchemaPrefix }, 'No OData navigation property generated, target $(TARGET) is outside of service $(SERVICE)');
930
973
  }
931
974
 
932
975
  function createSchemaRefFor(assoc, targetSchemaName) {
@@ -947,10 +990,10 @@ function initializeModel(csn, _options, messageFunctions)
947
990
  // 1) construct the proxy definition
948
991
  // proxyShortName: strip the serviceName and replace '.' with '_'
949
992
  const proxyShortName = assoc._target.name.replace(proxySchemaName + '.', '').replace(/\./g, '_');
950
- // fullName: Prepend serviceName and if in same service add '_proxy'
951
- const fullName = proxySchemaName + '.' + proxyShortName;
952
- const proxy = { name: fullName, kind: 'entity', $proxy: true, elements: Object.create(null) };
993
+ // fullName: Prepend serviceName and if in same service add '_proxy'
994
+ const proxy = { name: proxySchemaName + '.' + proxyShortName, kind: 'entity', $proxy: true, elements: Object.create(null) };
953
995
  setProp(proxy, '$mySchemaName', proxySchemaName);
996
+ setProp(proxy, '$proxyShortName', proxyShortName);
954
997
  setProp(proxy, '$keys', Object.create(null));
955
998
  setProp(proxy, '$hasEntitySet', false);
956
999
  setProp(proxy, '$exposedTypes', Object.create(null));
@@ -961,59 +1004,67 @@ function initializeModel(csn, _options, messageFunctions)
961
1004
  });
962
1005
 
963
1006
  // 2) create the elements and $keys
964
- populateProxyElements(proxy, assoc._target.$keys);
965
- // 3) sort the exposed types so that they appear lexicographically ordered in the EDM
966
- proxy.$exposedTypes = Object.keys(proxy.$exposedTypes).sort().reduce((dict, tn) => {
967
- dict[tn] = proxy.$exposedTypes[tn];
968
- return dict
969
- }, Object.create(null));
970
-
1007
+ populateProxyElements(assoc, proxy, assoc._target.$keys);
971
1008
  return proxy;
972
1009
 
1010
+ }
1011
+
1012
+ // return array of foreign key defs in the target
1013
+ function getForeignKeyDefinitions(e, path=undefined) {
1014
+ // populate foreign keys, if a key can't be resolved -> parametertype redirects, ignore them
1015
+ return e.keys ? e.keys.map((_fk, i) => {
1016
+ return inspectRef([...(path || e.$path), 'keys', i]).art;
1017
+ }).filter(fk=>fk) : [];
1018
+ }
973
1019
  // copy over the primary keys of the target and trigger the type exposure
974
- function populateProxyElements(proxy, keys) {
975
- forAll(keys, e => {
976
- if (isEdmPropertyRendered(e, options)) {
977
- let newElt = undefined;
978
- if(isAssocOrComposition(e.type)) {
979
- if(!e.on && e.keys) {
980
- if(options.toOdata.odataNoTransitiveProxies)
981
- newElt = convertManagedAssocIntoStruct(e);
982
- else
1020
+ function populateProxyElements(assoc, proxy, elements) {
1021
+ forAll(elements, e => {
1022
+ if (isEdmPropertyRendered(e, options) && !proxy.elements[e.name]) {
1023
+ let newElt = undefined;
1024
+ if(isAssocOrComposition(e.type)) {
1025
+ if(!e.on && e.keys) {
1026
+ if(options.toOdata.odataNoTransitiveProxies)
1027
+ newElt = convertManagedAssocIntoStruct(e);
1028
+ else
983
1029
  newElt = createProxyOrSchemaRefForManagedAssoc(e);
984
- }
985
- else {
986
- info(null, ['definitions', struct.name, 'elements', assoc.name],
987
- { name: fullName, target: assoc._target.name },
988
- 'Unmanaged associations are not supported as primary keys for proxy entity type $(NAME) of unexposed association target $(TARGET)');
989
- }
990
1030
  }
991
1031
  else {
992
- newElt = cloneCsn(e, options);
1032
+ info(null, ['definitions', struct.name, 'elements', assoc.name],
1033
+ { name: proxy.nname, target: assoc._target.name },
1034
+ 'Unmanaged associations are not supported as primary keys for proxy entity type $(NAME) of unexposed association target $(TARGET)');
993
1035
  }
994
- if(newElt) {
995
- initElement(newElt, e.name, proxy);
996
- if(isStructured(newElt)) {
1036
+ }
1037
+ else {
1038
+ newElt = cloneCsn(e, options);
1039
+ }
1040
+ if(newElt) {
1041
+ initElement(newElt, e.name, proxy);
1042
+ if(isStructured(newElt)) {
997
1043
  // argument proxySchemaName forces an anonymous type definition for newElt into the
998
1044
  // proxy schema. If omitted, this exposure defaults to 'root', in case API flavor of the day
999
1045
  // changes...
1000
- exposeStructTypeForProxyOf(proxy, newElt, proxyShortName + '_' + newElt.name, proxySchemaName);
1046
+ exposeStructTypeForProxyOf(proxy, newElt, proxy.$proxyShortName + '_' + newElt.name, proxy.$mySchemaName);
1001
1047
  // elements of newElt are required for key ref paths
1002
- }
1003
- // all elements must become primary key
1004
- proxy.$keys[e.name] = proxy.elements[newElt.name] = newElt;
1005
1048
  }
1049
+ proxy.elements[newElt.name] = newElt;
1050
+ if(newElt.key)
1051
+ proxy.$keys[newElt.name] = newElt;
1006
1052
  }
1007
- });
1008
- }
1053
+ }
1054
+ });
1055
+ // 3) sort the exposed types so that they appear lexicographically ordered in the EDM
1056
+ proxy.$exposedTypes = Object.keys(proxy.$exposedTypes).sort().reduce((dict, tn) => {
1057
+ dict[tn] = proxy.$exposedTypes[tn];
1058
+ return dict
1059
+ }, Object.create(null));
1009
1060
 
1010
1061
  // If 'node' exists and has a structured type that is not exposed in 'service', (because the type is
1011
1062
  // anonymous or has a definition outside of 'service'), create an equivalent type in 'service', either
1012
1063
  // using the type's name or (if anonymous) 'artificialName', and make 'node' use that type instead.
1013
1064
  // Complain if there is an error.
1014
- function exposeStructTypeForProxyOf(proxy, node, artificialName, typeSchemaName='root') {
1065
+ function exposeStructTypeForProxyOf(proxy, node, artificialName, typeSchemaName=autoexposeSchemaName) {
1015
1066
  const isNotInProtNS = node.type ? !isBuiltinType(node.type) : true;
1016
- // Always expose types referred to by a proxy, never reuse an eventually exisiting type
1067
+ // Always expose types referred to by a proxy, never reuse an eventually existing type
1017
1068
  // as the nested elements must all be not nullable
1018
1069
  if (isNotInProtNS) {
1019
1070
  let typeDef = node.type ? csn.definitions[node.type] : /* anonymous type */ node;
@@ -1045,7 +1096,7 @@ function initializeModel(csn, _options, messageFunctions)
1045
1096
  if(typeClone) {
1046
1097
  // Recurse into elements of 'type' (if any)
1047
1098
  typeClone.elements && Object.entries(typeClone.elements).forEach( ([elemName, elem]) => {
1048
- // if this is a foreign key elment, we must check wether or not the association
1099
+ // if this is a foreign key elment, we must check whether or not the association
1049
1100
  // has been exposed as proxy. If it has not been exposed, no further structured
1050
1101
  // types must be exposed as 'Proxy_' types.
1051
1102
 
@@ -1102,7 +1153,8 @@ function initializeModel(csn, _options, messageFunctions)
1102
1153
  Object.keys(elem).forEach(prop => type.elements[elemName][prop] = elem[prop])
1103
1154
  type.elements[elemName].notNull = true;
1104
1155
  }
1105
- else {
1156
+ else if(elem.keys && !elem.on) {
1157
+ // a primary key can never be an unmanaged association
1106
1158
  type.elements[elemName] = createProxyOrSchemaRefForManagedAssoc(elem);
1107
1159
  }
1108
1160
  setProp(type.elements[elemName], 'name', elem.name);
@@ -1159,6 +1211,7 @@ function initializeModel(csn, _options, messageFunctions)
1159
1211
  }
1160
1212
  else if(options.toOdata.odataProxies) {
1161
1213
  proxy = createProxyFor(e, e._target.$mySchemaName);
1214
+ populateProxyElements(e, proxy, getForeignKeyDefinitions(e));
1162
1215
  }
1163
1216
  proxy = registerProxy(proxy, e);
1164
1217
  }
@@ -1222,8 +1275,11 @@ function initializeModel(csn, _options, messageFunctions)
1222
1275
  function registerProxy(proxy, element) {
1223
1276
  if(proxy === undefined)
1224
1277
  return undefined;
1225
- const fqProxyName = globalSchemaPrefix + '.' + proxy.name;
1226
- const fqSchemaName = globalSchemaPrefix + '.' + proxy.$mySchemaName;
1278
+
1279
+ setProp(proxy, '$globalSchemaPrefix', globalSchemaPrefix);
1280
+ setProp(proxy, '$origin', element);
1281
+
1282
+ const fqProxyName = proxy.$globalSchemaPrefix + '.' + proxy.name;
1227
1283
 
1228
1284
  if(!element._target.$cachedProxy)
1229
1285
  assignProp(element._target, '$cachedProxy', Object.create(null));
@@ -1231,8 +1287,19 @@ function initializeModel(csn, _options, messageFunctions)
1231
1287
  info(null, ['definitions', struct.name, 'elements', element.name],
1232
1288
  { name: fqProxyName }, 'Proxy EDM entity type $(NAME) has already been registered');
1233
1289
  }
1234
- else
1290
+ else {
1291
+ determineEntitySet(proxy);
1292
+ proxyCache.push(proxy);
1235
1293
  element._target.$cachedProxy[globalSchemaPrefix] = proxy;
1294
+ }
1295
+ return proxy;
1296
+ }
1297
+ }
1298
+
1299
+ function mergeProxiesIntoModel() {
1300
+ proxyCache.forEach(proxy => {
1301
+ const fqProxyName = proxy.$globalSchemaPrefix + '.' + proxy.name;
1302
+ const fqSchemaName = proxy.$globalSchemaPrefix + '.' + proxy.$mySchemaName;
1236
1303
 
1237
1304
  if(proxy.kind === 'entity') {
1238
1305
  // collect all schemas even for newly exposed types
@@ -1244,7 +1311,7 @@ function initializeModel(csn, _options, messageFunctions)
1244
1311
  // don't forget to prepend the global namespace prefix
1245
1312
  // schemas are ordered in csn2edm.js for each service
1246
1313
  Object.keys(proxy.$exposedTypes).forEach(t =>
1247
- schemaSet.add(globalSchemaPrefix + '.' + getSchemaPrefix(t)));
1314
+ schemaSet.add(proxy.$globalSchemaPrefix + '.' + getSchemaPrefix(t)));
1248
1315
  schemaSet.forEach(schemaName => {
1249
1316
  if(!schemas[schemaName]) {
1250
1317
  schemas[schemaName] = { kind: 'schema', name: schemaName };
@@ -1252,26 +1319,29 @@ function initializeModel(csn, _options, messageFunctions)
1252
1319
  }
1253
1320
  });
1254
1321
  /** @type {object} */
1255
- const alreadyRegistered = csn.definitions[fqProxyName]
1322
+ const alreadyRegistered = csn.definitions[fqProxyName];
1256
1323
  if(!alreadyRegistered) {
1257
1324
  csn.definitions[fqProxyName] = proxy;
1258
1325
  setProp(proxy, '$path', ['definitions', fqProxyName]);
1259
1326
  Object.entries(proxy.$exposedTypes).forEach(([tn, v]) => {
1260
- const fqtn = globalSchemaPrefix + '.' + tn;
1327
+ const fqtn = proxy.$globalSchemaPrefix + '.' + tn;
1261
1328
  if(csn.definitions[fqtn] === undefined) {
1262
1329
  csn.definitions[fqtn] = v;
1263
1330
  setProp(v, '$path', ['definitions', fqtn]);
1264
1331
  }
1265
1332
  });
1266
- info(null, ['definitions', element._parent.name, 'elements', element.name],
1333
+ // default location is not always correct in case proxy has been created by a nested assoc
1334
+ // as foreign key targeting another proxy association
1335
+ let loc = ['definitions', proxy.$origin._parent.name, 'elements', proxy.$origin.name];
1336
+ if(proxy.$origin._parent.$path)
1337
+ loc = [...proxy.$origin._parent.$path, 'elements', proxy.$origin.name]
1338
+ info(null, loc,
1267
1339
  { name: proxy.name }, 'Created proxy EDM entity type $(NAME)');
1268
1340
  }
1269
1341
  else if(alreadyRegistered && !alreadyRegistered.$proxy &&
1270
- !['entity', 'view'].includes(alreadyRegistered.kind)) {
1271
- warning(null, ['definitions', element._parent.name, 'elements', element.name],
1272
- { name: fqProxyName, kind: alreadyRegistered.kind },
1273
- 'No proxy EDM entity type created due to name collision with $(NAME) of kind $(KIND)');
1274
- return undefined;
1342
+ alreadyRegistered.kind !== 'entity') {
1343
+ warning('odata-definition-exists', ['definitions', proxy.$origin._parent.name, 'elements', proxy.$origin.name],
1344
+ { '#': 'proxy', name: fqProxyName, kind: alreadyRegistered.kind });
1275
1345
  }
1276
1346
  }
1277
1347
  else {
@@ -1279,15 +1349,14 @@ function initializeModel(csn, _options, messageFunctions)
1279
1349
  if(!schemas[fqSchemaName]) {
1280
1350
  schemas[fqSchemaName] = proxy;
1281
1351
  schemaNames.push(fqSchemaName);
1282
- info(null, ['definitions', struct.name, 'elements', element.name],
1352
+ info(null, ['definitions', proxy.$origin._parent.name, 'elements', proxy.$origin.name],
1283
1353
  { name: proxy.name }, 'Created EDM namespace reference $(NAME)');
1284
1354
  }
1285
1355
  // don't error on duplicate schemas, if it's already present then all is good....
1286
1356
  }
1357
+ });
1287
1358
  // sort the global schemaNames array
1288
- schemaNames.sort((a,b) => b.length-a.length);
1289
- return proxy;
1290
- }
1359
+ schemaNames.sort((a,b) => b.length-a.length);
1291
1360
  }
1292
1361
 
1293
1362
  /*
@@ -1350,7 +1419,7 @@ function initializeModel(csn, _options, messageFunctions)
1350
1419
  // OData requires all elements along the path to be nullable: false (that is either key or notNull)
1351
1420
 
1352
1421
  const finalType = getFinalTypeDef(eltCsn.items && eltCsn.items.type || eltCsn.type);
1353
- const elements = eltCsn.elements || eltCsn.items && eltCsn.items.elements ||
1422
+ const elements = eltCsn.elements || eltCsn.items && eltCsn.items.elements ||
1354
1423
  (finalType && (finalType.elements || finalType.items && finalType.items.elements));
1355
1424
  if(elements) {
1356
1425
  Object.entries(elements).forEach(([eltName, elt]) => {
@@ -1415,7 +1484,7 @@ function initializeModel(csn, _options, messageFunctions)
1415
1484
  if(options.isV4() && type && !isAssociationOrComposition(type) && isBuiltinType(type.type)) {
1416
1485
  const edmType = edmUtils.mapCdsToEdmType(type);
1417
1486
  const legalEdmTypes = [
1418
- 'Edm.Boolean', 'Edm.Byte', 'Edm.Date', 'Edm.DateTimeOffset', 'Edm.Decimal', 'Edm.Duration',
1487
+ 'Edm.Boolean', 'Edm.Byte', 'Edm.Date', 'Edm.DateTimeOffset', 'Edm.Decimal', 'Edm.Duration',
1419
1488
  'Edm.Guid', 'Edm.Int16', 'Edm.Int32', 'Edm.Int64', 'Edm.SByte', 'Edm.String', 'Edm.TimeOfDay' ];
1420
1489
  if(!legalEdmTypes.includes(edmType)) {
1421
1490
  warning('odata-spec-violation-key-type', location,
@@ -1517,8 +1586,8 @@ function initializeModel(csn, _options, messageFunctions)
1517
1586
  // end point reached but must not be an external reference nor a proxy nor a composition itself
1518
1587
  // last assoc step must not be to-n and target a singleton
1519
1588
  let p = undefined;
1520
- if (!elt._target.$externalRef &&
1521
- !(edmUtils.isToMany(elt) && edmUtils.isSingleton(elt._target, options.isV4()))) {
1589
+ if (!elt._target.$externalRef &&
1590
+ !(edmUtils.isToMany(elt) && edmUtils.isSingleton(elt._target) && options.isV4())) {
1522
1591
  if(elt._target.$edmTgtPaths && elt._target.$edmTgtPaths.length) {
1523
1592
  p = elt._target.$edmTgtPaths.find(p => p[0] === edmUtils.getBaseName(struct.name)) || elt._target.$edmTgtPaths[0];
1524
1593
  }
@@ -1526,7 +1595,7 @@ function initializeModel(csn, _options, messageFunctions)
1526
1595
  const baseName = edmUtils.getBaseName(elt._target.$entitySetName || elt._target.name);
1527
1596
  // if own struct and target have a set they either are in the same $mySchemaName or not
1528
1597
  // if target is in another schema, target the full qualified entity set
1529
- p = (elt._target.$mySchemaName === struct.$mySchemaName) ?
1598
+ p = (elt._target.$mySchemaName === struct.$mySchemaName) ?
1530
1599
  [ baseName ] : [elt._target.$mySchemaName + '.EntityContainer', baseName];
1531
1600
  }
1532
1601
  if(p) {
@@ -1641,7 +1710,7 @@ function initializeModel(csn, _options, messageFunctions)
1641
1710
  !isBetaEnabled(options, 'nestedServices') && serviceRootNames.forEach(sn => {
1642
1711
  const parent = whatsMyServiceRootName(sn, false);
1643
1712
  if(parent && parent !== sn) {
1644
- error( 'service-nested-service', [ 'definitions', sn ], { art: parent },
1713
+ message( 'service-nested-service', [ 'definitions', sn ], { art: parent },
1645
1714
  'A service can\'t be nested within a service $(ART)' );
1646
1715
  }
1647
1716
  });
@@ -1650,7 +1719,7 @@ function initializeModel(csn, _options, messageFunctions)
1650
1719
  if(art.kind === 'context') {
1651
1720
  const parent = whatsMyServiceRootName(fqName);
1652
1721
  if(parent) {
1653
- error( 'service-nested-context', [ 'definitions', fqName ], { art: parent },
1722
+ message( 'service-nested-context', [ 'definitions', fqName ], { art: parent },
1654
1723
  'A context can\'t be nested within a service $(ART)' );
1655
1724
  }
1656
1725
  }
@@ -1731,12 +1800,13 @@ function initializeModel(csn, _options, messageFunctions)
1731
1800
  // serviceRef.push('');
1732
1801
  if(serviceRef[serviceRef.length-1] !== '$metadata')
1733
1802
  serviceRef.push('$metadata');
1734
- return { kind: 'reference',
1803
+ let sc = { kind: 'reference',
1735
1804
  name: targetSchemaName,
1736
1805
  ref: { Uri: serviceRef.join('/') },
1737
- inc: { Namespace: targetSchemaName },
1738
- $mySchemaName: targetSchemaName,
1806
+ inc: { Namespace: targetSchemaName }
1739
1807
  };
1808
+ setProp(sc, '$mySchemaName', targetSchemaName);
1809
+ return sc;
1740
1810
 
1741
1811
  /**
1742
1812
  * Resolve a service endpoint path to mount it to as follows...
@@ -1788,8 +1858,8 @@ function initializeModel(csn, _options, messageFunctions)
1788
1858
  if(!navPropEntry[prop]) {
1789
1859
  // Filter properties with prefix and reduce them into a new dictionary
1790
1860
  const o = props.filter(p => p[0].startsWith(prefix+'.')).reduce((a,c) => {
1791
- a[c[0].replace(prefix+'.', '')] = c[1];
1792
- return a;
1861
+ a[c[0].replace(prefix+'.', '')] = c[1];
1862
+ return a;
1793
1863
  }, { });
1794
1864
  // if dictionary has entries, add them to navPropEnty
1795
1865
  if(Object.keys(o).length) {
@@ -1925,8 +1995,9 @@ function applyAppSpecificLateCsnTransformationOnElement(options, element, struct
1925
1995
  // nested functions begin
1926
1996
  function PDMSemantics()
1927
1997
  {
1928
- let dict = Object.create(null);
1929
1998
  /*
1999
+ let dict = Object.create(null);
2000
+
1930
2001
  dict['@PDM.xxx1'] = [ '@sap.pdm-semantics' ];
1931
2002
  dict['@PDM.xxx2'] = [ '@sap.pdm-propery' ];
1932
2003
  dict['@PDM.xxx3'] = [ '@sap.pdm-display-sq-no' ];
@@ -1940,7 +2011,7 @@ function applyAppSpecificLateCsnTransformationOnElement(options, element, struct
1940
2011
  // respect flattened annotation $value
1941
2012
  Object.keys(dict).forEach(k => dict[k+'.$value'] = dict[k]);
1942
2013
  */
1943
- return dict;
2014
+ return Object.create(null);
1944
2015
  }
1945
2016
 
1946
2017
  function AnalyticalAnnotations()
@@ -2151,7 +2222,7 @@ function setSAPSpecificV2AnnotationsToEntitySet(options, carrier) {
2151
2222
  }
2152
2223
 
2153
2224
  function checkSemantics(struct, propName, propValue) {
2154
- if(['timeseries', 'aggregate'].includes(propValue)) {
2225
+ if(propValue === 'timeseries' || propValue === 'aggregate') {
2155
2226
  // aggregate is forwarded to Set and must remain on Type
2156
2227
  addToSetAttr(struct, propName, propValue, propValue !== 'aggregate');
2157
2228
  }