@sap/cds-compiler 6.3.4 → 6.4.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.
Files changed (55) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/LICENSE +32 -0
  3. package/README.md +14 -2
  4. package/bin/cdsse.js +0 -3
  5. package/doc/CHANGELOG_BETA.md +1 -1
  6. package/doc/CHANGELOG_DEPRECATED.md +1 -1
  7. package/lib/base/message-registry.js +7 -0
  8. package/lib/base/messages.js +1 -1
  9. package/lib/base/model.js +2 -0
  10. package/lib/compiler/assert-consistency.js +1 -0
  11. package/lib/compiler/checks.js +37 -26
  12. package/lib/compiler/define.js +1 -1
  13. package/lib/compiler/extend.js +39 -50
  14. package/lib/compiler/finalize-parse-cdl.js +1 -1
  15. package/lib/compiler/lsp-api.js +1 -1
  16. package/lib/compiler/populate.js +2 -2
  17. package/lib/compiler/propagator.js +29 -6
  18. package/lib/compiler/resolve.js +13 -3
  19. package/lib/compiler/shared.js +31 -25
  20. package/lib/compiler/tweak-assocs.js +86 -28
  21. package/lib/compiler/xpr-rewrite.js +70 -38
  22. package/lib/edm/annotations/edmJson.js +206 -37
  23. package/lib/edm/csn2edm.js +13 -0
  24. package/lib/edm/edmUtils.js +2 -2
  25. package/lib/gen/BaseParser.js +106 -72
  26. package/lib/gen/CdlGrammar.checksum +1 -1
  27. package/lib/gen/CdlParser.js +1500 -1509
  28. package/lib/json/to-csn.js +8 -5
  29. package/lib/language/genericAntlrParser.js +0 -0
  30. package/lib/main.js +19 -16
  31. package/lib/model/csnRefs.js +589 -521
  32. package/lib/model/csnUtils.js +26 -7
  33. package/lib/model/enrichCsn.js +1 -0
  34. package/lib/parsers/AstBuildingParser.js +72 -27
  35. package/lib/render/toCdl.js +2 -1
  36. package/lib/render/toHdbcds.js +6 -3
  37. package/lib/render/toSql.js +5 -0
  38. package/lib/transform/db/applyTransformations.js +1 -1
  39. package/lib/transform/db/assertUnique.js +4 -1
  40. package/lib/transform/db/cdsPersistence.js +17 -18
  41. package/lib/transform/db/expansion.js +179 -3
  42. package/lib/transform/db/flattening.js +16 -5
  43. package/lib/transform/db/rewriteCalculatedElements.js +79 -283
  44. package/lib/transform/effective/main.js +8 -1
  45. package/lib/transform/forOdata.js +1 -1
  46. package/lib/transform/forRelationalDB.js +21 -80
  47. package/lib/transform/localized.js +65 -110
  48. package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +89 -63
  49. package/lib/transform/transformUtils.js +23 -21
  50. package/lib/transform/translateAssocsToJoins.js +7 -5
  51. package/lib/transform/tupleExpansion.js +16 -3
  52. package/package.json +1 -1
  53. package/doc/DeprecatedOptions_v2.md +0 -150
  54. package/doc/NameResolution.md +0 -837
  55. package/lib/transform/parseExpr.js +0 -415
@@ -181,6 +181,7 @@ function xprRewriteFns( model ) {
181
181
 
182
182
  return {
183
183
  rewriteAnnotationsRefs,
184
+ rewriteRefsInExpression,
184
185
  };
185
186
 
186
187
  /**
@@ -189,14 +190,15 @@ function xprRewriteFns( model ) {
189
190
  * @param {string} [variant]
190
191
  */
191
192
  function reportAnnoRewriteError( expr, config, variant = 'std' ) {
192
- return error('anno-missing-rewrite', [
193
- weakLocation( config.target.location ), config.target,
194
- ], {
195
- '#': variant,
196
- anno: config.anno,
197
- art: config.origin,
198
- elemref: expr,
199
- });
193
+ return !config.tokenExpr || // TODO: some info message ?
194
+ error( 'anno-missing-rewrite', [
195
+ weakLocation( config.target.location ), config.target,
196
+ ], {
197
+ '#': variant,
198
+ anno: config.anno,
199
+ art: config.origin,
200
+ elemref: expr,
201
+ });
200
202
  }
201
203
 
202
204
  /**
@@ -217,21 +219,33 @@ function xprRewriteFns( model ) {
217
219
  if (!anno.kind || anno.$invalidPaths)
218
220
  return;
219
221
 
220
- // Annotation comes from the target's type. That's important to know, because
222
+ const hasError = rewriteRefsInExpression( target, origin, annoName );
223
+ target.$contains ??= {};
224
+ target.$contains.$annotation ??= {};
225
+ target.$contains.$annotation.$path ||= origin.$contains.$annotation.$path;
226
+ target.$contains.$annotation.$self ||= origin.$contains.$annotation.$self;
227
+ if (hasError)
228
+ anno.$invalidPaths = true; // avoid subsequent errors
229
+ }
230
+
231
+ function rewriteRefsInExpression( destination, origin, propName ) {
232
+ // Annotation comes from the destination's type. That's important to know, because
221
233
  // path prefixes need to be adapted.
222
- const fromTargetType = target.type?._artifact === origin;
223
- // Annotation comes from the target's calculated element. A special case propagation rule, e.g
234
+ const fromTargetType = destination.type?._artifact === origin;
235
+ // Annotation comes from the destination's calculated element.
236
+ // A special case propagation rule, e.g
224
237
  // for `calcString: String = str;`. We also need to adapt path prefixes.
225
- const fromCalcElement = !fromTargetType && target.$calcDepElement &&
226
- target.value?._artifact === origin;
238
+ const fromCalcElement = !fromTargetType && destination.$calcDepElement &&
239
+ destination.value?._artifact === origin;
227
240
 
228
- const { expandedRoot, expandedRootType } = !fromTargetType && getExpandRoot( target ) || {};
241
+ const { expandedRoot, expandedRootType }
242
+ = !fromTargetType && getExpandRoot( destination ) || {};
229
243
 
230
244
  const config = {
231
245
  __proto__: AnnoRewriteConfig.prototype,
232
- anno: annoName,
233
- target,
234
- targetRoot: annoRootArt( target ),
246
+ anno: propName,
247
+ target: destination,
248
+ targetRoot: annoRootArt( destination ),
235
249
  origin,
236
250
  fromTargetType,
237
251
  fromCalcElement,
@@ -239,13 +253,12 @@ function xprRewriteFns( model ) {
239
253
  expandedRootType,
240
254
  };
241
255
 
242
- const hasError = rewriteAnnotationExpr( target[annoName], config );
243
- target.$contains ??= {};
244
- target.$contains.$annotation ??= {};
245
- target.$contains.$annotation.$path ||= origin.$contains.$annotation.$path;
246
- target.$contains.$annotation.$self ||= origin.$contains.$annotation.$self;
247
- if (hasError)
248
- anno.$invalidPaths = true; // avoid subsequent errors
256
+ if (propName.charAt( 0 ) === '@')
257
+ return rewriteAnnotationExpr( destination[propName], config );
258
+ return traverseExpr.STOP === traverseExpr(
259
+ destination[propName], 'annoRewrite', destination,
260
+ // eslint-disable-next-line @stylistic/max-len, @stylistic/function-paren-newline
261
+ (e, refCtx) => (rewriteAnnoExpr( e, config, refCtx ) ? traverseExpr.STOP : traverseExpr.SKIP) );
249
262
  }
250
263
 
251
264
  /**
@@ -291,9 +304,11 @@ function xprRewriteFns( model ) {
291
304
  return reportAnnoRewriteError( expr, config, 'unsupported' );
292
305
 
293
306
  if (root.kind === 'key') {
294
- // Foreign keys can't be renamed and since we don't have absolute references to foreign keys,
295
- // i.e. `$self.assoc.target_id` always refers to the target side, we don't have to rewrite
296
- // them.
307
+ // References starting with a foreign key name (only possible in an
308
+ // annotation on a foreign key) have length 1 + the foreign key cannot be
309
+ // renamed → nothing to do. Otherwise, foreign keys cannot be directly
310
+ // addressed, except as `annotate` target. (Even as annotation value for
311
+ // foreign keys, `$self.assoc.target_id` always refers to the target side.)
297
312
  return null;
298
313
  }
299
314
 
@@ -341,8 +356,10 @@ function xprRewriteFns( model ) {
341
356
  // only understand the string representation. But we only do so for simple strings, which
342
357
  // is the case if this path expression has $tokenTexts. It then is of the form `@a: (path)`.
343
358
  const isSimpleStep = step => !step.where && !step.args && isSimpleCdlIdentifier( step.id );
344
- if (expr.path.every(isSimpleStep))
345
- expr.$tokenTexts = expr.path.map( step => step.id ).join('.');
359
+ if (expr.path.every(isSimpleStep)) {
360
+ expr.$tokenTexts = (expr.scope === 'param' ? ':' : '') +
361
+ expr.path.map( step => step.id ).join('.');
362
+ }
346
363
  }
347
364
 
348
365
  if (model.options.testMode) {
@@ -454,8 +471,10 @@ function xprRewriteFns( model ) {
454
471
 
455
472
  for (let i = startIndex; i < expr.path.length; ++i) {
456
473
  if (i > startIndex && art.target) {
457
- // if the current artifact is an association, we need to respect the redirection
458
- // chain from original target to new one.
474
+ // If the current artifact is an association, we need to respect the redirection
475
+ // chain from original target to new one. We need to use '_originalArtifact' due
476
+ // to secondary associations and their redirection chains. See comment in
477
+ // test3/Redirections/SecondaryAssocs/RedirectedPathRewriteOne.cds
459
478
  // FIXME: Won't work with associations in projected structures.
460
479
  const origTarget = expr.path[i - 1]?._originalArtifact?.target?._artifact;
461
480
  const chain = cachedRedirectionChain( art, origTarget );
@@ -489,9 +508,10 @@ function xprRewriteFns( model ) {
489
508
  }
490
509
 
491
510
  /**
492
- * Rewrite an expression that came via type propagation.
511
+ * Rewrite the initial part of a path in an expression that came via type
512
+ * propagation or via propagation with ref-only calculated element.
493
513
  *
494
- * @returns {boolean} Returns the expression if it couldn't be rewritten.
514
+ * @returns {boolean} Returns true if it couldn't be rewritten.
495
515
  */
496
516
  function adaptPathPrefixViaType( expr, config ) {
497
517
  const { target, origin } = config;
@@ -502,7 +522,8 @@ function xprRewriteFns( model ) {
502
522
  return true;
503
523
 
504
524
  // $self-paths via types from/to non-main artifacts always need to be rewritten.
505
- config.tokenExpr.$tokenTexts = true;
525
+ if (config.tokenExpr)
526
+ config.tokenExpr.$tokenTexts = true;
506
527
 
507
528
  const wasAbsolute = isAnnoPathAbsolute( expr );
508
529
  stripAbsolutePathPrefix( expr, origin );
@@ -526,6 +547,15 @@ function xprRewriteFns( model ) {
526
547
  return false;
527
548
  }
528
549
 
550
+ /**
551
+ * Rewrite the initial part of a path in an expression starting with $self on an
552
+ * expanded element:
553
+ *
554
+ * - nothing to do when not starting with $self (TODO: param?)
555
+ * - otherwise error if options.beta.rewriteAnnotationExpressionsViaType is not set
556
+ *
557
+ * @returns {boolean} Returns true if it couldn't be rewritten.
558
+ */
529
559
  function adaptPathPrefixViaTypeExpansion( expr, config ) {
530
560
  const root = expr.path[0]?._navigation;
531
561
  if (root?.kind !== '$self') {
@@ -548,7 +578,8 @@ function xprRewriteFns( model ) {
548
578
 
549
579
  config.target[config.anno].$inferred = 'anno-rewrite';
550
580
  // $self-paths via type expansion always need to be rewritten.
551
- config.tokenExpr.$tokenTexts = true;
581
+ if (config.tokenExpr)
582
+ config.tokenExpr.$tokenTexts = true;
552
583
 
553
584
  return false;
554
585
  }
@@ -586,7 +617,7 @@ function xprRewriteFns( model ) {
586
617
  if (relativeRoot >= 1)
587
618
  expr.path = expr.path.slice(relativeRoot);
588
619
  else if (relativeRoot === -1)
589
- throw new CompilerAssertion('Error while rewriting annotation');
620
+ throw new CompilerAssertion( 'Error while rewriting annotation' );
590
621
  }
591
622
 
592
623
  /**
@@ -632,7 +663,7 @@ function xprRewriteFns( model ) {
632
663
  * @returns {number}
633
664
  */
634
665
  function findRelativeRoot( expr, origin ) {
635
- if (!origin._main) // main artifacts can't have outer references
666
+ if (!origin._main) // main artifacts can't have outer references: $self → 0, -1 otherwise
636
667
  return expr.path[0]?._artifact === origin ? 0 : -1;
637
668
 
638
669
  const { path } = expr;
@@ -703,7 +734,8 @@ function xprRewriteFns( model ) {
703
734
  const item = expr.path[index];
704
735
  if (item.id !== found.name.id || (rewriteTarget[1] - index) !== 0) {
705
736
  // Path was rewritten; original token text string is no longer accurate
706
- config.tokenExpr.$tokenTexts = true;
737
+ if (config.tokenExpr)
738
+ config.tokenExpr.$tokenTexts = true;
707
739
  item.id = found.name.id;
708
740
  }
709
741
 
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
3
  const edmUtils = require('../edmUtils.js');
4
- const { parseExpr } = require('../../transform/parseExpr');
5
4
  const {
6
5
  EdmTypeFacetMap,
7
6
  EdmTypeFacetNames,
@@ -9,6 +8,7 @@ const {
9
8
  } = require('../EdmPrimitiveTypeDefinitions.js');
10
9
  const { isBuiltinType, isAnnotationExpression } = require('../../base/builtins');
11
10
  const { transformExpression } = require('../../transform/db/applyTransformations.js');
11
+ const { conditionAsTree, expressionAsTree } = require('../../model/xprAsTree');
12
12
 
13
13
  /**
14
14
  * Translate a given token stream expression into an edmJson representation
@@ -77,7 +77,7 @@ 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, txt) => {
81
81
  error('odata-anno-xpr', location, {
82
82
  anno, op: txt ?? op, '#': 'notadynexpr',
83
83
  });
@@ -104,15 +104,15 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
104
104
 
105
105
  //----------------------------------
106
106
  // list is a $Collection => []
107
- transform.list = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
108
- parentparent[parentprop] = xpr.filter(a => a);
109
- transformExpression(parentparent, parentprop, transform);
107
+ transform.list = (parent, prop, xpr, csnPath, parentParent, parentProp) => {
108
+ parentParent[parentProp] = xpr.filter(a => a);
109
+ transformExpression(parentParent, parentProp, transform);
110
110
  };
111
111
  // XPR
112
- transform.xpr = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
112
+ transform.xpr = (parent, prop, xpr, csnPath, parentParent, parentProp) => {
113
113
  // eliminate 'xpr' node by pulling up xpr node to its parent
114
- parentparent[parentprop] = xpr;
115
- transformExpression(parentparent, parentprop, transform);
114
+ parentParent[parentProp] = xpr;
115
+ transformExpression(parentParent, parentProp, transform);
116
116
  };
117
117
  //----------------------------------
118
118
  // CASE
@@ -150,7 +150,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
150
150
  };
151
151
  //----------------------------------
152
152
  // Cast => $Cast
153
- transform.cast = (parent, prop, castExpr, csnPath, parentparent, parentprop) => {
153
+ transform.cast = (parent, prop, castExpr, csnPath, parentParent, parentProp) => {
154
154
  const csnType = castExpr[0];
155
155
  // try to resolve to final scalar base type and use that instead of derived type
156
156
  if (!isBuiltinType(csnType.type)) {
@@ -184,8 +184,8 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
184
184
  typeFunc.args.push( { func: 'Scale', args: [ { val: csnType.scale } ] });
185
185
 
186
186
 
187
- parentparent[parentprop] = castFunc;
188
- transformExpression(parentparent, parentprop, transform);
187
+ parentParent[parentProp] = castFunc;
188
+ transformExpression(parentParent, parentProp, transform);
189
189
  };
190
190
  //----------------------------------
191
191
  const evalArgs = (argDef, args, propName) => {
@@ -261,11 +261,11 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
261
261
  transformExpression(parent, undefined, transform);
262
262
  };
263
263
  transform.$In = noOp;
264
- transform.between = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
264
+ transform.between = (parent, prop, xpr, csnPath, parentParent, parentProp) => {
265
265
  evalArgs({ exact: 2 }, xpr.slice(1), prop);
266
266
  transformExpression(xpr, undefined, transform);
267
267
  delete parent[prop];
268
- parentparent[parentprop]
268
+ parentParent[parentProp]
269
269
  = {
270
270
  $And: [
271
271
  { $Le: [ xpr[1], xpr[0] ] },
@@ -273,23 +273,23 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
273
273
  ],
274
274
  };
275
275
  };
276
- transform['||'] = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
276
+ transform['||'] = (parent, prop, xpr, csnPath, parentParent, parentProp) => {
277
277
  evalArgs({ exact: 2 }, xpr, prop);
278
278
  transformExpression(xpr, undefined, transform);
279
279
  delete parent[prop];
280
- parentparent[parentprop].$Apply = xpr;
281
- parentparent[parentprop].$Function = 'odata.concat';
280
+ parentParent[parentProp].$Apply = xpr;
281
+ parentParent[parentProp].$Function = 'odata.concat';
282
282
  };
283
283
  //----------------------------------
284
284
  // ARITHMETICAL AND UNARY
285
- transform['+'] = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
285
+ transform['+'] = (parent, prop, xpr, csnPath, parentParent, parentProp) => {
286
286
  if (Array.isArray(xpr)) {
287
287
  op('$Add')(parent, prop, xpr);
288
288
  }
289
289
  else {
290
290
  delete parent[prop];
291
- parentparent[parentprop] = xpr;
292
- transformExpression(parentparent, parentprop, transform);
291
+ parentParent[parentProp] = xpr;
292
+ transformExpression(parentParent, parentProp, transform);
293
293
  }
294
294
  };
295
295
  transform.$Add = noOp;
@@ -305,19 +305,19 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
305
305
  // $Div, $Mod are functions
306
306
  //----------------------------------
307
307
  // LITERALS
308
- transform.val = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
308
+ transform.val = (parent, prop, xpr, csnPath, parentParent, parentProp) => {
309
309
  if (xpr === null)
310
310
  parent.$Null = true;
311
311
  else
312
- parentparent[parentprop] = xpr;
312
+ parentParent[parentProp] = xpr;
313
313
  };
314
- transform['#'] = (parent, prop, xpr, csnPath, parentParent, parentprop, txt) => {
314
+ transform['#'] = (parent, prop, xpr, csnPath, parentParent, parentProp, txt) => {
315
315
  if (parent['#'] && parent.val) // enum reference that was resolved by the compiler
316
316
  delete parent['#'];
317
317
  else
318
- notADynExpr(parent, prop, xpr, csnPath, parentParent, parentprop, txt);
318
+ notADynExpr(parent, prop, xpr, csnPath, parentParent, parentProp, txt);
319
319
  };
320
- transform.ref = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
320
+ transform.ref = (parent, prop, xpr, csnPath, parentParent, parentProp) => {
321
321
  // until empty filter syntax is introduced for the annotation expressions,
322
322
  // we ignore the filters in order to generate EDMX
323
323
  if (xpr.some(ps => ps.args/* || ps.where */)) {
@@ -328,11 +328,11 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
328
328
  const [ head, ...tail ] = xpr;
329
329
  if ((head.id || head) === '$self')
330
330
  xpr = tail;
331
- parentparent[parentprop] = { $Path: xpr.map(ps => ps.id || ps).join('/') };
331
+ parentParent[parentProp] = { $Path: xpr.map(ps => ps.id || ps).join('/') };
332
332
  };
333
333
  //----------------------------------
334
334
  // Functions
335
- transform.func = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
335
+ transform.func = (parent, prop, xpr, csnPath, parentParent, parentProp) => {
336
336
  const rewriteArgs = (argDefs, evalVal = true) => {
337
337
  Object.entries(argDefs).forEach(([ argName, argDef ]) => {
338
338
  const [ foundProps, newArgs ]
@@ -722,8 +722,8 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
722
722
  UrlRef: [ oneArg, dollar ],
723
723
  // $Record ???
724
724
  $Collection: () => {
725
- standard(parentparent, parentprop);
726
- transformExpression(parentparent, parentprop, transform);
725
+ standard(parentParent, parentProp);
726
+ transformExpression(parentParent, parentProp, transform);
727
727
  },
728
728
  $Path: () => {
729
729
  oneArg(parent, xpr);
@@ -733,7 +733,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
733
733
  anno, op: `${ xpr }(…)`, meta: 'string', '#': 'wrongval_meta',
734
734
  });
735
735
  }
736
- transformExpression(parentparent, parentprop, transform);
736
+ transformExpression(parentParent, parentProp, transform);
737
737
  },
738
738
  $Null: () => {
739
739
  parent[xpr] = true;
@@ -766,11 +766,11 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
766
766
  }
767
767
  else {
768
768
  evalArgs(argDef, parent.args, xpr);
769
- parentparent[parentprop].$Apply = [ ...(parent.args || []) ];
770
- parentparent[parentprop].$Function = funcName;
771
- delete parentparent[parentprop].func;
772
- delete parentparent[parentprop].args;
773
- transformExpression(parentparent, parentprop, transform);
769
+ parentParent[parentProp].$Apply = [ ...(parent.args || []) ];
770
+ parentParent[parentProp].$Function = funcName;
771
+ delete parentParent[parentProp].func;
772
+ delete parentParent[parentProp].args;
773
+ transformExpression(parentParent, parentProp, transform);
774
774
  }
775
775
  }
776
776
  else {
@@ -783,13 +783,182 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
783
783
  };
784
784
 
785
785
  return transformExpression(carrier, anno, {
786
- '=': (parent, prop, xpr, csnPath, parentparent, parentprop) => {
786
+ '=': (parent, prop, xpr, csnPath, parentParent, parentProp) => {
787
787
  if (isAnnotationExpression(parent)) {
788
788
  delete parent['='];
789
- parentparent[parentprop] = transformExpression({ $edmJson: parseExpr( parent, { array: false }) }, undefined, transform);
789
+ const edmJson = preTransformXpr(parent);
790
+ parentParent[parentProp] = transformExpression({ $edmJson: edmJson }, undefined, transform);
790
791
  }
791
792
  },
792
- });
793
+ }, location);
794
+
795
+ /**
796
+ * Pre-transform the CSN expression into a tree structure for further EDM processing.
797
+ * Example:
798
+ * `[…, '+' …]` -> `{'+': [ …, … ]}`
799
+ *
800
+ * @param {object|Array} xpr
801
+ */
802
+ function preTransformXpr( xpr ) {
803
+ xpr = Array.isArray(xpr) ? conditionAsTree(xpr) : expressionAsTree(xpr);
804
+ xpr = preTransformExpression(xpr);
805
+ if (Array.isArray(xpr)) {
806
+ xpr = xpr.flat(Infinity);
807
+ return (xpr.length === 1) ? xpr[0] : xpr;
808
+ }
809
+ return xpr;
810
+ }
811
+
812
+ /**
813
+ * Reject any invalid expression such as `1 +`.
814
+ */
815
+ function rejectInvalidExpression( expr ) {
816
+ if (expr.length === 3 && expr[1] === 'is' && expr[2] === 'null') {
817
+ messageFunctions.error('odata-anno-xpr', location, {
818
+ '#': 'notadynexpr', anno, op: 'is null',
819
+ });
820
+ return;
821
+ }
822
+ if (expr.length === 4 && expr[1] === 'is' && expr[2] === 'not' && expr[3] === 'null') {
823
+ messageFunctions.error('odata-anno-xpr', location, {
824
+ '#': 'notadynexpr', anno, op: 'is not null',
825
+ });
826
+ return;
827
+ }
828
+ if (expr.length === 4 && expr[1] === 'not' && expr[2] === 'like') {
829
+ messageFunctions.error('odata-anno-xpr', location, {
830
+ '#': 'notadynexpr', anno, op: 'not like',
831
+ });
832
+ return;
833
+ }
834
+ if (expr.length === 3 && expr[1] === 'like') {
835
+ messageFunctions.error('odata-anno-xpr', location, {
836
+ '#': 'notadynexpr', anno, op: 'not like',
837
+ });
838
+ return;
839
+ }
840
+
841
+ // Any left-over operator is not recognized: reject it
842
+ for (let i = 0; i < expr.length; i++) {
843
+ if (expr[i] === 'not' && typeof expr[i + 1] === 'string')
844
+ ++i;
845
+
846
+ if (typeof expr[i] === 'string') {
847
+ messageFunctions.error('odata-anno-xpr', location, {
848
+ '#': 'invalid',
849
+ anno,
850
+ op: expr[i],
851
+ });
852
+ break;
853
+ }
854
+ }
855
+ }
856
+
857
+ /**
858
+ * Pre-transform a _structurized_ expression.
859
+ */
860
+ function preTransformExpression( xpr ) {
861
+ if (!xpr || typeof xpr !== 'object')
862
+ return xpr;
863
+
864
+ if (Array.isArray(xpr)) {
865
+ xpr = xpr.map(preTransformExpression); // not `preTransformXpr`, as that would re-structurize the array
866
+
867
+ if (xpr.length === 1)
868
+ return xpr[0]; // single entry, e.g. via structurizer
869
+
870
+ if (xpr[0]?.toLowerCase?.() === 'case')
871
+ return preTransformCase(xpr);
872
+
873
+ if (xpr[1]?.toLowerCase?.() === 'between')
874
+ return preTransformBetween(xpr);
875
+
876
+ if (xpr[1]?.toLowerCase?.() === 'not' &&
877
+ xpr[2]?.toLowerCase?.() === 'between')
878
+ return { not: preTransformExpression([ xpr[0], ...xpr.slice(2) ]) };
879
+
880
+ if (xpr[1]?.toLowerCase?.() === 'not' &&
881
+ xpr[2]?.toLowerCase?.() === 'in')
882
+ return { not: preTransformExpression([ xpr[0], ...xpr.slice(2) ]) };
883
+
884
+ // unary operators
885
+ if (xpr.length === 2 && (xpr[0] === '+' || xpr[0] === '-' || xpr[0] === 'not' || xpr[0] === 'new'))
886
+ return { [xpr[0]]: xpr[1] };
887
+
888
+ if (xpr.length === 3 && xpr[1] === 'is' && xpr[2] === 'null') {
889
+ rejectInvalidExpression(xpr);
890
+ return xpr;
891
+ }
892
+
893
+ // binary operators: '=', '<>', 'like', ...
894
+ if (xpr.length === 3 && typeof xpr[1] === 'string')
895
+ return { [xpr[1]]: [ xpr[0], xpr[2] ] };
896
+
897
+ rejectInvalidExpression(xpr);
898
+
899
+ return xpr;
900
+ }
901
+
902
+ if (xpr.list)
903
+ xpr.list.forEach( preTransformExpression );
904
+
905
+ if (xpr.args) {
906
+ if (!Array.isArray( xpr.args ))
907
+ Object.values( xpr.args ).forEach(preTransformExpression );
908
+ else if (xpr.args.length)
909
+ xpr.args.forEach( preTransformExpression );
910
+ }
911
+ if (xpr.ref)
912
+ xpr.ref.forEach( preTransformExpression );
913
+
914
+ if (xpr.xpr)
915
+ xpr.xpr = preTransformExpression( xpr.xpr );
916
+
917
+ if (xpr.cast) {
918
+ const castKeys = Object.keys(xpr).filter(k => k !== 'cast');
919
+ if (castKeys.length === 1)
920
+ return { cast: [ xpr.cast, { [castKeys[0]]: xpr[castKeys[0]] } ] };
921
+ }
922
+
923
+ return xpr;
924
+ }
925
+
926
+ function preTransformBetween( xpr ) {
927
+ return {
928
+ between: [
929
+ xpr[0],
930
+ xpr[2],
931
+ xpr[4],
932
+ ],
933
+ };
934
+ }
935
+
936
+ function preTransformCase( xpr ) {
937
+ const caseObj = [ ];
938
+ let pos = 1;
939
+
940
+ // CASE val WHEN val THEN …
941
+ if (xpr[pos]?.toLowerCase?.() !== 'when') {
942
+ caseObj.push( xpr[pos] );
943
+ pos++;
944
+ }
945
+
946
+ while (xpr[pos]?.toLowerCase?.() === 'when') {
947
+ if (xpr[pos + 2]?.toLowerCase?.() !== 'then')
948
+ return xpr;
949
+ caseObj.push({ when: [ xpr[pos + 1], xpr[pos + 3] ] });
950
+ pos += 4;
951
+ }
952
+ if (xpr[pos]?.toLowerCase?.() === 'else' ) {
953
+ caseObj.push(xpr[pos + 1]);
954
+ pos += 2;
955
+ }
956
+ if (xpr[pos++]?.toLowerCase?.() !== 'end')
957
+ return xpr;
958
+ return {
959
+ case: caseObj,
960
+ };
961
+ }
793
962
  }
794
963
 
795
964
  // Not everything that can occur in OData annotations can be expressed with
@@ -688,6 +688,11 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
688
688
  props.push(new Edm.Property(v, { Name: elementName }, elementCsn));
689
689
  }
690
690
  }
691
+
692
+ if (options.isV2() && isBetaEnabled(options, 'draftAdminDataHiddenFilter')) {
693
+ if (elementCsn._edmParentCsn.name === `${ elementCsn._edmParentCsn.$mySchemaName }.DraftAdministrativeData`)
694
+ elementCsn['@UI.HiddenFilter'] ??= true;
695
+ }
691
696
  });
692
697
  }
693
698
  if (options.isV2()) {
@@ -723,6 +728,8 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
723
728
 
724
729
  // add bound/unbound actions/functions for V4
725
730
  function createActionV4( actionCsn, _name, entityCsn = undefined ) {
731
+ if (actionCsn['@cds.api.ignore'])
732
+ return;
726
733
  const iAmAnAction = actionCsn.kind === 'action';
727
734
  const actionName = edmUtils.getBaseName(actionCsn.name);
728
735
  const attributes = { Name: actionName, IsBound: false };
@@ -837,6 +844,8 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
837
844
  // Parameter Nodes
838
845
  if (actionCsn.params) {
839
846
  forEach(actionCsn.params, ( parameterName, parameterCsn ) => {
847
+ if (parameterCsn['@cds.api.ignore'])
848
+ return;
840
849
  const p = new Edm.Parameter(v, { Name: parameterName }, parameterCsn );
841
850
  const pLoc = [ ...location, 'params', p._edmAttributes.Name ];
842
851
  if (!edmUtils.isODataSimpleIdentifier(parameterName))
@@ -861,6 +870,8 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
861
870
 
862
871
  // add bound/unbound actions/functions for V2
863
872
  function createActionV2( actionCsn, name, entityCsn = undefined ) {
873
+ if (actionCsn['@cds.api.ignore'])
874
+ return;
864
875
  /** @type {object} */
865
876
  const attributes = { Name: name.replace(schemaNamePrefix, '') };
866
877
  const functionImport = new Edm.FunctionImport(v, attributes );
@@ -924,6 +935,8 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
924
935
  // the client assume that Nullable is false.... Correct Nullable Handling is done inside Parameter constructor
925
936
  if (actionCsn.params) {
926
937
  Object.entries(actionCsn.params).forEach(([ parameterName, parameterCsn ], i) => {
938
+ if (parameterCsn['@cds.api.ignore'])
939
+ return;
927
940
  const type = parameterCsn?.items?.type || parameterCsn?.type;
928
941
  if (i === 0 && type === special$self) {
929
942
  // skip and remove the first parameter if it is a $self binding parameter to
@@ -292,7 +292,7 @@ function finalizeReferentialConstraints( csn, assocCsn, options, info ) {
292
292
  } );
293
293
  }
294
294
  // Remove all target elements that are not key in the principal entity
295
- // and all elements that annotated with '@cds.api.ignore'
295
+ // and all elements that are annotated with '@cds.api.ignore'
296
296
  const remainingPrincipalRefs = [];
297
297
  foreach(assocCsn._constraints.constraints,
298
298
  (c) => {
@@ -359,7 +359,7 @@ function finalizeReferentialConstraints( csn, assocCsn, options, info ) {
359
359
  // If FK is key in target => constraint
360
360
  // Don't consider primary key associations (fks become keys on the source entity) as
361
361
  // this would impose a constraint against the target.
362
- // Filter out all elements that annotated with '@cds.api.ignore'
362
+ // Filter out all elements that are annotated with '@cds.api.ignore'
363
363
 
364
364
  // In structured format, foreign keys of managed associations are never rendered, so
365
365
  // there are no constraints for them.