@sap/cds-compiler 2.5.2 → 2.11.0

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 (102) hide show
  1. package/CHANGELOG.md +235 -9
  2. package/bin/cdsc.js +44 -27
  3. package/bin/cdsse.js +1 -0
  4. package/doc/CHANGELOG_BETA.md +37 -3
  5. package/lib/api/.eslintrc.json +2 -0
  6. package/lib/api/main.js +37 -123
  7. package/lib/api/options.js +27 -15
  8. package/lib/api/validate.js +34 -9
  9. package/lib/backends.js +9 -89
  10. package/lib/base/dictionaries.js +2 -1
  11. package/lib/base/keywords.js +32 -2
  12. package/lib/base/message-registry.js +73 -11
  13. package/lib/base/messages.js +86 -30
  14. package/lib/base/model.js +6 -6
  15. package/lib/base/optionProcessorHelper.js +56 -22
  16. package/lib/checks/defaultValues.js +27 -2
  17. package/lib/checks/elements.js +1 -6
  18. package/lib/checks/foreignKeys.js +0 -6
  19. package/lib/checks/managedWithoutKeys.js +17 -0
  20. package/lib/checks/nonexpandableStructured.js +38 -0
  21. package/lib/checks/onConditions.js +9 -45
  22. package/lib/checks/queryNoDbArtifacts.js +25 -7
  23. package/lib/checks/selectItems.js +29 -2
  24. package/lib/checks/types.js +26 -2
  25. package/lib/checks/unknownMagic.js +41 -0
  26. package/lib/checks/utils.js +61 -0
  27. package/lib/checks/validator.js +60 -7
  28. package/lib/compiler/assert-consistency.js +23 -7
  29. package/lib/compiler/base.js +65 -0
  30. package/lib/compiler/builtins.js +30 -1
  31. package/lib/compiler/checks.js +8 -5
  32. package/lib/compiler/definer.js +157 -133
  33. package/lib/compiler/index.js +89 -31
  34. package/lib/compiler/propagator.js +5 -2
  35. package/lib/compiler/resolver.js +375 -185
  36. package/lib/compiler/shared.js +49 -202
  37. package/lib/compiler/utils.js +173 -0
  38. package/lib/edm/annotations/genericTranslation.js +183 -187
  39. package/lib/edm/csn2edm.js +104 -108
  40. package/lib/edm/edm.js +18 -21
  41. package/lib/edm/edmPreprocessor.js +388 -146
  42. package/lib/edm/edmUtils.js +104 -34
  43. package/lib/gen/Dictionary.json +22 -0
  44. package/lib/gen/language.checksum +1 -1
  45. package/lib/gen/language.interp +28 -1
  46. package/lib/gen/language.tokens +79 -69
  47. package/lib/gen/languageLexer.interp +28 -1
  48. package/lib/gen/languageLexer.js +879 -805
  49. package/lib/gen/languageLexer.tokens +71 -62
  50. package/lib/gen/languageParser.js +5330 -4300
  51. package/lib/json/from-csn.js +110 -52
  52. package/lib/json/to-csn.js +434 -120
  53. package/lib/language/antlrParser.js +15 -3
  54. package/lib/language/errorStrategy.js +1 -0
  55. package/lib/language/genericAntlrParser.js +93 -26
  56. package/lib/language/language.g4 +172 -31
  57. package/lib/main.d.ts +216 -19
  58. package/lib/main.js +32 -7
  59. package/lib/model/api.js +78 -0
  60. package/lib/model/csnRefs.js +413 -149
  61. package/lib/model/csnUtils.js +286 -75
  62. package/lib/model/enrichCsn.js +50 -6
  63. package/lib/model/revealInternalProperties.js +22 -5
  64. package/lib/modelCompare/compare.js +39 -21
  65. package/lib/optionProcessor.js +35 -18
  66. package/lib/render/.eslintrc.json +4 -1
  67. package/lib/render/DuplicateChecker.js +9 -6
  68. package/lib/render/toCdl.js +121 -36
  69. package/lib/render/toHdbcds.js +148 -98
  70. package/lib/render/toSql.js +114 -43
  71. package/lib/render/utils/common.js +8 -13
  72. package/lib/render/utils/sql.js +3 -3
  73. package/lib/sql-identifier.js +6 -1
  74. package/lib/transform/db/assertUnique.js +5 -6
  75. package/lib/transform/db/constraints.js +281 -106
  76. package/lib/transform/db/draft.js +11 -8
  77. package/lib/transform/db/expansion.js +584 -0
  78. package/lib/transform/db/flattening.js +341 -0
  79. package/lib/transform/db/groupByOrderBy.js +2 -2
  80. package/lib/transform/db/transformExists.js +345 -65
  81. package/lib/transform/db/views.js +438 -0
  82. package/lib/transform/forHanaNew.js +131 -793
  83. package/lib/transform/forOdataNew.js +30 -24
  84. package/lib/transform/localized.js +39 -10
  85. package/lib/transform/odata/attachPath.js +19 -4
  86. package/lib/transform/odata/generateForeignKeyElements.js +11 -10
  87. package/lib/transform/odata/referenceFlattener.js +60 -39
  88. package/lib/transform/odata/sortByAssociationDependency.js +2 -2
  89. package/lib/transform/odata/structuralPath.js +72 -0
  90. package/lib/transform/odata/structureFlattener.js +19 -18
  91. package/lib/transform/odata/typesExposure.js +22 -12
  92. package/lib/transform/transformUtilsNew.js +144 -78
  93. package/lib/transform/translateAssocsToJoins.js +22 -27
  94. package/lib/transform/universalCsnEnricher.js +67 -0
  95. package/lib/utils/file.js +5 -14
  96. package/lib/utils/moduleResolve.js +6 -8
  97. package/lib/utils/term.js +65 -42
  98. package/lib/utils/timetrace.js +48 -26
  99. package/package.json +1 -1
  100. package/lib/json/walker.js +0 -26
  101. package/lib/transform/sqlite +0 -0
  102. package/lib/utils/string.js +0 -17
@@ -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,13 +49,12 @@ 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
- * - _flatElementNameWithDots - names in the element path concatenated with dot
55
52
  * @param {CSN.Model} csn CSN-object to flatten
56
53
  * @param {*} csnUtils instances of utility functions
57
54
  * @param {*} options
58
55
  * @param {*} referenceFlattener
59
56
  * @param {Function} error
57
+ * @param {Function} isExternalServiceMember returns true for an artifact that is part of an external service
60
58
  */
61
59
  function flattenCSN(csn, csnUtils, options, referenceFlattener, error, isExternalServiceMember) {
62
60
  forEachDefinition(csn, (def, defName, propertyName, path) =>
@@ -76,17 +74,22 @@ function flattenDefinition(definition, definitionPath, csnUtils, options, refere
76
74
  if (definition.kind !== 'entity' && definition.kind !== 'view')
77
75
  return;
78
76
 
79
- let { newFlatElements } = flattenStructure(definition, definitionPath, csnUtils, options, error, referenceFlattener);
77
+ let { newFlatElements } = flattenStructure(definition.elements, definitionPath, csnUtils, options, error, referenceFlattener);
80
78
 
81
79
  attachPathOnPartialCSN(newFlatElements, definitionPath.concat('elements'));
82
-
83
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
+ }
84
87
  } // flattenDefinition
85
88
 
86
89
  /**
87
90
  * Flattens structured element by calling element flattener for each structured child.
88
91
  * Returns a dictionary containing all the new elements for the given structure.
89
- * @param {*} struct the structure to flatten
92
+ * @param {*} dictionary to flatten
90
93
  * @param {CSN.Path} path the path of the structure in the CSN tree
91
94
  * @param {*} csnUtils
92
95
  * @param {Function} error Error message function
@@ -95,14 +98,14 @@ function flattenDefinition(definition, definitionPath, csnUtils, options, refere
95
98
  * @param {*} [newFlatElements]
96
99
  * @param {boolean} [isTopLevelElement] states if this is a top level element
97
100
  */
98
- function flattenStructure(struct, path, csnUtils, options, error, referenceFlattener = undefined, elementPathInStructure = [],
101
+ function flattenStructure(dictionary, path, csnUtils, options, error, referenceFlattener = undefined, elementPathInStructure = [],
99
102
  newFlatElements = Object.create(null), isTopLevelElement = true, isParentNotNull = false) {
100
103
 
101
- if (!isTopLevelElement) addPropsForPropagationFromElement(struct);
104
+ if (!isTopLevelElement) addPropsForPropagationFromElement(dictionary);
102
105
 
103
106
  let generatedNewFlatElementsNames = []; // holds the names of all new child elements of the structure
104
107
 
105
- struct.elements && Object.entries(struct.elements).forEach(([elementName, element]) => {
108
+ dictionary && Object.entries(dictionary).forEach(([elementName, element]) => {
106
109
  let currPath = path.concat('elements', elementName);
107
110
 
108
111
  if (isTopLevelElement) {
@@ -120,8 +123,8 @@ function flattenStructure(struct, path, csnUtils, options, error, referenceFlatt
120
123
  addPropsForPropagationFromElement(element);
121
124
 
122
125
  // if the child element is structured itself -> needs to be flattened
123
- const subStruct = element.elements ? element : csnUtils.getFinalBaseType(element.type);
124
- 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());
125
128
  generatedNewFlatElementsNames.push(...result.generatedNewFlatElementsNames); // accomulate names of produced elements
126
129
 
127
130
  } else { // when we do not need to flat, this is scalar or empty (cds-compiler#4337) -> needs to be registered in referenceFlattener
@@ -129,7 +132,6 @@ function flattenStructure(struct, path, csnUtils, options, error, referenceFlatt
129
132
  let elementNameWithDots = elementPathInStructure.concat(elementName).join('.');
130
133
  addNewElementToResult(element, newElementName, elementNameWithDots, currPath);
131
134
  }
132
-
133
135
  });
134
136
 
135
137
  if (referenceFlattener) {
@@ -137,30 +139,29 @@ function flattenStructure(struct, path, csnUtils, options, error, referenceFlatt
137
139
  }
138
140
  return { newFlatElements, generatedNewFlatElementsNames };
139
141
 
140
-
141
142
  // adds newly created element into the final dictionary of elements
142
143
  function addNewElementToResult(element, elementName, elementNameWithDots, path) {
143
144
  if (newFlatElements[elementName]) {
144
145
  error(null, path, `Generated element ${elementName} conflicts with other generated element`);
145
146
  } else {
146
- let newElement = createNewElement(element, elementNameWithDots);
147
+ let newPath = path.slice(0, 2).concat('elements', elementName);
148
+ let newElement = createNewElement(element, elementNameWithDots, newPath);
147
149
  newFlatElements[elementName] = newElement;
148
150
  generatedNewFlatElementsNames.push(elementName);
149
151
 
150
152
  if (referenceFlattener) {
151
- let newPath = path.slice(0, 2).concat('elements', elementName);
152
153
  referenceFlattener.registerElementTransition(path, newPath);
153
154
  }
154
155
  }
155
156
  } // addNewElementToResult
156
157
 
157
158
  // creates new element by copying the properties of the originating element
158
- function createNewElement(element, elementNameWithDots) {
159
+ function createNewElement(element, elementNameWithDots, path) {
159
160
  let newElement = cloneCsn(element, options);
160
161
  if (!isTopLevelElement) propagatePropsToElement(newElement);
161
162
  if (isNotNull() === undefined) delete newElement.notNull;
162
- if (!isTopLevelElement) {
163
- setProp(newElement, '_flatElementNameWithDots', elementNameWithDots);
163
+ if (!isTopLevelElement && referenceFlattener) {
164
+ referenceFlattener.setElementNameWithDots(path, elementNameWithDots);
164
165
  }
165
166
  return newElement;
166
167
  } // createNewElement
@@ -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: { ... }'
@@ -8,9 +8,8 @@ const { hasErrors, makeMessageFunction } = require('../base/messages');
8
8
  const { setProp } = require('../base/model');
9
9
  const { csnRefs } = require('../model/csnRefs');
10
10
 
11
- const { copyAnnotations } = require('../model/csnUtils');
12
- const { cloneCsn, forEachMemberRecursively, forEachGeneric, forAllQueries,
13
- getUtils, isBuiltinType } = require('../model/csnUtils');
11
+ const { copyAnnotations, applyTransformations } = require('../model/csnUtils');
12
+ const { cloneCsn, getUtils, isBuiltinType } = require('../model/csnUtils');
14
13
 
15
14
  // Return the public functions of this module, with 'model' captured in a closure (for definitions, options etc).
16
15
  // Use 'pathDelimiter' for flattened names (e.g. of struct elements or foreign key elements).
@@ -62,6 +61,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
62
61
  recurseElements,
63
62
  renameAnnotation,
64
63
  setAnnotation,
64
+ resetAnnotation,
65
65
  expandStructsInExpression,
66
66
  };
67
67
 
@@ -80,6 +80,14 @@ function getTransformers(model, options, pathDelimiter = '_') {
80
80
  else if(defStrLen5k)
81
81
  element.length = 5000;
82
82
  }
83
+ if (element.type === 'cds.Binary' && element.length === undefined) {
84
+ if(options.defaultBinaryLength) {
85
+ element.length = options.defaultBinaryLength;
86
+ setProp(element, '$default', true);
87
+ }
88
+ else if(defStrLen5k)
89
+ element.length = 5000;
90
+ }
83
91
  /*
84
92
  if (element.type === 'cds.Decimal' && element.precision === undefined && options.precision) {
85
93
  element.precision = options.precision;
@@ -287,56 +295,64 @@ function getTransformers(model, options, pathDelimiter = '_') {
287
295
  return result;
288
296
  }
289
297
 
290
- // Return a copy of 'ref' where all path steps resulting from struct traversal are
291
- // fused together into one step, using '_' (so that the path fits again for flattened
292
- // structs), e.g.
293
- // [ (Entity), (struct1), (struct2), (assoc), (elem) ] should result in
294
- // [ (Entity), (struct1_struct2_assoc), (elem) ]
295
- // 'path' is the csn path to the ref
296
- function flattenStructStepsInRef(ref, path) {
298
+ /**
299
+ * Return a copy of 'ref' where all path steps resulting from struct traversal are
300
+ * fused together into one step, using '_' (so that the path fits again for flattened
301
+ * structs), e.g.
302
+ * [ (Entity), (struct1), (struct2), (assoc), (elem) ] should result in
303
+ * [ (Entity), (struct1_struct2_assoc), (elem) ]
304
+ *
305
+ * @param {string[]} ref
306
+ * @param {CSN.Path} path CSN path to the ref
307
+ * @param {object[]} [links] Pre-resolved links for the given ref - if not provided, will be calculated JIT
308
+ * @param {string} [scope] Pre-resolved scope for the given ref - if not provided, will be calculated JIT
309
+ * @param {WeakMap} [resolvedLinkTypes=new WeakMap()] A WeakMap with already resolved types for each link-step - safes an `artifactRef` call
310
+ * @returns {string[]}
311
+ */
312
+ function flattenStructStepsInRef(ref, path, links, scope, resolvedLinkTypes=new WeakMap()) {
297
313
  // Refs of length 1 cannot contain steps - no need to check
298
314
  if (ref.length < 2) {
299
315
  return ref;
300
316
  }
301
317
 
302
- try {
303
- return flatten(ref, path);
304
- } catch (e) {
305
- if (e.message && e.message === "Scope 'ref-target' but no entity was provided.") {
306
- // TODO: should not happen anymore
307
- return flatten(ref, path);
308
- } else {
309
- throw e;
310
- }
311
- }
318
+ return flatten(ref, path);
312
319
 
313
320
  function flatten(ref, path) {
314
321
  let result = [];
315
322
  //let stack = []; // IDs of path steps not yet processed or part of a struct traversal
316
- const { links, scope } = inspectRef(path);
323
+ if(!links && !scope) { // calculate JIT if not supplied
324
+ const res = inspectRef(path);
325
+ links = res.links;
326
+ scope = res.scope;
327
+ }
317
328
  if (scope === '$magic')
318
329
  return ref;
319
330
  let flattenStep = false;
320
331
  links.forEach((value, idx) => {
321
- if (flattenStep)
322
- result[result.length - 1] += pathDelimiter + ref[idx];
332
+ if (flattenStep) {
333
+ result[result.length - 1] += pathDelimiter + (ref[idx].id ? ref[idx].id : ref[idx]);
334
+ // if we had a filter or args, we had an assoc so this step is done
335
+ // we then keep along the filter/args by updating the id of the current ref
336
+ if(ref[idx].id) {
337
+ ref[idx].id = result[result.length-1];
338
+ result[result.length-1] = ref[idx];
339
+ }
340
+ }
323
341
  else {
324
342
  result.push(ref[idx]);
325
343
  }
326
- flattenStep = value.art && !(value.art.kind === 'entity') && !value.art.SELECT && !value.art.from && (value.art.elements || effectiveType(value.art).elements);
344
+
345
+ flattenStep = value.art && !value.art.kind && !value.art.SELECT && !value.art.from && (value.art.elements || effectiveType(value.art).elements || (resolvedLinkTypes.get(value)||{}).elements);
327
346
  });
328
- const magicVars = ['$now']
329
- // If the path starts with '$self', this is now redundant (because of flattening) and can be omitted,
330
- // making life easier for consumers
331
- if (result[0] === '$self' && result.length > 1 && !magicVars.includes(result[1])) {
332
- result = result.slice(1);
333
- }
347
+
334
348
  return result;
335
349
  }
336
350
  }
337
351
 
338
352
  /**
339
353
  * Copy properties of the referenced type, but don't resolve to the final base type.
354
+ *
355
+ * Do not copy the length if it was just set via the default-value.
340
356
  *
341
357
  * @param {any} node Node to copy to
342
358
  * @returns {void}
@@ -366,24 +382,31 @@ function getTransformers(model, options, pathDelimiter = '_') {
366
382
  Object.assign(node, { srid: typeDef.srid });
367
383
  }
368
384
 
369
- // Replace the type of 'node' with its final base type (in contrast to the compiler,
370
- // also unravel derived enum types, i.e. take the final base type of the enum's base type.
371
- // Similar with associations and compositions (we probably need a _baseType link)
372
- function toFinalBaseType(node) {
385
+ /**
386
+ * Replace the type of 'node' with its final base type (in contrast to the compiler,
387
+ * also unravel derived enum types, i.e. take the final base type of the enum's base type.
388
+ * Similar with associations and compositions (we probably need a _baseType link)
389
+ *
390
+ * @param {CSN.Artifact} node
391
+ * @param {WeakMap} [resolved] WeakMap containing already resolved refs
392
+ * @param {boolean} [keepLocalized=false] Wether to clone .localized from a type def
393
+ * @returns {void}
394
+ */
395
+ function toFinalBaseType(node, resolved, keepLocalized=false) {
373
396
  // Nothing to do if no type (or if array/struct type)
374
397
  if (!node || !node.type) return;
375
398
  // In case of a ref -> Follow the ref
376
399
  if (node.type && node.type.ref) {
377
- let finalBaseType = getFinalBaseType(node.type);
400
+ const finalBaseType = getFinalBaseType(node.type, undefined, resolved);
378
401
  if(finalBaseType === null)
379
402
  throw Error('Failed to obtain final base type for reference : ' + node.type.ref.join('/'));
380
- if (finalBaseType.elements) {
381
- // ATTENTION: This should be cloneCsnDictionary() but that would
382
- // change the foreign key order in many tests!
383
- node.elements = cloneCsn(finalBaseType.elements, options); // copy elements
403
+ if(finalBaseType.elements) {
404
+ // This changes the order - to be discussed!
405
+ node.elements = cloneCsn(finalBaseType, options).elements; // copy elements
384
406
  delete node.type; // delete the type reference as edm processing does not expect it
385
- } else if (finalBaseType.items) {
386
- node.items = cloneCsn(finalBaseType.items, options); // copy items
407
+ } else if(finalBaseType.items) {
408
+ // This changes the order - to be discussed!
409
+ node.items = cloneCsn(finalBaseType, options).items; // copy items
387
410
  delete node.type;
388
411
  } else {
389
412
  node.type=finalBaseType;
@@ -396,10 +419,31 @@ function getTransformers(model, options, pathDelimiter = '_') {
396
419
  // The type might already be a full fledged type def (array of)
397
420
  let typeDef = typeof node.type === 'string' ? getCsnDef(node.type) : node.type;
398
421
  // Nothing to do if type is an array or a struct type
399
- if (typeDef.items || typeDef.elements) return;
422
+ if (typeDef.items || typeDef.elements) {
423
+ if(!(options.transformation === 'hdbcds' || options.toSql))
424
+ return;
425
+
426
+ // cloneCsn only works correctly if we start "from the top"
427
+ const clone = cloneCsn({definitions: {'TypeDef': typeDef }}, options);
428
+ // With hdbcds-hdbcds, don't resolve structured types - but propagrate ".items", to turn into LargeString later on.
429
+ if(typeDef.items) {
430
+ delete node.type;
431
+ Object.assign(node, {items: clone.definitions.TypeDef.items});
432
+ }
433
+ if(typeDef.elements && !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds')) {
434
+ if(!typeDef.items)
435
+ delete node.type;
436
+ Object.assign(node, {elements: clone.definitions.TypeDef.elements});
437
+ }
438
+
439
+
440
+ return;
441
+ }
400
442
  // if the declared element is an enum, these values are with priority
401
- if (!node.enum && typeDef.enum)
402
- Object.assign(node, { enum: cloneCsn(typeDef.enum, options) });
443
+ if (!node.enum && typeDef.enum) {
444
+ const clone = cloneCsn({definitions: {'TypeDef': typeDef }}, options).definitions.TypeDef.enum;
445
+ Object.assign(node, { enum: clone });
446
+ }
403
447
  if (node.length === undefined && typeDef.length !== undefined)
404
448
  Object.assign(node, { length: typeDef.length });
405
449
  if (node.precision === undefined && typeDef.precision !== undefined)
@@ -408,6 +452,8 @@ function getTransformers(model, options, pathDelimiter = '_') {
408
452
  Object.assign(node, { scale: typeDef.scale });
409
453
  if (node.srid === undefined && typeDef.srid !== undefined)
410
454
  Object.assign(node, { srid: typeDef.srid });
455
+ if (keepLocalized && node.localized === undefined && typeDef.localized !== undefined)
456
+ Object.assign(node, { localized: typeDef.localized });
411
457
  node.type = typeDef.type;
412
458
  toFinalBaseType(node);
413
459
  }
@@ -888,6 +934,34 @@ function getTransformers(model, options, pathDelimiter = '_') {
888
934
  node[name] = value;
889
935
  }
890
936
 
937
+ /**
938
+ * Assigns unconditionally annotation to a node, which means it overwrites already existing annotation assignment.
939
+ * Overwritting is when the assignment differs from undefined and null, also when differs from the already set value.
940
+ * Setting new assignment results false as return value and overwriting - true.
941
+ *
942
+ * @param {object} node Assignee
943
+ * @param {string} name Annotation name
944
+ * @param {any} value Annotation value
945
+ * @param {function} info function that reports info messages
946
+ * @param {CSN.Path} path location of the warning
947
+ * @returns {boolean} wasOverwritten true when the annotation was overwritten
948
+ */
949
+ function resetAnnotation(node, name, value, info, path) {
950
+ if (!name.startsWith('@')) {
951
+ throw Error('Annotation name should start with "@": ' + name);
952
+ }
953
+ if (value === undefined) {
954
+ throw Error('Annotation value must not be undefined');
955
+ }
956
+
957
+ const wasOverwritten = node[name] !== undefined && node[name] !== null && node[name] !== value;
958
+ const oldValue = node[name];
959
+ node[name] = value;
960
+ if(wasOverwritten)
961
+ info(null, path, { anno: name, prop: value, otherprop: oldValue },
962
+ `Value $(OTHERPROP) of annotation $(ANNO) is overwritten with new value $(PROP)`);
963
+ return wasOverwritten;
964
+ }
891
965
 
892
966
  /*
893
967
  Resolve the type of an artifact
@@ -969,7 +1043,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
969
1043
  function flattenPath(path, fullRef=false, followMgdAssoc=false) {
970
1044
  let art = path._art;
971
1045
  if(art) {
972
- if(art && art.type && !((art.items && art.items.elements) || art.elements)) {
1046
+ if(art && !((art.items && art.items.elements) || art.elements)) {
973
1047
  if(followMgdAssoc && art.target && art.keys) {
974
1048
  let rc = [];
975
1049
  for(const k of art.keys) {
@@ -982,9 +1056,9 @@ function getTransformers(model, options, pathDelimiter = '_') {
982
1056
  }
983
1057
  return rc;
984
1058
  }
985
- if(art.type.ref)
1059
+ if(art.type && art.type.ref)
986
1060
  art = resolvePath(art.type);
987
- else if(!isBuiltinType(art.type))
1061
+ else if(art.type && !isBuiltinType(art.type))
988
1062
  art = model.definitions[art.type];
989
1063
  }
990
1064
  const elements = art.items && art.items.elements || art.elements;
@@ -1006,41 +1080,33 @@ function getTransformers(model, options, pathDelimiter = '_') {
1006
1080
  return [path];
1007
1081
  }
1008
1082
 
1009
- /*
1083
+ /**
1010
1084
  * Expand structured expression arguments to flat reference paths.
1011
1085
  * Structured elements are real sub element lists and managed associations.
1012
1086
  * All unmanaged association definitions are rewritten if applicable (elements/mixins).
1013
- * Also, HAVING and WHERE clauses are rewritten.
1087
+ * Also, HAVING and WHERE clauses are rewritten. We also check for infix filters and
1088
+ * .xpr in columns.
1014
1089
  *
1015
- * TODO: Check if can be skipped for abstract entity and or cds.persistence.skip ?
1016
- */
1017
- function expandStructsInExpression(artifact, artifactName, prop, path) {
1018
- forEachMemberRecursively(artifact,
1019
- (elem, elemName, prop, path) => {
1020
- if(prop === 'elements') {
1021
- if(elem.target && elem.on) {
1022
- elem.on = expand(elem.on, path)
1023
- }
1024
- }
1025
- }, path);
1026
-
1027
- if(artifact.query) {
1028
- forAllQueries(artifact.query, (query, queryPath) => {
1029
- if(query.SELECT) {
1030
- if(query.SELECT.mixin)
1031
- forEachGeneric(query.SELECT, 'mixin', (mixin, mixinName, prop, path) => {
1032
- if(mixin.target && mixin.on) {
1033
- mixin.on = expand(mixin.on, path);
1034
- }
1035
- },
1036
- path);
1037
- if(query.SELECT.having)
1038
- query.SELECT.having = expand(query.SELECT.having, queryPath.concat(['SELECT', 'having']))
1039
- if(query.SELECT.where)
1040
- query.SELECT.where = expand(query.SELECT.where, queryPath.concat(['SELECT', 'where']))
1041
- }
1042
- }, path.concat([ 'query' ]));
1043
- }
1090
+ * @todo Check if can be skipped for abstract entity and or cds.persistence.skip ?
1091
+ * @param {CSN.Model} csn
1092
+ * @param {object} [options={}] "skipArtifact": (artifact, name) => Boolean to skip certain artifacts
1093
+ */
1094
+ function expandStructsInExpression(csn, options = {}) {
1095
+ applyTransformations(csn, {
1096
+ 'on': (parent, name, on, path) => {
1097
+ parent.on = expand(parent.on, path);
1098
+ },
1099
+ 'having': (parent, name, having, path) => {
1100
+ parent.having = expand(parent.having, path);
1101
+ },
1102
+ 'where': (parent, name, where, path) => {
1103
+ parent.where = expand(parent.where, path);
1104
+ },
1105
+ 'xpr': (parent, name, xpr, path) => {
1106
+ parent.xpr = expand(parent.xpr, path);
1107
+ }
1108
+ }, undefined, undefined, options);
1109
+
1044
1110
  /*
1045
1111
  flatten structured leaf types and return array of paths
1046
1112
  Flattening stops on all non-structured types.