@sap/cds-compiler 6.5.2 → 6.6.2

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.
@@ -21,7 +21,7 @@ const { conditionAsTree, expressionAsTree } = require('../../model/xprAsTree');
21
21
  * @returns {object}
22
22
  */
23
23
 
24
- function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
24
+ function xpr2edmJson( carrier, anno, location, options, messageFunctions, genericTranslationHelpers ) {
25
25
  const { message, error } = messageFunctions;
26
26
 
27
27
  const annoVal = carrier[anno];
@@ -77,9 +77,9 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
77
77
  };
78
78
  //----------------------------------
79
79
  // Error transformer
80
- const notADynExpr = (parent, op, xpr, csnPath, parentParent, parentProp, txt) => {
80
+ const notADynExpr = (parent, op, xpr, csnPath, parentParent, parentProp, ctx) => {
81
81
  error('odata-anno-xpr', location, {
82
- anno, op: txt ?? op, '#': 'notadynexpr',
82
+ anno, op: ctx.txt ?? op, '#': 'notadynexpr',
83
83
  });
84
84
  delete parent[op];
85
85
  };
@@ -95,26 +95,26 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
95
95
  '.': notADynExpr,
96
96
  exists: notADynExpr,
97
97
  SELECT: notADynExpr,
98
- SET: (p, o) => notADynExpr(p, o, null, null, null, null, 'UNION'),
98
+ SET: (p, o, xpr, csnPath, parentParent, parentProp, ctx) => notADynExpr(p, o, null, null, null, null, Object.assign(ctx, { txt: 'UNION' })),
99
99
  like: notADynExpr,
100
100
  new: notADynExpr,
101
101
  };
102
102
 
103
103
  //----------------------------------
104
104
  // list is a $Collection => []
105
- transform.list = (parent, prop, xpr, csnPath, parentParent, parentProp) => {
105
+ transform.list = (parent, prop, xpr, csnPath, parentParent, parentProp, ctx) => {
106
106
  parentParent[parentProp] = xpr.filter(a => a);
107
- transformExpression(parentParent, parentProp, transform);
107
+ transformExpression(parentParent, parentProp, transform, csnPath, ctx);
108
108
  };
109
109
  // XPR
110
- transform.xpr = (parent, prop, xpr, csnPath, parentParent, parentProp) => {
110
+ transform.xpr = (parent, prop, xpr, csnPath, parentParent, parentProp, ctx) => {
111
111
  // eliminate 'xpr' node by pulling up xpr node to its parent
112
112
  parentParent[parentProp] = xpr;
113
- transformExpression(parentParent, parentProp, transform);
113
+ transformExpression(parentParent, parentProp, transform, csnPath, ctx);
114
114
  };
115
115
  //----------------------------------
116
116
  // CASE
117
- transform.case = (parent, prop, caseExpr) => {
117
+ transform.case = (parent, prop, caseExpr, csnPath, parentParent, parentProp, ctx) => {
118
118
  // transform simple case expression into search case expression
119
119
  // case <expr1> when <expr2> ... ===> case when <expr1> = <expr2> ...
120
120
  let i = 0;
@@ -141,14 +141,14 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
141
141
  curIf.$If.push(caseExpr[i]);
142
142
  parent.$If = edmIf.$If;
143
143
  delete parent.case;
144
- transformExpression(parent, undefined, transform);
144
+ transformExpression(parent, undefined, transform, csnPath, ctx);
145
145
  };
146
- transform.$If = (_parent, _prop, expr) => {
147
- transformExpression(expr, undefined, transform);
146
+ transform.$If = (_parent, _prop, expr, csnPath, parentParent, parentProp, ctx) => {
147
+ transformExpression(expr, undefined, transform, csnPath, ctx);
148
148
  };
149
149
  //----------------------------------
150
150
  // Cast => $Cast
151
- transform.cast = (parent, prop, castExpr, csnPath, parentParent, parentProp) => {
151
+ transform.cast = (parent, prop, castExpr, csnPath, parentParent, parentProp, ctx) => {
152
152
  const csnType = castExpr[0];
153
153
  // try to resolve to final scalar base type and use that instead of derived type
154
154
  if (!isBuiltinType(csnType.type)) {
@@ -183,7 +183,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
183
183
 
184
184
 
185
185
  parentParent[parentProp] = castFunc;
186
- transformExpression(parentParent, parentProp, transform);
186
+ transformExpression(parentParent, parentProp, transform, csnPath, ctx);
187
187
  };
188
188
  //----------------------------------
189
189
  const evalArgs = (argDef, args, propName) => {
@@ -215,11 +215,11 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
215
215
  };
216
216
 
217
217
  // Binary Operator Macro
218
- const op = (opStr, exact = 2) => (parent, prop, xpr) => {
218
+ const op = (opStr, exact = 2) => (parent, prop, xpr, csnPath, parentParent, parentProp, ctx) => {
219
219
  evalArgs({ exact }, xpr, prop);
220
220
  parent[opStr] = xpr;
221
221
  delete parent[prop];
222
- transformExpression(parent, undefined, transform);
222
+ transformExpression(parent, undefined, transform, csnPath, ctx);
223
223
  };
224
224
  //----------------------------------
225
225
  // LOGICAL
@@ -245,7 +245,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
245
245
  transform.$Lt = noOp;
246
246
  transform['<='] = op('$Le');
247
247
  transform.$Le = noOp;
248
- transform.in = (parent, prop, xpr) => {
248
+ transform.in = (parent, prop, xpr, csnPath, parentParent, parentProp, ctx) => {
249
249
  let args = xpr[1].list;
250
250
  if (!args) {
251
251
  if (Array.isArray(xpr[1].xpr))
@@ -256,12 +256,12 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
256
256
  evalArgs({ min: 1 }, args, prop);
257
257
  parent.$In = [ xpr[0], args ];
258
258
  delete parent[prop];
259
- transformExpression(parent, undefined, transform);
259
+ transformExpression(parent, undefined, transform, csnPath, ctx);
260
260
  };
261
261
  transform.$In = noOp;
262
- transform.between = (parent, prop, xpr, csnPath, parentParent, parentProp) => {
262
+ transform.between = (parent, prop, xpr, csnPath, parentParent, parentProp, ctx) => {
263
263
  evalArgs({ exact: 2 }, xpr.slice(1), prop);
264
- transformExpression(xpr, undefined, transform);
264
+ transformExpression(xpr, undefined, transform, csnPath, ctx);
265
265
  delete parent[prop];
266
266
  parentParent[parentProp]
267
267
  = {
@@ -271,28 +271,28 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
271
271
  ],
272
272
  };
273
273
  };
274
- transform['||'] = (parent, prop, xpr, csnPath, parentParent, parentProp) => {
274
+ transform['||'] = (parent, prop, xpr, csnPath, parentParent, parentProp, ctx) => {
275
275
  evalArgs({ exact: 2 }, xpr, prop);
276
- transformExpression(xpr, undefined, transform);
276
+ transformExpression(xpr, undefined, transform, csnPath, ctx);
277
277
  delete parent[prop];
278
278
  parentParent[parentProp].$Apply = xpr;
279
279
  parentParent[parentProp].$Function = 'odata.concat';
280
280
  };
281
281
  //----------------------------------
282
282
  // ARITHMETICAL AND UNARY
283
- transform['+'] = (parent, prop, xpr, csnPath, parentParent, parentProp) => {
283
+ transform['+'] = (parent, prop, xpr, csnPath, parentParent, parentProp, ctx) => {
284
284
  if (Array.isArray(xpr)) {
285
285
  op('$Add')(parent, prop, xpr);
286
286
  }
287
287
  else {
288
288
  delete parent[prop];
289
289
  parentParent[parentProp] = xpr;
290
- transformExpression(parentParent, parentProp, transform);
290
+ transformExpression(parentParent, parentProp, transform, csnPath, ctx);
291
291
  }
292
292
  };
293
293
  transform.$Add = noOp;
294
- transform['-'] = (parent, prop, xpr) => {
295
- op(Array.isArray(xpr) ? '$Sub' : '$Neg')(parent, prop, xpr);
294
+ transform['-'] = (parent, prop, xpr, csnPath, parentParent, parentProp, ctx) => {
295
+ op(Array.isArray(xpr) ? '$Sub' : '$Neg')(parent, prop, xpr, csnPath, parentParent, parentProp, ctx);
296
296
  };
297
297
  transform.$Sub = noOp;
298
298
  transform.$Neg = noOp;
@@ -311,12 +311,84 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
311
311
  else
312
312
  parentParent[parentProp] = xpr;
313
313
  };
314
- transform['#'] = (parent, prop, xpr, csnPath, parentParent, parentProp, txt) => {
315
- if (parent['#'] && parent.val) // enum reference that was resolved by the compiler
316
- delete parent['#'];
317
- else
318
- notADynExpr(parent, prop, xpr, csnPath, parentParent, parentProp, txt);
314
+ transform['#'] = (parent, prop, xpr, csnPath, parentParent, parentParentName, ctx) => {
315
+ // enum reference that was resolved by the compiler
316
+ if (!(parent['#'] && parent.val))
317
+ parent.EnumMember = getEnumMemberValue(xpr, ctx);
318
+ delete parent['#'];
319
319
  };
320
+ //----------------------------------
321
+ // Enum type resolving helper functions
322
+ function getEnumMemberValue( enumMember, ctx ) {
323
+ // inner annotations play a role here?
324
+
325
+ const annoNameParts = anno.slice(1).split('.');
326
+ // The term of an annotation is the part between the first '@' and the second '.'
327
+ const termName = `${ annoNameParts[0] }.${ annoNameParts[1] }`;
328
+ const term = genericTranslationHelpers.getDictTerm(termName, options);
329
+ // get term's type
330
+ let typeName = term?.Type;
331
+ let type = genericTranslationHelpers.getDictType(typeName);
332
+
333
+ ({ type, typeName } = resolveNestedTypePath(annoNameParts.slice(2), typeName, type));
334
+
335
+ if (type?.$kind === 'EnumType')
336
+ return valiteAndGetFullEnumMemberNameFromEnumType(enumMember, type, typeName);
337
+ if (type?.$kind === 'ComplexType') {
338
+ // If we end up with a ComplexType here, the enum member is defined in a property of the complex type, which is
339
+ // a collection. Only in that case the flattening of annotation stops in the core compiler.
340
+ // Here we need to continue resolving the substructure inside a collection item.
341
+ const indexOfAnnoInCsnPath = ctx.xprCsnPath.indexOf(anno);
342
+ // we take the part of the csnPath after the annotation and remove the numeric values, as these are the array indexes
343
+ const subStructAnnoNames = ctx.xprCsnPath.slice(indexOfAnnoInCsnPath + 1).filter(e => typeof e === 'string');
344
+ ({ type, typeName } = resolveNestedTypePath(subStructAnnoNames, typeName, type));
345
+ if (type?.$kind === 'EnumType')
346
+ return valiteAndGetFullEnumMemberNameFromEnumType(enumMember, type, typeName);
347
+ }
348
+
349
+ if (type && type.$kind !== 'EnumType') {
350
+ // resolved to not an EnumType -> warn about it and return what the input value
351
+ message('odata-anno-value', location, {
352
+ anno, value: `"#${ enumMember }"`, type: typeName, '#': 'std',
353
+ });
354
+ }
355
+ return enumMember;
356
+ }
357
+
358
+ function resolveNestedTypePath( annoNameParts, typeName, type ) {
359
+ for (let i = 0; i < annoNameParts.length; i++) {
360
+ if (type?.$kind === 'ComplexType') {
361
+ const properties = type.Properties;
362
+ const propName = Object.keys(properties).find(p => p === annoNameParts[i]);
363
+ if (propName) {
364
+ if (properties[propName].startsWith('Collection(')) {
365
+ // strip the 'Collection(...)' wrapper
366
+ typeName = properties[propName].slice(11, -1);
367
+ type = genericTranslationHelpers.getDictType(typeName);
368
+ }
369
+ else {
370
+ type = genericTranslationHelpers.getDictType(properties[propName]);
371
+ typeName = properties[propName];
372
+ }
373
+ }
374
+ }
375
+ }
376
+ return { type, typeName };
377
+ }
378
+
379
+ function valiteAndGetFullEnumMemberNameFromEnumType( member, enumType, typeName ) {
380
+ const members = enumType?.Members;
381
+ if (members && !members.includes(member)) {
382
+ message('odata-anno-value', location, {
383
+ anno, type: typeName, value: `"#${ member }"`, rawvalues: members.map(m => `#${ m }`), '#': 'enum',
384
+ });
385
+ return member;
386
+ }
387
+ return `${ typeName }/${ member }`;
388
+ }
389
+ //----------------------------------
390
+
391
+
320
392
  transform.ref = (parent, prop, xpr, csnPath, parentParent, parentProp) => {
321
393
  // until empty filter syntax is introduced for the annotation expressions,
322
394
  // we ignore the filters in order to generate EDMX
@@ -332,7 +404,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
332
404
  };
333
405
  //----------------------------------
334
406
  // Functions
335
- transform.func = (parent, prop, xpr, csnPath, parentParent, parentProp) => {
407
+ transform.func = (parent, prop, xpr, csnPath, parentParent, parentProp, ctx) => {
336
408
  const rewriteArgs = (argDefs, evalVal = true) => {
337
409
  Object.entries(argDefs).forEach(([ argName, argDef ]) => {
338
410
  const [ foundProps, newArgs ]
@@ -723,7 +795,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
723
795
  // $Record ???
724
796
  $Collection: () => {
725
797
  standard(parentParent, parentProp);
726
- transformExpression(parentParent, parentProp, transform);
798
+ transformExpression(parentParent, parentProp, transform, csnPath, ctx);
727
799
  },
728
800
  $Path: () => {
729
801
  oneArg(parent, xpr);
@@ -733,7 +805,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
733
805
  anno, op: `${ xpr }(…)`, meta: 'string', '#': 'wrongval_meta',
734
806
  });
735
807
  }
736
- transformExpression(parentParent, parentProp, transform);
808
+ transformExpression(parentParent, parentProp, transform, csnPath, ctx);
737
809
  },
738
810
  $Null: () => {
739
811
  parent[xpr] = true;
@@ -753,7 +825,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
753
825
  funcDef.forEach(f => f());
754
826
  else
755
827
  funcDef();
756
- transformExpression(parent, undefined, transform);
828
+ transformExpression(parent, undefined, transform, csnPath, ctx);
757
829
  }
758
830
  else {
759
831
  const funcName = xpr.startsWith('odata.') ? xpr : `odata.${ xpr }`;
@@ -770,7 +842,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
770
842
  parentParent[parentProp].$Function = funcName;
771
843
  delete parentParent[parentProp].func;
772
844
  delete parentParent[parentProp].args;
773
- transformExpression(parentParent, parentProp, transform);
845
+ transformExpression(parentParent, parentProp, transform, csnPath, ctx);
774
846
  }
775
847
  }
776
848
  else {
@@ -787,7 +859,9 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
787
859
  if (isAnnotationExpression(parent)) {
788
860
  delete parent['='];
789
861
  const edmJson = preTransformXpr(parent);
790
- parentParent[parentProp] = transformExpression({ $edmJson: edmJson }, undefined, transform);
862
+ // csnPath to this certain expression is passed inside of the 'ctx' variable, in case it is needed for resolving types from the
863
+ // dictionary, for instance in enum symbols resolving, see transform['#']
864
+ parentParent[parentProp] = transformExpression({ $edmJson: edmJson }, undefined, transform, csnPath, { xprCsnPath: csnPath });
791
865
  }
792
866
  },
793
867
  }, location);
@@ -468,7 +468,9 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
468
468
  }
469
469
  },
470
470
  }, ctx.csnPath || ctx.location);
471
- xpr2edmJson(carrier, knownAnno, ctx.location, options, messageFunctions);
471
+ xpr2edmJson(carrier, knownAnno, ctx.location, options, messageFunctions, {
472
+ oDataDictionary, getDictTerm, getDictType,
473
+ });
472
474
  }
473
475
  });
474
476
 
package/lib/main.d.ts CHANGED
@@ -1023,9 +1023,6 @@ declare namespace compiler {
1023
1023
  * - elements are flattened
1024
1024
  * - …
1025
1025
  *
1026
- * THIS IS HIGHLY EXPERIMENTAL
1027
- *
1028
- * Beta flag `effectiveCsn` is required.
1029
1026
  *
1030
1027
  * @internal
1031
1028
  */
package/lib/main.js CHANGED
@@ -188,9 +188,11 @@ module.exports = {
188
188
  $lsp: {
189
189
  parse: (...args) => compiler.parseX(...args),
190
190
  compile: (...args) => compiler.compileX(...args),
191
+ compileSync: (...args) => compiler.compileSyncX(...args),
191
192
  getArtifactName: art => base.getArtifactName(art),
192
193
  traverseSemanticTokens: (xsn, options) => lsp.traverseSemanticTokens(xsn, options),
193
194
  getSemanticTokenOrigin: obj => lsp.getSemanticTokenOrigin(obj),
195
+ xsnToCsn: ( xsn, options ) => toCsn.compactModel( xsn, options ),
194
196
  },
195
197
 
196
198
  // CSN Model related functionality
@@ -270,7 +270,7 @@ function applyTransformations( csn, customTransformers = {}, artifactTransformer
270
270
  * - the path to the property
271
271
  *
272
272
  * @param {object} parent The "parent" of which we transform a property of
273
- * @param {string} prop The property of parent to start at
273
+ * @param {string|number} prop The property of parent to start at
274
274
  * @param {object} customTransformers Map of prop to transform and function to apply
275
275
  * @param {applyTransformationsOptions} [options={}]
276
276
  * @param {CSN.Path} path Path pointing to parent
@@ -155,7 +155,6 @@ function getFKAccessFinalizer( csn, options, csnUtils, pathDelimiter, processOnI
155
155
  */
156
156
  function handleManagedAssocSteps( artifact, artifactName ) {
157
157
  const transformer = getTransformer();
158
- const inColumnsTransformer = getTransformer(true);
159
158
 
160
159
  if (options.transformation === 'effective')
161
160
  processRefsInAnnotations(artifact, transformer, [ 'definitions', artifactName ]);
@@ -185,14 +184,13 @@ function getFKAccessFinalizer( csn, options, csnUtils, pathDelimiter, processOnI
185
184
  if (processOnInQueries) {
186
185
  queryTransformers.columns = (parent, prop, columns, path) => {
187
186
  for (let i = 0; i < columns.length; i++) {
188
- const column = columns[i];
189
- if (column.ref) {
190
- inColumnsTransformer.ref(column, 'ref', column.ref, path.concat([ 'columns', i ]));
191
- column.ref.forEach((step, index) => {
192
- if (step.where)
193
- transform(step, 'where', step.where, path.concat([ 'columns', i, 'ref', index ]));
194
- });
195
- }
187
+ applyTransformationsOnNonDictionary(
188
+ columns,
189
+ i,
190
+ transformer,
191
+ { drillRef: true, skipStandard: { on: true } },
192
+ path.concat( [ 'columns' ] )
193
+ );
196
194
  }
197
195
  };
198
196
  queryTransformers.on = transform;
@@ -201,17 +199,27 @@ function getFKAccessFinalizer( csn, options, csnUtils, pathDelimiter, processOnI
201
199
  if (options.transformation === 'effective' || options.transformation === 'odata')
202
200
  queryTransformers.xpr = transform;
203
201
 
204
- applyTransformationsOnNonDictionary(artifact, artifact.query ? 'query' : 'projection', queryTransformers, { drillRef: true }, [ 'definitions', artifactName ]);
202
+ applyTransformationsOnNonDictionary(
203
+ artifact,
204
+ artifact.query ? 'query' : 'projection',
205
+ queryTransformers,
206
+ {
207
+ drillRef: true,
208
+ skipDict: {
209
+ mixin: !(options.transformation in { odata: 1, effective: 1 }),
210
+ },
211
+ },
212
+ [ 'definitions', artifactName ]
213
+ );
205
214
  }
206
215
 
207
216
  /**
208
217
  *
209
- * @param {boolean} isColumns Whether the transformation is taking place on a column
210
218
  * @returns {object}
211
219
  */
212
- function getTransformer( isColumns = false ) {
220
+ function getTransformer() {
213
221
  return {
214
- ref: (refOwner, prop, ref, path) => {
222
+ ref: (refOwner, prop, ref, path, grandParent) => {
215
223
  // [<assoc base>.]<managed assoc>.<field>
216
224
  if (ref.length > 1) {
217
225
  const { links } = inspectRef(path);
@@ -232,10 +240,11 @@ function getFKAccessFinalizer( csn, options, csnUtils, pathDelimiter, processOnI
232
240
  fkAlias = fks[0].as || fks[0].ref[fks[0].ref.length - 1];
233
241
  const managedAssocStepName = ref[i];
234
242
  const newFkName = `${ managedAssocStepName }${ pathDelimiter }${ fkAlias }`;
235
- if (isColumns) {
243
+ // only set alias for top level column refs
244
+ if (Array.isArray(grandParent) && path.at(-2) === 'columns') {
236
245
  refOwner.ref = [ ...ref.slice(0, i), newFkName ];
237
246
  if (!refOwner.as)
238
- refOwner.as = implicitAs(ref);
247
+ refOwner.as = ref.at(-1).id || ref.at(-1);
239
248
  }
240
249
  else {
241
250
  refOwner.ref = [ ...ref.slice(0, i), newFkName ];
@@ -226,7 +226,7 @@ function getStructStepsFlattener( csn, options, messageFunctions, resolved, path
226
226
  // Explicitly set implicit alias for things that are now flattened - but only in columns
227
227
  // TODO: Can this be done elegantly during expand phase already?
228
228
  if (parent.$implicitAlias) { // an expanded s -> s.a is marked with this - do not add implicit alias "a" there, we want s_a
229
- if (parent.ref.at(-1) === parent.as) // for a simple s that was expanded - for s.substructure this would not apply
229
+ if (parent.ref.at(-1) === parent.as && !parent.ref.at(-1).includes('.') ) // for a simple s that was expanded - for s.substructure this would not apply
230
230
  delete parent.as;
231
231
  delete parent.$implicitAlias;
232
232
  }
@@ -70,7 +70,6 @@ function getViewTransformer( csn, options, messageFunctions ) {
70
70
  get$combined, isAssocOrComposition,
71
71
  inspectRef, queryOrMain, // csnRefs
72
72
  } = csnUtils;
73
- const pathDelimiter = options.forHana && (options.sqlMapping === 'hdbcds') ? '.' : '_';
74
73
  const { error, info } = messageFunctions;
75
74
  const doA2J = !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds');
76
75
 
@@ -158,39 +157,6 @@ function getViewTransformer( csn, options, messageFunctions ) {
158
157
  columnMap[elemName] = { ref: [ elemName ] };
159
158
  }
160
159
 
161
- /**
162
- * So far, we only added foreign keys to elements - we also need to create corresponding columns
163
- * and respect aliasing etc.
164
- *
165
- * @todo Maybe this can be done earlier, during flattening/expansion already?
166
- * @param {object} columnMap
167
- * @param {CSN.Element} elem
168
- * @param {string} elemName
169
- */
170
- function addForeignKeysToColumns( columnMap, elem, elemName ) {
171
- const assocCol = columnMap[elemName];
172
- if (assocCol && assocCol.ref) {
173
- elem.keys.forEach((foreignKey) => {
174
- const ref = cloneCsnNonDict(assocCol.ref, options);
175
- ref[ref.length - 1] = [ getLastRefStepString(ref) ].concat(foreignKey.as).join(pathDelimiter);
176
- const result = {
177
- ref,
178
- };
179
- if (assocCol.as) {
180
- const columnName = `${ assocCol.as }${ pathDelimiter }${ foreignKey.as }`;
181
- result.as = columnName;
182
- }
183
-
184
- if (assocCol.key)
185
- result.key = true;
186
-
187
- const colName = result.as || getLastRefStepString(ref);
188
- columnMap[colName] = result;
189
- });
190
- }
191
- }
192
-
193
-
194
160
  /**
195
161
  * Check for invalid association publishing (in Union or in Subquery) (for hdbcds) and
196
162
  * create the __clone for publishing stuff.
@@ -354,11 +320,6 @@ function getViewTransformer( csn, options, messageFunctions ) {
354
320
  if (isSelect) {
355
321
  if (!columnMap[elemName])
356
322
  addProjectionOrStarElement(query, isProjection, isSelectStar, $combined, columnMap, elemName);
357
-
358
- // For associations - make sure that the foreign keys have the same "style"
359
- // If A.assoc => A.assoc_id, else if assoc => assoc_id or assoc as Assoc => Assoc_id
360
- if (elem.keys && doA2J)
361
- addForeignKeysToColumns(columnMap, elem, elemName);
362
323
  }
363
324
  // Views must have at least one element that is not an unmanaged assoc
364
325
  if (!elem.on && !elem.$ignore)
@@ -3,11 +3,10 @@
3
3
  const { setProp } = require('../../base/model');
4
4
  const flattening = require('../db/flattening');
5
5
  const {
6
- applyTransformations, forEachDefinition, forEachMemberRecursively, implicitAs, forEachMember, applyTransformationsOnNonDictionary,
6
+ applyTransformations, forEachDefinition, forEachMemberRecursively, forEachMember, applyTransformationsOnNonDictionary,
7
7
  } = require('../../model/csnUtils');
8
8
  const associations = require('../db/associations');
9
9
  const backlinks = require('../db/backlinks');
10
- const { cloneCsnNonDict } = require('../../model/cloneCsn');
11
10
 
12
11
 
13
12
  /**
@@ -17,13 +16,14 @@ const { cloneCsnNonDict } = require('../../model/cloneCsn');
17
16
  * - adding a corresponding on-condition
18
17
  * @param {CSN.Model} csn Input CSN - will not be transformed
19
18
  * @param {CSN.Options} options
20
- * @param {object} csnUtils
19
+ * @param {object} transformerUtils
21
20
  * @param {object} messageFunctions Message functions such as `error()`, `info()`, …
22
21
  * @todo Remove .keys afterwards
23
22
  * @todo Add created foreign keys into .columns in case of a query?
24
23
  * @returns {CSN.Model}
25
24
  */
26
- function turnAssociationsIntoUnmanaged( csn, options, csnUtils, messageFunctions ) {
25
+ function turnAssociationsIntoUnmanaged( csn, options, transformerUtils, messageFunctions ) {
26
+ const { csnUtils, expandManagedToFksInArray } = transformerUtils;
27
27
  // TODO: Do we really need this?
28
28
  forEachDefinition(csn, (artifact, artifactName) => {
29
29
  setProp(artifact, '$path', [ 'definitions', artifactName ]);
@@ -62,56 +62,6 @@ function turnAssociationsIntoUnmanaged( csn, options, csnUtils, messageFunctions
62
62
  associations.attachOnConditions(csn, csnUtils, '_', { allowArtifact: () => true }, options);
63
63
 
64
64
  return csn;
65
-
66
- /**
67
- * Expand managed associations in an array and insert them in-place
68
- *
69
- * If requested, leave out the assocs themselves
70
- *
71
- * @param {boolean} [killAssoc=false]
72
- * @returns {Function} applyTransformationsCallback
73
- */
74
- function expandManagedToFksInArray( killAssoc = false ) {
75
- return function expand(parent, prop, array, path) {
76
- const newColumns = [];
77
- for (let i = 0; i < array.length; i++) {
78
- const col = array[i];
79
- const element = csnUtils.getElement(col) || col.ref && csnUtils.inspectRef(path.concat(prop, i)).art;
80
- if (!killAssoc || !element?.keys)
81
- newColumns.push(col);
82
- if (element?.keys)
83
- element.keys.forEach(fk => addForeignKeyToColumns(fk, newColumns, col, options));
84
- }
85
- parent[prop] = newColumns;
86
- };
87
- }
88
- }
89
-
90
-
91
- /**
92
- * FKs need to be added to the .columns
93
- * @todo stolen from lib/transform/db/views.js
94
- * @todo Can we maybe do this during expansion already?
95
- * @param {object} foreignKey
96
- * @param {object[]} columns
97
- * @param {CSN.Column} associationColumn
98
- * @param {CSN.Options} options
99
- */
100
- function addForeignKeyToColumns( foreignKey, columns, associationColumn, options ) {
101
- const ref = cloneCsnNonDict(associationColumn.ref, options);
102
- ref[ref.length - 1] = [ implicitAs(ref) ].concat(foreignKey.as || foreignKey.ref).join('_');
103
- const result = {
104
- ref,
105
- };
106
- if (associationColumn.as) {
107
- const columnName = `${ associationColumn.as }_${ foreignKey.as || implicitAs(foreignKey.ref) }`;
108
- result.as = columnName;
109
- }
110
-
111
- if (associationColumn.key)
112
- result.key = true;
113
-
114
- columns.push(result);
115
65
  }
116
66
 
117
67
  /**
@@ -121,8 +71,8 @@ function addForeignKeyToColumns( foreignKey, columns, associationColumn, options
121
71
  * @param {object} csnUtils
122
72
  * @param {object} messageFunctions
123
73
  */
124
- function transformBacklinks( csn, options, csnUtils, messageFunctions ) {
125
- forEachDefinition(csn, backlinks.getBacklinkTransformer(csnUtils, messageFunctions, options, '_', true));
74
+ function transformBacklinks( csn, options, { csnUtils }, messageFunctions ) {
75
+ forEachDefinition(csn, backlinks.getBacklinkTransformer(csnUtils, messageFunctions, options, '_', true), { skip: [ 'aspect' ] });
126
76
  }
127
77
 
128
78
  module.exports = {
@@ -90,8 +90,10 @@ function effectiveCsn( model, options, messageFunctions ) {
90
90
  resolveTypesInActionsAfterFlattening(csnUtils);
91
91
 
92
92
  processCalculatedElementsInEntities(csn, options);
93
- associations.managedToUnmanaged(csn, options, csnUtils, messageFunctions);
94
- associations.transformBacklinks(csn, options, csnUtils, messageFunctions);
93
+ // invalidate csnRef caches after flattening
94
+ transformerUtils.csnUtils = getUtils(csn, 'init-all');
95
+ associations.managedToUnmanaged(csn, options, transformerUtils, messageFunctions);
96
+ associations.transformBacklinks(csn, options, transformerUtils, messageFunctions);
95
97
  const transformers = mergeTransformers([
96
98
  options.remapOdataAnnotations ? annotations.remapODataAnnotations(csn) : {},
97
99
  misc.removeDefinitionsAndProperties(csn, options),
@@ -87,10 +87,10 @@ function _removeDefinitionsAndProperties( csn, options ) {
87
87
  // Set when we remove .key from temporal things, used in localized.js
88
88
  $key: killProp,
89
89
  includes: killProp,
90
- enum: killProp,
91
90
  keys: killProp,
92
91
  excluding: killProp, // * is resolved, so has no effect anymore
93
92
  targetAspect: killProp,
93
+ $calc: killProp,
94
94
  };
95
95
 
96
96
  if (!options.keepLocalized)