@sap/cds-compiler 6.5.0 → 6.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 (38) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/bin/cdsc.js +1 -1
  3. package/lib/api/main.js +0 -3
  4. package/lib/base/builtins.js +2 -1
  5. package/lib/base/message-registry.js +13 -14
  6. package/lib/base/model.js +0 -1
  7. package/lib/checks/sql-snippets.js +8 -0
  8. package/lib/checks/validator.js +4 -2
  9. package/lib/compiler/define.js +102 -115
  10. package/lib/compiler/extend.js +67 -37
  11. package/lib/compiler/finalize-parse-cdl.js +1 -1
  12. package/lib/compiler/generate.js +34 -11
  13. package/lib/compiler/index.js +2 -2
  14. package/lib/compiler/kick-start.js +26 -35
  15. package/lib/compiler/populate.js +4 -7
  16. package/lib/compiler/propagator.js +20 -1
  17. package/lib/compiler/resolve.js +26 -1
  18. package/lib/compiler/shared.js +49 -9
  19. package/lib/compiler/utils.js +2 -2
  20. package/lib/edm/annotations/edmJson.js +111 -37
  21. package/lib/edm/annotations/genericTranslation.js +3 -1
  22. package/lib/main.d.ts +0 -3
  23. package/lib/main.js +2 -0
  24. package/lib/model/csnRefs.js +6 -1
  25. package/lib/render/toSql.js +0 -4
  26. package/lib/transform/db/applyTransformations.js +1 -1
  27. package/lib/transform/db/associations.js +24 -15
  28. package/lib/transform/db/flattening.js +1 -1
  29. package/lib/transform/db/views.js +0 -39
  30. package/lib/transform/effective/associations.js +5 -55
  31. package/lib/transform/effective/main.js +4 -2
  32. package/lib/transform/effective/misc.js +1 -1
  33. package/lib/transform/effective/types.js +36 -12
  34. package/lib/transform/forOdata.js +126 -3
  35. package/lib/transform/forRelationalDB.js +13 -4
  36. package/lib/transform/transformUtils.js +51 -1
  37. package/lib/transform/translateAssocsToJoins.js +43 -19
  38. package/package.json +1 -1
@@ -846,6 +846,8 @@ function fns( model ) {
846
846
 
847
847
  if (!semantics.dollar) {
848
848
  valid.push( dynamicDict );
849
+ if (isMainRef) // eslint-disable-next-line no-return-assign
850
+ valid.forEach( ( d, idx ) => (valid[idx] = removeGapArtifact( d )) );
849
851
  }
850
852
  else {
851
853
  const filterFn = semantics.variableFilter || removeRestrictedVariables;
@@ -908,14 +910,22 @@ function fns( model ) {
908
910
  }
909
911
  // need to do that here, because we also need to disallow Service.AutoExposed:elem
910
912
  // TODO: but Service.AutoExposed.NotAuto should be fine
911
- if (isMainRef !== 'all' && artItemsCount === 0 &&
912
- art.$inferred === 'autoexposed' && !user.$inferred) {
913
- // Depending on the processing sequence, the following could be a
914
- // simple 'ref-undefined-art'/'ref-undefined-def' - TODO: which we
915
- // could "change" to this message at the end of compile():
916
- error( 'ref-unexpected-autoexposed', [ item.location, user ], { art },
917
- 'An auto-exposed entity can\'t be referred to - expose entity $(ART) explicitly' );
918
- return null; // continuation semantics: like “not found”
913
+ if (isMainRef && isMainRef !== 'all' && artItemsCount === 0) {
914
+ if (art.kind === 'namespace') {
915
+ if (env !== false) {
916
+ semantics.notFound( user, item, [ removeGapArtifact( env ) ],
917
+ null, prev, path, semantics );
918
+ }
919
+ return null;
920
+ }
921
+ else if (art.$inferred === 'autoexposed' && !user.$inferred) {
922
+ // Depending on the processing sequence, the following could be a
923
+ // simple 'ref-undefined-art'/'ref-undefined-def' - TODO: which we
924
+ // could "change" to this message at the end of compile():
925
+ error( 'ref-unexpected-autoexposed', [ item.location, user ], { art },
926
+ 'An auto-exposed entity can\'t be referred to - expose entity $(ART) explicitly' );
927
+ return null; // continuation semantics: like “not found”
928
+ }
919
929
  }
920
930
  }
921
931
  return art;
@@ -978,7 +988,25 @@ function fns( model ) {
978
988
  return def;
979
989
  if (def.$duplicates)
980
990
  return false;
981
- return setArtifactLink( head, def ); // we do not want to see the using
991
+ art = setArtifactLink( head, def ); // we do not want to see the using
992
+ if (art.kind !== 'namespace')
993
+ return art;
994
+ }
995
+ /* FALLTHROUGH */
996
+ case 'namespace': {
997
+ if (semantics.isMainRef === 'all' || path.length !== 1 && ref.scope !== 1)
998
+ return art;
999
+ const valid = [];
1000
+ const lexical = userBlock( user );
1001
+ if (lexical) {
1002
+ for (let env = lexical; env; env = env._block)
1003
+ valid.push( removeGapArtifact( env.artifacts || Object.create( null ) ) );
1004
+ }
1005
+ valid.push( removeGapArtifact( model.definitions ) );
1006
+ semantics.notFound?.( user._user || user, head, valid, model.definitions,
1007
+ null, path, semantics );
1008
+
1009
+ return null;
982
1010
  }
983
1011
  case 'mixin': {
984
1012
  // use a source element having that name if in `extend … with columns`:
@@ -2153,6 +2181,18 @@ function removeDollarNames( dict ) {
2153
2181
  return r;
2154
2182
  }
2155
2183
 
2184
+ function removeGapArtifact( dict ) {
2185
+ const r = Object.create( null );
2186
+ for (const name in dict) {
2187
+ const art = dict[name];
2188
+ // TODO: for gaps with sub artifacts, we use use `${name}.` as name
2189
+ // TODO: clarify with LSP
2190
+ if (art.kind !== 'namespace' || art._subArtifacts)
2191
+ r[name] = dict[name];
2192
+ }
2193
+ return r;
2194
+ }
2195
+
2156
2196
  module.exports = {
2157
2197
  fns,
2158
2198
  };
@@ -206,6 +206,7 @@ function initExprAnnoBlock( art, block ) {
206
206
 
207
207
  function initDollarSelf( art ) {
208
208
  // TODO: use setMemberParent() ?
209
+ // TODO: also on 'namespace's? (test with annotation with checked ref on them)
209
210
  const self = {
210
211
  name: { id: '$self', location: art.location },
211
212
  kind: '$self',
@@ -285,10 +286,9 @@ function dependsOnSilent( user, art ) {
285
286
  user._deps.push( { art } );
286
287
  }
287
288
 
288
- function storeExtension( elem, name, prop, parent, block ) {
289
+ function storeExtension( elem, name, prop, parent ) {
289
290
  if (prop === 'enum')
290
291
  prop = 'elements';
291
- setLink( elem, '_block', block );
292
292
  const kind = `_${ elem.kind }`; // _extend or _annotate
293
293
  if (!parent[kind])
294
294
  setLink( parent, kind, {} );
@@ -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
@@ -245,9 +245,10 @@ const referenceSemantics = {
245
245
  inline: { lexical: justDollar, dynamic: 'inline' }, // ...using baseEnv
246
246
  ref_where: { lexical: justDollar, dynamic: 'ref-target' }, // ...using baseEnv
247
247
  on: { lexical: justDollar, dynamic: 'query' }, // assoc defs, redirected to
248
+ // there are also 'on_join' and 'on_mixin' with default semantics
249
+ $calc: { lexical: justDollar, dynamic: 'query' }, // calculation for draft
248
250
  annotation: { lexical: justDollar, dynamic: 'query' }, // anno top-level `ref`
249
251
  annotationExpr: { lexical: justDollar, dynamic: 'query' }, // annotation assignment
250
- // there are also 'on_join' and 'on_mixin' with default semantics
251
252
  orderBy_ref: { lexical: query => query, dynamic: 'query' },
252
253
  orderBy_expr: { lexical: query => query, dynamic: 'source' }, // ref in ORDER BY expression
253
254
  orderBy_set_ref: { lexical: query => query.$next, dynamic: 'query' }, // to outer SELECT (from UNION)
@@ -1075,6 +1076,7 @@ function analyseCsnPath( csnPath, csn, resolve ) {
1075
1076
  if (refCtx === 'annotation' && typeof obj === 'object') {
1076
1077
  // we do not know yet whether the annotation value is an expression or not →
1077
1078
  // loop over outer array and records (structure values):
1079
+ // TODO: add test → `@(A: [{target: ($user)}])` on query column
1078
1080
  if (Array.isArray( obj ) || !isAnnotationExpression( obj )) {
1079
1081
  obj = obj[prop];
1080
1082
  continue;
@@ -1164,6 +1166,9 @@ function analyseCsnPath( csnPath, csn, resolve ) {
1164
1166
  else if (prop[0] === '@') {
1165
1167
  refCtx = 'annotation';
1166
1168
  }
1169
+ else if (prop === '$calc') {
1170
+ refCtx = '$calc';
1171
+ }
1167
1172
  else if (prop !== 'xpr' && prop !== 'list') {
1168
1173
  // 'xpr' and 'list' do not change the ref context, all other props do:
1169
1174
  refCtx = prop;
@@ -1294,11 +1294,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
1294
1294
 
1295
1295
  // Set operation may also have an ORDER BY and LIMIT/OFFSET (in contrast to the ones belonging to
1296
1296
  // each SELECT)
1297
- // If the whole SET has an ORDER BY/LIMIT, wrap the part before that in parentheses
1298
- // (otherwise some SQL implementations (e.g. sqlite) would interpret the ORDER BY/LIMIT as belonging
1299
- // to the last SET argument, not to the whole SET)
1300
1297
  if (query.SET.orderBy || query.SET.limit) {
1301
- result = `(${ result })`;
1302
1298
  if (query.SET.orderBy) {
1303
1299
  const orderBy = query.SET.orderBy.map(entry => renderOrderByEntry(entry, env.withSubPath([ 'orderBy' ]))).join(', ');
1304
1300
  result += `\n${ env.indent }ORDER BY ${ orderBy }`;
@@ -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)