@sap/cds-compiler 6.4.2 → 6.4.6

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.
@@ -72,10 +72,10 @@
72
72
  // the path up to the last path step in the type-of.
73
73
  // - an element definition, `$self.sub.elem` needs to be replaced by the element name.
74
74
  // Paths without `$self` on the "type-of element" itself need to have
75
- // the first path step be replaced by the target element name.
75
+ // the first path step be replaced by the destination element name.
76
76
  // - a parameter, `$self.sub.elem` needs to be replaced by the parameter name.
77
77
  // Paths without `$self` on the "type-of element" itself need to have
78
- // the first path step be replaced by the target parameter name.
78
+ // the first path step be replaced by the destination parameter name.
79
79
  // - a return parameter, `$self` needs to be rejected, because there is no way
80
80
  // to refer to `returns`. Paths without `$self` on the "type-of element" itself
81
81
  // (not sub-elements), need to be rejected as well.
@@ -108,7 +108,7 @@
108
108
  // was projected or if a prefix was projected (e.g. for structures or associations).
109
109
  // The same rules as for ON-condition rewriting apply.
110
110
  //
111
- // Furthermore, as the target is a select item, and this select item belongs to a table
111
+ // Furthermore, as the destination is a select item, and this select item belongs to a table
112
112
  // alias, we should rewrite all annotation paths only to projected elements of that
113
113
  // table alias. Cross-rewriting between table aliases should not be done.
114
114
  // This is the same we do for association rewriting.
@@ -157,14 +157,13 @@ const { isSimpleCdlIdentifier } = require('../parsers/identifiers');
157
157
  // Config object passed around all "rewrite" functions.
158
158
  class AnnoRewriteConfig {
159
159
  anno;
160
- target;
161
- targetRoot;
160
+ destination;
161
+ destinationRoot;
162
162
  origin;
163
- fromTargetType;
163
+ fromDestinationType;
164
164
  fromCalcElement;
165
165
  expandedRoot;
166
166
  expandedRootType;
167
- isInFilter;
168
167
  tokenExpr;
169
168
  }
170
169
 
@@ -185,45 +184,50 @@ function xprRewriteFns( model ) {
185
184
  };
186
185
 
187
186
  /**
187
+ * Report anno-missing-rewrite if rewrite for annotations.
188
+ * Returns traverseExpr.STOP.
189
+ *
188
190
  * @param expr
189
191
  * @param {AnnoRewriteConfig} config
190
192
  * @param {string} [variant]
191
193
  */
192
194
  function reportAnnoRewriteError( expr, config, variant = 'std' ) {
193
- return !config.tokenExpr || // TODO: some info message ?
195
+ if (config.tokenExpr) {
194
196
  error( 'anno-missing-rewrite', [
195
- weakLocation( config.target.location ), config.target,
197
+ weakLocation( config.destination.location ), config.destination,
196
198
  ], {
197
199
  '#': variant,
198
200
  anno: config.anno,
199
201
  art: config.origin,
200
202
  elemref: expr,
201
203
  });
204
+ }
205
+ return traverseExpr.STOP;
202
206
  }
203
207
 
204
208
  /**
205
- * Rewrite the propagated annotation relative to the target.
209
+ * Rewrite the propagated annotation relative to the destination.
206
210
  *
207
- * @param {XSN.Artifact} target
211
+ * @param {XSN.Artifact} destination
208
212
  * @param {XSN.Artifact} origin
209
213
  * @param {string} annoName
210
214
  */
211
- function rewriteAnnotationsRefs( target, origin, annoName ) {
215
+ function rewriteAnnotationsRefs( destination, origin, annoName ) {
212
216
  // Make sure not to waste time if no inherited annotation has references:
213
217
  if (!origin?.$contains?.$annotation?.$path)
214
218
  return;
215
219
 
216
- const anno = target[annoName];
220
+ const anno = destination[annoName];
217
221
  // only annotations with expressions have a kind
218
222
  // also, don't report errors twice
219
223
  if (!anno.kind || anno.$invalidPaths)
220
224
  return;
221
225
 
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;
226
+ const hasError = rewriteRefsInExpression( destination, origin, annoName );
227
+ destination.$contains ??= {};
228
+ destination.$contains.$annotation ??= {};
229
+ destination.$contains.$annotation.$path ||= origin.$contains.$annotation.$path;
230
+ destination.$contains.$annotation.$self ||= origin.$contains.$annotation.$self;
227
231
  if (hasError)
228
232
  anno.$invalidPaths = true; // avoid subsequent errors
229
233
  }
@@ -231,23 +235,23 @@ function xprRewriteFns( model ) {
231
235
  function rewriteRefsInExpression( destination, origin, propName ) {
232
236
  // Annotation comes from the destination's type. That's important to know, because
233
237
  // path prefixes need to be adapted.
234
- const fromTargetType = destination.type?._artifact === origin;
238
+ const fromDestinationType = destination.type?._artifact === origin;
235
239
  // Annotation comes from the destination's calculated element.
236
240
  // A special case propagation rule, e.g
237
241
  // for `calcString: String = str;`. We also need to adapt path prefixes.
238
- const fromCalcElement = !fromTargetType && destination.$calcDepElement &&
242
+ const fromCalcElement = !fromDestinationType && destination.$calcDepElement &&
239
243
  destination.value?._artifact === origin;
240
244
 
241
245
  const { expandedRoot, expandedRootType }
242
- = !fromTargetType && getExpandRoot( destination ) || {};
246
+ = !fromDestinationType && getExpandRoot( destination ) || {};
243
247
 
244
248
  const config = {
245
249
  __proto__: AnnoRewriteConfig.prototype,
246
250
  anno: propName,
247
- target: destination,
248
- targetRoot: annoRootArt( destination ),
251
+ destination,
252
+ destinationRoot: annoRootArt( destination ),
249
253
  origin,
250
- fromTargetType,
254
+ fromDestinationType,
251
255
  fromCalcElement,
252
256
  expandedRoot,
253
257
  expandedRootType,
@@ -255,10 +259,8 @@ function xprRewriteFns( model ) {
255
259
 
256
260
  if (propName.charAt( 0 ) === '@')
257
261
  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) );
262
+ return traverseExpr( destination[propName], 'annoRewrite', destination,
263
+ rewriteAnnoExpr.bind( config ) );
262
264
  }
263
265
 
264
266
  /**
@@ -277,37 +279,36 @@ function xprRewriteFns( model ) {
277
279
  else if (expr.$tokenTexts) {
278
280
  // used to set `$tokenText` to true in case of rewritten annotation
279
281
  config.tokenExpr = expr;
280
- return traverseExpr.STOP === traverseExpr(
281
- expr, 'annoRewrite', config.target,
282
- // eslint-disable-next-line @stylistic/max-len, @stylistic/function-paren-newline
283
- (e, refCtx) => (rewriteAnnoExpr( e, config, refCtx ) ? traverseExpr.STOP : traverseExpr.SKIP) );
282
+ return traverseExpr( expr, 'annoRewrite', config.destination,
283
+ rewriteAnnoExpr.bind( config ) );
284
284
  }
285
285
  return false;
286
286
  }
287
287
 
288
288
  /**
289
- * @param {XSN.Expression} expr
290
- * @param {AnnoRewriteConfig} config
291
- * @param {string} refCtx
292
- * @returns {null|true} Returns true if the expression couldn't be rewritten.
289
+ * Rewrite the annotation expression `expr`.
290
+ * Returns traverseExpr.STOP if the expression couldn't be rewritten.
293
291
  */
294
- function rewriteAnnoExpr( expr, config, refCtx ) {
292
+ function rewriteAnnoExpr( expr, refCtx, user ) {
293
+ const config = this; // we use rewriteAnnoExpr.bind( config )
295
294
  const root = expr.path && (expr.path[0]?._navigation || expr.path[0]?._artifact);
296
295
  if (!root || !expr._artifact)
297
296
  return null; // invalid path
298
297
 
299
- const { target } = config;
298
+ if (refCtx === 'filter')
299
+ return rewriteGenericAnnoPath( expr, refCtx, config, user );
300
300
 
301
301
  // Report obsolete $parameters; parameters on non-actions not supported, yet.
302
- if (root.kind === '$parameters' || (root.kind === 'param' && root._parent.kind !== 'action' &&
303
- root._parent.kind !== 'function'))
302
+ if (root.kind === '$parameters' ||
303
+ (root.kind === 'param' && root._parent.kind !== 'action' &&
304
+ root._parent.kind !== 'function'))
304
305
  return reportAnnoRewriteError( expr, config, 'unsupported' );
305
306
 
306
307
  if (root.kind === 'key') {
307
308
  // References starting with a foreign key name (only possible in an
308
309
  // annotation on a foreign key) have length 1 + the foreign key cannot be
309
310
  // renamed → nothing to do. Otherwise, foreign keys cannot be directly
310
- // addressed, except as `annotate` target. (Even as annotation value for
311
+ // addressed, except via `annotate`. (Even as annotation value for
311
312
  // foreign keys, `$self.assoc.target_id` always refers to the target side.)
312
313
  return null;
313
314
  }
@@ -318,37 +319,15 @@ function xprRewriteFns( model ) {
318
319
  return null;
319
320
 
320
321
  let hasError = false;
321
- if (config.fromTargetType || config.fromCalcElement)
322
- hasError = adaptPathPrefixViaType( expr, config );
322
+ if (config.fromDestinationType || config.fromCalcElement)
323
+ hasError = adaptPathPrefixViaType( expr, config, user );
323
324
  else if (config.expandedRoot)
324
- hasError = adaptPathPrefixViaTypeExpansion( expr, config );
325
+ hasError = adaptPathPrefixViaTypeExpansion( expr, config, user );
325
326
 
326
- hasError ||= rewriteGenericAnnoPath( expr, config, refCtx );
327
+ hasError ||= rewriteGenericAnnoPath( expr, refCtx, config, user );
327
328
 
328
329
  if (hasError)
329
- return true;
330
-
331
- // TODO: Remove extra loop once filter traversal is added to traverseExpr (#12068)
332
- for (const step of expr.path) {
333
- if (step?._artifact && step.where && !Array.isArray( step._artifact ) ) {
334
- // We must not prefix `$`-renamed variables with `$self`, as it would
335
- // change meaning, see (#11775). Also, the path's target changes.
336
- const assocTarget = step._artifact.target._artifact;
337
- if (target) {
338
- const filterConfig = { ...config, target: assocTarget, isInFilter: true };
339
- if (traverseExpr.STOP === traverseExpr(
340
- step.where, 'filter', step,
341
- // eslint-disable-next-line @stylistic/max-len
342
- (e, ctx) => expr.path && (rewriteGenericAnnoPath( e, filterConfig, ctx ) ? traverseExpr.STOP : traverseExpr.SKIP)
343
- ))
344
- return true;
345
- }
346
- else {
347
- // can't happen: rejected earlier by compiler
348
- return reportAnnoRewriteError( expr, config, 'unsupported' );
349
- }
350
- }
351
- }
330
+ return traverseExpr.STOP;
352
331
 
353
332
  if (expr.$tokenTexts === true) {
354
333
  // TODO: do not do with Universal-CSN (and gensrc, but that does not matter)
@@ -365,46 +344,44 @@ function xprRewriteFns( model ) {
365
344
  if (model.options.testMode) {
366
345
  // re-resolve the modified path; all paths steps must match what we rewrote
367
346
  const ref = { ...expr, path: [ ...expr.path.map(item => ({ ...item })) ] };
368
- if (!resolvePath( ref, refCtx, target ))
347
+ if (!resolvePath( ref, refCtx, user ))
369
348
  throw new CompilerAssertion(`rewritten anno path must be resolvable: ${ JSON.stringify(ref.path) }`);
370
-
371
- for (let i = 0; i < ref.path.length; ++i) {
372
- const actual = ref.path[i];
373
- const expected = ref.path[i];
374
- if (actual._artifact !== expected._artifact) {
375
- throw new CompilerAssertion(`rewritten anno path contains incorrect artifact links: ${
376
- JSON.stringify(ref.path) }; step ${ i }`);
377
- }
378
- else if (actual._navigation !== undefined && actual._navigation !== expected._navigation) {
379
- throw new CompilerAssertion(`rewritten anno path contains incorrect navigation links: ${
380
- JSON.stringify(ref.path) }; step ${ i }`);
381
- }
382
- }
383
349
  }
384
-
385
350
  return false;
386
351
  }
387
352
 
388
353
  /**
389
- * @param {XSN.Expression} expr
390
- * @param {AnnoRewriteConfig} config
391
- * @returns {*}
354
+ * Return the destination node of the expr rewrite (stored in param `config`), or
355
+ * if we are in a filter expression, the association target of the corresponding
356
+ * path item (provided via param `user`).
357
+ *
358
+ * TODO: in the end, we should directly use the standard expression parameter
359
+ * `user` in all functions below. Probably if we unify this module with
360
+ * rewriteCondition() of tweak-assocs.js.
392
361
  */
393
- function getRootEnv( expr, config ) {
394
- const { target } = config;
362
+ function destinationOrAssocTarget( config, user ) {
363
+ return (user.id && user.where)
364
+ ? user._artifact.target._artifact
365
+ : config.destination;
366
+ }
395
367
 
368
+ /**
369
+ * TODO: what exactly is a root environment?
370
+ */
371
+ function getRootEnv( expr, config, destination ) {
396
372
  if (expr.scope === 'param') // path is absolute
397
- return navigationEnv( config.targetRoot, null, null, 'nav' );
373
+ return navigationEnv( config.destinationRoot, null, null, 'nav' );
398
374
 
399
375
  // On select items, use navigation elements or table alias
400
376
  // TODO: Expand/inline paths don't have a `_navigation` property on their last
401
377
  // path step, yet. We need to implement expand/inline.
402
- const isSimpleSelectItem = target.value?.path && target._main?.query && !target._columnParent;
378
+ const isSimpleSelectItem = destination.value?.path && destination._main?.query &&
379
+ !destination._columnParent;
403
380
  if (isSimpleSelectItem) {
404
381
  const isSelfPath = (expr.path[0]?._navigation?.kind === '$self');
405
382
  if (isSelfPath) {
406
383
  // Path is absolute, use table alias to resolve it.
407
- let tableAlias = target.value.path[0]._navigation;
384
+ let tableAlias = destination.value.path[0]._navigation;
408
385
  while (tableAlias && tableAlias.kind === '$navElement')
409
386
  tableAlias = tableAlias._parent;
410
387
  if (tableAlias)
@@ -412,37 +389,35 @@ function xprRewriteFns( model ) {
412
389
  }
413
390
  else {
414
391
  // Path is relative
415
- const nav = target.value.path[target.value.path.length - 1]._navigation?._parent;
392
+ const nav = destination.value.path[destination.value.path.length - 1]._navigation?._parent;
416
393
  if (nav)
417
394
  return nav;
418
395
  }
419
396
  }
420
397
 
421
398
  if (isSimpleSelectItem && model.options.testMode)
422
- throw new CompilerAssertion(`select item has no table alias: ${ JSON.stringify(target.value.path) }`);
399
+ throw new CompilerAssertion(`select item has no table alias: ${ JSON.stringify(destination.value.path) }`);
423
400
 
424
401
  if (isAnnoPathAbsolute( expr ))
425
- return navigationEnv( config.targetRoot, null, null, 'nav' );
402
+ return navigationEnv( config.destinationRoot, null, null, 'nav' );
426
403
 
427
404
  // anno path is relative / element reference (others were already rejected)
428
- // if the target is a root artifact, use it. Otherwise, use its parent.
429
- return navigationEnv( isAnnoRootArt( target ) ? target : target._parent, null, null, 'nav' );
405
+ // if the destination is a root artifact, use it. Otherwise, use its parent.
406
+ return navigationEnv( isAnnoRootArt( destination ) ? destination : destination._parent,
407
+ null, null, 'nav' );
430
408
  }
431
409
 
432
- /**
433
- * @param {XSN.Expression} expr
434
- * @param {AnnoRewriteConfig} config
435
- * @param {string} refCtx
436
- * @returns {boolean}
437
- */
438
- function rewriteGenericAnnoPath( expr, config, refCtx ) {
410
+ function rewriteGenericAnnoPath( expr, refCtx, config, user ) {
411
+ if (expr._artifact?.kind === 'builtin')
412
+ return false;
413
+ const destination = destinationOrAssocTarget( config, user );
439
414
  const isAbsolute = isAnnoPathAbsolute( expr );
440
415
  const startIndex = isAbsolute ? 1 : 0;
441
416
 
442
417
  // We get the root environment now, even though below we resolve the root item
443
418
  // again if it was absolute (e.g. $self). We do so, because for queries, we
444
419
  // want to respect the select item's corresponding table alias.
445
- const rootEnv = getRootEnv( expr, config );
420
+ const rootEnv = getRootEnv( expr, config, destination );
446
421
 
447
422
  // reset artifact link; we'll set it again if there are no errors
448
423
  setArtifactLink( expr, null );
@@ -454,7 +429,7 @@ function xprRewriteFns( model ) {
454
429
  delete expr.path[0]._navigation;
455
430
  // TODO: What about `up_`? Shouldn't we set `_navigation` as well?
456
431
  // TODO: Can we handle `$self` of anonymous-composition-of-aspect?
457
- const root = resolvePathRoot( expr, refCtx, config.target );
432
+ const root = resolvePathRoot( expr, refCtx, destination );
458
433
  if (!root)
459
434
  return reportAnnoRewriteError( expr, config );
460
435
  }
@@ -495,13 +470,13 @@ function xprRewriteFns( model ) {
495
470
  setArtifactLink( expr, art );
496
471
 
497
472
  if (startIndex === 0 && expr.path[0].id.startsWith('$')) {
498
- if (config.isInFilter) {
473
+ if (refCtx === 'filter') {
499
474
  // In filters, we must not prepend `$self`, as that would change its meaning.
500
475
  // We must reject it. See #11775
501
476
  return reportAnnoRewriteError( expr, config );
502
477
  }
503
478
  // After rewriting, if an element starts with `$` -> add root prefix
504
- prependRootPath( config.origin, config.targetRoot, expr );
479
+ prependRootPath( config.origin, config.destinationRoot, expr );
505
480
  }
506
481
 
507
482
  return false;
@@ -513,12 +488,12 @@ function xprRewriteFns( model ) {
513
488
  *
514
489
  * @returns {boolean} Returns true if it couldn't be rewritten.
515
490
  */
516
- function adaptPathPrefixViaType( expr, config ) {
517
- const { target, origin } = config;
518
- if (!target._main && !origin._main)
491
+ function adaptPathPrefixViaType( expr, config, user ) {
492
+ const { destination, origin } = config;
493
+ if (!destination._main && !origin._main)
519
494
  return false; // no need to rewrite; both are top-level
520
495
 
521
- if (rejectOuterReference( expr, origin, config ))
496
+ if (rejectOuterReference( expr, origin, config, user ))
522
497
  return true;
523
498
 
524
499
  // $self-paths via types from/to non-main artifacts always need to be rewritten.
@@ -529,16 +504,16 @@ function xprRewriteFns( model ) {
529
504
  stripAbsolutePathPrefix( expr, origin );
530
505
 
531
506
  if (wasAbsolute) {
532
- prependRootPath( origin, target, expr );
507
+ prependRootPath( origin, destination, expr );
533
508
  }
534
- else if (!isAnnoRootArt( target )) { // target is element
535
- const item = { id: target.name.id };
536
- setArtifactLink( item, target );
509
+ else if (!isAnnoRootArt( destination )) { // destination is element
510
+ const item = { id: destination.name.id };
511
+ setArtifactLink( item, destination );
537
512
  prependToStrippedPath( origin, expr, [ item ] );
538
513
  }
539
- else if (target.kind === 'param') {
514
+ else if (destination.kind === 'param') {
540
515
  // annotations on parameters need a `:prefix`
541
- prependRootPath( origin, target, expr );
516
+ prependRootPath( origin, destination, expr );
542
517
  }
543
518
  else {
544
519
  prependToStrippedPath( origin, expr, [ ] );
@@ -556,7 +531,7 @@ function xprRewriteFns( model ) {
556
531
  *
557
532
  * @returns {boolean} Returns true if it couldn't be rewritten.
558
533
  */
559
- function adaptPathPrefixViaTypeExpansion( expr, config ) {
534
+ function adaptPathPrefixViaTypeExpansion( expr, config, user ) {
560
535
  const root = expr.path[0]?._navigation;
561
536
  if (root?.kind !== '$self') {
562
537
  // non-self paths are always valid in expanded artifacts
@@ -569,14 +544,14 @@ function xprRewriteFns( model ) {
569
544
  if (!isBetaEnabled( model.options, 'rewriteAnnotationExpressionsViaType' ))
570
545
  return reportAnnoRewriteError( expr, config, 'unsupported' );
571
546
 
572
- if (rejectOuterReference( expr, config.expandedRootType, config ))
547
+ if (rejectOuterReference( expr, config.expandedRootType, config, user ))
573
548
  return true;
574
549
 
575
550
  stripAbsolutePathPrefix( expr, config.expandedRootType );
576
551
  prependRootPath( config.expandedRootType, config.expandedRoot, expr );
577
- setExpandStatusAnnotate( config.target, 'annotate' );
552
+ setExpandStatusAnnotate( config.destination, 'annotate' );
578
553
 
579
- config.target[config.anno].$inferred = 'anno-rewrite';
554
+ config.destination[config.anno].$inferred = 'anno-rewrite';
580
555
  // $self-paths via type expansion always need to be rewritten.
581
556
  if (config.tokenExpr)
582
557
  config.tokenExpr.$tokenTexts = true;
@@ -621,32 +596,29 @@ function xprRewriteFns( model ) {
621
596
  }
622
597
 
623
598
  /**
624
- * Returns false if the path can be propagated to the target without referring
625
- * to any "outer" elements. It differentiates between the target being a main
599
+ * Returns false if the path can be propagated to the destination without referring
600
+ * to any "outer" elements. It differentiates between the destination being a main
626
601
  * artifact and elements, because an element annotation referring to itself can't
627
602
  * be propagated to a type:
628
603
  *
629
604
  * type T1 : { @a: (elem) elem: String; };
630
605
  * type T2 : T1:elem; // invalid
631
606
  *
632
- * Also considers other targets such as `returns`, etc.
633
- *
634
- * @param {XSN.Expression} expr
635
- * @param {XSN.Artifact} origin
636
- * @param {AnnoRewriteConfig} config
637
- * @returns {boolean}
607
+ * Also considers other destinations such as `returns`, etc.
638
608
  */
639
- function rejectOuterReference( expr, origin, config ) {
609
+ function rejectOuterReference( expr, origin, config, user ) {
640
610
  if (!isAnnoPathAbsolute( expr ) && !origin._main)
641
611
  return false;
642
612
 
643
613
  const root = expr.path[0]?._navigation;
644
614
  const found = findRelativeRoot( expr, origin );
615
+ const destination = destinationOrAssocTarget( config, user );
645
616
  const isInvalid = (found === -1) ||
646
617
  // Can't use paths with `$self` in `returns`.
647
- (root?.kind === '$self' && isReturnParam( config.targetRoot )) ||
618
+ (root?.kind === '$self' && isReturnParam( config.destinationRoot )) ||
648
619
  // siblings are allowed for non-main artifacts, except for 'returns'
649
- (!config.target._main || isReturnParam( config.target )) && (expr.path.length - found) <= 1;
620
+ (!destination._main || isReturnParam( destination )) &&
621
+ (expr.path.length - found) <= 1;
650
622
 
651
623
  if (isInvalid)
652
624
  return reportAnnoRewriteError( expr, config );
@@ -720,7 +692,7 @@ function xprRewriteFns( model ) {
720
692
  * @returns {*|null}
721
693
  */
722
694
  function rewriteItem( expr, config, env, index ) {
723
- const rewriteTarget = findRewriteTarget( expr, index, env, config.target );
695
+ const rewriteTarget = findRewriteTarget( expr, index, env, config.destination );
724
696
  const found = setArtifactLink( expr.path[index], rewriteTarget[0] );
725
697
  if (!found)
726
698
  return null;
@@ -784,20 +756,20 @@ function isReturnParam( art ) {
784
756
  }
785
757
 
786
758
  /**
787
- * Gets the artifact (e.g. element) that was expanded. `target` is a sub-artifact of that root and
788
- * is an expanded element.
759
+ * Gets the artifact (e.g. element) that was expanded.
760
+ * `destination` is a sub-artifact of that root and is an expanded element.
789
761
  *
790
762
  * - expandedRoot: Top-most structure that was expanded.
791
763
  * - expandedRootType: The type of expandedRoot.
792
764
  *
793
- * @param {XSN.Artifact} target
765
+ * @param {XSN.Artifact} destination
794
766
  * @returns { {expandedRoot: XSN.Artifact, expandedRootType: XSN.Artifact}}
795
767
  */
796
- function getExpandRoot( target ) {
797
- if (target.$inferred !== 'expanded' && target.$inferred !== 'rewrite')
768
+ function getExpandRoot( destination ) {
769
+ if (destination.$inferred !== 'expanded' && destination.$inferred !== 'rewrite')
798
770
  return { expandedRoot: null, expandedRootType: null };
799
771
 
800
- let expandedRoot = target;
772
+ let expandedRoot = destination;
801
773
 
802
774
  // 'expanded' for structures, 'rewrite' for foreign keys
803
775
  while (expandedRoot.$inferred === 'expanded' || expandedRoot.$inferred === 'rewrite')
@@ -1 +1 @@
1
- 2f488eae5a0a809b97737edac6e2a42d
1
+ 8e38091fda3711e1d4d2b8b87fbdd328
@@ -2111,6 +2111,7 @@ default:this.gr([';']);continue
2111
2111
  }
2112
2112
  default:
2113
2113
  this.attachLocation( $.art )
2114
+ if (!$.art.name) this.addDef( $.art, $.outer, 'elements', 'element' )
2114
2115
  return this.exit_()
2115
2116
  }
2116
2117
  }
@@ -4451,8 +4452,8 @@ case 820:switch(this.l()){
4451
4452
  case')':this.gc(821,'fail')&&this.c(0);continue
4452
4453
  default:this.s=821;continue
4453
4454
  }
4454
- case 821:if(this.condition(_={},822))$.value=_.expr;continue
4455
- case 822:if(this.m(0,')')){ $.value.$tokenTexts = this.ruleTokensText(); }continue
4455
+ case 821:if(this.condition(_={},822)){$.value=_.expr; $.value.$tokenTexts = this.ruleTokensText(); }continue
4456
+ case 822:this.m(0,')');continue
4456
4457
  default:
4457
4458
  this.attachLocation( $.value )
4458
4459
  return this.exit_()
@@ -696,7 +696,7 @@ class AstBuildingParser extends BaseParser {
696
696
 
697
697
  ruleTokensText() {
698
698
  let tokenIdx = this.stack.at(-1).tokenIdx + 1;
699
- const stop = this.tokenIdx - 1;
699
+ const stop = this.tokenIdx;
700
700
 
701
701
  let { text: result, location: prev } = this.tokens[tokenIdx];
702
702
  while (++tokenIdx < stop) {
@@ -283,7 +283,7 @@ function handleExists( csn, options, messageFunctions, csnUtils ) {
283
283
 
284
284
  newExpr.push('exists');
285
285
  if (ref?.where) {
286
- const remappedWhere = remapExistingWhere(target, ref.where, exprPath, current);
286
+ const remappedWhere = remapExistingWhere( target, ref.where );
287
287
  subselect.SELECT.where.push('and');
288
288
  if (remappedWhere.length > 3)
289
289
  subselect.SELECT.where.push( { xpr: remappedWhere } );
@@ -361,21 +361,14 @@ function handleExists( csn, options, messageFunctions, csnUtils ) {
361
361
  *
362
362
  * This function does this by adding the assoc target before all the refs so that the refs are resolvable in the WHERE.
363
363
  *
364
- * This function also rejects $self paths in filter conditions.
365
- *
366
364
  * @param {string} target
367
365
  * @param {TokenStream} where
368
- * @param {CSN.Path} path path to the part, used if error needs to be thrown
369
- * @param {CSN.Artifact} parent the host of the `where`, used if error needs to be thrown
370
366
  *
371
367
  * @returns {TokenStream} where The input-where with the refs transformed to absolute ones
372
368
  */
373
- function remapExistingWhere( target, where, path, parent ) {
369
+ function remapExistingWhere( target, where ) {
374
370
  return where.map((part) => {
375
- if (part.$scope === '$self') {
376
- error('ref-unexpected-self', path, { '#': 'exists-filter', elemref: parent, id: part.ref[0] });
377
- }
378
- else if (part.ref && part.$scope !== '$magic') {
371
+ if (part.ref && part.$scope !== '$magic') {
379
372
  part.ref = [ target, ...part.ref ];
380
373
  return part;
381
374
  }
@@ -145,11 +145,6 @@ function getHelpers( csn, inspectRef, error ) {
145
145
  * @returns {object[]} The stuff to add to the where
146
146
  */
147
147
  function translateManagedAssocToWhere( root, target, isPrefixedWithTableAlias, base, current ) {
148
- if (current.$scope === '$self') {
149
- error('ref-unexpected-self', current.$path, { '#': 'exists', id: current.ref[0], name: 'exists' });
150
- return [];
151
- }
152
-
153
148
  const whereExtension = [];
154
149
  for (let j = 0; j < root.keys.length; j++) {
155
150
  const lop = { ref: [ target, ...root.keys[j].ref ] }; // target side
@@ -58,23 +58,7 @@ const annoPersistenceSkip = '@cds.persistence.skip';
58
58
  * @param {CSN.Model} csn
59
59
  * Input CSN model. Should not have existing convenience views.
60
60
  *
61
- * @param {object} options
62
- * CSN options. Only few options are used, see below for important ones.
63
- * Options such as `testMode` or `testSortCsn` can also be set.
64
- *
65
- * @param {string} [options.localizedLanguageFallback]
66
- * Valid values (if set): 'none', 'coalesce' (default)
67
- * Whether to use a `coalesce()` function when selecting from `.texts` entities.
68
- * If not set, untranslated strings may not return any value. If 'coalesce'
69
- * is used, it will fall back to the original string.
70
- *
71
- * @param {boolean} [options.localizedWithoutCoalesce]
72
- * Deprecated version of localizedLanguageFallback. Do not use.
73
- *
74
- * @param {boolean} [options.fewerLocalizedViews]
75
- * Default: true
76
- *
77
- * @param {boolean} [options.testMode]
61
+ * @param {CSN.Options} options
78
62
  *
79
63
  * @param {object} config
80
64
  * Configuration for creating convenience views. Non-user visible options.
@@ -252,12 +236,14 @@ function _addLocalizationViews(csn, options, config) {
252
236
  }
253
237
 
254
238
  function createJoinConditionFromLocaleElement() {
239
+ const targetAlias = 'localized_1';
240
+ const sourceAlias = 'L_0';
255
241
  return adaptExpr(entity.elements.localized.on);
256
242
 
257
243
  function adaptExpr(expr) {
258
244
  // We only support a few specific ON-conditions, not generic expressions.
259
245
  // In case of unsupported ON-conditions, we emit an error.
260
- return expr.map((x) => {
246
+ const res = expr.map((x) => {
261
247
  if (!x || typeof x === 'string')
262
248
  return x;
263
249
  if (x.xpr)
@@ -273,14 +259,21 @@ function _addLocalizationViews(csn, options, config) {
273
259
  );
274
260
  return x;
275
261
  });
262
+
263
+ // the `localized` association does not contain the `tenant` element, so we need to add it here
264
+ const addTenantCol = options.tenantDiscriminator && entity.elements.tenant?.key;
265
+ if (addTenantCol)
266
+ return [ { ref: [ targetAlias, 'tenant' ] }, '=', { ref: [ sourceAlias, 'tenant' ] }, 'AND', { xpr: [ ...res ] } ];
267
+
268
+ return res;
276
269
  }
277
270
 
278
271
  function adaptRef(expr) {
279
272
  if (expr.ref[0].charAt(0) === '$') // variable
280
273
  return { ref: [ ...expr.ref ] };
281
274
  if (expr.ref[0] === 'localized') // target side
282
- return { ref: [ 'localized_1', ...expr.ref.slice(1) ] };
283
- return { ref: [ 'L_0', ...expr.ref ] }; // source side
275
+ return { ref: [ targetAlias, ...expr.ref.slice(1) ] };
276
+ return { ref: [ sourceAlias, ...expr.ref ] }; // source side
284
277
  }
285
278
  }
286
279
  }