@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
@@ -0,0 +1,325 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ forEachDefinition, getUtils,
5
+ applyTransformations, forAllElements, isBuiltinType,
6
+ } = require('../../model/csnUtils');
7
+ const transformUtils = require('../transformUtilsNew');
8
+ const { csnRefs } = require('../../model/csnRefs');
9
+ const { setProp } = require('../../base/model');
10
+
11
+ /**
12
+ * Strip off leading $self from refs where applicable
13
+ *
14
+ * @param {CSN.Model} csn
15
+ */
16
+ function removeLeadingSelf(csn) {
17
+ const magicVars = [ '$now' ];
18
+ forEachDefinition(csn, (artifact, artifactName) => {
19
+ if (artifact.kind === 'entity' || artifact.kind === 'view') {
20
+ forAllElements(artifact, artifactName, (parent, elements) => {
21
+ for (const [ elementName, element ] of Object.entries(elements)) {
22
+ if (element.on) {
23
+ // applyTransformations expects the first thing to have a "definitions"
24
+ const fakeDefinitions = { definitions: {} };
25
+ fakeDefinitions.definitions[elementName] = element;
26
+ applyTransformations( fakeDefinitions, {
27
+ ref: (root, name, ref) => {
28
+ // Renderers seem to expect it to not be there...
29
+ if (ref[0] === '$self' && ref.length > 1 && !magicVars.includes(ref[1]))
30
+ root.ref = ref.slice(1);
31
+ },
32
+ });
33
+ }
34
+ }
35
+ });
36
+ }
37
+ });
38
+ }
39
+
40
+ /**
41
+ * Resolve type references and turn things with `.items` into elements of type `LargeString`.
42
+ *
43
+ * Also, replace actions, events and functions with simply dummy artifacts.
44
+ *
45
+ * @param {CSN.Model} csn
46
+ * @param {CSN.Options} options
47
+ * @param {WeakMap} resolved Cache for resolved refs
48
+ * @param {string} pathDelimiter
49
+ */
50
+ function resolveTypeReferences(csn, options, resolved, pathDelimiter) {
51
+ /**
52
+ * Remove .localized from the element and any sub-elements
53
+ *
54
+ * Only direct .localized usage should produce "localized things".
55
+ * If we don't remove it here, the second compile step adds localized stuff again.
56
+ *
57
+ * @param {object} obj
58
+ */
59
+ function removeLocalized(obj) {
60
+ const stack = [ obj ];
61
+ while (stack.length > 0) {
62
+ const current = stack.pop();
63
+ if (current.localized)
64
+ delete current.localized;
65
+
66
+
67
+ if (current.elements)
68
+ stack.push(...Object.values(current.elements));
69
+ }
70
+ }
71
+ const { toFinalBaseType } = transformUtils.getTransformers(csn, options, pathDelimiter);
72
+ applyTransformations(csn, {
73
+ cast: (parent) => {
74
+ // Resolve cast already - we otherwise lose .localized
75
+ if (parent.cast.type && !isBuiltinType(parent.cast.type))
76
+ toFinalBaseType(parent.cast, resolved, true);
77
+ },
78
+ type: (parent, prop, type) => {
79
+ if (!isBuiltinType(type)) {
80
+ const directLocalized = parent.localized || false;
81
+ toFinalBaseType(parent, resolved);
82
+ if (!directLocalized)
83
+ removeLocalized(parent);
84
+ }
85
+ // HANA/SQLite do not support array-of - turn into CLOB/Text
86
+ if (parent.items) {
87
+ parent.type = 'cds.LargeString';
88
+ delete parent.items;
89
+ }
90
+ },
91
+ // HANA/SQLite do not support array-of - turn into CLOB/Text
92
+ items: (parent) => {
93
+ parent.type = 'cds.LargeString';
94
+ delete parent.items;
95
+ },
96
+ }, [ (definitions, artifactName, artifact) => {
97
+ // Replace events, actions and functions with simple dummies - they don't have effect on forHanaNew stuff
98
+ // and that way they contain no references and don't hurt.
99
+ if (artifact.kind === 'action' || artifact.kind === 'function' || artifact.kind === 'event') {
100
+ const dummy = { kind: artifact.kind };
101
+ if (artifact.$location)
102
+ setProp(dummy, '$location', artifact.$location);
103
+
104
+ definitions[artifactName] = dummy;
105
+ }
106
+ } ], true, { skipDict: { actions: true } });
107
+ }
108
+
109
+ /**
110
+ * @param {CSN.Model} csn
111
+ * @param {CSN.Options} options
112
+ * @param {WeakMap} resolved Cache for resolved refs
113
+ * @param {string} pathDelimiter
114
+ */
115
+ function flattenAllStructStepsInRefs(csn, options, resolved, pathDelimiter) {
116
+ const { inspectRef, effectiveType } = csnRefs(csn);
117
+ const { flattenStructStepsInRef } = transformUtils.getTransformers(csn, options, pathDelimiter);
118
+ const adaptRefs = [];
119
+
120
+ /**
121
+ * For each step of the links, check if there is a type reference.
122
+ * If there is, resolve it and store the result in a WeakMap.
123
+ *
124
+ * @param {Array} [links=[]]
125
+ * @todo seems too hacky
126
+ * @returns {WeakMap} A WeakMap where a link is the key and the type is the value
127
+ */
128
+ function resolveLinkTypes(links = []) {
129
+ const resolvedLinkTypes = new WeakMap();
130
+ links.forEach((link) => {
131
+ const { art } = link;
132
+ if (art && art.type)
133
+ resolvedLinkTypes.set(link, effectiveType(art));
134
+ });
135
+
136
+ return resolvedLinkTypes;
137
+ }
138
+
139
+ applyTransformations(csn, {
140
+ ref: (parent, prop, ref, path) => {
141
+ const { links, art, scope } = inspectRef(path);
142
+ const resolvedLinkTypes = resolveLinkTypes(links);
143
+ setProp(parent, '$path', [ ...path ]);
144
+ const lastRef = ref[ref.length - 1];
145
+ const fn = () => {
146
+ const scopedPath = [ ...parent.$path ];
147
+
148
+ parent.ref = flattenStructStepsInRef(ref, scopedPath, links, scope, resolvedLinkTypes );
149
+ resolved.set(parent, { links, art, scope });
150
+ // Explicitly set implicit alias for things that are now flattened - but only in columns
151
+ // TODO: Can this be done elegantly during expand phase already?
152
+ if (parent.$implicitAlias) { // an expanded s -> s.a is marked with this - do not add implicit alias "a" there, we want s_a
153
+ if (parent.ref[parent.ref.length - 1] === parent.as) // for a simple s that was expanded - for s.substructure this would not apply
154
+ delete parent.as;
155
+ delete parent.$implicitAlias;
156
+ }
157
+ // To handle explicitly written s.a - add implicit alias a, since after flattening it would otherwise be s_a
158
+ else if (parent.ref[parent.ref.length - 1] !== lastRef && (insideColumns(scopedPath) || insideKeys(scopedPath)) && !parent.as) {
159
+ parent.as = lastRef;
160
+ }
161
+ };
162
+ // adapt queries later
163
+ adaptRefs.push(fn);
164
+ },
165
+ });
166
+
167
+ adaptRefs.forEach(fn => fn());
168
+
169
+ /**
170
+ * Return true if the path points inside columns
171
+ *
172
+ * @param {CSN.Path} path
173
+ * @returns {boolean}
174
+ */
175
+ function insideColumns(path) {
176
+ return path.length >= 3 && (path[path.length - 3] === 'SELECT' || path[path.length - 3] === 'projection') && path[path.length - 2] === 'columns';
177
+ }
178
+ /**
179
+ * Return true if the path points inside keys
180
+ *
181
+ * @param {CSN.Path} path
182
+ * @returns {boolean}
183
+ */
184
+ function insideKeys(path) {
185
+ return path.length >= 3 && path[path.length - 2] === 'keys' && typeof path[path.length - 1] === 'number';
186
+ }
187
+ }
188
+
189
+ /**
190
+ * @param {CSN.Model} csn
191
+ * @param {CSN.Options} options
192
+ * @param {string} pathDelimiter
193
+ * @param {Function} error
194
+ */
195
+ function flattenElements(csn, options, pathDelimiter, error) {
196
+ const { isAssocOrComposition } = getUtils(csn);
197
+ const { flattenStructuredElement } = transformUtils.getTransformers(csn, options, pathDelimiter);
198
+ const { effectiveType } = csnRefs(csn);
199
+ forEachDefinition(csn, flattenStructuredElements);
200
+ /**
201
+ * Flatten structures
202
+ *
203
+ * @param {CSN.Artifact} art Artifact
204
+ * @param {string} artName Artifact Name
205
+ */
206
+ function flattenStructuredElements(art, artName) {
207
+ forAllElements(art, artName, (parent, elements, pathToElements) => {
208
+ const elementsArray = [];
209
+ for (const elemName in elements) {
210
+ const pathToElement = pathToElements.concat([ elemName ]);
211
+ const elem = parent.elements[elemName];
212
+ elementsArray.push([ elemName, elem ]);
213
+ if (elem.elements) {
214
+ elementsArray.pop();
215
+ // Ignore the structured element, replace it by its flattened form
216
+ // TODO: use $ignore - _ is for links
217
+ elem._ignore = true;
218
+
219
+ const branches = getBranches(elem, elemName);
220
+ const flatElems = flattenStructuredElement(elem, elemName, [], pathToElement);
221
+
222
+ for (const flatElemName in flatElems) {
223
+ if (parent.elements[flatElemName])
224
+ error(null, pathToElement, `"${artName}.${elemName}": Flattened struct element name conflicts with existing element: "${flatElemName}"`);
225
+
226
+ const flatElement = flatElems[flatElemName];
227
+
228
+ // Check if we have a valid notNull chain
229
+ const branch = branches[flatElemName];
230
+ if (flatElement.notNull !== false && !branch.some(s => !s.notNull))
231
+ flatElement.notNull = true;
232
+
233
+
234
+ if (flatElement.type && isAssocOrComposition(flatElement.type) && flatElement.on) {
235
+ // Make refs resolvable by fixing the first ref step
236
+ for (let i = 0; i < flatElement.on.length; i++) {
237
+ const onPart = flatElement.on[i];
238
+ if (onPart.ref) {
239
+ const firstRef = flatElement.on[i].ref[0];
240
+
241
+ /*
242
+ when element is defined in the current name resolution scope, like
243
+ entity E {
244
+ key x: Integer;
245
+ s : {
246
+ y : Integer;
247
+ a3 : association to E on a3.x = y;
248
+ }
249
+ }
250
+ We need to replace y with s_y and a3 with s_a3 - we must take care to not escape our local scope
251
+ */
252
+ const prefix = flatElement._flatElementNameWithDots.split('.').slice(0, -1).join(pathDelimiter);
253
+ const possibleFlatName = prefix + pathDelimiter + firstRef;
254
+
255
+ if (flatElems[possibleFlatName])
256
+ flatElement.on[i].ref[0] = possibleFlatName;
257
+ }
258
+ }
259
+ }
260
+ elementsArray.push([ flatElemName, flatElement ]);
261
+ // Still add them - otherwise we might not detect collisions between generated elements.
262
+ parent.elements[flatElemName] = flatElement;
263
+ }
264
+ }
265
+ }
266
+ // Don't fake consistency of the model by adding empty elements {}
267
+ if (elementsArray.length === 0)
268
+ return;
269
+
270
+ parent.elements = elementsArray.reduce((previous, [ name, element ]) => {
271
+ previous[name] = element;
272
+ return previous;
273
+ }, Object.create(null));
274
+ });
275
+ }
276
+
277
+ /**
278
+ * Get not just the leafs, but all the branches of a structured element
279
+ *
280
+ * @param {object} element Structured element
281
+ * @param {string} elementName Name of the structured element
282
+ * @returns {object} Returns a dictionary, where the key is the flat name of the branch and the value is an array of element-steps.
283
+ */
284
+ function getBranches(element, elementName) {
285
+ const branches = {};
286
+ const subbranchNames = [];
287
+ const subbranchElements = [];
288
+ walkElements(element, elementName);
289
+ /**
290
+ * Walk the element chain
291
+ *
292
+ * @param {CSN.Element} e
293
+ * @param {string} name
294
+ */
295
+ function walkElements(e, name) {
296
+ if (isBuiltinType(e)) {
297
+ branches[subbranchNames.concat(name).join(pathDelimiter)] = subbranchElements.concat(e);
298
+ }
299
+ else {
300
+ const eType = effectiveType(e);
301
+ const subelements = e.elements || eType.elements;
302
+ if (subelements) {
303
+ subbranchElements.push(e);
304
+ subbranchNames.push(name);
305
+ for (const [ subelementName, subelement ] of Object.entries(subelements))
306
+ walkElements(subelement, subelementName);
307
+
308
+ subbranchNames.pop();
309
+ subbranchElements.pop();
310
+ }
311
+ else {
312
+ branches[subbranchNames.concat(name).join(pathDelimiter)] = subbranchElements.concat(e);
313
+ }
314
+ }
315
+ }
316
+ return branches;
317
+ }
318
+ }
319
+
320
+ module.exports = {
321
+ resolveTypeReferences,
322
+ flattenAllStructStepsInRefs,
323
+ flattenElements,
324
+ removeLeadingSelf,
325
+ };
@@ -24,7 +24,7 @@ function replaceAssociationsInGroupByOrderBy(inputQuery, options, inspectRef, er
24
24
  if (art.keys) {
25
25
  // This is (or used to be before transformation) a managed assoc
26
26
  // (230 c) If we keep associations as they are (hdbcds naming convention), we can't have associations in GROUP BY
27
- if (options.forHana.keepStructsAssocs) {
27
+ if (options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds') {
28
28
  error(null, groupByPath,
29
29
  { $reviewed: true },
30
30
  'Unexpected managed association in GROUP BY for naming mode “hdbcds“');
@@ -57,7 +57,7 @@ function replaceAssociationsInGroupByOrderBy(inputQuery, options, inspectRef, er
57
57
  if (art.keys) {
58
58
  // This is (or used to be before transformation) a managed assoc
59
59
  // (230 d) If we keep associations as they are (hdbcds naming convention), we can't have associations in ORDER BY
60
- if (options.forHana.keepStructsAssocs) {
60
+ if (options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds') {
61
61
  error(null, orderByPath,
62
62
  { $reviewed: true },
63
63
  'Unexpected managed association in ORDER BY for naming mode “hdbcds”');