@sap/cds-compiler 4.5.0 → 4.6.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 (64) hide show
  1. package/CHANGELOG.md +43 -7
  2. package/bin/cdsc.js +13 -11
  3. package/doc/CHANGELOG_BETA.md +6 -0
  4. package/lib/api/main.js +256 -115
  5. package/lib/api/options.js +8 -0
  6. package/lib/base/message-registry.js +17 -4
  7. package/lib/base/messages.js +15 -3
  8. package/lib/base/model.js +1 -0
  9. package/lib/base/optionProcessorHelper.js +45 -176
  10. package/lib/checks/elements.js +32 -34
  11. package/lib/checks/enricher.js +39 -3
  12. package/lib/checks/validator.js +2 -3
  13. package/lib/compiler/assert-consistency.js +2 -1
  14. package/lib/compiler/builtins.js +20 -4
  15. package/lib/compiler/checks.js +30 -6
  16. package/lib/compiler/define.js +31 -9
  17. package/lib/compiler/resolve.js +26 -21
  18. package/lib/compiler/shared.js +19 -9
  19. package/lib/compiler/tweak-assocs.js +82 -107
  20. package/lib/compiler/utils.js +2 -1
  21. package/lib/edm/annotations/edmJson.js +23 -22
  22. package/lib/edm/annotations/genericTranslation.js +14 -4
  23. package/lib/edm/csn2edm.js +24 -10
  24. package/lib/edm/edmInboundChecks.js +1 -2
  25. package/lib/edm/edmPreprocessor.js +11 -9
  26. package/lib/edm/edmUtils.js +5 -2
  27. package/lib/gen/Dictionary.json +2 -0
  28. package/lib/gen/language.checksum +1 -1
  29. package/lib/gen/language.interp +4 -1
  30. package/lib/gen/language.tokens +1 -0
  31. package/lib/gen/languageParser.js +5253 -5214
  32. package/lib/json/to-csn.js +7 -1
  33. package/lib/language/antlrParser.js +19 -1
  34. package/lib/language/errorStrategy.js +21 -4
  35. package/lib/language/genericAntlrParser.js +9 -11
  36. package/lib/main.d.ts +28 -3
  37. package/lib/main.js +3 -0
  38. package/lib/model/csnRefs.js +4 -1
  39. package/lib/model/csnUtils.js +12 -7
  40. package/lib/optionProcessor.js +21 -19
  41. package/lib/render/manageConstraints.js +13 -29
  42. package/lib/render/toCdl.js +18 -15
  43. package/lib/render/toHdbcds.js +59 -28
  44. package/lib/render/toRename.js +6 -10
  45. package/lib/render/toSql.js +57 -82
  46. package/lib/render/utils/common.js +17 -0
  47. package/lib/transform/.eslintrc.json +9 -1
  48. package/lib/transform/addTenantFields.js +228 -0
  49. package/lib/transform/db/applyTransformations.js +27 -31
  50. package/lib/transform/db/assertUnique.js +4 -4
  51. package/lib/transform/db/cdsPersistence.js +1 -1
  52. package/lib/transform/db/flattening.js +68 -69
  53. package/lib/transform/db/temporal.js +1 -1
  54. package/lib/transform/draft/db.js +2 -16
  55. package/lib/transform/draft/odata.js +3 -3
  56. package/lib/transform/effective/associations.js +3 -5
  57. package/lib/transform/effective/main.js +6 -9
  58. package/lib/transform/forOdata.js +13 -9
  59. package/lib/transform/forRelationalDB.js +36 -17
  60. package/lib/transform/odata/toFinalBaseType.js +3 -3
  61. package/lib/transform/odata/typesExposure.js +14 -5
  62. package/lib/transform/transformUtils.js +47 -34
  63. package/lib/transform/translateAssocsToJoins.js +33 -8
  64. package/package.json +2 -2
@@ -0,0 +1,228 @@
1
+ // Add tenant field MANDT to entities
2
+
3
+ // Prerequisites:
4
+
5
+ // - the input CSN is a `client` style CSN from the Core Compiler
6
+ // - using structure types with unmanaged associations is not supported by the
7
+ // Core Compiler (due to missing ON-rewrite)
8
+
9
+ // TODO entities without MANDT:
10
+
11
+ // - cache whether structure type contains (managed) association to entity with MANDT
12
+ // - disallow use of such a type in entity without MANDT
13
+
14
+ // Implementation remark:
15
+
16
+ // - the functions `forEachDefinition` & friends in csnUtils.js have become quite
17
+ // (too) general and are probably slow → not used here
18
+
19
+ 'use strict';
20
+
21
+ const { createMessageFunctions } = require( '../base/messages' );
22
+ const { traverseQuery } = require( '../model/csnRefs' );
23
+
24
+ const fieldName = 'tenant';
25
+ const fieldDef = {
26
+ key: true,
27
+ type: 'cds.String',
28
+ length: 36,
29
+ '@cds.api.ignore': true, // and/or $generated: 'tenant' for the full Universal CSN?
30
+ };
31
+
32
+ function addTenantFields( csn, options ) {
33
+ const { definitions } = csn;
34
+ if (!definitions)
35
+ return csn;
36
+ const { error, throwWithError } = createMessageFunctions( options, 'tenant', csn );
37
+
38
+ const csnPath = [ 'definitions', '' ];
39
+ let projection;
40
+
41
+ for (const name in definitions) {
42
+ const art = definitions[name];
43
+ if (art?.kind !== 'entity')
44
+ continue;
45
+ csnPath[1] = name;
46
+
47
+ if (art['@cds.tenant.independent'] != null) {
48
+ error( null, csnPath, { anno: '@cds.tenant.independent' },
49
+ 'Can\'t yet add annotation $(ANNO) to an entity' );
50
+ }
51
+ if (!handleElements( art ))
52
+ continue;
53
+ projection = art.query || art.projection && art;
54
+ if (projection)
55
+ traverseQuery( projection, null, null, handleQuery );
56
+ }
57
+
58
+ (csn.extensions || []).forEach( (ext, idx) => {
59
+ const tenant = ext.elements?.[fieldName];
60
+ const name = ext.annotate || ext.extend; // extend should not happen
61
+ if (tenant && definitions[name]?.kind === 'entity') { // TODO: ok for tenant-independent
62
+ error( null, [ 'extensions', idx, 'elements', 'tenant' ],
63
+ { name: fieldName },
64
+ 'Can\'t annotate element $(NAME) of a tenant-dependent entity' );
65
+ }
66
+ } );
67
+
68
+ throwWithError();
69
+ return csn; // input CSN changed by side effect
70
+
71
+ function handleElements( art ) {
72
+ const { elements } = art;
73
+ if (elements[fieldName]) {
74
+ error( null, [ ...csnPath, 'elements', fieldName ], { name: fieldName },
75
+ 'Can\'t add tenant field to entity having an element $(NAME)' );
76
+ return false;
77
+ }
78
+ if (!Object.values( elements ).some( e => e.key )) {
79
+ error( null, csnPath, {},
80
+ 'There must be a key in a tenant-dependent entity' );
81
+ return false;
82
+ }
83
+ handleAssociations( art );
84
+ art.elements = { [fieldName]: { ...fieldDef }, ...elements };
85
+ return true;
86
+ }
87
+
88
+ function handleQuery( query ) {
89
+ // TODO: errors are temporary: start with simple projections only = no better
90
+ // message $location necessary yet
91
+ if (!projection) // error already reported
92
+ return;
93
+ if (query.ref) {
94
+ if ((query.as || implicitAs( query.ref )) === fieldName) {
95
+ error( null, csnPath, { name: fieldName },
96
+ 'Can\'t have a table alias named $(NAME) in a tenant-dependent entity' );
97
+ }
98
+ return;
99
+ }
100
+
101
+ const select = query.SELECT || query.projection;
102
+ if (query.SET || query !== projection || !select?.from?.ref) {
103
+ error( null, csnPath, {},
104
+ 'Can\'t add tenant columns to non-simple query entities' );
105
+ projection = null;
106
+ return;
107
+ }
108
+
109
+ if (query.projection)
110
+ csnPath.push( 'projection' );
111
+ else
112
+ csnPath.push( 'query', 'SELECT' );
113
+
114
+ if (select.mixin)
115
+ handleMixins( select.mixin );
116
+ if (select.excluding)
117
+ checkExcluding( select.excluding );
118
+ if (select.columns)
119
+ handleColumns( select.columns );
120
+ // TODO: for subqueries, we might need to adapt the inferred elements
121
+ // TODO: where exists ref -
122
+ // TODO: select and query clauses, especially with aggregation functions
123
+ handleGroupBy( select );
124
+ csnPath.length = 2;
125
+ }
126
+
127
+ function handleMixins( mixin ) {
128
+ csnPath.push( 'mixin', '' );
129
+ for (const name in mixin) {
130
+ csnPath[csnPath.length - 1] = name;
131
+ if (name !== fieldName) {
132
+ addToCondition( mixin[name], name );
133
+ }
134
+ else {
135
+ error( null, csnPath, { name },
136
+ 'Can\'t define a mixin named $(NAME) in a tenant-dependent entity' );
137
+ }
138
+ }
139
+ csnPath.length -= 2;
140
+ }
141
+
142
+ function checkExcluding( excludeList ) {
143
+ if (excludeList.includes( fieldName )) {
144
+ error( null, csnPath, { name: fieldName },
145
+ 'Can\'t exclude $(NAME) from query source' );
146
+ }
147
+ }
148
+
149
+ function handleGroupBy( select ) {
150
+ // TODO: in the future, we allow model-wise keyless views when using
151
+ // aggregation function, and add a GROUP BY for MANDT in this case. Now, also
152
+ // views with agg functions need to have a key element → it very likely
153
+ // already contains a GROUP BY. And anyway: if we miss to add GROUP BY MANDT,
154
+ // the database will complain → no safetly risk.
155
+ if (select.groupBy)
156
+ select.groupBy.unshift( { ref: [ fieldName ] } );
157
+ }
158
+
159
+ function handleColumns( columns ) {
160
+ let specifiedKey = false;
161
+ csnPath.push( 'columns', -1 );
162
+ for (const col of columns) {
163
+ ++csnPath[csnPath.length - 1];
164
+ if (col.expand || col.inline) {
165
+ error( null, csnPath, {},
166
+ 'Can\'t use expand/inline in a tenant-dependent entity' );
167
+ }
168
+ if (col.key != null) // yes, also with key: false
169
+ specifiedKey = true;
170
+ if (col.cast?.on) // REDIRECTED TO with explicit ON - TODO (low prio): less $self
171
+ addToCondition( col.cast, col.as || implicitAs( col.ref ) );
172
+ }
173
+ csnPath.length -= 2;
174
+ columns.unshift( specifiedKey
175
+ ? { key: true, ref: [ fieldName ] }
176
+ : { ref: [ fieldName ] } );
177
+ }
178
+
179
+ function handleAssociations( elem ) {
180
+ const { elements } = elem;
181
+ if (elements) {
182
+ csnPath.push( 'elements', '' );
183
+ for (const name in elements) {
184
+ csnPath[csnPath.length - 1] = name;
185
+ handleAssociations( elements[name] );
186
+ }
187
+ csnPath.length -= 2;
188
+ }
189
+ else if (elem.target) {
190
+ if (elem.on) {
191
+ addToCondition( elem, csnPath[csnPath.length - 1] );
192
+ }
193
+ else {
194
+ error( null, csnPath, {},
195
+ 'Can\'t yet use managed associations in a tenant-dependent entity' );
196
+ }
197
+ }
198
+ else if (elem.items) {
199
+ csnPath.push( 'items' );
200
+ handleAssociations( elem.items );
201
+ --csnPath.length;
202
+ }
203
+ }
204
+
205
+ function addToCondition( elem, assoc ) {
206
+ if (!elem.on)
207
+ return;
208
+ const withSelf = csnPath.length > 4;
209
+ elem.on = [
210
+ { ref: [ assoc, fieldName ] }, // TODO: consider assoc name starting with '$'
211
+ '=',
212
+ { ref: (withSelf) ? [ '$self', fieldName ] : [ fieldName ] },
213
+ 'and',
214
+ // TODO: avoid (...) for standard AND-ed EQ-comparisons ?
215
+ { xpr: elem.on },
216
+ ];
217
+ }
218
+ }
219
+
220
+ function implicitAs( ref ) {
221
+ const item = ref[ref.length - 1];
222
+ const id = (typeof item === 'string') ? item : item.id;
223
+ return id.substring( id.lastIndexOf('.') + 1 );
224
+ }
225
+
226
+ module.exports = {
227
+ addTenantFields,
228
+ };
@@ -13,7 +13,7 @@
13
13
 
14
14
 
15
15
  const { setProp } = require('../../base/model');
16
- const { xprInAnnoProperties } = require('../../compiler/builtins');
16
+ const { isAnnotationExpression } = require('../../compiler/builtins');
17
17
 
18
18
 
19
19
  /**
@@ -148,7 +148,7 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
148
148
  */
149
149
  function annotation( _parent, _prop, node ) {
150
150
  if (options.processAnnotations) {
151
- if (node?.['='] !== undefined && xprInAnnoProperties.some(xProp => node[xProp] !== undefined)) {
151
+ if (isAnnotationExpression(node)) {
152
152
  standard(_parent, _prop, node);
153
153
  }
154
154
  else if (node && typeof node === 'object') {
@@ -299,47 +299,43 @@ function applyTransformationsOnDictionary( dictionary, customTransformers = {},
299
299
  /**
300
300
  * transformExpression is a lightweight version of applyTransformations
301
301
  * used primarily to transform annotation expressions.
302
+ * If propName is undefined, all properties of parent are transformed.
302
303
  * @param {object} parent Start node
303
- * @param {object} customTransformers Map of callback functions
304
+ * @param {string} propName Start at specific property of parent
305
+ * @param {object} transformers Map of callback functions
304
306
  * @param {CSN.Path} path Path to parent
305
- * @returns {object} transformed parent
307
+ * @returns {object} transformed node
306
308
  */
307
- function transformExpression( parent, customTransformers, path = [] ) {
308
- const csnPath = [ ...path ];
309
- /**
310
- *
311
- * @param {object} _parent
312
- * @param {string} _prop
313
- * @param {object} node
314
- */
315
- function standard( _parent, _prop, node ) {
316
- if (!node || typeof node !== 'object' ||
317
- !{}.propertyIsEnumerable.call( _parent, _prop ) ||
318
- (typeof _prop === 'string' && _prop.startsWith('@')))
319
- return;
320
-
321
-
322
- csnPath.push(_prop);
323
- if (Array.isArray(node)) {
324
- node.forEach( (n, i) => standard( node, i, n ) );
309
+ function transformExpression( parent, propName, transformers, path = [] ) {
310
+ if (propName != null) {
311
+ const child = parent[propName];
312
+ if (!child || typeof child !== 'object' ||
313
+ !{}.propertyIsEnumerable.call( parent, propName ))
314
+ return parent;
315
+
316
+ path = [ ...path, propName ];
317
+ if (Array.isArray(child)) {
318
+ child.forEach( (n, i) => transformExpression( child, i, transformers, path ) );
325
319
  }
326
320
  else {
327
- for (const name of Object.getOwnPropertyNames( node )) {
328
- const ct = customTransformers[name];
321
+ for (const cpn of Object.getOwnPropertyNames( child )) {
322
+ const ct = transformers[cpn];
329
323
  if (ct) {
324
+ const ppn = propName;
330
325
  if (Array.isArray(ct))
331
- ct.forEach(cti => cti(node, name, node[name], csnPath, _parent, _prop));
326
+ ct.forEach(cti => cti(child, cpn, child[cpn], path, parent, ppn));
327
+
332
328
  else
333
- ct(node, name, node[name], csnPath, _parent, _prop);
329
+ ct(child, cpn, child[cpn], path, parent, ppn);
334
330
  }
335
- standard(node, name, node[name]);
331
+ transformExpression(child, cpn, transformers, path);
336
332
  }
337
333
  }
338
- csnPath.pop();
339
334
  }
340
-
341
- for (const name of Object.getOwnPropertyNames( parent ))
342
- standard( parent, name, parent[name] );
335
+ else {
336
+ for (propName of Object.getOwnPropertyNames( parent ))
337
+ transformExpression( parent, propName, transformers, path );
338
+ }
343
339
  return parent;
344
340
  }
345
341
 
@@ -16,12 +16,12 @@ const { pathName } = require('../../compiler/utils');
16
16
  *
17
17
  * @param {CSN.Model} csn Overall CSN model
18
18
  * @param {CSN.Options} options Options
19
- * @param {Function} error Message function for errors
20
- * @param {Function} info Message function for info
19
+ * @param {object} messageFunctions Message functions (error(), info(), …)
21
20
  * @returns {Function} forEachDefinition callback
22
21
  */
23
- function processAssertUnique( csn, options, error, info ) {
24
- const { resolvePath, flattenPath } = getTransformers(csn, options);
22
+ function processAssertUnique( csn, options, messageFunctions ) {
23
+ const { resolvePath, flattenPath } = getTransformers(csn, options, messageFunctions);
24
+ const { error, info } = messageFunctions;
25
25
 
26
26
  return handleAssertUnique;
27
27
  /**
@@ -106,7 +106,7 @@ function getPersistenceTableProcessor( csn, options, messageFunctions ) {
106
106
  const { error } = messageFunctions;
107
107
  const {
108
108
  recurseElements,
109
- } = transformUtils.getTransformers(csn, options, '_');
109
+ } = transformUtils.getTransformers(csn, options, messageFunctions, '_');
110
110
 
111
111
  return handleQueryish;
112
112
 
@@ -9,8 +9,8 @@ const transformUtils = require('../transformUtils');
9
9
  const { csnRefs } = require('../../model/csnRefs');
10
10
  const { setProp, isBetaEnabled } = require('../../base/model');
11
11
  const { forEach } = require('../../utils/objectUtils');
12
- const { cardinality2str } = require('../../model/csnUtils');
13
-
12
+ const { cardinality2str, isAnnotationExpression } = require('../../model/csnUtils');
13
+ const { transformExpression } = require('./applyTransformations');
14
14
  /**
15
15
  * Strip off leading $self from refs where applicable
16
16
  *
@@ -42,11 +42,12 @@ function removeLeadingSelf( csn ) {
42
42
  *
43
43
  * @param {CSN.Model} csn
44
44
  * @param {CSN.Options} options
45
+ * @param {object} messageFunctions Message functions such as `error()`, `info()`, …
45
46
  * @param {WeakMap} resolved Cache for resolved refs
46
47
  * @param {string} pathDelimiter
47
48
  * @param {object} iterateOptions
48
49
  */
49
- function resolveTypeReferences( csn, options, resolved, pathDelimiter, iterateOptions = {} ) {
50
+ function resolveTypeReferences( csn, options, messageFunctions, resolved, pathDelimiter, iterateOptions = {} ) {
50
51
  /**
51
52
  * Remove .localized from the element and any sub-elements
52
53
  *
@@ -67,7 +68,7 @@ function resolveTypeReferences( csn, options, resolved, pathDelimiter, iterateOp
67
68
  stack.push(...Object.values(current.elements));
68
69
  }
69
70
  }
70
- const { toFinalBaseType, csnUtils } = transformUtils.getTransformers(csn, options, pathDelimiter);
71
+ const { toFinalBaseType, csnUtils } = transformUtils.getTransformers(csn, options, messageFunctions, pathDelimiter);
71
72
  const { getServiceName, getFinalTypeInfo } = csnUtils;
72
73
 
73
74
  // We don't want to iterate over actions
@@ -95,18 +96,17 @@ function resolveTypeReferences( csn, options, resolved, pathDelimiter, iterateOp
95
96
 
96
97
  // structured types might not have the child-types replaced.
97
98
  // Drill down to ensure this.
98
- let nextElements = node.elements || node.items?.elements;
99
- if (nextElements) {
100
- const stack = [ nextElements ];
101
- while (stack.length > 0) {
102
- const elements = stack.pop();
103
- for (const e of Object.values(elements)) {
104
- if (e.type && !isBuiltinType(e.type))
105
- toFinalBaseType(e, resolved, true);
106
- nextElements = e.elements || e.items?.elements;
107
- if (nextElements)
108
- stack.push(nextElements);
109
- }
99
+ const nextElements = node.elements || node.items?.elements;
100
+ const stack = nextElements ? [ nextElements ] : [];
101
+ while (stack.length > 0) {
102
+ const elements = stack.pop();
103
+ for (const e of Object.values(elements)) {
104
+ toFinalBaseType(e, resolved, true);
105
+ if (!options.toOdata && e.items) // items could have unresolved types
106
+ toFinalBaseType(e.items, resolved, true);
107
+ const next = e.elements || e.items?.elements;
108
+ if (next)
109
+ stack.push(next);
110
110
  }
111
111
  }
112
112
 
@@ -164,20 +164,21 @@ function resolveTypeReferences( csn, options, resolved, pathDelimiter, iterateOp
164
164
  */
165
165
  function isODataItems( typeName ) {
166
166
  const typeDef = csn.definitions[typeName];
167
- return !!(options.toOdata && typeDef && typeDef.items);
167
+ return !!(options.toOdata && typeDef && typeDef?.items);
168
168
  }
169
169
  }
170
170
 
171
171
  /**
172
172
  * @param {CSN.Model} csn
173
173
  * @param {CSN.Options} options
174
+ * @param {object} messageFunctions Message functions such as `error()`, `info()`, …
174
175
  * @param {WeakMap} resolved Cache for resolved refs
175
176
  * @param {string} pathDelimiter
176
177
  * @param {object} iterateOptions
177
178
  */
178
- function flattenAllStructStepsInRefs( csn, options, resolved, pathDelimiter, iterateOptions = {} ) {
179
+ function flattenAllStructStepsInRefs( csn, options, messageFunctions, resolved, pathDelimiter, iterateOptions = {} ) {
179
180
  const { inspectRef, effectiveType } = csnRefs(csn);
180
- const { flattenStructStepsInRef } = transformUtils.getTransformers(csn, options, pathDelimiter);
181
+ const { flattenStructStepsInRef } = transformUtils.getTransformers(csn, options, messageFunctions, pathDelimiter);
181
182
  const adaptRefs = [];
182
183
 
183
184
  /**
@@ -258,12 +259,13 @@ function flattenAllStructStepsInRefs( csn, options, resolved, pathDelimiter, ite
258
259
  /**
259
260
  * @param {CSN.Model} csn
260
261
  * @param {CSN.Options} options
262
+ * @param {object} messageFunctions Message functions such as `error()`, `info()`, …
261
263
  * @param {string} pathDelimiter
262
- * @param {Function} error
263
264
  * @param {object} iterateOptions
264
265
  */
265
- function flattenElements( csn, options, pathDelimiter, error, iterateOptions = {} ) {
266
- const { flattenStructuredElement, csnUtils } = transformUtils.getTransformers(csn, options, pathDelimiter);
266
+ function flattenElements( csn, options, messageFunctions, pathDelimiter, iterateOptions = {} ) {
267
+ const { error } = messageFunctions;
268
+ const { flattenStructuredElement, csnUtils } = transformUtils.getTransformers(csn, options, messageFunctions, pathDelimiter);
267
269
  const { isAssocOrComposition, effectiveType } = csnUtils;
268
270
  const transformers = {
269
271
  elements: flatten,
@@ -309,33 +311,28 @@ function flattenElements( csn, options, pathDelimiter, error, iterateOptions = {
309
311
  if (flatElement.notNull !== false && !branch.some(s => !s.notNull))
310
312
  flatElement.notNull = true;
311
313
 
312
-
313
314
  if (flatElement.type && isAssocOrComposition(flatElement) && flatElement.on) {
314
315
  // unmanaged relations can't be primary key
315
316
  delete flatElement.key;
316
- // Make refs resolvable by fixing the first ref step
317
- for (const onPart of flatElement.on) {
318
- if (onPart.ref) {
319
- const firstRef = onPart.ref[0];
320
-
321
- /*
322
- when element is defined in the current name resolution scope, like
323
- entity E {
324
- key x: Integer;
325
- s : {
326
- y : Integer;
327
- a3 : association to E on a3.x = y;
328
- }
329
- }
330
- We need to replace y with s_y and a3 with s_a3 - we must take care to not escape our local scope
331
- */
317
+ transformExpression(flatElement, 'on', {
318
+ ref: (_parent, _prop, xpr) => {
332
319
  const prefix = flatElement._flatElementNameWithDots.split('.').slice(0, -1).join(pathDelimiter);
333
- const possibleFlatName = prefix + pathDelimiter + firstRef;
334
-
320
+ const possibleFlatName = prefix + pathDelimiter + xpr[0];
321
+ /*
322
+ when element is defined in the current name resolution scope, like
323
+ entity E {
324
+ key x: Integer;
325
+ s : {
326
+ y : Integer;
327
+ a3 : association to E on a3.x = y;
328
+ }
329
+ }
330
+ We need to replace y with s_y and a3 with s_a3 - we must take care to not escape our local scope
331
+ */
335
332
  if (flatElems[possibleFlatName])
336
- onPart.ref[0] = possibleFlatName;
337
- }
338
- }
333
+ xpr[0] = possibleFlatName;
334
+ },
335
+ });
339
336
  }
340
337
  parent[prop].$orderedElements.push([ flatElemName, flatElement ]);
341
338
  // Still add them - otherwise we might not detect collisions between generated elements.
@@ -434,16 +431,16 @@ function linkForeignKeyAnnotationExtensionsToAssociation( csn, options ) {
434
431
  /**
435
432
  * @param {CSN.Model} csn
436
433
  * @param {CSN.Options} options
437
- * @param {Function} error
438
- * @param {Function} warning
434
+ * @param {object} messageFunctions Message functions such as `error()`, `info()`, …
439
435
  * @param {string} pathDelimiter
440
436
  * @param {boolean} flattenKeyRefs
441
437
  * @param {object} csnUtils
442
438
  * @param {object} iterateOptions
443
439
  */
444
- function handleManagedAssociationsAndCreateForeignKeys( csn, options, error, warning, pathDelimiter, flattenKeyRefs, csnUtils, iterateOptions = {} ) {
440
+ function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFunctions, pathDelimiter, flattenKeyRefs, csnUtils, iterateOptions = {} ) {
441
+ const { error, warning } = messageFunctions;
445
442
  const { isManagedAssociation, inspectRef, isStructured } = csnUtils;
446
- const { flattenStructStepsInRef, flattenStructuredElement } = transformUtils.getTransformers(csn, options, pathDelimiter);
443
+ const { flattenStructStepsInRef, flattenStructuredElement } = transformUtils.getTransformers(csn, options, messageFunctions, pathDelimiter);
447
444
  if (flattenKeyRefs) {
448
445
  applyTransformations(csn, {
449
446
  elements: (parent, prop, elements, path) => {
@@ -814,6 +811,29 @@ function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathD
814
811
  return [ [ prefix, newFk ] ];
815
812
  }
816
813
 
814
+ fks.forEach((fk) => {
815
+ // prepend current prefix
816
+ fk[0] = `${prefix}${pathDelimiter}${fk[0]}`;
817
+ // if this is the entry association, decorate the final foreign keys with the association props
818
+ if (lvl === 0) {
819
+ if (options.transformation !== 'effective')
820
+ fk[1]['@odata.foreignKey4'] = prefix;
821
+ if (options.transformation === 'odata' || options.transformation === 'effective') {
822
+ const validAnnoNames = Object.keys(element).filter(pn => pn[0] === '@' && !isAnnotationExpression(element[pn]));
823
+ copyAnnotations(element, fk[1], true, {}, validAnnoNames);
824
+ }
825
+ // propagate not null to final foreign key
826
+ for (const prop of [ 'notNull', 'key' ]) {
827
+ if (element[prop] !== undefined)
828
+ fk[1][prop] = element[prop];
829
+ }
830
+ if (element.$location)
831
+ setProp(fk[1], '$location', element.$location);
832
+ }
833
+ });
834
+ return fks;
835
+
836
+
817
837
  /**
818
838
  * Get the path to continue resolving references
819
839
  *
@@ -834,27 +854,6 @@ function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathD
834
854
  return [ path, ...additions ];
835
855
  return [ ...path, ...additions ];
836
856
  }
837
-
838
- fks.forEach((fk) => {
839
- // prepend current prefix
840
- fk[0] = `${prefix}${pathDelimiter}${fk[0]}`;
841
- // if this is the entry association, decorate the final foreign keys with the association props
842
- if (lvl === 0) {
843
- if (options.transformation !== 'effective')
844
- fk[1]['@odata.foreignKey4'] = prefix;
845
- if (options.transformation === 'odata' || options.transformation === 'effective')
846
- copyAnnotations(element, fk[1], true);
847
-
848
- // propagate not null to final foreign key
849
- for (const prop of [ 'notNull', 'key' ]) {
850
- if (element[prop] !== undefined)
851
- fk[1][prop] = element[prop];
852
- }
853
- if (element.$location)
854
- setProp(fk[1], '$location', element.$location);
855
- }
856
- });
857
- return fks;
858
857
  }
859
858
 
860
859
  module.exports = {
@@ -171,7 +171,7 @@ function getAnnotationHandler( csn, options, pathDelimiter, messageFunctions ) {
171
171
  const { error } = messageFunctions;
172
172
  const {
173
173
  extractValidFromToKeyElement, checkAssignment, checkMultipleAssignments, recurseElements,
174
- } = getTransformers(csn, options, pathDelimiter);
174
+ } = getTransformers(csn, options, messageFunctions, pathDelimiter);
175
175
 
176
176
  return handleTemporalAnnotations;
177
177
  /**
@@ -26,7 +26,7 @@ function generateDrafts( csn, options, pathDelimiter, messageFunctions ) {
26
26
  const {
27
27
  createForeignKeyElement, createAndAddDraftAdminDataProjection, createScalarElement, createAssociationElement,
28
28
  addElement, copyAndAddElement, createAssociationPathComparison, csnUtils,
29
- } = getTransformers(csn, options, pathDelimiter);
29
+ } = getTransformers(csn, options, messageFunctions, pathDelimiter);
30
30
  const { getCsnDef, isComposition } = csnUtils;
31
31
  const { error, warning } = messageFunctions;
32
32
  const generatedArtifacts = Object.create(null);
@@ -122,26 +122,12 @@ function generateDrafts( csn, options, pathDelimiter, messageFunctions ) {
122
122
 
123
123
  generatedArtifacts[draftsArtifactName] = true;
124
124
 
125
- // extract keys for UUID inspection
126
- const keys = [];
125
+ // TODO: Do we really need this? Is this possibly done by a validator earlier?
127
126
  forEachMemberRecursively(artifact, (elt, name, prop, path) => {
128
127
  if (!elt.elements && !elt.type && !elt.virtual) // only check leafs
129
128
  error(null, path, 'Expecting element to have a type when used in a draft-enabled artifact');
130
- if (elt.key && elt.key === true && !elt.virtual)
131
- keys.push(elt);
132
129
  }, [ 'definitions', artifactName ], false, { elementsOnly: true });
133
130
 
134
- // In contrast to EDM, the DB entity may have more than one technical keys but should have ideally exactly one key of type cds.UUID
135
- if (keys.length !== 1) {
136
- warning(null, [ 'definitions', artifactName ], { count: keys.length },
137
- 'Entity annotated with “@odata.draft.enabled” should have exactly one key element, but found $(COUNT)');
138
- }
139
- else {
140
- const uuidCount = keys.reduce((acc, k) => ((k.type === 'cds.UUID' || k.type === 'cds.String' && k.$renamed === 'cds.UUID' && k.length === 36) ? acc + 1 : acc), 0);
141
- if (uuidCount === 0)
142
- warning(null, [ 'definitions', artifactName ], 'Entity annotated with “@odata.draft.enabled” should have one key element of type “cds.UUID”');
143
- }
144
-
145
131
  // Ignore boolean return value. We know that we're inside a service or else we wouldn't have reached this code.
146
132
  const matchingService = getMatchingService(artifactName) || '';
147
133
  // Generate the DraftAdministrativeData projection into the service, unless there is already one
@@ -27,21 +27,21 @@ const { makeMessageFunction } = require('../../base/messages');
27
27
  * @todo check if needed at all: Remove '$projection' from paths in the element's ON-condition
28
28
  */
29
29
  function generateDrafts( csn, options, services ) {
30
+ const messageFunctions = makeMessageFunction(csn, options, 'for.odata');
31
+ const { error, info } = messageFunctions;
30
32
  const {
31
33
  createAndAddDraftAdminDataProjection, createScalarElement,
32
34
  createAssociationElement, createAssociationPathComparison,
33
35
  addElement, createAction, assignAction,
34
36
  resetAnnotation,
35
37
  csnUtils,
36
- } = getTransformers(csn, options);
38
+ } = getTransformers(csn, options, messageFunctions);
37
39
  const {
38
40
  getServiceName,
39
41
  hasAnnotationValue,
40
42
  getFinalTypeInfo,
41
43
  } = csnUtils;
42
44
 
43
- const { error, info } = makeMessageFunction(csn, options, 'for.odata');
44
-
45
45
  if (!services)
46
46
  services = getServiceNames(csn);
47
47