@sap/cds-compiler 2.12.0 → 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 (118) hide show
  1. package/CHANGELOG.md +110 -15
  2. package/bin/cdsc.js +13 -13
  3. package/bin/cdsse.js +2 -2
  4. package/doc/CHANGELOG_BETA.md +13 -6
  5. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  6. package/doc/NameResolution.md +21 -16
  7. package/lib/api/main.js +28 -63
  8. package/lib/api/options.js +3 -3
  9. package/lib/api/validate.js +0 -5
  10. package/lib/backends.js +15 -23
  11. package/lib/base/dictionaries.js +0 -8
  12. package/lib/base/error.js +26 -0
  13. package/lib/base/keywords.js +7 -17
  14. package/lib/base/location.js +9 -4
  15. package/lib/base/message-registry.js +25 -4
  16. package/lib/base/messages.js +16 -26
  17. package/lib/base/model.js +2 -63
  18. package/lib/base/optionProcessorHelper.js +158 -123
  19. package/lib/checks/annotationsOData.js +1 -1
  20. package/lib/checks/cdsPersistence.js +2 -1
  21. package/lib/checks/enricher.js +17 -1
  22. package/lib/checks/invalidTarget.js +3 -1
  23. package/lib/checks/managedWithoutKeys.js +3 -1
  24. package/lib/checks/selectItems.js +4 -4
  25. package/lib/checks/sql-snippets.js +27 -26
  26. package/lib/checks/types.js +1 -1
  27. package/lib/checks/validator.js +4 -7
  28. package/lib/compiler/assert-consistency.js +5 -3
  29. package/lib/compiler/builtins.js +8 -6
  30. package/lib/compiler/checks.js +14 -3
  31. package/lib/compiler/cycle-detector.js +1 -1
  32. package/lib/compiler/define.js +1103 -0
  33. package/lib/compiler/extend.js +983 -0
  34. package/lib/compiler/finalize-parse-cdl.js +231 -0
  35. package/lib/compiler/index.js +32 -13
  36. package/lib/compiler/kick-start.js +190 -0
  37. package/lib/compiler/moduleLayers.js +4 -4
  38. package/lib/compiler/populate.js +1226 -0
  39. package/lib/compiler/propagator.js +111 -46
  40. package/lib/compiler/resolve.js +1433 -0
  41. package/lib/compiler/shared.js +64 -37
  42. package/lib/compiler/tweak-assocs.js +529 -0
  43. package/lib/compiler/utils.js +197 -33
  44. package/lib/edm/.eslintrc.json +5 -0
  45. package/lib/edm/annotations/genericTranslation.js +5 -9
  46. package/lib/edm/annotations/preprocessAnnotations.js +2 -2
  47. package/lib/edm/csn2edm.js +9 -8
  48. package/lib/edm/edm.js +11 -12
  49. package/lib/edm/edmPreprocessor.js +137 -73
  50. package/lib/edm/edmUtils.js +116 -22
  51. package/lib/gen/Dictionary.json +10 -3
  52. package/lib/gen/language.checksum +1 -1
  53. package/lib/gen/language.interp +9 -1
  54. package/lib/gen/language.tokens +86 -83
  55. package/lib/gen/languageLexer.interp +10 -1
  56. package/lib/gen/languageLexer.js +860 -833
  57. package/lib/gen/languageLexer.tokens +78 -75
  58. package/lib/gen/languageParser.js +5282 -4265
  59. package/lib/json/from-csn.js +12 -1
  60. package/lib/json/to-csn.js +126 -66
  61. package/lib/language/docCommentParser.js +2 -2
  62. package/lib/language/genericAntlrParser.js +76 -3
  63. package/lib/language/language.g4 +297 -130
  64. package/lib/language/multiLineStringParser.js +5 -5
  65. package/lib/main.d.ts +468 -59
  66. package/lib/main.js +35 -9
  67. package/lib/model/api.js +3 -1
  68. package/lib/model/csnRefs.js +225 -156
  69. package/lib/model/csnUtils.js +192 -223
  70. package/lib/model/enrichCsn.js +70 -29
  71. package/lib/model/revealInternalProperties.js +27 -6
  72. package/lib/model/sortViews.js +2 -1
  73. package/lib/modelCompare/compare.js +17 -12
  74. package/lib/optionProcessor.js +5 -4
  75. package/lib/render/manageConstraints.js +35 -32
  76. package/lib/render/toCdl.js +73 -288
  77. package/lib/render/toHdbcds.js +25 -23
  78. package/lib/render/toSql.js +98 -41
  79. package/lib/render/utils/common.js +5 -10
  80. package/lib/render/utils/sql.js +4 -3
  81. package/lib/render/utils/stringEscapes.js +111 -0
  82. package/lib/sql-identifier.js +1 -1
  83. package/lib/transform/.eslintrc.json +5 -0
  84. package/lib/transform/db/.eslintrc.json +2 -0
  85. package/lib/transform/db/applyTransformations.js +35 -12
  86. package/lib/transform/db/assertUnique.js +1 -1
  87. package/lib/transform/db/associations.js +103 -305
  88. package/lib/transform/db/cdsPersistence.js +2 -2
  89. package/lib/transform/db/constraints.js +55 -52
  90. package/lib/transform/db/expansion.js +46 -24
  91. package/lib/transform/db/flattening.js +553 -102
  92. package/lib/transform/db/groupByOrderBy.js +3 -1
  93. package/lib/transform/db/transformExists.js +59 -6
  94. package/lib/transform/db/views.js +5 -4
  95. package/lib/transform/draft/.eslintrc.json +38 -0
  96. package/lib/transform/{db/draft.js → draft/db.js} +6 -5
  97. package/lib/transform/draft/odata.js +227 -0
  98. package/lib/transform/forHanaNew.js +67 -183
  99. package/lib/transform/forOdataNew.js +17 -171
  100. package/lib/transform/localized.js +34 -19
  101. package/lib/transform/odata/generateForeignKeyElements.js +1 -1
  102. package/lib/transform/odata/referenceFlattener.js +95 -89
  103. package/lib/transform/odata/structureFlattener.js +1 -1
  104. package/lib/transform/odata/toFinalBaseType.js +86 -12
  105. package/lib/transform/odata/typesExposure.js +5 -5
  106. package/lib/transform/odata/utils.js +2 -2
  107. package/lib/transform/transformUtilsNew.js +36 -22
  108. package/lib/transform/translateAssocsToJoins.js +2 -19
  109. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  110. package/lib/transform/universalCsn/coreComputed.js +170 -0
  111. package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
  112. package/lib/transform/universalCsn/utils.js +63 -0
  113. package/lib/utils/objectUtils.js +30 -0
  114. package/package.json +1 -1
  115. package/share/messages/README.md +26 -0
  116. package/lib/compiler/definer.js +0 -2361
  117. package/lib/compiler/resolver.js +0 -3079
  118. package/lib/transform/universalCsnEnricher.js +0 -237
@@ -43,19 +43,23 @@ function initializeModel(csn, _options, 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
  }
@@ -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);
@@ -581,16 +612,16 @@ function initializeModel(csn, _options, messageFunctions)
581
612
 
582
613
  // Iterate all struct elements
583
614
  forEachMemberRecursively(def.items || def, (element, elementName, prop, path = [], construct) => {
584
- if(!['elements'].includes(prop))
615
+ if(prop !== 'elements')
585
616
  return;
586
617
 
587
618
  initElement(element, elementName, construct);
588
619
 
589
- if(!['event', 'aspect'].includes(def.kind)) {
620
+ if(def.kind !== 'event' && def.kind !== 'aspect') {
590
621
  if(element._parent && element._parent.$mySchemaName) {
591
622
  if(!isODataSimpleIdentifier(elementName)) {
592
623
  signalIllegalIdentifier(elementName, ['definitions', def.name].concat(path));
593
- } 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') {
594
625
  // FIXME: Rewrite signalIllegalIdentifier function to be more flexible
595
626
  error(null, ['definitions', def.name, 'elements', elementName], { prop: elementName[0] },
596
627
  'Element names must not start with $(PROP) for OData V2');
@@ -845,7 +876,7 @@ function initializeModel(csn, _options, messageFunctions)
845
876
  proxies.
846
877
  */
847
878
  function exposeTargetsAsProxiesOrSchemaRefs(struct) {
848
- if([ 'context', 'service' ].includes(struct.kind) || struct.$proxy)
879
+ if(struct.kind === 'context' || struct.kind === 'service' || struct.$proxy)
849
880
  return;
850
881
 
851
882
  // globalSchemaPrefix is the prefix for all proxy registrations and must not change
@@ -880,6 +911,7 @@ function initializeModel(csn, _options, messageFunctions)
880
911
  // If the target is in another schema, check if both the source and the target share the same service name.
881
912
  // If they share the same service name, then it is just a cross schema navigation within the same EDM, no
882
913
  // proxy required.
914
+ // association must be managed and not unmanaged
883
915
 
884
916
  // odataProxies (P) and odataXServiceRefs (X) are evalutated as follows:
885
917
  // P | X | Action
@@ -890,7 +922,10 @@ function initializeModel(csn, _options, messageFunctions)
890
922
 
891
923
  const targetSchemaName = element._target.$mySchemaName;
892
924
  if(isProxyRequired(element)) {
893
- 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 */) {
894
929
  // reuse proxy if available
895
930
  let proxy = getProxyForTargetOf(element);
896
931
  if(!proxy) {
@@ -909,6 +944,7 @@ function initializeModel(csn, _options, messageFunctions)
909
944
  element._constraints.constraints = Object.create(null);
910
945
  if(proxy.kind === 'entity') {
911
946
  element._target = proxy;
947
+ populateProxyElements(element, proxy, getForeignKeyDefinitions(element, ['definitions', struct.name, 'elements', element.name]));
912
948
  }
913
949
  else {
914
950
  // fake the target to be proxy
@@ -933,7 +969,7 @@ function initializeModel(csn, _options, messageFunctions)
933
969
 
934
970
  function noNavPropMsg(elt) {
935
971
  warning(null, ['definitions', struct.name, 'elements', elt.name],
936
- { 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)');
937
973
  }
938
974
 
939
975
  function createSchemaRefFor(assoc, targetSchemaName) {
@@ -954,10 +990,10 @@ function initializeModel(csn, _options, messageFunctions)
954
990
  // 1) construct the proxy definition
955
991
  // proxyShortName: strip the serviceName and replace '.' with '_'
956
992
  const proxyShortName = assoc._target.name.replace(proxySchemaName + '.', '').replace(/\./g, '_');
957
- // fullName: Prepend serviceName and if in same service add '_proxy'
958
- const fullName = proxySchemaName + '.' + proxyShortName;
959
- 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) };
960
995
  setProp(proxy, '$mySchemaName', proxySchemaName);
996
+ setProp(proxy, '$proxyShortName', proxyShortName);
961
997
  setProp(proxy, '$keys', Object.create(null));
962
998
  setProp(proxy, '$hasEntitySet', false);
963
999
  setProp(proxy, '$exposedTypes', Object.create(null));
@@ -968,59 +1004,67 @@ function initializeModel(csn, _options, messageFunctions)
968
1004
  });
969
1005
 
970
1006
  // 2) create the elements and $keys
971
- populateProxyElements(proxy, assoc._target.$keys);
972
- // 3) sort the exposed types so that they appear lexicographically ordered in the EDM
973
- proxy.$exposedTypes = Object.keys(proxy.$exposedTypes).sort().reduce((dict, tn) => {
974
- dict[tn] = proxy.$exposedTypes[tn];
975
- return dict
976
- }, Object.create(null));
977
-
1007
+ populateProxyElements(assoc, proxy, assoc._target.$keys);
978
1008
  return proxy;
979
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
+ }
980
1019
  // copy over the primary keys of the target and trigger the type exposure
981
- function populateProxyElements(proxy, keys) {
982
- forAll(keys, e => {
983
- if (isEdmPropertyRendered(e, options)) {
984
- let newElt = undefined;
985
- if(isAssocOrComposition(e.type)) {
986
- if(!e.on && e.keys) {
987
- if(options.toOdata.odataNoTransitiveProxies)
988
- newElt = convertManagedAssocIntoStruct(e);
989
- 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
990
1029
  newElt = createProxyOrSchemaRefForManagedAssoc(e);
991
- }
992
- else {
993
- info(null, ['definitions', struct.name, 'elements', assoc.name],
994
- { name: fullName, target: assoc._target.name },
995
- 'Unmanaged associations are not supported as primary keys for proxy entity type $(NAME) of unexposed association target $(TARGET)');
996
- }
997
1030
  }
998
1031
  else {
999
- 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)');
1000
1035
  }
1001
- if(newElt) {
1002
- initElement(newElt, e.name, proxy);
1003
- 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)) {
1004
1043
  // argument proxySchemaName forces an anonymous type definition for newElt into the
1005
1044
  // proxy schema. If omitted, this exposure defaults to 'root', in case API flavor of the day
1006
1045
  // changes...
1007
- exposeStructTypeForProxyOf(proxy, newElt, proxyShortName + '_' + newElt.name, proxySchemaName);
1046
+ exposeStructTypeForProxyOf(proxy, newElt, proxy.$proxyShortName + '_' + newElt.name, proxy.$mySchemaName);
1008
1047
  // elements of newElt are required for key ref paths
1009
- }
1010
- // all elements must become primary key
1011
- proxy.$keys[e.name] = proxy.elements[newElt.name] = newElt;
1012
1048
  }
1049
+ proxy.elements[newElt.name] = newElt;
1050
+ if(newElt.key)
1051
+ proxy.$keys[newElt.name] = newElt;
1013
1052
  }
1014
- });
1015
- }
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));
1016
1060
 
1017
1061
  // If 'node' exists and has a structured type that is not exposed in 'service', (because the type is
1018
1062
  // anonymous or has a definition outside of 'service'), create an equivalent type in 'service', either
1019
1063
  // using the type's name or (if anonymous) 'artificialName', and make 'node' use that type instead.
1020
1064
  // Complain if there is an error.
1021
- function exposeStructTypeForProxyOf(proxy, node, artificialName, typeSchemaName='root') {
1065
+ function exposeStructTypeForProxyOf(proxy, node, artificialName, typeSchemaName=autoexposeSchemaName) {
1022
1066
  const isNotInProtNS = node.type ? !isBuiltinType(node.type) : true;
1023
- // 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
1024
1068
  // as the nested elements must all be not nullable
1025
1069
  if (isNotInProtNS) {
1026
1070
  let typeDef = node.type ? csn.definitions[node.type] : /* anonymous type */ node;
@@ -1052,7 +1096,7 @@ function initializeModel(csn, _options, messageFunctions)
1052
1096
  if(typeClone) {
1053
1097
  // Recurse into elements of 'type' (if any)
1054
1098
  typeClone.elements && Object.entries(typeClone.elements).forEach( ([elemName, elem]) => {
1055
- // 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
1056
1100
  // has been exposed as proxy. If it has not been exposed, no further structured
1057
1101
  // types must be exposed as 'Proxy_' types.
1058
1102
 
@@ -1109,7 +1153,8 @@ function initializeModel(csn, _options, messageFunctions)
1109
1153
  Object.keys(elem).forEach(prop => type.elements[elemName][prop] = elem[prop])
1110
1154
  type.elements[elemName].notNull = true;
1111
1155
  }
1112
- else {
1156
+ else if(elem.keys && !elem.on) {
1157
+ // a primary key can never be an unmanaged association
1113
1158
  type.elements[elemName] = createProxyOrSchemaRefForManagedAssoc(elem);
1114
1159
  }
1115
1160
  setProp(type.elements[elemName], 'name', elem.name);
@@ -1166,6 +1211,7 @@ function initializeModel(csn, _options, messageFunctions)
1166
1211
  }
1167
1212
  else if(options.toOdata.odataProxies) {
1168
1213
  proxy = createProxyFor(e, e._target.$mySchemaName);
1214
+ populateProxyElements(e, proxy, getForeignKeyDefinitions(e));
1169
1215
  }
1170
1216
  proxy = registerProxy(proxy, e);
1171
1217
  }
@@ -1229,8 +1275,11 @@ function initializeModel(csn, _options, messageFunctions)
1229
1275
  function registerProxy(proxy, element) {
1230
1276
  if(proxy === undefined)
1231
1277
  return undefined;
1232
- const fqProxyName = globalSchemaPrefix + '.' + proxy.name;
1233
- const fqSchemaName = globalSchemaPrefix + '.' + proxy.$mySchemaName;
1278
+
1279
+ setProp(proxy, '$globalSchemaPrefix', globalSchemaPrefix);
1280
+ setProp(proxy, '$origin', element);
1281
+
1282
+ const fqProxyName = proxy.$globalSchemaPrefix + '.' + proxy.name;
1234
1283
 
1235
1284
  if(!element._target.$cachedProxy)
1236
1285
  assignProp(element._target, '$cachedProxy', Object.create(null));
@@ -1238,8 +1287,19 @@ function initializeModel(csn, _options, messageFunctions)
1238
1287
  info(null, ['definitions', struct.name, 'elements', element.name],
1239
1288
  { name: fqProxyName }, 'Proxy EDM entity type $(NAME) has already been registered');
1240
1289
  }
1241
- else
1290
+ else {
1291
+ determineEntitySet(proxy);
1292
+ proxyCache.push(proxy);
1242
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;
1243
1303
 
1244
1304
  if(proxy.kind === 'entity') {
1245
1305
  // collect all schemas even for newly exposed types
@@ -1251,7 +1311,7 @@ function initializeModel(csn, _options, messageFunctions)
1251
1311
  // don't forget to prepend the global namespace prefix
1252
1312
  // schemas are ordered in csn2edm.js for each service
1253
1313
  Object.keys(proxy.$exposedTypes).forEach(t =>
1254
- schemaSet.add(globalSchemaPrefix + '.' + getSchemaPrefix(t)));
1314
+ schemaSet.add(proxy.$globalSchemaPrefix + '.' + getSchemaPrefix(t)));
1255
1315
  schemaSet.forEach(schemaName => {
1256
1316
  if(!schemas[schemaName]) {
1257
1317
  schemas[schemaName] = { kind: 'schema', name: schemaName };
@@ -1259,25 +1319,29 @@ function initializeModel(csn, _options, messageFunctions)
1259
1319
  }
1260
1320
  });
1261
1321
  /** @type {object} */
1262
- const alreadyRegistered = csn.definitions[fqProxyName]
1322
+ const alreadyRegistered = csn.definitions[fqProxyName];
1263
1323
  if(!alreadyRegistered) {
1264
1324
  csn.definitions[fqProxyName] = proxy;
1265
1325
  setProp(proxy, '$path', ['definitions', fqProxyName]);
1266
1326
  Object.entries(proxy.$exposedTypes).forEach(([tn, v]) => {
1267
- const fqtn = globalSchemaPrefix + '.' + tn;
1327
+ const fqtn = proxy.$globalSchemaPrefix + '.' + tn;
1268
1328
  if(csn.definitions[fqtn] === undefined) {
1269
1329
  csn.definitions[fqtn] = v;
1270
1330
  setProp(v, '$path', ['definitions', fqtn]);
1271
1331
  }
1272
1332
  });
1273
- 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,
1274
1339
  { name: proxy.name }, 'Created proxy EDM entity type $(NAME)');
1275
1340
  }
1276
1341
  else if(alreadyRegistered && !alreadyRegistered.$proxy &&
1277
- !['entity', 'view'].includes(alreadyRegistered.kind)) {
1278
- warning('odata-definition-exists', ['definitions', element._parent.name, 'elements', element.name],
1342
+ alreadyRegistered.kind !== 'entity') {
1343
+ warning('odata-definition-exists', ['definitions', proxy.$origin._parent.name, 'elements', proxy.$origin.name],
1279
1344
  { '#': 'proxy', name: fqProxyName, kind: alreadyRegistered.kind });
1280
- return undefined;
1281
1345
  }
1282
1346
  }
1283
1347
  else {
@@ -1285,15 +1349,14 @@ function initializeModel(csn, _options, messageFunctions)
1285
1349
  if(!schemas[fqSchemaName]) {
1286
1350
  schemas[fqSchemaName] = proxy;
1287
1351
  schemaNames.push(fqSchemaName);
1288
- info(null, ['definitions', struct.name, 'elements', element.name],
1352
+ info(null, ['definitions', proxy.$origin._parent.name, 'elements', proxy.$origin.name],
1289
1353
  { name: proxy.name }, 'Created EDM namespace reference $(NAME)');
1290
1354
  }
1291
1355
  // don't error on duplicate schemas, if it's already present then all is good....
1292
1356
  }
1357
+ });
1293
1358
  // sort the global schemaNames array
1294
- schemaNames.sort((a,b) => b.length-a.length);
1295
- return proxy;
1296
- }
1359
+ schemaNames.sort((a,b) => b.length-a.length);
1297
1360
  }
1298
1361
 
1299
1362
  /*
@@ -1737,12 +1800,13 @@ function initializeModel(csn, _options, messageFunctions)
1737
1800
  // serviceRef.push('');
1738
1801
  if(serviceRef[serviceRef.length-1] !== '$metadata')
1739
1802
  serviceRef.push('$metadata');
1740
- return { kind: 'reference',
1803
+ let sc = { kind: 'reference',
1741
1804
  name: targetSchemaName,
1742
1805
  ref: { Uri: serviceRef.join('/') },
1743
- inc: { Namespace: targetSchemaName },
1744
- $mySchemaName: targetSchemaName,
1806
+ inc: { Namespace: targetSchemaName }
1745
1807
  };
1808
+ setProp(sc, '$mySchemaName', targetSchemaName);
1809
+ return sc;
1746
1810
 
1747
1811
  /**
1748
1812
  * Resolve a service endpoint path to mount it to as follows...
@@ -2158,7 +2222,7 @@ function setSAPSpecificV2AnnotationsToEntitySet(options, carrier) {
2158
2222
  }
2159
2223
 
2160
2224
  function checkSemantics(struct, propName, propValue) {
2161
- if(['timeseries', 'aggregate'].includes(propValue)) {
2225
+ if(propValue === 'timeseries' || propValue === 'aggregate') {
2162
2226
  // aggregate is forwarded to Set and must remain on Type
2163
2227
  addToSetAttr(struct, propName, propValue, propValue !== 'aggregate');
2164
2228
  }