@sap/cds-compiler 2.4.4 → 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 (106) hide show
  1. package/CHANGELOG.md +241 -1
  2. package/bin/.eslintrc.json +17 -0
  3. package/bin/cds_update_identifiers.js +8 -7
  4. package/bin/cdsc.js +180 -132
  5. package/bin/cdshi.js +18 -11
  6. package/bin/cdsse.js +38 -32
  7. package/bin/cdsv2m.js +8 -7
  8. package/doc/CHANGELOG_BETA.md +36 -1
  9. package/lib/api/main.js +81 -100
  10. package/lib/api/options.js +17 -11
  11. package/lib/api/validate.js +12 -8
  12. package/lib/backends.js +0 -81
  13. package/lib/base/keywords.js +32 -2
  14. package/lib/base/location.js +2 -2
  15. package/lib/base/message-registry.js +66 -4
  16. package/lib/base/messages.js +84 -27
  17. package/lib/base/model.js +2 -61
  18. package/lib/checks/arrayOfs.js +0 -1
  19. package/lib/checks/defaultValues.js +27 -2
  20. package/lib/checks/elements.js +1 -6
  21. package/lib/checks/enricher.js +8 -2
  22. package/lib/checks/foreignKeys.js +0 -6
  23. package/lib/checks/managedWithoutKeys.js +17 -0
  24. package/lib/checks/nonexpandableStructured.js +38 -0
  25. package/lib/checks/onConditions.js +9 -45
  26. package/lib/checks/queryNoDbArtifacts.js +27 -9
  27. package/lib/checks/selectItems.js +25 -2
  28. package/lib/checks/types.js +26 -2
  29. package/lib/checks/unknownMagic.js +38 -0
  30. package/lib/checks/utils.js +61 -0
  31. package/lib/checks/validator.js +66 -13
  32. package/lib/compiler/assert-consistency.js +24 -12
  33. package/lib/compiler/builtins.js +2 -0
  34. package/lib/compiler/checks.js +6 -4
  35. package/lib/compiler/definer.js +101 -39
  36. package/lib/compiler/index.js +88 -59
  37. package/lib/compiler/resolver.js +455 -209
  38. package/lib/compiler/shared.js +57 -33
  39. package/lib/edm/annotations/genericTranslation.js +183 -187
  40. package/lib/edm/csn2edm.js +128 -99
  41. package/lib/edm/edm.js +18 -21
  42. package/lib/edm/edmPreprocessor.js +361 -127
  43. package/lib/edm/edmUtils.js +103 -33
  44. package/lib/gen/Dictionary.json +74 -28
  45. package/lib/gen/language.checksum +1 -1
  46. package/lib/gen/language.interp +18 -4
  47. package/lib/gen/language.tokens +124 -118
  48. package/lib/gen/languageLexer.interp +13 -1
  49. package/lib/gen/languageLexer.js +870 -839
  50. package/lib/gen/languageLexer.tokens +116 -111
  51. package/lib/gen/languageParser.js +5894 -5614
  52. package/lib/json/from-csn.js +152 -67
  53. package/lib/json/to-csn.js +334 -135
  54. package/lib/language/antlrParser.js +4 -3
  55. package/lib/language/errorStrategy.js +1 -0
  56. package/lib/language/genericAntlrParser.js +24 -14
  57. package/lib/language/language.g4 +188 -128
  58. package/lib/main.d.ts +435 -0
  59. package/lib/main.js +31 -7
  60. package/lib/model/api.js +78 -0
  61. package/lib/model/csnRefs.js +463 -187
  62. package/lib/model/csnUtils.js +280 -136
  63. package/lib/model/enrichCsn.js +75 -4
  64. package/lib/model/revealInternalProperties.js +2 -1
  65. package/lib/modelCompare/compare.js +70 -25
  66. package/lib/optionProcessor.js +13 -10
  67. package/lib/render/.eslintrc.json +4 -1
  68. package/lib/render/DuplicateChecker.js +8 -5
  69. package/lib/render/toCdl.js +123 -40
  70. package/lib/render/toHdbcds.js +156 -65
  71. package/lib/render/toSql.js +87 -11
  72. package/lib/render/utils/common.js +55 -9
  73. package/lib/render/utils/sql.js +3 -3
  74. package/lib/sql-identifier.js +6 -1
  75. package/lib/transform/{sql → db}/.eslintrc.json +0 -0
  76. package/lib/transform/{sql → db}/assertUnique.js +7 -8
  77. package/lib/transform/{sql → db}/constraints.js +35 -20
  78. package/lib/transform/db/draft.js +353 -0
  79. package/lib/transform/db/expansion.js +582 -0
  80. package/lib/transform/db/flattening.js +325 -0
  81. package/lib/transform/{sql → db}/groupByOrderBy.js +8 -16
  82. package/lib/transform/{sql → db}/helpers.js +0 -0
  83. package/lib/transform/{sql → db}/transformExists.js +256 -60
  84. package/lib/transform/forHanaNew.js +216 -765
  85. package/lib/transform/forOdataNew.js +60 -56
  86. package/lib/transform/localized.js +48 -26
  87. package/lib/transform/odata/attachPath.js +19 -4
  88. package/lib/transform/odata/expandStructKeysInAssociations.js +2 -2
  89. package/lib/transform/odata/generateForeignKeyElements.js +13 -12
  90. package/lib/transform/odata/referenceFlattener.js +60 -36
  91. package/lib/transform/odata/sortByAssociationDependency.js +4 -4
  92. package/lib/transform/odata/structuralPath.js +76 -0
  93. package/lib/transform/odata/structureFlattener.js +21 -22
  94. package/lib/transform/odata/toFinalBaseType.js +5 -5
  95. package/lib/transform/odata/typesExposure.js +27 -17
  96. package/lib/transform/odata/utils.js +2 -2
  97. package/lib/transform/transformUtilsNew.js +141 -77
  98. package/lib/transform/translateAssocsToJoins.js +17 -14
  99. package/lib/transform/universalCsnEnricher.js +67 -0
  100. package/lib/utils/file.js +0 -11
  101. package/lib/utils/moduleResolve.js +6 -8
  102. package/lib/utils/timetrace.js +6 -1
  103. package/package.json +2 -1
  104. package/lib/base/deepCopy.js +0 -66
  105. package/lib/json/walker.js +0 -26
  106. package/lib/utils/string.js +0 -17
@@ -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:
@@ -38,6 +39,7 @@ class ReferenceFlattener {
38
39
  this.structuredReference = {};
39
40
  this.generatedElementsForPath = {};
40
41
  this.elementTransitions = {};
42
+ this.elementNamesWithDots = {};
41
43
  }
42
44
 
43
45
  /**
@@ -72,8 +74,7 @@ class ReferenceFlattener {
72
74
  });
73
75
  if (paths)
74
76
  setProp(node, '$paths', paths);
75
- setProp(node, '$scope', resolved.scope);
76
- // cache also if structured or not
77
+ // cache if structured or not
77
78
  let structured = resolved.links.map(link => link.art ? (isStructured(link.art)) : undefined);
78
79
  this.structuredReference[path.join('/')] = structured;
79
80
  });
@@ -157,6 +158,14 @@ class ReferenceFlattener {
157
158
  return this.generatedElementsForPath[path.join('/')];
158
159
  }
159
160
 
161
+ setElementNameWithDots(path, elementNameWithDots) {
162
+ this.elementNamesWithDots[path.join('/')] = elementNameWithDots;
163
+ }
164
+
165
+ getElementNameWithDots(path) {
166
+ return this.elementNamesWithDots[path.join('/')];
167
+ }
168
+
160
169
  /**
161
170
  * Generic reference flattener as {@link ReferenceFlattener described}.
162
171
  *
@@ -167,35 +176,40 @@ class ReferenceFlattener {
167
176
  if (node.$paths) {
168
177
  let newRef = []; // flattened reference
169
178
  let flattenWithPrevious = false;
170
- 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
171
180
  node.$paths.forEach((path, i) => {
172
181
  if (path === undefined || !ref[i]) return;
173
- let spath = path.join('/')
182
+ let spath = path.join('/');
174
183
  let movedTo = this.elementTransitions[spath]; // detect element transition
175
184
  let flattened = this.flattenedElementPaths[spath];
176
185
  if (flattenWithPrevious) {
177
- newRef[newRef.length - 1] = newRef[newRef.length - 1] + '_' + ref[i];
178
- anythingFlattened = true;
186
+ newRef[newRef.length - 1] = newRef[newRef.length - 1] + '_' + (ref[i].id || ref[i]);
187
+ // if we have a filter or args in an assoc, it needs to be kept, therefore
188
+ // the id of the ref is updated with the flattened version
189
+ if (ref[i].id) {
190
+ ref[i].id = newRef[newRef.length - 1];
191
+ newRef[newRef.length - 1] = ref[i];
192
+ }
193
+ lastFlattenedID = i;
179
194
  } else if(movedTo && i===0) { // handle local scope reference which is transitioned - replace first item in reference
180
195
  let movedToElementName = movedTo[movedTo.length-1];
181
196
  newRef.push(movedToElementName);
182
- anythingFlattened=true;
197
+ lastFlattenedID = i;
183
198
  } else {
184
199
  newRef.push(ref[i]);
185
200
  }
186
201
  flattenWithPrevious = flattened;
187
202
  });
188
- 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
189
204
  // check if this is a column and add alias if missing
190
- if (isColumnInSelect(ref.$path)) {
191
- 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
192
208
  node.as = node.ref[node.ref.length - 1];
193
- }
194
209
  }
195
210
  setProp(newRef, '$path', path.concat('ref'));
196
211
  if (!node.as) {
197
- // FIXME: rather use scope from inspectRef to determin if path leads to a key
198
- if (ifKeysScope(path))
212
+ if (isPartOfKeysStructure(structural))
199
213
  node.as = implicitAs(node.ref);
200
214
  else
201
215
  setProp(node, 'as', implicitAs(node.ref))
@@ -208,10 +222,11 @@ class ReferenceFlattener {
208
222
 
209
223
  applyAliasesInOnCond(csn, inspectRef) {
210
224
  forEachRef(csn, (ref, node, path) => {
211
- // is there a better way to detect if we are dealing with on-cond?
212
- if (!['on', '$self', null].includes(node.$scope)) return;
213
-
225
+ // Process only on-conditions of associations
226
+ let structural = structuralPath(csn, path);
227
+ if(!isOnCondition(structural)) return;
214
228
  let { links } = inspectRef(path);
229
+ if(!links) return; // $user not resolvable
215
230
  let keysOfPreviousStepWhenManagedAssoc = undefined;
216
231
 
217
232
  let aliasedRef = [...ref];
@@ -235,32 +250,41 @@ class ReferenceFlattener {
235
250
  }
236
251
  }
237
252
 
238
- // FIXME: rather use scope from inspectRef to determin if path leads to a key
239
- function ifKeysScope(path) {
240
- if (!path) return false;
241
- if (Number.isInteger(path[path.length - 1]) && path[path.length - 2] === 'keys' && path[path.length - 4] === 'elements')
242
- return true;
243
- 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';
244
263
  }
245
264
 
246
265
  /**
247
- * Checks if the provided path is a column inside a select node
248
- * 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'.
249
269
  *
250
- * @param {CSN.Path} path Path to explore
270
+ * @param structural structural path to explore
251
271
  * @returns {boolean} True if the provided path matched the requirements to be a select node.
252
272
  */
253
- function isColumnInSelect(path) {
254
- if (!path)
255
- return false;
256
- const len = path.length;
257
- if (path[len - 4] === 'SELECT' && path[len - 3] === 'columns') {
258
- // check if -2 element is an integer / array index
259
- const arrayIndex = parseInt(String(path[len - 2]), 10);
260
- if (!isNaN(arrayIndex))
261
- return true;
262
- }
263
- 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';
264
288
  }
265
289
 
266
290
  module.exports = ReferenceFlattener;
@@ -18,13 +18,13 @@ const {
18
18
  } = require('./utils');
19
19
  const { forEach } = require('../../utils/objectUtils.js');
20
20
 
21
- function buildDependenciesFor(csn, referenceFlattener) {
21
+ function buildDependenciesFor(csn, referenceFlattener, isExternalServiceMember) {
22
22
 
23
23
  let dependencies = {};
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,10 +44,10 @@ function buildDependenciesFor(csn, referenceFlattener) {
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
- }) // forEachDefinition
50
+ }, { skipArtifact: isExternalServiceMember }) // forEachDefinition
51
51
 
52
52
  let result = []; // final list of sorted association items
53
53
  let inResult = {}; // paths of associations which were added in the result
@@ -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
+ }
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
3
  const { copyAnnotations } = require('../../model/csnUtils');
4
- const { setProp } = require('../../base/model');
5
4
  const { cloneCsn, forEachDefinition } = require('../../model/csnUtils');
6
5
  const { attachPathOnPartialCSN } = require('./attachPath');
7
6
 
@@ -50,18 +49,16 @@ const { isNotNull, setNotNull, setUpNotNull } = function () {
50
49
  * During the OData transformations in flat-mode, all structured elements will be flattened.
51
50
  * This module performs the complete flattening.
52
51
  * It also provides information to the reference flattener: elements produced for specific path in the CSN structure.
53
- * Each generated element gets hidden attributes:
54
- * - $viaTransform - states that the element was generated during transformation
55
- * - _flatElementNameWithDots - names in the element path concatenated with dot
56
52
  * @param {CSN.Model} csn CSN-object to flatten
57
53
  * @param {*} csnUtils instances of utility functions
58
54
  * @param {*} options
59
55
  * @param {*} referenceFlattener
60
56
  * @param {Function} error
57
+ * @param {Function} isExternalServiceMember returns true for an artifact that is part of an external service
61
58
  */
62
- function flattenCSN(csn, csnUtils, options, referenceFlattener, error) {
59
+ function flattenCSN(csn, csnUtils, options, referenceFlattener, error, isExternalServiceMember) {
63
60
  forEachDefinition(csn, (def, defName, propertyName, path) =>
64
- flattenDefinition(def, path, csnUtils, options, referenceFlattener, error));
61
+ flattenDefinition(def, path, csnUtils, options, referenceFlattener, error), { skipArtifact: isExternalServiceMember });
65
62
  }
66
63
 
67
64
  /**
@@ -77,17 +74,22 @@ function flattenDefinition(definition, definitionPath, csnUtils, options, refere
77
74
  if (definition.kind !== 'entity' && definition.kind !== 'view')
78
75
  return;
79
76
 
80
- let { newFlatElements } = flattenStructure(definition, definitionPath, csnUtils, options, error, referenceFlattener);
77
+ let { newFlatElements } = flattenStructure(definition.elements, definitionPath, csnUtils, options, error, referenceFlattener);
81
78
 
82
79
  attachPathOnPartialCSN(newFlatElements, definitionPath.concat('elements'));
83
-
84
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
+ }
85
87
  } // flattenDefinition
86
88
 
87
89
  /**
88
90
  * Flattens structured element by calling element flattener for each structured child.
89
91
  * Returns a dictionary containing all the new elements for the given structure.
90
- * @param {*} struct the structure to flatten
92
+ * @param {*} dictionary to flatten
91
93
  * @param {CSN.Path} path the path of the structure in the CSN tree
92
94
  * @param {*} csnUtils
93
95
  * @param {Function} error Error message function
@@ -96,14 +98,14 @@ function flattenDefinition(definition, definitionPath, csnUtils, options, refere
96
98
  * @param {*} [newFlatElements]
97
99
  * @param {boolean} [isTopLevelElement] states if this is a top level element
98
100
  */
99
- function flattenStructure(struct, path, csnUtils, options, error, referenceFlattener = undefined, elementPathInStructure = [],
101
+ function flattenStructure(dictionary, path, csnUtils, options, error, referenceFlattener = undefined, elementPathInStructure = [],
100
102
  newFlatElements = Object.create(null), isTopLevelElement = true, isParentNotNull = false) {
101
103
 
102
- if (!isTopLevelElement) addPropsForPropagationFromElement(struct);
104
+ if (!isTopLevelElement) addPropsForPropagationFromElement(dictionary);
103
105
 
104
106
  let generatedNewFlatElementsNames = []; // holds the names of all new child elements of the structure
105
107
 
106
- struct.elements && Object.entries(struct.elements).forEach(([elementName, element]) => {
108
+ dictionary && Object.entries(dictionary).forEach(([elementName, element]) => {
107
109
  let currPath = path.concat('elements', elementName);
108
110
 
109
111
  if (isTopLevelElement) {
@@ -121,8 +123,8 @@ function flattenStructure(struct, path, csnUtils, options, error, referenceFlatt
121
123
  addPropsForPropagationFromElement(element);
122
124
 
123
125
  // if the child element is structured itself -> needs to be flattened
124
- const subStruct = element.elements ? element : csnUtils.getFinalBaseType(element.type);
125
- 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());
126
128
  generatedNewFlatElementsNames.push(...result.generatedNewFlatElementsNames); // accomulate names of produced elements
127
129
 
128
130
  } else { // when we do not need to flat, this is scalar or empty (cds-compiler#4337) -> needs to be registered in referenceFlattener
@@ -130,7 +132,6 @@ function flattenStructure(struct, path, csnUtils, options, error, referenceFlatt
130
132
  let elementNameWithDots = elementPathInStructure.concat(elementName).join('.');
131
133
  addNewElementToResult(element, newElementName, elementNameWithDots, currPath);
132
134
  }
133
-
134
135
  });
135
136
 
136
137
  if (referenceFlattener) {
@@ -138,31 +139,29 @@ function flattenStructure(struct, path, csnUtils, options, error, referenceFlatt
138
139
  }
139
140
  return { newFlatElements, generatedNewFlatElementsNames };
140
141
 
141
-
142
142
  // adds newly created element into the final dictionary of elements
143
143
  function addNewElementToResult(element, elementName, elementNameWithDots, path) {
144
144
  if (newFlatElements[elementName]) {
145
145
  error(null, path, `Generated element ${elementName} conflicts with other generated element`);
146
146
  } else {
147
- let newElement = createNewElement(element, elementNameWithDots);
147
+ let newPath = path.slice(0, 2).concat('elements', elementName);
148
+ let newElement = createNewElement(element, elementNameWithDots, newPath);
148
149
  newFlatElements[elementName] = newElement;
149
150
  generatedNewFlatElementsNames.push(elementName);
150
151
 
151
152
  if (referenceFlattener) {
152
- let newPath = path.slice(0, 2).concat('elements', elementName);
153
153
  referenceFlattener.registerElementTransition(path, newPath);
154
154
  }
155
155
  }
156
156
  } // addNewElementToResult
157
157
 
158
158
  // creates new element by copying the properties of the originating element
159
- function createNewElement(element, elementNameWithDots) {
159
+ function createNewElement(element, elementNameWithDots, path) {
160
160
  let newElement = cloneCsn(element, options);
161
161
  if (!isTopLevelElement) propagatePropsToElement(newElement);
162
162
  if (isNotNull() === undefined) delete newElement.notNull;
163
- if (!isTopLevelElement) {
164
- setProp(newElement, '$viaTransform', true);
165
- setProp(newElement, '_flatElementNameWithDots', elementNameWithDots);
163
+ if (!isTopLevelElement && referenceFlattener) {
164
+ referenceFlattener.setElementNameWithDots(path, elementNameWithDots);
166
165
  }
167
166
  return newElement;
168
167
  } // createNewElement
@@ -1,12 +1,12 @@
1
1
  'use strict';
2
2
 
3
3
  const {
4
- cloneCsn, forEachDefinition,
5
- forEachMemberRecursively, isBuiltinType,
4
+ forEachDefinition, forEachMemberRecursively,
5
+ isBuiltinType, cloneCsnDictionary,
6
6
  } = require('../../model/csnUtils');
7
7
  const { isArtifactInSomeService, isArtifactInService } = require('./utils');
8
8
 
9
- function expandToFinalBaseType(csn, transformers, csnUtils, services, options) {
9
+ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, isExternalServiceMember) {
10
10
  const isV4 = options.toOdata.version === 'v4';
11
11
  forEachDefinition(csn, (def, defName) => {
12
12
  // Unravel derived type chains to final one for elements, actions, action parameters (propagating annotations)
@@ -60,7 +60,7 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options) {
60
60
  if (def.kind === 'type' && def.items && isArtifactInSomeService(defName, services)) {
61
61
  expandFirstLevelOfArrayed(def);
62
62
  }
63
- });
63
+ }, { skipArtifact: isExternalServiceMember });
64
64
 
65
65
  // In case we have in the model something like:
66
66
  // type Foo: array of Bar; type Bar: { qux: Integer };
@@ -70,7 +70,7 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options) {
70
70
  if (def.items.type && !isBuiltinType(def.items.type)) {
71
71
  let finalType = csnUtils.getFinalTypeDef(def.items.type);
72
72
  if (csnUtils.isStructured(finalType)) {
73
- def.items.elements = cloneCsn(finalType.elements, options);
73
+ def.items.elements = cloneCsnDictionary(finalType.elements, options);
74
74
  delete def.items.type;
75
75
  }
76
76
  }
@@ -8,7 +8,7 @@
8
8
 
9
9
  const { setProp } = require('../../base/model');
10
10
  const { defNameWithoutServiceOrContextName, isArtifactInService } = require('./utils');
11
- const { cloneCsn, isBuiltinType, forEachDefinition, forEachMember } = require('../../model/csnUtils');
11
+ const { cloneCsn, isBuiltinType, forEachDefinition, forEachMember, cloneCsnDictionary } = require('../../model/csnUtils');
12
12
  const { copyAnnotations } = require('../../model/csnUtils');
13
13
 
14
14
  /**
@@ -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,
@@ -167,7 +174,7 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
167
174
  /**
168
175
  * Calculate the new type name that will be exposed in multi schema,
169
176
  * in case that the element has a named type.
170
- *
177
+ *
171
178
  * @param {string} typeName type of the element
172
179
  * @param {string} service current service name
173
180
  */
@@ -196,7 +203,7 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
196
203
  /**
197
204
  * Calculate the new type name that will be exposed in multi schema,
198
205
  * in case that the element has an anonymous type.
199
- *
206
+ *
200
207
  * @param {string} typeName type of the element
201
208
  * @param {string} parentName name of the parent def holding the element
202
209
  */
@@ -212,7 +219,7 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
212
219
 
213
220
  /**
214
221
  * Tf does not exists, create a context with the given name in the CSN
215
- * @param {string} name
222
+ * @param {string} name
216
223
  */
217
224
  function createSchema(name) {
218
225
  schemas[`${name}`] = { kind: 'schema', name };
@@ -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: { ... }'
@@ -323,7 +333,7 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
323
333
  if (def.items.type && !isBuiltinType(def.items.type)) {
324
334
  let finalType = csnUtils.getFinalTypeDef(def.items.type);
325
335
  if (csnUtils.isStructured(finalType)) {
326
- if (!def.items.elements) def.items.elements = cloneCsn(finalType.elements, options);
336
+ if (!def.items.elements) def.items.elements = cloneCsnDictionary(finalType.elements, options);
327
337
  delete def.items.type;
328
338
  }
329
339
  }
@@ -22,7 +22,7 @@ function isManagedAssociationElement(artifact) {
22
22
  return artifact.target !== undefined && artifact.on === undefined;
23
23
  }
24
24
 
25
- function forEachManagedAssociation(csn, callback) {
25
+ function forEachManagedAssociation(csn, callback, isExternalServiceMember) {
26
26
 
27
27
  forEachDefinition(csn, (def) => {
28
28
  forEachMemberRecursively(def, (element) => {
@@ -30,7 +30,7 @@ function forEachManagedAssociation(csn, callback) {
30
30
  callback(element)
31
31
  }
32
32
  })
33
- })
33
+ }, { skipArtifact: isExternalServiceMember });
34
34
 
35
35
  }
36
36