@sap/cds-compiler 5.3.2 → 5.4.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 (49) hide show
  1. package/CHANGELOG.md +29 -2
  2. package/bin/cdsc.js +1 -1
  3. package/doc/CHANGELOG_BETA.md +2 -2
  4. package/lib/api/options.js +4 -2
  5. package/lib/base/builtins.js +0 -10
  6. package/lib/base/keywords.js +3 -31
  7. package/lib/base/message-registry.js +23 -5
  8. package/lib/base/messages.js +1 -1
  9. package/lib/checks/existsMustEndInAssoc.js +7 -2
  10. package/lib/checks/foreignKeys.js +12 -7
  11. package/lib/compiler/assert-consistency.js +11 -3
  12. package/lib/compiler/builtins.js +2 -0
  13. package/lib/compiler/checks.js +88 -38
  14. package/lib/compiler/define.js +2 -2
  15. package/lib/compiler/shared.js +9 -10
  16. package/lib/compiler/xpr-rewrite.js +11 -0
  17. package/lib/compiler/xsn-model.js +1 -1
  18. package/lib/edm/csn2edm.js +2 -0
  19. package/lib/edm/edm.js +2 -1
  20. package/lib/edm/edmPreprocessor.js +14 -1
  21. package/lib/edm/edmUtils.js +17 -2
  22. package/lib/gen/BaseParser.js +291 -197
  23. package/lib/gen/CdlParser.js +1631 -1605
  24. package/lib/gen/Dictionary.json +74 -6
  25. package/lib/gen/language.checksum +1 -1
  26. package/lib/gen/language.interp +1 -1
  27. package/lib/gen/languageParser.js +1808 -1804
  28. package/lib/language/antlrParser.js +8 -4
  29. package/lib/language/genericAntlrParser.js +3 -3
  30. package/lib/model/csnUtils.js +6 -1
  31. package/lib/optionProcessor.js +4 -0
  32. package/lib/parsers/AstBuildingParser.js +172 -108
  33. package/lib/parsers/CdlGrammar.g4 +154 -134
  34. package/lib/parsers/Lexer.js +3 -3
  35. package/lib/parsers/identifiers.js +59 -0
  36. package/lib/render/toCdl.js +5 -5
  37. package/lib/render/utils/common.js +5 -0
  38. package/lib/render/utils/delta.js +23 -5
  39. package/lib/transform/db/expansion.js +2 -1
  40. package/lib/transform/db/transformExists.js +10 -9
  41. package/lib/transform/effective/annotations.js +147 -0
  42. package/lib/transform/effective/main.js +16 -2
  43. package/lib/transform/forOdata.js +53 -10
  44. package/lib/transform/forRelationalDB.js +7 -0
  45. package/lib/transform/odata/createForeignKeys.js +180 -0
  46. package/lib/transform/odata/flattening.js +135 -19
  47. package/lib/transform/odata/typesExposure.js +4 -3
  48. package/lib/transform/transformUtils.js +6 -6
  49. package/package.json +1 -1
@@ -0,0 +1,180 @@
1
+ 'use strict';
2
+
3
+ const { isBuiltinType } = require('../../base/builtins');
4
+ const { setProp } = require('../../base/model');
5
+ const { applyTransformations, implicitAs, findAnnotationExpression, copyAnnotations, isDeepEqual } = require('../../model/csnUtils');
6
+ const { EdmTypeFacetNames } = require('../../edm/EdmPrimitiveTypeDefinitions');
7
+
8
+ function createForeignKeyElements(csn, options, messageFunctions, csnUtils, iterateOptions = {}) {
9
+
10
+ const { error } = messageFunctions;
11
+
12
+ applyTransformations(csn, { elements: createForeignKeysInCsn, params: createForeignKeysInCsn},
13
+ [], Object.assign(iterateOptions, { skip: ['aspect', 'event'], skipStandard: { targetAspect: true } }));
14
+
15
+ /**
16
+ * Process a given elements or params dictionary and create foreign key elements.
17
+ *
18
+ * @param {object} parent The thing HAVING params or elements
19
+ * @param {string} prop
20
+ * @param {object} dict The params or elements thing
21
+ * @param {CSN.Path} path
22
+ */
23
+ function createForeignKeysInCsn( parent, prop, dict, path ) {
24
+ const orderedElements = [];
25
+ // First, generate the FK elements for given element
26
+ Object.entries(dict).forEach(([elementName, element]) => {
27
+ orderedElements.push([ elementName, element ]);
28
+ if (!csnUtils.isManagedAssociation(element)) return;
29
+ const elementPath = path.concat(prop, elementName);
30
+ const generatedForeignKeys = createForeignKeysForElement(csnUtils, elementPath, element, elementName, csn, options, '_');
31
+
32
+ // Second, finalize the generated FK elements
33
+ const refCount = generatedForeignKeys.reduce((acc, fk) => {
34
+ // count duplicates
35
+ if (acc[fk[0]])
36
+ acc[fk[0]]++;
37
+ else
38
+ acc[fk[0]] = 1;
39
+
40
+ // check for name clash with existing elements
41
+ if (parent[prop][fk[0]] && isDeepEqual(element, parent[prop][fk[0]], true)) {
42
+ // error location is the colliding element
43
+ error('name-duplicate-element', elementPath, { '#': 'flatten-fkey-exists', name: fk[0], art: elementName });
44
+ }
45
+ // attach a proper $path
46
+ setProp(element, '$path', elementPath);
47
+ return acc;
48
+ }, Object.create(null));
49
+
50
+ // set default for single foreign key from association (if available)
51
+ if (element.default?.val !== undefined && generatedForeignKeys.length === 1)
52
+ generatedForeignKeys[0][1].default = element.default;
53
+
54
+ // check for duplicate foreign keys
55
+ Object.entries(refCount).forEach(([ name, occ ]) => {
56
+ if (occ > 1)
57
+ error('name-duplicate-element', elementPath, { '#': 'flatten-fkey-gen', name, art: elementName });
58
+ });
59
+ if (element.keys) {
60
+ if (options.transformation === 'effective')
61
+ delete element.default;
62
+ }
63
+ setProp(element, '$generatedForeignKeys', generatedForeignKeys.map(gfk => gfk[0]));
64
+ orderedElements.push(...generatedForeignKeys);
65
+
66
+ });
67
+
68
+ parent[prop] = orderedElements.reduce((elementsAccumulator, [ name, element ]) => {
69
+ elementsAccumulator[name] = element;
70
+ return elementsAccumulator;
71
+ }, Object.create(null));
72
+ }
73
+
74
+ function createForeignKeysForElement(csnUtils, path, element, prefix, csn, options, pathDelimiter, lvl = 0 ) {
75
+ const special$self = !csn?.definitions?.$self && '$self';
76
+ const isInspectRefResult = !Array.isArray(path);
77
+
78
+ let fks = [];
79
+ if (!element)
80
+ return fks;
81
+
82
+ let finalElement = element;
83
+ let finalTypeName;
84
+
85
+ // resolve derived type
86
+ if (element.type && !isBuiltinType(element.type) && element.type !== special$self) {
87
+ const tmpElt = csnUtils.effectiveType(element);
88
+ // effective type resolves to structs and enums only but not scalars
89
+ if (Object.keys(tmpElt).length) {
90
+ finalElement = tmpElt;
91
+ finalTypeName = finalElement.$path[1];
92
+ }
93
+ else {
94
+ // unwind a derived type chain to a scalar type
95
+ while (finalElement?.type && !isBuiltinType(finalElement?.type)) {
96
+ finalTypeName = finalElement.type;
97
+ finalElement = csn.definitions[finalElement.type];
98
+ }
99
+ }
100
+ }
101
+
102
+ if (!finalElement)
103
+ return [];
104
+
105
+ // main if for this function
106
+ // the element is a managed association
107
+ if (csnUtils.isManagedAssociation(finalElement)) {
108
+ finalElement.keys.forEach((key, keyIndex) => {
109
+ const continuePath = getContinuePath([ 'keys', keyIndex ]);
110
+ const alias = key.as || implicitAs(key.ref);
111
+ const result = csnUtils.inspectRef(continuePath);
112
+ fks = fks.concat(createForeignKeysForElement(csnUtils, result, result.art, alias, csn, options, pathDelimiter, lvl + 1));
113
+ });
114
+ }
115
+ // the element is a structure
116
+ else if (finalElement.elements) {
117
+ Object.entries(finalElement.elements).forEach(([ elemName, elem ]) => {
118
+ // Skip already produced foreign keys
119
+ if (!elem['@odata.foreignKey4']) {
120
+ const continuePath = getContinuePath([ 'elements', elemName ]);
121
+ fks = fks.concat(createForeignKeysForElement(csnUtils, continuePath, elem, elemName, csn, options, pathDelimiter, lvl + 1));
122
+ }
123
+ });
124
+ }
125
+ // we have reached a leaf element, create a foreign key
126
+ else if ((finalElement.type == null || isBuiltinType(finalElement.type)) && !finalElement.on) {
127
+ const newFk = Object.create(null);
128
+ [ 'type', 'length', 'scale', 'precision', 'srid', 'default', '@odata.Type', ...EdmTypeFacetNames.map(f => `@odata.${f}`) ].forEach((prop) => {
129
+ // copy props from original element to preserve derived types!
130
+ if (element[prop] !== undefined)
131
+ newFk[prop] = element[prop];
132
+ });
133
+ return [ [ prefix, newFk ] ];
134
+ }
135
+
136
+ fks.forEach((fk) => {
137
+ // prepend current prefix
138
+ fk[0] = `${prefix}${pathDelimiter}${fk[0]}`;
139
+ // if this is the entry association, decorate the final foreign keys with the association props/annos
140
+ if (lvl === 0) {
141
+ if (options.transformation !== 'effective')
142
+ fk[1]['@odata.foreignKey4'] = prefix;
143
+
144
+ const validAnnoNames = Object.keys(element).filter(pn => pn[0] === '@' && !findAnnotationExpression(element, pn));
145
+ copyAnnotations(element, fk[1], true, {}, validAnnoNames);
146
+ // propagate not null to final foreign key
147
+ for (const prop of [ 'notNull', 'key' ]) {
148
+ if (element[prop] !== undefined)
149
+ fk[1][prop] = element[prop];
150
+ }
151
+ if (element.$location)
152
+ setProp(fk[1], '$location', element.$location);
153
+ }
154
+ });
155
+ return fks;
156
+
157
+ /**
158
+ * Get the path to continue resolving references
159
+ *
160
+ * If we are currently inside of a type, we need to start our path fresh from that given type.
161
+ * Otherwise, we would try to resolve .elements on a thing that does not exist.
162
+ *
163
+ * We also respect if we have a previous inspectRef result as our base.
164
+ *
165
+ * @param {Array} additions
166
+ * @returns {CSN.Path}
167
+ */
168
+ function getContinuePath( additions ) {
169
+ if (csn.definitions[finalElement.type])
170
+ return [ 'definitions', finalElement.type, ...additions ];
171
+ else if (finalTypeName)
172
+ return [ 'definitions', finalTypeName, ...additions ];
173
+ else if (isInspectRefResult)
174
+ return [ path, ...additions ];
175
+ return [ ...path, ...additions ];
176
+ }
177
+ }
178
+ }
179
+
180
+ module.exports = createForeignKeyElements;
@@ -5,14 +5,16 @@ const { forEachDefinition,
5
5
  transformExpression, transformAnnotationExpression } = require('../../model/csnUtils');
6
6
  const { isBuiltinType, isMagicVariable } = require('../../base/builtins');
7
7
  const transformUtils = require('../transformUtils');
8
- const { setProp } = require('../../base/model');
8
+ const { setProp, forEachGeneric } = require('../../base/model');
9
9
  const { applyTransformationsOnDictionary,
10
10
  applyTransformationsOnNonDictionary } = require('../db/applyTransformations.js');
11
11
  const { handleManagedAssociationsAndCreateForeignKeys } = require('../db/flattening');
12
12
  const { cloneCsnNonDict } = require('../../model/cloneCsn');
13
13
  const { forEach } = require('../../utils/objectUtils');
14
+ const { assignAnnotation } = require('../../edm/edmUtils');
14
15
 
15
- function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternalServiceMember, error, csnUtils, options) {
16
+ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, getFinalTypeInfo, isExternalServiceMember, error, csnUtils, options) {
17
+ const allMgdAssocDefs = [];
16
18
  forEachDefinition(csn, (def, defName) => {
17
19
  if (def.kind === 'entity' && !isExternalServiceMember(def, defName)) {
18
20
  ['elements', 'params'].forEach(dictName => {
@@ -27,7 +29,7 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
27
29
  let resolvedElt = child;
28
30
  let typeIdx = 0;
29
31
  if (child.type && !child.elements) {
30
- resolvedElt = csnUtils.getFinalTypeInfo(child.type);
32
+ resolvedElt = getFinalTypeInfo(child.type);
31
33
  if (resolvedElt.elements)
32
34
  typeIdx = rootPrefix.length + 1;
33
35
  }
@@ -41,10 +43,12 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
41
43
  { '#': 'flatten-element-exist', name: flatEltName });
42
44
  propagateToFlatElem(child, flatElt);
43
45
  rewriteOnCondition(flatElt, flattenedSubTree);
46
+ adaptManagedAssociationSpecialFields(flatElt, flatEltName, flattenedSubTree);
44
47
  orderedElementList.push([flatEltName, flatElt]);
45
48
  });
46
49
  }
47
50
  else {
51
+ // TODO: run adaptManagedAssociationSpecialFields here as well
48
52
  const flatElt = cloneElt(dictName, child, location, [ defName, childName ]);
49
53
  orderedElementList.push([childName, flatElt]);
50
54
  }
@@ -53,16 +57,26 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
53
57
  const flatDict = orderedElementList.reduce((elements, [ flatEltName, flatElt ]) => {
54
58
  if (flatElt.items) {
55
59
  // rewrite annotation paths inside items.elements
56
- forEachMemberRecursively(flatElt.items, (elt, _eltName, _prop, path) => {
60
+ forEachMemberRecursively(flatElt.items, (elt, eltName, _prop, path) => {
57
61
  const exprAnnos = Object.keys(elt).filter(pn => pn[0] === '@');
58
62
  flattenAndPrefixExprPaths(elt, exprAnnos, elt.$path, path, 0, true);
63
+ if (csnUtils.isManagedAssociation(elt)) {
64
+ allMgdAssocDefs.push(elt);
65
+ }
59
66
  }, [ flatEltName ], true, { pathWithoutProp: true } );
60
67
  }
68
+ setProp(flatElt, '$pathInStructuredModel', flatElt.$path);
61
69
  setProp(flatElt, '$path', [ ...csnPath, flatEltName ]);
62
70
  elements[flatEltName] = flatElt;
63
71
  return elements;
64
72
  }, Object.create(null));
65
73
  setProp(def, `$flat${dictName}`, flatDict);
74
+ orderedElementList.reduce(
75
+ (mgdAssocs, [ _flatEltName, flatElt ]) => {
76
+ if(csnUtils.isManagedAssociation(flatElt))
77
+ mgdAssocs.push(flatElt);
78
+ return mgdAssocs;
79
+ }, allMgdAssocDefs);
66
80
  }
67
81
  });
68
82
  // entity annotations
@@ -126,7 +140,30 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
126
140
  });
127
141
  }
128
142
  }
143
+ // loop through types as well in order to collect the managaed associations
144
+ // that reside in types definitions
145
+ if ((def.kind === 'action' || def.kind === 'function' || def.kind === 'type') && !isExternalServiceMember(def, defName)) {
146
+ if (def.kind === 'type' && csnUtils.isManagedAssociation(def))
147
+ allMgdAssocDefs.push(def);
148
+ else
149
+ forEachMemberRecursively(def, (elt, _eltName, _prop, _path) => {
150
+ if (csnUtils.isManagedAssociation(elt)) {
151
+ allMgdAssocDefs.push(elt);
152
+ }
153
+ });
154
+ }
155
+ // loop through actions/functions and action/function parameters as well
156
+ if (def.kind === 'entity' && def.actions && !isExternalServiceMember(def, defName)) {
157
+ forEachGeneric(def, 'actions', (act) => {
158
+ forEachMemberRecursively(act, (elt, _eltName, _prop, _path) => {
159
+ if (csnUtils.isManagedAssociation(elt)) {
160
+ allMgdAssocDefs.push(elt);
161
+ }
162
+ });
163
+ });
164
+ }
129
165
  });
166
+ return allMgdAssocDefs;
130
167
 
131
168
  function recurseIntoElement(scope, elt, resolvedElt, rootPathIsNotNull, location, rootPrefix = [], typeIdx = 0) {
132
169
  const eltName = rootPrefix[rootPrefix.length-1];
@@ -143,7 +180,7 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
143
180
  forEach(resolvedElt.elements, (childName, child) => {
144
181
  resolvedElt = child;
145
182
  if (child.type && !child.elements) {
146
- resolvedElt = csnUtils.getFinalTypeInfo(child.type);
183
+ resolvedElt = getFinalTypeInfo(child.type);
147
184
  if (resolvedElt.elements)
148
185
  typeIdx = rootPrefix.length + 1;
149
186
  }
@@ -206,14 +243,16 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
206
243
  function cloneElt(scope, elt, location, rootPrefix = [], typeIdx = 0) {
207
244
  const flatElt = cloneCsnNonDict(elt,
208
245
  { ...options,
209
- hiddenPropertiesToClone: [ '$structRef', '$fkExtensions' ]
246
+ hiddenPropertiesToClone: [ '$structRef', '$fkExtensions', '$generatedForeignKeys' ]
210
247
  } );
211
248
 
212
249
  // needed for @cds.persistence.name
213
250
  setProp(flatElt, '$defPath', rootPrefix);
214
251
  setProp(flatElt, '$scope', scope);
215
252
  retypeCloneWithFinalBaseType(flatElt, location);
216
-
253
+ if((elt._type || elt).type === 'cds.Map') {
254
+ assignAnnotation(flatElt, '@open', elt['@open'] || true);
255
+ }
217
256
  const [ nonAnnoProps, exprAnnoProps ] = Object.keys(flatElt).reduce((acc, pn) => {
218
257
  if (pn[0] !== '@' && pn !== 'value')
219
258
  acc[0].push(pn);
@@ -405,6 +444,25 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
405
444
  });
406
445
  }
407
446
  }
447
+
448
+ // update the @odata.foreignKey4 and the $generatedForeignKeys array of
449
+ // an association that have been flattened
450
+ function adaptManagedAssociationSpecialFields(flatElt, flatEltName, flattenedSubTree) {
451
+ if (!(flatElt.target || flatElt.keys) && !flatElt.on )
452
+ return;
453
+ const structuredAssocName = flatElt.$defPath[flatElt.$defPath.length - 1];
454
+ const generatedForeignKeysForAssoc = flattenedSubTree
455
+ .filter(se => {
456
+ // compare $defPath without the last element
457
+ let comeFromSameDef = flatElt.$defPath.slice(0, flatElt.$defPath.length - 1).join('.') === se[1].$defPath.slice(0, se[1].$defPath.length - 1).join('.');
458
+ return (comeFromSameDef && (se[1]['@odata.foreignKey4'] && se[1]['@odata.foreignKey4'] === structuredAssocName) && se[0].startsWith(flatEltName))
459
+ });
460
+
461
+ generatedForeignKeysForAssoc.forEach(gfk => gfk[1]['@odata.foreignKey4'] = flatEltName);
462
+ // reassign the generated foreign keys for current assoc in order to assign
463
+ // correct values for $generatedFieldName later on during flattenManagedAssocsAsKeys();
464
+ setProp(flatElt, '$generatedForeignKeys', generatedForeignKeysForAssoc.map(gfk => gfk[0]));
465
+ }
408
466
  }
409
467
 
410
468
  function flattenAllStructStepsInRefs( csn, refFlattener, adaptRefs, inspectRef, effectiveType,
@@ -494,6 +552,8 @@ function getStructRefFlatteningTransformer(csn, inspectRef, effectiveType, optio
494
552
  const { links, art, scope } = inspectRef(path);
495
553
  const resolvedLinkTypes = resolveLinkTypes(links);
496
554
  setProp(parent, '$path', [ ...path ]);
555
+ if (insideKeys(path))
556
+ setProp(parent, '_art', art);
497
557
  const lastRef = ref[ref.length - 1];
498
558
  const fn = (suspend = false, suspendPos = 0,
499
559
  refFilter = (_parent) => true) => {
@@ -536,18 +596,19 @@ function getStructRefFlatteningTransformer(csn, inspectRef, effectiveType, optio
536
596
  || path[path.length - 3] === 'projection')
537
597
  && path[path.length - 2] === 'columns';
538
598
  }
539
- /**
540
- * Return true if the path points inside keys
541
- *
542
- * @param {CSN.Path} path
543
- * @returns {boolean}
544
- */
545
- function insideKeys( path ) {
546
- return path.length >= 3
547
- && path[path.length - 2] === 'keys'
548
- && typeof path[path.length - 1] === 'number';
549
- }
550
599
  };
600
+ /**
601
+ * Return true if the path points inside keys
602
+ *
603
+ * @param {CSN.Path} path
604
+ * @returns {boolean}
605
+ */
606
+ function insideKeys( path ) {
607
+ return path.length >= 3
608
+ && path[path.length - 2] === 'keys'
609
+ && typeof path[path.length - 1] === 'number';
610
+ }
611
+
551
612
 
552
613
  // adapt queries later
553
614
  if(ctx?.annoExpr?.['=']) {
@@ -565,9 +626,64 @@ function getStructRefFlatteningTransformer(csn, inspectRef, effectiveType, optio
565
626
  return { adaptRefs, transformer, inspectRef, effectiveType };
566
627
  }
567
628
 
629
+ // replace managed associations in key refs with their respective foreing keys
630
+ function replaceManagedAssocsAsKeys(allFlatManagedAssocDefinitions, csnUtils) {
631
+ allFlatManagedAssocDefinitions.forEach( assoc => {
632
+ let finished = false;
633
+
634
+ while (!finished) {
635
+ const newKeys = [];
636
+ finished = processKeys(newKeys);
637
+ assoc.keys = newKeys;
638
+ }
639
+
640
+ function processKeys(collector) {
641
+ let done = true;
642
+ assoc.keys.forEach( key => {
643
+ const art = key._art;
644
+ if (art && csnUtils.isManagedAssociation(art)) {
645
+ done = false;
646
+ // key._art is the artifact from the structured model, because of that we need to look
647
+ // for the flat representation in the allFlatManagedAssocDefinitions
648
+ allFlatManagedAssocDefinitions.find(fa => (fa.$pathInStructuredModel || fa.$path).join() === art.$path.join())
649
+ .keys.forEach( keyAssocKey => {
650
+ collector.push(cloneAndExtendRef(keyAssocKey, key));
651
+ });
652
+ }
653
+ else if (art && !art.on){
654
+ if (!key.$generatedFieldName) {
655
+ const flatAssocName = assoc.$path[assoc.$path.length - 1];
656
+ // When we have a definition like type "<type_name>: Association to <target_name>",
657
+ // we do not generate foreign keys in the definition, therefore no $generatedForeignKeys,
658
+ // respectively we do not assign $generatedFieldName
659
+ if (assoc.$generatedForeignKeys) {
660
+ const generatedForeignKey = assoc.$generatedForeignKeys.find(gfk => gfk === `${flatAssocName}_${key.as || key.ref.join('_')}`);
661
+ key.$generatedFieldName = generatedForeignKey;
662
+ }
663
+ }
664
+ if (key.as && key.as === key.ref[0]) delete key.as;
665
+ collector.push(key);
666
+ }
667
+ })
668
+
669
+ return done;
670
+ }
671
+ });
672
+
673
+ function cloneAndExtendRef(keyAssocKey, key) {
674
+ let newKey = { ref: [`${key.ref.join('_')}_${keyAssocKey.as || keyAssocKey.ref.join('_')}`] };
675
+ setProp(newKey, '_art', keyAssocKey._art);
676
+ if (key.as) {
677
+ newKey.as = `${key.as}_${keyAssocKey.as || keyAssocKey.ref.join('_')}`;
678
+ }
679
+ return newKey;
680
+ }
681
+ }
682
+
568
683
  module.exports = {
569
684
  allInOneFlattening,
570
685
  flattenAllStructStepsInRefs,
571
686
  handleManagedAssociationsAndCreateForeignKeys,
572
- getStructRefFlatteningTransformer
687
+ getStructRefFlatteningTransformer,
688
+ replaceManagedAssocsAsKeys
573
689
  };
@@ -64,6 +64,7 @@ const { cloneCsnNonDict } = require('../../model/cloneCsn');
64
64
  function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackSchemaName, options, csnUtils, message) {
65
65
  const { error } = message;
66
66
  const special$self = !csn?.definitions?.$self && '$self';
67
+
67
68
  // are we working with OData proxies or cross-service refs
68
69
  const isMultiSchema = options.odataVersion === 'v4' && (options.odataProxies || options.odataXServiceRefs);
69
70
  // service sub schemas as return value
@@ -166,7 +167,7 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
166
167
  isKey = false;
167
168
  // in case this was a named type and if the openness does not match the type definition
168
169
  // expose the type as a new one not changing the original definition.
169
- if(elements && !!node['@open'] !== !!typeDef['@open'])
170
+ if(elements && node['@open'] != null && !!node['@open'] !== !!typeDef['@open'])
170
171
  fullQualifiedNewTypeName += node['@open'] ? '_open' : '_closed';
171
172
  lastNonAnonymousFQDefName = fullQualifiedNewTypeName;
172
173
  }
@@ -293,10 +294,10 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
293
294
  if(type) {
294
295
  typeName = (type.ref && csnUtils.artifactRef(type)) || type;
295
296
  const rc = { isExposable: true, typeDef, typeName, isAnonymous: false };
296
- if(!isBuiltinType(typeName) && typeName !== special$self) {
297
+ if((!isBuiltinType(typeName) || typeName === 'cds.Map') && typeName !== special$self) {
297
298
  rc.typeDef = typeDef = csnUtils.artifactRef(typeName, typeName);
298
299
  if(!isArtifactInService(typeName, serviceName)) {
299
- while(!isBuiltinType(typeName)) {
300
+ while(!isBuiltinType(typeName) || typeDef.$emtpyMapType) {
300
301
  typeDef = csnUtils.artifactRef(typeName, typeName);
301
302
  if(typeDef !== typeName) {
302
303
  // Implementation note: For `type S: T:struct;`, elements from `T:struct` were already propagated to `S`.
@@ -1332,14 +1332,14 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
1332
1332
 
1333
1333
  function isScalarOrNoType(art) {
1334
1334
  art = effectiveType(art);
1335
- if(art) {
1336
- const type = art.type || (art.items && art.items.type);
1337
- // items in ON conds are illegal but this should be checked elsewhere
1335
+ if (art) {
1336
+ const type = art.type || art.items?.type;
1337
+ // items in ON-conditions are illegal but this should be checked elsewhere
1338
1338
  const elements = art.elements || (art.items && art.items.elements);
1339
1339
  // @Core.Computed has no type
1340
- return(!elements && !type ||
1341
- (type && isBuiltinType(type) &&
1342
- !['cds.Association', 'cds.Composition'].includes(type)))
1340
+ return (!elements && !type ||
1341
+ (type && isBuiltinType(type) &&
1342
+ type !== 'cds.Association' && type !== 'cds.Composition' && type !== 'cds.Map'))
1343
1343
  }
1344
1344
  return false;
1345
1345
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds-compiler",
3
- "version": "5.3.2",
3
+ "version": "5.4.2",
4
4
  "description": "CDS (Core Data Services) compiler and backends",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "author": "SAP SE (https://www.sap.com)",