@sap/cds-compiler 6.2.2 → 6.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/bin/cdsc.js +11 -4
- package/lib/api/options.js +1 -1
- package/lib/base/message-registry.js +36 -7
- package/lib/base/messages.js +11 -4
- package/lib/base/model.js +0 -1
- package/lib/checks/assocOutsideService.js +17 -30
- package/lib/checks/checkForTypes.js +0 -18
- package/lib/checks/checkPathsInStoredCalcElement.js +2 -1
- package/lib/checks/onConditions.js +2 -2
- package/lib/checks/queryNoDbArtifacts.js +16 -15
- package/lib/checks/types.js +1 -1
- package/lib/checks/utils.js +30 -6
- package/lib/checks/validator.js +4 -5
- package/lib/compiler/checks.js +47 -18
- package/lib/compiler/index.js +88 -6
- package/lib/compiler/resolve.js +7 -7
- package/lib/compiler/tweak-assocs.js +47 -25
- package/lib/gen/BaseParser.js +1 -1
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +381 -378
- package/lib/gen/Dictionary.json +0 -2
- package/lib/model/csnRefs.js +9 -4
- package/lib/model/csnUtils.js +67 -2
- package/lib/optionProcessor.js +2 -3
- package/lib/parsers/AstBuildingParser.js +5 -6
- package/lib/render/toCdl.js +10 -4
- package/lib/render/utils/common.js +4 -2
- package/lib/transform/db/assertUnique.js +2 -1
- package/lib/transform/db/associations.js +37 -1
- package/lib/transform/db/assocsToQueries/transformExists.js +21 -32
- package/lib/transform/db/assocsToQueries/utils.js +1 -1
- package/lib/transform/db/cdsPersistence.js +1 -1
- package/lib/transform/db/expansion.js +37 -36
- package/lib/transform/draft/db.js +20 -20
- package/lib/transform/draft/odata.js +38 -40
- package/lib/transform/effective/associations.js +1 -1
- package/lib/transform/effective/flattening.js +40 -47
- package/lib/transform/effective/main.js +6 -4
- package/lib/transform/forOdata.js +135 -115
- package/lib/transform/forRelationalDB.js +151 -142
- package/lib/transform/localized.js +116 -109
- package/lib/transform/odata/adaptAnnotationRefs.js +21 -16
- package/lib/transform/odata/createForeignKeys.js +73 -70
- package/lib/transform/odata/flattening.js +216 -200
- package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +47 -45
- package/lib/transform/odata/toFinalBaseType.js +40 -39
- package/lib/transform/odata/typesExposure.js +151 -133
- package/lib/transform/odata/utils.js +7 -6
- package/lib/transform/parseExpr.js +165 -162
- package/lib/transform/transformUtils.js +184 -551
- package/lib/transform/translateAssocsToJoins.js +510 -571
- package/lib/transform/tupleExpansion.js +495 -0
- package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
- package/package.json +1 -1
- package/lib/base/cleanSymbols.js +0 -17
- package/lib/checks/nonexpandableStructured.js +0 -39
package/lib/compiler/checks.js
CHANGED
|
@@ -116,13 +116,21 @@ function check( model ) {
|
|
|
116
116
|
return;
|
|
117
117
|
|
|
118
118
|
const isVirtual = parentProps.virtual?.val || elem.virtual?.val;
|
|
119
|
+
const typeName = elem._effectiveType?.name?.id;
|
|
119
120
|
if (isVirtual) {
|
|
120
121
|
error( 'def-unexpected-key', [ (parentProps.key || elem.key).location, elem ],
|
|
121
|
-
{ '#': 'virtual',
|
|
122
|
+
{ '#': 'virtual', keyword: 'key' } );
|
|
122
123
|
}
|
|
123
|
-
else if (
|
|
124
|
+
else if (typeName === 'cds.Map') {
|
|
124
125
|
error( 'def-unexpected-key', [ elem.type?.location || elem.location, elem ],
|
|
125
|
-
{ '#': 'invalidType',
|
|
126
|
+
{ '#': 'invalidType', keyword: 'key', type: typeName } );
|
|
127
|
+
}
|
|
128
|
+
else if (typeName === 'cds.LargeString' ||
|
|
129
|
+
typeName === 'cds.Vector' ||
|
|
130
|
+
typeName === 'cds.hana.CLOB' ||
|
|
131
|
+
typeName === 'cds.LargeBinary') {
|
|
132
|
+
warning( 'def-unsupported-key', [ elem.type?.location || elem.location, elem ],
|
|
133
|
+
{ '#': 'type', keyword: 'key', type: typeName } );
|
|
126
134
|
}
|
|
127
135
|
}
|
|
128
136
|
|
|
@@ -704,12 +712,15 @@ function check( model ) {
|
|
|
704
712
|
*
|
|
705
713
|
* @param {any} xpr The expression to check
|
|
706
714
|
* @param {XSN.Artifact} user User for semantic location
|
|
715
|
+
* @param {any} _parentExpr
|
|
707
716
|
* @param {string} [context] where the expression is used, e.g. 'anno'
|
|
708
717
|
*/
|
|
709
|
-
function checkGenericExpression( xpr, user, context ) {
|
|
718
|
+
function checkGenericExpression( xpr, user, _parentExpr, context ) {
|
|
710
719
|
if (context !== 'anno')
|
|
711
720
|
checkExpressionNotVirtual( xpr, user );
|
|
712
|
-
checkExpressionAssociationUsage( xpr, user,
|
|
721
|
+
checkExpressionAssociationUsage( xpr, user, {
|
|
722
|
+
context, rejectManaged: context === 'anno', rejectUnmanaged: true,
|
|
723
|
+
} );
|
|
713
724
|
if (xpr.op?.val === 'cast') {
|
|
714
725
|
requireExplicitTypeInSqlCast( xpr, user );
|
|
715
726
|
checkTypeCast( xpr, user );
|
|
@@ -730,7 +741,7 @@ function check( model ) {
|
|
|
730
741
|
|
|
731
742
|
visitExpression( elem.on, elem, (xpr, user) => {
|
|
732
743
|
checkExpressionNotVirtual( xpr, user );
|
|
733
|
-
checkExpressionAssociationUsage( xpr, user,
|
|
744
|
+
checkExpressionAssociationUsage( xpr, user, null );
|
|
734
745
|
|
|
735
746
|
if (xpr._artifact?._effectiveType?.name.id === 'cds.Map') {
|
|
736
747
|
error( 'ref-unexpected-map', [ xpr.location, user ], { '#': 'onCond', type: 'cds.Map' } );
|
|
@@ -744,7 +755,9 @@ function check( model ) {
|
|
|
744
755
|
}
|
|
745
756
|
|
|
746
757
|
function checkSelectItemValue( elem ) {
|
|
747
|
-
checkExpressionAssociationUsage( elem.value, elem,
|
|
758
|
+
checkExpressionAssociationUsage( elem.value, elem, {
|
|
759
|
+
context: 'query', rejectManaged: false, rejectUnmanaged: true,
|
|
760
|
+
} );
|
|
748
761
|
checkVirtualSelectItemChangeForV6( elem );
|
|
749
762
|
// To avoid duplicate messages, only run this check if the type wasn't inferred from
|
|
750
763
|
// the cast, as otherwise we will check it twice (once here, once via element).
|
|
@@ -753,8 +766,8 @@ function check( model ) {
|
|
|
753
766
|
checkTypeCast( elem.value, elem );
|
|
754
767
|
checkTypeArguments( elem.value, elem );
|
|
755
768
|
}
|
|
756
|
-
visitSubExpression( elem.value, elem, (xpr) => {
|
|
757
|
-
checkGenericExpression( xpr, elem );
|
|
769
|
+
visitSubExpression( elem.value, elem, (xpr, user, parentExpr) => {
|
|
770
|
+
checkGenericExpression( xpr, elem, parentExpr, 'query' );
|
|
758
771
|
} );
|
|
759
772
|
}
|
|
760
773
|
|
|
@@ -811,7 +824,8 @@ function check( model ) {
|
|
|
811
824
|
// For inferred (e.g. included) calc elements, this error is already emitted at the origin.
|
|
812
825
|
// And users can't change structured to non-structured elements.
|
|
813
826
|
if (!elem.$inferred && xpr._artifact._effectiveType?.elements) {
|
|
814
|
-
error( 'ref-unexpected-structured', [ sourceLoc, elem ],
|
|
827
|
+
error( 'ref-unexpected-structured', [ sourceLoc, elem ],
|
|
828
|
+
{ '#': 'struct-expr', elemref: xpr } );
|
|
815
829
|
}
|
|
816
830
|
else if (xpr._artifact.target !== undefined && (!lastStep.where || isStored)) {
|
|
817
831
|
// Allow using an association _with filter_, but only for on-read calculated elements.
|
|
@@ -884,10 +898,11 @@ function check( model ) {
|
|
|
884
898
|
*
|
|
885
899
|
* @param {any} xpr The expression to check
|
|
886
900
|
* @param {XSN.Artifact} user
|
|
887
|
-
* @param {
|
|
901
|
+
* @param {{context: string, rejectUnmanaged, rejectManaged}|null} [rejectAssocTail]
|
|
902
|
+
* Context where association tails are not allowed.
|
|
888
903
|
* @returns {void}
|
|
889
904
|
*/
|
|
890
|
-
function checkExpressionAssociationUsage( xpr, user,
|
|
905
|
+
function checkExpressionAssociationUsage( xpr, user, rejectAssocTail = null ) {
|
|
891
906
|
if (!xpr.args)
|
|
892
907
|
return;
|
|
893
908
|
|
|
@@ -902,12 +917,18 @@ function check( model ) {
|
|
|
902
917
|
const op = getBinaryOp( xpr );
|
|
903
918
|
for (const arg of args) {
|
|
904
919
|
if (arg && !(op?.val !== '=' && isDollarSelfOrProjectionOperand( arg )))
|
|
905
|
-
checkExpressionIsNotAssocOrSelf( arg, user,
|
|
920
|
+
checkExpressionIsNotAssocOrSelf( arg, user, rejectAssocTail );
|
|
906
921
|
}
|
|
907
922
|
}
|
|
908
923
|
}
|
|
909
924
|
|
|
910
|
-
|
|
925
|
+
/**
|
|
926
|
+
* @param arg
|
|
927
|
+
* @param {XSN.Artifact} user
|
|
928
|
+
* @param {{context: string, rejectUnmanaged, rejectManaged}|null} [rejectAssocTail]
|
|
929
|
+
* Context where association tails are not allowed.
|
|
930
|
+
*/
|
|
931
|
+
function checkExpressionIsNotAssocOrSelf( arg, user, rejectAssocTail ) {
|
|
911
932
|
// Arg must not be an association and not $self
|
|
912
933
|
// Only if path is not approved exists path (that is non-query position)
|
|
913
934
|
if (arg.path && arg.$expected !== undefined) { // not 'approved-exists'
|
|
@@ -916,9 +937,17 @@ function check( model ) {
|
|
|
916
937
|
error( 'ref-unexpected-assoc', [ arg.location, user ], { '#': variant } );
|
|
917
938
|
}
|
|
918
939
|
}
|
|
919
|
-
else if (
|
|
920
|
-
|
|
921
|
-
|
|
940
|
+
else if (rejectAssocTail && isAssociationOperand( arg )) {
|
|
941
|
+
if (rejectAssocTail.rejectManaged && rejectAssocTail.rejectUnmanaged ||
|
|
942
|
+
rejectAssocTail.rejectManaged && arg._artifact.keys ||
|
|
943
|
+
rejectAssocTail.rejectUnmanaged && arg._artifact.on) {
|
|
944
|
+
// only a few contexts have special message
|
|
945
|
+
const context = rejectAssocTail.context === 'query' && 'query-' ||
|
|
946
|
+
rejectAssocTail.context === 'anno' && 'anno-' ||
|
|
947
|
+
'';
|
|
948
|
+
const variant = isComposition( model, arg._artifact ) ? 'expr-comp' : 'expr';
|
|
949
|
+
error( 'ref-unexpected-assoc', [ arg.location, user ], { '#': `${ context }${ variant }` } );
|
|
950
|
+
}
|
|
922
951
|
}
|
|
923
952
|
}
|
|
924
953
|
|
|
@@ -1105,7 +1134,7 @@ function check( model ) {
|
|
|
1105
1134
|
*/
|
|
1106
1135
|
function checkAnnotationExpressions( anno, art ) {
|
|
1107
1136
|
if (anno.$tokenTexts) {
|
|
1108
|
-
checkGenericExpression( anno, art, 'anno' );
|
|
1137
|
+
checkGenericExpression( anno, art, null, 'anno' );
|
|
1109
1138
|
}
|
|
1110
1139
|
else if (anno.literal === 'array') {
|
|
1111
1140
|
anno.val.forEach( val => checkAnnotationExpressions( val, art ) );
|
package/lib/compiler/index.js
CHANGED
|
@@ -183,7 +183,7 @@ function compileX( filenames, dir = '', options = {}, fileCache = Object.create(
|
|
|
183
183
|
return all.then( () => {
|
|
184
184
|
options.abortSignal?.throwIfAborted();
|
|
185
185
|
moduleLayers.setLayers( input.sources );
|
|
186
|
-
return compileDoX( model );
|
|
186
|
+
return compileDoX( model ); // also async
|
|
187
187
|
} );
|
|
188
188
|
|
|
189
189
|
// Read file `filename` and parse its content, return messages
|
|
@@ -303,7 +303,7 @@ function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.cre
|
|
|
303
303
|
}
|
|
304
304
|
|
|
305
305
|
moduleLayers.setLayers( a.sources );
|
|
306
|
-
return
|
|
306
|
+
return compileDoXSync( model );
|
|
307
307
|
|
|
308
308
|
// Read file `filename` and parse its content, return messages
|
|
309
309
|
function readAndParseSync( filename, cb ) {
|
|
@@ -423,7 +423,7 @@ function compileSourcesX( sourcesDict, options = {} ) {
|
|
|
423
423
|
}
|
|
424
424
|
moduleLayers.setLayers( sources );
|
|
425
425
|
|
|
426
|
-
return
|
|
426
|
+
return compileDoXSync( model );
|
|
427
427
|
}
|
|
428
428
|
|
|
429
429
|
/**
|
|
@@ -457,8 +457,8 @@ function recompileX( csn, options ) {
|
|
|
457
457
|
|
|
458
458
|
sources[file] = parseCsn.augment( csn, file, options, model.$messageFunctions );
|
|
459
459
|
moduleLayers.setLayers( sources );
|
|
460
|
-
const compiled =
|
|
461
|
-
if (options.messages) // does not help with exception in
|
|
460
|
+
const compiled = compileDoXSync( model ); // calls throwWithError()
|
|
461
|
+
if (options.messages) // does not help with exception in compileDoXSync()
|
|
462
462
|
deduplicateMessages( options.messages ); // TODO: do better
|
|
463
463
|
return compiled;
|
|
464
464
|
}
|
|
@@ -467,10 +467,12 @@ function recompileX( csn, options ) {
|
|
|
467
467
|
* On the given model (AST like CSN) run the definer, resolver as well as semantic checks.
|
|
468
468
|
* Creates an augmented CSN (XSN) and returns it.
|
|
469
469
|
*
|
|
470
|
+
* This is the non-interruptible version of `compileDoX()` and can be used in non-`async` functions.
|
|
471
|
+
*
|
|
470
472
|
* @param {object} model AST like CSN generated e.g. by `parsers.parseCdl()`
|
|
471
473
|
* @returns {XSN.Model} Augmented CSN (XSN)
|
|
472
474
|
*/
|
|
473
|
-
function
|
|
475
|
+
function compileDoXSync( model ) {
|
|
474
476
|
const { options } = model;
|
|
475
477
|
const { throwWithError } = model.$messageFunctions;
|
|
476
478
|
if (!options.testMode)
|
|
@@ -512,6 +514,61 @@ function compileDoX( model ) {
|
|
|
512
514
|
return propagator.propagate( model );
|
|
513
515
|
}
|
|
514
516
|
|
|
517
|
+
/**
|
|
518
|
+
* On the given model (AST like CSN) run the definer, resolver as well as semantic checks.
|
|
519
|
+
* Creates an augmented CSN (XSN) and returns it.
|
|
520
|
+
*
|
|
521
|
+
* @param {object} model AST like CSN generated e.g. by `parsers.parseCdl()`
|
|
522
|
+
* @returns {XSN.Model} Augmented CSN (XSN)
|
|
523
|
+
*/
|
|
524
|
+
async function compileDoX( model ) {
|
|
525
|
+
const { options } = model;
|
|
526
|
+
const { throwWithError } = model.$messageFunctions;
|
|
527
|
+
if (!options.testMode)
|
|
528
|
+
model.meta = {}; // provide initial central meta object
|
|
529
|
+
|
|
530
|
+
checkRemovedDeprecatedFlags( options, model.$messageFunctions );
|
|
531
|
+
|
|
532
|
+
if (options.parseOnly) {
|
|
533
|
+
throwWithError();
|
|
534
|
+
return model;
|
|
535
|
+
}
|
|
536
|
+
model.$functions = {};
|
|
537
|
+
fns( model ); // attach (mostly) paths functions
|
|
538
|
+
define( model );
|
|
539
|
+
await checkAsyncAbortFlag( options.abortSignal );
|
|
540
|
+
|
|
541
|
+
// do not run the resolver in parse-cdl mode or we get duplicate annotations, etc.
|
|
542
|
+
// TODO: do not use this function for parseCdl anyway…
|
|
543
|
+
if (options.parseCdl) {
|
|
544
|
+
finalizeParseCdl( model );
|
|
545
|
+
throwWithError();
|
|
546
|
+
return model;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
for (const phase of [ extend, generate, kickStart, populate ]) {
|
|
550
|
+
phase( model );
|
|
551
|
+
// eslint-disable-next-line no-await-in-loop
|
|
552
|
+
await checkAsyncAbortFlag( options.abortSignal );
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
model.definitions = model.$functions.shuffleDict( model.definitions );
|
|
556
|
+
// Shuffling extensions is more difficult due to intra-file extensions of same artifact
|
|
557
|
+
// TODO: think about making this work
|
|
558
|
+
|
|
559
|
+
for (const phase of [ resolve, tweakAssocs, assertConsistency, check ]) {
|
|
560
|
+
phase( model );
|
|
561
|
+
// eslint-disable-next-line no-await-in-loop
|
|
562
|
+
await checkAsyncAbortFlag( options.abortSignal );
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
throwWithError();
|
|
566
|
+
if (options.lintMode)
|
|
567
|
+
return model;
|
|
568
|
+
|
|
569
|
+
return propagator.propagate( model );
|
|
570
|
+
}
|
|
571
|
+
|
|
515
572
|
/**
|
|
516
573
|
* Process an array of `filenames`. Returns an object with properties:
|
|
517
574
|
* - `sources`: dictionary which has a filename as key (value is irrelevant)
|
|
@@ -595,6 +652,31 @@ function createSourcesDict( filenames, filenameMap, dir ) {
|
|
|
595
652
|
return { sources, files, repeated };
|
|
596
653
|
}
|
|
597
654
|
|
|
655
|
+
/**
|
|
656
|
+
* An `await`able function to fake a real asynchronous event.
|
|
657
|
+
*
|
|
658
|
+
* @returns {Promise<unknown>}
|
|
659
|
+
*/
|
|
660
|
+
async function waitForNextEventLoopIteration() {
|
|
661
|
+
return new Promise( ( r ) => {
|
|
662
|
+
setTimeout( r, 0 );
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* An actual async function that uses `setTimeout()` to allow the Node event loop to
|
|
668
|
+
* start its next iteration, but only if the `abortSignal` is defined.
|
|
669
|
+
*
|
|
670
|
+
* @param {AbortSignal?} abortSignal
|
|
671
|
+
* @returns {Promise<void>}
|
|
672
|
+
*/
|
|
673
|
+
async function checkAsyncAbortFlag( abortSignal ) {
|
|
674
|
+
if (!abortSignal)
|
|
675
|
+
return;
|
|
676
|
+
await waitForNextEventLoopIteration();
|
|
677
|
+
abortSignal.throwIfAborted();
|
|
678
|
+
}
|
|
679
|
+
|
|
598
680
|
module.exports = {
|
|
599
681
|
parseX,
|
|
600
682
|
compileX,
|
package/lib/compiler/resolve.js
CHANGED
|
@@ -430,10 +430,7 @@ function resolve( model ) {
|
|
|
430
430
|
}
|
|
431
431
|
else if (!allowedInMain || !isTopLevelElement) {
|
|
432
432
|
warning( 'def-unsupported-key', [ art.key.location, art ],
|
|
433
|
-
{ '#': allowedInMain ? 'sub' : '
|
|
434
|
-
std: '$(KEYWORD) is only supported for elements in an entity or an aspect',
|
|
435
|
-
sub: '$(KEYWORD) is only supported for top-level elements',
|
|
436
|
-
} );
|
|
433
|
+
{ '#': allowedInMain ? 'sub' : 'kind', keyword: 'key' } );
|
|
437
434
|
}
|
|
438
435
|
}
|
|
439
436
|
|
|
@@ -905,10 +902,13 @@ function resolve( model ) {
|
|
|
905
902
|
}
|
|
906
903
|
else if (effectiveType( art )?.elements && !art.$inferred) {
|
|
907
904
|
// For inferred (e.g. included) calc elements, this error is already emitted at the origin.
|
|
908
|
-
if (art.type)
|
|
905
|
+
if (art.type) {
|
|
909
906
|
error( 'type-unexpected-structure', [ art.type.location, art ], { '#': 'calc' } );
|
|
910
|
-
|
|
911
|
-
|
|
907
|
+
}
|
|
908
|
+
else {
|
|
909
|
+
error( 'ref-unexpected-structured', [ art.value.location, art ],
|
|
910
|
+
{ '#': 'struct-expr', elemref: art.value } );
|
|
911
|
+
}
|
|
912
912
|
}
|
|
913
913
|
else if (effectiveType( art )?.items && !art.$inferred) {
|
|
914
914
|
// For inferred (e.g. included) calc elements, this error is already emitted at the origin.
|
|
@@ -349,42 +349,46 @@ function tweakAssocs( model ) {
|
|
|
349
349
|
setLink( fk, '_effectiveType', fk );
|
|
350
350
|
fk.targetElement = copyExpr( orig.targetElement, location );
|
|
351
351
|
if (elem._redirected)
|
|
352
|
-
rewriteKey( elem, fk
|
|
352
|
+
rewriteKey( elem, fk );
|
|
353
353
|
} );
|
|
354
354
|
if (elem.foreignKeys) // Possibly no fk was set
|
|
355
355
|
elem.foreignKeys[$inferred] = 'rewrite';
|
|
356
356
|
}
|
|
357
357
|
|
|
358
|
-
function rewriteKey( elem,
|
|
358
|
+
function rewriteKey( elem, fk ) {
|
|
359
|
+
const { targetElement } = fk;
|
|
359
360
|
let projectedKey = null;
|
|
360
361
|
// rewrite along redirection chain
|
|
361
362
|
for (const alias of elem._redirected) {
|
|
362
|
-
if (alias.kind
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
setArtifactLink( targetElement.path[0], null );
|
|
374
|
-
setArtifactLink( targetElement, null );
|
|
363
|
+
if (alias.kind === '$tableAlias') {
|
|
364
|
+
projectedKey = firstProjectionForPath( targetElement.path, 0, alias, null );
|
|
365
|
+
if (projectedKey.elem) {
|
|
366
|
+
const item = targetElement.path[projectedKey.index];
|
|
367
|
+
item.id = projectedKey.elem.name.id;
|
|
368
|
+
if (projectedKey.index > 0)
|
|
369
|
+
targetElement.path.splice(0, projectedKey.index);
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
setArtifactLink( targetElement.path[0], null );
|
|
373
|
+
setArtifactLink( targetElement, null );
|
|
375
374
|
|
|
376
|
-
|
|
375
|
+
const culprit = !elem.target.$inferred && elem.target ||
|
|
377
376
|
elem.value?.path?.[elem.value.path.length - 1] ||
|
|
378
377
|
elem;
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
378
|
+
// TODO: probably better to collect the non-projected foreign keys
|
|
379
|
+
// and have one message for all
|
|
380
|
+
error('rewrite-undefined-key', [ weakLocation( culprit.location ), elem ], {
|
|
381
|
+
'#': 'std',
|
|
382
|
+
id: targetElement.path.map(p => p.id).join('.'),
|
|
383
|
+
target: alias._main,
|
|
384
|
+
name: elem.name.id,
|
|
385
|
+
});
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
// e.g. redirection target is entity that includes original target
|
|
391
|
+
projectedKey = { elem: findTargetElement( alias, targetElement ) };
|
|
388
392
|
}
|
|
389
393
|
}
|
|
390
394
|
|
|
@@ -397,6 +401,24 @@ function tweakAssocs( model ) {
|
|
|
397
401
|
return null;
|
|
398
402
|
}
|
|
399
403
|
|
|
404
|
+
/**
|
|
405
|
+
* Find the target element in the given redirection target.
|
|
406
|
+
* Used to find the target element in entities that include the original
|
|
407
|
+
* target entity.
|
|
408
|
+
*
|
|
409
|
+
* @param redirected
|
|
410
|
+
* @param targetElement
|
|
411
|
+
* @returns {*|null}
|
|
412
|
+
*/
|
|
413
|
+
function findTargetElement( redirected, targetElement ) {
|
|
414
|
+
for (const step of targetElement.path) {
|
|
415
|
+
redirected = redirected.elements?.[step.id];
|
|
416
|
+
if (!redirected)
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
return redirected;
|
|
420
|
+
}
|
|
421
|
+
|
|
400
422
|
// TODO: there is no need to rewrite the on condition of non-leading queries,
|
|
401
423
|
// i.e. we could just have on = {…}
|
|
402
424
|
// TODO: re-check $self rewrite (with managed composition of aspects),
|
package/lib/gen/BaseParser.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
1f3bba2acb882f5120a55a01eb9a7bdc
|