@sap/cds-compiler 2.11.2 → 2.11.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 (46) hide show
  1. package/CHANGELOG.md +23 -2
  2. package/bin/.eslintrc.json +1 -2
  3. package/bin/cds_update_identifiers.js +3 -1
  4. package/bin/cdsc.js +8 -1
  5. package/bin/cdsv2m.js +3 -2
  6. package/lib/api/main.js +2 -16
  7. package/lib/api/options.js +3 -2
  8. package/lib/api/validate.js +7 -1
  9. package/lib/backends.js +3 -5
  10. package/lib/base/keywords.js +3 -2
  11. package/lib/base/message-registry.js +24 -8
  12. package/lib/base/messages.js +15 -9
  13. package/lib/base/optionProcessorHelper.js +1 -1
  14. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  15. package/lib/checks/unknownMagic.js +1 -1
  16. package/lib/compiler/assert-consistency.js +2 -2
  17. package/lib/compiler/builtins.js +34 -15
  18. package/lib/compiler/definer.js +8 -17
  19. package/lib/compiler/index.js +13 -25
  20. package/lib/compiler/resolver.js +89 -23
  21. package/lib/compiler/shared.js +25 -28
  22. package/lib/compiler/utils.js +11 -0
  23. package/lib/gen/language.checksum +1 -1
  24. package/lib/json/to-csn.js +60 -14
  25. package/lib/language/errorStrategy.js +26 -8
  26. package/lib/language/genericAntlrParser.js +2 -1
  27. package/lib/language/language.g4 +6 -3
  28. package/lib/main.d.ts +79 -1
  29. package/lib/model/csnRefs.js +11 -4
  30. package/lib/model/csnUtils.js +2 -107
  31. package/lib/model/enrichCsn.js +33 -35
  32. package/lib/model/revealInternalProperties.js +5 -4
  33. package/lib/model/sortViews.js +8 -1
  34. package/lib/optionProcessor.js +5 -1
  35. package/lib/render/.eslintrc.json +1 -2
  36. package/lib/render/toHdbcds.js +2 -7
  37. package/lib/render/toSql.js +16 -11
  38. package/lib/transform/db/applyTransformations.js +189 -0
  39. package/lib/transform/db/flattening.js +1 -1
  40. package/lib/transform/db/transformExists.js +9 -0
  41. package/lib/transform/db/views.js +89 -42
  42. package/lib/transform/forHanaNew.js +34 -12
  43. package/lib/transform/translateAssocsToJoins.js +3 -3
  44. package/lib/utils/file.js +6 -2
  45. package/package.json +1 -1
  46. package/lib/transform/db/helpers.js +0 -58
@@ -0,0 +1,189 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Module for general (partial) CSN looper functions, respecting dictionaries and allowing
5
+ * to pass custom callbacks for certain properties like "ref".
6
+ *
7
+ * Functions are also published in csnUtils.js for convenience.
8
+ *
9
+ * They should stay here due to the stricter linter rules for the time being.
10
+ *
11
+ * @module lib/transform/db/applyTransformations
12
+ */
13
+ const { setProp } = require('../../base/model');
14
+
15
+
16
+ /**
17
+ * @param {object} parent The "parent" of which we transform a property of
18
+ * @param {string} prop The property of parent to start at
19
+ * @param {object} customTransformers Map of prop to transform and function to apply
20
+ * @param {Function[]} [artifactTransformers=[]] Transformations to run on the artifacts, like forEachDefinition
21
+ * @param {boolean} [skipIgnore=true] Wether to skip _ignore elements or not
22
+ * @param {object} [options={}] "skipArtifact": (artifact, name) => Boolean to skip certain artifacts, drillRef: boolean - whether to drill into infix/args
23
+ * @param {CSN.Path} path Path to parent
24
+ * @returns {object} parent with transformations applied
25
+ */
26
+ function applyTransformationsInternal(parent, prop, customTransformers, artifactTransformers, skipIgnore, options, path = []) {
27
+ const transformers = {
28
+ elements: dictionary,
29
+ definitions: dictionary,
30
+ actions: dictionary,
31
+ params: dictionary,
32
+ enum: dictionary,
33
+ mixin: dictionary,
34
+ ref: pathRef,
35
+ };
36
+
37
+ const csnPath = [ ...path ];
38
+ if (prop === 'definitions')
39
+ definitions( parent, 'definitions', parent.definitions );
40
+ else
41
+ standard(parent, prop, parent[prop]);
42
+ return parent;
43
+
44
+ /**
45
+ * Default transformer for things that are not dictionaries, like "type" or "keys".
46
+ * The customTransformers are applied here (and only here).
47
+ *
48
+ * @param {object | Array} _parent the thing that has _prop
49
+ * @param {string|number} _prop the name of the current property
50
+ * @param {object} node The value of node[_prop]
51
+ */
52
+ function standard( _parent, _prop, node ) {
53
+ if (!node || typeof node !== 'object' || !{}.propertyIsEnumerable.call( _parent, _prop ) || (typeof _prop === 'string' && _prop.startsWith('@')) || (skipIgnore && node._ignore))
54
+ return;
55
+
56
+ csnPath.push( _prop );
57
+
58
+ if (Array.isArray(node)) {
59
+ node.forEach( (n, i) => standard( node, i, n ) );
60
+ }
61
+
62
+ else {
63
+ for (const name of Object.getOwnPropertyNames( node )) {
64
+ const trans = transformers[name] || standard;
65
+ if (customTransformers[name])
66
+ customTransformers[name](node, name, node[name], csnPath, _parent, _prop);
67
+
68
+ trans( node, name, node[name], csnPath );
69
+ }
70
+ }
71
+ csnPath.pop();
72
+ }
73
+
74
+ /**
75
+ * Transformer for things that are dictionaries - like "elements".
76
+ *
77
+ * @param {object | Array} node the thing that has _prop
78
+ * @param {string|number} _prop the name of the current property
79
+ * @param {object} dict The value of node[_prop]
80
+ */
81
+ function dictionary( node, _prop, dict ) {
82
+ // Allow skipping dicts like actions in forHanaNew
83
+ if (options.skipDict && options.skipDict[_prop])
84
+ return;
85
+ csnPath.push( _prop );
86
+ for (const name of Object.getOwnPropertyNames( dict ))
87
+ standard( dict, name, dict[name] );
88
+
89
+ if (!Object.prototype.propertyIsEnumerable.call( node, _prop ))
90
+ setProp(node, `$${_prop}`, dict);
91
+ csnPath.pop();
92
+ }
93
+
94
+ /**
95
+ * Special version of "dictionary" to apply artifactTransformers.
96
+ *
97
+ * @param {object | Array} node the thing that has _prop
98
+ * @param {string|number} _prop the name of the current property
99
+ * @param {object} dict The value of node[_prop]
100
+ */
101
+ function definitions( node, _prop, dict ) {
102
+ csnPath.push( _prop );
103
+ for (const name of Object.getOwnPropertyNames( dict )) {
104
+ const skip = options && options.skipArtifact && options.skipArtifact(dict[name], name) || false;
105
+ if (!skip) {
106
+ artifactTransformers.forEach(fn => fn(dict, name, dict[name]));
107
+ standard( dict, name, dict[name] );
108
+ }
109
+ }
110
+ if (!Object.prototype.propertyIsEnumerable.call( node, _prop ))
111
+ setProp(node, `$${_prop}`, dict);
112
+ csnPath.pop();
113
+ }
114
+
115
+ /**
116
+ * Keep looping through the pathRef - because in a .ref we can have .args and .where
117
+ *
118
+ * @param {object | Array} node the thing that has _prop
119
+ * @param {string|number} _prop the name of the current property
120
+ * @param {any} _path The value of node[_prop]
121
+ */
122
+ function pathRef( node, _prop, _path ) {
123
+ csnPath.push( _prop );
124
+ _path.forEach( ( s, i ) => {
125
+ if (s && typeof s === 'object') {
126
+ csnPath.push( i );
127
+ if (options.drillRef) {
128
+ standard(_path, i, s);
129
+ }
130
+ else {
131
+ if (s.args)
132
+ standard( s, 'args', s.args );
133
+ if (s.where)
134
+ standard( s, 'where', s.where );
135
+ }
136
+ csnPath.pop();
137
+ }
138
+ } );
139
+ csnPath.pop();
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Loop through the model, applying the custom transformations on the node's matching.
145
+ *
146
+ * Each transformer gets:
147
+ * - the parent having the property
148
+ * - the name of the property
149
+ * - the value of the property
150
+ * - the path to the property
151
+ *
152
+ * @param {object} csn CSN to enrich in-place
153
+ * @param {object} customTransformers Map of _prop to transform and function to apply
154
+ * @param {Function[]} [artifactTransformers=[]] Transformations to run on the artifacts, like forEachDefinition
155
+ * @param {boolean} [skipIgnore=true] Wether to skip _ignore elements or not
156
+ * @param {object} [options={}] "skipArtifact": (artifact, name) => Boolean to skip certain artifacts, drillRef: boolean - whether to drill into infix/args
157
+ * @returns {object} CSN with transformations applied
158
+ */
159
+ function applyTransformations( csn, customTransformers = {}, artifactTransformers = [], skipIgnore = true, options = {} ) {
160
+ if (csn && csn.definitions)
161
+ return applyTransformationsInternal(csn, 'definitions', customTransformers, artifactTransformers, skipIgnore, options);
162
+ return csn;
163
+ }
164
+
165
+
166
+ /**
167
+ * Instead of looping through the whole model, start at a given thing (like an on-condition),
168
+ * as long as it is not a dictionary.
169
+ *
170
+ * Each transformer gets:
171
+ * - the parent having the property
172
+ * - the name of the property
173
+ * - the value of the property
174
+ * - the path to the property
175
+ *
176
+ * @param {object} parent The "parent" of which we transform a property of
177
+ * @param {string} prop The property of parent to start at
178
+ * @param {object} customTransformers Map of prop to transform and function to apply
179
+ * @param {CSN.Path} path Path pointing to parent
180
+ * @returns {object} parent[prop] with transformations applied
181
+ */
182
+ function applyTransformationsOnNonDictionary(parent, prop, customTransformers = {}, path = []) {
183
+ return applyTransformationsInternal(parent, prop, customTransformers, [], true, {}, path)[prop];
184
+ }
185
+
186
+ module.exports = {
187
+ applyTransformations,
188
+ applyTransformationsOnNonDictionary,
189
+ };
@@ -14,7 +14,7 @@ const { setProp } = require('../../base/model');
14
14
  * @param {CSN.Model} csn
15
15
  */
16
16
  function removeLeadingSelf(csn) {
17
- const magicVars = [ '$now' ];
17
+ const magicVars = [ '$now', '$self', '$projection', '$user', '$session', '$at' ];
18
18
  forEachDefinition(csn, (artifact, artifactName) => {
19
19
  if (artifact.kind === 'entity' || artifact.kind === 'view') {
20
20
  forAllElements(artifact, artifactName, (parent, elements) => {
@@ -51,6 +51,9 @@ function handleExists(csn, options, error) {
51
51
  const { inspectRef } = csnRefs(csn);
52
52
  const generatedExists = new WeakMap();
53
53
  forEachDefinition(csn, (artifact, artifactName) => {
54
+ if (artifact.projection) // do the same hack we do for the other stuff...
55
+ artifact.query = { SELECT: artifact.projection };
56
+
54
57
  if (artifact.query) {
55
58
  forAllQueries(artifact.query, (query, path) => {
56
59
  if (!generatedExists.has(query)) {
@@ -83,6 +86,12 @@ function handleExists(csn, options, error) {
83
86
  }
84
87
  }, [ 'definitions', artifactName, 'query' ]);
85
88
  }
89
+
90
+ if (artifact.projection) { // undo our hack
91
+ artifact.projection = artifact.query.SELECT;
92
+
93
+ delete artifact.query;
94
+ }
86
95
  });
87
96
 
88
97
  /**
@@ -1,10 +1,62 @@
1
1
  'use strict';
2
2
 
3
- const { usesMixinAssociation, getMixinAssocOfQueryIfPublished } = require('./helpers');
4
- const { getUtils, cloneCsn } = require('../../model/csnUtils');
3
+ const {
4
+ getUtils, cloneCsn, applyTransformationsOnNonDictionary,
5
+ } = require('../../model/csnUtils');
5
6
  const { implicitAs, csnRefs } = require('../../model/csnRefs');
6
7
  const { isBetaEnabled } = require('../../base/model');
7
8
 
9
+ /**
10
+ * If a mixin association is published, return the mixin association.
11
+ *
12
+ * @param {CSN.Query} query Query of the artifact to check
13
+ * @param {object} association Association (Element) published by the view
14
+ * @param {string} associationName
15
+ * @returns {object} The mixin association
16
+ */
17
+ function getMixinAssocOfQueryIfPublished(query, association, associationName) {
18
+ if (query && query.SELECT && query.SELECT.mixin) {
19
+ const aliasedColumnsMap = Object.create(null);
20
+ if (query.SELECT.columns) {
21
+ for (const column of query.SELECT.columns) {
22
+ if (column.as && column.ref && column.ref.length === 1)
23
+ aliasedColumnsMap[column.as] = column;
24
+ }
25
+ }
26
+
27
+ for (const elem of Object.keys(query.SELECT.mixin)) {
28
+ const mixinElement = query.SELECT.mixin[elem];
29
+ let originalName = associationName;
30
+ if (aliasedColumnsMap[associationName])
31
+ originalName = aliasedColumnsMap[associationName].ref[0];
32
+
33
+ if (elem === originalName)
34
+ return { mixinElement, mixinName: originalName };
35
+ }
36
+ }
37
+ return {};
38
+ }
39
+
40
+ /**
41
+ * Check wether the given artifact uses the given mixin association.
42
+ *
43
+ * We can rely on the fact that there can be no usage starting with $self/$projection,
44
+ * since lib/checks/selectItems.js forbids that.
45
+ *
46
+ * @param {CSN.Query} query Query of the artifact to check
47
+ * @param {object} association Mixin association (Element) to check for
48
+ * @param {string} associationName
49
+ * @returns {boolean} True if used
50
+ */
51
+ function usesMixinAssociation(query, association, associationName) {
52
+ if (query && query.SELECT && query.SELECT.columns) {
53
+ for (const column of query.SELECT.columns) {
54
+ if (typeof column === 'object' && column.ref && column.ref.length > 1 && (column.ref[0] === associationName || column.ref[0].id === associationName))
55
+ return true;
56
+ }
57
+ }
58
+ return false;
59
+ }
8
60
 
9
61
  /**
10
62
  * @param {CSN.Model} csn
@@ -15,7 +67,7 @@ const { isBetaEnabled } = require('../../base/model');
15
67
  */
16
68
  function getViewTransformer(csn, options, messageFunctions, transformCommon) {
17
69
  const {
18
- get$combined, cloneWithTransformations, isAssocOrComposition,
70
+ get$combined, isAssocOrComposition,
19
71
  } = getUtils(csn);
20
72
  const { inspectRef, queryOrMain } = csnRefs(csn);
21
73
  const pathDelimiter = (options.forHana.names === 'hdbcds') ? '.' : '_';
@@ -181,41 +233,38 @@ function getViewTransformer(csn, options, messageFunctions, transformCommon) {
181
233
  * create the __clone for publishing stuff.
182
234
  *
183
235
  * @todo Factor out the checks
184
- * @todo Union, Join, Subqueries? Assoc usage in there? __clone?
185
236
  * @param {CSN.Query} query
186
237
  * @param {object} elements
187
238
  * @param {object} columnMap
188
239
  * @param {WeakMap} publishedMixins Map to collect the published mixins
189
240
  * @param {CSN.Element} elem
190
241
  * @param {string} elemName
191
- * @param {CSN.Path} path
242
+ * @param {CSN.Path} elementsPath Path pointing to elements
243
+ * @param {CSN.Path} queryPath Path pointing to the query
192
244
  */
193
- function handleAssociationElement(query, elements, columnMap, publishedMixins, elem, elemName, path) {
194
- if (isUnion(path) && options.transformation === 'hdbcds') {
245
+ function handleAssociationElement(query, elements, columnMap, publishedMixins, elem, elemName, elementsPath, queryPath) {
246
+ if (isUnion(queryPath) && options.transformation === 'hdbcds') {
195
247
  if (isBetaEnabled(options, 'ignoreAssocPublishingInUnion') && doA2J) {
196
248
  if (elem.keys)
197
- info(null, path, `Managed association "${elemName}", published in a UNION, will be ignored`);
249
+ info(null, queryPath, `Managed association "${elemName}", published in a UNION, will be ignored`);
198
250
  else
199
- info(null, path, `Association "${elemName}", published in a UNION, will be ignored`);
251
+ info(null, queryPath, `Association "${elemName}", published in a UNION, will be ignored`);
200
252
 
201
253
  elem._ignore = true;
202
254
  }
203
255
  else {
204
- error(null, path, `Association "${elemName}" can't be published in a SAP HANA CDS UNION`);
256
+ error(null, queryPath, `Association "${elemName}" can't be published in a SAP HANA CDS UNION`);
205
257
  }
206
258
  }
207
- else if (path.length > 4 && options.transformation === 'hdbcds') { // path.length > 4 -> is a subquery
208
- error(null, path, { name: elemName },
259
+ else if (queryPath.length > 4 && options.transformation === 'hdbcds') { // path.length > 4 -> is a subquery
260
+ error(null, queryPath, { name: elemName },
209
261
  'Association $(NAME) can\'t be published in a subquery');
210
262
  }
211
263
  else {
212
- /* Old implementation:
213
- const isNotMixinByItself = !(elem.value && elem.value.path && elem.value.path.length == 1 && art.query && art.query.mixin && art.query.mixin[elem.value.path[0].id]);
214
- */
215
264
  const isNotMixinByItself = checkIsNotMixinByItself(query, columnMap, elemName);
216
265
  const { mixinElement, mixinName } = getMixinAssocOfQueryIfPublished(query, elem, elemName);
217
266
  if (isNotMixinByItself || mixinElement !== undefined) {
218
- // If the mixin is only published and not used, only display the __ clone. Kill the "original".
267
+ // If the mixin is only published and not used, only display the __ clone. Kill the "original".
219
268
  if (mixinElement !== undefined && !usesMixinAssociation(query, elem, elemName))
220
269
  delete query.SELECT.mixin[mixinName];
221
270
 
@@ -226,8 +275,8 @@ function getViewTransformer(csn, options, messageFunctions, transformCommon) {
226
275
  mixinElemName = `_${mixinElemName}`;
227
276
 
228
277
  // Copy the association element to the MIXIN clause under its alias name
229
- // (shallow copy is sufficient, just fix name and value)
230
- const mixinElem = Object.assign({}, elem);
278
+ // Needs to be a deep copy, as we transform the on-condition
279
+ const mixinElem = cloneCsn(elem, options);
231
280
  // Perform common transformations on the newly generated MIXIN element (won't be reached otherwise)
232
281
  transformCommon(mixinElem, mixinElemName);
233
282
 
@@ -238,30 +287,24 @@ function getViewTransformer(csn, options, messageFunctions, transformCommon) {
238
287
  // and fixing the association alias just created
239
288
 
240
289
  if (mixinElem.on) {
241
- mixinElem.on = cloneWithTransformations(mixinElem.on, {
242
- ref: (ref) => {
243
- // Clone the path, without any transformations
244
- const clonedPath = cloneWithTransformations(ref, {});
245
- // Prepend '$projection' to the path, unless the first path step is the (mixin) element itself or starts with '$')
246
- if (clonedPath[0] === elemName) {
247
- clonedPath[0] = mixinElemName;
290
+ mixinElem.on = applyTransformationsOnNonDictionary(mixinElem, 'on', {
291
+ ref: (parent, prop, ref, refpath) => {
292
+ if (ref[0] === elemName) {
293
+ ref[0] = mixinElemName;
248
294
  }
249
- else if (!(clonedPath[0] && clonedPath[0].startsWith('$'))) {
250
- const projectionId = '$projection';
251
- clonedPath.unshift(projectionId);
295
+ else if (!(ref[0] && ref[0].startsWith('$'))) {
296
+ ref.unshift('$projection');
252
297
  }
253
- return clonedPath;
254
- },
255
- func: (func) => {
256
- // Unfortunately, function names are disguised as paths, so we would prepend a '$projection'
257
- // above (no way to distinguish that in the callback for 'path' above). We can only pluck it
258
- // off again here ... sigh
259
- if (func.ref && func.ref[0] && func.ref[0] === '$projection')
260
- func.ref = func.ref.slice(1);
261
-
262
- return func;
298
+ else if (ref[0] && ref[0].startsWith('$')) {
299
+ // TODO: I think this is non-sense. Stuff with $ is either magic or must start with $self, right?
300
+ const { scope } = inspectRef(refpath);
301
+ if (scope !== '$magic' && scope !== '$self')
302
+ ref.unshift('$projection');
303
+ }
304
+ parent.ref = ref;
305
+ return ref;
263
306
  },
264
- });
307
+ }, elementsPath.concat(elemName));
265
308
  }
266
309
 
267
310
  if (!mixinElem._ignore)
@@ -304,6 +347,10 @@ function getViewTransformer(csn, options, messageFunctions, transformCommon) {
304
347
  // eslint-disable-next-line complexity
305
348
  function transformViewOrEntity(query, artifact, artName, path) {
306
349
  const { elements } = queryOrMain(query, artifact);
350
+ // We use the elements from the leading query/main artifact - adapt the path
351
+ const elementsPath = elements === artifact.elements ? path.slice(0, 2).concat('elements') : path.concat('elements');
352
+ const queryPath = path;
353
+
307
354
  let hasNonAssocElements = false;
308
355
  const isSelect = query && query.SELECT;
309
356
  const isProjection = !!artifact.projection || query && query.SELECT && !query.SELECT.columns;
@@ -338,12 +385,12 @@ function getViewTransformer(csn, options, messageFunctions, transformCommon) {
338
385
  // (180 b) Create MIXINs for association elements in projections or views (those that are not mixins by themselves)
339
386
  // CDXCORE-585: Allow mixin associations to be used and published in parallel
340
387
  if (query !== undefined && elem.target)
341
- handleAssociationElement(query, elements, columnMap, publishedMixins, elem, elemName, path);
388
+ handleAssociationElement(query, elements, columnMap, publishedMixins, elem, elemName, elementsPath, queryPath);
342
389
  }
343
390
 
344
391
  if (query && !hasNonAssocElements) {
345
- // Complain if there are no elements other than unmanaged associations
346
- // Allow with plain
392
+ // Complain if there are no elements other than unmanaged associations
393
+ // Allow with plain
347
394
  error(null, [ 'definitions', artName ], { $reviewed: true },
348
395
  'Expecting view or projection to have at least one element that is not an unmanaged association');
349
396
  }
@@ -3,7 +3,7 @@
3
3
  const { setProp, isBetaEnabled } = require('../base/model');
4
4
  const { getUtils, cloneCsn, forEachGeneric,
5
5
  forEachMember,
6
- forEachMemberRecursively, forEachRef,
6
+ forEachMemberRecursively, forEachRef, getNamespace, getResultingName,
7
7
  forAllQueries, forAllElements, hasAnnotationValue, getArtifactDatabaseNameOf,
8
8
  getElementDatabaseNameOf, isBuiltinType, applyTransformations,
9
9
  isPersistedOnDatabase, getNormalizedQuery, isAspect, walkCsnPath,
@@ -213,10 +213,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
213
213
  }
214
214
  });
215
215
 
216
- // Must happen after A2J, as A2J needs $self to correctly resolve stuff
217
- if(doA2J)
218
- flattening.removeLeadingSelf(csn);
219
-
220
216
  const {
221
217
  flattenStructuredElement,
222
218
  flattenStructStepsInRef, getForeignKeyArtifact,
@@ -232,6 +228,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
232
228
  isStructured,
233
229
  addStringAnnotationTo,
234
230
  cloneWithTransformations,
231
+ getContextOfArtifact,
235
232
  } = getUtils(csn);
236
233
 
237
234
  // (000) Rename primitive types, make UUID a String
@@ -394,6 +391,10 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
394
391
  removeKeyPropInType,
395
392
  ]);
396
393
 
394
+ // Remove leading $self to keep renderer-diffs smaller
395
+ if(doA2J)
396
+ flattening.removeLeadingSelf(csn);
397
+
397
398
  throwWithError();
398
399
 
399
400
  timetrace.stop();
@@ -797,14 +798,31 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
797
798
  || hasAnnotationValue(artifact, '@cds.persistence.exists'))
798
799
  artifact._ignore = true;
799
800
 
801
+ const namingMode = options.forHana && options.forHana.names;
800
802
  // issue #3450 HANA CDS can not handle external artifacts which are part of a HANA CDS context
801
- if (options.forHana.names === 'quoted' &&
802
- hasAnnotationValue(artifact, '@cds.persistence.exists')) {
803
- const firstPath = artifactName.split('.')[0];
804
- const topParent = csn.definitions[firstPath];
805
- // namespaces, contexts and services become contexts in HANA CDS
806
- if (topParent && [ 'namespace', 'context', 'service' ].includes(topParent.kind))
807
- warning(null, [ 'definitions', artifactName ], `"${ artifactName }": external definition belongs to ${ topParent.kind } "${ firstPath }"`);
803
+ if (hasAnnotationValue(artifact, '@cds.persistence.exists') &&
804
+ options.transformation === 'hdbcds' &&
805
+ (namingMode === 'quoted' || namingMode === 'hdbcds')) {
806
+ let hanaCDSContextName;
807
+ if(namingMode === 'hdbcds') {
808
+ // for hdbcds names we only create a context if you defined a context/service in your cdl model
809
+ hanaCDSContextName = getContextOfArtifact(artifactName);
810
+ }
811
+ else {
812
+ // for quoted naming mode, we create a context if you either defined a context/service
813
+ // or a namespace in your cdl model
814
+ hanaCDSContextName = getContextOfArtifact(artifactName) || getNamespace(csn, artifactName);
815
+ }
816
+ if (hanaCDSContextName) {
817
+ warning('anno-unstable-hdbcds', [ 'definitions', artifactName ],
818
+ {
819
+ id: getResultingName(csn, options.forHana.names, artifactName),
820
+ name: getResultingName(csn, options.forHana.names, hanaCDSContextName),
821
+ anno: 'cds.persistence.exists',
822
+ },
823
+ 'Do not use $(ANNO) on an entity named $(ID) in SAP HANA CDS when we also create a SAP HANA CDS context named $(NAME)'
824
+ );
825
+ }
808
826
  }
809
827
  }
810
828
  }
@@ -1324,6 +1342,10 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1324
1342
  if (ref[0] === assocName) // we are in the "path" from the forwarding assoc => need to remove the first part of the path
1325
1343
  {
1326
1344
  ref.shift();
1345
+ } else if(ref && ref.length > 1 && ref[0] === '$self' && ref[1] === assocName) {
1346
+ // We could also have a $self infront of the assoc name - so we would need to shift twice
1347
+ ref.shift();
1348
+ ref.shift();
1327
1349
  }
1328
1350
  else { // we are in the backlink assoc "path" => need to push at the beginning the association's id
1329
1351
  ref.unshift(elemName);
@@ -1,9 +1,9 @@
1
1
  'use strict'
2
2
 
3
3
  const { setProp, forEachGeneric, forEachDefinition, isBetaEnabled } = require('../base/model');
4
- var { makeMessageFunction } = require('../base/messages');
4
+ const { makeMessageFunction } = require('../base/messages');
5
5
  const { recompileX } = require('../compiler/index');
6
- var { linkToOrigin } = require('../compiler/utils');
6
+ const { linkToOrigin, pathName } = require('../compiler/utils');
7
7
  const {compactModel, compactExpr} = require('../json/to-csn');
8
8
  const { deduplicateMessages } = require('../base/messages');
9
9
  const { timetrace } = require('../utils/timetrace');
@@ -309,7 +309,7 @@ function translateAssocsToJoins(model, inputOptions = {})
309
309
  let [QA, ps] = rightMostQA(tail, head._navigation._parent.$QA || head._navigation.$QA);
310
310
  if(!QA) {
311
311
  error(null, pathNode.$location,
312
- { name: pathNode.path.map(ps=ps.id).join('.') },
312
+ { name: pathName(pathNode.path) },
313
313
  'Please debug me: No QA found for generic path rewriting in $(NAME)')
314
314
  return;
315
315
  }
package/lib/utils/file.js CHANGED
@@ -3,6 +3,7 @@
3
3
  'use strict';
4
4
 
5
5
  const fs = require('fs');
6
+ const util = require('util');
6
7
 
7
8
  /**
8
9
  * Split the given source string into its lines. Respects Unix,
@@ -19,8 +20,8 @@ function splitLines(src) {
19
20
  * Returns filesystem utils readFile(), isFile(), realpath() for _CDS_ usage.
20
21
  * This includes a trace as well as usage of a file cache.
21
22
  *
22
- * Note: The synchronous versions accept a callback as well, which is executed
23
- * immediately! This is different from NodeJS's readFileSync()!
23
+ * Note: The synchronous versions accept a callback instead of being async (duh!), which
24
+ * is executed immediately! This is different from NodeJS's readFileSync()!
24
25
  * This is done to allow using it in places where fs.readFile (async) is used.
25
26
  *
26
27
  * @param {object} fileCache
@@ -47,10 +48,13 @@ function cdsFs(fileCache, enableTrace) {
47
48
  });
48
49
 
49
50
  return {
51
+ readFileAsync: util.promisify(readFile),
50
52
  readFile,
51
53
  readFileSync,
54
+ isFileAsync: util.promisify(isFile),
52
55
  isFile,
53
56
  isFileSync,
57
+ realpathAsync: util.promisify(realpath),
54
58
  realpath,
55
59
  realpathSync,
56
60
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds-compiler",
3
- "version": "2.11.2",
3
+ "version": "2.11.4",
4
4
  "description": "CDS (Core Data Services) compiler and backends",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "author": "SAP SE (https://www.sap.com)",
@@ -1,58 +0,0 @@
1
- 'use strict';
2
-
3
- /**
4
- * If a mixin association is published, return the mixin association.
5
- *
6
- * @param {CSN.Query} query Query of the artifact to check
7
- * @param {object} association Association (Element) published by the view
8
- * @param {string} associationName
9
- * @returns {object} The mixin association
10
- */
11
- function getMixinAssocOfQueryIfPublished(query, association, associationName) {
12
- if (query && query.SELECT && query.SELECT.mixin) {
13
- const aliasedColumnsMap = Object.create(null);
14
- if (query.SELECT.columns) {
15
- for (const column of query.SELECT.columns) {
16
- if (column.as && column.ref && column.ref.length === 1)
17
- aliasedColumnsMap[column.as] = column;
18
- }
19
- }
20
-
21
- for (const elem of Object.keys(query.SELECT.mixin)) {
22
- const mixinElement = query.SELECT.mixin[elem];
23
- let originalName = associationName;
24
- if (aliasedColumnsMap[associationName])
25
- originalName = aliasedColumnsMap[associationName].ref[0];
26
-
27
- if (elem === originalName)
28
- return { mixinElement, mixinName: originalName };
29
- }
30
- }
31
- return {};
32
- }
33
-
34
- /**
35
- * Check wether the given artifact uses the given mixin association.
36
- *
37
- * @param {CSN.Query} query Query of the artifact to check
38
- * @param {object} association Mixin association (Element) to check for
39
- * @param {string} associationName
40
- * @returns {boolean} True if used
41
- */
42
- function usesMixinAssociation(query, association, associationName) {
43
- if (query && query.SELECT && query.SELECT.columns) {
44
- for (const column of query.SELECT.columns) {
45
- if (typeof column === 'object' && column.ref && column.ref.length > 1 && (column.ref[0] === associationName || column.ref[0].id === associationName)) {
46
- // FIXME: This is not necessarily correct: the assoc name needs not be the first component, as e.g. $projection.assoc
47
- // would be also valid. Check other paths like $self.assoc ....
48
- return true;
49
- }
50
- }
51
- }
52
- return false;
53
- }
54
-
55
- module.exports = {
56
- usesMixinAssociation,
57
- getMixinAssocOfQueryIfPublished,
58
- };