@sap/cds-compiler 2.7.0 → 2.10.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 (63) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/lib/api/main.js +8 -10
  3. package/lib/api/options.js +13 -9
  4. package/lib/api/validate.js +11 -8
  5. package/lib/base/keywords.js +32 -2
  6. package/lib/base/message-registry.js +16 -0
  7. package/lib/base/messages.js +2 -0
  8. package/lib/base/model.js +1 -0
  9. package/lib/checks/onConditions.js +5 -0
  10. package/lib/checks/types.js +26 -2
  11. package/lib/checks/unknownMagic.js +38 -0
  12. package/lib/checks/validator.js +7 -2
  13. package/lib/compiler/assert-consistency.js +11 -5
  14. package/lib/compiler/builtins.js +2 -0
  15. package/lib/compiler/checks.js +3 -1
  16. package/lib/compiler/definer.js +87 -29
  17. package/lib/compiler/resolver.js +75 -16
  18. package/lib/compiler/shared.js +29 -9
  19. package/lib/edm/annotations/genericTranslation.js +182 -186
  20. package/lib/edm/csn2edm.js +93 -98
  21. package/lib/edm/edm.js +16 -20
  22. package/lib/edm/edmPreprocessor.js +274 -83
  23. package/lib/edm/edmUtils.js +29 -10
  24. package/lib/gen/language.checksum +1 -1
  25. package/lib/gen/language.interp +12 -1
  26. package/lib/gen/language.tokens +57 -53
  27. package/lib/gen/languageLexer.interp +10 -1
  28. package/lib/gen/languageLexer.js +770 -744
  29. package/lib/gen/languageLexer.tokens +49 -46
  30. package/lib/gen/languageParser.js +4727 -4323
  31. package/lib/json/from-csn.js +52 -23
  32. package/lib/json/to-csn.js +185 -71
  33. package/lib/language/errorStrategy.js +1 -0
  34. package/lib/language/genericAntlrParser.js +9 -0
  35. package/lib/language/language.g4 +90 -31
  36. package/lib/main.js +4 -0
  37. package/lib/model/api.js +78 -0
  38. package/lib/model/csnRefs.js +7 -1
  39. package/lib/model/csnUtils.js +5 -4
  40. package/lib/optionProcessor.js +7 -1
  41. package/lib/render/.eslintrc.json +3 -1
  42. package/lib/render/toCdl.js +45 -9
  43. package/lib/render/toHdbcds.js +100 -34
  44. package/lib/render/toSql.js +12 -4
  45. package/lib/render/utils/common.js +5 -9
  46. package/lib/sql-identifier.js +6 -1
  47. package/lib/transform/db/draft.js +6 -4
  48. package/lib/transform/db/expansion.js +14 -4
  49. package/lib/transform/db/flattening.js +13 -5
  50. package/lib/transform/db/transformExists.js +252 -58
  51. package/lib/transform/forHanaNew.js +7 -1
  52. package/lib/transform/forOdataNew.js +12 -8
  53. package/lib/transform/odata/attachPath.js +19 -4
  54. package/lib/transform/odata/generateForeignKeyElements.js +11 -10
  55. package/lib/transform/odata/referenceFlattener.js +44 -38
  56. package/lib/transform/odata/sortByAssociationDependency.js +2 -2
  57. package/lib/transform/odata/structuralPath.js +76 -0
  58. package/lib/transform/odata/structureFlattener.js +13 -10
  59. package/lib/transform/odata/typesExposure.js +22 -12
  60. package/lib/transform/transformUtilsNew.js +33 -1
  61. package/lib/transform/translateAssocsToJoins.js +6 -4
  62. package/lib/transform/universalCsnEnricher.js +67 -0
  63. package/package.json +1 -1
@@ -35,7 +35,7 @@ module.exports = function (csn, options, referenceFlattener, csnUtils, error, is
35
35
  let generatedForeignKeyNamesForPath = Object.create(null); // map<path,[key-name]>
36
36
 
37
37
  sortedAssociations.forEach(item => {
38
- const { definitionName, elementName, element, parent, path } = item;
38
+ const { definitionName, structuralNodeName, elementName, element, parent, path } = item;
39
39
 
40
40
  if (csnUtils.isManagedAssociationElement(element) && element.keys) {
41
41
  if (flatKeys) // tackling the ref value in assoc.keys
@@ -44,7 +44,7 @@ module.exports = function (csn, options, referenceFlattener, csnUtils, error, is
44
44
  fixCardinality(element);
45
45
  }
46
46
 
47
- let arrayOfGeneratedForeignKeyNames = generateForeignKeys(definitionName, elementName, element, parent, path);
47
+ let arrayOfGeneratedForeignKeyNames = generateForeignKeys(definitionName, structuralNodeName, elementName, element, parent, path);
48
48
  generatedForeignKeyNamesForPath[item.path.join('/')] = arrayOfGeneratedForeignKeyNames;
49
49
  })
50
50
 
@@ -111,7 +111,7 @@ module.exports = function (csn, options, referenceFlattener, csnUtils, error, is
111
111
  /**
112
112
  * Generates foreign keys and returns their names as an array
113
113
  */
114
- function generateForeignKeys(definitionName, assocName, assoc, parent, path) {
114
+ function generateForeignKeys(definitionName, structuralNodeName, assocName, assoc, parent, path) {
115
115
  let foreignKeyElements = Object.create(null);
116
116
 
117
117
  // First, loop over the keys array of the association and generate the FKs.
@@ -134,13 +134,14 @@ module.exports = function (csn, options, referenceFlattener, csnUtils, error, is
134
134
  if (parent.returns)
135
135
  parent = parent.returns.items || parent.returns;
136
136
 
137
- let currElementsNames = Object.keys(parent.elements);
137
+ const dictionary = parent[structuralNodeName];
138
+ let currElementsNames = Object.keys(parent[structuralNodeName]);
138
139
  for (const [foreignKeyName, foreignKey] of Object.entries(foreignKeyElements)) {
139
140
  copyAnnotations(assoc, foreignKey, true);
140
141
  // Insert artificial element into artifact, with all cross-links
141
- if (parent.elements[foreignKeyName]) {
142
- if (!(parent.elements[foreignKeyName]['@odata.foreignKey4'] || isDeepEqual(parent.elements[foreignKeyName], foreignKey))) {
143
- const path = parent.elements[foreignKeyName].$path;
142
+ if (dictionary[foreignKeyName]) {
143
+ if (!(dictionary[foreignKeyName]['@odata.foreignKey4'] || isDeepEqual(dictionary[foreignKeyName], foreignKey))) {
144
+ const path = dictionary[foreignKeyName].$path;
144
145
  error(null, path, { name: foreignKeyName, art: assocName }, 'Generated foreign key element $(NAME) for association $(ART) conflicts with existing element');
145
146
  }
146
147
  }
@@ -151,8 +152,8 @@ module.exports = function (csn, options, referenceFlattener, csnUtils, error, is
151
152
  // if (flatKeys)
152
153
  currElementsNames.splice(assocIndex + 1, 0, ...Object.keys(foreignKeyElements));
153
154
 
154
- parent.elements = currElementsNames.reduce((previous, name) => {
155
- previous[name] = parent.elements[name] || foreignKeyElements[name];
155
+ parent[structuralNodeName] = currElementsNames.reduce((previous, name) => {
156
+ previous[name] = dictionary[name] || foreignKeyElements[name];
156
157
  return previous;
157
158
  }, Object.create(null));
158
159
 
@@ -180,7 +181,7 @@ module.exports = function (csn, options, referenceFlattener, csnUtils, error, is
180
181
 
181
182
  function processStucturedKey(fkArtifact, assocName, foreignKeyRef) {
182
183
  const subStruct = fkArtifact.elements ? fkArtifact : csnUtils.getFinalBaseType(fkArtifact.type);
183
- const flatElements = flattenStructure(subStruct, subStruct.$path, csnUtils, options, error, undefined, fkArtifact.$path.slice(-1) || []).newFlatElements;
184
+ const flatElements = flattenStructure(subStruct.elements, subStruct.$path, csnUtils, options, error, undefined, fkArtifact.$path.slice(-1) || []).newFlatElements;
184
185
  for (const [flatElemName, flatElem] of Object.entries(flatElements)) {
185
186
  const foreignKeyElementName =
186
187
  `${assocName.replace(/\./g, '_')}_${foreignKeyRef.as ? flatElemName.replace(implicitAs(foreignKeyRef.ref), foreignKeyRef.as) : flatElemName}`;
@@ -1,6 +1,7 @@
1
1
  const { forEachRef } = require('../../model/csnUtils');
2
2
  const { setProp } = require('../../base/model');
3
3
  const { implicitAs } = require('../../model/csnRefs');
4
+ const { structuralPath } = require('./structuralPath');
4
5
 
5
6
  /** This class is used for generic reference flattening.
6
7
  * It provides the following functionality:
@@ -73,8 +74,7 @@ class ReferenceFlattener {
73
74
  });
74
75
  if (paths)
75
76
  setProp(node, '$paths', paths);
76
- setProp(node, '$scope', resolved.scope);
77
- // cache also if structured or not
77
+ // cache if structured or not
78
78
  let structured = resolved.links.map(link => link.art ? (isStructured(link.art)) : undefined);
79
79
  this.structuredReference[path.join('/')] = structured;
80
80
  });
@@ -176,10 +176,10 @@ class ReferenceFlattener {
176
176
  if (node.$paths) {
177
177
  let newRef = []; // flattened reference
178
178
  let flattenWithPrevious = false;
179
- let anythingFlattened = false; // will be set to true if at least one node is flatted
179
+ let lastFlattenedID = null; // The variable will be set to the index of the last flattened path
180
180
  node.$paths.forEach((path, i) => {
181
181
  if (path === undefined || !ref[i]) return;
182
- let spath = path.join('/')
182
+ let spath = path.join('/');
183
183
  let movedTo = this.elementTransitions[spath]; // detect element transition
184
184
  let flattened = this.flattenedElementPaths[spath];
185
185
  if (flattenWithPrevious) {
@@ -190,27 +190,26 @@ class ReferenceFlattener {
190
190
  ref[i].id = newRef[newRef.length - 1];
191
191
  newRef[newRef.length - 1] = ref[i];
192
192
  }
193
- anythingFlattened = true;
193
+ lastFlattenedID = i;
194
194
  } else if(movedTo && i===0) { // handle local scope reference which is transitioned - replace first item in reference
195
195
  let movedToElementName = movedTo[movedTo.length-1];
196
196
  newRef.push(movedToElementName);
197
- anythingFlattened=true;
197
+ lastFlattenedID = i;
198
198
  } else {
199
199
  newRef.push(ref[i]);
200
200
  }
201
201
  flattenWithPrevious = flattened;
202
202
  });
203
- if (newRef !== undefined && anythingFlattened) { // do not replace unchanged references
203
+ if (newRef !== undefined && lastFlattenedID !== null) { // make sure the reference changed and only then replace it with the new one
204
204
  // check if this is a column and add alias if missing
205
- if (isColumnInSelect(ref.$path)) {
206
- if (!node.as) {
205
+ let structural = structuralPath(csn, path);
206
+ if (isColumnInSelectOrProjection(structural)) {
207
+ if (!node.as && lastFlattenedID === ref.length-1) // attach alias only if there is none and it is the last item in the reference that was flattened
207
208
  node.as = node.ref[node.ref.length - 1];
208
- }
209
209
  }
210
210
  setProp(newRef, '$path', path.concat('ref'));
211
211
  if (!node.as) {
212
- // FIXME: rather use scope from inspectRef to determin if path leads to a key
213
- if (ifKeysScope(path))
212
+ if (isPartOfKeysStructure(structural))
214
213
  node.as = implicitAs(node.ref);
215
214
  else
216
215
  setProp(node, 'as', implicitAs(node.ref))
@@ -223,13 +222,11 @@ class ReferenceFlattener {
223
222
 
224
223
  applyAliasesInOnCond(csn, inspectRef) {
225
224
  forEachRef(csn, (ref, node, path) => {
226
- // is there a better way to detect if we are dealing with on-cond?
227
- // TODO: please do not use inspectRef() results for "where am I?" info
228
- if (!['$self', 'parent'].includes(node.$scope)) return;
229
- // the above condition means: only handle references starting with
230
- // $self or element references outside queries
231
-
225
+ // Process only on-conditions of associations
226
+ let structural = structuralPath(csn, path);
227
+ if(!isOnCondition(structural)) return;
232
228
  let { links } = inspectRef(path);
229
+ if(!links) return; // $user not resolvable
233
230
  let keysOfPreviousStepWhenManagedAssoc = undefined;
234
231
 
235
232
  let aliasedRef = [...ref];
@@ -253,32 +250,41 @@ class ReferenceFlattener {
253
250
  }
254
251
  }
255
252
 
256
- // FIXME: rather use scope from inspectRef to determin if path leads to a key
257
- function ifKeysScope(path) {
258
- if (!path) return false;
259
- if (Number.isInteger(path[path.length - 1]) && path[path.length - 2] === 'keys' && path[path.length - 4] === 'elements')
260
- return true;
261
- return false;
253
+ /**
254
+ * Checks if the provided path is a column inside a key node
255
+ * by exploring the possibility of the structural path to ends with 'elements' and 'keys'.
256
+ *
257
+ * @param structural structural path to explore
258
+ * @returns {boolean} True if the provided path matched the requirements to be part of a key node.
259
+ */
260
+ function isPartOfKeysStructure(structural) {
261
+ return structural[structural.length-2] === 'elements'
262
+ && structural[structural.length-1] === 'keys';
262
263
  }
263
264
 
264
265
  /**
265
- * Checks if the provided path is a column inside a select node
266
- * by exploring the possibility of the path to end with ['SELECT','columns','<an integer>', <ref - not checked>].
266
+ * Checks if the provided path is a column inside a select or a projection node
267
+ * by exploring the possibility of the structural path to contain 'SELECT' or 'projection'
268
+ * and ends with 'columns' or 'columns' and 'expand'.
267
269
  *
268
- * @param {CSN.Path} path Path to explore
270
+ * @param structural structural path to explore
269
271
  * @returns {boolean} True if the provided path matched the requirements to be a select node.
270
272
  */
271
- function isColumnInSelect(path) {
272
- if (!path)
273
- return false;
274
- const len = path.length;
275
- if (path[len - 4] === 'SELECT' && path[len - 3] === 'columns') {
276
- // check if -2 element is an integer / array index
277
- const arrayIndex = parseInt(String(path[len - 2]), 10);
278
- if (!isNaN(arrayIndex))
279
- return true;
280
- }
281
- return false;
273
+ function isColumnInSelectOrProjection(structural) {
274
+ return (structural.includes('SELECT') || structural.includes('projection'))
275
+ && (structural[structural.length-1] === 'columns' || (structural[structural.length-2] === 'columns' && structural[structural.length-1] === 'expand'));
276
+ }
277
+
278
+ /**
279
+ * Checks if the provided path is a column inside an on-condition of an element
280
+ * by exploring the possibility of the structural path to ends with 'elements' and 'on'.
281
+ *
282
+ * @param structural structural path to explore
283
+ * @returns {boolean} True if the provided path matched the requirements to be part of an element's on-condition.
284
+ */
285
+ function isOnCondition(structural) {
286
+ return structural[structural.length-2] === 'elements'
287
+ && structural[structural.length-1] === 'on';
282
288
  }
283
289
 
284
290
  module.exports = ReferenceFlattener;
@@ -24,7 +24,7 @@ function buildDependenciesFor(csn, referenceFlattener, isExternalServiceMember)
24
24
  forEachDefinition(csn, (def, definitionName) => {
25
25
  /** @type {CSN.Path} */
26
26
  let root = ['definitions', definitionName];
27
- forEachMemberRecursively(def, (element, elementName, _prop, subpath, parent) => {
27
+ forEachMemberRecursively(def, (element, elementName, structuralNodeName, subpath, parent) => {
28
28
  let path = root.concat(subpath);
29
29
  // go only through managed associations and compositions
30
30
  if (isAssociationOrComposition(element) && element.keys && !element.on) { // check association FKs
@@ -44,7 +44,7 @@ function buildDependenciesFor(csn, referenceFlattener, isExternalServiceMember)
44
44
  elementDependencies.push(targetElementPath);
45
45
  }
46
46
  })
47
- dependencies[path.join("/")] = { definitionName, elementName, element, path, parent, dependencies: elementDependencies };
47
+ dependencies[path.join("/")] = { structuralNodeName, definitionName, elementName, element, path, parent, dependencies: elementDependencies };
48
48
  }
49
49
  }) // forEachMemberRecursively
50
50
  }, { skipArtifact: isExternalServiceMember }) // forEachDefinition
@@ -0,0 +1,76 @@
1
+ // The module traverses a given CSN using a specific path, collects structural node names and returns them.
2
+
3
+ const structuralNodeHandlers = {
4
+ definitions: traverseDict,
5
+ elements: traverseDict,
6
+ actions: traverseDict,
7
+ params: traverseDict,
8
+ items: traverseTyped,
9
+ enum: traverseDict,
10
+ returns: traverseTyped,
11
+ on: traverseArray,
12
+ keys: traverseArray,
13
+ ref: traverseRef,
14
+ query: traverseTyped,
15
+ SELECT: traverseTyped,
16
+ SET: traverseTyped,
17
+ args: traverseArray,
18
+ columns: traverseArray,
19
+ projection: traverseTyped,
20
+ from: traverseTyped,
21
+ mixin: traverseDict,
22
+ where: traverseArray,
23
+ orderBy: traverseArray,
24
+ groupBy: traverseArray,
25
+ having: traverseArray,
26
+ xpr: traverseArray,
27
+ expand: traverseArray,
28
+ inline: traverseArray,
29
+ cast: traverseTyped,
30
+ }
31
+
32
+ function structuralPath(csn, path) {
33
+ return traverseDict(csn.definitions, path, 1, ['definitions']);
34
+ }
35
+
36
+ function traverseRef(obj, path, index, typeStack) {
37
+ return traverseArray(obj, path, index, typeStack);
38
+ }
39
+
40
+ function traverseArray(obj, path, index, typeStack) {
41
+ if(!Array.isArray(obj)) return typeStack;
42
+ const name = path[index];
43
+ const element = obj[name];
44
+ return traverseTyped(element, path, index+1, typeStack);
45
+ }
46
+
47
+ function traverseDict(obj, path, index, typeStack) {
48
+ if(typeof obj !== 'object') return typeStack;
49
+ const name = path[index];
50
+ if(name === undefined) return typeStack;
51
+ return traverseTyped(obj[name], path, index+1, typeStack);
52
+ }
53
+
54
+ function traverseDictArray(obj, path, index, typeStack) {
55
+ if(typeof obj !== 'object') return typeStack;
56
+ const name = path[index];
57
+ if(name === undefined) return typeStack;
58
+ return traverseArray(obj[name], path, index+1, typeStack);
59
+ }
60
+
61
+ function traverseTyped(obj, path, index, typeStack) {
62
+ if(!obj) return typeStack;
63
+ const name = path[index];
64
+ if(name === undefined) return typeStack;
65
+ if(name[0] === '@') return typeStack; // skip annotations
66
+ const func = structuralNodeHandlers[name];
67
+ if(func) return func(obj[name], path, index+1, typeStack.concat(name));
68
+ // not typed -> columns?
69
+ if(typeStack[typeStack.length-1] === 'columns')
70
+ return traverseDictArray(obj, path, index, typeStack);
71
+ return typeStack;
72
+ }
73
+
74
+ module.exports = {
75
+ structuralPath
76
+ }
@@ -74,17 +74,22 @@ function flattenDefinition(definition, definitionPath, csnUtils, options, refere
74
74
  if (definition.kind !== 'entity' && definition.kind !== 'view')
75
75
  return;
76
76
 
77
- let { newFlatElements } = flattenStructure(definition, definitionPath, csnUtils, options, error, referenceFlattener);
77
+ let { newFlatElements } = flattenStructure(definition.elements, definitionPath, csnUtils, options, error, referenceFlattener);
78
78
 
79
79
  attachPathOnPartialCSN(newFlatElements, definitionPath.concat('elements'));
80
-
81
80
  definition.elements = newFlatElements;
81
+
82
+ if (definition.params) {
83
+ let { newFlatElements } = flattenStructure(definition.params, definitionPath, csnUtils, options, error, referenceFlattener);
84
+ attachPathOnPartialCSN(newFlatElements, definitionPath.concat('params'));
85
+ definition.params = newFlatElements;
86
+ }
82
87
  } // flattenDefinition
83
88
 
84
89
  /**
85
90
  * Flattens structured element by calling element flattener for each structured child.
86
91
  * Returns a dictionary containing all the new elements for the given structure.
87
- * @param {*} struct the structure to flatten
92
+ * @param {*} dictionary to flatten
88
93
  * @param {CSN.Path} path the path of the structure in the CSN tree
89
94
  * @param {*} csnUtils
90
95
  * @param {Function} error Error message function
@@ -93,14 +98,14 @@ function flattenDefinition(definition, definitionPath, csnUtils, options, refere
93
98
  * @param {*} [newFlatElements]
94
99
  * @param {boolean} [isTopLevelElement] states if this is a top level element
95
100
  */
96
- function flattenStructure(struct, path, csnUtils, options, error, referenceFlattener = undefined, elementPathInStructure = [],
101
+ function flattenStructure(dictionary, path, csnUtils, options, error, referenceFlattener = undefined, elementPathInStructure = [],
97
102
  newFlatElements = Object.create(null), isTopLevelElement = true, isParentNotNull = false) {
98
103
 
99
- if (!isTopLevelElement) addPropsForPropagationFromElement(struct);
104
+ if (!isTopLevelElement) addPropsForPropagationFromElement(dictionary);
100
105
 
101
106
  let generatedNewFlatElementsNames = []; // holds the names of all new child elements of the structure
102
107
 
103
- struct.elements && Object.entries(struct.elements).forEach(([elementName, element]) => {
108
+ dictionary && Object.entries(dictionary).forEach(([elementName, element]) => {
104
109
  let currPath = path.concat('elements', elementName);
105
110
 
106
111
  if (isTopLevelElement) {
@@ -118,8 +123,8 @@ function flattenStructure(struct, path, csnUtils, options, error, referenceFlatt
118
123
  addPropsForPropagationFromElement(element);
119
124
 
120
125
  // if the child element is structured itself -> needs to be flattened
121
- const subStruct = element.elements ? element : csnUtils.getFinalBaseType(element.type);
122
- let result = flattenStructure(subStruct, currPath, csnUtils, options, error, referenceFlattener, elementPathInStructure.concat(elementName), newFlatElements, false, isNotNull());
126
+ const elements = element.elements || csnUtils.getFinalBaseType(element.type).elements;
127
+ let result = flattenStructure(elements, currPath, csnUtils, options, error, referenceFlattener, elementPathInStructure.concat(elementName), newFlatElements, false, isNotNull());
123
128
  generatedNewFlatElementsNames.push(...result.generatedNewFlatElementsNames); // accomulate names of produced elements
124
129
 
125
130
  } else { // when we do not need to flat, this is scalar or empty (cds-compiler#4337) -> needs to be registered in referenceFlattener
@@ -127,7 +132,6 @@ function flattenStructure(struct, path, csnUtils, options, error, referenceFlatt
127
132
  let elementNameWithDots = elementPathInStructure.concat(elementName).join('.');
128
133
  addNewElementToResult(element, newElementName, elementNameWithDots, currPath);
129
134
  }
130
-
131
135
  });
132
136
 
133
137
  if (referenceFlattener) {
@@ -135,7 +139,6 @@ function flattenStructure(struct, path, csnUtils, options, error, referenceFlatt
135
139
  }
136
140
  return { newFlatElements, generatedNewFlatElementsNames };
137
141
 
138
-
139
142
  // adds newly created element into the final dictionary of elements
140
143
  function addNewElementToResult(element, elementName, elementNameWithDots, path) {
141
144
  if (newFlatElements[elementName]) {
@@ -25,7 +25,6 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
25
25
  // collect in this variable all the newly exposed types
26
26
  const exposedStructTypes = [];
27
27
  const schemas = Object.create(null);
28
-
29
28
  // walk through the definitions of the given CSN and expose types where needed
30
29
  forEachDefinition(csn, (def, defName, propertyName, path) => {
31
30
  // we do expose types only for definition from inside services
@@ -33,11 +32,11 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
33
32
  if (serviceName) {
34
33
  if (['type', 'entity', 'view'].includes(def.kind)) {
35
34
  forEachMember(def, (element, elementName, propertyName, path) => {
36
- if (propertyName === 'elements' || propertyName === 'params') {
35
+ if (['elements', 'params'].includes(propertyName)) {
37
36
  const artificialtName = `${isMultiSchema ?
38
37
  defNameWithoutServiceOrContextName(defName, serviceName)
39
38
  : defNameWithoutServiceOrContextName(defName, serviceName).replace(/\./g, '_')}_${elementName}`;
40
- exposeTypeOf(element, elementName, defName, serviceName, artificialtName, path);
39
+ exposeTypeOf(element, element.key || propertyName === 'params', elementName, defName, serviceName, artificialtName, path);
41
40
  }
42
41
  }, path);
43
42
  }
@@ -64,11 +63,11 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
64
63
  * @param {string} artificialName
65
64
  * @param {CSN.Path} path
66
65
  */
67
- function exposeTypeOf(node, memberName, defName, service, artificialName, path) {
66
+ function exposeTypeOf(node, isKey, memberName, defName, service, artificialName, path) {
68
67
  if (isArrayed(node))
69
- exposeArrayOfTypeOf(node, memberName, defName, service, artificialName, path);
68
+ exposeArrayOfTypeOf(node, isKey, memberName, defName, service, artificialName, path);
70
69
  else if (csnUtils.isStructured(node))
71
- exposeStructTypeOf(node, memberName, defName, service, artificialName, path);
70
+ exposeStructTypeOf(node, isKey, memberName, defName, service, artificialName, path);
72
71
  }
73
72
 
74
73
  /**
@@ -90,12 +89,12 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
90
89
  function exposeTypesOfAction(action, actionName, defName, service, path) {
91
90
  if (action.returns) {
92
91
  const artificialName = `return_${actionName.replace(/\./g, '_')}`;
93
- exposeTypeOf(action.returns, actionName, defName, service, artificialName, path.concat(['returns']));
92
+ exposeTypeOf(action.returns, false, actionName, defName, service, artificialName, path.concat(['returns']));
94
93
  }
95
94
 
96
95
  action.params && Object.entries(action.params).forEach(([paramName, param]) => {
97
96
  const artificialName = `param_${actionName.replace(/\./g, '_')}_${paramName}`;
98
- exposeTypeOf(param, actionName, defName, service, artificialName, path.concat(['params', paramName]));
97
+ exposeTypeOf(param, false, actionName, defName, service, artificialName, path.concat(['params', paramName]));
99
98
  });
100
99
  }
101
100
 
@@ -108,9 +107,11 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
108
107
  * @param {String} service
109
108
  * @param {String} artificialName
110
109
  */
111
- function exposeStructTypeOf(node, memberName, defName, service, artificialName, path, parentName) {
110
+ function exposeStructTypeOf(node, isKey, memberName, defName, service, artificialName, path, parentName) {
111
+ if (node.items) exposeStructTypeOf(node.items, isKey, memberName, defName, service, artificialName, path);
112
112
 
113
- if (node.items) exposeStructTypeOf(node.items, memberName, defName, service, artificialName, path);
113
+ // start conservative, assume we're in a named type
114
+ let isAnonymous = false;
114
115
 
115
116
  if (isExposableStructure(node)) {
116
117
  let typeDef = node.type ? csnUtils.getCsnDef(node.type) : /* structure|anonymous type */ node;
@@ -124,6 +125,11 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
124
125
  // and the new target. Consequently, we now have both type and elements properties in this case, and the elements should be taken as a priority
125
126
  // as the correct target is there and no longer in the type definition
126
127
  let newTypeElements = (node.type && node.elements) ? node.elements : typeDef.elements;
128
+ // if node and typeDef are identical, we're anonymous
129
+ isAnonymous = node === typeDef;
130
+ // if we've left the anonymous world, we're no longer in a key def
131
+ if (!isAnonymous)
132
+ isKey = false;
127
133
 
128
134
  let newType = exposeStructType(newTypeFullName, newTypeElements, memberName, path);
129
135
  if (!newType) {
@@ -138,6 +144,7 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
138
144
  newType.elements && Object.entries(newType.elements).forEach(([elemName, newElem]) => {
139
145
  if (node.elements && node.elements[elemName].$location) setProp(newElem, '$location', node.elements[elemName].$location);
140
146
  exposeStructTypeOf(newElem,
147
+ isKey,
141
148
  memberName,
142
149
  typeDef.kind === 'type' ? node.type : defName,
143
150
  service,
@@ -251,6 +258,9 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
251
258
  error(null, path, `"${elemName}": Element name conflicts with existing element`);
252
259
  }
253
260
  let cloned = cloneCsn(element, options);
261
+ // if this was an anonymous sub element of a key, mark it as not nullable
262
+ if(isAnonymous && isKey && !cloned.key && cloned.notNull === undefined)
263
+ cloned.notNull = true;
254
264
  type.elements[elemName] = cloned;
255
265
  });
256
266
 
@@ -265,12 +275,12 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
265
275
 
266
276
  // If a member is of type "array of <named type|anonymous type>", we expose the arrayed type,
267
277
  // like we expose structures in structured mode
268
- function exposeArrayOfTypeOf(node, memberName, defName, service, artificialName, path) {
278
+ function exposeArrayOfTypeOf(node, isKey, memberName, defName, service, artificialName, path) {
269
279
  // if anonymously defined in place -> we always expose the type
270
280
  // this would be definition like 'elem: array of { ... }'
271
281
  // and we use the artificial name for the new type name
272
282
  if (node.items && !node.type) {
273
- exposeStructTypeOf(node.items, memberName, defName, service, artificialName, path.concat('items'));
283
+ exposeStructTypeOf(node.items, isKey, memberName, defName, service, artificialName, path.concat('items'));
274
284
  }
275
285
  // we can have both of the 'type' and 'items' in the cases:
276
286
  // 1. 'elem: Foo' and 'type Foo: array of Baz' and 'type Baz: { ... }'
@@ -61,6 +61,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
61
61
  recurseElements,
62
62
  renameAnnotation,
63
63
  setAnnotation,
64
+ resetAnnotation,
64
65
  expandStructsInExpression,
65
66
  };
66
67
 
@@ -378,9 +379,10 @@ function getTransformers(model, options, pathDelimiter = '_') {
378
379
  *
379
380
  * @param {CSN.Artifact} node
380
381
  * @param {WeakMap} [resolved] WeakMap containing already resolved refs
382
+ * @param {boolean} [keepLocalized=false] Wether to clone .localized from a type def
381
383
  * @returns {void}
382
384
  */
383
- function toFinalBaseType(node, resolved) {
385
+ function toFinalBaseType(node, resolved, keepLocalized=false) {
384
386
  // Nothing to do if no type (or if array/struct type)
385
387
  if (!node || !node.type) return;
386
388
  // In case of a ref -> Follow the ref
@@ -440,6 +442,8 @@ function getTransformers(model, options, pathDelimiter = '_') {
440
442
  Object.assign(node, { scale: typeDef.scale });
441
443
  if (node.srid === undefined && typeDef.srid !== undefined)
442
444
  Object.assign(node, { srid: typeDef.srid });
445
+ if (keepLocalized && node.localized === undefined && typeDef.localized !== undefined)
446
+ Object.assign(node, { localized: typeDef.localized });
443
447
  node.type = typeDef.type;
444
448
  toFinalBaseType(node);
445
449
  }
@@ -920,6 +924,34 @@ function getTransformers(model, options, pathDelimiter = '_') {
920
924
  node[name] = value;
921
925
  }
922
926
 
927
+ /**
928
+ * Assigns unconditionally annotation to a node, which means it overwrites already existing annotation assignment.
929
+ * Overwritting is when the assignment differs from undefined and null, also when differs from the already set value.
930
+ * Setting new assignment results false as return value and overwriting - true.
931
+ *
932
+ * @param {object} node Assignee
933
+ * @param {string} name Annotation name
934
+ * @param {any} value Annotation value
935
+ * @param {function} info function that reports info messages
936
+ * @param {CSN.Path} path location of the warning
937
+ * @returns {boolean} wasOverwritten true when the annotation was overwritten
938
+ */
939
+ function resetAnnotation(node, name, value, info, path) {
940
+ if (!name.startsWith('@')) {
941
+ throw Error('Annotation name should start with "@": ' + name);
942
+ }
943
+ if (value === undefined) {
944
+ throw Error('Annotation value must not be undefined');
945
+ }
946
+
947
+ const wasOverwritten = node[name] !== undefined && node[name] !== null && node[name] !== value;
948
+ const oldValue = node[name];
949
+ node[name] = value;
950
+ if(wasOverwritten)
951
+ info(null, path, { anno: name, prop: value, otherprop: oldValue },
952
+ `Value $(OTHERPROP) of annotation $(ANNO) is overwritten with new value $(PROP)`);
953
+ return wasOverwritten;
954
+ }
923
955
 
924
956
  /*
925
957
  Resolve the type of an artifact
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { setProp, forEachGeneric, forEachDefinition, isBetaEnabled } = require('../base/model');
4
- var { handleMessages, makeMessageFunction } = require('../base/messages');
4
+ var { makeMessageFunction } = require('../base/messages');
5
5
  const { recompileX } = require('../compiler/index');
6
6
  var { linkToOrigin } = require('../compiler/shared');
7
7
  const {compactModel, compactExpr} = require('../json/to-csn');
@@ -14,7 +14,9 @@ const internalArtifactKinds = ['builtin'/*, '$parameters'*/, 'param'];
14
14
  function translateAssocsToJoinsCSN(csn, options){
15
15
  timetrace.start('Recompiling model');
16
16
  // Do not re-complain about localized
17
- const model = recompileX(csn, { ...options, $skipNameCheck: true });
17
+ const compileOptions = { ...options, $skipNameCheck: true };
18
+ delete compileOptions.csnFlavor;
19
+ const model = recompileX(csn, compileOptions);
18
20
  timetrace.stop();
19
21
  timetrace.start('Translating associations to joins');
20
22
  translateAssocsToJoins(model, options);
@@ -42,9 +44,9 @@ function translateAssocsToJoinsCSN(csn, options){
42
44
  }
43
45
 
44
46
  // If A2J reports error - end! Continuing with a broken CSN makes no sense
45
- handleMessages(model, options);
47
+ makeMessageFunction(model, options).throwWithError();
46
48
  // FIXME: Move this somewhere more appropriate
47
- const compact = compactModel(model, options);
49
+ const compact = compactModel(model, compileOptions);
48
50
  return compact;
49
51
  }
50
52
 
@@ -0,0 +1,67 @@
1
+ 'use strict';
2
+
3
+ const { forEachDefinition } = require('../base/model');
4
+ const {
5
+ applyTransformations,
6
+ cloneCsn,
7
+ getUtils,
8
+ isBuiltinType,
9
+ } = require('../model/csnUtils');
10
+
11
+ /**
12
+ * Loop through a universal CSN and enrich it with the properties
13
+ * from the source definition - modifies the input model in-place
14
+ *
15
+ * @param {CSN.Model} csn
16
+ * @param {CSN.Options} options
17
+ */
18
+ module.exports = function(csn, options) {
19
+ let { getOrigin, getFinalType, getFinalTypeDef } = getUtils(csn);
20
+ // User-defined structured types do not have the elements propagated any longer
21
+ // if there is no association among the elements. For that reason,
22
+ // as a first step propagate the elements of these
23
+ forEachDefinition(csn, (def) => {
24
+ if (def.kind === 'type' && def.type && !def.elements) {
25
+ const finalType = getFinalType(def.type);
26
+ if (isBuiltinType(finalType)) return;
27
+ const finalTypeDef = getFinalTypeDef(def.type);
28
+ if (finalTypeDef.elements)
29
+ def.elements = cloneCsn(finalTypeDef.elements, options);
30
+ }
31
+ });
32
+
33
+ // as a second step, loop through all the $origin properties in the model
34
+ // and propagate the properties from the origin definition
35
+ applyTransformations(csn, {
36
+ '$origin': (node, _$orign, $originValue, _path, parent, propName) => {
37
+ if (!node.kind) { // we do not want to replace whole definitions
38
+ if (Array.isArray($originValue))
39
+ propagatePropsFromOrigin(node, propName, parent);
40
+ else if ($originValue.$origin && Array.isArray($originValue.$origin)) {
41
+ // cover the case of query entity elements where we have own and ihnerited attributes/annotations
42
+ propagatePropsFromOrigin($originValue, propName, parent);
43
+ }
44
+
45
+ }
46
+ }
47
+ }, undefined, undefined, options);
48
+
49
+ function propagatePropsFromOrigin(member, memberName, construct) {
50
+ // TODO: shall the $origin be kept as part of the element?
51
+ const origin = getOrigin(member);
52
+ if (origin.kind) return;
53
+ if (member.elements && origin.type) {
54
+ delete member.$origin;
55
+ member.type = origin.type;
56
+ return;
57
+ }
58
+ let newMember = cloneCsn(origin, options);
59
+ // keep targets and keys of assoc, if it was redirected
60
+ if (origin.type === 'cds.Association') {
61
+ newMember.target = member.target || newMember.target;
62
+ newMember.keys = member.keys || newMember.keys;
63
+ }
64
+ // TODO: check if this works fine for items/returns/actions
65
+ construct[memberName] = newMember;
66
+ }
67
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds-compiler",
3
- "version": "2.7.0",
3
+ "version": "2.10.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)",