@sap/cds-compiler 5.5.2 → 5.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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { isBuiltinType } = require('../../base/builtins');
4
4
  const { setProp } = require('../../base/model');
5
- const { applyTransformations, implicitAs, findAnnotationExpression, copyAnnotations, isDeepEqual } = require('../../model/csnUtils');
5
+ const { transformAnnotationExpression, applyTransformations, implicitAs, findAnnotationExpression, copyAnnotations, isDeepEqual } = require('../../model/csnUtils');
6
6
  const { EdmTypeFacetNames } = require('../../edm/EdmPrimitiveTypeDefinitions');
7
7
 
8
8
  function createForeignKeyElements(csn, options, messageFunctions, csnUtils, iterateOptions = {}) {
@@ -60,6 +60,8 @@ function createForeignKeyElements(csn, options, messageFunctions, csnUtils, iter
60
60
  if (options.transformation === 'effective')
61
61
  delete element.default;
62
62
  }
63
+
64
+ adaptAnnotationsRefs(generatedForeignKeys, csnUtils);
63
65
  setProp(element, '$generatedForeignKeys', generatedForeignKeys.map(gfk => gfk[0]));
64
66
  orderedElements.push(...generatedForeignKeys);
65
67
 
@@ -69,9 +71,9 @@ function createForeignKeyElements(csn, options, messageFunctions, csnUtils, iter
69
71
  elementsAccumulator[name] = element;
70
72
  return elementsAccumulator;
71
73
  }, Object.create(null));
72
- }
74
+ }
73
75
 
74
- function createForeignKeysForElement(csnUtils, path, element, prefix, csn, options, pathDelimiter, lvl = 0 ) {
76
+ function createForeignKeysForElement(csnUtils, path, element, prefix, csn, options, pathDelimiter, lvl = 0, originalkey = {} ) {
75
77
  const special$self = !csn?.definitions?.$self && '$self';
76
78
  const isInspectRefResult = !Array.isArray(path);
77
79
 
@@ -109,7 +111,16 @@ function createForeignKeysForElement(csnUtils, path, element, prefix, csn, optio
109
111
  const continuePath = getContinuePath([ 'keys', keyIndex ]);
110
112
  const alias = key.as || implicitAs(key.ref);
111
113
  const result = csnUtils.inspectRef(continuePath);
112
- fks = fks.concat(createForeignKeysForElement(csnUtils, result, result.art, alias, csn, options, pathDelimiter, lvl + 1));
114
+ let gfks = createForeignKeysForElement(csnUtils, result, result.art, alias, csn, options, pathDelimiter, lvl + 1,
115
+ lvl === 0 ? { ref: key.ref, as: key.as, $path: key.$path, $originalKeyRef: key.$originalKeyRef } : originalkey);
116
+ if (lvl === 0) {
117
+ gfks.forEach(gfk => copyAnnotations(key, gfk[1]));
118
+ // once applied -> remove the annotations from the keys array, to keep the OData CSN size as small as possible
119
+ Object.keys(key).forEach( prop => {
120
+ if (prop[0] === '@') delete key[prop]
121
+ });
122
+ }
123
+ fks = fks.concat(gfks);
113
124
  });
114
125
  }
115
126
  // the element is a structure
@@ -118,7 +129,7 @@ function createForeignKeysForElement(csnUtils, path, element, prefix, csn, optio
118
129
  // Skip already produced foreign keys
119
130
  if (!elem['@odata.foreignKey4']) {
120
131
  const continuePath = getContinuePath([ 'elements', elemName ]);
121
- fks = fks.concat(createForeignKeysForElement(csnUtils, continuePath, elem, elemName, csn, options, pathDelimiter, lvl + 1));
132
+ fks = fks.concat(createForeignKeysForElement(csnUtils, continuePath, elem, elemName, csn, options, pathDelimiter, lvl + 1, originalkey));
122
133
  }
123
134
  });
124
135
  }
@@ -130,7 +141,7 @@ function createForeignKeysForElement(csnUtils, path, element, prefix, csn, optio
130
141
  if (element[prop] !== undefined)
131
142
  newFk[prop] = element[prop];
132
143
  });
133
- return [ [ prefix, newFk ] ];
144
+ return [ [ prefix, newFk, originalkey ] ];
134
145
  }
135
146
 
136
147
  fks.forEach((fk) => {
@@ -140,9 +151,13 @@ function createForeignKeysForElement(csnUtils, path, element, prefix, csn, optio
140
151
  if (lvl === 0) {
141
152
  if (options.transformation !== 'effective')
142
153
  fk[1]['@odata.foreignKey4'] = prefix;
143
-
144
- const validAnnoNames = Object.keys(element).filter(pn => pn[0] === '@' && !findAnnotationExpression(element, pn));
145
- copyAnnotations(element, fk[1], true, {}, validAnnoNames);
154
+
155
+ const allowedOverwriteAnnotationNames = ['@odata.Type', ...EdmTypeFacetNames.map(f => `@odata.${f}`)];
156
+ const validAnnoNames = Object.keys(element).filter(pn => pn[0] === '@' && !allowedOverwriteAnnotationNames.includes(pn) && !findAnnotationExpression(element, pn));
157
+ copyAnnotations(element, fk[1], false, {}, validAnnoNames);
158
+ const overwriteAnnoNames = Object.keys(element).filter(pn => allowedOverwriteAnnotationNames.includes(pn) && !findAnnotationExpression(element, pn));
159
+ copyAnnotations(element, fk[1], true, {}, overwriteAnnoNames);
160
+
146
161
  // propagate not null to final foreign key
147
162
  for (const prop of [ 'notNull', 'key' ]) {
148
163
  if (element[prop] !== undefined)
@@ -175,6 +190,74 @@ function createForeignKeysForElement(csnUtils, path, element, prefix, csn, optio
175
190
  return [ ...path, ...additions ];
176
191
  }
177
192
  }
193
+
194
+ function adaptAnnotationsRefs(generatedForeignKeys, csnUtils) {
195
+ let reportedErrorsForAnnoPath = {};
196
+ generatedForeignKeys.forEach(gfk => {
197
+ Object.entries(gfk[1]).forEach(([key, value]) => {
198
+ if (key[0] !== '@') return;
199
+
200
+ transformAnnotationExpression(gfk[1], key, {
201
+ ref: (_parent, _prop, ref, path, _p, _ppn, ctx) => {
202
+ if (ref[0] !== '$self') {
203
+
204
+ const { art } = csnUtils.inspectRef(getOriginatingKeyPath(gfk, path));
205
+ if (csnUtils.isManagedAssociation(art)) {
206
+ if (!reportedErrorsForAnnoPath[path]) {
207
+ error('odata-anno-xpr-ref', path, { elemref: { ref }, anno: key, '#': 'fk_substitution' });
208
+ reportedErrorsForAnnoPath[path] = true;
209
+ }
210
+ } else {
211
+ const gfkForRef = findGeneratedForeignKeyForKeyRef(generatedForeignKeys, ref);
212
+ if (gfkForRef.length === 1) {
213
+ ref[0] = gfkForRef[0][0];
214
+
215
+ if (ctx?.annoExpr?.['=']) {
216
+ ctx.annoExpr['='] = true;
217
+ }
218
+ } else {
219
+ // check if the annotation reference points to a structure that has been expanded,
220
+ // if so -> report an error
221
+ const foundInOriginalRef = findOriginalRef(generatedForeignKeys.filter(gfk => gfk[2].$originalKeyRef), ref);
222
+ // references to expanded structures in flat mode will be found in the $originalKeyRef
223
+ // and in strucred mode more than one match will be found in the generated foreign keys
224
+ if ((foundInOriginalRef.length || gfkForRef.length > 1) && !reportedErrorsForAnnoPath[path]) {
225
+ error('odata-anno-xpr-ref', path, { elemref: { ref }, anno: key, '#': 'fk_substitution' });
226
+ reportedErrorsForAnnoPath[path] = true;
227
+ }
228
+ }
229
+ }
230
+ }
231
+ }
232
+ }, value?.$path?.slice(0, value.$path.length - 1));
233
+ });
234
+ });
235
+
236
+ // During tuple expansion, the key ref object looses the $path, therefore
237
+ // it needs to be extracted from the anno path
238
+ function getOriginatingKeyPath(gfk, path) {
239
+ return gfk[2].$path || path.slice(0, path.findIndex(ps => ps[0] === '@'));
240
+ }
241
+
242
+ // Loops through the generated foreign keys for this entity
243
+ // and filters the ones, which were created for this specific
244
+ // key ref. In case there are more than one foreign keys found,
245
+ // that means the key ref points to a structured element/managed assoc
246
+ function findGeneratedForeignKeyForKeyRef(generatedForeignKeys, ref) {
247
+ return generatedForeignKeys.filter(gfk => {
248
+ return (ref.join() === (gfk[2].as || implicitAs(gfk[2].ref)));
249
+ });
250
+ }
251
+
252
+ // Tuple expansion is performed before the generation of the foreign keys and the original(unexpanded) key ref
253
+ // is stored in the property $originalKeyRef. Here we try to evaluate whether the reference in the annotation
254
+ // points to a structure that has been expanded.
255
+ function findOriginalRef(generatedForeignKeys, ref) {
256
+ return generatedForeignKeys.filter(gfk => {
257
+ return (ref.join() === (gfk[2].$originalKeyRef.as || implicitAs(gfk[2].$originalKeyRef.ref)));
258
+ });
259
+ }
260
+ }
178
261
  }
179
262
 
180
263
  module.exports = createForeignKeyElements;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds-compiler",
3
- "version": "5.5.2",
3
+ "version": "5.6.0",
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)",