@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.
- package/CHANGELOG.md +55 -5
- package/lib/base/message-registry.js +2 -2
- package/lib/checks/existsExpressionsOnlyForeignKeys.js +16 -10
- package/lib/checks/existsMustEndInAssoc.js +1 -1
- package/lib/checks/existsMustNotStartWithDollarSelf.js +31 -0
- package/lib/checks/validator.js +4 -2
- package/lib/compiler/assert-consistency.js +2 -2
- package/lib/compiler/builtins.js +5 -6
- package/lib/compiler/shared.js +127 -109
- package/lib/compiler/tweak-assocs.js +1 -1
- package/lib/compiler/xpr-rewrite.js +111 -139
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +3 -2
- package/lib/parsers/AstBuildingParser.js +1 -1
- package/lib/transform/db/assocsToQueries/transformExists.js +3 -10
- package/lib/transform/db/assocsToQueries/utils.js +0 -5
- package/lib/transform/localized.js +13 -20
- package/package.json +3 -3
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
|
|
161
|
-
|
|
160
|
+
destination;
|
|
161
|
+
destinationRoot;
|
|
162
162
|
origin;
|
|
163
|
-
|
|
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
|
-
|
|
195
|
+
if (config.tokenExpr) {
|
|
194
196
|
error( 'anno-missing-rewrite', [
|
|
195
|
-
weakLocation( config.
|
|
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
|
|
209
|
+
* Rewrite the propagated annotation relative to the destination.
|
|
206
210
|
*
|
|
207
|
-
* @param {XSN.Artifact}
|
|
211
|
+
* @param {XSN.Artifact} destination
|
|
208
212
|
* @param {XSN.Artifact} origin
|
|
209
213
|
* @param {string} annoName
|
|
210
214
|
*/
|
|
211
|
-
function rewriteAnnotationsRefs(
|
|
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 =
|
|
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(
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
|
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 = !
|
|
242
|
+
const fromCalcElement = !fromDestinationType && destination.$calcDepElement &&
|
|
239
243
|
destination.value?._artifact === origin;
|
|
240
244
|
|
|
241
245
|
const { expandedRoot, expandedRootType }
|
|
242
|
-
= !
|
|
246
|
+
= !fromDestinationType && getExpandRoot( destination ) || {};
|
|
243
247
|
|
|
244
248
|
const config = {
|
|
245
249
|
__proto__: AnnoRewriteConfig.prototype,
|
|
246
250
|
anno: propName,
|
|
247
|
-
|
|
248
|
-
|
|
251
|
+
destination,
|
|
252
|
+
destinationRoot: annoRootArt( destination ),
|
|
249
253
|
origin,
|
|
250
|
-
|
|
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
|
|
259
|
-
|
|
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
|
|
281
|
-
|
|
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
|
-
*
|
|
290
|
-
*
|
|
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,
|
|
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
|
-
|
|
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' ||
|
|
303
|
-
|
|
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
|
|
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.
|
|
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,
|
|
327
|
+
hasError ||= rewriteGenericAnnoPath( expr, refCtx, config, user );
|
|
327
328
|
|
|
328
329
|
if (hasError)
|
|
329
|
-
return
|
|
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,
|
|
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
|
-
*
|
|
390
|
-
*
|
|
391
|
-
*
|
|
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
|
|
394
|
-
|
|
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.
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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.
|
|
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
|
|
429
|
-
return navigationEnv( isAnnoRootArt(
|
|
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
|
-
|
|
434
|
-
|
|
435
|
-
|
|
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,
|
|
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 (
|
|
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.
|
|
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 {
|
|
518
|
-
if (!
|
|
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,
|
|
507
|
+
prependRootPath( origin, destination, expr );
|
|
533
508
|
}
|
|
534
|
-
else if (!isAnnoRootArt(
|
|
535
|
-
const item = { id:
|
|
536
|
-
setArtifactLink( item,
|
|
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 (
|
|
514
|
+
else if (destination.kind === 'param') {
|
|
540
515
|
// annotations on parameters need a `:prefix`
|
|
541
|
-
prependRootPath( origin,
|
|
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.
|
|
552
|
+
setExpandStatusAnnotate( config.destination, 'annotate' );
|
|
578
553
|
|
|
579
|
-
config.
|
|
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
|
|
625
|
-
* to any "outer" elements. It differentiates between the
|
|
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
|
|
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.
|
|
618
|
+
(root?.kind === '$self' && isReturnParam( config.destinationRoot )) ||
|
|
648
619
|
// siblings are allowed for non-main artifacts, except for 'returns'
|
|
649
|
-
(!
|
|
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.
|
|
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.
|
|
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}
|
|
765
|
+
* @param {XSN.Artifact} destination
|
|
794
766
|
* @returns { {expandedRoot: XSN.Artifact, expandedRootType: XSN.Artifact}}
|
|
795
767
|
*/
|
|
796
|
-
function getExpandRoot(
|
|
797
|
-
if (
|
|
768
|
+
function getExpandRoot( destination ) {
|
|
769
|
+
if (destination.$inferred !== 'expanded' && destination.$inferred !== 'rewrite')
|
|
798
770
|
return { expandedRoot: null, expandedRootType: null };
|
|
799
771
|
|
|
800
|
-
let expandedRoot =
|
|
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
|
-
|
|
1
|
+
8e38091fda3711e1d4d2b8b87fbdd328
|
package/lib/gen/CdlParser.js
CHANGED
|
@@ -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:
|
|
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
|
|
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
|
|
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
|
|
369
|
+
function remapExistingWhere( target, where ) {
|
|
374
370
|
return where.map((part) => {
|
|
375
|
-
if (part.$scope
|
|
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 {
|
|
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
|
-
|
|
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: [
|
|
283
|
-
return { ref: [
|
|
275
|
+
return { ref: [ targetAlias, ...expr.ref.slice(1) ] };
|
|
276
|
+
return { ref: [ sourceAlias, ...expr.ref ] }; // source side
|
|
284
277
|
}
|
|
285
278
|
}
|
|
286
279
|
}
|