@sap/cds-compiler 3.6.2 → 3.8.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 (89) hide show
  1. package/CHANGELOG.md +109 -1
  2. package/README.md +3 -0
  3. package/bin/cdsc.js +12 -5
  4. package/doc/CHANGELOG_ARCHIVE.md +6 -6
  5. package/doc/CHANGELOG_BETA.md +35 -2
  6. package/doc/CHANGELOG_DEPRECATED.md +2 -2
  7. package/doc/DeprecatedOptions_v2.md +1 -1
  8. package/doc/NameResolution.md +1 -1
  9. package/lib/api/main.js +63 -23
  10. package/lib/api/options.js +1 -0
  11. package/lib/api/validate.js +5 -0
  12. package/lib/base/dictionaries.js +15 -3
  13. package/lib/base/keywords.js +2 -0
  14. package/lib/base/message-registry.js +120 -34
  15. package/lib/base/messages.js +51 -27
  16. package/lib/base/model.js +4 -2
  17. package/lib/base/shuffle.js +2 -1
  18. package/lib/checks/arrayOfs.js +1 -1
  19. package/lib/checks/defaultValues.js +1 -1
  20. package/lib/checks/elements.js +29 -1
  21. package/lib/checks/{emptyOrOnlyVirtual.js → hasPersistedElements.js} +10 -6
  22. package/lib/checks/invalidTarget.js +1 -1
  23. package/lib/checks/nonexpandableStructured.js +1 -1
  24. package/lib/checks/onConditions.js +15 -9
  25. package/lib/checks/sql-snippets.js +2 -2
  26. package/lib/checks/types.js +5 -1
  27. package/lib/checks/validator.js +7 -3
  28. package/lib/compiler/assert-consistency.js +42 -26
  29. package/lib/compiler/base.js +50 -4
  30. package/lib/compiler/builtins.js +17 -8
  31. package/lib/compiler/checks.js +241 -246
  32. package/lib/compiler/define.js +113 -146
  33. package/lib/compiler/extend.js +889 -383
  34. package/lib/compiler/finalize-parse-cdl.js +5 -58
  35. package/lib/compiler/index.js +1 -1
  36. package/lib/compiler/kick-start.js +7 -8
  37. package/lib/compiler/populate.js +297 -293
  38. package/lib/compiler/propagator.js +27 -18
  39. package/lib/compiler/resolve.js +146 -463
  40. package/lib/compiler/shared.js +36 -79
  41. package/lib/compiler/tweak-assocs.js +30 -28
  42. package/lib/compiler/utils.js +31 -5
  43. package/lib/edm/annotations/genericTranslation.js +131 -59
  44. package/lib/edm/annotations/preprocessAnnotations.js +3 -0
  45. package/lib/edm/csn2edm.js +22 -5
  46. package/lib/edm/edm.js +6 -4
  47. package/lib/edm/edmAnnoPreprocessor.js +1 -0
  48. package/lib/edm/edmPreprocessor.js +42 -26
  49. package/lib/gen/Dictionary.json +38 -2
  50. package/lib/gen/language.checksum +1 -1
  51. package/lib/gen/language.interp +3 -1
  52. package/lib/gen/languageLexer.js +1 -1
  53. package/lib/gen/languageParser.js +4828 -4472
  54. package/lib/inspect/inspectPropagation.js +20 -34
  55. package/lib/json/from-csn.js +140 -44
  56. package/lib/json/to-csn.js +114 -122
  57. package/lib/language/errorStrategy.js +2 -0
  58. package/lib/language/genericAntlrParser.js +156 -36
  59. package/lib/language/language.g4 +100 -58
  60. package/lib/language/textUtils.js +13 -0
  61. package/lib/main.d.ts +43 -3
  62. package/lib/main.js +4 -2
  63. package/lib/model/csnRefs.js +15 -3
  64. package/lib/model/csnUtils.js +12 -74
  65. package/lib/model/revealInternalProperties.js +4 -2
  66. package/lib/modelCompare/compare.js +2 -1
  67. package/lib/optionProcessor.js +3 -0
  68. package/lib/render/manageConstraints.js +5 -2
  69. package/lib/render/toCdl.js +216 -104
  70. package/lib/render/toHdbcds.js +2 -9
  71. package/lib/render/toRename.js +14 -51
  72. package/lib/render/toSql.js +4 -3
  73. package/lib/render/utils/common.js +9 -5
  74. package/lib/transform/braceExpression.js +6 -0
  75. package/lib/transform/db/assertUnique.js +2 -1
  76. package/lib/transform/db/expansion.js +2 -0
  77. package/lib/transform/db/flattening.js +37 -36
  78. package/lib/transform/db/rewriteCalculatedElements.js +600 -0
  79. package/lib/transform/db/transformExists.js +4 -0
  80. package/lib/transform/db/views.js +40 -37
  81. package/lib/transform/forOdataNew.js +20 -15
  82. package/lib/transform/forRelationalDB.js +58 -41
  83. package/lib/transform/odata/typesExposure.js +50 -15
  84. package/lib/transform/parseExpr.js +16 -8
  85. package/lib/transform/transformUtilsNew.js +42 -14
  86. package/lib/transform/translateAssocsToJoins.js +60 -37
  87. package/lib/transform/universalCsn/coreComputed.js +15 -7
  88. package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
  89. package/package.json +2 -1
@@ -0,0 +1,600 @@
1
+ 'use strict';
2
+
3
+ const { setProp } = require('../../base/model');
4
+ const { CompilerAssertion } = require('../../base/error');
5
+ const {
6
+ forEachDefinition, applyTransformationsOnNonDictionary, applyTransformationsOnDictionary, implicitAs, cloneCsnNonDict, getUtils,
7
+ } = require('../../model/csnUtils');
8
+ const { getBranches } = require('./flattening');
9
+ const { getColumnMap } = require('./views');
10
+
11
+ const cloneCsnOptions = { hiddenPropertiesToClone: [ '_art', '_links', '$env', '$scope' ] };
12
+ /**
13
+ * Rewrite usage of calculated Elements into the expression itself.
14
+ * Delete calculated elements in entities after processing so they don't materialize on the db.
15
+ *
16
+ * @param {CSN.Model} csn
17
+ * @param {CSN.Options} options
18
+ * @param {string} pathDelimiter
19
+ * @param {Function} error
20
+ */
21
+ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error ) {
22
+ const { inspectRef, effectiveType } = getUtils(csn, 'init-all');
23
+
24
+ const views = [];
25
+ const entities = [];
26
+
27
+ // In this first pass, we rewrite all the .value things in tables into their most basic form
28
+ forEachDefinition(csn, (artifact, artifactName) => {
29
+ if (artifact.kind === 'entity') {
30
+ if (artifact.query || artifact.projection) {
31
+ views.push({ artifact, artifactName });
32
+ }
33
+ else {
34
+ rewriteInEntity(artifact);
35
+ entities.push({ artifact, artifactName });
36
+ }
37
+ }
38
+ });
39
+
40
+ // Replace calculated elements in filters (if the root-association element is in an entity).
41
+ // Depends on the first pass!
42
+ entities.forEach(({ artifactName }) => {
43
+ applyTransformationsOnNonDictionary(csn.definitions, artifactName, {
44
+ where: (parent, prop) => {
45
+ applyTransformationsOnNonDictionary(parent, prop, {
46
+ ref: (_parent, _prop, ref, _path, root, index) => {
47
+ if (_parent._art && _parent._art.value) {
48
+ root[index] = _parent._art.value;
49
+ applyTransformationsOnNonDictionary(root, index, {
50
+ ref: (__parent, _, _ref) => {
51
+ if (_ref[0] === '$self' || _ref[0] === '$projection')
52
+ __parent.ref = _ref.slice(-1);
53
+ },
54
+ });
55
+ }
56
+ },
57
+ });
58
+ },
59
+ }, { drillRef: true }, [ 'definitions' ]);
60
+ });
61
+
62
+
63
+ // In this third pass, we process our views, generate .columns if needed and replace usage
64
+ // of calculated elements with their respective `.value`.
65
+ // This depends on the first pass!
66
+ views.forEach(({ artifact, artifactName }) => {
67
+ applyTransformationsOnNonDictionary(csn.definitions, artifactName, {
68
+ SELECT: (parent, prop, SELECT, path) => {
69
+ rewriteInView(SELECT, SELECT.elements || artifact.elements, path);
70
+ },
71
+ projection: (parent, prop, projection, path) => {
72
+ parent.SELECT = projection; // Fake as SELECT so our path below will match in the applyTransformations...
73
+ rewriteInView(parent.SELECT, artifact.elements, path);
74
+ delete parent.SELECT;
75
+ },
76
+ }, {}, [ 'definitions' ]);
77
+ });
78
+
79
+ // Last pass, turn .value in tables into a simple val: 1 so we don't need to rewrite/flatten properly - will kill them later
80
+ entities.forEach(({ artifact, artifactName }) => {
81
+ dummifyInEntity(artifact, [ 'definitions', artifactName ]);
82
+ });
83
+
84
+ /**
85
+ * Rewrite calculated-elements-columns in views/projections and replace them
86
+ * with their "root"-expression.
87
+ *
88
+ * As a first step, we ensure that all views/projections have a .columns (see {@link calculateColumns}) and that
89
+ * all calculated elements are addressed explicitly and not via a * (see {@link makeAllCalculatedElementsExplicitColumns}).
90
+ *
91
+ * Then, we check the `art` of each ref for a `.value` and rewrite accordingly.
92
+ * We need to ensure that the scope of the rewritten expressions is still correct!
93
+ * An `id` in the `.value` needs to point to the entity containing the element,
94
+ * not to some random view element named `id`. See {@link absolutifyPaths} for
95
+ * details on that.
96
+ *
97
+ * @param {CSN.QuerySelect} SELECT
98
+ * @param {CSN.Elements} elements
99
+ * @param {CSN.Path} path
100
+ */
101
+ function rewriteInView( SELECT, elements, path ) {
102
+ const containsExpandInline = hasExpandInline(SELECT);
103
+ if (!SELECT.columns) // needs to happen for all subqueries!
104
+ calculateColumns(elements, SELECT);
105
+ else
106
+ makeAllCalculatedElementsExplicitColumns(elements, SELECT, containsExpandInline);
107
+
108
+ const name = SELECT.from.args ? undefined : SELECT.from.as || (SELECT.from.ref && implicitAs(SELECT.from.ref));
109
+
110
+ if (!containsExpandInline) {
111
+ applyTransformationsOnNonDictionary({ SELECT }, 'SELECT', {
112
+ ref: (parent, prop, ref, p, root) => {
113
+ const {
114
+ art, env, links, scope,
115
+ } = getRefInfo(parent, p);
116
+
117
+ if (art?.value) {
118
+ const alias = parent.as || implicitAs(parent.ref);
119
+ // TODO: What about other scopes? expand/inline?
120
+ const value = (scope !== 'ref-target') ? absolutifyPaths(env, art, ref, links, name).value : keepAssocStepsInRef(ref, links, art).value;
121
+
122
+ // Is a shallow copy enough?
123
+ if (art.value.cast)
124
+ root[p[p.length - 1]] = { xpr: [ value ] };
125
+ else
126
+ root[p[p.length - 1]] = { ...value };
127
+
128
+ if (p[p.length - 2] === 'columns')
129
+ root[p[p.length - 1]].as = alias;
130
+ else
131
+ delete root[p[p.length - 1]].as;
132
+ }
133
+ },
134
+ }, {}, path);
135
+ }
136
+ }
137
+
138
+ /**
139
+ *
140
+ * @param {CSN.QuerySelect} SELECT
141
+ * @returns {boolean}
142
+ */
143
+ function hasExpandInline( SELECT ) {
144
+ if (!SELECT.columns)
145
+ return false;
146
+
147
+ for (const column of SELECT.columns) {
148
+ if (column.expand || column.inline)
149
+ return true;
150
+ }
151
+
152
+ return false;
153
+ }
154
+
155
+ /**
156
+ * Replace all nested .value things (in .xpr, in .ref) with their most-direct thing:
157
+ * - A ref to a non-calculated element
158
+ * - A .val
159
+ * - An expression containing the above
160
+ *
161
+ * @param {CSN.Artifact} artifact The artifact currently being processed
162
+ */
163
+ function rewriteInEntity( artifact ) {
164
+ applyTransformationsOnDictionary(artifact.elements, {
165
+ value: (parent, prop, value) => {
166
+ replaceValuesWithBaseValue(parent, value);
167
+ },
168
+ });
169
+ }
170
+
171
+ /**
172
+ * Iteratively replace all .values with the most-basic form:
173
+ * - a .val thing
174
+ * - a .ref to a non-value thing
175
+ *
176
+ * @param {object | Array} parent
177
+ * @param {object} value
178
+ */
179
+ function replaceValuesWithBaseValue( parent, value ) {
180
+ if (value.val && parent.value === value)
181
+ return;
182
+
183
+ const stack = [ { parent, value } ];
184
+ while (stack.length > 0) {
185
+ const current = stack.pop();
186
+
187
+ if (current.value.xpr) {
188
+ applyTransformationsOnNonDictionary(current.value, 'xpr', {
189
+ ref: (p, prop, ref, path, root ) => {
190
+ stack.push({
191
+ parent: root,
192
+ value: p,
193
+ isInXpr: true,
194
+ refBase: current.refBase,
195
+ linksBase: current.linksBase,
196
+ });
197
+ },
198
+ });
199
+ }
200
+ else if (current.value.ref && current.value._art?.value) {
201
+ const linksBase = current.value._links;
202
+ const refBase = current.value.ref;
203
+ const parentIndex = Array.isArray(current.parent) ? current.parent.indexOf(current.value) : -1;
204
+
205
+ replaceInRef(current.parent, current.value._art.value, current.isInXpr, refBase, linksBase, parentIndex);
206
+
207
+ stack.push(Object.assign(current, {
208
+ value: parentIndex > -1 ? current.parent[parentIndex] : current.parent.value,
209
+ refBase,
210
+ linksBase,
211
+ }));
212
+ }
213
+ // No need for cloning here, as we don't rewrite this further and will later on kill all the stuff anyway
214
+ else if (current.value.val) { // this is the base case - or a ref to a non-calculated element
215
+ if (current.isInXpr) { // inside of expressions we directly need the val
216
+ current.parent.val = current.value.val;
217
+ delete current.parent.value;
218
+ }
219
+ else { // outside of expressions, i.e. as normal elements, we need it in a .value wrapper
220
+ current.parent.value = current.value;
221
+ }
222
+ }
223
+ }
224
+ }
225
+
226
+ /**
227
+ * A value referenced via a ref is replaced here
228
+ * - kill the ref
229
+ * - explicitly mention the value
230
+ *
231
+ * We either "trick" it into the correct place in an .xpr or we simply overwrite the existing .ref
232
+ *
233
+ * @param {object} parent
234
+ * @param {object} newValue
235
+ * @param {boolean} isInXpr
236
+ * @param {Array} refBase
237
+ * @param {Array} linksBase
238
+ * @param {number} indexInParent
239
+ */
240
+ function replaceInRef( parent, newValue, isInXpr, refBase, linksBase, indexInParent ) {
241
+ delete parent.ref;
242
+ const clone = {
243
+ value: cloneCsnNonDict({ value: newValue }, cloneCsnOptions).value,
244
+ };
245
+ const refPrefix = refBase.slice(0, -1);
246
+ const linksPrefix = linksBase.slice(0, -1);
247
+ if (newValue.xpr) {
248
+ // We need to adapt the scope of all refs in the new .xpr, as it might have been at a different "root"
249
+ applyTransformationsOnNonDictionary(clone, 'value', {
250
+ ref: (p, prop, ref) => {
251
+ if (ref[0] !== '$self' && ref[0] !== '$projection') {
252
+ p.ref = [ ...refPrefix, ...ref ];
253
+ if (p._links)
254
+ p._links = [ ...linksPrefix, ...p._links ]; // TODO: Make non-enum, increment idx
255
+ }
256
+ },
257
+ }, {
258
+ // Do not rewrite refs inside of an association-where; avoids endless loop
259
+ skipStandard: { where: true },
260
+ });
261
+ if (indexInParent > -1) // a .xpr in a .xpr
262
+ parent[indexInParent] = clone.value;
263
+ else
264
+ parent.value = clone.value;
265
+ }
266
+ else {
267
+ if (indexInParent > -1) // a .ref in a .xpr
268
+ parent[indexInParent] = clone.value;
269
+ else
270
+ parent.value = clone.value;
271
+ if (clone.value.ref && clone.value.ref[0] !== '$self' && clone.value.ref[0] !== '$projection' ) {
272
+ clone.value.ref = [ ...refPrefix, ...clone.value.ref ];
273
+ clone.value._links = [ ...linksPrefix, ...clone.value._links ]; // TODO: Make non-enum, increment idx
274
+ }
275
+ }
276
+ }
277
+
278
+ /**
279
+ * For a `view V as select from E;` or a `entity P as projection on E;` calculate and
280
+ * attach the .columns if they contain a calculated element so we can rewrite them in
281
+ * the later steps.
282
+ *
283
+ * @param {CSN.Elements} elements Artifact elements
284
+ * @param {object} carrier The thing that will "carry" the columns - .SELECT or .projection
285
+ */
286
+ function calculateColumns( elements, carrier ) {
287
+ carrier.columns = [ '*' ];
288
+ makeAllCalculatedElementsExplicitColumns(elements, carrier, false);
289
+ if (carrier.columns.length === 1 && carrier.columns[0] === '*')
290
+ delete carrier.columns;
291
+ }
292
+
293
+ /**
294
+ *
295
+ * @param {CSN.QuerySelect} SELECT
296
+ * @returns {object}
297
+ */
298
+ function getDirectlyAdressableElements( SELECT ) {
299
+ const { from } = SELECT;
300
+ if (from.ref) {
301
+ return from._art.elements;
302
+ }
303
+ else if (from.SELECT) {
304
+ return from.SELECT.elements;
305
+ }
306
+ else if (from.SET) {
307
+ // FIXME: Check if this is correct
308
+ // args[0] could be SELECT or UNION
309
+ return getDirectlyAdressableElements({ from: from.SET.args[0] });
310
+ }
311
+ else if (from.args) {
312
+ const mergedElements = Object.create(null);
313
+ for (const arg of from.args) {
314
+ if (arg.ref) {
315
+ for (const elementName in arg._art.elements)
316
+ mergedElements[elementName] = arg._art.elements[elementName];
317
+ }
318
+ else if (arg.SET) {
319
+ // FIXME: Check if this is correct
320
+ return getDirectlyAdressableElements({ from: arg.SET.args[0] });
321
+ }
322
+ else if (arg.SELECT) { // TODO: UNION
323
+ for (const elementName in arg.SELECT.elements)
324
+ mergedElements[elementName] = arg.SELECT.elements[elementName];
325
+ }
326
+ else if (arg.args) { // TODO: Is it safe to do recursion here?
327
+ for (const subarg of arg.args) {
328
+ const elements = getDirectlyAdressableElements({ from: subarg });
329
+ for (const elementName in elements)
330
+ mergedElements[elementName] = elements[elementName];
331
+ }
332
+ }
333
+ else {
334
+ throw new CompilerAssertion(`Unhandled arg type: ${JSON.stringify(arg, null, 2)}`);
335
+ }
336
+ }
337
+ return mergedElements;
338
+ }
339
+ throw new CompilerAssertion(`Unhandled query type: ${JSON.stringify(SELECT, null, 2)}`);
340
+ }
341
+
342
+ /**
343
+ * Ensure that all elements of the query that are calculated elements have an explicit column that we can rewrite.
344
+ * If a field originally comes in via the *, then we need to add an explicit column for it.
345
+ *
346
+ * @param {CSN.Elements} elements
347
+ * @param {CSN.QuerySelect} SELECT
348
+ * @param {boolean} containsExpandInline
349
+ */
350
+ function makeAllCalculatedElementsExplicitColumns( elements, SELECT, containsExpandInline ) {
351
+ const root = getDirectlyAdressableElements(SELECT);
352
+ const columnMap = getColumnMap( { SELECT });
353
+ const hasStar = SELECT.columns.includes('*');
354
+ const unfoldingMap = {};
355
+ let starContainsCalculated = false;
356
+ let containsCalculated = false;
357
+ for (const name in elements) {
358
+ const originalRef = columnMap[name] && columnMap[name].ref || [ name ];
359
+
360
+ if (columnMap[name] || hasStar) {
361
+ let element;
362
+ if (columnMap[name]?.expand || columnMap[name]?.inline)
363
+ element = elements[name]; // only the direct thing in .elements has the .excluding respected properly!
364
+ else
365
+ element = columnMap[name]?._art || columnMap[name]?._element || root[name] || elements[name];
366
+ const branches = getBranches(element, name, effectiveType, pathDelimiter); // TODO: is our elements[name] really the root[name]?
367
+ if (hasCalculatedLeaf(branches)) {
368
+ containsCalculated = true;
369
+ const columns = [];
370
+ for (const branchName in branches) {
371
+ if (columnMap[branchName]) // Existing column - don't overwrite, we need $env!
372
+ columns.push(columnMap[branchName]);
373
+ else // TODO: Hm, will we have a $env in the leaf of the thing then?
374
+ columns.push({ ref: [ ...originalRef, ...branches[branchName].ref.slice(1) ], as: branchName });
375
+ }
376
+ if (columnMap[name]) {
377
+ unfoldingMap[name] = [ false, [ ...columns ] ];
378
+ }
379
+ else if (hasStar) { // Via * - just append
380
+ starContainsCalculated = true;
381
+ unfoldingMap[name] = [ true, [ ...columns ] ];
382
+ }
383
+ }
384
+ else if (!columnMap[name] && hasStar) { // Via * - just append
385
+ unfoldingMap[name] = [ true, [ { ref: [ name ] } ] ];
386
+ }
387
+ else { // just a random column - keep
388
+ unfoldingMap[name] = [ false, [ columnMap[name] ] ];
389
+ }
390
+ }
391
+ }
392
+
393
+ if (containsExpandInline && containsCalculated) {
394
+ error('query-unsupported-calc', SELECT.$path, { '#': 'std' });
395
+ }
396
+ else if (containsCalculated) {
397
+ const newColumns = [];
398
+ if (hasStar && !starContainsCalculated)
399
+ newColumns.push('*');
400
+ for (const name in elements) {
401
+ const [ isViaStar, columns ] = unfoldingMap[name];
402
+ if (isViaStar && starContainsCalculated || !isViaStar)
403
+ newColumns.push(...columns);
404
+ }
405
+
406
+
407
+ SELECT.columns = newColumns;
408
+ }
409
+ }
410
+
411
+ /**
412
+ *
413
+ * @param {object} branches
414
+ * @returns {boolean}
415
+ */
416
+ function hasCalculatedLeaf( branches ) {
417
+ for (const branchName in branches) {
418
+ const branch = branches[branchName].steps;
419
+ const leaf = branch[branch.length - 1];
420
+ if (hasValue(leaf))
421
+ return true;
422
+ }
423
+
424
+ return false;
425
+ }
426
+
427
+ /**
428
+ * A leaf can reference a column which in turn references a real element - that might have a .value.
429
+ * Find such cases.
430
+ *
431
+ * @param {object} baseLeaf Leaf to start at
432
+ * @returns {boolean}
433
+ */
434
+ function hasValue( baseLeaf ) {
435
+ const visited = new WeakSet();
436
+ const stack = [ baseLeaf ];
437
+ while (stack.length > 0) {
438
+ const leaf = stack.pop();
439
+ if (!visited.has(leaf)) { // Don't re-process things
440
+ if (leaf.value)
441
+ return true;
442
+ else if (leaf._art)
443
+ stack.push(leaf._art);
444
+ else if (leaf['@Core.Computed'] && leaf._column && leaf._column !== baseLeaf)
445
+ stack.push(leaf._column);
446
+ }
447
+
448
+ visited.add(leaf);
449
+ }
450
+
451
+ return false;
452
+ }
453
+
454
+ /**
455
+ * We need to keep association steps in front of the paths - else they would lead into nothing
456
+ *
457
+ * @param {Array} artRef
458
+ * @param {Array} links
459
+ * @param {object} art
460
+ * @returns {object}
461
+ */
462
+ function keepAssocStepsInRef( artRef, links, art ) {
463
+ let lastAssocIndex = -1;
464
+ for (let i = links.length - 1; i > -1; i--) {
465
+ if (links[i].art.target) {
466
+ lastAssocIndex = i;
467
+ break;
468
+ }
469
+ }
470
+
471
+ if (lastAssocIndex > -1) {
472
+ const clone = { value: cloneCsnNonDict(art.value, cloneCsnOptions) };
473
+ applyTransformationsOnNonDictionary(clone, 'value', {
474
+ ref: (parent, prop, ref) => {
475
+ parent.ref = [ ...artRef.slice(0, lastAssocIndex + 1), ...ref ];
476
+ if (parent._links)
477
+ parent._links = [ ...links.slice(0, lastAssocIndex + 1), ...parent._links ];
478
+ },
479
+ }, {
480
+ skipStandard: { where: true }, // Do not rewrite refs inside of an association-where
481
+ });
482
+
483
+ return clone;
484
+ }
485
+
486
+ return art;
487
+ }
488
+
489
+ /**
490
+ * In order to just replace them in views, our calculated elements need to reference absolute things, i.e. have a table alias in front!
491
+ *
492
+ * @param {string | object} env
493
+ * @param {object} art
494
+ * @param {Array} artRef
495
+ * @param {Array} artLinks
496
+ * @param {string|undefined} name
497
+ * @todo this is probably very wonky and will break with some view hierarchy stuff etc!
498
+ * @returns {object}
499
+ */
500
+ function absolutifyPaths( env, art, artRef, artLinks, name ) {
501
+ const clone = { value: cloneCsnNonDict(art.value, cloneCsnOptions) };
502
+ applyTransformationsOnNonDictionary(clone, 'value', {
503
+ ref: (parent, prop, ref) => {
504
+ const artifactName = typeof env === 'string' ? env : name;
505
+ if (parent._links) {
506
+ if (parent._links[0].art.kind !== 'entity') {
507
+ if (artLinks[0].art.kind === 'entity' || artifactName === undefined) {
508
+ parent.ref = [ ...artRef.slice(0, -1), ...ref ];
509
+ setProp(parent, '_links', [ ...artLinks.slice(0, -1), ...parent._links ]); // TODO: increment idx
510
+ }
511
+ else {
512
+ parent.ref = [ artifactName, ...artRef.slice(0, -1), ...ref ];
513
+ setProp(parent, '_links', [ { idx: 0 }, ...artLinks.slice(0, -1), ...parent._links ]); // TODO: increment idx
514
+ }
515
+ }
516
+ else if (parent.$scope === '$self') {
517
+ if (artifactName !== undefined)
518
+ parent.ref[0] = artifactName;
519
+ else
520
+ parent.ref = parent.ref.slice(-1);
521
+ }
522
+ }
523
+ },
524
+ }, {
525
+ skipStandard: { where: true }, // Do not rewrite refs inside of an association-where
526
+ });
527
+
528
+ return clone;
529
+ }
530
+
531
+ /**
532
+ * Get the ref-info
533
+ * - either the cached _art etc.
534
+ * - or calculate using inspectRef
535
+ *
536
+ * @param {object} parent
537
+ * @param {CSN.Path} path
538
+ * @returns {object}
539
+ */
540
+ function getRefInfo( parent, path ) {
541
+ if (parent._art) {
542
+ return {
543
+ art: parent._art,
544
+ env: parent.$env,
545
+ links: parent._links,
546
+ scope: parent.$scope,
547
+ };
548
+ }
549
+
550
+ return inspectRef(path);
551
+ }
552
+ }
553
+
554
+ /**
555
+ * @param {CSN.Model} csn
556
+ * @param {CSN.Options} _options
557
+ */
558
+ function processCalculatedElementsInEntities( csn, _options ) {
559
+ forEachDefinition(csn, (artifact, artifactName) => {
560
+ if (artifact.kind === 'entity' && !(artifact.query || artifact.projection))
561
+ killInEntity(artifact, [ 'definitions', artifactName ]);
562
+ });
563
+ }
564
+
565
+
566
+ /**
567
+ * In an entity, remove all instances of calculated elements.
568
+ *
569
+ * @param {CSN.Artifact} artifact
570
+ * @param {CSN.Path} path
571
+ * @todo calculated elements that "live" on the database?
572
+ * @todo error when artifact is empty afterwards? Probably better as a CSN check!
573
+ */
574
+ function killInEntity( artifact, path ) {
575
+ applyTransformationsOnDictionary(artifact.elements, {
576
+ value: (parent, prop, value, p, root) => {
577
+ delete root[p[p.length - 1]];
578
+ },
579
+ }, {}, path);
580
+ }
581
+
582
+ /**
583
+ * In an entity, turn all instances of calculated elements into an = 1. This way,
584
+ * we don't have to rewrite any scope there and can kill them after A2J, see {@link processCalculatedElementsInEntities}.
585
+ *
586
+ * @param {CSN.Artifact} artifact
587
+ * @param {CSN.Path} path
588
+ */
589
+ function dummifyInEntity( artifact, path ) {
590
+ applyTransformationsOnDictionary(artifact.elements, {
591
+ value: (parent) => {
592
+ parent.value = { val: 1 };
593
+ },
594
+ }, {}, path);
595
+ }
596
+
597
+ module.exports = {
598
+ rewriteCalculatedElementsInViews,
599
+ processCalculatedElementsInEntities,
600
+ };
@@ -724,6 +724,10 @@ function handleExists( csn, options, error, inspectRef, initDefinition, dropDefi
724
724
  where: [],
725
725
  },
726
726
  };
727
+
728
+ setProp(subselect.SELECT.from, '_art', csn.definitions[target]);
729
+ setProp(subselect.SELECT.from, '_links', [ { idx: 0, art: csn.definitions[target] } ]);
730
+
727
731
  // Because the generated things don't have _links, _art etc. set
728
732
  // We could also make getParent more robust to calculate the links JIT if they are missing
729
733
  generatedExists.set(subselect, true);
@@ -115,44 +115,7 @@ function getViewTransformer( csn, options, messageFunctions, transformCommon ) {
115
115
  }
116
116
  }
117
117
  }
118
- /**
119
- * Build a map of the resulting names (i.e. the element name of the column) and references to the respective columns
120
- *
121
- * This can later be used to match from elements to columns.
122
- *
123
- * @param {CSN.Query} query
124
- * @returns {object}
125
- */
126
- function getColumnMap( query ) {
127
- const map = Object.create(null);
128
- if (query && query.SELECT && query.SELECT.columns) {
129
- query.SELECT.columns.forEach((col) => {
130
- if (col === '*') {
131
- // do nothing
132
- }
133
- else if (col.as) {
134
- if (!map[col.as])
135
- map[col.as] = col;
136
- }
137
- else if (col.ref) {
138
- // .id on last path step can happen with hdbcds.hdbcds and malicious CSN input - maybe also with params?
139
- // We made things right in the end with the second add of missing stuff, but why not do it
140
- // right from the getgo
141
- const last = getLastRefStepString(col.ref);
142
- if (!map[last])
143
- map[last] = col;
144
- }
145
- else if (col.func) {
146
- map[col.func] = col;
147
- }
148
- else if (!map[col]) {
149
- map[col] = col;
150
- }
151
- });
152
- }
153
118
 
154
- return map;
155
- }
156
119
  /**
157
120
  * For things that are not explicitly found in the columns but still present in the elements, add them to the columnMap.
158
121
  *
@@ -513,6 +476,46 @@ function getLastRefStepString( ref ) {
513
476
  return last;
514
477
  }
515
478
 
479
+ /**
480
+ * Build a map of the resulting names (i.e. the element name of the column) and references to the respective columns
481
+ *
482
+ * This can later be used to match from elements to columns.
483
+ *
484
+ * @param {CSN.Query} query
485
+ * @returns {object}
486
+ */
487
+ function getColumnMap( query ) {
488
+ const map = Object.create(null);
489
+ if (query && query.SELECT && query.SELECT.columns) {
490
+ query.SELECT.columns.forEach((col) => {
491
+ if (col === '*') {
492
+ // do nothing
493
+ }
494
+ else if (col.as) {
495
+ if (!map[col.as])
496
+ map[col.as] = col;
497
+ }
498
+ else if (col.ref) {
499
+ // .id on last path step can happen with hdbcds.hdbcds and malicious CSN input - maybe also with params?
500
+ // We made things right in the end with the second add of missing stuff, but why not do it
501
+ // right from the getgo
502
+ const last = getLastRefStepString(col.ref);
503
+ if (!map[last])
504
+ map[last] = col;
505
+ }
506
+ else if (col.func) {
507
+ map[col.func] = col;
508
+ }
509
+ else if (!map[col]) {
510
+ map[col] = col;
511
+ }
512
+ });
513
+ }
514
+
515
+ return map;
516
+ }
517
+
516
518
  module.exports = {
517
519
  getViewTransformer,
520
+ getColumnMap,
518
521
  };