@sap/cds-compiler 6.3.6 → 6.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +101 -3
- package/LICENSE +32 -0
- package/README.md +14 -2
- package/bin/cdsse.js +0 -3
- package/doc/CHANGELOG_BETA.md +1 -1
- package/doc/CHANGELOG_DEPRECATED.md +1 -1
- package/lib/base/message-registry.js +9 -2
- package/lib/base/messages.js +1 -1
- package/lib/base/model.js +2 -0
- package/lib/checks/existsExpressionsOnlyForeignKeys.js +16 -10
- package/lib/checks/existsMustEndInAssoc.js +1 -1
- package/lib/checks/existsMustNotStartWithDollarSelf.js +31 -0
- package/lib/checks/validator.js +4 -2
- package/lib/compiler/assert-consistency.js +3 -2
- package/lib/compiler/builtins.js +5 -6
- package/lib/compiler/checks.js +37 -26
- package/lib/compiler/define.js +1 -1
- package/lib/compiler/extend.js +39 -50
- package/lib/compiler/finalize-parse-cdl.js +1 -1
- package/lib/compiler/lsp-api.js +1 -1
- package/lib/compiler/populate.js +2 -2
- package/lib/compiler/propagator.js +29 -6
- package/lib/compiler/resolve.js +13 -3
- package/lib/compiler/shared.js +157 -133
- package/lib/compiler/tweak-assocs.js +87 -29
- package/lib/compiler/xpr-rewrite.js +164 -160
- package/lib/edm/annotations/edmJson.js +206 -37
- package/lib/edm/csn2edm.js +13 -0
- package/lib/edm/edmUtils.js +2 -2
- package/lib/gen/BaseParser.js +106 -72
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +1501 -1509
- package/lib/json/to-csn.js +8 -5
- package/lib/language/genericAntlrParser.js +0 -0
- package/lib/main.js +19 -16
- package/lib/model/csnRefs.js +589 -521
- package/lib/model/csnUtils.js +8 -5
- package/lib/model/enrichCsn.js +1 -0
- package/lib/parsers/AstBuildingParser.js +73 -28
- package/lib/render/toCdl.js +2 -1
- package/lib/render/toHdbcds.js +6 -3
- package/lib/render/toSql.js +5 -0
- package/lib/transform/db/applyTransformations.js +1 -1
- package/lib/transform/db/assertUnique.js +4 -1
- package/lib/transform/db/assocsToQueries/transformExists.js +3 -10
- package/lib/transform/db/assocsToQueries/utils.js +0 -5
- package/lib/transform/db/cdsPersistence.js +17 -18
- package/lib/transform/db/expansion.js +179 -3
- package/lib/transform/db/flattening.js +16 -5
- package/lib/transform/db/rewriteCalculatedElements.js +79 -283
- package/lib/transform/effective/main.js +8 -1
- package/lib/transform/forOdata.js +1 -1
- package/lib/transform/forRelationalDB.js +21 -80
- package/lib/transform/localized.js +75 -127
- package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +89 -63
- package/lib/transform/transformUtils.js +23 -21
- package/lib/transform/translateAssocsToJoins.js +7 -5
- package/lib/transform/tupleExpansion.js +16 -3
- package/package.json +3 -3
- package/doc/DeprecatedOptions_v2.md +0 -150
- package/doc/NameResolution.md +0 -837
- package/lib/transform/parseExpr.js +0 -415
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
const {
|
|
6
6
|
forEachGeneric,
|
|
7
7
|
forEachInOrder,
|
|
8
|
+
isBetaEnabled,
|
|
8
9
|
} = require('../base/model');
|
|
9
10
|
const { dictLocation, weakLocation, weakRefLocation } = require('../base/location');
|
|
10
11
|
|
|
@@ -90,7 +91,7 @@ function tweakAssocs( model ) {
|
|
|
90
91
|
|
|
91
92
|
if (art.query) {
|
|
92
93
|
traverseQueryPost(art.query, false, (query) => {
|
|
93
|
-
forEachGeneric( query, 'elements',
|
|
94
|
+
forEachGeneric( query, 'elements', handleQueryElements );
|
|
94
95
|
});
|
|
95
96
|
}
|
|
96
97
|
}
|
|
@@ -116,10 +117,10 @@ function tweakAssocs( model ) {
|
|
|
116
117
|
return;
|
|
117
118
|
const loc = [ elem.target.location, elem ];
|
|
118
119
|
const main = elem._main || elem;
|
|
119
|
-
if (!elem.$inferred && !main.$inferred) {
|
|
120
|
+
if (!elem.$inferred && !main.$inferred && !model.options.$recompile) {
|
|
120
121
|
info( 'assoc-target-not-in-service', loc,
|
|
121
122
|
{ target, '#': (elem._main.query ? 'select' : 'define') }, {
|
|
122
|
-
std: '
|
|
123
|
+
std: 'Association target $(TARGET) is outside any service', // not used
|
|
123
124
|
define: 'Target $(TARGET) of explicitly defined association is outside any service',
|
|
124
125
|
select: 'Target $(TARGET) of explicitly selected association is outside any service',
|
|
125
126
|
} );
|
|
@@ -135,6 +136,26 @@ function tweakAssocs( model ) {
|
|
|
135
136
|
}
|
|
136
137
|
}
|
|
137
138
|
|
|
139
|
+
function handleQueryElements( column ) {
|
|
140
|
+
rewriteAssociationCheck( column );
|
|
141
|
+
if (!isBetaEnabled( model.options, '$calcForDraft' ))
|
|
142
|
+
return;
|
|
143
|
+
const { value } = column; // `value` = column expression
|
|
144
|
+
if (!value || !value.args && !value.suffix)
|
|
145
|
+
return;
|
|
146
|
+
// TODO: what about non-simple refs (assocs, even with filter/args)?
|
|
147
|
+
|
|
148
|
+
// with “real” expressions, set $calc according to these
|
|
149
|
+
// (with references, $calc might be inherited from the source element)
|
|
150
|
+
column.$calc = copyExpr( column.value, null ); // copy while keeping location
|
|
151
|
+
|
|
152
|
+
if (traverseExpr.STOP ===
|
|
153
|
+
traverseExpr( column.$calc, 'rewrite-on', column,
|
|
154
|
+
ref => rewriteColumnPath( ref, column ) ))
|
|
155
|
+
column.$calc = true;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Check explicit ON / keys with REDIRECTED TO
|
|
138
159
|
function rewriteAssociationCheck( element ) {
|
|
139
160
|
const elem = element.items || element; // TODO v6: nested items
|
|
140
161
|
if (elem.elements)
|
|
@@ -436,7 +457,7 @@ function tweakAssocs( model ) {
|
|
|
436
457
|
return;
|
|
437
458
|
|
|
438
459
|
if (elem._parent?.kind === 'element') {
|
|
439
|
-
//
|
|
460
|
+
// unmanaged association as sub element not supported yet
|
|
440
461
|
// TODO: Only report once for multi-include chains, see
|
|
441
462
|
// Associations/SubElements/UnmanagedInSubElement.err.cds
|
|
442
463
|
error( 'type-unsupported-rewrite', [ elem.location, elem ], { '#': 'sub-element' } );
|
|
@@ -732,6 +753,7 @@ function tweakAssocs( model ) {
|
|
|
732
753
|
rewritePathForEnv( expr, navEnv, assoc );
|
|
733
754
|
}
|
|
734
755
|
else if (assoc._main.query) { // from ON cond of mixin element in query
|
|
756
|
+
// here also $calc
|
|
735
757
|
const root = expr.path[0]._navigation || expr.path[0]._artifact;
|
|
736
758
|
if (expr.scope === 'param' || root?.kind === '$parameters') {
|
|
737
759
|
if (assoc.$errorReported !== 'assoc-unexpected-scope') {
|
|
@@ -743,7 +765,8 @@ function tweakAssocs( model ) {
|
|
|
743
765
|
}
|
|
744
766
|
return;
|
|
745
767
|
}
|
|
746
|
-
|
|
768
|
+
|
|
769
|
+
if (expr.path[0]._navigation) { // rewrite: src elem, mixin, $self[.elem]
|
|
747
770
|
const nav = pathNavigation( expr );
|
|
748
771
|
const elem = (assoc._origin === root) ? assoc : navProjection( nav.navigation, assoc );
|
|
749
772
|
// TODO: Use rewritePathForEnv(); make it handle mixins
|
|
@@ -823,8 +846,11 @@ function tweakAssocs( model ) {
|
|
|
823
846
|
|
|
824
847
|
for (let i = startIndex; i < ref.path.length; ++i) {
|
|
825
848
|
if (i > startIndex && art.target) {
|
|
826
|
-
//
|
|
827
|
-
//
|
|
849
|
+
// TODO: Can we combine this with the code from xpr-rewrite.js
|
|
850
|
+
// If the current artifact is an association, we need to respect the redirection
|
|
851
|
+
// chain from original target to new one. We need to use '_originalArtifact' due
|
|
852
|
+
// to secondary associations and their redirection chains. See comment in
|
|
853
|
+
// test3/Redirections/SecondaryAssocs/RedirectedPathRewriteOne.cds
|
|
828
854
|
// FIXME: Won't work with associations in projected structures.
|
|
829
855
|
const origTarget = ref.path[i - 1]?._originalArtifact?.target?._artifact;
|
|
830
856
|
const chain = cachedRedirectionChain( art, origTarget );
|
|
@@ -914,6 +940,33 @@ function tweakAssocs( model ) {
|
|
|
914
940
|
}
|
|
915
941
|
}
|
|
916
942
|
|
|
943
|
+
/**
|
|
944
|
+
* Rewrite the reference `ref` with first elem/mixin ref item `item` for user
|
|
945
|
+
* `assoc` (the query element), `elem` is the first (or preferred) query element
|
|
946
|
+
* for item.
|
|
947
|
+
*/
|
|
948
|
+
function rewriteColumnPath( ref, column ) {
|
|
949
|
+
if (!ref._artifact)
|
|
950
|
+
return null;
|
|
951
|
+
const root = ref.path?.[0];
|
|
952
|
+
const nav = pathNavigation( ref );
|
|
953
|
+
if (nav.navigation) { // TabAlias.elem, elem, mixin
|
|
954
|
+
const elem = navProjection( nav.navigation, null );
|
|
955
|
+
// TODO?: Use rewritePathForEnv(); make it handle mixins?
|
|
956
|
+
if (rewritePath( ref, nav.item, column, elem, null ))
|
|
957
|
+
return traverseExpr.STOP;
|
|
958
|
+
}
|
|
959
|
+
else if (ref.scope === 'param' || root?.kind === '$parameters') {
|
|
960
|
+
return traverseExpr.STOP;
|
|
961
|
+
}
|
|
962
|
+
return null;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
/**
|
|
966
|
+
* Rewrite the reference `ref` with first elem/mixin ref item `item` for user
|
|
967
|
+
* `assoc` (the query element), `elem` is the first (or preferred) query element
|
|
968
|
+
* for item.
|
|
969
|
+
*/
|
|
917
970
|
function rewritePath( ref, item, assoc, elem, location ) {
|
|
918
971
|
const { path } = ref;
|
|
919
972
|
const root = path[0];
|
|
@@ -928,9 +981,9 @@ function tweakAssocs( model ) {
|
|
|
928
981
|
delete root._navigation;
|
|
929
982
|
setArtifactLink( root, elem );
|
|
930
983
|
setArtifactLink( ref, elem );
|
|
931
|
-
return;
|
|
984
|
+
return true; // ERROR
|
|
932
985
|
}
|
|
933
|
-
if (item !== root) {
|
|
986
|
+
if (item !== root) { // TableAlias.item, $self.item
|
|
934
987
|
// e.g. mixin ON-condition: Base.foo -> $self.foo or multi-path projection,
|
|
935
988
|
// $projection -> $self
|
|
936
989
|
root.id = '$self';
|
|
@@ -948,7 +1001,7 @@ function tweakAssocs( model ) {
|
|
|
948
1001
|
setLink( root, '_navigation', elem );
|
|
949
1002
|
}
|
|
950
1003
|
if (!elem.name) // nothing to do for own $projection, $projection.elem
|
|
951
|
-
return;
|
|
1004
|
+
return false; // (except having it renamed to $self)
|
|
952
1005
|
item.id = elem.name.id;
|
|
953
1006
|
let state = null;
|
|
954
1007
|
for (const i of path) {
|
|
@@ -964,6 +1017,7 @@ function tweakAssocs( model ) {
|
|
|
964
1017
|
}
|
|
965
1018
|
if (state !== true)
|
|
966
1019
|
setArtifactLink( ref, state );
|
|
1020
|
+
return false;
|
|
967
1021
|
}
|
|
968
1022
|
|
|
969
1023
|
function prependSelfToPath( path, elem ) {
|
|
@@ -1049,24 +1103,6 @@ function tweakAssocs( model ) {
|
|
|
1049
1103
|
}
|
|
1050
1104
|
}
|
|
1051
1105
|
|
|
1052
|
-
function navProjection( navigation, preferred ) {
|
|
1053
|
-
// TODO: Info if more than one possibility?
|
|
1054
|
-
if (!navigation)
|
|
1055
|
-
return {};
|
|
1056
|
-
|
|
1057
|
-
if (!navigation._projections && !navigation._complexProjections)
|
|
1058
|
-
return null;
|
|
1059
|
-
|
|
1060
|
-
// _complexProjections contains projections that are not "simple",
|
|
1061
|
-
// i.e. contain a filter or arguments. Only used if it contains our
|
|
1062
|
-
// preferred association.
|
|
1063
|
-
if (preferred && ( navigation._complexProjections?.includes( preferred ) ||
|
|
1064
|
-
navigation._projections?.includes( preferred )))
|
|
1065
|
-
return preferred;
|
|
1066
|
-
|
|
1067
|
-
return navigation._projections?.[0] || null;
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
1106
|
function findRewriteTarget( expr, index, env, user ) {
|
|
1071
1107
|
if (env.kind === '$navElement' || env.kind === '$tableAlias') {
|
|
1072
1108
|
const r = firstProjectionForPath( expr.path, index, env, user );
|
|
@@ -1216,7 +1252,7 @@ function followNavigationPath( path, nav ) {
|
|
|
1216
1252
|
* - $projection.elem -> also $self.item -> { item: path[1], tableAlias: $self }
|
|
1217
1253
|
* - $self -> { item: undefined, tableAlias: $self }
|
|
1218
1254
|
* - $parameters.P, :P -> {}
|
|
1219
|
-
* - $now
|
|
1255
|
+
* - $now -> {}
|
|
1220
1256
|
* - undef, redef -> {}
|
|
1221
1257
|
* With 'navigation': store that navigation._artifact is projected
|
|
1222
1258
|
* With 'navigation': rewrite its ON condition
|
|
@@ -1245,4 +1281,26 @@ function pathNavigation( ref ) {
|
|
|
1245
1281
|
return { navigation: root.elements[item.id], item, tableAlias: root };
|
|
1246
1282
|
}
|
|
1247
1283
|
|
|
1284
|
+
/**
|
|
1285
|
+
* Return the first (or preferred) query elements which projections the navigation
|
|
1286
|
+
* element `navigation` (i.e. source element belonging to a specific table alias).
|
|
1287
|
+
*/
|
|
1288
|
+
function navProjection( navigation, preferred ) {
|
|
1289
|
+
// TODO: Info if more than one possibility?
|
|
1290
|
+
if (!navigation)
|
|
1291
|
+
return {};
|
|
1292
|
+
|
|
1293
|
+
if (!navigation._projections && !navigation._complexProjections)
|
|
1294
|
+
return null;
|
|
1295
|
+
|
|
1296
|
+
// _complexProjections contains projections that are not "simple",
|
|
1297
|
+
// i.e. contain a filter or arguments. Only used if it contains our
|
|
1298
|
+
// preferred association.
|
|
1299
|
+
if (preferred && ( navigation._complexProjections?.includes( preferred ) ||
|
|
1300
|
+
navigation._projections?.includes( preferred )))
|
|
1301
|
+
return preferred;
|
|
1302
|
+
|
|
1303
|
+
return navigation._projections?.[0] || null;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1248
1306
|
module.exports = tweakAssocs;
|