@sap/cds-compiler 6.3.0 → 6.3.4
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 +14 -0
- package/lib/checks/enricher.js +15 -3
- package/lib/checks/validator.js +32 -32
- package/lib/compiler/assert-consistency.js +1 -1
- package/lib/compiler/extend.js +1 -1
- package/lib/compiler/populate.js +1 -1
- package/lib/compiler/tweak-assocs.js +1 -0
- package/lib/edm/annotations/edmJson.js +19 -19
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +3 -5
- package/lib/json/to-csn.js +3 -2
- package/lib/parsers/AstBuildingParser.js +7 -5
- package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +3 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,20 @@ Note: `beta` fixes, changes and features are usually not listed in this ChangeLo
|
|
|
8
8
|
but in [doc/CHANGELOG_BETA.md](doc/CHANGELOG_BETA.md).
|
|
9
9
|
The compiler behavior concerning `beta` features can change at any time without notice.
|
|
10
10
|
|
|
11
|
+
## Version 6.3.4 - 2025-09-11
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- parser: Keep parentheses around lists on the right side of an `in` operator.
|
|
16
|
+
- compiler: For calculated elements using associations with filters and cardinality, CSN recompilation could
|
|
17
|
+
fail for `gensrc` CSN, as happens for MTX.
|
|
18
|
+
|
|
19
|
+
## Version 6.3.2 - 2025-09-02
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- to.sql: Fix internal inconsistency when handling nested projections.
|
|
24
|
+
|
|
11
25
|
## Version 6.3.0 - 2025-08-28
|
|
12
26
|
|
|
13
27
|
### Added
|
package/lib/checks/enricher.js
CHANGED
|
@@ -114,9 +114,19 @@ function enrichCsn( csn, options ) {
|
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Enrich a query's columns, including nested projections.
|
|
119
|
+
*
|
|
120
|
+
* @param {object} parent
|
|
121
|
+
* @param {string} prop
|
|
122
|
+
* @param {object} node
|
|
123
|
+
*/
|
|
117
124
|
function columns( parent, prop, node ) {
|
|
118
125
|
// Establish the link relationships
|
|
119
|
-
parent[prop].forEach(
|
|
126
|
+
parent[prop].forEach(enrichColumn);
|
|
127
|
+
standard(parent, prop, node);
|
|
128
|
+
|
|
129
|
+
function enrichColumn( column ) {
|
|
120
130
|
const element = getElement(column);
|
|
121
131
|
if (element) {
|
|
122
132
|
setProp(column, '_element', element);
|
|
@@ -124,8 +134,10 @@ function enrichCsn( csn, options ) {
|
|
|
124
134
|
setProp(element, '_column', column);
|
|
125
135
|
cleanupCallbacks.push(() => delete element._column);
|
|
126
136
|
}
|
|
127
|
-
|
|
128
|
-
|
|
137
|
+
|
|
138
|
+
column.inline?.forEach?.(enrichColumn);
|
|
139
|
+
column.expand?.forEach?.(enrichColumn);
|
|
140
|
+
}
|
|
129
141
|
}
|
|
130
142
|
|
|
131
143
|
function simpleRef( node, prop, ref ) {
|
package/lib/checks/validator.js
CHANGED
|
@@ -55,20 +55,20 @@ const featureFlags = require('./featureFlags');
|
|
|
55
55
|
const { timetrace } = require('../utils/timetrace');
|
|
56
56
|
|
|
57
57
|
const forRelationalDBMemberValidators
|
|
58
|
-
= [
|
|
58
|
+
= [
|
|
59
59
|
// For HANA CDS specifically, reject any default parameter values, as these are not supported.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
];
|
|
60
|
+
rejectParamDefaultsInHanaCds,
|
|
61
|
+
checkTypeIsScalar,
|
|
62
|
+
checkDecimalScale,
|
|
63
|
+
checkExplicitlyNullableKeys,
|
|
64
|
+
managedWithoutKeys,
|
|
65
|
+
warnAboutDefaultOnAssociationForHanaCds,
|
|
66
|
+
// sql.prepend/append
|
|
67
|
+
checkSqlAnnotationOnElement,
|
|
68
|
+
// no temporal annotations on calc elements
|
|
69
|
+
rejectAnnotationsOnCalcElement,
|
|
70
|
+
checkElementTypeDefinitionHasType,
|
|
71
|
+
];
|
|
72
72
|
|
|
73
73
|
const forRelationalDBArtifactValidators = [
|
|
74
74
|
checkPrimaryKey,
|
|
@@ -101,35 +101,35 @@ const forRelationalDBQueryValidators = [
|
|
|
101
101
|
];
|
|
102
102
|
|
|
103
103
|
const forOdataMemberValidators
|
|
104
|
-
= [
|
|
104
|
+
= [
|
|
105
105
|
// OData allows only simple values, no expressions or functions
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
];
|
|
106
|
+
validateDefaultValues,
|
|
107
|
+
managedWithoutKeys,
|
|
108
|
+
];
|
|
109
109
|
|
|
110
110
|
const forOdataArtifactValidators
|
|
111
|
-
= [
|
|
111
|
+
= [
|
|
112
112
|
// actions and functions are not of interest for the database
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
];
|
|
113
|
+
checkActionOrFunction,
|
|
114
|
+
// arrays are just CLOBs/LargeString for the database,
|
|
115
|
+
// no inner for the array structure is of interest for the database
|
|
116
|
+
// NOTE: moved to the renderer for a while
|
|
117
|
+
// TODO: Re-enable this code and remove the duplicated code from the renderer.
|
|
118
|
+
// Not possible at the moment, because running this at the beginning of
|
|
119
|
+
// the renderer does not work because the enricher can't handle certain
|
|
120
|
+
// OData specifics.
|
|
121
|
+
// checkChainedArray,
|
|
122
|
+
checkReadOnlyAndInsertOnly,
|
|
123
|
+
];
|
|
124
124
|
|
|
125
125
|
const forOdataCsnValidators = [ checkCdsMap ];
|
|
126
126
|
|
|
127
127
|
const forOdataQueryValidators = [];
|
|
128
128
|
|
|
129
129
|
const commonMemberValidators
|
|
130
|
-
= [ validateOnCondition, validateForeignKeys,
|
|
131
|
-
|
|
132
|
-
|
|
130
|
+
= [ validateOnCondition, validateForeignKeys,
|
|
131
|
+
validateAssociationsInItems, checkForInvalidTarget,
|
|
132
|
+
checkVirtualElement, checkManagedAssoc ];
|
|
133
133
|
|
|
134
134
|
// TODO: checkManagedAssoc is a forEachMemberRecursively!
|
|
135
135
|
const commonArtifactValidators = [
|
|
@@ -602,7 +602,7 @@ function assertConsistency( model, stage ) {
|
|
|
602
602
|
cardinality: {
|
|
603
603
|
kind: true,
|
|
604
604
|
requires: [ 'location' ],
|
|
605
|
-
optional: [ 'sourceMin', 'sourceMax', 'targetMin', 'targetMax' ],
|
|
605
|
+
optional: [ 'sourceMin', 'sourceMax', 'targetMin', 'targetMax', '$inferred' ],
|
|
606
606
|
},
|
|
607
607
|
sourceMin: { test: isNumberVal },
|
|
608
608
|
sourceMax: { test: isNumberVal, also: [ '*' ] },
|
package/lib/compiler/extend.js
CHANGED
|
@@ -75,7 +75,7 @@ function extend( model ) {
|
|
|
75
75
|
} );
|
|
76
76
|
|
|
77
77
|
const includesNonShadowedFirst
|
|
78
|
-
|
|
78
|
+
= isDeprecatedEnabled( model.options, '_includesNonShadowedFirst' );
|
|
79
79
|
|
|
80
80
|
sortModelSources();
|
|
81
81
|
const extensionsDict = Object.create( null ); // TODO TMP
|
package/lib/compiler/populate.js
CHANGED
|
@@ -92,7 +92,7 @@ function populate( model ) {
|
|
|
92
92
|
let newAutoExposed = [];
|
|
93
93
|
|
|
94
94
|
const ignoreSpecifiedElements
|
|
95
|
-
|
|
95
|
+
= isDeprecatedEnabled( model.options, 'ignoreSpecifiedQueryElements' );
|
|
96
96
|
|
|
97
97
|
forEachDefinition( model, traverseElementEnvironments );
|
|
98
98
|
while (newAutoExposed.length) {
|
|
@@ -527,6 +527,7 @@ function tweakAssocs( model ) {
|
|
|
527
527
|
if (lastStep.cardinality) {
|
|
528
528
|
elem.cardinality ??= { ...assoc.cardinality };
|
|
529
529
|
elem.cardinality.location = location;
|
|
530
|
+
elem.cardinality.$inferred = 'rewrite';
|
|
530
531
|
for (const card of [ 'sourceMin', 'targetMin', 'targetMax' ]) {
|
|
531
532
|
if (lastStep.cardinality[card])
|
|
532
533
|
elem.cardinality[card] = copyExpr( lastStep.cardinality[card], location );
|
|
@@ -266,7 +266,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
266
266
|
transformExpression(xpr, undefined, transform);
|
|
267
267
|
delete parent[prop];
|
|
268
268
|
parentparent[parentprop]
|
|
269
|
-
|
|
269
|
+
= {
|
|
270
270
|
$And: [
|
|
271
271
|
{ $Le: [ xpr[1], xpr[0] ] },
|
|
272
272
|
{ $Le: [ xpr[0], xpr[2] ] },
|
|
@@ -370,12 +370,12 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
370
370
|
|
|
371
371
|
// Map Edm primitive type funcs to $Type funcs
|
|
372
372
|
let [ foundTypeProps, newArgs ]
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
373
|
+
= parent.args
|
|
374
|
+
? parent.args.reduce((acc, arg) => {
|
|
375
|
+
(arg.func === '$Collection' || arg.func === 'Collection' ? acc[0] : acc[1]).push(arg);
|
|
376
|
+
return acc;
|
|
377
|
+
}, [ [], [] ] )
|
|
378
|
+
: [ [], [] ];
|
|
379
379
|
|
|
380
380
|
if (foundTypeProps.length === 1) {
|
|
381
381
|
const type = foundTypeProps[0];
|
|
@@ -395,7 +395,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
395
395
|
|
|
396
396
|
let typePropName = isDollarFunc ? '$Type' : 'Type';
|
|
397
397
|
[ foundTypeProps, newArgs ]
|
|
398
|
-
|
|
398
|
+
= parent.args
|
|
399
399
|
? parent.args.reduce((acc, arg) => {
|
|
400
400
|
(EdmPrimitiveTypeMap[`Edm.${ arg.func }`] ? acc[0] : acc[1]).push(arg);
|
|
401
401
|
return acc;
|
|
@@ -429,7 +429,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
429
429
|
}
|
|
430
430
|
|
|
431
431
|
[ foundTypeProps, newArgs ]
|
|
432
|
-
|
|
432
|
+
= parent.args
|
|
433
433
|
? parent.args.reduce((acc, arg) => {
|
|
434
434
|
((arg.func === '$Type' || arg.func === 'Type') ? acc[0] : acc[1]).push(arg);
|
|
435
435
|
return acc;
|
|
@@ -448,19 +448,19 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
448
448
|
typePropName = typeProp.func;
|
|
449
449
|
|
|
450
450
|
const [ collTypes, newTypeArgs ]
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
451
|
+
= typeProp.args
|
|
452
|
+
? typeProp.args.reduce((acc, arg) => {
|
|
453
|
+
((arg.func === '$Collection' || arg.func === 'Collection') ? acc[0] : acc[1]).push(arg);
|
|
454
|
+
return acc;
|
|
455
|
+
}, [ [], [] ] )
|
|
456
|
+
: [ [], [] ];
|
|
457
457
|
typeProp.args = newTypeArgs;
|
|
458
458
|
|
|
459
459
|
const [ scalarTypes, typeFacets ]
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
460
|
+
= typeProp.args.reduce((acc, arg) => {
|
|
461
|
+
((/* arg.ref || */ arg.val) ? acc[0] : acc[1]).push(arg);
|
|
462
|
+
return acc;
|
|
463
|
+
}, [ [], [] ] );
|
|
464
464
|
|
|
465
465
|
let typeOpStr = collTypes.length
|
|
466
466
|
? `${ typePropName }(${ isDollarFunc ? '$Collection' : 'Collection' }(…))`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
bffacbf5acb61179807cb657191c5114
|
package/lib/gen/CdlParser.js
CHANGED
|
@@ -3946,7 +3946,7 @@ case 681:switch(this.l()){
|
|
|
3946
3946
|
case',':this.continueExpressionslist($,682);continue
|
|
3947
3947
|
default:this.s=682;continue
|
|
3948
3948
|
}
|
|
3949
|
-
case 682:if(this.m(0,')')){ this.surroundByParens( $.expr ); }continue
|
|
3949
|
+
case 682:if(this.m(0,')')){ if ($.expr.op?.val !== 'list' || $.expr.location) this.surroundByParens( $.expr ); else this.attachLocation( $.expr ); }continue
|
|
3950
3950
|
default:return this.exit_()
|
|
3951
3951
|
}
|
|
3952
3952
|
}
|
|
@@ -3954,7 +3954,7 @@ continueExpressionslist($,$next){
|
|
|
3954
3954
|
let e;let _
|
|
3955
3955
|
this.rule_(684,$next)
|
|
3956
3956
|
for(;;)switch(this.s){
|
|
3957
|
-
case 684:if(this.m(685,',')){ $.expr = { op: this.valueWithLocation( 'list' ), args: [ $.expr ], location:
|
|
3957
|
+
case 684:if(this.m(685,',')){ $.expr = { op: this.valueWithLocation( 'list' ), args: [ $.expr ], location: null }; }continue
|
|
3958
3958
|
case 685:switch(this.lk()){
|
|
3959
3959
|
case'Id':case'#':case'(':case'+':case'-':case':':case'?':case'not':case'case':case'cast':case'null':case'true':case'false':case'Number':case'String':case'exists':case'QuotedLiteral':this.s=686;continue
|
|
3960
3960
|
default:this.ei();continue
|
|
@@ -3967,9 +3967,7 @@ case 687:switch(this.l()){
|
|
|
3967
3967
|
case',':this.c(686);continue
|
|
3968
3968
|
default:this.gr([')']);continue
|
|
3969
3969
|
}
|
|
3970
|
-
default:
|
|
3971
|
-
this.attachLocation( $.expr )
|
|
3972
|
-
return this.exit_()
|
|
3970
|
+
default:return this.exit_()
|
|
3973
3971
|
}
|
|
3974
3972
|
}
|
|
3975
3973
|
caseExpression($,$next){
|
package/lib/json/to-csn.js
CHANGED
|
@@ -31,6 +31,7 @@ const normalizedKind = {
|
|
|
31
31
|
let gensrcFlavor = true; // good enough here...
|
|
32
32
|
let universalCsn = false;
|
|
33
33
|
let strictMode = false; // whether to dump with unknown properties (in standard)
|
|
34
|
+
/** @type {boolean|string} */
|
|
34
35
|
let withLocations = false;
|
|
35
36
|
let withDocComments = false;
|
|
36
37
|
let structXpr = false;
|
|
@@ -252,7 +253,7 @@ function compactModel( model, options = model.options || {} ) {
|
|
|
252
253
|
const loc = srcDict[first].location;
|
|
253
254
|
if (loc && loc.file) {
|
|
254
255
|
Object.defineProperty( csn, '$location', {
|
|
255
|
-
value: { file: loc.file }, configurable: true, writable: true, enumerable: withLocations,
|
|
256
|
+
value: { file: loc.file }, configurable: true, writable: true, enumerable: !!withLocations,
|
|
256
257
|
} );
|
|
257
258
|
}
|
|
258
259
|
set( '$extra', csn, srcDict[first] );
|
|
@@ -611,7 +612,7 @@ function addLocation( loc, csn ) {
|
|
|
611
612
|
val.endCol = loc.endCol;
|
|
612
613
|
}
|
|
613
614
|
Object.defineProperty( csn, '$location', {
|
|
614
|
-
value: val, configurable: true, writable: true, enumerable: withLocations,
|
|
615
|
+
value: val, configurable: true, writable: true, enumerable: !!withLocations,
|
|
615
616
|
} );
|
|
616
617
|
}
|
|
617
618
|
return csn;
|
|
@@ -867,7 +867,7 @@ class AstBuildingParser extends BaseParser {
|
|
|
867
867
|
// if (args.length !== 1) throw new CompilerAssertion()
|
|
868
868
|
const sign = ixpr.args[0];
|
|
869
869
|
const nval
|
|
870
|
-
|
|
870
|
+
= (sign.val === '-' &&
|
|
871
871
|
expr && // expr may be null if `-` rule can't be parsed
|
|
872
872
|
expr.literal === 'number' &&
|
|
873
873
|
sign.location.endLine === expr.location.line &&
|
|
@@ -1166,10 +1166,12 @@ class AstBuildingParser extends BaseParser {
|
|
|
1166
1166
|
secureParens( expr ) {
|
|
1167
1167
|
const op = expr?.op?.val;
|
|
1168
1168
|
const $parens = expr?.$parens;
|
|
1169
|
-
if (!$parens || expr.query || op && op !== 'call' && op !== 'cast')
|
|
1169
|
+
if (!$parens || expr.query || op && op !== 'call' && op !== 'cast' && op !== 'list')
|
|
1170
1170
|
return expr;
|
|
1171
|
-
//
|
|
1172
|
-
// (is for expressions the case anyway)
|
|
1171
|
+
// Ensure that references, literals, functions and lists keep their
|
|
1172
|
+
// surrounding parentheses (is for expressions the case anyway).
|
|
1173
|
+
// (Remark: as opposed to product types in type theory, a 1-tupel of an
|
|
1174
|
+
// n-tupel in SQL is different from an n-tupel → keep parens around `list`.)
|
|
1173
1175
|
const location = $parens.pop();
|
|
1174
1176
|
if (!$parens.length)
|
|
1175
1177
|
delete expr.$parens;
|
|
@@ -1355,7 +1357,7 @@ class AstBuildingParser extends BaseParser {
|
|
|
1355
1357
|
|
|
1356
1358
|
if (def.kind !== 'annotate') {
|
|
1357
1359
|
const numDefines
|
|
1358
|
-
|
|
1360
|
+
= def.$duplicates.reduce( addOneForDefinition, addOneForDefinition( 0, def ) );
|
|
1359
1361
|
this.handleDuplicateExtension( def, name, numDefines );
|
|
1360
1362
|
for (const dup of def.$duplicates)
|
|
1361
1363
|
this.handleDuplicateExtension( dup, name, numDefines );
|
|
@@ -17,9 +17,9 @@ function replaceForeignKeyRefsInExpressionAnnotations(csn, options, messageFunct
|
|
|
17
17
|
transformAnnotationExpression(parent, prop, {
|
|
18
18
|
ref: (parent, _prop, ref, path, _p, _ppn, ctx) => {
|
|
19
19
|
const { art, links }
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
= (parent._art && parent._links)
|
|
21
|
+
? { art: parent._art, links: parent._links }
|
|
22
|
+
: csnUtils.inspectRef(path);
|
|
23
23
|
// if a reference points to a structure(managed assoc or structured element), then we do not process
|
|
24
24
|
// as we can't guess which specific foreign key is targeted
|
|
25
25
|
if (!art || csnUtils.isManagedAssociation(art) || csnUtils.isStructured(art))
|