@sap/cds-compiler 5.1.0 → 5.2.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 +2 -2
- package/bin/cdshi.js +24 -17
- package/bin/cdsse.js +17 -18
- package/lib/api/main.js +19 -2
- package/lib/api/options.js +4 -1
- package/lib/base/builtins.js +1 -0
- package/lib/base/message-registry.js +16 -3
- package/lib/base/model.js +0 -10
- package/lib/checks/actionsFunctions.js +0 -12
- package/lib/checks/structuredAnnoExpressions.js +10 -14
- package/lib/compiler/assert-consistency.js +19 -11
- package/lib/compiler/builtins.js +1 -1
- package/lib/compiler/define.js +6 -4
- package/lib/compiler/extend.js +5 -5
- package/lib/compiler/populate.js +9 -9
- package/lib/compiler/propagator.js +1 -0
- package/lib/compiler/resolve.js +29 -34
- package/lib/compiler/shared.js +7 -8
- package/lib/compiler/tweak-assocs.js +155 -64
- package/lib/compiler/utils.js +1 -1
- package/lib/compiler/xpr-rewrite.js +4 -3
- package/lib/edm/annotations/genericTranslation.js +13 -9
- package/lib/edm/csn2edm.js +26 -2
- package/lib/edm/edm.js +23 -8
- package/lib/edm/edmInboundChecks.js +5 -7
- package/lib/edm/edmPreprocessor.js +43 -30
- package/lib/gen/BaseParser.js +720 -0
- package/lib/gen/CdlParser.js +4421 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +4006 -4001
- package/lib/language/antlrParser.js +62 -0
- package/lib/language/genericAntlrParser.js +28 -0
- package/lib/model/csnUtils.js +2 -0
- package/lib/model/revealInternalProperties.js +2 -0
- package/lib/modelCompare/utils/filter.js +70 -42
- package/lib/optionProcessor.js +9 -3
- package/lib/parsers/AstBuildingParser.js +1172 -0
- package/lib/parsers/CdlGrammar.g4 +1940 -0
- package/lib/parsers/Lexer.js +239 -0
- package/lib/render/toCdl.js +23 -27
- package/lib/render/toSql.js +5 -5
- package/lib/transform/db/applyTransformations.js +54 -16
- package/lib/transform/draft/odata.js +10 -11
- package/lib/transform/effective/flattening.js +10 -14
- package/lib/transform/odata/flattening.js +42 -31
- package/lib/transform/odata/toFinalBaseType.js +7 -6
- package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
- package/package.json +2 -2
- package/share/messages/redirected-to-ambiguous.md +5 -4
|
@@ -73,7 +73,6 @@ function tweakAssocs( model ) {
|
|
|
73
73
|
// Only top-level queries and sub queries in FROM
|
|
74
74
|
|
|
75
75
|
function rewriteArtifact( art ) {
|
|
76
|
-
// return;
|
|
77
76
|
if (!art.query) {
|
|
78
77
|
rewriteAssociation( art );
|
|
79
78
|
}
|
|
@@ -333,27 +332,62 @@ function tweakAssocs( model ) {
|
|
|
333
332
|
|
|
334
333
|
// TODO: split this function: create foreign keys without `targetElement`
|
|
335
334
|
// already in Phase 2: redirectImplicitly()
|
|
336
|
-
// console.log(message( null, elem.location, elem, {art:assoc,target:assoc.target},
|
|
337
|
-
// 'Info','FK').toString())
|
|
338
335
|
elem.foreignKeys = Object.create(null); // set already here (also for zero foreign keys)
|
|
339
336
|
forEachInOrder( assoc, 'foreignKeys', ( orig, name ) => {
|
|
340
337
|
const location = weakRefLocation( elem.target );
|
|
341
338
|
const fk = linkToOrigin( orig, name, elem, 'foreignKeys', location );
|
|
342
339
|
fk.$inferred = 'rewrite'; // Override existing value; TODO: other $inferred value?
|
|
343
340
|
setLink( fk, '_effectiveType', fk );
|
|
344
|
-
|
|
345
|
-
if (elem._redirected)
|
|
346
|
-
|
|
347
|
-
const state = rewriteItem( elem, i, i.id, elem, true );
|
|
348
|
-
if (state && state !== true && te.path.length === 1)
|
|
349
|
-
setArtifactLink( te, state );
|
|
350
|
-
}
|
|
351
|
-
fk.targetElement = te;
|
|
341
|
+
fk.targetElement = copyExpr( orig.targetElement, location );
|
|
342
|
+
if (elem._redirected)
|
|
343
|
+
rewriteKey( elem, fk.targetElement );
|
|
352
344
|
} );
|
|
353
345
|
if (elem.foreignKeys) // Possibly no fk was set
|
|
354
346
|
elem.foreignKeys[$inferred] = 'rewrite';
|
|
355
347
|
}
|
|
356
348
|
|
|
349
|
+
function rewriteKey( elem, targetElement ) {
|
|
350
|
+
let projectedKey = null;
|
|
351
|
+
// rewrite along redirection chain
|
|
352
|
+
for (const alias of elem._redirected) {
|
|
353
|
+
if (alias.kind !== '$tableAlias')
|
|
354
|
+
continue;
|
|
355
|
+
|
|
356
|
+
projectedKey = firstProjectionForPath( targetElement.path, 0, alias, null );
|
|
357
|
+
if (projectedKey.elem) {
|
|
358
|
+
const item = targetElement.path[projectedKey.index];
|
|
359
|
+
item.id = projectedKey.elem.name.id;
|
|
360
|
+
if (projectedKey.index > 0)
|
|
361
|
+
targetElement.path.splice(0, projectedKey.index);
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
setArtifactLink( targetElement.path[0], null );
|
|
365
|
+
setArtifactLink( targetElement, null );
|
|
366
|
+
|
|
367
|
+
const culprit = !elem.target.$inferred && elem.target ||
|
|
368
|
+
elem.value?.path?.[elem.value.path.length - 1] ||
|
|
369
|
+
elem;
|
|
370
|
+
// TODO: probably better to collect the non-projected foreign keys
|
|
371
|
+
// and have one message for all
|
|
372
|
+
error('rewrite-undefined-key', [ weakLocation( culprit.location ), elem ], {
|
|
373
|
+
'#': 'std',
|
|
374
|
+
id: targetElement.path.map(p => p.id).join('.'),
|
|
375
|
+
target: alias._main,
|
|
376
|
+
name: elem.name.id,
|
|
377
|
+
});
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (projectedKey?.elem) {
|
|
383
|
+
const item = targetElement.path[0];
|
|
384
|
+
setArtifactLink( item, projectedKey.elem );
|
|
385
|
+
setArtifactLink( targetElement, projectedKey.elem );
|
|
386
|
+
return projectedKey.elem;
|
|
387
|
+
}
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
|
|
357
391
|
// TODO: there is no need to rewrite the on condition of non-leading queries,
|
|
358
392
|
// i.e. we could just have on = {…}
|
|
359
393
|
// TODO: re-check $self rewrite (with managed composition of aspects),
|
|
@@ -380,27 +414,35 @@ function tweakAssocs( model ) {
|
|
|
380
414
|
elem.on.$inferred = 'copy';
|
|
381
415
|
|
|
382
416
|
const { navigation } = nav;
|
|
383
|
-
if (!navigation) // TODO: what about $projection.assoc as myAssoc ?
|
|
417
|
+
if (!navigation) { // TODO: what about $projection.assoc as myAssoc ?
|
|
418
|
+
if (elem._columnParent)
|
|
419
|
+
error( 'rewrite-not-supported', [ elem.target.location, elem ], { '#': 'inline-expand' } );
|
|
384
420
|
return; // should not happen: $projection, $magic, or ref to const
|
|
385
|
-
|
|
386
|
-
// Currently, having an unmanaged association inside a struct is not
|
|
387
|
-
// supported by this function:
|
|
388
|
-
if (navigation !== assoc && navigation._origin !== assoc) { // TODO: re-check
|
|
389
|
-
// For "assoc1.assoc2" and "struct.elem1.assoc2"
|
|
390
|
-
if (elem._redirected !== null) // null = already reported
|
|
391
|
-
error( 'rewrite-not-supported', [ elem.target.location, elem ] );
|
|
392
421
|
}
|
|
393
|
-
|
|
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;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (!nav.tableAlias || nav.tableAlias.path) {
|
|
433
|
+
const navEnv = followNavigationPath( elem.value?.path, nav ) || nav.tableAlias;
|
|
394
434
|
traverseExpr( elem.on, 'rewrite-on', elem,
|
|
395
|
-
expr => rewriteExpr( expr, elem, nav.tableAlias ) );
|
|
435
|
+
expr => rewriteExpr( expr, elem, nav.tableAlias, navEnv ) );
|
|
396
436
|
}
|
|
397
|
-
else if (elem.
|
|
398
|
-
error( 'rewrite-not-supported', [ elem.target.location, elem ] );
|
|
437
|
+
else if (elem._columnParent) {
|
|
438
|
+
error( 'rewrite-not-supported', [ elem.target.location, elem ], { '#': 'inline-expand' } );
|
|
439
|
+
return;
|
|
399
440
|
}
|
|
400
441
|
else {
|
|
401
442
|
// TODO: support that, now that the ON condition is rewritten in the right order
|
|
402
443
|
error( null, [ elem.value.location, elem ], {},
|
|
403
444
|
'Selecting unmanaged associations from a sub query is not supported' );
|
|
445
|
+
return;
|
|
404
446
|
}
|
|
405
447
|
|
|
406
448
|
addConditionFromAssocPublishing( elem, assoc, nav );
|
|
@@ -528,7 +570,7 @@ function tweakAssocs( model ) {
|
|
|
528
570
|
|
|
529
571
|
// Caller must ensure ON-condition correctness via rewriteExpr()!
|
|
530
572
|
function foreignKeysToOnCondition( elem, assoc, nav ) {
|
|
531
|
-
if (model.options.testMode && !nav.tableAlias && !elem.
|
|
573
|
+
if (model.options.testMode && !nav.tableAlias && !elem._columnParent && elem.$syntax !== 'calc')
|
|
532
574
|
throw new CompilerAssertion('rewriting keys to cond: no tableAlias but not inline/calc');
|
|
533
575
|
|
|
534
576
|
if ((!nav.tableAlias && elem.$syntax !== 'calc') || elem._parent?.kind === 'element' ||
|
|
@@ -571,8 +613,11 @@ function tweakAssocs( model ) {
|
|
|
571
613
|
setLink( rhs.path[0], '_artifact', assoc );
|
|
572
614
|
setLink( rhs, '_artifact', rhs.path[rhs.path.length - 1]._artifact );
|
|
573
615
|
|
|
574
|
-
if (elem.$syntax !== 'calc') {
|
|
575
|
-
|
|
616
|
+
if (elem.$syntax !== 'calc') {
|
|
617
|
+
// Not passing an element, as we don't want to use our own filtered association here!
|
|
618
|
+
// That's done for lhs.
|
|
619
|
+
const projectedFk = firstProjectionForPath( rhs.path, 0, nav.tableAlias, null );
|
|
620
|
+
// different to lhs!
|
|
576
621
|
rewritePath( rhs, projectedFk.item, elem, projectedFk.elem, elem.value.location );
|
|
577
622
|
}
|
|
578
623
|
|
|
@@ -607,7 +652,13 @@ function tweakAssocs( model ) {
|
|
|
607
652
|
return cond;
|
|
608
653
|
}
|
|
609
654
|
|
|
610
|
-
|
|
655
|
+
/**
|
|
656
|
+
* @param expr
|
|
657
|
+
* @param assoc
|
|
658
|
+
* @param tableAlias
|
|
659
|
+
* @param navEnv Navigation element / table alias, used to traverse/rewrite the path.
|
|
660
|
+
*/
|
|
661
|
+
function rewriteExpr( expr, assoc, tableAlias, navEnv = tableAlias ) {
|
|
611
662
|
// Rewrite ON condition (resulting in outside perspective) for association
|
|
612
663
|
// 'assoc' in query or including entity from ON cond of mixin element /
|
|
613
664
|
// element in included structure / element in source ref/d by table alias.
|
|
@@ -620,7 +671,7 @@ function tweakAssocs( model ) {
|
|
|
620
671
|
return;
|
|
621
672
|
if (!assoc._main)
|
|
622
673
|
return;
|
|
623
|
-
if (
|
|
674
|
+
if (navEnv) { // from ON cond of element in source ref/d by table alias
|
|
624
675
|
const source = tableAlias._origin;
|
|
625
676
|
const root = expr.path[0]._navigation || expr.path[0]._artifact;
|
|
626
677
|
if (!root || root._main !== source)
|
|
@@ -628,7 +679,8 @@ function tweakAssocs( model ) {
|
|
|
628
679
|
if (expr.scope === 'param' || root.kind === '$parameters')
|
|
629
680
|
return; // are not allowed anyway - there was an error before
|
|
630
681
|
const startIndex = (root.kind === '$self' ? 1 : 0);
|
|
631
|
-
const
|
|
682
|
+
const exprNavigation = (root.kind === '$self' ? tableAlias : navEnv);
|
|
683
|
+
const result = firstProjectionForPath( expr.path, startIndex, exprNavigation, assoc );
|
|
632
684
|
// For `assoc[…]`, ensure that we don't rewrite to another projection on `assoc`.
|
|
633
685
|
if (result.item && assoc._origin === result.item._artifact)
|
|
634
686
|
result.elem = assoc;
|
|
@@ -647,8 +699,8 @@ function tweakAssocs( model ) {
|
|
|
647
699
|
}
|
|
648
700
|
return;
|
|
649
701
|
}
|
|
650
|
-
|
|
651
|
-
|
|
702
|
+
if (expr.path[0]._navigation) { // rewrite src elem, mixin, $self[.elem]
|
|
703
|
+
const nav = pathNavigation( expr );
|
|
652
704
|
const elem = (assoc._origin === root) ? assoc : navProjection( nav.navigation, assoc );
|
|
653
705
|
rewritePath( expr, nav.item, assoc, elem,
|
|
654
706
|
nav.item ? nav.item.location : expr.path[0].location );
|
|
@@ -691,8 +743,9 @@ function tweakAssocs( model ) {
|
|
|
691
743
|
if (!elem) {
|
|
692
744
|
if (location) {
|
|
693
745
|
const elemref = root._navigation?.kind === '$self' ? path.slice(1) : path;
|
|
746
|
+
// TODO: Fix message for sub-elements: `s: { a: Association on x=1, x: Integer};` for x
|
|
694
747
|
error( 'rewrite-not-projected', [ location, assoc ], {
|
|
695
|
-
name: assoc.name.id, art:
|
|
748
|
+
name: assoc.name.id, art: elemref[0]._artifact, elemref: { ref: elemref },
|
|
696
749
|
}, {
|
|
697
750
|
std: 'Projected association $(NAME) uses non-projected element $(ELEMREF)',
|
|
698
751
|
element: 'Projected association $(NAME) uses non-projected element $(ELEMREF) of $(ART)',
|
|
@@ -729,14 +782,11 @@ function tweakAssocs( model ) {
|
|
|
729
782
|
if (i === item)
|
|
730
783
|
state = setArtifactLink( i, elem );
|
|
731
784
|
}
|
|
732
|
-
else
|
|
733
|
-
state = rewriteItem( state, i,
|
|
785
|
+
else {
|
|
786
|
+
state = rewriteItem( state, i, assoc );
|
|
734
787
|
if (!state || state === true)
|
|
735
788
|
break;
|
|
736
789
|
}
|
|
737
|
-
else {
|
|
738
|
-
return;
|
|
739
|
-
}
|
|
740
790
|
}
|
|
741
791
|
if (state !== true)
|
|
742
792
|
setArtifactLink( ref, state );
|
|
@@ -749,9 +799,15 @@ function tweakAssocs( model ) {
|
|
|
749
799
|
path.unshift( root );
|
|
750
800
|
}
|
|
751
801
|
|
|
752
|
-
|
|
802
|
+
/**
|
|
803
|
+
* @param elem "Navigation environment" (element) for `item`.
|
|
804
|
+
* @param item Path segment to rewrite.
|
|
805
|
+
* @param assoc Published association of query.
|
|
806
|
+
*/
|
|
807
|
+
function rewriteItem( elem, item, assoc ) {
|
|
753
808
|
if (!elem._redirected)
|
|
754
809
|
return true;
|
|
810
|
+
let name = item.id;
|
|
755
811
|
for (const alias of elem._redirected) {
|
|
756
812
|
// TODO: a message for the same situation as msg 'rewrite-shadowed'?
|
|
757
813
|
if (alias.kind === '$tableAlias') { // _redirected also contains structures for includes
|
|
@@ -760,21 +816,8 @@ function tweakAssocs( model ) {
|
|
|
760
816
|
// but its origins, too.
|
|
761
817
|
const proj = navProjection( alias.elements[name], assoc );
|
|
762
818
|
name = proj?.name?.id;
|
|
763
|
-
if (!name)
|
|
764
|
-
|
|
765
|
-
break;
|
|
766
|
-
setArtifactLink( item, null );
|
|
767
|
-
const culprit = elem.target && !elem.target.$inferred && elem.target ||
|
|
768
|
-
elem.value?.path?.[elem.value.path.length - 1] ||
|
|
769
|
-
elem;
|
|
770
|
-
// TODO: probably better to collect the non-projected foreign keys
|
|
771
|
-
// and have one message for all
|
|
772
|
-
error( 'rewrite-undefined-key', [ weakLocation( culprit.location ), assoc ], {
|
|
773
|
-
'#': 'std', id: item.id, target: alias._main, name: assoc.name.id,
|
|
774
|
-
});
|
|
775
|
-
// ''
|
|
776
|
-
return null;
|
|
777
|
-
}
|
|
819
|
+
if (!name)
|
|
820
|
+
break;
|
|
778
821
|
item.id = name;
|
|
779
822
|
// TODO: Why not break here? Test test3/scenarios/AFC/db/view/consumption/C_ScopedRole.cds
|
|
780
823
|
}
|
|
@@ -802,14 +845,20 @@ function tweakAssocs( model ) {
|
|
|
802
845
|
|
|
803
846
|
function navProjection( navigation, preferred ) {
|
|
804
847
|
// TODO: Info if more than one possibility?
|
|
805
|
-
// console.log(navigation,navigation._projections)
|
|
806
848
|
if (!navigation)
|
|
807
849
|
return {};
|
|
808
|
-
|
|
850
|
+
|
|
851
|
+
if (!navigation._projections && !navigation._complexProjections)
|
|
809
852
|
return null;
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
853
|
+
|
|
854
|
+
// _complexProjections contains projections that are not "simple",
|
|
855
|
+
// i.e. contain a filter or arguments. Only used if it contains our
|
|
856
|
+
// preferred association.
|
|
857
|
+
if (preferred && ( navigation._complexProjections?.includes( preferred ) ||
|
|
858
|
+
navigation._projections?.includes( preferred )))
|
|
859
|
+
return preferred;
|
|
860
|
+
|
|
861
|
+
return navigation._projections?.[0] || null;
|
|
813
862
|
}
|
|
814
863
|
|
|
815
864
|
|
|
@@ -824,6 +873,9 @@ function navProjection( navigation, preferred ) {
|
|
|
824
873
|
* The returned object `ret` has `ret.item`, which is the path item at index `ret.index`
|
|
825
874
|
* that is projected. `ret.elem` is the element projection.
|
|
826
875
|
*
|
|
876
|
+
* If nothing was found, `ret.elem` is null, and `ret.item` is the last segment for which
|
|
877
|
+
* there was a $navElement.
|
|
878
|
+
*
|
|
827
879
|
* @param {any[]} path
|
|
828
880
|
* @param {number} startIndex
|
|
829
881
|
* @param {object} nav
|
|
@@ -844,30 +896,69 @@ function firstProjectionForPath( path, startIndex, nav, elem ) {
|
|
|
844
896
|
|
|
845
897
|
let proj = null;
|
|
846
898
|
let navItem = nav;
|
|
847
|
-
|
|
848
|
-
|
|
899
|
+
let navIndex = startIndex;
|
|
900
|
+
for (; navIndex < path.length; ++navIndex) {
|
|
901
|
+
const item = path[navIndex];
|
|
849
902
|
navItem = item?.id && navItem.elements?.[item.id];
|
|
850
903
|
if (!navItem) {
|
|
851
904
|
break;
|
|
852
905
|
}
|
|
853
|
-
else if (navItem._projections) {
|
|
906
|
+
else if (navItem._projections || navItem._complexProjections) {
|
|
854
907
|
const projElem = navProjection( navItem, elem );
|
|
855
908
|
if (projElem && projElem === elem) {
|
|
856
909
|
// in case the specified association is found, _always_ use it.
|
|
857
|
-
return { index:
|
|
910
|
+
return { index: navIndex, item, elem };
|
|
858
911
|
}
|
|
859
912
|
else if (projElem) {
|
|
860
913
|
const queryIndex = selectedElements.indexOf(projElem);
|
|
861
914
|
if (!proj || queryIndex < proj.queryIndex) {
|
|
862
915
|
proj = {
|
|
863
|
-
index:
|
|
916
|
+
index: navIndex, item, elem: projElem, queryIndex,
|
|
864
917
|
};
|
|
865
918
|
}
|
|
866
919
|
}
|
|
867
920
|
}
|
|
868
921
|
}
|
|
922
|
+
if (proj)
|
|
923
|
+
return proj;
|
|
924
|
+
|
|
925
|
+
const index = (navIndex - 1) <= startIndex ? startIndex : (navIndex - 1);
|
|
926
|
+
return { index, item: path[index], elem: null };
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
/**
|
|
930
|
+
* Follow the navigation along the given path to its N-1 path step, so
|
|
931
|
+
* that the last step can be resolved against the returned navigation like
|
|
932
|
+
* `returnValue.elements[last.id]`.
|
|
933
|
+
*
|
|
934
|
+
* @param {XSN.Path} path
|
|
935
|
+
* @param {object} nav
|
|
936
|
+
* @returns {object|null}
|
|
937
|
+
*/
|
|
938
|
+
function followNavigationPath( path, nav ) {
|
|
939
|
+
if (!nav.item || !path || path.length === 1)
|
|
940
|
+
return nav.tableAlias;
|
|
941
|
+
|
|
942
|
+
const startIndex = path.indexOf(nav.item);
|
|
943
|
+
if (startIndex === -1)
|
|
944
|
+
return null;
|
|
945
|
+
|
|
946
|
+
// navigation is already at last path step
|
|
947
|
+
if (startIndex === path.length - 1) {
|
|
948
|
+
return nav.navigation?.kind === '$navElement'
|
|
949
|
+
? nav.navigation._parent
|
|
950
|
+
: nav.tableAlias;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
let navItem = nav.navigation || nav.tableAlias;
|
|
954
|
+
for (let i = startIndex + 1; i < path.length - 1; ++i) {
|
|
955
|
+
const item = path[i];
|
|
956
|
+
navItem = item?.id && navItem.elements?.[item.id];
|
|
957
|
+
if (!navItem)
|
|
958
|
+
return null;
|
|
959
|
+
}
|
|
869
960
|
|
|
870
|
-
return
|
|
961
|
+
return navItem;
|
|
871
962
|
}
|
|
872
963
|
|
|
873
964
|
/**
|
package/lib/compiler/utils.js
CHANGED
|
@@ -607,7 +607,7 @@ function pathStartsWithSelf( ref ) {
|
|
|
607
607
|
}
|
|
608
608
|
|
|
609
609
|
function columnRefStartsWithSelf( col ) {
|
|
610
|
-
for (; col; col = col.
|
|
610
|
+
for (; col; col = col._columnParent) {
|
|
611
611
|
const ref = col.value;
|
|
612
612
|
const head = ref && !ref.scope && ref.path?.[0];
|
|
613
613
|
if (head?._navigation?.kind === '$self')
|
|
@@ -355,7 +355,7 @@ function xprRewriteFns( model ) {
|
|
|
355
355
|
// On select items, use navigation elements or table alias
|
|
356
356
|
// TODO: Expand/inline paths don't have a `_navigation` property on their last
|
|
357
357
|
// path step, yet. We need to implement expand/inline.
|
|
358
|
-
const isSimpleSelectItem = target.value?.path && target._main?.query && !target.
|
|
358
|
+
const isSimpleSelectItem = target.value?.path && target._main?.query && !target._columnParent;
|
|
359
359
|
if (isSimpleSelectItem) {
|
|
360
360
|
const isSelfPath = (expr.path[0]?._navigation?.kind === '$self');
|
|
361
361
|
if (isSelfPath) {
|
|
@@ -665,8 +665,9 @@ function xprRewriteFns( model ) {
|
|
|
665
665
|
}
|
|
666
666
|
|
|
667
667
|
const item = expr.path[index];
|
|
668
|
-
//
|
|
669
|
-
if
|
|
668
|
+
// If the artifact is already in the same definition, we must not check the query.
|
|
669
|
+
// Or if it is not a query -> no $navElement -> use `elements`
|
|
670
|
+
if (item._artifact._main === env || !env.query && env.kind !== 'select') {
|
|
670
671
|
if (env.elements?.[item.id])
|
|
671
672
|
return [ env.elements[item.id], index ];
|
|
672
673
|
return [ null, expr.path.length ];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { isEdmPropertyRendered,
|
|
3
|
+
const { isEdmPropertyRendered, transformAnnotationExpression } = require('../../model/csnUtils');
|
|
4
4
|
const { isBuiltinType, isMagicVariable } = require('../../base/builtins');
|
|
5
5
|
const edmUtils = require('../edmUtils.js');
|
|
6
6
|
const oDataDictionary = require('../../gen/Dictionary.json');
|
|
@@ -131,7 +131,7 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
|
|
|
131
131
|
const knownAnnos = filterKnownAnnotations(carrier);
|
|
132
132
|
knownAnnos.forEach((pn) => {
|
|
133
133
|
scopeCheck.anno = pn;
|
|
134
|
-
|
|
134
|
+
transformAnnotationExpression(carrier, pn, scopeCheck, carrier.$path);
|
|
135
135
|
});
|
|
136
136
|
});
|
|
137
137
|
}
|
|
@@ -141,7 +141,7 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
|
|
|
141
141
|
const knownAnnos = filterKnownAnnotations(obj);
|
|
142
142
|
knownAnnos.forEach((pn) => {
|
|
143
143
|
scopeCheck.anno = pn;
|
|
144
|
-
|
|
144
|
+
transformAnnotationExpression(obj, pn, scopeCheck, obj.$path);
|
|
145
145
|
});
|
|
146
146
|
};
|
|
147
147
|
if (def.$isParamEntity && def._origin) {
|
|
@@ -171,7 +171,7 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
|
|
|
171
171
|
if (innerAnnotation)
|
|
172
172
|
newAnno += `.@${innerAnnotation}`;
|
|
173
173
|
edmUtils.assignAnnotation(def, newAnno, def._origin[attr]);
|
|
174
|
-
|
|
174
|
+
transformAnnotationExpression(def._origin, attr, scopeCheck, def._origin.$path);
|
|
175
175
|
if (paramAnnoParts.length > 1)
|
|
176
176
|
delete def._origin[attr];
|
|
177
177
|
}
|
|
@@ -419,7 +419,7 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
|
|
|
419
419
|
|
|
420
420
|
knownAnnos.forEach((knownAnno) => {
|
|
421
421
|
if (knownAnno.search(/\.\$edmJson\./g) < 0) {
|
|
422
|
-
|
|
422
|
+
transformAnnotationExpression(carrier, knownAnno, {
|
|
423
423
|
ref: (elemref, prop, xpr, csnPath) => {
|
|
424
424
|
if (options.isV2() && elemref.$bparam) {
|
|
425
425
|
error('odata-anno-xpr-ref', ctx.location, {
|
|
@@ -842,8 +842,8 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
|
|
|
842
842
|
* */
|
|
843
843
|
let newAnno;
|
|
844
844
|
const omissions = { 'Aggregation.default': 1 };
|
|
845
|
-
const nullList = { 'Core.OperationAvailable': 1 };
|
|
846
|
-
if (annoValue
|
|
845
|
+
const nullList = { 'Core.OperationAvailable': 1, 'Core.OptionalParameter': 1 };
|
|
846
|
+
if (annoValue != null && !omissions[termName] || nullList[termName]) {
|
|
847
847
|
// termName may contain a qualifier: @UI.FieldGroup#shippingStatus
|
|
848
848
|
// -> remove qualifier from termName and set Qualifier attribute in newAnno
|
|
849
849
|
const i = termName.indexOf('#');
|
|
@@ -989,6 +989,7 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
|
|
|
989
989
|
else {
|
|
990
990
|
const res = handleSimpleValue(cAnnoValue, dTypeName, msg);
|
|
991
991
|
if (((oTermName === 'Core.OperationAvailable' && dTypeName === 'Edm.Boolean') ||
|
|
992
|
+
(oTermName === 'Core.OptionalParameter' && dTypeName === 'Edm.String') ||
|
|
992
993
|
(oTermName === 'Validation.AllowedValues' && dTypeName === 'Edm.PrimitiveType')) &&
|
|
993
994
|
cAnnoValue === null) {
|
|
994
995
|
oTarget.append(new Edm.ValueThing(v, 'Null'));
|
|
@@ -1230,7 +1231,10 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
|
|
|
1230
1231
|
}
|
|
1231
1232
|
}
|
|
1232
1233
|
else if (value === null) {
|
|
1233
|
-
if ((resolvedType == null ||
|
|
1234
|
+
if ((resolvedType == null ||
|
|
1235
|
+
resolvedType === 'Edm.PrimitiveType' ||
|
|
1236
|
+
resolvedType === 'Edm.String') &&
|
|
1237
|
+
typeName === 'String') {
|
|
1234
1238
|
resolvedType = 'Edm.String';
|
|
1235
1239
|
}
|
|
1236
1240
|
else {
|
|
@@ -1589,7 +1593,7 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
|
|
|
1589
1593
|
|
|
1590
1594
|
function filterKnownAnnotations( carrier ) {
|
|
1591
1595
|
const annoNames = Object.keys(carrier).filter( x => x[0] === '@' );
|
|
1592
|
-
const nullWhitelist = [ '@Core.OperationAvailable' ];
|
|
1596
|
+
const nullWhitelist = [ '@Core.OperationAvailable', '@Core.OptionalParameter.DefaultValue' ];
|
|
1593
1597
|
const knownAnnosP = annoNames.filter((n) => {
|
|
1594
1598
|
const tns = whatsMyTermNamespace(n);
|
|
1595
1599
|
return tns &&
|
package/lib/edm/csn2edm.js
CHANGED
|
@@ -266,8 +266,19 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
266
266
|
});
|
|
267
267
|
}
|
|
268
268
|
});
|
|
269
|
+
if (!options.odataNoCreator) {
|
|
270
|
+
// remove unqualified @Core.Links and #CAP
|
|
271
|
+
Object.keys(serviceCsn).forEach((key) => {
|
|
272
|
+
if (key === '@Core.Links' || key.startsWith('@Core.Links.') ||
|
|
273
|
+
key === '@Core.Links#CAP' || key.startsWith('@Core.Links#CAP.'))
|
|
274
|
+
delete serviceCsn[key];
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
269
278
|
// Create annotations and distribute into Schemas, merge vocabulary cross refs into xServiceRefs
|
|
270
|
-
|
|
279
|
+
addAnnotationsAndXServiceRefs();
|
|
280
|
+
if (!options.odataNoCreator)
|
|
281
|
+
LeadSchema.prepend(waterMark());
|
|
271
282
|
|
|
272
283
|
// Finally add cross service references into the EDM and extract the targetSchemaNames
|
|
273
284
|
// for the type cross check
|
|
@@ -308,6 +319,18 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
308
319
|
|
|
309
320
|
return edm;
|
|
310
321
|
|
|
322
|
+
function waterMark() {
|
|
323
|
+
const rel = new Edm.PropertyValue(v, 'rel');
|
|
324
|
+
rel._xmlOnlyAttributes.String = 'author';
|
|
325
|
+
rel._jsonOnlyAttributes['Edm.String'] = 'author';
|
|
326
|
+
const href = new Edm.PropertyValue(v, 'href');
|
|
327
|
+
href._xmlOnlyAttributes.String = 'https://cap.cloud.sap';
|
|
328
|
+
href._jsonOnlyAttributes['Edm.String'] = 'https://cap.cloud.sap';
|
|
329
|
+
const watermark = new Edm.Annotation(v, 'Core.Links', new Edm.Collection(v, new Edm.Record(v, rel, href)));
|
|
330
|
+
// watermark._edmAttributes['Qualifier'] = 'CAP';
|
|
331
|
+
return watermark;
|
|
332
|
+
}
|
|
333
|
+
|
|
311
334
|
// Sort definitions into their schema container
|
|
312
335
|
function populateSchemas( schemas ) {
|
|
313
336
|
forEach(reqDefs.definitions, ( fqName, art ) => {
|
|
@@ -1099,8 +1122,9 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
1099
1122
|
}
|
|
1100
1123
|
|
|
1101
1124
|
// generate the Edm.Annotations tree and append it to the corresponding schema
|
|
1102
|
-
function
|
|
1125
|
+
function addAnnotationsAndXServiceRefs( ) {
|
|
1103
1126
|
options.getFinalTypeInfo = csnUtils.getFinalTypeInfo;
|
|
1127
|
+
|
|
1104
1128
|
const { annos, usedVocabularies, xrefs } = translate.csn2annotationEdm(reqDefs, csnUtils, csn.vocabularies, serviceCsn.name, Edm, options, messageFunctions, mergedVocabularies);
|
|
1105
1129
|
// distribute edm:Annotations into the schemas
|
|
1106
1130
|
// Distribute each anno into Schema
|
package/lib/edm/edm.js
CHANGED
|
@@ -111,8 +111,7 @@ function getEdm( options, messageFunctions ) {
|
|
|
111
111
|
json.$Kind = this.kind;
|
|
112
112
|
|
|
113
113
|
this.toJSONattributes(json);
|
|
114
|
-
this.toJSONchildren(json);
|
|
115
|
-
return json;
|
|
114
|
+
return this.toJSONchildren(json);
|
|
116
115
|
}
|
|
117
116
|
|
|
118
117
|
// virtual
|
|
@@ -131,6 +130,7 @@ function getEdm( options, messageFunctions ) {
|
|
|
131
130
|
this._children.filter(c => c._edmAttributes.Name).forEach((c) => {
|
|
132
131
|
json[c._edmAttributes.Name] = c.toJSON();
|
|
133
132
|
});
|
|
133
|
+
return json;
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
// virtual
|
|
@@ -325,7 +325,12 @@ function getEdm( options, messageFunctions ) {
|
|
|
325
325
|
|
|
326
326
|
toJSONchildren(json) {
|
|
327
327
|
// 'edmx:DataServices' should not appear in JSON
|
|
328
|
-
|
|
328
|
+
// Annotations first
|
|
329
|
+
this._children.filter(c => c._edmAttributes.Term).forEach((c) => {
|
|
330
|
+
json = { ...json, ...c.toJSON() };
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
json = super.toJSONchildren(json);
|
|
329
334
|
if (this._annotations.length > 0) {
|
|
330
335
|
this._annotations.filter(a => a._edmAttributes.Term).forEach((a) => {
|
|
331
336
|
Object.entries(a.toJSON()).forEach(([ n, v ]) => {
|
|
@@ -445,8 +450,7 @@ function getEdm( options, messageFunctions ) {
|
|
|
445
450
|
json.$Reference = referenceJson;
|
|
446
451
|
|
|
447
452
|
this._service.toJSONattributes(json);
|
|
448
|
-
this._service.toJSONchildren(json);
|
|
449
|
-
return json;
|
|
453
|
+
return this._service.toJSONchildren(json);
|
|
450
454
|
}
|
|
451
455
|
|
|
452
456
|
// all(default), metadata, annotations
|
|
@@ -1154,8 +1158,7 @@ function getEdm( options, messageFunctions ) {
|
|
|
1154
1158
|
toJSON() {
|
|
1155
1159
|
const json = Object.create(null);
|
|
1156
1160
|
this.toJSONattributes(json);
|
|
1157
|
-
this.toJSONchildren(json);
|
|
1158
|
-
return json;
|
|
1161
|
+
return this.toJSONchildren(json);
|
|
1159
1162
|
}
|
|
1160
1163
|
|
|
1161
1164
|
getConstantExpressionValue() {
|
|
@@ -1226,6 +1229,7 @@ function getEdm( options, messageFunctions ) {
|
|
|
1226
1229
|
json[n] = v;
|
|
1227
1230
|
});
|
|
1228
1231
|
});
|
|
1232
|
+
return json;
|
|
1229
1233
|
}
|
|
1230
1234
|
}
|
|
1231
1235
|
|
|
@@ -1242,8 +1246,9 @@ function getEdm( options, messageFunctions ) {
|
|
|
1242
1246
|
// since it was discovered, that in JSON the EnumMember type must be
|
|
1243
1247
|
// transported this is no longer the case....
|
|
1244
1248
|
class Annotation extends AnnotationBase {
|
|
1245
|
-
constructor(version, termName) {
|
|
1249
|
+
constructor(version, termName, ...children) {
|
|
1246
1250
|
super(version, { Term: termName } );
|
|
1251
|
+
this.append(...children);
|
|
1247
1252
|
}
|
|
1248
1253
|
|
|
1249
1254
|
toJSON() {
|
|
@@ -1263,6 +1268,11 @@ function getEdm( options, messageFunctions ) {
|
|
|
1263
1268
|
}
|
|
1264
1269
|
|
|
1265
1270
|
class Collection extends AnnotationBase {
|
|
1271
|
+
constructor(version, ...children) {
|
|
1272
|
+
super(version);
|
|
1273
|
+
this.append(...children);
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1266
1276
|
toJSON() {
|
|
1267
1277
|
// EDM JSON doesn't mention annotations on collections
|
|
1268
1278
|
return this._children.map(a => a.toJSON());
|
|
@@ -1270,6 +1280,10 @@ function getEdm( options, messageFunctions ) {
|
|
|
1270
1280
|
}
|
|
1271
1281
|
|
|
1272
1282
|
class Record extends AnnotationBase {
|
|
1283
|
+
constructor(version, ...children) {
|
|
1284
|
+
super(version);
|
|
1285
|
+
this.append(...children);
|
|
1286
|
+
}
|
|
1273
1287
|
toJSONattributes(json) {
|
|
1274
1288
|
if (this._jsonOnlyAttributes.Type)
|
|
1275
1289
|
json['@type'] = this._jsonOnlyAttributes.Type;
|
|
@@ -1300,6 +1314,7 @@ function getEdm( options, messageFunctions ) {
|
|
|
1300
1314
|
error(null, `Pease debug me: Unhandled Record child: ${c.kind}`);
|
|
1301
1315
|
}
|
|
1302
1316
|
});
|
|
1317
|
+
return json;
|
|
1303
1318
|
}
|
|
1304
1319
|
}
|
|
1305
1320
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const { setProp, isBetaEnabled } = require('../base/model');
|
|
4
4
|
const {
|
|
5
5
|
forEachDefinition, forEachMemberRecursively, getUtils,
|
|
6
|
-
|
|
6
|
+
transformAnnotationExpression,
|
|
7
7
|
} = require('../model/csnUtils');
|
|
8
8
|
const { isBuiltinType } = require('../base/builtins');
|
|
9
9
|
const { assignAnnotation } = require('./edmUtils.js');
|
|
@@ -144,14 +144,12 @@ function inboundQualificationChecks( csn, options, messageFunctions,
|
|
|
144
144
|
parent.$bparam = true;
|
|
145
145
|
},
|
|
146
146
|
};
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
transformExpression(action, pn, markBindingParam, loc);
|
|
147
|
+
Object.keys(action).filter(pn => pn[0] === '@').forEach((pn) => {
|
|
148
|
+
transformAnnotationExpression(action, pn, markBindingParam, loc);
|
|
150
149
|
});
|
|
151
150
|
forEachMemberRecursively(action, (member, _memberName, _prop, path, _parent) => {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
transformExpression(member, pn, markBindingParam, path);
|
|
151
|
+
Object.keys(member).filter(pn => pn[0] === '@').forEach((pn) => {
|
|
152
|
+
transformAnnotationExpression(member, pn, markBindingParam, path);
|
|
155
153
|
});
|
|
156
154
|
}, loc);
|
|
157
155
|
}
|