@sap/cds-compiler 4.2.2 → 4.3.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 (66) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/bin/cdsc.js +8 -0
  3. package/bin/cdshi.js +3 -3
  4. package/doc/CHANGELOG_BETA.md +7 -0
  5. package/lib/api/main.js +19 -0
  6. package/lib/base/location.js +16 -0
  7. package/lib/base/message-registry.js +47 -16
  8. package/lib/base/messages.js +49 -38
  9. package/lib/base/model.js +2 -1
  10. package/lib/checks/checkPathsInStoredCalcElement.js +83 -0
  11. package/lib/checks/existsExpressionsOnlyForeignKeys.js +71 -0
  12. package/lib/checks/existsMustEndInAssoc.js +27 -0
  13. package/lib/checks/onConditions.js +47 -1
  14. package/lib/checks/validator.js +10 -1
  15. package/lib/compiler/assert-consistency.js +23 -15
  16. package/lib/compiler/base.js +31 -14
  17. package/lib/compiler/builtins.js +21 -20
  18. package/lib/compiler/checks.js +36 -49
  19. package/lib/compiler/define.js +71 -91
  20. package/lib/compiler/extend.js +27 -25
  21. package/lib/compiler/finalize-parse-cdl.js +1 -1
  22. package/lib/compiler/generate.js +67 -87
  23. package/lib/compiler/kick-start.js +7 -5
  24. package/lib/compiler/populate.js +32 -30
  25. package/lib/compiler/propagator.js +2 -0
  26. package/lib/compiler/resolve.js +29 -25
  27. package/lib/compiler/shared.js +57 -31
  28. package/lib/compiler/tweak-assocs.js +203 -22
  29. package/lib/compiler/utils.js +0 -18
  30. package/lib/gen/Dictionary.json +10 -4
  31. package/lib/gen/language.checksum +1 -1
  32. package/lib/gen/languageParser.js +3 -3
  33. package/lib/inspect/inspectPropagation.js +2 -1
  34. package/lib/json/from-csn.js +63 -28
  35. package/lib/json/to-csn.js +23 -13
  36. package/lib/language/antlrParser.js +1 -1
  37. package/lib/language/errorStrategy.js +5 -1
  38. package/lib/language/genericAntlrParser.js +67 -61
  39. package/lib/main.d.ts +26 -1
  40. package/lib/main.js +2 -1
  41. package/lib/model/csnRefs.js +1 -0
  42. package/lib/model/csnUtils.js +28 -0
  43. package/lib/model/revealInternalProperties.js +3 -9
  44. package/lib/optionProcessor.js +17 -1
  45. package/lib/render/toCdl.js +1 -1
  46. package/lib/transform/db/associations.js +3 -4
  47. package/lib/transform/db/backlinks.js +293 -0
  48. package/lib/transform/db/expansion.js +18 -7
  49. package/lib/transform/db/flattening.js +3 -2
  50. package/lib/transform/db/rewriteCalculatedElements.js +1 -67
  51. package/lib/transform/db/transformExists.js +3 -58
  52. package/lib/transform/db/views.js +8 -14
  53. package/lib/transform/effective/.eslintrc.json +4 -0
  54. package/lib/transform/effective/associations.js +101 -0
  55. package/lib/transform/effective/main.js +88 -0
  56. package/lib/transform/effective/misc.js +61 -0
  57. package/lib/transform/effective/queries.js +42 -0
  58. package/lib/transform/effective/types.js +121 -0
  59. package/lib/transform/forRelationalDB.js +12 -235
  60. package/lib/transform/localized.js +22 -3
  61. package/lib/transform/parseExpr.js +7 -3
  62. package/lib/transform/transformUtils.js +5 -22
  63. package/lib/transform/translateAssocsToJoins.js +42 -38
  64. package/lib/transform/universalCsn/universalCsnEnricher.js +17 -1
  65. package/package.json +1 -2
  66. package/lib/language/language.g4 +0 -3260
@@ -0,0 +1,293 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ cloneCsnNonDict, applyTransformationsOnNonDictionary, isAssociationOperand, isDollarSelfOrProjectionOperand,
5
+ } = require('../../model/csnUtils');
6
+
7
+ const { setProp } = require('../../base/model');
8
+ const { ModelError } = require('../../base/error');
9
+ const { forEach } = require('../../utils/objectUtils');
10
+
11
+ /**
12
+ * Get a function that transforms $self backlinks
13
+ * @param {object} csnUtils
14
+ * @param {object} messageFunctions
15
+ * @param {CSN.Options} options
16
+ * @param {string} pathDelimiter
17
+ * @param {boolean} doA2J
18
+ * @returns {import('../../model/csnUtils').genericCallback} callback for forEachDefinition
19
+ */
20
+ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimiter, doA2J = true ) {
21
+ return transformSelfInBacklinks;
22
+ /**
23
+ * @param {CSN.Artifact} artifact
24
+ * @param {string} artifactName
25
+ * @param {any} dummy unused Parameter
26
+ * @param {CSN.Path} path
27
+ */
28
+ function transformSelfInBacklinks( artifact, artifactName, dummy, path ) {
29
+ // Fixme: For toHana mixins must be transformed, for toSql -d hana
30
+ // mixin elements must be transformed, why can't toSql also use mixins?
31
+ if (artifact.kind === 'entity' || artifact.query || (options.forHana && options.sqlMapping === 'hdbcds' && artifact.kind === 'type'))
32
+ processDict(artifact.elements, path.concat([ 'elements' ]));
33
+ if (artifact.query?.SELECT?.mixin)
34
+ processDict(artifact.query.SELECT.mixin, path.concat([ 'query', 'SELECT', 'mixin' ]));
35
+
36
+ /**
37
+ * Loop over the dict and start the processing.
38
+ *
39
+ * @param {object} dict .elements or .mixin
40
+ * @param {Array} subPath Path into the dict
41
+ */
42
+ function processDict( dict, subPath ) {
43
+ forEach(dict, (elemName, elem) => {
44
+ if (elem.on && csnUtils.isAssocOrComposition(elem))
45
+ processBacklinkAssoc(elem, elemName, artifact, artifactName, subPath.concat([ elemName, 'on' ]));
46
+ });
47
+ }
48
+ }
49
+
50
+
51
+ /**
52
+ * If the association element 'elem' of 'art' is a backlink association, massage its ON-condition
53
+ * (in place) so that it
54
+ * - compares the generated foreign key fields of the corresponding forward
55
+ * association with their respective keys in 'art' (for managed forward associations)
56
+ * - contains the corresponding forward association's ON-condition in "reversed" form,
57
+ * i.e. as seen from 'elem' (for unmanaged associations)
58
+ * Otherwise, do nothing.
59
+ * @param {CSN.Element} elem
60
+ * @param {string} elemName
61
+ * @param {CSN.Artifact} art
62
+ * @param {string} artName
63
+ * @param {CSN.Path} pathToOn
64
+ */
65
+ function processBacklinkAssoc( elem, elemName, art, artName, pathToOn ) {
66
+ // Don't add braces if it is a single expression (ignoring superfluous braces)
67
+ const multipleExprs = elem.on.filter(x => x !== '(' && x !== ')' ).length > 3;
68
+ elem.on = processExpressionArgs(elem.on, pathToOn);
69
+
70
+ /**
71
+ * Process the args
72
+ *
73
+ * @param {Array} xprArgs
74
+ * @param {CSN.Path} path
75
+ * @returns {Array} Array of parsed expression
76
+ */
77
+ function processExpressionArgs( xprArgs, path ) {
78
+ const result = [];
79
+ let i = 0;
80
+ while (i < xprArgs.length) {
81
+ // Only token tripel `<path>, '=', <path>` are of interest here
82
+ if (i < xprArgs.length - 2 && xprArgs[i + 1] === '=') {
83
+ // Check if one side is $self and the other an association
84
+ // (if so, replace all three tokens with the condition generated from the other side, in parentheses)
85
+ if (isDollarSelfOrProjectionOperand(xprArgs[i]) && isAssociationOperand(xprArgs[i + 2], path.concat([ i + 2 ]), csnUtils.inspectRef)) {
86
+ const assoc = csnUtils.inspectRef(path.concat([ i + 2 ])).art;
87
+ if (multipleExprs)
88
+ result.push('(');
89
+ const backlinkName = xprArgs[i + 2].ref[xprArgs[i + 2].ref.length - 1];
90
+ result.push(...transformDollarSelfComparison(xprArgs[i + 2],
91
+ assoc,
92
+ backlinkName,
93
+ elem, elemName, art, artName, path.concat([ i ])));
94
+ if (multipleExprs)
95
+ result.push(')');
96
+ i += 3;
97
+ attachBacklinkInformation(backlinkName);
98
+ }
99
+ else if (isDollarSelfOrProjectionOperand(xprArgs[i + 2]) && isAssociationOperand(xprArgs[i], path.concat([ i ]), csnUtils.inspectRef)) {
100
+ const assoc = csnUtils.inspectRef(path.concat([ i ])).art;
101
+ if (multipleExprs)
102
+ result.push('(');
103
+ const backlinkName = xprArgs[i].ref[xprArgs[i].ref.length - 1];
104
+ result.push(...transformDollarSelfComparison(xprArgs[i], assoc, backlinkName, elem, elemName, art, artName, path.concat([ i + 2 ])));
105
+ if (multipleExprs)
106
+ result.push(')');
107
+ i += 3;
108
+ attachBacklinkInformation(backlinkName);
109
+ }
110
+ // Otherwise take one (!) token unchanged
111
+ else {
112
+ result.push(xprArgs[i]);
113
+ i++;
114
+ }
115
+ }
116
+ // Process subexpressions - but keep them as subexpressions
117
+ else if (xprArgs[i].xpr) {
118
+ result.push({ xpr: processExpressionArgs(xprArgs[i].xpr, path.concat([ i, 'xpr' ])) });
119
+ i++;
120
+ }
121
+ // Take all other tokens unchanged
122
+ else {
123
+ result.push(xprArgs[i]);
124
+ i++;
125
+ }
126
+ }
127
+ return result;
128
+
129
+ /**
130
+ * The knowledge whether an association was an `<up_>` association in a
131
+ * `$self = <comp>.<up_>` comparison, is important for the foreign key constraints.
132
+ * By the time we generate them, such on-conditions are already transformed
133
+ * --> no more `$self` in the on-conditions, that is why we need to remember it here.
134
+ *
135
+ * @param {string} backlinkName name of `<up_>` in a `$self = <comp>.<up_>` comparison
136
+ */
137
+ function attachBacklinkInformation( backlinkName ) {
138
+ if (elem.$selfOnCondition) {
139
+ elem.$selfOnCondition.up_.push(backlinkName);
140
+ }
141
+ else {
142
+ setProp(elem, '$selfOnCondition', {
143
+ up_: [ backlinkName ],
144
+ });
145
+ }
146
+ }
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Return the condition to replace the comparison `<assocOp> = $self` in the ON-condition
152
+ * of element <elem> of artifact 'art'. If there is anything to complain, use location <loc>
153
+ *
154
+ * @param {any} assocOp
155
+ * @param {CSN.Element} assoc
156
+ * @param {string} assocName
157
+ * @param {CSN.Element} elem
158
+ * @param {string} elemName
159
+ * @param {CSN.Artifact} art
160
+ * @param {string} artifactName
161
+ * @param {CSN.Path} path
162
+ * @returns {Array} New on-condition
163
+ */
164
+ function transformDollarSelfComparison( assocOp, assoc, assocName, elem, elemName, art, artifactName, path ) {
165
+ // Check: The forward link <assocOp> must point back to this artifact
166
+ // FIXME: Unfortunately, we can currently only check this for non-views (because when a view selects
167
+ // a backlink association element from an entity, the forward link will point to the entity,
168
+ // not to the view).
169
+ // FIXME: This also means that corresponding key fields should be in the select list etc ...
170
+ if (!art.query && !art.projection && assoc.target && assoc.target !== artifactName) {
171
+ messageFunctions.error( null, path, { id: '$self', name: artifactName, target: assoc.target },
172
+ 'Expected association using $(ID) to point back to $(NAME) but found $(TARGET)' );
173
+ }
174
+
175
+ // Check: The forward link <assocOp> must not contain '$self' in its own ON-condition
176
+ if (assoc.on) {
177
+ const containsDollarSelf = assoc.on.some(isDollarSelfOrProjectionOperand);
178
+
179
+ if (containsDollarSelf) {
180
+ messageFunctions.error(null, path, { name: '$self' },
181
+ 'An association that uses $(NAME) in its ON-condition can\'t be compared to $(NAME)');
182
+ }
183
+ }
184
+
185
+ // Transform comparison of $self to managed association into AND-combined foreign key comparisons
186
+ if (assoc.keys) {
187
+ if (assoc.keys.length)
188
+ return transformDollarSelfComparisonWithManagedAssoc(assocOp, assoc, assocName, elemName);
189
+
190
+ elem.$ignore = true;
191
+ return [];
192
+ }
193
+
194
+ // Transform comparison of $self to unmanaged association into "reversed" ON-condition
195
+ else if (assoc.on) {
196
+ return transformDollarSelfComparisonWithUnmanagedAssoc(assocOp, assoc, assocName, elemName);
197
+ }
198
+
199
+ throw new ModelError(`Expected either managed or unmanaged association in $self-comparison: ${JSON.stringify(elem.on)}`);
200
+ }
201
+
202
+
203
+ /**
204
+ * For a condition `<elemName>.<assoc> = $self` in the ON-condition of element <elemName>,
205
+ * where <assoc> is a managed association, return a condition comparing the generated
206
+ * foreign key elements <elemName>.<assoc>_<fkey1..n> of <assoc> to the corresponding
207
+ * keys in this artifact.
208
+ * For example, `ON elem.ass = $self` becomes `ON elem.ass_key1 = key1 AND elem.ass_key2 = key2`
209
+ * (assuming that `ass` has the foreign keys `key1` and `key2`)
210
+ * @param {any} assocOp
211
+ * @param {CSN.Element} assoc
212
+ * @param {string} originalAssocName
213
+ * @param {string} elemName
214
+ * @returns {Array} New on-condition
215
+ */
216
+ function transformDollarSelfComparisonWithManagedAssoc( assocOp, assoc, originalAssocName, elemName ) {
217
+ const conditions = [];
218
+ // if the element was structured then it was flattened => change of the delimiter from '.' to '_'
219
+ // this is done in the flattening, but as we do not alter the onCond itself there should be done here as well
220
+ const assocName = originalAssocName.replace(/\./g, pathDelimiter);
221
+ elemName = elemName.replace(/\./g, pathDelimiter);
222
+
223
+ assoc.keys.forEach((k) => {
224
+ // Depending on naming conventions, the foreign key may two path steps (hdbcds) or be a single path step with a flattened name (plain, quoted)
225
+ // With to.hdbcds in conjunction with hdbcds naming, we need to NOT use the alias - else we get deployment errors
226
+ const keyName = k.as && doA2J ? [ k.as ] : k.ref;
227
+ const fKeyPath = !doA2J ? [ assocName, ...keyName ] : [ `${assocName}${pathDelimiter}${keyName[0]}` ];
228
+ // FIXME: _artifact to the args ???
229
+ const a = [
230
+ {
231
+ ref: [ elemName, ...fKeyPath ],
232
+ },
233
+ { ref: k.ref },
234
+ ];
235
+
236
+ conditions.push([ a[0], '=', a[1] ]);
237
+ });
238
+
239
+ return conditions.reduce((prev, current) => {
240
+ if (prev.length === 0)
241
+ return [ ...current ];
242
+
243
+ return [ ...prev, 'and', ...current ];
244
+ }, []);
245
+ }
246
+
247
+ /**
248
+ * For a condition `<elemName>.<assoc> = $self` in the ON-condition of element <elemName>,
249
+ * where <assoc> is an unmanaged association, return the ON-condition of <assoc> as it would
250
+ * be written from the perspective of the artifact containing association <elemName>.
251
+ * For example, `ON elem.ass = $self` becomes `ON a = elem.x AND b = elem.y`
252
+ * (assuming that `ass` has the ON-condition `ON ass.a = x AND ass.b = y`)
253
+ *
254
+ * @param {any} assocOp
255
+ * @param {CSN.Element} assoc
256
+ * @param {string} originalAssocName
257
+ * @param {string} elemName
258
+ * @returns {Array} New on-condition
259
+ */
260
+ function transformDollarSelfComparisonWithUnmanagedAssoc( assocOp, assoc, originalAssocName, elemName ) {
261
+ // if the element was structured then it may have been flattened => change of the delimiter from '.' to '_'
262
+ // this is done in the flattening, but as we do not alter the onCond itself there should be done here as well
263
+ elemName = elemName.replace(/\./g, pathDelimiter);
264
+ const assocName = originalAssocName.replace(/\./g, pathDelimiter);
265
+ // clone the onCond for later use in the path transformation
266
+ const newOnCond = cloneCsnNonDict(assoc.on, options);
267
+ applyTransformationsOnNonDictionary({ on: newOnCond }, 'on', {
268
+ ref: (parent, prop, ref) => {
269
+ // we are in the "path" from the forwarding assoc => need to remove the first part of the path
270
+ if (ref[0] === assocName) {
271
+ ref.shift();
272
+ }
273
+ else if (ref.length > 1 && ref[0] === '$self' && ref[1] === assocName) {
274
+ // We could also have a $self in front of the assoc name - so we would need to shift twice
275
+ ref.shift();
276
+ ref.shift();
277
+ }
278
+ else { // we are in the backlink assoc "path" => need to push at the beginning the association's id
279
+ ref.unshift(elemName);
280
+ // if there was a $self identifier in the forwarding association onCond
281
+ // we do not need it anymore, as we prepended in the previous step the back association's id
282
+ if (ref[1] === '$self')
283
+ ref.splice(1, 1);
284
+ }
285
+ },
286
+ });
287
+ return newOnCond;
288
+ }
289
+ }
290
+
291
+ module.exports = {
292
+ getBacklinkTransformer,
293
+ };
@@ -7,7 +7,7 @@ const {
7
7
  walkCsnPath,
8
8
  getUtils,
9
9
  } = require('../../model/csnUtils');
10
- const { implicitAs, columnAlias } = require('../../model/csnRefs');
10
+ const { implicitAs, columnAlias, pathId } = require('../../model/csnRefs');
11
11
  const { setProp } = require('../../base/model');
12
12
  const { forEach } = require('../../utils/objectUtils');
13
13
 
@@ -39,9 +39,12 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
39
39
  // TODO: replace with the correct options.transformation?
40
40
  // Do not expand the * in OData for a moment, not to introduce changes
41
41
  // while the OData CSN is still official
42
+ const isComplexQuery = parent.from.join !== undefined;
42
43
  if (!options.toOdata)
43
- parent.columns = replaceStar(root, columns, parent.excluding, parent.from.join !== undefined);
44
- parent.columns = expand(parent.columns, path.concat('columns'), true);
44
+ parent.columns = replaceStar(root, columns, parent.excluding, isComplexQuery);
45
+ // FIXME(v5): Remove argument "isComplexOrNestedQuery"; we use path.length > 4 to check
46
+ // if we're inside the outermost "columns". If so, always prepend a table alias. See #11662
47
+ parent.columns = expand(parent.columns, path.concat('columns'), true, isComplexQuery || path.length > 4);
45
48
  }
46
49
  },
47
50
  groupBy: (parent, name, groupBy, path) => {
@@ -512,16 +515,17 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
512
515
  * @param {Array} thing
513
516
  * @param {CSN.Path} path
514
517
  * @param {boolean} [withAlias=false] Whether to "expand" the (implicit) alias as well.
518
+ * @param {boolean} [isComplexOrNestedQuery]
515
519
  * @returns {Array} New array - with all structured things expanded
516
520
  */
517
- function expand( thing, path, withAlias = false ) {
521
+ function expand( thing, path, withAlias = false, isComplexOrNestedQuery = false ) {
518
522
  const newThing = [];
519
523
  for (let i = 0; i < thing.length; i++) {
520
524
  const col = thing[i];
521
525
  if (col.ref && col.$scope !== '$magic') {
522
526
  const _art = col._art || csnUtils.inspectRef(path.concat(i)).art;
523
527
  if (_art && csnUtils.isStructured(_art))
524
- newThing.push(...expandRef(_art, col, withAlias));
528
+ newThing.push(...expandRef(_art, col, withAlias, isComplexOrNestedQuery));
525
529
  else
526
530
  newThing.push(col);
527
531
  }
@@ -596,9 +600,10 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
596
600
  * @param {CSN.Element} art
597
601
  * @param {object} root Column, ref in order by, etc.
598
602
  * @param {boolean} withAlias Whether to add an explicit flattened alias to the expanded columns/references.
603
+ * @param {boolean} [isComplexOrNestedQuery]
599
604
  * @returns {Array}
600
605
  */
601
- function expandRef( art, root, withAlias ) {
606
+ function expandRef( art, root, withAlias, isComplexOrNestedQuery ) {
602
607
  return _expandStructCol(art, columnAlias(root), root.ref, ( currentRef, currentAlias) => {
603
608
  const obj = { ...root, ref: currentRef };
604
609
  if (withAlias) {
@@ -613,7 +618,13 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
613
618
  setProp(obj, '$implicitAlias', true);
614
619
  }
615
620
 
616
- if (typeof root.$env === 'string')
621
+ // The Java runtime, as of 2023-09-13, assumes that for _simple projections_, all references
622
+ // are relative to the query source. To avoid breaking that assumption unless necessary,
623
+ // we only add the table alias if:
624
+ // - it is a complex query with possibly multiple available table aliases, or
625
+ // - the transformation is not for OData (which is used by Java), or
626
+ // - the first path step has the same name as the table alias (only one, as otherwise the query would be complex)
627
+ if (typeof root.$env === 'string' && (isComplexOrNestedQuery || options.transformation !== 'odata' || root.$env === pathId(obj.ref[0])))
617
628
  obj.ref = [ root.$env, ...obj.ref ];
618
629
 
619
630
  return obj;
@@ -840,8 +840,9 @@ function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathD
840
840
  fk[0] = `${prefix}${pathDelimiter}${fk[0]}`;
841
841
  // if this is the entry association, decorate the final foreign keys with the association props
842
842
  if (lvl === 0) {
843
- fk[1]['@odata.foreignKey4'] = prefix;
844
- if (!options.forHana)
843
+ if (options.transformation !== 'effective')
844
+ fk[1]['@odata.foreignKey4'] = prefix;
845
+ if (options.transformation === 'odata' || options.transformation === 'effective')
845
846
  copyAnnotations(element, fk[1], true);
846
847
 
847
848
  // propagate not null to final foreign key
@@ -8,11 +8,9 @@ const {
8
8
  applyTransformationsOnDictionary,
9
9
  implicitAs,
10
10
  cloneCsnNonDict,
11
- forEachMemberRecursively,
12
11
  } = require('../../model/csnUtils');
13
12
  const { getBranches } = require('./flattening');
14
13
  const { getColumnMap } = require('./views');
15
- const { requireForeignKeyAccess } = require('../../checks/onConditions');
16
14
 
17
15
  const cloneCsnOptions = { hiddenPropertiesToClone: [ '_art', '_links', '$env', '$scope' ] };
18
16
 
@@ -96,73 +94,9 @@ function rewriteCalculatedElementsInViews( csn, options, csnUtils, pathDelimiter
96
94
  }, {}, [ 'definitions' ]);
97
95
  });
98
96
 
99
- /**
100
- * Check that all paths in calculated elements-on write either access normal fields
101
- * (structures are already rejected by the compiler) or access the foreign keys of
102
- * associations. Non-fk fields must be rejected.
103
- * Filters and parameters are not allowed.
104
- *
105
- * Note: This coding is similar to checks/onConditions.js, but does not check $self or other
106
- * ON-condition related stuff.
107
- *
108
- * @param {object} parent
109
- * @param {(string|object)[]} value
110
- * @param {CSN.Path} csnPath
111
- */
112
- function checkPathsInStoredCalcElement( parent, value, csnPath ) {
113
- const { _links } = parent;
114
-
115
- // If there is only one path step, it's been checked before.
116
- for (let i = 0; i < value.length - 1; ++i) {
117
- let hasPathError = false;
118
- const step = value[i];
119
- const stepArt = _links[i].art;
120
-
121
- if (stepArt.target) {
122
- const id = step.id || step;
123
- if (stepArt.on) {
124
- // It's an unmanaged association - traversal is always forbidden
125
- error('ref-unexpected-navigation', csnPath, { '#': 'calc-unmanaged', id, elemref: parent });
126
- hasPathError = true;
127
- }
128
- else {
129
- // It's a managed association - access of the foreign keys is allowed
130
- requireForeignKeyAccess(parent, i, (errorIndex) => {
131
- error('ref-unexpected-navigation', csnPath, {
132
- '#': 'calc-non-fk', id, elemref: parent, name: value[errorIndex].id || value[errorIndex],
133
- });
134
- hasPathError = true;
135
- });
136
- }
137
- }
138
- if (typeof step === 'object') {
139
- if (step.where) {
140
- error('ref-unexpected-filter', csnPath, { '#': 'calc', elemref: parent });
141
- hasPathError = true;
142
- }
143
- if (step.args) {
144
- error('ref-unexpected-args', csnPath, { '#': 'calc', elemref: parent });
145
- hasPathError = true;
146
- }
147
- }
148
- if (hasPathError)
149
- break; // avoid too many consequent errors
150
- }
151
- }
152
-
153
97
  // Last pass, turn .value in tables into a simple 'val' so we don't need to rewrite/flatten properly - will kill them later
154
98
  entities.forEach(({ artifact, artifactName }) => {
155
99
  dummifyInEntity(artifact, [ 'definitions', artifactName ]);
156
-
157
- forEachMemberRecursively({ elements: artifact.elements }, (element, elementName, _prop, path) => {
158
- if (element.value?.stored) {
159
- applyTransformationsOnNonDictionary(element, 'value', {
160
- ref(parent, prop, value, csnPath) {
161
- checkPathsInStoredCalcElement(parent, value, csnPath);
162
- },
163
- }, {}, path);
164
- }
165
- }, [ 'definitions', artifactName ]);
166
100
  });
167
101
 
168
102
  /**
@@ -329,7 +263,7 @@ function rewriteCalculatedElementsInViews( csn, options, csnUtils, pathDelimiter
329
263
  linksBase: current.linksBase,
330
264
  });
331
265
  },
332
- });
266
+ }, { skipStandard: { where: true } });
333
267
  }
334
268
  else if (current.value.ref && current.value._art?.value && !current.value._art?.value.stored) {
335
269
  const linksBase = current.value._links;
@@ -66,6 +66,9 @@ function handleExists( csn, options, error, inspectRef, initDefinition, dropDefi
66
66
  if (query.SELECT?.where?.length > 1)
67
67
  toProcess.push([ path.slice(0, -1), path.concat('where') ]);
68
68
 
69
+ if (query.SELECT?.having?.length > 1)
70
+ toProcess.push([ path.slice(0, -1), path.concat('having') ]);
71
+
69
72
  if (query.SELECT?.columns)
70
73
  toProcess.push([ path.slice(0, -1), path.concat('columns') ]);
71
74
 
@@ -73,7 +76,6 @@ function handleExists( csn, options, error, inspectRef, initDefinition, dropDefi
73
76
  toProcess.push([ path.slice(0, -1), path.concat([ 'from', 'on' ]) ]);
74
77
 
75
78
  for (const [ , exprPath ] of toProcess) {
76
- forbidAssocInExists(exprPath);
77
79
  const expr = nestExists(exprPath);
78
80
  walkCsnPath(csn, exprPath.slice(0, -1))[exprPath[exprPath.length - 1]] = expr;
79
81
  }
@@ -254,63 +256,6 @@ function handleExists( csn, options, error, inspectRef, initDefinition, dropDefi
254
256
  return startAssoc;
255
257
  }
256
258
 
257
- /**
258
- * Check that associations in filters (in an exists expression) are only fk-accesses. Everything else is forbidden.
259
- *
260
- * @param {CSN.Path} exprPath
261
- * @returns {void}
262
- */
263
- function forbidAssocInExists( exprPath ) {
264
- const expr = walkCsnPath(csn, exprPath);
265
- for (let i = 0; i < expr.length; i++) {
266
- if (i < expr.length - 1 && expr[i] === 'exists' && expr[i + 1].ref) {
267
- i++;
268
- const current = expr[i];
269
-
270
- const { links } = inspectRef(exprPath.concat(i));
271
-
272
- const assocs = links.filter(link => link.art?.target).map(link => current.ref[link.idx]);
273
-
274
- checkForInvalidAssoc(assocs);
275
- }
276
- }
277
- }
278
-
279
- /**
280
- * @param {object[]} assocs Array of refs of assocs - possibly with a .where to check
281
- */
282
- function checkForInvalidAssoc( assocs ) {
283
- for (const assoc of assocs) {
284
- if (assoc.where) {
285
- for (let i = 0; i < assoc.where.length; i++) {
286
- const part = assoc.where[i];
287
-
288
- if (part._links && !(assoc.where[i - 1] && assoc.where[i - 1] === 'exists')) {
289
- for (const link of part._links) {
290
- if (link.art && link.art.target) {
291
- if (link.art.keys) { // managed - allow FK access
292
- if (part._links[link.idx + 1] !== undefined) { // there is a next path step - check if it is a fk
293
- if (!(part._links[link.idx + 1] && link.art.keys.some(fk => fk._art === part._links[link.idx + 1].art)))
294
- error(null, part.$path, { id: assoc.id, name: assoc.where[part.$path[part.$path.length - 1]].ref[link.idx] }, 'Unexpected non foreign key access after managed association $(NAME) in filter expression of $(ID)');
295
- }
296
- else { // no traversal, ends on managed
297
- error(null, part.$path, { id: assoc.id, name: assoc.where[part.$path[part.$path.length - 1]].ref[link.idx] }, 'Unexpected managed association $(NAME) in filter expression of $(ID)');
298
- }
299
- }
300
- else { // unmanaged - always wrong
301
- error(null, part.$path, { id: assoc.id, name: assoc.where[part.$path[part.$path.length - 1]].ref[link.idx] }, 'Unexpected unmanaged association $(NAME) in filter expression of $(ID)');
302
- }
303
- // Recursively drill down if the assoc-step has a filter
304
- if (part.ref[link.idx].where)
305
- checkForInvalidAssoc([ part.ref[link.idx] ]);
306
- }
307
- }
308
- }
309
- }
310
- }
311
- }
312
- }
313
-
314
259
  /**
315
260
  * Walk to the expr using the given path and scan it for the "exists" + "ref" pattern.
316
261
  * If such a pattern is found, nest association steps therein into filters.
@@ -3,7 +3,7 @@
3
3
  const {
4
4
  getUtils, cloneCsnNonDict, applyTransformationsOnNonDictionary, forEachDefinition,
5
5
  } = require('../../model/csnUtils');
6
- const { implicitAs, columnAlias } = require('../../model/csnRefs');
6
+ const { implicitAs, columnAlias, pathId } = require('../../model/csnRefs');
7
7
  const { ModelError } = require('../../base/error');
8
8
  const { setProp } = require('../../base/model');
9
9
 
@@ -16,20 +16,18 @@ const { setProp } = require('../../base/model');
16
16
  * @returns {object} The mixin association
17
17
  */
18
18
  function getMixinAssocOfQueryIfPublished( query, association, associationName ) {
19
- if (query && query.SELECT && query.SELECT.mixin) {
19
+ if (query?.SELECT?.mixin) {
20
20
  const aliasedColumnsMap = Object.create(null);
21
- if (query.SELECT.columns) {
22
- for (const column of query.SELECT.columns) {
23
- if (column.as && column.ref && column.ref.length === 1)
24
- aliasedColumnsMap[column.as] = column;
25
- }
21
+ for (const column of query.SELECT.columns || []) {
22
+ if (column.as && column.ref?.length === 1)
23
+ aliasedColumnsMap[column.as] = column;
26
24
  }
27
25
 
28
26
  for (const elem of Object.keys(query.SELECT.mixin)) {
29
27
  const mixinElement = query.SELECT.mixin[elem];
30
28
  let originalName = associationName;
31
29
  if (aliasedColumnsMap[associationName])
32
- originalName = aliasedColumnsMap[associationName].ref[0];
30
+ originalName = pathId(aliasedColumnsMap[associationName].ref[0]);
33
31
 
34
32
  if (elem === originalName)
35
33
  return { mixinElement, mixinName: originalName };
@@ -210,11 +208,7 @@ function getViewTransformer( csn, options, messageFunctions, transformCommon ) {
210
208
  function handleAssociationElement( query, elements, columnMap, publishedMixins, elem, elemName, elementsPath, queryPath ) {
211
209
  if (isUnion(queryPath) && options.transformation === 'hdbcds') {
212
210
  if (doA2J) {
213
- if (elem.keys)
214
- info('query-ignoring-assoc-in-union', queryPath, { name: elemName, '#': 'managed' });
215
- else
216
- info('query-ignoring-assoc-in-union', queryPath, { name: elemName, '#': 'std' });
217
-
211
+ info('query-ignoring-assoc-in-union', queryPath, { name: elemName, '#': elem.keys ? 'managed' : 'std' });
218
212
  elem.$ignore = true;
219
213
  }
220
214
  else {
@@ -448,7 +442,7 @@ function stripLeadingSelf( col ) {
448
442
  * @returns {boolean}
449
443
  */
450
444
  function checkIsNotMixinByItself( query, columnMap, elementName ) {
451
- if (query && query.SELECT && query.SELECT.mixin) {
445
+ if (query?.SELECT?.mixin) {
452
446
  const col = columnMap[elementName];
453
447
 
454
448
  if (!col.ref) // No ref -> new association, but not a mixin.
@@ -0,0 +1,4 @@
1
+ {
2
+ "root": true,
3
+ "extends": ["../db/.eslintrc.json"]
4
+ }