@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
package/lib/compiler/resolve.js
CHANGED
|
@@ -99,6 +99,7 @@ function resolve( model ) {
|
|
|
99
99
|
Object.assign( model.$functions, {
|
|
100
100
|
resolveExpr,
|
|
101
101
|
addForeignKeyNavigations,
|
|
102
|
+
redirectionChain,
|
|
102
103
|
} );
|
|
103
104
|
|
|
104
105
|
const ignoreSpecifiedElements
|
|
@@ -321,7 +322,7 @@ function resolve( model ) {
|
|
|
321
322
|
propagateKeys = false;
|
|
322
323
|
info( 'query-from-many', [ toMany.location, query ], { art: toMany }, {
|
|
323
324
|
std: 'Key properties are not propagated because a to-many association $(ART) is selected',
|
|
324
|
-
// eslint-disable-next-line max-len
|
|
325
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
325
326
|
element: 'Key properties are not propagated because a to-many association $(MEMBER) of $(ART) is selected',
|
|
326
327
|
} );
|
|
327
328
|
}
|
|
@@ -362,9 +363,9 @@ function resolve( model ) {
|
|
|
362
363
|
// ID published! Used in stakeholder project; if renamed, add to oldMessageIds
|
|
363
364
|
info( 'query-navigate-many', [ art.location, user || query ], { art }, {
|
|
364
365
|
std: 'Navigating along to-many association $(ART) - key properties are not propagated',
|
|
365
|
-
// eslint-disable-next-line max-len
|
|
366
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
366
367
|
element: 'Navigating along to-many association $(MEMBER) of $(ART) - key properties are not propagated',
|
|
367
|
-
// eslint-disable-next-line max-len
|
|
368
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
368
369
|
alias: 'Navigating along to-many mixin association $(MEMBER) - key properties are not propagated',
|
|
369
370
|
} );
|
|
370
371
|
}
|
|
@@ -400,9 +401,6 @@ function resolve( model ) {
|
|
|
400
401
|
function resolveRefs( art ) {
|
|
401
402
|
if (art.builtin)
|
|
402
403
|
return;
|
|
403
|
-
// console.log(info( null, [ art.location, art ], {}, 'REFS').toString());
|
|
404
|
-
// console.log(info( null, [ art.location, art ], { art: art.target || 'none' },
|
|
405
|
-
// 'RR: $(ART)').toString());
|
|
406
404
|
const parent = art._parent;
|
|
407
405
|
const allowedInMain = [ 'entity', 'aspect', 'event' ].includes( adHocOrMainKind( art ) );
|
|
408
406
|
const isTopLevelElement = parent && (parent.kind !== 'element' || parent.targetAspect);
|
|
@@ -518,8 +516,6 @@ function resolve( model ) {
|
|
|
518
516
|
}
|
|
519
517
|
}
|
|
520
518
|
if (obj.target) {
|
|
521
|
-
// console.log(error( 'test-target', [ obj.location, obj ],
|
|
522
|
-
// { target: obj.target, kind: obj.kind }, 'Target: $(TARGET), Kind $(KIND)'));
|
|
523
519
|
if (!obj.target.$inferred || obj.target.$inferred === 'aspect-composition')
|
|
524
520
|
resolveTarget( art, obj );
|
|
525
521
|
else
|
|
@@ -535,7 +531,6 @@ function resolve( model ) {
|
|
|
535
531
|
resolvePath( art.targetElement, 'targetElement', art );
|
|
536
532
|
|
|
537
533
|
// Resolve projections/views
|
|
538
|
-
// if (art.query)console.log( info( null, [art.query.location,art.query], 'VQ:' ).toString() );
|
|
539
534
|
|
|
540
535
|
if (art.$queries)
|
|
541
536
|
art.$queries.forEach( resolveQuery );
|
|
@@ -928,14 +923,12 @@ function resolve( model ) {
|
|
|
928
923
|
// TODO: or set silent dependencies in init?
|
|
929
924
|
forEachGeneric( query, 'elements', elem => dependsOnSilent( query, elem ) );
|
|
930
925
|
forEachGeneric( query, '$tableAliases', ( alias ) => {
|
|
931
|
-
// console.log( info( null, [alias.location,alias], 'SQA:' ).toString() );
|
|
932
926
|
if (alias.kind === 'mixin')
|
|
933
927
|
resolveRefs( alias ); // mixin element
|
|
934
928
|
else if (alias.kind !== '$self')
|
|
935
929
|
// pure path has been resolved, resolve args and filter now:
|
|
936
930
|
resolveExpr( alias, 'from', query._parent );
|
|
937
931
|
} );
|
|
938
|
-
// if (!query.$inlines) console.log('RQ:',query)
|
|
939
932
|
for (const col of query.$inlines)
|
|
940
933
|
resolveExpr( col.value, 'column', col );
|
|
941
934
|
// for (const col of query.$inlines)
|
|
@@ -1095,17 +1088,8 @@ function resolve( model ) {
|
|
|
1095
1088
|
issue['#'] = 'order';
|
|
1096
1089
|
}
|
|
1097
1090
|
}
|
|
1098
|
-
if (issue['#'])
|
|
1099
|
-
|
|
1100
|
-
message( 'type-expecting-service-target', [ art.target.location, art ], issue, {
|
|
1101
|
-
std: 'Expecting service entity $(TARGET)',
|
|
1102
|
-
ref: 'Expecting service entity $(TARGET); its element $(ID) referred to at line $(LINE), column $(COL) is not from an element with the same name in the provided model target',
|
|
1103
|
-
key: 'Expecting service entity $(TARGET); its key element $(ID) is not from a key element with the same name in the provided model target',
|
|
1104
|
-
missing: 'Expecting service entity $(TARGET); it does not have the key element $(ID) of the provided model target',
|
|
1105
|
-
order: 'Expecting service entity $(TARGET); its key elements are in a different order than those of the provided model target',
|
|
1106
|
-
} );
|
|
1107
|
-
/* eslint-enable max-len */
|
|
1108
|
-
}
|
|
1091
|
+
if (issue['#'])
|
|
1092
|
+
message( 'type-expecting-service-target', [ art.target.location, art ], issue );
|
|
1109
1093
|
}
|
|
1110
1094
|
|
|
1111
1095
|
function keyElementNames( elements ) {
|
|
@@ -1143,7 +1127,7 @@ function resolve( model ) {
|
|
|
1143
1127
|
function addImplicitForeignKeys( art, obj, target ) {
|
|
1144
1128
|
obj.foreignKeys = Object.create( null );
|
|
1145
1129
|
forEachInOrder( target, 'elements', ( elem, name ) => {
|
|
1146
|
-
if (elem.key
|
|
1130
|
+
if (elem.key?.val) {
|
|
1147
1131
|
const location = weakLocation( obj.target.location );
|
|
1148
1132
|
const key = {
|
|
1149
1133
|
name: { location, id: elem.name.id, $inferred: 'keys' },
|
|
@@ -1228,7 +1212,7 @@ function resolve( model ) {
|
|
|
1228
1212
|
const text = (item !== last) ? 'sub' : 'std';
|
|
1229
1213
|
error( 'duplicate-key-ref', [ item.location, key ], { '#': text, name }, {
|
|
1230
1214
|
std: 'Foreign key $(NAME) already refers to the same target element',
|
|
1231
|
-
// eslint-disable-next-line max-len
|
|
1215
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
1232
1216
|
sub: 'Foreign key $(NAME) already refers to the target element whose sub element is again referred to here',
|
|
1233
1217
|
// TODO: please add ideas for a better text, e.g. to (closed) PR #11325
|
|
1234
1218
|
} );
|
|
@@ -1246,9 +1230,8 @@ function resolve( model ) {
|
|
|
1246
1230
|
const origType = assoc && effectiveType( assoc );
|
|
1247
1231
|
if (origType === 0)
|
|
1248
1232
|
return;
|
|
1249
|
-
if (!origType
|
|
1250
|
-
const
|
|
1251
|
-
const loc = (path && path[path.length - 1] || elem.value || elem).location;
|
|
1233
|
+
if (!origType?.target) {
|
|
1234
|
+
const loc = (elem.value?.path?.at(-1) || elem.value || elem).location;
|
|
1252
1235
|
error( 'redirected-no-assoc', [ loc, elem ], {},
|
|
1253
1236
|
'Only an association can be redirected' );
|
|
1254
1237
|
return;
|
|
@@ -1258,52 +1241,68 @@ function resolve( model ) {
|
|
|
1258
1241
|
error( 'type-invalid-cast', [ elem.type.location, elem ], { '#': 'assoc' } );
|
|
1259
1242
|
return;
|
|
1260
1243
|
}
|
|
1244
|
+
|
|
1261
1245
|
const origTarget = origType.target._artifact;
|
|
1262
|
-
if (!origTarget || !target)
|
|
1263
|
-
return;
|
|
1264
1246
|
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
info( 'redirected-to-same', [ elem.target.location, elem ], { art: target },
|
|
1272
|
-
'The redirected target is the original $(ART)' );
|
|
1273
|
-
}
|
|
1274
|
-
setLink( elem, '_redirected', chain ); // store the chain
|
|
1275
|
-
return;
|
|
1247
|
+
if (target === origTarget && !elem.target.$inferred && !elem.on && !elem.foreignKeys) {
|
|
1248
|
+
// Only a managed redirection gets this info message. Because otherwise
|
|
1249
|
+
// we'd have to check whether on-condition/foreignKeys are the same.
|
|
1250
|
+
// ID published! Used in stakeholder project; if renamed, add to oldMessageIds
|
|
1251
|
+
info( 'redirected-to-same', [ elem.target.location, elem ], { art: target },
|
|
1252
|
+
'The redirected target is the original $(ART)' );
|
|
1276
1253
|
}
|
|
1254
|
+
|
|
1277
1255
|
if (elem.foreignKeys || elem.on)
|
|
1278
1256
|
return; // TODO: or should we still bring an msg if nothing in common?
|
|
1257
|
+
|
|
1258
|
+
const chain = redirectionChain( elem, target, origTarget );
|
|
1259
|
+
setLink( elem, '_redirected', chain );
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
/**
|
|
1263
|
+
* Get the redirection chain between a target and the original target.
|
|
1264
|
+
*
|
|
1265
|
+
* @param {XSN.Artifact} elem
|
|
1266
|
+
* @param {XSN.Artifact} target
|
|
1267
|
+
* @param {XSN.Artifact} origTarget
|
|
1268
|
+
* @param {boolean} [silent] Whether to report error and other messages.
|
|
1269
|
+
* @returns {XSN.Artifact[]|null}
|
|
1270
|
+
*/
|
|
1271
|
+
function redirectionChain( elem, target, origTarget, silent = false ) {
|
|
1272
|
+
if (!origTarget || !target)
|
|
1273
|
+
return null;
|
|
1274
|
+
if (target === origTarget)
|
|
1275
|
+
return []; // e.g. explicit ON-condition/foreign keys or original target
|
|
1276
|
+
|
|
1277
|
+
const chain = [];
|
|
1279
1278
|
// now check whether target and origTarget are "related"
|
|
1279
|
+
// first: check via simple projections
|
|
1280
1280
|
while (target.query) {
|
|
1281
1281
|
const from = target.query.args ? {} : target.query.from;
|
|
1282
1282
|
if (!from)
|
|
1283
|
-
return;
|
|
1283
|
+
return null; // parse error - TODO: or UNION?
|
|
1284
1284
|
if (!from.path) {
|
|
1285
|
+
if (silent)
|
|
1286
|
+
break;
|
|
1285
1287
|
const isTarget = target === elem.target._artifact;
|
|
1286
1288
|
const op = from.op?.val || target.query.op?.val;
|
|
1287
1289
|
const variant = (!isTarget && 'std') || (op && 'targetOp') || 'target';
|
|
1288
|
-
// ID published! Used in stakeholder project; if renamed, add to oldMessageIds
|
|
1289
1290
|
info( 'redirected-to-complex', [ elem.target.location, elem ],
|
|
1290
|
-
{ art: target, '#': variant, keyword: op || '' }
|
|
1291
|
-
std: 'Redirection involves the complex view $(ART)',
|
|
1292
|
-
target: 'The redirected target $(ART) is a complex view',
|
|
1293
|
-
targetOp: 'The redirected target $(ART) is a complex view with $(KEYWORD)',
|
|
1294
|
-
} );
|
|
1291
|
+
{ art: target, '#': variant, keyword: op || '' } );
|
|
1295
1292
|
break;
|
|
1296
1293
|
}
|
|
1297
1294
|
target = from._artifact;
|
|
1298
1295
|
if (!target)
|
|
1299
|
-
return;
|
|
1296
|
+
return null;
|
|
1300
1297
|
chain.push( from );
|
|
1301
1298
|
if (target === origTarget) {
|
|
1299
|
+
// found in simple projection chain
|
|
1302
1300
|
chain.reverse();
|
|
1303
|
-
|
|
1304
|
-
return;
|
|
1301
|
+
return chain;
|
|
1305
1302
|
}
|
|
1306
1303
|
}
|
|
1304
|
+
|
|
1305
|
+
// there is a complex view in-between; search through table aliases
|
|
1307
1306
|
let redirected = null;
|
|
1308
1307
|
chain.reverse();
|
|
1309
1308
|
let news = [ { chain, sources: [ target ] } ];
|
|
@@ -1321,25 +1320,25 @@ function resolve( model ) {
|
|
|
1321
1320
|
else if (!redirected) {
|
|
1322
1321
|
redirected = (s.kind === '$tableAlias') ? [ s, ...o.chain ] : o.chain;
|
|
1323
1322
|
}
|
|
1324
|
-
else {
|
|
1323
|
+
else if (!silent) {
|
|
1325
1324
|
error( 'redirected-to-ambiguous', [ elem.target.location, elem ], { art: origTarget },
|
|
1326
1325
|
'The redirected target originates more than once from $(ART)' );
|
|
1327
|
-
return;
|
|
1326
|
+
return null;
|
|
1327
|
+
}
|
|
1328
|
+
else {
|
|
1329
|
+
return null;
|
|
1328
1330
|
}
|
|
1329
1331
|
}
|
|
1330
1332
|
}
|
|
1331
1333
|
}
|
|
1332
|
-
if (redirected) {
|
|
1333
|
-
setLink( elem, '_redirected', redirected );
|
|
1334
|
-
}
|
|
1335
|
-
else if (redirected == null) {
|
|
1334
|
+
if (!silent && redirected == null) {
|
|
1336
1335
|
error( 'redirected-to-unrelated', [ elem.target.location, elem ], { art: origTarget },
|
|
1337
1336
|
'The redirected target does not originate from $(ART)' );
|
|
1338
1337
|
}
|
|
1339
|
-
return;
|
|
1338
|
+
return redirected;
|
|
1340
1339
|
|
|
1341
1340
|
// B = proj on A, C = A x B, X = { a: assoc to A on a.Q1 = ...}, Y = X.{ a: redirected to C }
|
|
1342
|
-
// what does a: redirected to C
|
|
1341
|
+
// what does 'a: redirected to C' mean?
|
|
1343
1342
|
// -> collect all elements Qi used in ON (corr: foreign keys)
|
|
1344
1343
|
// -> only use an tableAlias which has propagation for all elements
|
|
1345
1344
|
// no - error if the original target can be reached twice
|
|
@@ -1430,7 +1429,6 @@ function resolve( model ) {
|
|
|
1430
1429
|
}
|
|
1431
1430
|
|
|
1432
1431
|
function resolveExpr( expr, exprCtx, user ) {
|
|
1433
|
-
// console.log(expr?.location.line,exprCtx)
|
|
1434
1432
|
traverseExpr( expr, exprCtx, user, resolveExprItem );
|
|
1435
1433
|
}
|
|
1436
1434
|
|
|
@@ -1507,7 +1505,7 @@ function resolve( model ) {
|
|
|
1507
1505
|
message( 'expr-unexpected-filter', [ location, user ], { '#': variant }, {
|
|
1508
1506
|
std: 'A filter can only be provided when navigating along associations',
|
|
1509
1507
|
// to help users for `… from E:toF { toF[…].x }`
|
|
1510
|
-
// eslint-disable-next-line max-len
|
|
1508
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
1511
1509
|
tableAlias: 'A filter can only be provided when navigating along associations, but found table alias',
|
|
1512
1510
|
from: 'A filter can only be provided for the source entity or associations',
|
|
1513
1511
|
} );
|
package/lib/compiler/shared.js
CHANGED
|
@@ -1225,7 +1225,7 @@ function fns( model ) {
|
|
|
1225
1225
|
// of invisible table aliases; at least one stakeholder uses this,
|
|
1226
1226
|
// so it can't be an error (yet).
|
|
1227
1227
|
message( 'ref-deprecated-self-element', [ ref.path[0].location, user._user ], {},
|
|
1228
|
-
// eslint-disable-next-line max-len
|
|
1228
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
1229
1229
|
'Referring to the query\'s own elements here might lead to invalid SQL references; use source elements only' );
|
|
1230
1230
|
return false;
|
|
1231
1231
|
default:
|
|
@@ -1617,7 +1617,7 @@ function fns( model ) {
|
|
|
1617
1617
|
else if (target._artifact && target._artifact !== user._main && user._main.kind === 'entity') {
|
|
1618
1618
|
const last = path[path.length - 1];
|
|
1619
1619
|
warning( 'ref-invalid-backlink', [ last.location, user ], { art: target, id: '$self' },
|
|
1620
|
-
// eslint-disable-next-line max-len
|
|
1620
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
1621
1621
|
'The target $(ART) of the association is not the current entity represented by $(ID)' );
|
|
1622
1622
|
}
|
|
1623
1623
|
}
|
|
@@ -1707,11 +1707,11 @@ function fns( model ) {
|
|
|
1707
1707
|
std: 'Can follow association $(ART) only to its foreign key references, not to $(NAME)',
|
|
1708
1708
|
keys: 'Can follow managed association $(ART) only to the keys of its target, not to $(NAME)',
|
|
1709
1709
|
complete: 'The reference must cover a full foreign key reference of association $(ART)',
|
|
1710
|
-
// eslint-disable-next-line max-len
|
|
1710
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
1711
1711
|
'self-std': 'In column ref starting with $(ALIAS), we can follow association $(ART) only to its foreign key references, not to $(NAME)',
|
|
1712
|
-
// eslint-disable-next-line max-len
|
|
1712
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
1713
1713
|
'self-keys': 'In column ref starting with $(ALIAS), we can follow managed association $(ART) only to the keys of its target, not to $(NAME)',
|
|
1714
|
-
// eslint-disable-next-line max-len
|
|
1714
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
1715
1715
|
'self-complete': 'The column reference starting with $(ALIAS) must cover a full foreign key reference of association $(ART)',
|
|
1716
1716
|
} );
|
|
1717
1717
|
// TODO later: mention allowed ones
|