@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 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
@@ -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((column) => {
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
- standard(parent, prop, node);
137
+
138
+ column.inline?.forEach?.(enrichColumn);
139
+ column.expand?.forEach?.(enrichColumn);
140
+ }
129
141
  }
130
142
 
131
143
  function simpleRef( node, prop, ref ) {
@@ -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
- 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
- ];
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
- validateDefaultValues,
107
- managedWithoutKeys,
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
- 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
- ];
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
- validateAssociationsInItems, checkForInvalidTarget,
132
- checkVirtualElement, checkManagedAssoc ];
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: [ '*' ] },
@@ -75,7 +75,7 @@ function extend( model ) {
75
75
  } );
76
76
 
77
77
  const includesNonShadowedFirst
78
- = isDeprecatedEnabled( model.options, '_includesNonShadowedFirst' );
78
+ = isDeprecatedEnabled( model.options, '_includesNonShadowedFirst' );
79
79
 
80
80
  sortModelSources();
81
81
  const extensionsDict = Object.create( null ); // TODO TMP
@@ -92,7 +92,7 @@ function populate( model ) {
92
92
  let newAutoExposed = [];
93
93
 
94
94
  const ignoreSpecifiedElements
95
- = isDeprecatedEnabled( model.options, 'ignoreSpecifiedQueryElements' );
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
- = 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
- : [ [], [] ];
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
- = parent.args
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
- = parent.args
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
- = 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
- : [ [], [] ];
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
- = typeProp.args.reduce((acc, arg) => {
461
- ((/* arg.ref || */ arg.val) ? acc[0] : acc[1]).push(arg);
462
- return acc;
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
- 1f3bba2acb882f5120a55a01eb9a7bdc
1
+ bffacbf5acb61179807cb657191c5114
@@ -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: { ... $.expr.$parens?.at( -1 ) ?? $.expr.location } }; }continue
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){
@@ -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
- = (sign.val === '-' &&
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
- // ensure that references, literals and functions keep their surrounding parentheses
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
- = def.$duplicates.reduce( addOneForDefinition, addOneForDefinition( 0, def ) );
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
- = (parent._art && parent._links)
21
- ? { art: parent._art, links: parent._links }
22
- : csnUtils.inspectRef(path);
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))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds-compiler",
3
- "version": "6.3.0",
3
+ "version": "6.3.4",
4
4
  "description": "CDS (Core Data Services) compiler and backends",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "author": "SAP SE (https://www.sap.com)",