@sap/cds-compiler 5.7.4 → 5.8.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.
- package/CHANGELOG.md +60 -2
- package/bin/cdsse.js +13 -1
- package/doc/CHANGELOG_BETA.md +7 -0
- package/lib/api/options.js +2 -1
- package/lib/api/validate.js +9 -0
- package/lib/base/message-registry.js +55 -20
- package/lib/base/messages.js +5 -2
- package/lib/base/model.js +4 -1
- package/lib/checks/assocOutsideService.js +40 -0
- package/lib/checks/featureFlags.js +4 -1
- package/lib/checks/types.js +7 -4
- package/lib/checks/validator.js +3 -0
- package/lib/compiler/assert-consistency.js +11 -5
- package/lib/compiler/checks.js +79 -17
- package/lib/compiler/define.js +57 -3
- package/lib/compiler/extend.js +1 -2
- package/lib/compiler/generate.js +1 -1
- package/lib/compiler/populate.js +17 -6
- package/lib/compiler/propagator.js +1 -1
- package/lib/compiler/resolve.js +181 -150
- package/lib/compiler/shared.js +276 -22
- package/lib/compiler/tweak-assocs.js +15 -4
- package/lib/compiler/xpr-rewrite.js +76 -50
- package/lib/edm/annotations/edmJson.js +1 -1
- package/lib/edm/annotations/genericTranslation.js +2 -2
- package/lib/edm/csn2edm.js +2 -2
- package/lib/edm/edmPreprocessor.js +15 -9
- package/lib/edm/edmUtils.js +12 -5
- package/lib/gen/CdlGrammar.checksum +1 -0
- package/lib/gen/CdlParser.js +2234 -2233
- package/lib/gen/Dictionary.json +55 -8
- package/lib/json/from-csn.js +37 -17
- package/lib/json/to-csn.js +4 -0
- package/lib/language/genericAntlrParser.js +7 -0
- package/lib/main.d.ts +5 -0
- package/lib/model/cloneCsn.js +1 -0
- package/lib/model/csnRefs.js +1 -0
- package/lib/model/csnUtils.js +0 -5
- package/lib/modelCompare/utils/filter.js +2 -2
- package/lib/optionProcessor.js +2 -0
- package/lib/parsers/AstBuildingParser.js +47 -17
- package/lib/parsers/CdlGrammar.g4 +10 -12
- package/lib/parsers/XprTree.js +206 -0
- package/lib/render/toCdl.js +61 -89
- package/lib/render/toSql.js +59 -29
- package/lib/render/utils/standardDatabaseFunctions.js +252 -15
- package/lib/transform/addTenantFields.js +9 -3
- package/lib/transform/db/assocsToQueries/transformExists.js +3 -0
- package/lib/transform/db/assocsToQueries/utils.js +10 -3
- package/lib/transform/db/expansion.js +3 -1
- package/lib/transform/db/flattening.js +7 -3
- package/lib/transform/db/killAnnotations.js +1 -0
- package/lib/transform/db/processSqlServices.js +70 -17
- package/lib/transform/draft/db.js +8 -3
- package/lib/transform/draft/odata.js +27 -4
- package/lib/transform/effective/main.js +37 -10
- package/lib/transform/effective/misc.js +4 -9
- package/lib/transform/effective/service.js +34 -0
- package/lib/transform/effective/types.js +28 -17
- package/lib/transform/forOdata.js +36 -10
- package/lib/transform/forRelationalDB.js +30 -18
- package/lib/transform/odata/adaptAnnotationRefs.js +37 -21
- package/lib/transform/odata/createForeignKeys.js +121 -117
- package/lib/transform/odata/flattening.js +12 -9
- package/lib/transform/transformUtils.js +58 -25
- package/lib/transform/translateAssocsToJoins.js +10 -6
- package/lib/transform/universalCsn/coreComputed.js +5 -1
- package/package.json +1 -1
- package/share/messages/message-explanations.json +1 -0
- package/share/messages/rewrite-not-supported.md +5 -0
- package/share/messages/rewrite-undefined-key.md +94 -0
package/lib/compiler/shared.js
CHANGED
|
@@ -319,6 +319,7 @@ function fns( model ) {
|
|
|
319
319
|
|
|
320
320
|
Object.assign( model.$functions, {
|
|
321
321
|
traverseExpr,
|
|
322
|
+
traverseTypedExpr,
|
|
322
323
|
resolveUncheckedPath,
|
|
323
324
|
resolveTypeArgumentsUnchecked, // TODO: move to some other file
|
|
324
325
|
resolvePathRoot,
|
|
@@ -330,28 +331,263 @@ function fns( model ) {
|
|
|
330
331
|
nestedElements,
|
|
331
332
|
attachAndEmitValidNames,
|
|
332
333
|
} );
|
|
334
|
+
traverseExpr.STOP = Symbol( 'STOP' );
|
|
335
|
+
traverseExpr.SKIP = Symbol( 'SKIP' );
|
|
336
|
+
traverseTypedExpr.STOP = traverseExpr.STOP;
|
|
337
|
+
traverseTypedExpr.SKIP = traverseExpr.SKIP;
|
|
333
338
|
return;
|
|
334
339
|
|
|
335
340
|
// Expression traversal function ----------------------------------------------
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Recursively traverse the expression `expr` and call `callback` on the expression nodes.
|
|
344
|
+
*
|
|
345
|
+
* …
|
|
346
|
+
*
|
|
347
|
+
* Sub queries are not further traversed, but `callback` is called on the
|
|
348
|
+
* expression node having the property `query`.
|
|
349
|
+
*
|
|
350
|
+
* Callbacks can influence the traversal by returning a symbol:
|
|
351
|
+
*
|
|
352
|
+
* - `traverseExpr.STOP`: the traversal is stopped immediately
|
|
353
|
+
* - `traverseExpr.SKIP` on a node with a `path` property: the path items
|
|
354
|
+
* with its filters and arguments are not traversed
|
|
355
|
+
* - `traverseExpr.SKIP` on a path item: the expression in the `where`
|
|
356
|
+
* condition is not traversed
|
|
357
|
+
*/
|
|
336
358
|
function traverseExpr( expr, exprCtx, user, callback ) {
|
|
337
359
|
if (!expr || typeof expr === 'string') // parse error or keywords in {xpr:...}
|
|
338
360
|
return null;
|
|
339
361
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
362
|
+
let exit = null;
|
|
363
|
+
// `type` property for `cast, `query` for sub query
|
|
364
|
+
if (expr.path || expr.type || expr.query) {
|
|
365
|
+
exit = callback( expr, exprCtx, user );
|
|
366
|
+
if (exit === traverseExpr.STOP)
|
|
367
|
+
return exit;
|
|
343
368
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
369
|
+
if (expr.path && exit !== traverseExpr.SKIP) {
|
|
370
|
+
for (const step of expr.path) {
|
|
371
|
+
if (step && (step.args || step.where || step.cardinality) &&
|
|
372
|
+
traversePathItem( step, exprCtx, user, callback ))
|
|
373
|
+
return traverseExpr.STOP;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
if (expr.args) {
|
|
347
377
|
const args = Array.isArray( expr.args ) ? expr.args : Object.values( expr.args );
|
|
348
|
-
|
|
349
|
-
|
|
378
|
+
for (const arg of args ) {
|
|
379
|
+
if (traverseExpr( arg, exprCtx, user, callback ) === traverseExpr.STOP)
|
|
380
|
+
return traverseExpr.STOP;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
if (expr.suffix) {
|
|
384
|
+
for (const arg of expr.suffix) {
|
|
385
|
+
if (traverseExpr( arg, exprCtx, user, callback ) === traverseExpr.STOP)
|
|
386
|
+
return traverseExpr.STOP;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function traversePathItem( step, exprCtx, user, callback ) {
|
|
393
|
+
const exit = callback( step, exprCtx, user );
|
|
394
|
+
if (exit === traverseExpr.STOP)
|
|
395
|
+
return true;
|
|
396
|
+
if (step.where && exit !== traverseExpr.SKIP &&
|
|
397
|
+
traverseExpr( step.where,
|
|
398
|
+
// TODO: use property in fn dictionary above
|
|
399
|
+
( exprCtx === 'calc' || exprCtx === 'calc-filter'
|
|
400
|
+
? 'calc-filter'
|
|
401
|
+
: 'filter' ),
|
|
402
|
+
step, callback ) === traverseExpr.STOP)
|
|
403
|
+
return true;
|
|
404
|
+
if (step.args) {
|
|
405
|
+
const ctx = (exprCtx === 'from') ? 'from-args' : exprCtx;
|
|
406
|
+
const args = Array.isArray( step.args ) ? step.args : Object.values( step.args );
|
|
407
|
+
// TODO: there should be no array `args` on path item
|
|
408
|
+
for (const arg of args) {
|
|
409
|
+
if (traverseExpr( arg, ctx, user, callback ) === traverseExpr.STOP)
|
|
410
|
+
return true;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Special expression traversal function for `resolveExpr`. Let's see
|
|
417
|
+
// later whether we can use this version as the general one.
|
|
418
|
+
// If we continue to have separate ones, remove the STOP stuff – it is not
|
|
419
|
+
// needed for `resolveExpr`.
|
|
420
|
+
|
|
421
|
+
function traverseTypedExpr( expr, exprCtx, user, type, callback ) {
|
|
422
|
+
if (!expr || typeof expr === 'string') // parse error or keywords in {xpr:...}
|
|
423
|
+
return null;
|
|
424
|
+
|
|
425
|
+
let { args } = expr;
|
|
426
|
+
let exit = null;
|
|
427
|
+
// `type` property for `cast, `query` for sub query
|
|
428
|
+
if (expr.path || expr.type || expr.sym || expr.query) {
|
|
429
|
+
exit = callback( expr, exprCtx, user, type );
|
|
430
|
+
if (exit === traverseExpr.STOP)
|
|
431
|
+
return exit;
|
|
432
|
+
// `args` with `cast` function
|
|
433
|
+
}
|
|
434
|
+
else if (!args) {
|
|
435
|
+
// empty on purpose
|
|
436
|
+
}
|
|
437
|
+
else if (expr.func) {
|
|
438
|
+
if (!Array.isArray( args ))
|
|
439
|
+
args = Object.values( args );
|
|
440
|
+
}
|
|
441
|
+
else if (expr.op?.val === 'list' || args.length === 1) {
|
|
442
|
+
exit = type;
|
|
443
|
+
}
|
|
444
|
+
else if (expr.op?.val === '?:') {
|
|
445
|
+
args = traverseChoiceArgs( args, exprCtx, user, type, callback );
|
|
446
|
+
exit = type;
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
args = traverseSpecialArgs( args, exprCtx, user, type, callback );
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
if (expr.path && exit !== traverseExpr.SKIP) {
|
|
453
|
+
for (const step of expr.path) {
|
|
454
|
+
if (step && (step.args || step.where || step.cardinality) &&
|
|
455
|
+
traverseTypedPathItem( step, exprCtx, user, callback ))
|
|
456
|
+
return traverseExpr.STOP;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
if (expr.args) {
|
|
460
|
+
if (!args)
|
|
461
|
+
return traverseExpr.STOP;
|
|
462
|
+
for (const arg of args) {
|
|
463
|
+
if (traverseTypedExpr( arg, exprCtx, user, exit, callback ) === traverseExpr.STOP)
|
|
464
|
+
return traverseExpr.STOP;
|
|
465
|
+
}
|
|
350
466
|
}
|
|
467
|
+
if (expr.suffix) {
|
|
468
|
+
for (const arg of expr.suffix) {
|
|
469
|
+
if (traverseTypedExpr( arg, exprCtx, user, null, callback ) === traverseExpr.STOP)
|
|
470
|
+
return traverseExpr.STOP;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return exit;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Traverse arguments `args` if they match a specific pattern:
|
|
478
|
+
*
|
|
479
|
+
* - a (sub) expression is a comparison, i.e. uses one of the binary operators
|
|
480
|
+
* `=`, `<>`, `!=`, `in` or `not in`,
|
|
481
|
+
* - one side of the comparison is a reference or a `cast` function call when
|
|
482
|
+
* typed with an enum type,
|
|
483
|
+
* - the other side is a enum reference, an enum reference in parentheses, or a
|
|
484
|
+
* list of enum references.
|
|
485
|
+
*
|
|
486
|
+
* Return an array of the arguments which are to be traversed normally, or
|
|
487
|
+
* `null` if the traversal is stopped immediately
|
|
488
|
+
*/
|
|
489
|
+
function traverseSpecialArgs( args, exprCtx, user, type, callback ) {
|
|
490
|
+
if (args.length <= 3) {
|
|
491
|
+
if (args.length === 3 && args[1].literal === 'token' &&
|
|
492
|
+
[ '=', '<>', '!=', 'in' ].includes( args[1].val ))
|
|
493
|
+
return traverseComparison( args[0], args[2], exprCtx, user, callback );
|
|
494
|
+
}
|
|
495
|
+
else if (args[0].val === 'case' && args[0].literal === 'token') {
|
|
496
|
+
return traverseCaseWhen( args, exprCtx, user, type, callback );
|
|
497
|
+
}
|
|
498
|
+
else if (args.length === 4 && args[1].val === 'not' && args[2].val === 'in' &&
|
|
499
|
+
args[1].literal === 'token' && args[2].literal === 'token') {
|
|
500
|
+
return traverseComparison( args[0], args[3], exprCtx, user, callback );
|
|
501
|
+
}
|
|
502
|
+
return args;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function traverseComparison( left, right, exprCtx, user, callback ) {
|
|
506
|
+
if (!left || !right) // can happen in old parser
|
|
507
|
+
return [ left || right ];
|
|
508
|
+
if (left.path || left.type) { // ref or cast fn
|
|
509
|
+
const type = traverseTypedExpr( left, exprCtx, user, null, callback );
|
|
510
|
+
if (type === traverseExpr.STOP ||
|
|
511
|
+
traverseTypedExpr( right, exprCtx, user, type, callback ) === traverseExpr.STOP)
|
|
512
|
+
return null;
|
|
513
|
+
return [];
|
|
514
|
+
}
|
|
515
|
+
if (right.path || right.type) { // ref or cast fn
|
|
516
|
+
const type = traverseTypedExpr( right, exprCtx, user, null, callback );
|
|
517
|
+
if (type === traverseExpr.STOP ||
|
|
518
|
+
traverseTypedExpr( left, exprCtx, user, type, callback ) === traverseExpr.STOP)
|
|
519
|
+
return null;
|
|
520
|
+
return [];
|
|
521
|
+
}
|
|
522
|
+
return [ left, right ];
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// for '?:' operator, only via CDL (translates to `case…when` in CSN):
|
|
526
|
+
function traverseChoiceArgs( args, exprCtx, user, type, callback ) {
|
|
527
|
+
if (traverseTypedExpr( args[0], exprCtx, user, null, callback ) === traverseExpr.STOP)
|
|
528
|
+
return null;
|
|
529
|
+
return args.slice( 1 );
|
|
530
|
+
// TODO: adopt if we extend this to ?:?:…
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function traverseCaseWhen( args, exprCtx, user, type, callback ) {
|
|
534
|
+
let idx = 1;
|
|
535
|
+
let when = null;
|
|
536
|
+
let node = args[1];
|
|
537
|
+
// For `CASE <expr> WHEN <…> THEN <…>`
|
|
538
|
+
if (node?.val !== 'when' || node.literal !== 'token') {
|
|
539
|
+
when = traverseTypedExpr( node, exprCtx, user, null, callback );
|
|
540
|
+
if (when === traverseExpr.STOP)
|
|
541
|
+
return null;
|
|
542
|
+
++idx;
|
|
543
|
+
}
|
|
544
|
+
// Remark: no need to test `literal` in the following - ensured by CDL and CSN
|
|
545
|
+
// parser
|
|
546
|
+
while (args[idx]?.val === 'when' && ++idx < args.length) {
|
|
547
|
+
node = args[idx];
|
|
548
|
+
// be robust against corrupted sources:
|
|
549
|
+
if ((node.literal !== 'token' || ![ 'then', 'when', 'end' ].includes( node.val )) &&
|
|
550
|
+
traverseTypedExpr( args[idx++], exprCtx, user, when, callback ) === traverseExpr.STOP)
|
|
551
|
+
return null;
|
|
351
552
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
553
|
+
if (args[idx]?.val !== 'then')
|
|
554
|
+
continue;
|
|
555
|
+
node = args[++idx];
|
|
556
|
+
if (node &&
|
|
557
|
+
(node.literal !== 'token' || node.val !== 'when' && node.val !== 'end') &&
|
|
558
|
+
traverseTypedExpr( args[idx++], exprCtx, user, type, callback ) === traverseExpr.STOP)
|
|
559
|
+
return null;
|
|
560
|
+
}
|
|
561
|
+
if (args[idx]?.val === 'else') {
|
|
562
|
+
if (++idx < args.length &&
|
|
563
|
+
traverseTypedExpr( args[idx], exprCtx, user, type, callback ) === traverseExpr.STOP)
|
|
564
|
+
return null;
|
|
565
|
+
}
|
|
566
|
+
return [];
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function traverseTypedPathItem( step, exprCtx, user, callback ) {
|
|
570
|
+
const exit = callback( step, exprCtx, user, null );
|
|
571
|
+
if (exit === traverseExpr.STOP)
|
|
572
|
+
return true;
|
|
573
|
+
if (step.where && exit !== traverseExpr.SKIP &&
|
|
574
|
+
traverseTypedExpr( step.where,
|
|
575
|
+
// TODO: use property in fn dictionary above
|
|
576
|
+
( exprCtx === 'calc' || exprCtx === 'calc-filter'
|
|
577
|
+
? 'calc-filter'
|
|
578
|
+
: 'filter' ),
|
|
579
|
+
step, null, callback ) === traverseExpr.STOP)
|
|
580
|
+
return true;
|
|
581
|
+
if (step.args) {
|
|
582
|
+
const ctx = (exprCtx === 'from') ? 'from-args' : exprCtx;
|
|
583
|
+
const args = Array.isArray( step.args ) ? step.args : Object.values( step.args );
|
|
584
|
+
// TODO: there should be no array `args` on path item
|
|
585
|
+
for (const arg of args) {
|
|
586
|
+
if (traverseTypedExpr( arg, ctx, user, arg.name, callback ) === traverseExpr.STOP)
|
|
587
|
+
return true;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return false;
|
|
355
591
|
}
|
|
356
592
|
|
|
357
593
|
// Return absolute name for unchecked path `ref`. We first try searching for
|
|
@@ -740,7 +976,8 @@ function fns( model ) {
|
|
|
740
976
|
case '$tableAlias': {
|
|
741
977
|
// use a source element having that name if in `extend … with columns`:
|
|
742
978
|
const { $extended } = user._user || user;
|
|
743
|
-
|
|
979
|
+
// if query source has duplicates, table alias has no elements
|
|
980
|
+
const elem = $extended && art.elements?.[head.id];
|
|
744
981
|
if (elem) {
|
|
745
982
|
path.$prefix = art.name.id; // prepend alias name
|
|
746
983
|
info( 'ref-special-in-extend', [ head.location, user ],
|
|
@@ -748,7 +985,7 @@ function fns( model ) {
|
|
|
748
985
|
setLink( head, '_navigation', elem );
|
|
749
986
|
return setArtifactLink( head, elem._origin );
|
|
750
987
|
}
|
|
751
|
-
else if ($extended) {
|
|
988
|
+
else if ($extended && art.elements) {
|
|
752
989
|
warning( 'ref-deprecated-in-extend', [ head.location, user ], { id: head.id },
|
|
753
990
|
// eslint-disable-next-line @stylistic/js/max-len
|
|
754
991
|
'In an added column, do not use the table alias $(ID) to refer to source elements' );
|
|
@@ -783,7 +1020,7 @@ function fns( model ) {
|
|
|
783
1020
|
}
|
|
784
1021
|
case 'builtin': {
|
|
785
1022
|
if (art.name.id === '$at') {
|
|
786
|
-
|
|
1023
|
+
message( 'ref-deprecated-variable', [ head.location, user ],
|
|
787
1024
|
{ code: '$at', newcode: '$valid' },
|
|
788
1025
|
'$(CODE) is deprecated; use $(NEWCODE) instead' );
|
|
789
1026
|
}
|
|
@@ -1394,21 +1631,30 @@ function fns( model ) {
|
|
|
1394
1631
|
const { path, scope } = ref;
|
|
1395
1632
|
// see getPathItem(): how many path items are for the main artifact ref?
|
|
1396
1633
|
const artItemsCount = (typeof scope === 'number' && scope) || (scope ? 1 : path.length);
|
|
1634
|
+
|
|
1397
1635
|
// at least the last main definition should be an entity or an
|
|
1398
|
-
// event (if the user is
|
|
1636
|
+
// event (if the user is an event) or type (if the user is a type)
|
|
1399
1637
|
// an additional check for target would need effectiveType()
|
|
1400
1638
|
const source = path[artItemsCount - 1]._artifact;
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1639
|
+
if (user._main?.kind === 'type') {
|
|
1640
|
+
if (!acceptTypeProjectionSource( source )) {
|
|
1641
|
+
signalNotFound( 'ref-invalid-source', [ ref.location, user ], null,
|
|
1642
|
+
{ '#': 'type' } );
|
|
1643
|
+
return (source === art) ? art : false; // art to show cyclic issues
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
else if (source.kind !== 'entity' &&
|
|
1647
|
+
!acceptEventProjectionSource( source, user )) {
|
|
1648
|
+
signalNotFound( 'ref-invalid-source', [ ref.location, user ], null,
|
|
1649
|
+
{ '#': user._main.kind } );
|
|
1650
|
+
return (source === art) ? art : false; // art to show cyclic issues
|
|
1405
1651
|
}
|
|
1406
1652
|
if (source === art)
|
|
1407
1653
|
return art;
|
|
1408
1654
|
const assoc = Functions.effectiveType( art );
|
|
1409
1655
|
if (assoc.target)
|
|
1410
1656
|
return art; // TODO: use target here
|
|
1411
|
-
signalNotFound( 'ref-invalid-source', [ ref.location, user ], null, { '#':
|
|
1657
|
+
signalNotFound( 'ref-invalid-source', [ ref.location, user ], null, { '#': user._main.kind } );
|
|
1412
1658
|
return false;
|
|
1413
1659
|
}
|
|
1414
1660
|
|
|
@@ -1422,6 +1668,12 @@ function fns( model ) {
|
|
|
1422
1668
|
return (kind === 'entity' || kind === 'event' || (kind === 'type' && effectiveType.elements));
|
|
1423
1669
|
}
|
|
1424
1670
|
|
|
1671
|
+
function acceptTypeProjectionSource( source ) {
|
|
1672
|
+
// We require the projection source to be structured.
|
|
1673
|
+
// TODO: Also allow all associations?
|
|
1674
|
+
return Functions.effectiveType( source )?.elements;
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1425
1677
|
function acceptTypeOrElement( art, user, ref ) { // for type
|
|
1426
1678
|
// was ['action', 'function'].includes( user._parent?.kind ))
|
|
1427
1679
|
while (user._outer)
|
|
@@ -1475,8 +1727,10 @@ function fns( model ) {
|
|
|
1475
1727
|
const semantics = (typeof s === 'string') ? referenceSemantics[s] : s;
|
|
1476
1728
|
const checkFn = semantics.check; // || !semantics.isMainRef && checkElementStd;
|
|
1477
1729
|
|
|
1478
|
-
if (checkFn)
|
|
1479
|
-
traverseExpr( expr, exprCtx, user,
|
|
1730
|
+
if (checkFn) {
|
|
1731
|
+
traverseExpr( expr, exprCtx, user,
|
|
1732
|
+
( ...args ) => (checkFn( ...args ) ? traverseExpr.STOP : traverseExpr.SKIP) );
|
|
1733
|
+
}
|
|
1480
1734
|
}
|
|
1481
1735
|
|
|
1482
1736
|
// TODO: Don't allow path args and filter!
|
|
@@ -43,6 +43,7 @@ function tweakAssocs( model ) {
|
|
|
43
43
|
mergeSpecifiedForeignKeys,
|
|
44
44
|
navigationEnv,
|
|
45
45
|
redirectionChain,
|
|
46
|
+
resolveExprInAnnotations,
|
|
46
47
|
} = model.$functions;
|
|
47
48
|
|
|
48
49
|
Object.assign(model.$functions, {
|
|
@@ -245,8 +246,13 @@ function tweakAssocs( model ) {
|
|
|
245
246
|
doRewriteAssociation( element );
|
|
246
247
|
if (element.target) {
|
|
247
248
|
extendForeignKeys( element );
|
|
248
|
-
if (element.foreignKeys$)
|
|
249
|
+
if (element.foreignKeys$) {
|
|
250
|
+
// TODO: Also checkSpecifiedElement?
|
|
249
251
|
mergeSpecifiedForeignKeys( element );
|
|
252
|
+
}
|
|
253
|
+
for (const key in element.foreignKeys)
|
|
254
|
+
// TODO: This will re-evaluate all annotations
|
|
255
|
+
resolveExprInAnnotations( element.foreignKeys[key] );
|
|
250
256
|
}
|
|
251
257
|
}
|
|
252
258
|
|
|
@@ -435,7 +441,10 @@ function tweakAssocs( model ) {
|
|
|
435
441
|
if (!nav.tableAlias || nav.tableAlias.path) {
|
|
436
442
|
const navEnv = followNavigationPath( elem.value?.path, nav ) || nav.tableAlias;
|
|
437
443
|
traverseExpr( elem.on, 'rewrite-on', elem,
|
|
438
|
-
|
|
444
|
+
( expr ) => {
|
|
445
|
+
rewriteExpr( expr, elem, nav.tableAlias, navEnv );
|
|
446
|
+
return traverseExpr.SKIP; // TODO: really necessary?
|
|
447
|
+
} );
|
|
439
448
|
}
|
|
440
449
|
else if (elem._columnParent) {
|
|
441
450
|
error( 'rewrite-not-supported', [ elem.target.location, elem ], { '#': 'inline-expand' } );
|
|
@@ -461,6 +470,7 @@ function tweakAssocs( model ) {
|
|
|
461
470
|
function removeArtifactLinks() {
|
|
462
471
|
traverseExpr( elem.on, 'rewrite-on', elem, (expr) => {
|
|
463
472
|
setArtifactLink( expr, null );
|
|
473
|
+
return traverseExpr.SKIP; // TODO: necessary?
|
|
464
474
|
} );
|
|
465
475
|
}
|
|
466
476
|
}
|
|
@@ -556,11 +566,11 @@ function tweakAssocs( model ) {
|
|
|
556
566
|
const navEnv = nav && followNavigationPath( elem.value?.path, nav ) || nav?.tableAlias;
|
|
557
567
|
traverseExpr( cond, 'rewrite-filter', elem, (expr) => {
|
|
558
568
|
if (!expr.path || expr.path.length === 0)
|
|
559
|
-
return;
|
|
569
|
+
return traverseExpr.SKIP;
|
|
560
570
|
|
|
561
571
|
const root = expr.path[0]._navigation || expr.path[0]._artifact;
|
|
562
572
|
if (!root)
|
|
563
|
-
return; // only for compile error, e.g. missing definition
|
|
573
|
+
return traverseExpr.SKIP; // only for compile error, e.g. missing definition
|
|
564
574
|
if (root.kind === '$self') {
|
|
565
575
|
// $projection -> $self for recompilability
|
|
566
576
|
expr.path[0].id = '$self';
|
|
@@ -579,6 +589,7 @@ function tweakAssocs( model ) {
|
|
|
579
589
|
// up to here, filter is relative to original association
|
|
580
590
|
rewriteExpr( expr, elem, nav?.tableAlias, navEnv );
|
|
581
591
|
}
|
|
592
|
+
return traverseExpr.SKIP;
|
|
582
593
|
} );
|
|
583
594
|
|
|
584
595
|
checkOnCondition( cond, 'on', elem );
|
|
@@ -160,10 +160,10 @@ class AnnoRewriteConfig {
|
|
|
160
160
|
target;
|
|
161
161
|
targetRoot;
|
|
162
162
|
origin;
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
163
|
+
fromTargetType;
|
|
164
|
+
fromCalcElement;
|
|
165
|
+
expandedRoot;
|
|
166
|
+
expandedRootType;
|
|
167
167
|
isInFilter;
|
|
168
168
|
tokenExpr;
|
|
169
169
|
}
|
|
@@ -217,21 +217,26 @@ function xprRewriteFns( model ) {
|
|
|
217
217
|
if (!anno.kind || anno.$invalidPaths)
|
|
218
218
|
return;
|
|
219
219
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const
|
|
220
|
+
// Annotation comes from the target's type. That's important to know, because
|
|
221
|
+
// 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
|
|
224
|
+
// for `calcString: String = str;`. We also need to adapt path prefixes.
|
|
225
|
+
const fromCalcElement = !fromTargetType && target.$calcDepElement &&
|
|
223
226
|
target.value?._artifact === origin;
|
|
224
227
|
|
|
228
|
+
const { expandedRoot, expandedRootType } = !fromTargetType && getExpandRoot( target ) || {};
|
|
229
|
+
|
|
225
230
|
const config = {
|
|
226
231
|
__proto__: AnnoRewriteConfig.prototype,
|
|
227
232
|
anno: annoName,
|
|
228
233
|
target,
|
|
229
234
|
targetRoot: annoRootArt( target ),
|
|
230
235
|
origin,
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
236
|
+
fromTargetType,
|
|
237
|
+
fromCalcElement,
|
|
238
|
+
expandedRoot,
|
|
239
|
+
expandedRootType,
|
|
235
240
|
};
|
|
236
241
|
|
|
237
242
|
const hasError = rewriteAnnotationExpr( target[annoName], config );
|
|
@@ -259,10 +264,10 @@ function xprRewriteFns( model ) {
|
|
|
259
264
|
else if (expr.$tokenTexts) {
|
|
260
265
|
// used to set `$tokenText` to true in case of rewritten annotation
|
|
261
266
|
config.tokenExpr = expr;
|
|
262
|
-
return
|
|
267
|
+
return traverseExpr.STOP === traverseExpr(
|
|
263
268
|
expr, 'annoRewrite', config.target,
|
|
264
|
-
|
|
265
|
-
|
|
269
|
+
// eslint-disable-next-line @stylistic/js/max-len, @stylistic/js/function-paren-newline
|
|
270
|
+
(e, refCtx) => (rewriteAnnoExpr( e, config, refCtx ) ? traverseExpr.STOP : traverseExpr.SKIP) );
|
|
266
271
|
}
|
|
267
272
|
return false;
|
|
268
273
|
}
|
|
@@ -285,15 +290,22 @@ function xprRewriteFns( model ) {
|
|
|
285
290
|
root._parent.kind !== 'function'))
|
|
286
291
|
return reportAnnoRewriteError( expr, config, 'unsupported' );
|
|
287
292
|
|
|
293
|
+
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.
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
|
|
288
300
|
// magic variables / replacement variables are never rewritten; they can't
|
|
289
301
|
// have filters nor can they point to elements.
|
|
290
302
|
if (expr._artifact?.kind === 'builtin')
|
|
291
303
|
return null;
|
|
292
304
|
|
|
293
305
|
let hasError = false;
|
|
294
|
-
if (config.
|
|
306
|
+
if (config.fromTargetType || config.fromCalcElement)
|
|
295
307
|
hasError = adaptPathPrefixViaType( expr, config );
|
|
296
|
-
else if (config.
|
|
308
|
+
else if (config.expandedRoot)
|
|
297
309
|
hasError = adaptPathPrefixViaTypeExpansion( expr, config );
|
|
298
310
|
|
|
299
311
|
hasError ||= rewriteGenericAnnoPath( expr, config, refCtx );
|
|
@@ -309,8 +321,11 @@ function xprRewriteFns( model ) {
|
|
|
309
321
|
const assocTarget = step._artifact.target._artifact;
|
|
310
322
|
if (target) {
|
|
311
323
|
const filterConfig = { ...config, target: assocTarget, isInFilter: true };
|
|
312
|
-
if (traverseExpr
|
|
313
|
-
|
|
324
|
+
if (traverseExpr.STOP === traverseExpr(
|
|
325
|
+
step.where, 'filter', step,
|
|
326
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
327
|
+
(e, ctx) => expr.path && (rewriteGenericAnnoPath( e, filterConfig, ctx ) ? traverseExpr.STOP : traverseExpr.SKIP)
|
|
328
|
+
))
|
|
314
329
|
return true;
|
|
315
330
|
}
|
|
316
331
|
else {
|
|
@@ -490,7 +505,7 @@ function xprRewriteFns( model ) {
|
|
|
490
505
|
config.tokenExpr.$tokenTexts = true;
|
|
491
506
|
|
|
492
507
|
const wasAbsolute = isAnnoPathAbsolute( expr );
|
|
493
|
-
|
|
508
|
+
stripAbsolutePathPrefix( expr, origin );
|
|
494
509
|
|
|
495
510
|
if (wasAbsolute) {
|
|
496
511
|
prependRootPath( origin, target, expr );
|
|
@@ -519,28 +534,28 @@ function xprRewriteFns( model ) {
|
|
|
519
534
|
return false;
|
|
520
535
|
}
|
|
521
536
|
|
|
522
|
-
// $self-paths via type expansion always need to be rewritten.
|
|
523
|
-
config.tokenExpr.$tokenTexts = true;
|
|
524
|
-
|
|
525
|
-
const { target } = config;
|
|
526
537
|
// We reject $self-paths because they need to be rewritten.
|
|
527
538
|
// However, with a special flag, we allow rewriting it for testing purposes.
|
|
528
539
|
if (!isBetaEnabled( model.options, 'rewriteAnnotationExpressionsViaType' ))
|
|
529
540
|
return reportAnnoRewriteError( expr, config, 'unsupported' );
|
|
530
541
|
|
|
531
|
-
if (rejectOuterReference( expr, config.
|
|
542
|
+
if (rejectOuterReference( expr, config.expandedRootType, config ))
|
|
532
543
|
return true;
|
|
533
544
|
|
|
534
|
-
|
|
535
|
-
prependRootPath( config.
|
|
536
|
-
setExpandStatusAnnotate( target, 'annotate' );
|
|
545
|
+
stripAbsolutePathPrefix( expr, config.expandedRootType );
|
|
546
|
+
prependRootPath( config.expandedRootType, config.expandedRoot, expr );
|
|
547
|
+
setExpandStatusAnnotate( config.target, 'annotate' );
|
|
548
|
+
|
|
537
549
|
config.target[config.anno].$inferred = 'anno-rewrite';
|
|
550
|
+
// $self-paths via type expansion always need to be rewritten.
|
|
551
|
+
config.tokenExpr.$tokenTexts = true;
|
|
552
|
+
|
|
538
553
|
return false;
|
|
539
554
|
}
|
|
540
555
|
|
|
541
556
|
/**
|
|
542
557
|
* Prepend a path to `expr.path` or replace the root item.
|
|
543
|
-
* The path needs to have been run through
|
|
558
|
+
* The path needs to have been run through stripPrefixToNewRoot(…)`.
|
|
544
559
|
* It is prepended if the root item is not the origin.
|
|
545
560
|
* Replaced otherwise.
|
|
546
561
|
*
|
|
@@ -559,15 +574,14 @@ function xprRewriteFns( model ) {
|
|
|
559
574
|
|
|
560
575
|
/**
|
|
561
576
|
* Strips a prefix path from `expr.path`. The prefix is defined
|
|
562
|
-
* by where `
|
|
577
|
+
* by where `art` appears in the path.
|
|
563
578
|
*
|
|
564
579
|
* @param {XSN.Expression} expr
|
|
565
|
-
* @param {XSN.Artifact}
|
|
566
|
-
* @param {XSN.Artifact} newRootArt
|
|
580
|
+
* @param {XSN.Artifact} art
|
|
567
581
|
*/
|
|
568
|
-
function
|
|
569
|
-
const relativeRoot = findRelativeRoot( expr,
|
|
570
|
-
if (relativeRoot === -1 && isAnnoRootArt(
|
|
582
|
+
function stripAbsolutePathPrefix( expr, art ) {
|
|
583
|
+
const relativeRoot = findRelativeRoot( expr, art );
|
|
584
|
+
if (relativeRoot === -1 && isAnnoRootArt( art ))
|
|
571
585
|
return; // no $self; root item is element
|
|
572
586
|
if (relativeRoot >= 1)
|
|
573
587
|
expr.path = expr.path.slice(relativeRoot);
|
|
@@ -614,17 +628,17 @@ function xprRewriteFns( model ) {
|
|
|
614
628
|
* Returns -1 if `origin` isn't found in the path.
|
|
615
629
|
*
|
|
616
630
|
* @param {XSN.Expression} expr
|
|
617
|
-
* @param {XSN.Artifact}
|
|
631
|
+
* @param {XSN.Artifact} origin
|
|
618
632
|
* @returns {number}
|
|
619
633
|
*/
|
|
620
|
-
function findRelativeRoot( expr,
|
|
621
|
-
if (!
|
|
622
|
-
return expr.path[0]?._artifact ===
|
|
634
|
+
function findRelativeRoot( expr, origin ) {
|
|
635
|
+
if (!origin._main) // main artifacts can't have outer references
|
|
636
|
+
return expr.path[0]?._artifact === origin ? 0 : -1;
|
|
623
637
|
|
|
624
638
|
const { path } = expr;
|
|
625
639
|
for (let i = 0; i < path.length; ++i) {
|
|
626
640
|
const item = path[i];
|
|
627
|
-
if (item._artifact ===
|
|
641
|
+
if (item._artifact === origin)
|
|
628
642
|
return i;
|
|
629
643
|
}
|
|
630
644
|
return -1;
|
|
@@ -738,21 +752,33 @@ function isReturnParam( art ) {
|
|
|
738
752
|
}
|
|
739
753
|
|
|
740
754
|
/**
|
|
741
|
-
*
|
|
755
|
+
* Gets the artifact (e.g. element) that was expanded. `target` is a sub-artifact of that root and
|
|
756
|
+
* is an expanded element.
|
|
757
|
+
*
|
|
758
|
+
* - expandedRoot: Top-most structure that was expanded.
|
|
759
|
+
* - expandedRootType: The type of expandedRoot.
|
|
742
760
|
*
|
|
743
761
|
* @param {XSN.Artifact} target
|
|
744
|
-
* @returns {
|
|
762
|
+
* @returns { {expandedRoot: XSN.Artifact, expandedRootType: XSN.Artifact}}
|
|
745
763
|
*/
|
|
746
764
|
function getExpandRoot( target ) {
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
765
|
+
if (target.$inferred !== 'expanded' && target.$inferred !== 'rewrite')
|
|
766
|
+
return { expandedRoot: null, expandedRootType: null };
|
|
767
|
+
|
|
768
|
+
let expandedRoot = target;
|
|
769
|
+
|
|
770
|
+
// 'expanded' for structures, 'rewrite' for foreign keys
|
|
771
|
+
while (expandedRoot.$inferred === 'expanded' || expandedRoot.$inferred === 'rewrite')
|
|
772
|
+
expandedRoot = expandedRoot._parent;
|
|
773
|
+
|
|
774
|
+
// `items` may be inferred via a type, hence why we check `items.type` after `type`
|
|
775
|
+
let expandedRootType = expandedRoot?.type || expandedRoot?.items?.type;
|
|
776
|
+
expandedRootType = (!expandedRootType?.$inferred && expandedRootType?._artifact) || null;
|
|
777
|
+
|
|
778
|
+
const viaInclude = expandedRoot?.$inferred === 'include';
|
|
779
|
+
expandedRoot = !viaInclude && expandedRootType ? expandedRoot : false;
|
|
780
|
+
|
|
781
|
+
return { expandedRoot, expandedRootType };
|
|
756
782
|
}
|
|
757
783
|
|
|
758
784
|
module.exports = {
|