@sap/cds-compiler 5.2.0 → 5.3.2
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 +39 -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/db/rewriteCalculatedElements.js +11 -5
- package/lib/transform/db/transformExists.js +43 -18
- package/lib/transform/effective/main.js +1 -1
- package/lib/transform/forRelationalDB.js +8 -7
- package/lib/utils/moduleResolve.js +1 -1
- package/package.json +1 -1
- package/share/messages/redirected-to-complex.md +6 -3
package/lib/render/toCdl.js
CHANGED
|
@@ -1331,6 +1331,9 @@ function csnToCdl( csn, options, msg ) {
|
|
|
1331
1331
|
if (artifact.localized) // works even for type definitions
|
|
1332
1332
|
result += 'localized ';
|
|
1333
1333
|
|
|
1334
|
+
// Some properties are always "top-level", even for "many", e.g. "default" or
|
|
1335
|
+
// "not null". Keep a reference to the outer artifact.
|
|
1336
|
+
const origArtifact = artifact;
|
|
1334
1337
|
if (!artifact.type && artifact.items) {
|
|
1335
1338
|
checkArrayedArtifact(artifact, env);
|
|
1336
1339
|
result += 'many '; // alternative: 'array of'; but not used
|
|
@@ -1342,11 +1345,14 @@ function csnToCdl( csn, options, msg ) {
|
|
|
1342
1345
|
|
|
1343
1346
|
if (!type && artifact.elements) {
|
|
1344
1347
|
result += renderElements(artifact, env);
|
|
1345
|
-
result += renderNullability(artifact);
|
|
1348
|
+
result += renderNullability(artifact.notNull);
|
|
1346
1349
|
// structured default not possible at the moment
|
|
1347
1350
|
return result;
|
|
1348
1351
|
}
|
|
1349
1352
|
|
|
1353
|
+
const defaultValue = origArtifact.default ? origArtifact.default : artifact.default;
|
|
1354
|
+
const notNull = origArtifact.notNull ? origArtifact.notNull : artifact.notNull;
|
|
1355
|
+
|
|
1350
1356
|
// Association type
|
|
1351
1357
|
if (isDirectAssocOrComp(type)) {
|
|
1352
1358
|
const isComp = type === 'cds.Composition';
|
|
@@ -1378,11 +1384,11 @@ function csnToCdl( csn, options, msg ) {
|
|
|
1378
1384
|
if (artifact.keys && !artifact.on)
|
|
1379
1385
|
result += ` ${ renderForeignKeys(artifact, env) }`;
|
|
1380
1386
|
|
|
1381
|
-
if (
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1387
|
+
if (!artifact.on) {
|
|
1388
|
+
// unmanaged associations can't be followed by "not null" or "default"
|
|
1389
|
+
result += renderNullability(notNull);
|
|
1390
|
+
result += renderDefaultExpr(defaultValue, env.withSubPath([ 'default' ]));
|
|
1391
|
+
}
|
|
1386
1392
|
return result;
|
|
1387
1393
|
}
|
|
1388
1394
|
|
|
@@ -1409,13 +1415,12 @@ function csnToCdl( csn, options, msg ) {
|
|
|
1409
1415
|
|
|
1410
1416
|
if (artifact.enum && !typeRefOnly)
|
|
1411
1417
|
result += renderEnum(artifact.enum, env);
|
|
1412
|
-
if (artifact.notNull !== undefined)
|
|
1413
|
-
result += renderNullability(artifact);
|
|
1414
1418
|
|
|
1419
|
+
result += renderNullability(notNull);
|
|
1415
1420
|
// If there is a default value, and it's a calculated element, do not
|
|
1416
1421
|
// render the default (because it's not supported for calc elements).
|
|
1417
|
-
if (
|
|
1418
|
-
result += renderDefaultExpr(
|
|
1422
|
+
if (defaultValue !== undefined && !artifact.value)
|
|
1423
|
+
result += renderDefaultExpr(defaultValue, env.withSubPath([ 'default' ]));
|
|
1419
1424
|
|
|
1420
1425
|
return result;
|
|
1421
1426
|
}
|
|
@@ -1789,24 +1794,24 @@ function csnToCdl( csn, options, msg ) {
|
|
|
1789
1794
|
return result;
|
|
1790
1795
|
}
|
|
1791
1796
|
|
|
1792
|
-
function renderDefaultExpr(
|
|
1793
|
-
if (!
|
|
1797
|
+
function renderDefaultExpr( defaultValue, env ) {
|
|
1798
|
+
if (!defaultValue)
|
|
1794
1799
|
return '';
|
|
1795
1800
|
let result = ' default ';
|
|
1796
|
-
if (
|
|
1797
|
-
result += exprRenderer.renderSubExpr(withoutCast(
|
|
1801
|
+
if (defaultValue.xpr && xprContainsCondition( defaultValue.xpr))
|
|
1802
|
+
result += exprRenderer.renderSubExpr(withoutCast(defaultValue), env);
|
|
1798
1803
|
else
|
|
1799
|
-
result += exprRenderer.renderExpr(withoutCast(
|
|
1804
|
+
result += exprRenderer.renderExpr(withoutCast(defaultValue), env);
|
|
1800
1805
|
return result;
|
|
1801
1806
|
}
|
|
1802
1807
|
|
|
1803
1808
|
// Render the nullability of an element or parameter (can be unset, true, or false)
|
|
1804
|
-
function renderNullability(
|
|
1805
|
-
if (
|
|
1809
|
+
function renderNullability( notNull /* , env */) {
|
|
1810
|
+
if (notNull === undefined) {
|
|
1806
1811
|
// Attribute not set at all
|
|
1807
1812
|
return '';
|
|
1808
1813
|
}
|
|
1809
|
-
return
|
|
1814
|
+
return notNull ? ' not null' : ' null';
|
|
1810
1815
|
}
|
|
1811
1816
|
|
|
1812
1817
|
/**
|
|
@@ -78,7 +78,7 @@ function addTenantFields( csn, options, messageFunctions ) {
|
|
|
78
78
|
else if (!independent && independent != null) {
|
|
79
79
|
error( 'tenant-invalid-anno-value', msgLocations( csnPath ),
|
|
80
80
|
{ anno: annoTenantIndep, value: independent },
|
|
81
|
-
// eslint-disable-next-line max-len
|
|
81
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
82
82
|
'Can\'t add $(ANNO) with value $(VALUE) to a non-entity, which is always tenant-independent' );
|
|
83
83
|
}
|
|
84
84
|
else if (art.includes) {
|
|
@@ -119,9 +119,9 @@ function addTenantFields( csn, options, messageFunctions ) {
|
|
|
119
119
|
.filter( name => isTenantDepEntity( csn.definitions[name] ) );
|
|
120
120
|
if (names.length) {
|
|
121
121
|
error( 'tenant-invalid-include', msgLocations( csnPath ), { names }, {
|
|
122
|
-
// eslint-disable-next-line max-len
|
|
122
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
123
123
|
std: 'Can\'t include the tenant-dependent entities $(NAMES) into a tenant-independent definition',
|
|
124
|
-
// eslint-disable-next-line max-len
|
|
124
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
125
125
|
one: 'Can\'t include the tenant-dependent entity $(NAMES) into a tenant-independent definition',
|
|
126
126
|
} );
|
|
127
127
|
}
|
|
@@ -206,7 +206,7 @@ function addTenantFields( csn, options, messageFunctions ) {
|
|
|
206
206
|
if (art[annoTenantIndep]) {
|
|
207
207
|
error( 'tenant-expecting-tenant-source', msgLocations( csnPath ), { art: query },
|
|
208
208
|
// TODO: better the final entity name of assoc navigation in FROM
|
|
209
|
-
// eslint-disable-next-line max-len
|
|
209
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
210
210
|
'Expecting the query source $(ART) to be tenant-dependent for a tenant-dependent query entity' );
|
|
211
211
|
}
|
|
212
212
|
return true;
|
|
@@ -660,11 +660,12 @@ function rewriteCalculatedElementsInViews( csn, options, csnUtils, pathDelimiter
|
|
|
660
660
|
|
|
661
661
|
/**
|
|
662
662
|
* @param {CSN.Model} csn
|
|
663
|
+
* @param {CSN.Options} options
|
|
663
664
|
*/
|
|
664
|
-
function processCalculatedElementsInEntities( csn ) {
|
|
665
|
+
function processCalculatedElementsInEntities( csn, options ) {
|
|
665
666
|
forEachDefinition(csn, (artifact, definitionName) => {
|
|
666
667
|
if (artifact.kind === 'entity' && !(artifact.query || artifact.projection))
|
|
667
|
-
removeDummyValueInEntity(artifact, [ 'definitions', definitionName ]);
|
|
668
|
+
removeDummyValueInEntity(artifact, [ 'definitions', definitionName ], options);
|
|
668
669
|
});
|
|
669
670
|
}
|
|
670
671
|
|
|
@@ -674,14 +675,19 @@ function processCalculatedElementsInEntities( csn ) {
|
|
|
674
675
|
*
|
|
675
676
|
* @param {CSN.Artifact} artifact
|
|
676
677
|
* @param {CSN.Path} path
|
|
678
|
+
* @param {CSN.Options} options
|
|
677
679
|
* @todo calculated elements that "live" on the database?
|
|
678
680
|
* @todo error when artifact is empty afterwards? Probably better as a CSN check!
|
|
679
681
|
*/
|
|
680
|
-
function removeDummyValueInEntity( artifact, path ) {
|
|
682
|
+
function removeDummyValueInEntity( artifact, path, options ) {
|
|
681
683
|
applyTransformationsOnDictionary(artifact.elements, {
|
|
682
684
|
value: (parent, prop, value, p, elements) => {
|
|
683
|
-
if (!value.stored)
|
|
684
|
-
|
|
685
|
+
if (!value.stored) {
|
|
686
|
+
if (options.transformation === 'effective' && parent.on)
|
|
687
|
+
delete parent.value;
|
|
688
|
+
else
|
|
689
|
+
delete elements[p.at(-1)];
|
|
690
|
+
}
|
|
685
691
|
},
|
|
686
692
|
}, {}, path.concat( 'elements' ));
|
|
687
693
|
}
|
|
@@ -409,6 +409,7 @@ function handleExists( csn, options, error, inspectRef, initDefinition, dropDefi
|
|
|
409
409
|
}
|
|
410
410
|
|
|
411
411
|
/**
|
|
412
|
+
*
|
|
412
413
|
* Translate an `EXISTS <unmanaged assoc>` into a part of a WHERE condition.
|
|
413
414
|
*
|
|
414
415
|
* A valid $self-backlink is handled in translateDollarSelfToWhere.
|
|
@@ -428,53 +429,77 @@ function handleExists( csn, options, error, inspectRef, initDefinition, dropDefi
|
|
|
428
429
|
*/
|
|
429
430
|
function translateUnmanagedAssocToWhere( root, target, isPrefixedWithTableAlias, base, current ) {
|
|
430
431
|
const whereExtension = [];
|
|
431
|
-
|
|
432
|
-
|
|
432
|
+
|
|
433
|
+
for (let j = 0; j < root.on.length; j++)
|
|
434
|
+
j = processExpressionPart(root.on, root.$path.concat('on'), j, whereExtension);
|
|
435
|
+
|
|
436
|
+
return whereExtension;
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Process the given expression and apply the steps described above.
|
|
440
|
+
*
|
|
441
|
+
* @param {Array} expression Expression we are processing
|
|
442
|
+
* @param {CSN.Path} path Path to the expression
|
|
443
|
+
* @param {number} expressionIndex Index in the current expression, imporant for paths and stuff
|
|
444
|
+
* @param {Array} collector Array to collect the processed expressionparts into
|
|
445
|
+
* @returns {number} How far along expression we have processed - so the main loop can jump ahead
|
|
446
|
+
*/
|
|
447
|
+
function processExpressionPart(expression, path, expressionIndex, collector) {
|
|
448
|
+
const part = expression[expressionIndex];
|
|
449
|
+
|
|
450
|
+
if (part?.xpr) {
|
|
451
|
+
const xpr = { xpr: [] };
|
|
452
|
+
for (let i = 0; i < part.xpr.length; i++)
|
|
453
|
+
i = processExpressionPart(part.xpr, path.concat(expressionIndex, 'xpr'), i, xpr.xpr);
|
|
454
|
+
|
|
455
|
+
collector.push(xpr);
|
|
456
|
+
return expressionIndex;
|
|
457
|
+
}
|
|
433
458
|
|
|
434
459
|
// we can only resolve stuff on refs - skip literals like =
|
|
435
460
|
// but also keep along stuff like null and undefined, so compiler
|
|
436
461
|
// can have a chance to complain/ we can fail later nicely maybe
|
|
437
462
|
if (!(part && part.ref)) {
|
|
438
|
-
|
|
439
|
-
|
|
463
|
+
collector.push(part);
|
|
464
|
+
return expressionIndex;
|
|
440
465
|
}
|
|
441
466
|
|
|
442
467
|
// root.$path should be safe - we can only reference things in exists that exist when we enrich
|
|
443
468
|
// so all of them should have a $path.
|
|
444
|
-
const { art, links } = inspectRef(
|
|
469
|
+
const { art, links } = inspectRef(path.concat(expressionIndex));
|
|
445
470
|
// Dollar Self Backlink
|
|
446
|
-
if (isValidDollarSelf(
|
|
447
|
-
if (
|
|
448
|
-
|
|
471
|
+
if (isValidDollarSelf(expression[expressionIndex], path.concat(expressionIndex), expression[expressionIndex + 1], expression[expressionIndex + 2], path.concat(expressionIndex + 2 ))) {
|
|
472
|
+
if (expression[expressionIndex].ref[0] === '$self' && expression[expressionIndex].ref.length === 1)
|
|
473
|
+
collector.push(...translateDollarSelfToWhere(base, target, expression[expressionIndex + 2], path.concat(expressionIndex + 2 )));
|
|
449
474
|
else
|
|
450
|
-
|
|
475
|
+
collector.push(...translateDollarSelfToWhere(base, target, expression[expressionIndex], path.concat(expressionIndex)));
|
|
451
476
|
|
|
452
|
-
|
|
477
|
+
return expressionIndex + 2;
|
|
453
478
|
}
|
|
454
479
|
else if (links && links[0].art === root) { // target side
|
|
455
|
-
|
|
480
|
+
collector.push({ ref: [ target, ...part.ref.slice(1) ] });
|
|
456
481
|
}
|
|
457
482
|
else if (part.$scope === '$self') { // source side - "absolute" scope
|
|
458
483
|
const column = part._art._column;
|
|
459
484
|
if (column && column.as) { // Replace with the "original" expression (the .ref, .xpr etc.)
|
|
460
|
-
|
|
485
|
+
collector.push(translateToSourceSide(column));
|
|
461
486
|
}
|
|
462
487
|
else {
|
|
463
|
-
|
|
488
|
+
collector.push(assignAndDeleteAs({}, part, { ref: [ base, ...part.ref.slice(1) ] }));
|
|
464
489
|
}
|
|
465
490
|
}
|
|
466
491
|
else if (art) { // source side - with local scope
|
|
467
492
|
if (isPrefixedWithTableAlias || part.$scope === 'alias')
|
|
468
|
-
|
|
493
|
+
collector.push({ ref: [ ...current.ref.slice(0, -1), ...part.ref ] });
|
|
469
494
|
else
|
|
470
|
-
|
|
495
|
+
collector.push({ ref: [ base, ...current.ref.slice(0, -1), ...part.ref ] });
|
|
471
496
|
}
|
|
472
497
|
else { // operator - or any other leftover
|
|
473
|
-
|
|
498
|
+
collector.push(part);
|
|
474
499
|
}
|
|
475
|
-
}
|
|
476
500
|
|
|
477
|
-
|
|
501
|
+
return expressionIndex;
|
|
502
|
+
}
|
|
478
503
|
|
|
479
504
|
|
|
480
505
|
/**
|
|
@@ -75,7 +75,7 @@ function effectiveCsn( model, options, messageFunctions ) {
|
|
|
75
75
|
// ensure getElement works on flattened struct_assoc columns
|
|
76
76
|
csnUtils = getUtils(csn, 'init-all');
|
|
77
77
|
|
|
78
|
-
processCalculatedElementsInEntities(csn);
|
|
78
|
+
processCalculatedElementsInEntities(csn, options);
|
|
79
79
|
associations.managedToUnmanaged(csn, options, csnUtils, messageFunctions);
|
|
80
80
|
associations.transformBacklinks(csn, options, csnUtils, messageFunctions);
|
|
81
81
|
const transformers = mergeTransformers([ options.addCdsPersistenceName ? misc.attachPersistenceName(csn, options, csnUtils) : {}, options.remapOdataAnnotations ? annotations.remapODataAnnotations(csn) : {}, misc.removeDefinitionsAndProperties(csn, options) ], null);
|
|
@@ -248,7 +248,7 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
248
248
|
}
|
|
249
249
|
});
|
|
250
250
|
|
|
251
|
-
processCalculatedElementsInEntities(csn);
|
|
251
|
+
processCalculatedElementsInEntities(csn, options);
|
|
252
252
|
|
|
253
253
|
timetrace.start('Transform CSN')
|
|
254
254
|
|
|
@@ -817,16 +817,17 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
817
817
|
function checkTypeParameters(artifact, artifactName) {
|
|
818
818
|
forEachMemberRecursively(artifact, (member, memberName, prop, path) => {
|
|
819
819
|
// Check type parameters (length, precision, scale ...)
|
|
820
|
-
if (!member.$ignore
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
820
|
+
if (!member.$ignore) {
|
|
821
|
+
if (member.type)
|
|
822
|
+
_check(member, memberName, csn, path);
|
|
823
|
+
if (member.items?.type)
|
|
824
|
+
_check(member.items, memberName, csn, path.concat([ 'items' ]));
|
|
825
|
+
}
|
|
825
826
|
}, [ 'definitions', artifactName ]);
|
|
826
827
|
|
|
827
828
|
// Check that required actual parameters on 'node.type' are set, that their values are in the correct range etc.
|
|
828
829
|
function _check(node, nodeName, model, path) {
|
|
829
|
-
if (node.type) {
|
|
830
|
+
if (node.type && !node.virtual) {
|
|
830
831
|
const absolute = node.type;
|
|
831
832
|
switch (absolute) {
|
|
832
833
|
case 'cds.String':
|
|
@@ -579,7 +579,7 @@ function checkFileCase( dep, realpath, nativeRealpath, { warning } ) {
|
|
|
579
579
|
}
|
|
580
580
|
for (const using of dep.usingFroms) {
|
|
581
581
|
warning('file-unexpected-case-mismatch', [ using.location, using ], {},
|
|
582
|
-
// eslint-disable-next-line max-len
|
|
582
|
+
// eslint-disable-next-line @stylistic/js/max-len
|
|
583
583
|
'The imported filename differs on the filesystem; ensure that capitalization matches the actual file\'s name');
|
|
584
584
|
}
|
|
585
585
|
}
|
package/package.json
CHANGED
|
@@ -53,9 +53,12 @@ CrossJoin
|
|
|
53
53
|
|
|
54
54
|
## How to Fix
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
redirection target. That means, the redirection target shouldn't
|
|
58
|
-
make it a to-many association.
|
|
56
|
+
First, ensure that the redirected association points to an entity that is
|
|
57
|
+
a reasonable redirection target. That means, the redirection target shouldn't
|
|
58
|
+
accidentally make it a to-many association.
|
|
59
|
+
|
|
60
|
+
Then add an explicit ON-condition or explicit foreign keys to the redirected
|
|
61
|
+
association. That will silence the compiler message.
|
|
59
62
|
|
|
60
63
|
## Related Messages
|
|
61
64
|
|