@sap/cds-compiler 5.1.2 → 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 +26 -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 +3 -3
- 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
package/lib/compiler/populate.js
CHANGED
|
@@ -789,8 +789,8 @@ function populate( model ) {
|
|
|
789
789
|
return col.name.id;
|
|
790
790
|
}
|
|
791
791
|
}
|
|
792
|
-
else if (col.expand || col.value && (col.
|
|
793
|
-
//
|
|
792
|
+
else if (col.expand || col.value && (col._columnParent || query._parent.kind !== 'select')) {
|
|
793
|
+
// _columnParent => inline/expand; _parent -> only allowed in sub-selects
|
|
794
794
|
error( 'query-req-name', [ col.value?.location || col.location, query ], {},
|
|
795
795
|
'Alias name is required for this select item' );
|
|
796
796
|
}
|
|
@@ -867,7 +867,7 @@ function populate( model ) {
|
|
|
867
867
|
const inferred = query._main.$inferred;
|
|
868
868
|
const excludingDict = (colParent || query).excludingDict || Object.create( null );
|
|
869
869
|
|
|
870
|
-
const envParent = wildcard.
|
|
870
|
+
const envParent = wildcard._columnParent;
|
|
871
871
|
const env = wildcardColumnEnv( wildcard, query );
|
|
872
872
|
if (!env)
|
|
873
873
|
return;
|
|
@@ -931,7 +931,7 @@ function populate( model ) {
|
|
|
931
931
|
// already done in populateQuery (TODO: change that and check whether
|
|
932
932
|
// `*` is allowed at all in definer)
|
|
933
933
|
if (!colParent || colParent.value._artifact) {
|
|
934
|
-
// avoid "not found" messages if
|
|
934
|
+
// avoid "not found" messages if columnParent can't be found
|
|
935
935
|
const user = colParent || query;
|
|
936
936
|
for (const name in user.excludingDict)
|
|
937
937
|
resolveExcluding( name, env, excludingDict, query );
|
|
@@ -939,9 +939,9 @@ function populate( model ) {
|
|
|
939
939
|
}
|
|
940
940
|
}
|
|
941
941
|
|
|
942
|
-
function wildcardColumnEnv( wildcard, query ) { // etc. wildcard.
|
|
942
|
+
function wildcardColumnEnv( wildcard, query ) { // etc. wildcard._columnParent;
|
|
943
943
|
// if (envParent) console.log( 'CE:', envParent._origin, query );
|
|
944
|
-
const colParent = wildcard.
|
|
944
|
+
const colParent = wildcard._columnParent;
|
|
945
945
|
if (!colParent)
|
|
946
946
|
return userQuery( query )._combined; // see combinedSourcesOrParentElements
|
|
947
947
|
|
|
@@ -985,14 +985,14 @@ function populate( model ) {
|
|
|
985
985
|
}
|
|
986
986
|
}
|
|
987
987
|
|
|
988
|
-
function setWildcardExpandInline( queryElem,
|
|
989
|
-
setLink( queryElem, '
|
|
988
|
+
function setWildcardExpandInline( queryElem, columnParent, origin, name, location ) {
|
|
989
|
+
setLink( queryElem, '_columnParent', columnParent );
|
|
990
990
|
const path = [ { id: name, location } ];
|
|
991
991
|
queryElem.value = { path, location }; // TODO: can we omit that? We have _origin
|
|
992
992
|
setArtifactLink( path[0], origin );
|
|
993
993
|
setLink( queryElem, '_origin', origin );
|
|
994
994
|
// set _projections when inline with table alias:
|
|
995
|
-
// const alias =
|
|
995
|
+
// const alias = columnParent?.value?.path?.[0]?._navigation;
|
|
996
996
|
// if (alias?.kind === '$tableAlias')
|
|
997
997
|
// pushLink( alias.elements[name], '_projections', queryElem );
|
|
998
998
|
}
|
package/lib/compiler/resolve.js
CHANGED
|
@@ -174,9 +174,10 @@ function resolve( model ) {
|
|
|
174
174
|
// TODO: or should we push elems with `expand` sibling to extra list for
|
|
175
175
|
// better messages? (Whatever that means exactly.)
|
|
176
176
|
|
|
177
|
-
if (elem.
|
|
178
|
-
if (elem.
|
|
179
|
-
// we're traversing top-level elements of the query;
|
|
177
|
+
if (elem._columnParent) {
|
|
178
|
+
if (elem._columnParent?.kind !== '$inline')
|
|
179
|
+
// we're traversing top-level elements of the query;
|
|
180
|
+
// other _columnParent kinds can't happen
|
|
180
181
|
throw new CompilerAssertion('found unexpected "expand", but expected "inline"');
|
|
181
182
|
|
|
182
183
|
if (!isPathBreakout( elem.value )) {
|
|
@@ -205,13 +206,17 @@ function resolve( model ) {
|
|
|
205
206
|
if (!nav.item._navigation) // first non-table-alias
|
|
206
207
|
setLink( nav.item, '_navigation', navItem );
|
|
207
208
|
|
|
208
|
-
if
|
|
209
|
-
|
|
209
|
+
// We consider an element only projected if the path doesn't have
|
|
210
|
+
// either arguments or filters; but we build up the navigation env
|
|
211
|
+
// nonetheless, as it makes rewriting paths later on easier.
|
|
212
|
+
let isComplexPath = !!(path[index].where || path[index].args);
|
|
213
|
+
|
|
210
214
|
++index;
|
|
211
215
|
while (navItem && index < path.length) {
|
|
212
216
|
const step = path[index];
|
|
213
|
-
if (!step?.id
|
|
217
|
+
if (!step?.id)
|
|
214
218
|
break;
|
|
219
|
+
isComplexPath ||= !!(step.where || step.args);
|
|
215
220
|
if (!navItem.elements?.[step.id]) {
|
|
216
221
|
const elements = navItem._origin?.elements ||
|
|
217
222
|
navItem._origin?.target?._artifact?.elements;
|
|
@@ -228,26 +233,26 @@ function resolve( model ) {
|
|
|
228
233
|
setLink( step, '_navigation', navItem );
|
|
229
234
|
++index;
|
|
230
235
|
}
|
|
231
|
-
// Last path step, if found, is a simple
|
|
236
|
+
// Last path step, if found, is a projected, either complex or simple.
|
|
232
237
|
if (index === path.length && navItem)
|
|
233
|
-
pushLink( navItem, '_projections', elem );
|
|
238
|
+
pushLink( navItem, isComplexPath ? '_complexProjections' : '_projections', elem );
|
|
234
239
|
}
|
|
235
240
|
}
|
|
236
241
|
|
|
237
242
|
function columnParentPath( elem ) {
|
|
238
|
-
if (!elem.
|
|
243
|
+
if (!elem._columnParent || !elem.value?.path || isPathBreakout( elem.value ))
|
|
239
244
|
return elem.value?.path;
|
|
240
245
|
|
|
241
246
|
const fullPath = [ ...elem.value.path ];
|
|
242
|
-
let
|
|
243
|
-
while (
|
|
244
|
-
if (
|
|
245
|
-
isPathBreakout(
|
|
247
|
+
let columnParent = elem._columnParent;
|
|
248
|
+
while (columnParent) {
|
|
249
|
+
if (columnParent.kind !== '$inline' || !columnParent.value?.path ||
|
|
250
|
+
isPathBreakout( columnParent.value )) {
|
|
246
251
|
// path breakout for e.g. `$self.{ foo }`, `1 as a .{ foo }`
|
|
247
252
|
return null;
|
|
248
253
|
}
|
|
249
|
-
fullPath.unshift(...
|
|
250
|
-
|
|
254
|
+
fullPath.unshift(...columnParent.value.path);
|
|
255
|
+
columnParent = columnParent._columnParent;
|
|
251
256
|
}
|
|
252
257
|
return fullPath;
|
|
253
258
|
}
|
|
@@ -285,16 +290,16 @@ function resolve( model ) {
|
|
|
285
290
|
return false;
|
|
286
291
|
}
|
|
287
292
|
|
|
288
|
-
function inheritedSourceKeyProp( { value,
|
|
293
|
+
function inheritedSourceKeyProp( { value, _columnParent } ) {
|
|
289
294
|
if (!value || !value.path)
|
|
290
295
|
return null;
|
|
291
|
-
const nav = !
|
|
296
|
+
const nav = !_columnParent && pathNavigation( value );
|
|
292
297
|
const item = value.path[value.path.length - 1];
|
|
293
298
|
if (nav?.navigation && nav.item === item)
|
|
294
299
|
return item._artifact?.key;
|
|
295
|
-
if (value.path.length !== 1 ||
|
|
300
|
+
if (value.path.length !== 1 || _columnParent?.kind !== '$inline')
|
|
296
301
|
return null;
|
|
297
|
-
const hpath =
|
|
302
|
+
const hpath = _columnParent.value?.path;
|
|
298
303
|
const head = hpath?.length === 1 && hpath[0]._navigation;
|
|
299
304
|
return head?.kind === '$tableAlias' && item._artifact?.key;
|
|
300
305
|
}
|
|
@@ -344,7 +349,7 @@ function resolve( model ) {
|
|
|
344
349
|
const elem = query.elements[name];
|
|
345
350
|
|
|
346
351
|
if (!elem.$inferred && elem.value?.path) {
|
|
347
|
-
const path = elem.
|
|
352
|
+
const path = elem._columnParent ? columnParentPath( elem ) : elem.value.path;
|
|
348
353
|
if (testExpr({ path }, selectTest, () => false, elem))
|
|
349
354
|
propagateKeys = false;
|
|
350
355
|
}
|
|
@@ -1008,7 +1013,7 @@ function resolve( model ) {
|
|
|
1008
1013
|
}
|
|
1009
1014
|
const target = resolvePath( obj.target, 'target', art );
|
|
1010
1015
|
|
|
1011
|
-
if (obj.
|
|
1016
|
+
if (obj._columnParent && obj.type && !obj.type.$inferred && art._main && art._main.query) {
|
|
1012
1017
|
// New association inside expand/inline: The on-condition can't be properly checked,
|
|
1013
1018
|
// so abort early. See #8797
|
|
1014
1019
|
error( 'query-unexpected-assoc', [ obj.name.location, art ], {},
|
|
@@ -1253,18 +1258,7 @@ function resolve( model ) {
|
|
|
1253
1258
|
error( 'type-invalid-cast', [ elem.type.location, elem ], { '#': 'assoc' } );
|
|
1254
1259
|
return;
|
|
1255
1260
|
}
|
|
1256
|
-
// console.log(message( null, elem.location, elem, {target,art:assoc}, 'Info','RE')
|
|
1257
|
-
// .toString(), elem.value)
|
|
1258
|
-
const nav = elem._main && elem._main.query && elem.value && pathNavigation( elem.value );
|
|
1259
|
-
if (nav && nav.item !== elem.value.path[elem.value.path.length - 1]) {
|
|
1260
|
-
if (!elem.on && origType.on) {
|
|
1261
|
-
error( 'rewrite-not-supported', [ elem.target.location, elem ] );
|
|
1262
|
-
return;
|
|
1263
|
-
}
|
|
1264
|
-
}
|
|
1265
1261
|
const origTarget = origType.target._artifact;
|
|
1266
|
-
// console.log(require('../model/revealInternalProperties').ref(elem),
|
|
1267
|
-
// !!origTarget,!!origType._effectiveType,!!origType.target)
|
|
1268
1262
|
if (!origTarget || !target)
|
|
1269
1263
|
return;
|
|
1270
1264
|
|
|
@@ -1377,7 +1371,7 @@ function resolve( model ) {
|
|
|
1377
1371
|
if (a.path && a.kind !== '$self' && a.kind !== 'mixin')
|
|
1378
1372
|
sources.push( a );
|
|
1379
1373
|
}
|
|
1380
|
-
if (alias.kind === '$
|
|
1374
|
+
if (alias.kind === '$tableAlias')
|
|
1381
1375
|
news.push( { chain: [ alias, ...chain ], sources } );
|
|
1382
1376
|
else
|
|
1383
1377
|
news.push( { chain, sources } );
|
|
@@ -1593,7 +1587,8 @@ function pathNavigation( ref ) {
|
|
|
1593
1587
|
if (root.kind === '$self')
|
|
1594
1588
|
return { item, tableAlias: root };
|
|
1595
1589
|
if (root.kind !== '$tableAlias' || ref.path.length < 2)
|
|
1596
|
-
return {};
|
|
1590
|
+
return {}; // should not happen
|
|
1591
|
+
// table alias
|
|
1597
1592
|
return { navigation: root.elements?.[item.id], item, tableAlias: root };
|
|
1598
1593
|
}
|
|
1599
1594
|
|
package/lib/compiler/shared.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// Compiler functions and utilities shared across all phases
|
|
2
|
-
// TODO: rename to paths.js and move non resolve-paths functions to somewhere else
|
|
3
2
|
|
|
4
3
|
'use strict';
|
|
5
4
|
|
|
@@ -546,7 +545,7 @@ function fns( model ) {
|
|
|
546
545
|
ruser = ruser._outer;
|
|
547
546
|
|
|
548
547
|
// Handle expand/inline, `type of`, :param, global (internally for CDL):
|
|
549
|
-
if (user.
|
|
548
|
+
if (user._columnParent && !semantics.isMainRef) { // in expand/inline
|
|
550
549
|
const { name } = semantics;
|
|
551
550
|
semantics = semantics.nestedColumn();
|
|
552
551
|
semantics.name = name;
|
|
@@ -563,7 +562,7 @@ function fns( model ) {
|
|
|
563
562
|
|
|
564
563
|
// Search in lexical environments, including $self/$projection:
|
|
565
564
|
const { isMainRef } = semantics;
|
|
566
|
-
const lexical = semantics.lexical?.( ruser ); // TODO:
|
|
565
|
+
const lexical = semantics.lexical?.( ruser ); // TODO: _columnParent?
|
|
567
566
|
if (lexical) {
|
|
568
567
|
const [ nextProp, dictProp ] = (isMainRef)
|
|
569
568
|
? [ '_block', 'artifacts' ]
|
|
@@ -608,7 +607,7 @@ function fns( model ) {
|
|
|
608
607
|
// element item in the path)
|
|
609
608
|
// TODO - think about setting _navigation for all $navElement – the
|
|
610
609
|
// "ref: ['tabAlias']: inline: […]" handling might be easier
|
|
611
|
-
// (no
|
|
610
|
+
// (no _columnParent consultation for key prop and renaming support)
|
|
612
611
|
function getPathItem( ref, semantics, user ) {
|
|
613
612
|
// let art = (headArt && headArt.kind === '$tableAlias') ? headArt._origin : headArt;
|
|
614
613
|
const { path } = ref;
|
|
@@ -918,7 +917,7 @@ function fns( model ) {
|
|
|
918
917
|
}
|
|
919
918
|
|
|
920
919
|
function nestedElements( user ) {
|
|
921
|
-
const colParent = user.
|
|
920
|
+
const colParent = user._columnParent;
|
|
922
921
|
Functions.effectiveType( colParent ); // set _origin
|
|
923
922
|
const path = colParent?.value?.path;
|
|
924
923
|
if (!path?.length)
|
|
@@ -1129,7 +1128,7 @@ function fns( model ) {
|
|
|
1129
1128
|
}
|
|
1130
1129
|
|
|
1131
1130
|
function undefinedNestedElement( user, head, valid, _dict, _art, path, semantics ) {
|
|
1132
|
-
const art = user.
|
|
1131
|
+
const art = user._columnParent._origin;
|
|
1133
1132
|
if (!art)
|
|
1134
1133
|
return null; // no consequential error
|
|
1135
1134
|
return undefinedItemElement( user, head, valid, null, art, path, semantics );
|
|
@@ -1551,7 +1550,7 @@ function fns( model ) {
|
|
|
1551
1550
|
const location = (user.expand || user.inline)[$location];
|
|
1552
1551
|
// mention `table alias` in text only with initial single path item ref,
|
|
1553
1552
|
// but do not mention that $self { … } is allowed, shouldn't be advertised:
|
|
1554
|
-
const txt = (path.length > 1 || user.
|
|
1553
|
+
const txt = (path.length > 1 || user._columnParent) ? 'struct' : 'init';
|
|
1555
1554
|
const code = (user.expand) ? '{ ‹expand› }' : '.{ ‹inline› }';
|
|
1556
1555
|
message( 'def-unexpected-nested-proj', [ location, user ], { '#': txt, code } );
|
|
1557
1556
|
}
|
|
@@ -1655,7 +1654,7 @@ function fns( model ) {
|
|
|
1655
1654
|
|
|
1656
1655
|
function checkOnlyForeignKeyNavigation( user, path, startIndex = 0, msgPrefix = '' ) {
|
|
1657
1656
|
// has to be run after foreign-key rewrite
|
|
1658
|
-
const outer = user.
|
|
1657
|
+
const outer = user._columnParent?._origin;
|
|
1659
1658
|
let assoc = outer?.foreignKeys &&
|
|
1660
1659
|
pathStartsWithSelf( { path } ) == null && // not $self or CDS var like $now
|
|
1661
1660
|
outer;
|
|
@@ -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')
|