@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
@@ -0,0 +1,341 @@
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
+ // structured types might not have the child-types replaced.
83
+ // Drill down to ensure this.
84
+ if (parent.elements) {
85
+ const stack = [ parent.elements ];
86
+ while (stack.length > 0) {
87
+ const elements = stack.pop();
88
+ for (const e of Object.values(elements)) {
89
+ if (e.type && !isBuiltinType(e.type))
90
+ toFinalBaseType(e, resolved);
91
+
92
+ if (e.elements)
93
+ stack.push(e.elements);
94
+ }
95
+ }
96
+ }
97
+
98
+ if (!directLocalized)
99
+ removeLocalized(parent);
100
+ }
101
+ // HANA/SQLite do not support array-of - turn into CLOB/Text
102
+ if (parent.items) {
103
+ parent.type = 'cds.LargeString';
104
+ delete parent.items;
105
+ }
106
+ },
107
+ // HANA/SQLite do not support array-of - turn into CLOB/Text
108
+ items: (parent) => {
109
+ parent.type = 'cds.LargeString';
110
+ delete parent.items;
111
+ },
112
+ }, [ (definitions, artifactName, artifact) => {
113
+ // Replace events, actions and functions with simple dummies - they don't have effect on forHanaNew stuff
114
+ // and that way they contain no references and don't hurt.
115
+ if (artifact.kind === 'action' || artifact.kind === 'function' || artifact.kind === 'event') {
116
+ const dummy = { kind: artifact.kind };
117
+ if (artifact.$location)
118
+ setProp(dummy, '$location', artifact.$location);
119
+
120
+ definitions[artifactName] = dummy;
121
+ }
122
+ } ], true, { skipDict: { actions: true } });
123
+ }
124
+
125
+ /**
126
+ * @param {CSN.Model} csn
127
+ * @param {CSN.Options} options
128
+ * @param {WeakMap} resolved Cache for resolved refs
129
+ * @param {string} pathDelimiter
130
+ */
131
+ function flattenAllStructStepsInRefs(csn, options, resolved, pathDelimiter) {
132
+ const { inspectRef, effectiveType } = csnRefs(csn);
133
+ const { flattenStructStepsInRef } = transformUtils.getTransformers(csn, options, pathDelimiter);
134
+ const adaptRefs = [];
135
+
136
+ /**
137
+ * For each step of the links, check if there is a type reference.
138
+ * If there is, resolve it and store the result in a WeakMap.
139
+ *
140
+ * @param {Array} [links=[]]
141
+ * @todo seems too hacky
142
+ * @returns {WeakMap} A WeakMap where a link is the key and the type is the value
143
+ */
144
+ function resolveLinkTypes(links = []) {
145
+ const resolvedLinkTypes = new WeakMap();
146
+ links.forEach((link) => {
147
+ const { art } = link;
148
+ if (art && art.type)
149
+ resolvedLinkTypes.set(link, effectiveType(art));
150
+ });
151
+
152
+ return resolvedLinkTypes;
153
+ }
154
+
155
+ applyTransformations(csn, {
156
+ ref: (parent, prop, ref, path) => {
157
+ const { links, art, scope } = inspectRef(path);
158
+ const resolvedLinkTypes = resolveLinkTypes(links);
159
+ setProp(parent, '$path', [ ...path ]);
160
+ const lastRef = ref[ref.length - 1];
161
+ const fn = () => {
162
+ const scopedPath = [ ...parent.$path ];
163
+
164
+ parent.ref = flattenStructStepsInRef(ref, scopedPath, links, scope, resolvedLinkTypes );
165
+ resolved.set(parent, { links, art, scope });
166
+ // Explicitly set implicit alias for things that are now flattened - but only in columns
167
+ // TODO: Can this be done elegantly during expand phase already?
168
+ if (parent.$implicitAlias) { // an expanded s -> s.a is marked with this - do not add implicit alias "a" there, we want s_a
169
+ if (parent.ref[parent.ref.length - 1] === parent.as) // for a simple s that was expanded - for s.substructure this would not apply
170
+ delete parent.as;
171
+ delete parent.$implicitAlias;
172
+ }
173
+ // To handle explicitly written s.a - add implicit alias a, since after flattening it would otherwise be s_a
174
+ else if (parent.ref[parent.ref.length - 1] !== lastRef && (insideColumns(scopedPath) || insideKeys(scopedPath)) && !parent.as) {
175
+ parent.as = lastRef;
176
+ }
177
+ };
178
+ // adapt queries later
179
+ adaptRefs.push(fn);
180
+ },
181
+ });
182
+
183
+ adaptRefs.forEach(fn => fn());
184
+
185
+ /**
186
+ * Return true if the path points inside columns
187
+ *
188
+ * @param {CSN.Path} path
189
+ * @returns {boolean}
190
+ */
191
+ function insideColumns(path) {
192
+ return path.length >= 3 && (path[path.length - 3] === 'SELECT' || path[path.length - 3] === 'projection') && path[path.length - 2] === 'columns';
193
+ }
194
+ /**
195
+ * Return true if the path points inside keys
196
+ *
197
+ * @param {CSN.Path} path
198
+ * @returns {boolean}
199
+ */
200
+ function insideKeys(path) {
201
+ return path.length >= 3 && path[path.length - 2] === 'keys' && typeof path[path.length - 1] === 'number';
202
+ }
203
+ }
204
+
205
+ /**
206
+ * @param {CSN.Model} csn
207
+ * @param {CSN.Options} options
208
+ * @param {string} pathDelimiter
209
+ * @param {Function} error
210
+ */
211
+ function flattenElements(csn, options, pathDelimiter, error) {
212
+ const { isAssocOrComposition } = getUtils(csn);
213
+ const { flattenStructuredElement } = transformUtils.getTransformers(csn, options, pathDelimiter);
214
+ const { effectiveType } = csnRefs(csn);
215
+ forEachDefinition(csn, flattenStructuredElements);
216
+ /**
217
+ * Flatten structures
218
+ *
219
+ * @param {CSN.Artifact} art Artifact
220
+ * @param {string} artName Artifact Name
221
+ */
222
+ function flattenStructuredElements(art, artName) {
223
+ forAllElements(art, artName, (parent, elements, pathToElements) => {
224
+ const elementsArray = [];
225
+ for (const elemName in elements) {
226
+ const pathToElement = pathToElements.concat([ elemName ]);
227
+ const elem = parent.elements[elemName];
228
+ elementsArray.push([ elemName, elem ]);
229
+ if (elem.elements) {
230
+ elementsArray.pop();
231
+ // Ignore the structured element, replace it by its flattened form
232
+ // TODO: use $ignore - _ is for links
233
+ elem._ignore = true;
234
+
235
+ const branches = getBranches(elem, elemName);
236
+ const flatElems = flattenStructuredElement(elem, elemName, [], pathToElement);
237
+
238
+ for (const flatElemName in flatElems) {
239
+ if (parent.elements[flatElemName])
240
+ error(null, pathToElement, `"${artName}.${elemName}": Flattened struct element name conflicts with existing element: "${flatElemName}"`);
241
+
242
+ const flatElement = flatElems[flatElemName];
243
+
244
+ // Check if we have a valid notNull chain
245
+ const branch = branches[flatElemName];
246
+ if (flatElement.notNull !== false && !branch.some(s => !s.notNull))
247
+ flatElement.notNull = true;
248
+
249
+
250
+ if (flatElement.type && isAssocOrComposition(flatElement.type) && flatElement.on) {
251
+ // Make refs resolvable by fixing the first ref step
252
+ for (let i = 0; i < flatElement.on.length; i++) {
253
+ const onPart = flatElement.on[i];
254
+ if (onPart.ref) {
255
+ const firstRef = flatElement.on[i].ref[0];
256
+
257
+ /*
258
+ when element is defined in the current name resolution scope, like
259
+ entity E {
260
+ key x: Integer;
261
+ s : {
262
+ y : Integer;
263
+ a3 : association to E on a3.x = y;
264
+ }
265
+ }
266
+ We need to replace y with s_y and a3 with s_a3 - we must take care to not escape our local scope
267
+ */
268
+ const prefix = flatElement._flatElementNameWithDots.split('.').slice(0, -1).join(pathDelimiter);
269
+ const possibleFlatName = prefix + pathDelimiter + firstRef;
270
+
271
+ if (flatElems[possibleFlatName])
272
+ flatElement.on[i].ref[0] = possibleFlatName;
273
+ }
274
+ }
275
+ }
276
+ elementsArray.push([ flatElemName, flatElement ]);
277
+ // Still add them - otherwise we might not detect collisions between generated elements.
278
+ parent.elements[flatElemName] = flatElement;
279
+ }
280
+ }
281
+ }
282
+ // Don't fake consistency of the model by adding empty elements {}
283
+ if (elementsArray.length === 0)
284
+ return;
285
+
286
+ parent.elements = elementsArray.reduce((previous, [ name, element ]) => {
287
+ previous[name] = element;
288
+ return previous;
289
+ }, Object.create(null));
290
+ }, true);
291
+ }
292
+
293
+ /**
294
+ * Get not just the leafs, but all the branches of a structured element
295
+ *
296
+ * @param {object} element Structured element
297
+ * @param {string} elementName Name of the structured element
298
+ * @returns {object} Returns a dictionary, where the key is the flat name of the branch and the value is an array of element-steps.
299
+ */
300
+ function getBranches(element, elementName) {
301
+ const branches = {};
302
+ const subbranchNames = [];
303
+ const subbranchElements = [];
304
+ walkElements(element, elementName);
305
+ /**
306
+ * Walk the element chain
307
+ *
308
+ * @param {CSN.Element} e
309
+ * @param {string} name
310
+ */
311
+ function walkElements(e, name) {
312
+ if (isBuiltinType(e)) {
313
+ branches[subbranchNames.concat(name).join(pathDelimiter)] = subbranchElements.concat(e);
314
+ }
315
+ else {
316
+ const eType = effectiveType(e);
317
+ const subelements = e.elements || eType.elements;
318
+ if (subelements) {
319
+ subbranchElements.push(e);
320
+ subbranchNames.push(name);
321
+ for (const [ subelementName, subelement ] of Object.entries(subelements))
322
+ walkElements(subelement, subelementName);
323
+
324
+ subbranchNames.pop();
325
+ subbranchElements.pop();
326
+ }
327
+ else {
328
+ branches[subbranchNames.concat(name).join(pathDelimiter)] = subbranchElements.concat(e);
329
+ }
330
+ }
331
+ }
332
+ return branches;
333
+ }
334
+ }
335
+
336
+ module.exports = {
337
+ resolveTypeReferences,
338
+ flattenAllStructStepsInRefs,
339
+ flattenElements,
340
+ removeLeadingSelf,
341
+ };
@@ -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”');