@sap/cds-compiler 3.7.2 → 3.8.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 (70) hide show
  1. package/CHANGELOG.md +71 -4
  2. package/bin/cdsc.js +3 -0
  3. package/doc/CHANGELOG_ARCHIVE.md +6 -6
  4. package/doc/CHANGELOG_BETA.md +15 -0
  5. package/doc/DeprecatedOptions_v2.md +1 -1
  6. package/doc/NameResolution.md +1 -1
  7. package/lib/api/main.js +61 -22
  8. package/lib/api/options.js +1 -0
  9. package/lib/api/validate.js +5 -0
  10. package/lib/base/dictionaries.js +5 -3
  11. package/lib/base/keywords.js +2 -0
  12. package/lib/base/message-registry.js +64 -22
  13. package/lib/base/messages.js +12 -7
  14. package/lib/base/model.js +3 -2
  15. package/lib/checks/arrayOfs.js +1 -1
  16. package/lib/checks/defaultValues.js +1 -1
  17. package/lib/checks/hasPersistedElements.js +1 -1
  18. package/lib/checks/invalidTarget.js +1 -1
  19. package/lib/checks/onConditions.js +9 -6
  20. package/lib/checks/sql-snippets.js +2 -2
  21. package/lib/checks/types.js +1 -2
  22. package/lib/compiler/assert-consistency.js +25 -6
  23. package/lib/compiler/base.js +51 -2
  24. package/lib/compiler/builtins.js +15 -6
  25. package/lib/compiler/checks.js +4 -4
  26. package/lib/compiler/define.js +59 -80
  27. package/lib/compiler/extend.js +717 -498
  28. package/lib/compiler/finalize-parse-cdl.js +4 -3
  29. package/lib/compiler/index.js +1 -1
  30. package/lib/compiler/kick-start.js +2 -2
  31. package/lib/compiler/populate.js +17 -9
  32. package/lib/compiler/propagator.js +12 -5
  33. package/lib/compiler/resolve.js +26 -173
  34. package/lib/compiler/shared.js +20 -58
  35. package/lib/compiler/tweak-assocs.js +1 -1
  36. package/lib/compiler/utils.js +2 -2
  37. package/lib/edm/annotations/genericTranslation.js +124 -46
  38. package/lib/edm/csn2edm.js +22 -1
  39. package/lib/edm/edmPreprocessor.js +41 -21
  40. package/lib/gen/Dictionary.json +4 -0
  41. package/lib/gen/language.checksum +1 -1
  42. package/lib/gen/language.interp +3 -1
  43. package/lib/gen/languageLexer.js +1 -1
  44. package/lib/gen/languageParser.js +4844 -4508
  45. package/lib/inspect/inspectPropagation.js +20 -36
  46. package/lib/json/from-csn.js +56 -7
  47. package/lib/json/to-csn.js +71 -110
  48. package/lib/language/errorStrategy.js +1 -0
  49. package/lib/language/genericAntlrParser.js +49 -9
  50. package/lib/language/language.g4 +106 -83
  51. package/lib/language/textUtils.js +13 -0
  52. package/lib/main.d.ts +43 -3
  53. package/lib/main.js +4 -2
  54. package/lib/model/csnRefs.js +19 -4
  55. package/lib/model/csnUtils.js +11 -74
  56. package/lib/model/revealInternalProperties.js +3 -0
  57. package/lib/optionProcessor.js +3 -0
  58. package/lib/render/toCdl.js +203 -104
  59. package/lib/render/toHdbcds.js +0 -1
  60. package/lib/render/toRename.js +14 -51
  61. package/lib/transform/braceExpression.js +6 -0
  62. package/lib/transform/db/rewriteCalculatedElements.js +55 -14
  63. package/lib/transform/forOdataNew.js +20 -15
  64. package/lib/transform/forRelationalDB.js +21 -14
  65. package/lib/transform/parseExpr.js +2 -0
  66. package/lib/transform/transformUtilsNew.js +36 -9
  67. package/lib/transform/translateAssocsToJoins.js +11 -4
  68. package/lib/transform/universalCsn/coreComputed.js +15 -7
  69. package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
  70. package/package.json +2 -1
@@ -5,7 +5,7 @@ const oDataDictionary = require('../../gen/Dictionary.json');
5
5
  const preprocessAnnotations = require('./preprocessAnnotations.js');
6
6
  const { forEachDefinition } = require('../../model/csnUtils');
7
7
  const { forEach } = require('../../utils/objectUtils');
8
- const { isBetaEnabled } = require('../../base/model.js');
8
+ const { isBetaEnabled, setProp } = require('../../base/model.js');
9
9
 
10
10
  /*
11
11
  OASIS: https://github.com/oasis-tcs/odata-vocabularies/tree/master/vocabularies
@@ -147,10 +147,23 @@ const vocabularyDefinitions = {
147
147
  'inc': { Alias: 'Validation', Namespace: 'Org.OData.Validation.V1' },
148
148
  'int': { filename: 'Validation.xml' }
149
149
  },
150
+ /* unvalidated vocabularies below here:
151
+ A vocabulary is unvalidated if it doesn't have an int.filename property as this indicates that
152
+ the vocabulary is added to the validation dictionary
153
+ Example:
154
+ 'Org.Snafu.V1': {
155
+ 'ref': { Uri: 'https://snafu.org/snafu.xml' },
156
+ 'inc': { Alias: 'Snafu', Namespace: 'Org.Snafu.V1' },
157
+ },
158
+ */
150
159
  };
151
160
 
152
- const knownVocabularies = Object.keys(vocabularyDefinitions);
153
-
161
+ /* create inverted voc definitions list to allow addressing full qualified vocabularies
162
+ Object.entries(vocabularyDefinitions).forEach(([n, v]) => {
163
+ if(!vocabularyDefinitions[v.inc.Namespace])
164
+ vocabularyDefinitions[v.inc.Namespace] = vocabularyDefinitions[n];
165
+ });
166
+ */
154
167
  /**************************************************************************************************
155
168
  * csn2annotationEdm
156
169
  *
@@ -158,24 +171,16 @@ const knownVocabularies = Object.keys(vocabularyDefinitions);
158
171
  * v - array with two boolean entries, first is for v2, second is for v4
159
172
  * dictReplacement: for test purposes, replaces the standard oDataDictionary
160
173
  */
161
- function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefined, options=undefined, messageFunctions=undefined) {
174
+ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
175
+ Edm = undefined, options=undefined, messageFunctions=undefined, mergedVocDefs=vocabularyDefinitions) {
162
176
  // global variable where we store all the generated annotations
163
177
  const g_annosArray = [];
164
178
 
165
179
  const { message } = messageFunctions;
166
180
 
167
- const [ adhocDictionary, allKnownVocabularies ] = createAdhocDictionary();
181
+ const [ userDefinedTermDict, allKnownVocabularies ] = createUserDefinedTermDictionary();
168
182
 
169
- /*
170
- allKnownVocabularies is the union of knownVocabularies & adhoc $mySchemaName annotation definition
171
- this allows to identify namespace prefixes of arbitrary length.
172
-
173
- If in the future, the full Oasis vocabulary names shall be made usable for assignments
174
- eg. @com.sap.vocabularies.Common.v1.Label, all that needs to be done is to x-link the
175
- vocabulary dictionary whith the full ns as key:
176
- vocabularyDefinitions['@com.sap.vocabularies.Common.v1'] = vocabularyDefinitions['Common']
177
- */
178
- allKnownVocabularies.push(...knownVocabularies);
183
+ allKnownVocabularies.push(...Object.keys(mergedVocDefs));
179
184
  allKnownVocabularies.sort((a,b) => b.length-a.length);
180
185
  const whatsMyTermNamespace = function(anno) {
181
186
  return allKnownVocabularies.reduce((rc, ns) => !rc && anno && anno.startsWith('@' + ns + '.') ? ns : rc, undefined);
@@ -189,13 +194,21 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
189
194
 
190
195
  // we take note of which vocabularies are actually used in a service in order to avoid
191
196
  // producing useless references; reset everything to "unused"
192
- knownVocabularies.forEach(n => {
193
- vocabularyDefinitions[n].used = false;
194
- });
197
+ for(const n in mergedVocDefs) {
198
+ mergedVocDefs[n].used = false;
199
+ delete mergedVocDefs[n].$ignore;
200
+ }
195
201
 
196
202
  // These vocabularies are always added for the runtimes
197
- vocabularyDefinitions['Common'].used = true;
198
- vocabularyDefinitions['Core'].used = true;
203
+ mergedVocDefs['Common'].used = true;
204
+ mergedVocDefs['Core'].used = true;
205
+
206
+ const vocDef = mergedVocDefs[serviceName];
207
+ if(vocDef && vocDef.$optVocRef) {
208
+ setProp(vocDef, '$ignore', true);
209
+ message('odata-anno-vocref', null,
210
+ { name: serviceName, '#': 'service' } );
211
+ }
199
212
 
200
213
  // provide functions for dictionary lookup
201
214
  // use closure to avoid making "dict" and "experimental" global variables
@@ -210,15 +223,16 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
210
223
  // - issue a warning if the term is flagged as "experimental"
211
224
  getDictTerm: function(termName, msg) {
212
225
  const dictTerm = (dict.terms[termName] ||
213
- adhocDictionary.terms[serviceName + '.' + termName] ||
214
- adhocDictionary.terms[termName]);
226
+ userDefinedTermDict.terms[serviceName + '.' + termName] ||
227
+ userDefinedTermDict.terms[termName]);
215
228
  // register vocabulary usage if possible
216
229
  const vocName = termName.slice(0, termName.indexOf('.'));
217
- if(vocabularyDefinitions[vocName])
218
- vocabularyDefinitions[vocName].used = true;
230
+ const vocDef = mergedVocDefs[vocName];
231
+ if(vocDef && !vocDef.$ignore)
232
+ vocDef.used = true;
219
233
  else if(dictTerm?.$myServiceRoot &&
220
- adhocDictionary.xrefs[dictTerm?.$myServiceRoot])
221
- adhocDictionary.xrefs[dictTerm.$myServiceRoot].used = true;
234
+ userDefinedTermDict.xrefs[dictTerm?.$myServiceRoot])
235
+ userDefinedTermDict.xrefs[dictTerm.$myServiceRoot].used = true;
222
236
  if (dictTerm) {
223
237
  // issue message for usage of experimental Terms, but only once per Term
224
238
  if (dictTerm['$experimental'] && !experimental[termName]) {
@@ -237,13 +251,14 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
237
251
  // in addition, note usage of the respective vocabulary
238
252
  getDictType: function (typeName) {
239
253
  let dictType = (dict.types[typeName] ||
240
- adhocDictionary.types[serviceName + '.' + typeName] ||
241
- adhocDictionary.types[typeName]);
254
+ userDefinedTermDict.types[serviceName + '.' + typeName] ||
255
+ userDefinedTermDict.types[typeName]);
242
256
  if (dictType) {
243
257
  // register usage of vocabulary
244
- const voc = vocabularyDefinitions[typeName.slice(0, typeName.indexOf('.'))];
245
- if(voc)
246
- voc.used = true;
258
+ const vocName = typeName.slice(0, typeName.indexOf('.'));
259
+ const vocDef = mergedVocDefs[vocName];
260
+ if(vocDef && !vocDef.$ignore)
261
+ vocDef.used = true;
247
262
  }
248
263
  return dictType;
249
264
  }
@@ -275,12 +290,13 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
275
290
  }
276
291
  });
277
292
 
293
+
278
294
  // filter out empty <Annotations...> elements
279
295
  // add references for the used vocabularies
280
296
  return {
281
297
  annos: g_annosArray,
282
- usedVocabularies: Object.values(vocabularyDefinitions).filter(v => v.used),
283
- xrefs: Object.values(adhocDictionary.xrefs).filter(v => v.used).map(v => v.$myServiceRoot)
298
+ usedVocabularies: Object.values(mergedVocDefs).filter(v => v.used),
299
+ xrefs: Object.values(userDefinedTermDict.xrefs).filter(v => v.used).map(v => v.$myServiceRoot)
284
300
  };
285
301
 
286
302
  //-------------------------------------------------------------------------------------------------
@@ -503,10 +519,14 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
503
519
  function filterKnownAnnotations() {
504
520
  const annoNames = Object.keys(carrier).filter( x => x[0] === '@' );
505
521
  const nullWhitelist = [ '@Core.OperationAvailable' ];
506
- const knownAnnos = annoNames.filter(whatsMyTermNamespace).filter(x => carrier[x] !== null || nullWhitelist.includes(x));
522
+ const knownAnnos = annoNames.filter(n => {
523
+ const tns = whatsMyTermNamespace(n);
524
+ return tns &&
525
+ (mergedVocDefs[tns] && !mergedVocDefs[tns].$ignore ||
526
+ !mergedVocDefs[tns]);
527
+ }).filter(x => carrier[x] !== null || nullWhitelist.includes(x));
507
528
  if(isBetaEnabled(options, 'odataTerms')) {
508
529
  // Extend knownAnnos with the in-service term definitions
509
- const adhoc = [];
510
530
  annoNames.forEach(an => {
511
531
  const paths = an.slice(1).split('.');
512
532
  const hasNSPrefix = paths[0] === serviceName;
@@ -530,10 +550,9 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
530
550
  carrier[fqName] = carrier[an];
531
551
  delete carrier[an];
532
552
  }
533
- adhoc.push(fqName);
553
+ knownAnnos.push(fqName);
534
554
  }
535
555
  });
536
- knownAnnos.push(...adhoc);
537
556
  }
538
557
  return knownAnnos;
539
558
  }
@@ -868,17 +887,20 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
868
887
  newAnno.setEdmAttribute('Term', termNameWithoutQualifiers);
869
888
  newAnno.setEdmAttribute('Qualifier', qualifier);
870
889
  }
871
- // get the type of the term from the dictionary
890
+ // get the type of the term from the dictionary
872
891
  let termTypeName = null;
873
892
  const dictTerm = getDictTerm(termNameWithoutQualifiers, msg);
874
893
  if (dictTerm) {
875
894
  termTypeName = dictTerm.Type;
876
895
  }
877
896
  else {
878
- message('odata-anno-def', msg.location,{ anno: termNameWithoutQualifiers });
897
+ // message if term is completely unknown or if vocabulary is unchecked
898
+ const vocDef = mergedVocDefs[whatsMyTermNamespace('@'+termNameWithoutQualifiers)];
899
+ if((vocDef?.int && vocDef?.int?.filename) || !vocDef)
900
+ message('odata-anno-def', msg.location,{ anno: termNameWithoutQualifiers });
879
901
  }
880
902
 
881
- // handle the annotation value and put the result into the <Annotation ...> tag just created above
903
+ // handle the annotation value and put the result into the <Annotation ...> tag just created above
882
904
  handleValue(annoValue, newAnno, termNameWithoutQualifiers, termTypeName, msg);
883
905
  }
884
906
  return newAnno;
@@ -1240,7 +1262,7 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
1240
1262
  if (dTypeName && !isComplexType(dTypeName)) {
1241
1263
  if (!getDictType(dTypeName) && !isPrimitiveType(dTypeName) && !isCollection(dTypeName))
1242
1264
  message('odata-anno-dict', msg.location,
1243
- { anno: msg.anno(), type: dTypeName, '#': 'std' });
1265
+ { anno: msg.anno(), type: dTypeName });
1244
1266
  else
1245
1267
  message('odata-anno-value', msg.location,
1246
1268
  { anno: msg.anno(), str: 'structured', type: dTypeName, '#': 'struct' });
@@ -1280,7 +1302,7 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
1280
1302
  // Dictionary Type, render in XML only for backward compatibility
1281
1303
  newRecord.setXml( { Type: actualTypeName });
1282
1304
  const vocName = actualTypeName.slice(0, actualTypeName.indexOf('.'));
1283
- const vocDef = vocabularyDefinitions[vocName];
1305
+ const vocDef = mergedVocDefs[vocName];
1284
1306
  // Set full qualified type in JSON
1285
1307
  // TODO: Adhoc type x-ref URIs (only if abstract types are allowed in CDS)
1286
1308
  if(vocDef)
@@ -1552,7 +1574,7 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
1552
1574
  }
1553
1575
 
1554
1576
  /**
1555
- * translate vocabulary definitions into an adhoc dictionary
1577
+ * translate vocabulary definitions into a userDefinedTermDict
1556
1578
  * with the same structure as the global jsonDictionary that
1557
1579
  * contains all official term and type definitions.
1558
1580
  *
@@ -1561,7 +1583,7 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
1561
1583
  *
1562
1584
  * @returns [object, Array<object>]
1563
1585
  */
1564
- function createAdhocDictionary() {
1586
+ function createUserDefinedTermDictionary() {
1565
1587
  const allKnownVocabularies = [];
1566
1588
  const dict = { terms: {}, types: {}, xrefs: {} };
1567
1589
 
@@ -1572,7 +1594,7 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
1572
1594
  let dictDef = oDataDictionary.terms[termName];
1573
1595
  if(dictDef) {
1574
1596
  message('odata-anno-dict', ['vocabularies', termName],
1575
- { anno: termName, '#': 'redefinition' } );
1597
+ { anno: termName, string: 'annotation', '#': 'redefinition' } );
1576
1598
  }
1577
1599
  else if(!dictDef) {
1578
1600
  const annoDef = csnVocabularies[termName];
@@ -1799,8 +1821,64 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName, Edm = undefine
1799
1821
 
1800
1822
  }
1801
1823
 
1824
+ function mergeVocRefs(options, message) {
1825
+ /* Merge options.odataVocRefs into vocabularyDefinitions and
1826
+ create a csn2edm stack local dictionary.
1827
+ odataVocRefs is an object, each property is the
1828
+ annotation prefix (as in mergedVocDefs), the value
1829
+ is an object { Alias, Namespace, Uri }, this way
1830
+ the definitions are unique and duplicate entries to address
1831
+ the annotation via alias and namespace is possible (see
1832
+ inverted index of mergedVocDefs above)
1833
+ */
1834
+ const mergedVocDefs = Object.assign({}, vocabularyDefinitions);
1835
+ const reqProps = ['Alias', 'Namespace', 'Uri'];
1836
+ if(options.odataVocRefs) {
1837
+ const vocRefs = options.odataVocRefs;
1838
+ if (typeof vocRefs === 'object' && !Array.isArray(vocRefs)) {
1839
+ Object.entries(vocRefs).forEach(([id, def]) => {
1840
+ let defOk = true;
1841
+ reqProps.forEach(name => {
1842
+ if(!def[name] || typeof def[name] !== 'string') {
1843
+ message('odata-anno-vocref', null,
1844
+ { id, name, '#': 'malformed' } );
1845
+ defOk = false;
1846
+ }
1847
+ else if(name === 'Alias' && !edmUtils.isODataSimpleIdentifier(def[name])) {
1848
+ message('odata-spec-violation-id', null, { id:name, value: def[name], '#': 'vocrefalias' });
1849
+ defOk = false;
1850
+ }
1851
+ });
1852
+ if(defOk) {
1853
+ const vocDef = mergedVocDefs[id];
1854
+ if(vocDef && !vocDef.$optVocRef) {
1855
+ message('odata-anno-vocref', null,
1856
+ { id, type: mergedVocDefs[id].inc.Namespace, '#': 'redef' } );
1857
+ }
1858
+ else {
1859
+ if(id !== def.Alias) {
1860
+ message('odata-anno-vocref', null,
1861
+ { id, name: def.Alias } );
1862
+ }
1863
+ else {
1864
+ // no int.filename => no validation
1865
+ const vocDef = {
1866
+ ref: { Uri: def.Uri },
1867
+ inc: { Alias: def.Alias, Namespace: def.Namespace }
1868
+ };
1869
+ setProp(vocDef, '$optVocRef', true);
1870
+ mergedVocDefs[id] = vocDef;
1871
+ }
1872
+ }
1873
+ }
1874
+ });
1875
+ }
1876
+ }
1877
+ return mergedVocDefs;
1878
+ }
1879
+
1802
1880
  //-------------------------------------------------------------------------------------------------
1803
1881
  //-------------------------------------------------------------------------------------------------
1804
1882
  //-------------------------------------------------------------------------------------------------
1805
1883
 
1806
- module.exports = { knownVocabularies, vocabularyDefinitions, csn2annotationEdm };
1884
+ module.exports = { vocabularyDefinitions, csn2annotationEdm, mergeVocRefs };
@@ -51,6 +51,8 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
51
51
  whatsMyServiceRootName,
52
52
  fallBackSchemaName,
53
53
  options ] = initializeModel(csn, _options, messageFunctions, serviceNames);
54
+
55
+ const mergedVocabularies = translate.mergeVocRefs(options, message);
54
56
 
55
57
  const Edm = getEdm(options, messageFunctions);
56
58
 
@@ -78,6 +80,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
78
80
  services[serviceCsn.name] = createEdm(serviceCsn);
79
81
  return services; }, rc);
80
82
  }
83
+
81
84
  throwWithError();
82
85
  return rc;
83
86
 
@@ -423,6 +426,24 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
423
426
  }
424
427
  });
425
428
 
429
+ /*
430
+ Remove EntityContainer if empty
431
+ V4 spec says:
432
+ Chapter 5 Element edm:Schema
433
+ It MAY contain elements [...], edm:EntityContainer, [...].
434
+ Chapter 13 Element edm:EntityContainer
435
+ The edm:EntityContainer MUST contain one or more edm:EntitySet, edm:Singleton, edm:ActionImport, or edm:FunctionImport elements.
436
+
437
+ The first sentence in chapter 13 is:
438
+ Each metadata document used to describe an OData service MUST define exactly one entity container.
439
+
440
+ This sentence expresses that an OData SERVICE must contain an entity container, but an EDMX is not required to have a container.
441
+ Therefore it is absolutely legal and necessary to remove an empty container from the IR!
442
+ */
443
+ if(Schema._ec && Schema._ec._children.length === 0) {
444
+ Schema._children.splice(Schema._children.indexOf(Schema._ec), 1);
445
+ }
446
+
426
447
  Object.entries(NamesInSchemaXRef).forEach(([name, refs]) => {
427
448
  if(refs.length > 1) {
428
449
  error(null, ['definitions', `${Schema._edmAttributes.Namespace}.${name}`], { name: Schema._edmAttributes.Namespace },
@@ -1026,7 +1047,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
1026
1047
 
1027
1048
  // generate the Edm.Annotations tree and append it to the corresponding schema
1028
1049
  function addAnnotations(xServiceRefs) {
1029
- let { annos, usedVocabularies, xrefs } = translate.csn2annotationEdm(reqDefs, csn.vocabularies, serviceCsn.name, Edm, options, messageFunctions);
1050
+ let { annos, usedVocabularies, xrefs } = translate.csn2annotationEdm(reqDefs, csn.vocabularies, serviceCsn.name, Edm, options, messageFunctions, mergedVocabularies);
1030
1051
  // distribute edm:Annotations into the schemas
1031
1052
  // Distribute each anno into Schema
1032
1053
  annos.forEach(anno => {
@@ -820,8 +820,11 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
820
820
  (isContainerAssoc && !options.renderForeignKeys))
821
821
  edmUtils.assignAnnotation(element, '@cds.api.ignore', true);
822
822
  // Only in containment:
823
- // If this element is a foreign key and if it is rendered, remove it from the key ref vector
824
- else if(options.odataContainment && isContainerAssoc && options.renderForeignKeys) {
823
+ // If this element is a foreign key and if it is rendered, remove it from the key ref vector (if available)
824
+ else if(options.odataContainment &&
825
+ isContainerAssoc &&
826
+ options.renderForeignKeys &&
827
+ struct.$keys) {
825
828
  delete struct.$keys[element.name];
826
829
  }
827
830
  }
@@ -830,8 +833,8 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
830
833
  if(options.odataContainment && element['@odata.containment.ignore']) {
831
834
  if(!options.renderForeignKeys)
832
835
  edmUtils.assignAnnotation(element, '@cds.api.ignore', true);
833
- else
834
- // If foreign keys shall be rendered, remove it from key ref vector
836
+ else if(struct.$keys)
837
+ // If foreign keys shall be rendered, remove it from key ref vector (if available)
835
838
  delete struct.$keys[element.name];
836
839
  }
837
840
  }
@@ -1560,7 +1563,10 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1560
1563
  Object.entries(elements).forEach(([eltName, elt]) => {
1561
1564
  if(!elt.$visited) {
1562
1565
  setProp(elt, '$visited', true);
1563
- const newRefs = produceKeyRefPaths(elt, prefix + options.pathDelimiter + eltName, path);
1566
+ let newRefs = [];
1567
+ // if the foreign keys are explicitly requested, ignore associations and use the flat foreign key instead
1568
+ if(!options.renderForeignKeys || (options.renderForeignKeys && !elt.target))
1569
+ newRefs = produceKeyRefPaths(elt, prefix + options.pathDelimiter + eltName, path);
1564
1570
  if(newRefs.length) {
1565
1571
  keyPaths.push(...newRefs);
1566
1572
  // check path step key for spec violations
@@ -1855,33 +1861,47 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1855
1861
  if(typeDef?.enum) {
1856
1862
  const enumValue = [];
1857
1863
  for(const enumSymbol in typeDef.enum) {
1858
- let enumSymbolDef = typeDef.enum[enumSymbol];
1859
1864
  const result = { '@Core.SymbolicName': enumSymbol };
1860
- if(enumSymbolDef['#'])
1865
+ let enumSymbolDef = typeDef.enum[enumSymbol];
1866
+ while(enumSymbolDef && !enumSymbolDef.$visited && enumSymbolDef['#']) {
1867
+ setProp(enumSymbolDef, '$visited', true);
1861
1868
  enumSymbolDef = typeDef.enum[enumSymbolDef['#']];
1862
- if(enumSymbolDef.val === undefined) {
1863
- if(typeDef.type === 'cds.String') {
1864
- // the symbol is used as value for type 'cds.String'
1865
- result.Value = enumSymbol;
1869
+ }
1870
+ // reset visited
1871
+ for(const es in typeDef.enum)
1872
+ delete typeDef.enum[es].$visited;
1873
+
1874
+ if(enumSymbolDef) {
1875
+ if(enumSymbolDef.val !== undefined) {
1876
+ // 'null' value is represented spec conform as empty record in AllowedValues collection
1877
+ result.Value = enumSymbolDef.val;
1866
1878
  enumValue.push(result);
1867
1879
  }
1868
- else if(node.kind !== 'annotation')
1869
- // omit the entry and warn
1870
- warning('odata-enum-missing-value', path,
1880
+ else {
1881
+ if(typeDef.type === 'cds.String') {
1882
+ // the symbol is used as value for type 'cds.String'
1883
+ result.Value = enumSymbol;
1884
+ enumValue.push(result);
1885
+ }
1886
+ else if(node.kind !== 'annotation') {
1887
+ // omit the entry and warn
1888
+ warning('odata-enum-missing-value', path,
1889
+ { name: enumSymbol, anno: '@Valiation.AllowedValues', type: typeDef.type },
1890
+ 'Expected enum element $(NAME) of type $(TYPE) to have a value, not added to $(ANNO)');
1891
+ }
1892
+ }
1893
+ }
1894
+ else { // enumSymbolDef not found
1895
+ // omit the entry and warn
1896
+ warning('odata-enum-missing-value', path,
1871
1897
  { name: enumSymbol, anno: '@Valiation.AllowedValues', type: typeDef.type },
1872
1898
  'Expected enum element $(NAME) of type $(TYPE) to have a value, not added to $(ANNO)');
1873
1899
  }
1874
- else {
1875
- // 'null' value is represented spec conform as empty record in AllowedValues collection
1876
- //if(enumSymbolDef.val !== null)
1877
- result.Value = enumSymbolDef.val;
1878
- enumValue.push(result);
1879
- }
1880
1900
 
1881
1901
  // Can't rely that @description has already been renamed to @Core.Description
1882
1902
  // Eval description according to precedence (doc comment must be considered already in Odata transformer
1883
1903
  // as in contrast to the other doc commments as it is used to annotate the @Validation.AllowedValues)
1884
- const desc = enumSymbolDef['@Core.Description'] || enumSymbolDef['@description'] || enumSymbolDef.doc;
1904
+ const desc = enumSymbolDef ? enumSymbolDef['@Core.Description'] || enumSymbolDef['@description'] || enumSymbolDef.doc : undefined;
1885
1905
  if (desc)
1886
1906
  result['@Core.Description'] = desc;
1887
1907
  }
@@ -3540,6 +3540,10 @@
3540
3540
  "UserID": {
3541
3541
  "Value": "UserID",
3542
3542
  "Type": "String"
3543
+ },
3544
+ "EndOfPurposeDate": {
3545
+ "Value": "EndOfPurposeDate",
3546
+ "Type": "String"
3543
3547
  }
3544
3548
  },
3545
3549
  "Symbols": {}
@@ -1 +1 @@
1
- 6c1cc14fd8155dcb48908afb59568b42
1
+ 7b8ead4c652cf564b5ba45f94d2c45af