@sap/cds-compiler 2.5.0 → 2.10.4

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 (92) hide show
  1. package/CHANGELOG.md +191 -9
  2. package/bin/cdsc.js +2 -2
  3. package/doc/CHANGELOG_BETA.md +33 -3
  4. package/lib/api/main.js +29 -101
  5. package/lib/api/options.js +15 -11
  6. package/lib/api/validate.js +12 -8
  7. package/lib/backends.js +0 -81
  8. package/lib/base/keywords.js +32 -2
  9. package/lib/base/message-registry.js +63 -9
  10. package/lib/base/messages.js +63 -21
  11. package/lib/base/model.js +2 -3
  12. package/lib/checks/defaultValues.js +27 -2
  13. package/lib/checks/elements.js +1 -6
  14. package/lib/checks/foreignKeys.js +0 -6
  15. package/lib/checks/managedWithoutKeys.js +17 -0
  16. package/lib/checks/nonexpandableStructured.js +38 -0
  17. package/lib/checks/onConditions.js +9 -45
  18. package/lib/checks/queryNoDbArtifacts.js +25 -7
  19. package/lib/checks/selectItems.js +25 -2
  20. package/lib/checks/types.js +26 -2
  21. package/lib/checks/unknownMagic.js +38 -0
  22. package/lib/checks/utils.js +61 -0
  23. package/lib/checks/validator.js +60 -7
  24. package/lib/compiler/assert-consistency.js +16 -7
  25. package/lib/compiler/builtins.js +2 -0
  26. package/lib/compiler/checks.js +6 -4
  27. package/lib/compiler/definer.js +99 -42
  28. package/lib/compiler/index.js +73 -27
  29. package/lib/compiler/resolver.js +288 -157
  30. package/lib/compiler/shared.js +31 -11
  31. package/lib/edm/annotations/genericTranslation.js +182 -186
  32. package/lib/edm/csn2edm.js +103 -108
  33. package/lib/edm/edm.js +18 -21
  34. package/lib/edm/edmPreprocessor.js +361 -114
  35. package/lib/edm/edmUtils.js +103 -33
  36. package/lib/gen/Dictionary.json +22 -0
  37. package/lib/gen/language.checksum +1 -1
  38. package/lib/gen/language.interp +12 -1
  39. package/lib/gen/language.tokens +57 -53
  40. package/lib/gen/languageLexer.interp +10 -1
  41. package/lib/gen/languageLexer.js +770 -744
  42. package/lib/gen/languageLexer.tokens +49 -46
  43. package/lib/gen/languageParser.js +4713 -4279
  44. package/lib/json/from-csn.js +103 -45
  45. package/lib/json/to-csn.js +296 -117
  46. package/lib/language/antlrParser.js +4 -3
  47. package/lib/language/errorStrategy.js +1 -0
  48. package/lib/language/genericAntlrParser.js +21 -12
  49. package/lib/language/language.g4 +99 -31
  50. package/lib/main.d.ts +81 -3
  51. package/lib/main.js +30 -7
  52. package/lib/model/api.js +78 -0
  53. package/lib/model/csnRefs.js +329 -142
  54. package/lib/model/csnUtils.js +235 -58
  55. package/lib/model/enrichCsn.js +18 -1
  56. package/lib/model/revealInternalProperties.js +2 -1
  57. package/lib/modelCompare/compare.js +37 -20
  58. package/lib/optionProcessor.js +9 -3
  59. package/lib/render/.eslintrc.json +4 -1
  60. package/lib/render/DuplicateChecker.js +8 -5
  61. package/lib/render/toCdl.js +112 -33
  62. package/lib/render/toHdbcds.js +134 -64
  63. package/lib/render/toSql.js +91 -38
  64. package/lib/render/utils/common.js +8 -13
  65. package/lib/render/utils/sql.js +3 -3
  66. package/lib/sql-identifier.js +6 -1
  67. package/lib/transform/db/assertUnique.js +5 -6
  68. package/lib/transform/db/constraints.js +29 -13
  69. package/lib/transform/db/draft.js +8 -6
  70. package/lib/transform/db/expansion.js +582 -0
  71. package/lib/transform/db/flattening.js +325 -0
  72. package/lib/transform/db/groupByOrderBy.js +2 -2
  73. package/lib/transform/db/transformExists.js +284 -63
  74. package/lib/transform/forHanaNew.js +98 -381
  75. package/lib/transform/forOdataNew.js +21 -22
  76. package/lib/transform/localized.js +37 -10
  77. package/lib/transform/odata/attachPath.js +19 -4
  78. package/lib/transform/odata/generateForeignKeyElements.js +11 -10
  79. package/lib/transform/odata/referenceFlattener.js +60 -39
  80. package/lib/transform/odata/sortByAssociationDependency.js +2 -2
  81. package/lib/transform/odata/structuralPath.js +72 -0
  82. package/lib/transform/odata/structureFlattener.js +19 -18
  83. package/lib/transform/odata/typesExposure.js +22 -12
  84. package/lib/transform/transformUtilsNew.js +134 -78
  85. package/lib/transform/translateAssocsToJoins.js +17 -14
  86. package/lib/transform/universalCsnEnricher.js +67 -0
  87. package/lib/utils/file.js +0 -11
  88. package/lib/utils/moduleResolve.js +6 -8
  89. package/package.json +1 -1
  90. package/lib/json/walker.js +0 -26
  91. package/lib/transform/sqlite +0 -0
  92. 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
 
@@ -287,50 +287,56 @@ function getTransformers(model, options, pathDelimiter = '_') {
287
287
  return result;
288
288
  }
289
289
 
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) {
290
+ /**
291
+ * Return a copy of 'ref' where all path steps resulting from struct traversal are
292
+ * fused together into one step, using '_' (so that the path fits again for flattened
293
+ * structs), e.g.
294
+ * [ (Entity), (struct1), (struct2), (assoc), (elem) ] should result in
295
+ * [ (Entity), (struct1_struct2_assoc), (elem) ]
296
+ *
297
+ * @param {string[]} ref
298
+ * @param {CSN.Path} path CSN path to the ref
299
+ * @param {object[]} [links] Pre-resolved links for the given ref - if not provided, will be calculated JIT
300
+ * @param {string} [scope] Pre-resolved scope for the given ref - if not provided, will be calculated JIT
301
+ * @param {WeakMap} [resolvedLinkTypes=new WeakMap()] A WeakMap with already resolved types for each link-step - safes an `artifactRef` call
302
+ * @returns {string[]}
303
+ */
304
+ function flattenStructStepsInRef(ref, path, links, scope, resolvedLinkTypes=new WeakMap()) {
297
305
  // Refs of length 1 cannot contain steps - no need to check
298
306
  if (ref.length < 2) {
299
307
  return ref;
300
308
  }
301
309
 
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
- }
310
+ return flatten(ref, path);
312
311
 
313
312
  function flatten(ref, path) {
314
313
  let result = [];
315
314
  //let stack = []; // IDs of path steps not yet processed or part of a struct traversal
316
- const { links, scope } = inspectRef(path);
315
+ if(!links && !scope) { // calculate JIT if not supplied
316
+ const res = inspectRef(path);
317
+ links = res.links;
318
+ scope = res.scope;
319
+ }
317
320
  if (scope === '$magic')
318
321
  return ref;
319
322
  let flattenStep = false;
320
323
  links.forEach((value, idx) => {
321
- if (flattenStep)
322
- result[result.length - 1] += pathDelimiter + ref[idx];
324
+ if (flattenStep) {
325
+ result[result.length - 1] += pathDelimiter + (ref[idx].id ? ref[idx].id : ref[idx]);
326
+ // if we had a filter or args, we had an assoc so this step is done
327
+ // we then keep along the filter/args by updating the id of the current ref
328
+ if(ref[idx].id) {
329
+ ref[idx].id = result[result.length-1];
330
+ result[result.length-1] = ref[idx];
331
+ }
332
+ }
323
333
  else {
324
334
  result.push(ref[idx]);
325
335
  }
326
- flattenStep = value.art && !(value.art.kind === 'entity') && !value.art.SELECT && !value.art.from && (value.art.elements || effectiveType(value.art).elements);
336
+
337
+ flattenStep = value.art && !value.art.kind && !value.art.SELECT && !value.art.from && (value.art.elements || effectiveType(value.art).elements || (resolvedLinkTypes.get(value)||{}).elements);
327
338
  });
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
- }
339
+
334
340
  return result;
335
341
  }
336
342
  }
@@ -366,24 +372,31 @@ function getTransformers(model, options, pathDelimiter = '_') {
366
372
  Object.assign(node, { srid: typeDef.srid });
367
373
  }
368
374
 
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) {
375
+ /**
376
+ * Replace the type of 'node' with its final base type (in contrast to the compiler,
377
+ * also unravel derived enum types, i.e. take the final base type of the enum's base type.
378
+ * Similar with associations and compositions (we probably need a _baseType link)
379
+ *
380
+ * @param {CSN.Artifact} node
381
+ * @param {WeakMap} [resolved] WeakMap containing already resolved refs
382
+ * @param {boolean} [keepLocalized=false] Wether to clone .localized from a type def
383
+ * @returns {void}
384
+ */
385
+ function toFinalBaseType(node, resolved, keepLocalized=false) {
373
386
  // Nothing to do if no type (or if array/struct type)
374
387
  if (!node || !node.type) return;
375
388
  // In case of a ref -> Follow the ref
376
389
  if (node.type && node.type.ref) {
377
- let finalBaseType = getFinalBaseType(node.type);
390
+ const finalBaseType = getFinalBaseType(node.type, undefined, resolved);
378
391
  if(finalBaseType === null)
379
392
  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
393
+ if(finalBaseType.elements) {
394
+ // This changes the order - to be discussed!
395
+ node.elements = cloneCsn(finalBaseType, options).elements; // copy elements
384
396
  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
397
+ } else if(finalBaseType.items) {
398
+ // This changes the order - to be discussed!
399
+ node.items = cloneCsn(finalBaseType, options).items; // copy items
387
400
  delete node.type;
388
401
  } else {
389
402
  node.type=finalBaseType;
@@ -396,10 +409,31 @@ function getTransformers(model, options, pathDelimiter = '_') {
396
409
  // The type might already be a full fledged type def (array of)
397
410
  let typeDef = typeof node.type === 'string' ? getCsnDef(node.type) : node.type;
398
411
  // Nothing to do if type is an array or a struct type
399
- if (typeDef.items || typeDef.elements) return;
412
+ if (typeDef.items || typeDef.elements) {
413
+ if(!(options.transformation === 'hdbcds' || options.toSql))
414
+ return;
415
+
416
+ // cloneCsn only works correctly if we start "from the top"
417
+ const clone = cloneCsn({definitions: {'TypeDef': typeDef }}, options);
418
+ // With hdbcds-hdbcds, don't resolve structured types - but propagrate ".items", to turn into LargeString later on.
419
+ if(typeDef.items) {
420
+ delete node.type;
421
+ Object.assign(node, {items: clone.definitions.TypeDef.items});
422
+ }
423
+ if(typeDef.elements && !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds')) {
424
+ if(!typeDef.items)
425
+ delete node.type;
426
+ Object.assign(node, {elements: clone.definitions.TypeDef.elements});
427
+ }
428
+
429
+
430
+ return;
431
+ }
400
432
  // 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) });
433
+ if (!node.enum && typeDef.enum) {
434
+ const clone = cloneCsn({definitions: {'TypeDef': typeDef }}, options).definitions.TypeDef.enum;
435
+ Object.assign(node, { enum: clone });
436
+ }
403
437
  if (node.length === undefined && typeDef.length !== undefined)
404
438
  Object.assign(node, { length: typeDef.length });
405
439
  if (node.precision === undefined && typeDef.precision !== undefined)
@@ -408,6 +442,8 @@ function getTransformers(model, options, pathDelimiter = '_') {
408
442
  Object.assign(node, { scale: typeDef.scale });
409
443
  if (node.srid === undefined && typeDef.srid !== undefined)
410
444
  Object.assign(node, { srid: typeDef.srid });
445
+ if (keepLocalized && node.localized === undefined && typeDef.localized !== undefined)
446
+ Object.assign(node, { localized: typeDef.localized });
411
447
  node.type = typeDef.type;
412
448
  toFinalBaseType(node);
413
449
  }
@@ -888,6 +924,34 @@ function getTransformers(model, options, pathDelimiter = '_') {
888
924
  node[name] = value;
889
925
  }
890
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
+ }
891
955
 
892
956
  /*
893
957
  Resolve the type of an artifact
@@ -969,7 +1033,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
969
1033
  function flattenPath(path, fullRef=false, followMgdAssoc=false) {
970
1034
  let art = path._art;
971
1035
  if(art) {
972
- if(art && art.type && !((art.items && art.items.elements) || art.elements)) {
1036
+ if(art && !((art.items && art.items.elements) || art.elements)) {
973
1037
  if(followMgdAssoc && art.target && art.keys) {
974
1038
  let rc = [];
975
1039
  for(const k of art.keys) {
@@ -982,9 +1046,9 @@ function getTransformers(model, options, pathDelimiter = '_') {
982
1046
  }
983
1047
  return rc;
984
1048
  }
985
- if(art.type.ref)
1049
+ if(art.type && art.type.ref)
986
1050
  art = resolvePath(art.type);
987
- else if(!isBuiltinType(art.type))
1051
+ else if(art.type && !isBuiltinType(art.type))
988
1052
  art = model.definitions[art.type];
989
1053
  }
990
1054
  const elements = art.items && art.items.elements || art.elements;
@@ -1006,41 +1070,33 @@ function getTransformers(model, options, pathDelimiter = '_') {
1006
1070
  return [path];
1007
1071
  }
1008
1072
 
1009
- /*
1073
+ /**
1010
1074
  * Expand structured expression arguments to flat reference paths.
1011
1075
  * Structured elements are real sub element lists and managed associations.
1012
1076
  * All unmanaged association definitions are rewritten if applicable (elements/mixins).
1013
- * Also, HAVING and WHERE clauses are rewritten.
1077
+ * Also, HAVING and WHERE clauses are rewritten. We also check for infix filters and
1078
+ * .xpr in columns.
1014
1079
  *
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
- }
1080
+ * @todo Check if can be skipped for abstract entity and or cds.persistence.skip ?
1081
+ * @param {CSN.Model} csn
1082
+ * @param {object} [options={}] "skipArtifact": (artifact, name) => Boolean to skip certain artifacts
1083
+ */
1084
+ function expandStructsInExpression(csn, options = {}) {
1085
+ applyTransformations(csn, {
1086
+ 'on': (parent, name, on, path) => {
1087
+ parent.on = expand(parent.on, path);
1088
+ },
1089
+ 'having': (parent, name, having, path) => {
1090
+ parent.having = expand(parent.having, path);
1091
+ },
1092
+ 'where': (parent, name, where, path) => {
1093
+ parent.where = expand(parent.where, path);
1094
+ },
1095
+ 'xpr': (parent, name, xpr, path) => {
1096
+ parent.xpr = expand(parent.xpr, path);
1097
+ }
1098
+ }, undefined, undefined, options);
1099
+
1044
1100
  /*
1045
1101
  flatten structured leaf types and return array of paths
1046
1102
  Flattening stops on all non-structured types.
@@ -1,7 +1,8 @@
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
+ const { recompileX } = require('../compiler/index');
5
6
  var { linkToOrigin } = require('../compiler/shared');
6
7
  const {compactModel, compactExpr} = require('../json/to-csn');
7
8
  const { deduplicateMessages } = require('../base/messages');
@@ -12,13 +13,10 @@ const internalArtifactKinds = ['builtin'/*, '$parameters'*/, 'param'];
12
13
 
13
14
  function translateAssocsToJoinsCSN(csn, options){
14
15
  timetrace.start('Recompiling model');
15
- let { augment } = require('../json/from-csn');
16
- // Append `.csn` to `.cds` files to indicate recompilation.
17
- const file = csn.$location && csn.$location.file.replace(/[.]cds$/, '.cds.csn') || '<recompile>.csn';
18
- let xsn = augment(csn, file, options);
19
- const { compileSourcesX } = require('../compiler');
20
16
  // Do not re-complain about localized
21
- const model = compileSourcesX( { [file]: xsn }, { ...options, $recompile: true } );
17
+ const compileOptions = { ...options, $skipNameCheck: true };
18
+ delete compileOptions.csnFlavor;
19
+ const model = recompileX(csn, compileOptions);
22
20
  timetrace.stop();
23
21
  timetrace.start('Translating associations to joins');
24
22
  translateAssocsToJoins(model, options);
@@ -46,9 +44,9 @@ function translateAssocsToJoinsCSN(csn, options){
46
44
  }
47
45
 
48
46
  // If A2J reports error - end! Continuing with a broken CSN makes no sense
49
- handleMessages(model, options);
47
+ makeMessageFunction(model, options).throwWithError();
50
48
  // FIXME: Move this somewhere more appropriate
51
- const compact = compactModel(model, options);
49
+ const compact = compactModel(model, compileOptions);
52
50
  return compact;
53
51
  }
54
52
 
@@ -618,7 +616,7 @@ function translateAssocsToJoins(model, inputOptions = {})
618
616
  const id = this.id();
619
617
  if(elt) {
620
618
  let found = true;
621
- const epath = elt.split('.');
619
+ const epath = [elt];
622
620
  const epl = epath.length+offset;
623
621
  if(epl < path.length) {
624
622
  for(let i = 0; i < epl && found; i++) {
@@ -630,7 +628,7 @@ function translateAssocsToJoins(model, inputOptions = {})
630
628
  }
631
629
  if(id) {
632
630
  let found = true;
633
- const epath = id.split('.');
631
+ const epath = [id];
634
632
  const epl = epath.length+offset;
635
633
  if(epl < path.length) {
636
634
  for(let i = 0; i < epl && found; i++) {
@@ -745,9 +743,14 @@ function translateAssocsToJoins(model, inputOptions = {})
745
743
  function swapTableAliasesForFwdAssoc(fwdAssoc, srcAlias, tgtAlias) {
746
744
  let newSrcAlias = tgtAlias;
747
745
  let newTgtAlias = {};
748
- // first try to identify table alias for complex views or
749
- // redirected associations
750
- if(fwdAssoc._redirected && fwdAssoc._redirected.length) {
746
+ // first try to identify table alias for complex views or redirected associations
747
+ if(fwdAssoc._redirected && fwdAssoc._redirected.length &&
748
+ // redirected target must have a $QA
749
+ fwdAssoc._redirected[fwdAssoc._redirected.length-1].$QA &&
750
+ // $QA's artifact must either be same srcAlias artifact
751
+ (fwdAssoc._redirected[fwdAssoc._redirected.length-1].$QA._artifact === srcAlias._artifact ||
752
+ // OR original assoc is a mixin (then just use the $QA)
753
+ assoc.kind === 'mixin')) {
751
754
  newTgtAlias.id = fwdAssoc._redirected[fwdAssoc._redirected.length-1].$QA.name.id;
752
755
  newTgtAlias._artifact = fwdAssoc._redirected[fwdAssoc._redirected.length-1]._effectiveType;
753
756
  newTgtAlias._navigation = fwdAssoc._redirected[fwdAssoc._redirected.length-1].$QA.path[0]._navigation;