@sap/cds-compiler 5.2.0 → 5.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 +32 -0
- package/bin/cdsc.js +5 -0
- package/bin/cdshi.js +8 -8
- package/doc/CHANGELOG_BETA.md +9 -4
- package/lib/api/validate.js +5 -0
- package/lib/base/message-registry.js +25 -1
- package/lib/base/messages.js +1 -1
- package/lib/base/model.js +0 -1
- package/lib/compiler/assert-consistency.js +2 -2
- package/lib/compiler/builtins.js +1 -1
- package/lib/compiler/checks.js +25 -6
- package/lib/compiler/define.js +24 -28
- package/lib/compiler/extend.js +11 -13
- package/lib/compiler/generate.js +3 -3
- package/lib/compiler/populate.js +13 -7
- package/lib/compiler/propagator.js +2 -2
- package/lib/compiler/resolve.js +58 -60
- package/lib/compiler/shared.js +5 -5
- package/lib/compiler/tweak-assocs.js +247 -34
- package/lib/compiler/utils.js +40 -32
- package/lib/compiler/xpr-rewrite.js +44 -58
- package/lib/edm/annotations/genericTranslation.js +4 -4
- package/lib/edm/csn2edm.js +2 -2
- package/lib/edm/edm.js +46 -21
- package/lib/edm/edmInboundChecks.js +0 -1
- package/lib/edm/edmPreprocessor.js +40 -27
- package/lib/edm/edmUtils.js +1 -1
- package/lib/gen/BaseParser.js +180 -122
- package/lib/gen/CdlParser.js +2226 -2170
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +3820 -3777
- package/lib/inspect/inspectPropagation.js +1 -1
- package/lib/json/from-csn.js +5 -3
- package/lib/json/to-csn.js +7 -10
- package/lib/language/antlrParser.js +38 -4
- package/lib/language/errorStrategy.js +1 -1
- package/lib/language/genericAntlrParser.js +4 -4
- package/lib/language/multiLineStringParser.js +1 -1
- package/lib/main.d.ts +23 -0
- package/lib/model/cloneCsn.js +22 -13
- package/lib/optionProcessor.js +7 -7
- package/lib/parsers/AstBuildingParser.js +155 -37
- package/lib/parsers/CdlGrammar.g4 +154 -81
- package/lib/parsers/Lexer.js +20 -10
- package/lib/render/toCdl.js +23 -18
- package/lib/transform/addTenantFields.js +4 -4
- package/lib/transform/forRelationalDB.js +7 -6
- package/lib/utils/moduleResolve.js +1 -1
- package/package.json +1 -1
- package/share/messages/redirected-to-complex.md +6 -3
|
@@ -41,10 +41,13 @@ function tweakAssocs( model ) {
|
|
|
41
41
|
extendForeignKeys,
|
|
42
42
|
createRemainingAnnotateStatements,
|
|
43
43
|
mergeSpecifiedForeignKeys,
|
|
44
|
+
navigationEnv,
|
|
45
|
+
redirectionChain,
|
|
44
46
|
} = model.$functions;
|
|
45
47
|
|
|
46
48
|
Object.assign(model.$functions, {
|
|
47
|
-
|
|
49
|
+
findRewriteTarget,
|
|
50
|
+
cachedRedirectionChain,
|
|
48
51
|
});
|
|
49
52
|
|
|
50
53
|
// Phase 5: rewrite associations
|
|
@@ -125,7 +128,7 @@ function tweakAssocs( model ) {
|
|
|
125
128
|
// ID published! Used in stakeholder project; if renamed, add to oldMessageIds
|
|
126
129
|
info( 'assoc-outside-service', loc, { '#': text, target, service: main._service }, {
|
|
127
130
|
std: 'Association target $(TARGET) is outside any service',
|
|
128
|
-
// eslint-disable-next-line max-len
|
|
131
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
129
132
|
exposed: 'If association is published in service $(SERVICE), its target $(TARGET) is outside any service',
|
|
130
133
|
} );
|
|
131
134
|
}
|
|
@@ -142,7 +145,7 @@ function tweakAssocs( model ) {
|
|
|
142
145
|
if (assoc && assoc.foreignKeys) {
|
|
143
146
|
error( 'rewrite-key-for-unmanaged', [ elem.on.location, elem ],
|
|
144
147
|
{ keyword: 'on', art: assocWithExplicitSpec( assoc ) },
|
|
145
|
-
// eslint-disable-next-line max-len
|
|
148
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
146
149
|
'Do not specify an $(KEYWORD) condition when redirecting the managed association $(ART)' );
|
|
147
150
|
}
|
|
148
151
|
checkIgnoredFilter( elem );
|
|
@@ -398,11 +401,18 @@ function tweakAssocs( model ) {
|
|
|
398
401
|
// same (TODO later: set status whether rewrite changes anything),
|
|
399
402
|
// especially problematic are refs starting with $self:
|
|
400
403
|
setExpandStatus( elem, 'target' );
|
|
404
|
+
|
|
405
|
+
// There were previous issues in resolving the target artifact.
|
|
406
|
+
// Avoid further compiler messages.
|
|
407
|
+
if (!elem.target._artifact)
|
|
408
|
+
return;
|
|
409
|
+
|
|
401
410
|
if (elem._parent?.kind === 'element') {
|
|
402
411
|
// managed association as sub element not supported yet
|
|
403
412
|
// TODO: Only report once for multi-include chains, see
|
|
404
413
|
// Associations/SubElements/UnmanagedInSubElement.err.cds
|
|
405
414
|
error( 'type-unsupported-rewrite', [ elem.location, elem ], { '#': 'sub-element' } );
|
|
415
|
+
removeArtifactLinks();
|
|
406
416
|
return;
|
|
407
417
|
}
|
|
408
418
|
const nav = (elem._main?.query && elem.value)
|
|
@@ -415,18 +425,11 @@ function tweakAssocs( model ) {
|
|
|
415
425
|
|
|
416
426
|
const { navigation } = nav;
|
|
417
427
|
if (!navigation) { // TODO: what about $projection.assoc as myAssoc ?
|
|
418
|
-
if (elem._columnParent)
|
|
428
|
+
if (elem._columnParent) {
|
|
419
429
|
error( 'rewrite-not-supported', [ elem.target.location, elem ], { '#': 'inline-expand' } );
|
|
420
|
-
|
|
421
|
-
}
|
|
422
|
-
const isAssocInStruct = (navigation !== assoc && navigation._origin !== assoc);
|
|
423
|
-
if (isAssocInStruct) {
|
|
424
|
-
// For "[sub.]assoc1.assoc2": not supported, yet (#3977)
|
|
425
|
-
const multipleAssoc = elem.value.path.slice(0, -1).some(segment => segment._artifact?.target);
|
|
426
|
-
if (multipleAssoc && elem._redirected !== null) { // null = already reported
|
|
427
|
-
error('rewrite-not-supported', [ elem.target.location, elem ], { '#': 'secondary' });
|
|
428
|
-
return;
|
|
430
|
+
removeArtifactLinks();
|
|
429
431
|
}
|
|
432
|
+
return; // should not happen: $projection, $magic, or ref to const
|
|
430
433
|
}
|
|
431
434
|
|
|
432
435
|
if (!nav.tableAlias || nav.tableAlias.path) {
|
|
@@ -436,17 +439,30 @@ function tweakAssocs( model ) {
|
|
|
436
439
|
}
|
|
437
440
|
else if (elem._columnParent) {
|
|
438
441
|
error( 'rewrite-not-supported', [ elem.target.location, elem ], { '#': 'inline-expand' } );
|
|
442
|
+
removeArtifactLinks();
|
|
439
443
|
return;
|
|
440
444
|
}
|
|
441
445
|
else {
|
|
442
446
|
// TODO: support that, now that the ON condition is rewritten in the right order
|
|
443
447
|
error( null, [ elem.value.location, elem ], {},
|
|
444
448
|
'Selecting unmanaged associations from a sub query is not supported' );
|
|
449
|
+
removeArtifactLinks();
|
|
445
450
|
return;
|
|
446
451
|
}
|
|
447
452
|
|
|
448
453
|
addConditionFromAssocPublishing( elem, assoc, nav );
|
|
449
454
|
elem.on.$inferred = 'rewrite';
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Clear all `_artifact` links in the ON-condition to avoid follow-up
|
|
458
|
+
* issues during ON-condition rewriting of associations that inherit
|
|
459
|
+
* the ON-condition.
|
|
460
|
+
*/
|
|
461
|
+
function removeArtifactLinks() {
|
|
462
|
+
traverseExpr( elem.on, 'rewrite-on', elem, (expr) => {
|
|
463
|
+
setArtifactLink( expr, null );
|
|
464
|
+
} );
|
|
465
|
+
}
|
|
450
466
|
}
|
|
451
467
|
|
|
452
468
|
/**
|
|
@@ -537,6 +553,7 @@ function tweakAssocs( model ) {
|
|
|
537
553
|
function filterToCondition( assocPathStep, elem, nav ) {
|
|
538
554
|
const cond = copyExpr( assocPathStep.where );
|
|
539
555
|
cond.$parens = [ assocPathStep.location ];
|
|
556
|
+
const navEnv = nav && followNavigationPath( elem.value?.path, nav ) || nav?.tableAlias;
|
|
540
557
|
traverseExpr( cond, 'rewrite-filter', elem, (expr) => {
|
|
541
558
|
if (!expr.path || expr.path.length === 0)
|
|
542
559
|
return;
|
|
@@ -560,7 +577,7 @@ function tweakAssocs( model ) {
|
|
|
560
577
|
setLink( expr.path[0], '_navigation', assocPathStep._navigation );
|
|
561
578
|
}
|
|
562
579
|
// up to here, filter is relative to original association
|
|
563
|
-
rewriteExpr( expr, elem, nav?.tableAlias );
|
|
580
|
+
rewriteExpr( expr, elem, nav?.tableAlias, navEnv );
|
|
564
581
|
}
|
|
565
582
|
} );
|
|
566
583
|
|
|
@@ -664,28 +681,21 @@ function tweakAssocs( model ) {
|
|
|
664
681
|
// element in included structure / element in source ref/d by table alias.
|
|
665
682
|
|
|
666
683
|
// TODO: complain about $self (unclear semantics)
|
|
667
|
-
// console.log( info(null, [assoc.name.location, assoc],
|
|
668
|
-
// { art: expr._artifact, names: expr.path.map(i=>i.id) }, 'A').toString(), expr.path)
|
|
669
684
|
|
|
670
685
|
if (!expr.path || !expr._artifact)
|
|
671
686
|
return;
|
|
672
687
|
if (!assoc._main)
|
|
673
688
|
return;
|
|
674
689
|
if (navEnv) { // from ON cond of element in source ref/d by table alias
|
|
675
|
-
const source = tableAlias._origin;
|
|
676
690
|
const root = expr.path[0]._navigation || expr.path[0]._artifact;
|
|
677
|
-
if (!root || root.
|
|
678
|
-
return;
|
|
691
|
+
if (!root || root.kind === 'builtin')
|
|
692
|
+
return; // not $self or source element, e.g. builtin
|
|
693
|
+
|
|
694
|
+
// parameters are not allowed in ON-conditions; error emitted elsewhere already
|
|
679
695
|
if (expr.scope === 'param' || root.kind === '$parameters')
|
|
680
|
-
return;
|
|
681
|
-
const startIndex = (root.kind === '$self' ? 1 : 0);
|
|
682
|
-
const exprNavigation = (root.kind === '$self' ? tableAlias : navEnv);
|
|
683
|
-
const result = firstProjectionForPath( expr.path, startIndex, exprNavigation, assoc );
|
|
684
|
-
// For `assoc[…]`, ensure that we don't rewrite to another projection on `assoc`.
|
|
685
|
-
if (result.item && assoc._origin === result.item._artifact)
|
|
686
|
-
result.elem = assoc;
|
|
696
|
+
return;
|
|
687
697
|
|
|
688
|
-
|
|
698
|
+
rewritePathForEnv( expr, navEnv, assoc );
|
|
689
699
|
}
|
|
690
700
|
else if (assoc._main.query) { // from ON cond of mixin element in query
|
|
691
701
|
const root = expr.path[0]._navigation || expr.path[0]._artifact;
|
|
@@ -693,7 +703,7 @@ function tweakAssocs( model ) {
|
|
|
693
703
|
if (assoc.$errorReported !== 'assoc-unexpected-scope') {
|
|
694
704
|
error( 'assoc-unexpected-scope', [ assoc.value.location, assoc ],
|
|
695
705
|
{ id: assoc.value._artifact.name.id },
|
|
696
|
-
// eslint-disable-next-line max-len
|
|
706
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
697
707
|
'Association $(ID) can\'t be projected because its ON-condition refers to a parameter' );
|
|
698
708
|
assoc.$errorReported = 'assoc-unexpected-scope';
|
|
699
709
|
}
|
|
@@ -702,6 +712,7 @@ function tweakAssocs( model ) {
|
|
|
702
712
|
if (expr.path[0]._navigation) { // rewrite src elem, mixin, $self[.elem]
|
|
703
713
|
const nav = pathNavigation( expr );
|
|
704
714
|
const elem = (assoc._origin === root) ? assoc : navProjection( nav.navigation, assoc );
|
|
715
|
+
// TODO: Use rewritePathForEnv(); make it handle mixins
|
|
705
716
|
rewritePath( expr, nav.item, assoc, elem,
|
|
706
717
|
nav.item ? nav.item.location : expr.path[0].location );
|
|
707
718
|
}
|
|
@@ -725,11 +736,11 @@ function tweakAssocs( model ) {
|
|
|
725
736
|
if (!(elem === item._artifact || // redirection for explicit def
|
|
726
737
|
elem._origin === item._artifact)) {
|
|
727
738
|
const art = assoc._origin;
|
|
728
|
-
// eslint-disable-next-line max-len
|
|
739
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
729
740
|
warning( 'rewrite-shadowed', [ elem.name.location, elem ], { art: art && effectiveType( art ) }, {
|
|
730
|
-
// eslint-disable-next-line max-len
|
|
741
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
731
742
|
std: 'This element is not originally referred to in the ON-condition of association $(ART)',
|
|
732
|
-
// eslint-disable-next-line max-len
|
|
743
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
733
744
|
element: 'This element is not originally referred to in the ON-condition of association $(MEMBER) of $(ART)',
|
|
734
745
|
} );
|
|
735
746
|
}
|
|
@@ -737,6 +748,138 @@ function tweakAssocs( model ) {
|
|
|
737
748
|
}
|
|
738
749
|
}
|
|
739
750
|
|
|
751
|
+
/**
|
|
752
|
+
* Rewrite the given reference by using projected elements of the given
|
|
753
|
+
* navigation environment.
|
|
754
|
+
*
|
|
755
|
+
* @param {XSN.Expression} ref
|
|
756
|
+
* @param {object} navEnv
|
|
757
|
+
* @param {XSN.Artifact} user
|
|
758
|
+
*/
|
|
759
|
+
function rewritePathForEnv( ref, navEnv, user ) {
|
|
760
|
+
// TODO: combine with rewriteGenericAnnoPath() of xpr-rewrite
|
|
761
|
+
|
|
762
|
+
// reset artifact link; we'll set it again if there are no errors
|
|
763
|
+
setArtifactLink( ref, null );
|
|
764
|
+
|
|
765
|
+
const rootItem = ref.path[0];
|
|
766
|
+
const root = ref.path[0]._navigation || ref.path[0]._artifact;
|
|
767
|
+
const startIndex = (root.kind === '$self' ? 1 : 0);
|
|
768
|
+
|
|
769
|
+
if (root.kind === '$self') {
|
|
770
|
+
let rootEnv = navEnv;
|
|
771
|
+
while (rootEnv?.kind === '$navElement') {
|
|
772
|
+
if (rootEnv._origin?.target?._artifact === root._origin)
|
|
773
|
+
break;
|
|
774
|
+
rootEnv = rootEnv._parent;
|
|
775
|
+
}
|
|
776
|
+
navEnv = rootEnv;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// Store the original artifact, so that we can use it to
|
|
780
|
+
// calculate a redirection chain later on.
|
|
781
|
+
ref.path.forEach((item) => {
|
|
782
|
+
if (item._artifact)
|
|
783
|
+
setLink( item, '_originalArtifact', item._artifact );
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
let env = navEnv;
|
|
787
|
+
let art = rootItem._artifact;
|
|
788
|
+
let isTargetSide = null;
|
|
789
|
+
|
|
790
|
+
for (let i = startIndex; i < ref.path.length; ++i) {
|
|
791
|
+
if (i > startIndex && art.target) {
|
|
792
|
+
// if the current artifact is an association, we need to respect the redirection
|
|
793
|
+
// chain from original target to new one.
|
|
794
|
+
// FIXME: Won't work with associations in projected structures.
|
|
795
|
+
const origTarget = ref.path[i - 1]?._originalArtifact?.target?._artifact;
|
|
796
|
+
const chain = cachedRedirectionChain( art, origTarget );
|
|
797
|
+
if (!chain) {
|
|
798
|
+
missingProjection( ref, i, user, false );
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
for (const alias of chain) {
|
|
802
|
+
art = rewritePathItemForEnv( ref, alias, i, user );
|
|
803
|
+
isTargetSide ??= (art === user);
|
|
804
|
+
if (!art) {
|
|
805
|
+
missingProjection( ref, i, user, isTargetSide );
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
art = rewritePathItemForEnv( ref, env, i, user );
|
|
812
|
+
isTargetSide ??= (art === user);
|
|
813
|
+
if (!art) {
|
|
814
|
+
missingProjection( ref, i, user, isTargetSide );
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
env = navigationEnv( art, null, null, 'nav' );
|
|
818
|
+
}
|
|
819
|
+
setArtifactLink( ref, art );
|
|
820
|
+
|
|
821
|
+
if (startIndex === 0 && rootItem.id.startsWith('$')) {
|
|
822
|
+
// TODO: What about filters? Also rewritten there?
|
|
823
|
+
// After rewriting, if an element starts with `$` -> add root prefix
|
|
824
|
+
// FIXME: "user" not correct for association inside sub-element,
|
|
825
|
+
// because `user._parent` is assumed to be the query
|
|
826
|
+
prependSelfToPath( ref.path, user );
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
function rewritePathItemForEnv( ref, navEnv, index, user ) {
|
|
831
|
+
const rewriteTarget = findRewriteTarget( ref, index, navEnv, user );
|
|
832
|
+
const found = rewriteTarget[0];
|
|
833
|
+
if (!found) {
|
|
834
|
+
setArtifactLink( ref.path[index], found );
|
|
835
|
+
return found;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
if (rewriteTarget[1] > index) {
|
|
839
|
+
// we keep the last segment, in case it has non-enumerable properties
|
|
840
|
+
ref.path[index] = ref.path[rewriteTarget[1]];
|
|
841
|
+
ref.path.splice(index + 1, rewriteTarget[1] - index);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
const item = ref.path[index];
|
|
845
|
+
if (item.id !== found.name.id || (rewriteTarget[1] - index) !== 0)
|
|
846
|
+
item.id = found.name.id;
|
|
847
|
+
|
|
848
|
+
return setArtifactLink( ref.path[index], found );
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
/**
|
|
852
|
+
* @param {XSN.Path} ref
|
|
853
|
+
* @param {number} index
|
|
854
|
+
* @param {XSN.Artifact} user
|
|
855
|
+
* @param {boolean} isTargetSide
|
|
856
|
+
*/
|
|
857
|
+
function missingProjection( ref, index, user, isTargetSide ) {
|
|
858
|
+
const item = ref.path[index];
|
|
859
|
+
if (!isTargetSide) {
|
|
860
|
+
const { location } = user.value;
|
|
861
|
+
const rootItem = ref.path[0];
|
|
862
|
+
const elemref = rootItem._navigation?.kind === '$self' ? ref.path.slice(1) : ref.path;
|
|
863
|
+
// TODO: Fix message for sub-elements: `s: { a: Association on x=1, x: Integer};` for x
|
|
864
|
+
error( 'rewrite-not-projected', [ location, user ], {
|
|
865
|
+
name: user.name.id,
|
|
866
|
+
art: item._artifact || item._originalArtifact,
|
|
867
|
+
elemref: { ref: elemref },
|
|
868
|
+
} );
|
|
869
|
+
}
|
|
870
|
+
else {
|
|
871
|
+
const isExplicit = user.target && !user.target.$inferred;
|
|
872
|
+
const loc = isExplicit ? user.target.location : item.location;
|
|
873
|
+
error( 'query-undefined-element', [ loc, user ], {
|
|
874
|
+
'#': isExplicit ? 'redirected' : 'std',
|
|
875
|
+
id: item.id,
|
|
876
|
+
name: user.name.id,
|
|
877
|
+
target: user.target._artifact,
|
|
878
|
+
keyword: 'redirected to',
|
|
879
|
+
} );
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
740
883
|
function rewritePath( ref, item, assoc, elem, location ) {
|
|
741
884
|
const { path } = ref;
|
|
742
885
|
const root = path[0];
|
|
@@ -746,9 +889,6 @@ function tweakAssocs( model ) {
|
|
|
746
889
|
// TODO: Fix message for sub-elements: `s: { a: Association on x=1, x: Integer};` for x
|
|
747
890
|
error( 'rewrite-not-projected', [ location, assoc ], {
|
|
748
891
|
name: assoc.name.id, art: elemref[0]._artifact, elemref: { ref: elemref },
|
|
749
|
-
}, {
|
|
750
|
-
std: 'Projected association $(NAME) uses non-projected element $(ELEMREF)',
|
|
751
|
-
element: 'Projected association $(NAME) uses non-projected element $(ELEMREF) of $(ART)',
|
|
752
892
|
} );
|
|
753
893
|
}
|
|
754
894
|
delete root._navigation;
|
|
@@ -841,6 +981,38 @@ function tweakAssocs( model ) {
|
|
|
841
981
|
} );
|
|
842
982
|
return null;
|
|
843
983
|
}
|
|
984
|
+
|
|
985
|
+
/**
|
|
986
|
+
* Get the redirection chain between the element's target and the original target.
|
|
987
|
+
* Returns `null` if there is no valid chain.
|
|
988
|
+
* Uses `_redirected` if valid.
|
|
989
|
+
*
|
|
990
|
+
* @param {XSN.Artifact} elem
|
|
991
|
+
* @param {XSN.Artifact} origTarget
|
|
992
|
+
* @returns {null|XSN.Artifact[]}
|
|
993
|
+
*/
|
|
994
|
+
function cachedRedirectionChain( elem, origTarget ) {
|
|
995
|
+
const target = elem.target?._artifact;
|
|
996
|
+
if (!target || !origTarget)
|
|
997
|
+
return null;
|
|
998
|
+
if (target === origTarget)
|
|
999
|
+
return [];
|
|
1000
|
+
|
|
1001
|
+
if (elem._redirected === null) {
|
|
1002
|
+
// means: "don't touch paths after assoc"
|
|
1003
|
+
// TODO: figure out if we can assume that here as well
|
|
1004
|
+
return [];
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
if (elem._redirected) {
|
|
1008
|
+
// No need to recalculate if the original target is already in '_redirected'.
|
|
1009
|
+
const i = elem._redirected.findIndex(ta => ta._origin === origTarget);
|
|
1010
|
+
if (i > -1)
|
|
1011
|
+
return elem._redirected.slice(i); // TODO: check if it is always "i===0".
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
return redirectionChain( elem, target, origTarget, true );
|
|
1015
|
+
}
|
|
844
1016
|
}
|
|
845
1017
|
|
|
846
1018
|
function navProjection( navigation, preferred ) {
|
|
@@ -861,6 +1033,46 @@ function navProjection( navigation, preferred ) {
|
|
|
861
1033
|
return navigation._projections?.[0] || null;
|
|
862
1034
|
}
|
|
863
1035
|
|
|
1036
|
+
function findRewriteTarget( expr, index, env, user ) {
|
|
1037
|
+
if (env.kind === '$navElement' || env.kind === '$tableAlias') {
|
|
1038
|
+
const r = firstProjectionForPath( expr.path, index, env, user );
|
|
1039
|
+
return [ r.elem, r.index ];
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
const item = expr.path[index];
|
|
1043
|
+
// If the artifact is already in the same definition, we must not check the query.
|
|
1044
|
+
// Or if it is not a query -> no $navElement -> use `elements`
|
|
1045
|
+
if (item._artifact?._main === env || !env.query && env.kind !== 'select') {
|
|
1046
|
+
if (env.elements?.[item.id])
|
|
1047
|
+
return [ env.elements[item.id], index ];
|
|
1048
|
+
return [ null, expr.path.length ];
|
|
1049
|
+
}
|
|
1050
|
+
const items = (env._leadingQuery || env)._combined?.[item.id];
|
|
1051
|
+
const allNavs = !items || Array.isArray(items) ? items : [ items ];
|
|
1052
|
+
|
|
1053
|
+
// If the annotation target itself has a table alias, require projections of that
|
|
1054
|
+
// table alias. Of course, that only works if we're talking about the same query.
|
|
1055
|
+
const tableAlias = (user._main?._origin === item._artifact?._main &&
|
|
1056
|
+
user.value?.path[0]?._navigation?.kind === '$tableAlias')
|
|
1057
|
+
? user.value.path[0]._navigation : null;
|
|
1058
|
+
|
|
1059
|
+
// Look at all table aliase that could project `item` and only select
|
|
1060
|
+
// those that have actual projections.
|
|
1061
|
+
const navs = allNavs?.filter(p => p._origin === item._artifact &&
|
|
1062
|
+
(!tableAlias || tableAlias === p._parent));
|
|
1063
|
+
if (!navs || navs.length === 0)
|
|
1064
|
+
return [ null, expr.path.length ];
|
|
1065
|
+
|
|
1066
|
+
// If there are multiple navigations for the element, just use the first that matches.
|
|
1067
|
+
// In case of table aliases, it's just one.
|
|
1068
|
+
for (const nav of navs) {
|
|
1069
|
+
const r = firstProjectionForPath( expr.path, index, nav._parent, user );
|
|
1070
|
+
if (r.elem)
|
|
1071
|
+
return [ r.elem, r.index ];
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
return [ null, expr.path.length ];
|
|
1075
|
+
}
|
|
864
1076
|
|
|
865
1077
|
/**
|
|
866
1078
|
* For a path `a.b.c.d`, return a projection for the first path item that is projected,
|
|
@@ -961,6 +1173,7 @@ function followNavigationPath( path, nav ) {
|
|
|
961
1173
|
return navItem;
|
|
962
1174
|
}
|
|
963
1175
|
|
|
1176
|
+
|
|
964
1177
|
/**
|
|
965
1178
|
* Return condensed info about reference in select item
|
|
966
1179
|
* - tableAlias.elem -> { navigation: navElem, item: path[1], tableAlias }
|
package/lib/compiler/utils.js
CHANGED
|
@@ -212,9 +212,11 @@ function storeExtension( elem, name, prop, parent, block ) {
|
|
|
212
212
|
/** @type {(a: any, b: any) => boolean} */
|
|
213
213
|
const testFunctionPlaceholder = () => true;
|
|
214
214
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
215
|
+
/**
|
|
216
|
+
* Return path step if the path navigates along an association whose final type
|
|
217
|
+
* satisfies function `test`; "navigates along" = last path item not considered
|
|
218
|
+
* without truthy optional argument `alsoTestLast`.
|
|
219
|
+
*/
|
|
218
220
|
function withAssociation( ref, test = testFunctionPlaceholder, alsoTestLast = false ) {
|
|
219
221
|
for (const item of ref.path || []) {
|
|
220
222
|
const art = item && item._artifact; // item can be null with parse error
|
|
@@ -435,7 +437,7 @@ function forEachJoinOn( query, from, callback ) {
|
|
|
435
437
|
}
|
|
436
438
|
|
|
437
439
|
function forEachExprArray( query, array, refContext, exprContext, callback ) {
|
|
438
|
-
for (const expr of array
|
|
440
|
+
for (const expr of array) {
|
|
439
441
|
if (expr)
|
|
440
442
|
callback( expr, (expr.path ? refContext : exprContext), query );
|
|
441
443
|
}
|
|
@@ -512,8 +514,10 @@ function traverseQueryExtra( main, callback ) {
|
|
|
512
514
|
}
|
|
513
515
|
}
|
|
514
516
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
+
/**
|
|
518
|
+
* Returns what was available at view._from[0] before:
|
|
519
|
+
* (think first whether to really use this function)
|
|
520
|
+
*/
|
|
517
521
|
function viewFromPrimary( view ) {
|
|
518
522
|
let query = view.$queries?.[0];
|
|
519
523
|
while (query?._origin?.kind === 'select') // sub query in from
|
|
@@ -521,24 +525,26 @@ function viewFromPrimary( view ) {
|
|
|
521
525
|
return dictFirst( query?.$tableAliases );
|
|
522
526
|
}
|
|
523
527
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
528
|
+
/**
|
|
529
|
+
* About Helper property $expand for faster the XSN-to-CSN transformation
|
|
530
|
+
* - null/undefined: artifact, member, items does not contain expanded members
|
|
531
|
+
* - 'origin': all expanded (sub) elements have no new target/on and no new annotations
|
|
532
|
+
* that value is only on elements, types, and params -> no other members
|
|
533
|
+
* when set, only on elem/art with expanded elements
|
|
534
|
+
* - 'target': all expanded (sub) elements might only have new target/on, but
|
|
535
|
+
* no individual annotations on any (sub) member
|
|
536
|
+
* when set, traverse all parents where the value has been 'origin' before
|
|
537
|
+
* - 'annotate': at least one inferred (sub) member has an individual annotation,
|
|
538
|
+
* not counting propagated ones; set up to the definition (main artifact)
|
|
539
|
+
* (only set with anno on $inferred elem), annotate “beats” target
|
|
540
|
+
* Usage according to CSN flavor:
|
|
541
|
+
* - gensrc: do not render inferred elements (including expanded elements),
|
|
542
|
+
* collect annotate statements with value 'annotate'
|
|
543
|
+
* - client: do not render expanded sub elements if artifact/member is no type, has a type,
|
|
544
|
+
* has $expand = 'origin', and all its _origin also have $expand = 'origin'
|
|
545
|
+
* (might sometimes render the elements unnecessarily, which is not wrong)
|
|
546
|
+
* - universal: do not render expanded sub elements if $expand = 'origin'
|
|
547
|
+
*/
|
|
542
548
|
function setExpandStatus( elem, status ) {
|
|
543
549
|
// set on element
|
|
544
550
|
while (elem._main) {
|
|
@@ -618,12 +624,14 @@ function columnRefStartsWithSelf( col ) {
|
|
|
618
624
|
return false;
|
|
619
625
|
}
|
|
620
626
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
627
|
+
/**
|
|
628
|
+
* Remark: this function is based on an early check that no target element is
|
|
629
|
+
* covered more than once by a foreign key: then…
|
|
630
|
+
* we only need to check that all foreign key references are primary keys and
|
|
631
|
+
* that the number of foreign and primary keys are the same.
|
|
632
|
+
*/
|
|
625
633
|
function isAssocToPrimaryKeys( assoc ) {
|
|
626
|
-
let
|
|
634
|
+
let keyCount = 0;
|
|
627
635
|
const { foreignKeys } = assoc;
|
|
628
636
|
if (!foreignKeys)
|
|
629
637
|
return undefined;
|
|
@@ -634,7 +642,7 @@ function isAssocToPrimaryKeys( assoc ) {
|
|
|
634
642
|
return undefined;
|
|
635
643
|
if (!elem.key?.val)
|
|
636
644
|
return false;
|
|
637
|
-
++
|
|
645
|
+
++keyCount;
|
|
638
646
|
}
|
|
639
647
|
|
|
640
648
|
const elements = assoc.target._artifact?.elements;
|
|
@@ -642,9 +650,9 @@ function isAssocToPrimaryKeys( assoc ) {
|
|
|
642
650
|
return undefined;
|
|
643
651
|
for (const name in elements) {
|
|
644
652
|
if (elements[name].key?.val)
|
|
645
|
-
--
|
|
653
|
+
--keyCount;
|
|
646
654
|
}
|
|
647
|
-
return
|
|
655
|
+
return keyCount === 0;
|
|
648
656
|
}
|
|
649
657
|
|
|
650
658
|
// only if _effectiveType has been computed:
|