@sap/cds-compiler 6.5.0 → 6.5.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 CHANGED
@@ -13,6 +13,14 @@ we might not list every change in its behavior here.
13
13
  Productive code should never require a `beta` flag to be set, and
14
14
  might use a deprecated flag only for a limited period of time.
15
15
 
16
+ ## Version 6.5.2 - 2025-12-02
17
+
18
+ ### Fixed
19
+
20
+ - to.sql|hdi:
21
+ + Don't add superfluous (and actually wrong) parentheses around `UNION`s
22
+ + Don't dump with a specific column expressions in the query after a `UNION`
23
+
16
24
  ## Version 6.5.0 - 2025-11-21
17
25
 
18
26
  ### Added
package/bin/cdsc.js CHANGED
@@ -153,7 +153,7 @@ function cdscMain() {
153
153
  // Internally, parseCdl/parseOnly are options, so we map the command to it.
154
154
  if (cmdLine.command === 'parse') {
155
155
  cmdLine.command = 'toCsn';
156
- cmdLine.options.toCsn = cmdLine.options.parseCdl;
156
+ cmdLine.options.toCsn = cmdLine.options.parse || cmdLine.options.parseCdl;
157
157
  cmdLine.options.parseCdl = true;
158
158
  cmdLine.args.files = [ cmdLine.args.file ];
159
159
  }
@@ -959,28 +959,28 @@ const centralMessageTexts = {
959
959
  'ext-undefined-art-sec': 'No artifact has been found with name $(ART)',
960
960
  'ext-undefined-element': {
961
961
  std: 'Element $(NAME) has not been found',
962
- element: 'Artifact $(ART) has no element $(NAME)',
963
- enum: 'Artifact $(ART) has no enum $(NAME)',
964
- returns: 'Return value of $(ART) has no element $(NAME)',
965
- 'enum-returns': 'Return value of $(ART) has no enum $(NAME)',
962
+ enum: 'Enum symbol $(NAME) has not been found',
963
+ returns: 'The return value has no element $(NAME)',
964
+ 'enum-returns': 'The return value has no enum $(NAME)',
965
+ },
966
+ 'ext-undefined-element-sec': {
967
+ std: 'Element $(NAME) has not been found',
968
+ enum: 'Enum symbol $(NAME) has not been found',
969
+ returns: 'The return value has no element $(NAME)',
970
+ 'enum-returns': 'The return value has no enum $(NAME)',
966
971
  },
967
- 'ext-undefined-element-sec': 'Element $(NAME) has not been found',
968
972
  'ext-undefined-key': 'Foreign key $(NAME) has not been found',
969
973
  'ext-undefined-action': {
970
- std: 'Action $(ART) has not been found',
971
- action: 'Artifact $(ART) has no action $(NAME)',
974
+ std: 'Action $(NAME) has not been found',
972
975
  },
973
976
  'ext-undefined-action-sec': {
974
- std: 'Action $(ART) has not been found',
975
- action: 'Artifact $(ART) has no action $(NAME)',
977
+ std: 'Action $(NAME) has not been found',
976
978
  },
977
979
  'ext-undefined-param': {
978
- std: 'Parameter $(ART) has not been found',
979
- param: 'Artifact $(ART) has no parameter $(NAME)',
980
+ std: 'Parameter $(NAME) has not been found',
980
981
  },
981
982
  'ext-undefined-param-sec': {
982
- std: 'Parameter $(ART) has not been found',
983
- param: 'Artifact $(ART) has no parameter $(NAME)',
983
+ std: 'Parameter $(NAME) has not been found',
984
984
  },
985
985
 
986
986
  // annotation checks against their definition
@@ -140,7 +140,6 @@ const {
140
140
  initDollarSelf,
141
141
  initDollarParameters,
142
142
  initBoundSelfParam,
143
- storeExtension,
144
143
  dependsOnSilent,
145
144
  pathName,
146
145
  targetCantBeAspect,
@@ -178,7 +177,7 @@ function define( model ) {
178
177
  Object.assign( model.$functions, {
179
178
  shuffleDict,
180
179
  shuffleArray,
181
- initArtifact,
180
+ initMainArtifact,
182
181
  initMembers, // for finalize-parser-cdl.js
183
182
  targetIsTargetAspect,
184
183
  checkRedefinition,
@@ -213,7 +212,7 @@ function define( model ) {
213
212
  // Phase 2:
214
213
  for (const name of sourceNames)
215
214
  initNamespaceAndUsing( model.sources[name] );
216
- dictForEach( model.definitions, initArtifact );
215
+ dictForEach( model.definitions, initMainArtifact );
217
216
  dictForEach( model.vocabularies, initVocabulary );
218
217
  dictForEach( model.$collectedExtensions, e => e._extensions.forEach( initExtension ) );
219
218
 
@@ -254,7 +253,7 @@ function define( model ) {
254
253
  if (src.$frontend !== 'json') { // CDL input
255
254
  // TODO: set _block to builtin
256
255
  if (src.artifacts) {
257
- // addArtifact() adds usings to src.artifacts: shuffleDict must be assigned first
256
+ // addBlockArtifact() adds usings to src.artifacts: shuffleDict must be assigned first
258
257
  src.artifacts = shuffleDict( src.artifacts );
259
258
  addPathPrefixes( src.artifacts, prefix ); // before addUsing
260
259
  }
@@ -265,14 +264,14 @@ function define( model ) {
265
264
  shuffleArray( src.usings ).forEach( u => addUsing( u, src ) );
266
265
  if (namespace?.id) // successfully set a full name for namespace
267
266
  addNamespace( namespace, src );
268
- if (src.artifacts) { // addArtifact needs usings for context extensions
267
+ if (src.artifacts) { // addBlockArtifact needs usings for context extensions
269
268
  src.artifacts = shuffleDict( src.artifacts );
270
- dictForEach( src.artifacts, a => addArtifact( a, src, prefix ) );
269
+ dictForEach( src.artifacts, a => addBlockArtifact( a, src, prefix ) );
271
270
  }
272
271
  }
273
272
  else if (src.definitions) { // CSN input
274
273
  prefix = ''; // also for addVocabulary() below
275
- dictForEach( shuffleDict( src.definitions ), def => addDefinition( def, src, prefix ) );
274
+ dictForEach( shuffleDict( src.definitions ), def => addMainArtifact( def, src, prefix ) );
276
275
  }
277
276
  if (src.vocabularies) {
278
277
  if (!model.vocabularies)
@@ -284,7 +283,7 @@ function define( model ) {
284
283
  }
285
284
  }
286
285
 
287
- function addDefinition( art, block, prefix ) {
286
+ function addMainArtifact( art, block, prefix ) {
288
287
  setLink( art, '_block', block );
289
288
  initExprAnnoBlock( art, block );
290
289
  art.name.id ??= prefix + pathName( art.name.path );
@@ -384,15 +383,15 @@ function define( model ) {
384
383
  $inferred: 'namespace',
385
384
  };
386
385
  }
387
- function addArtifact( art, block, prefix ) {
386
+ function addBlockArtifact( art, block, prefix ) {
388
387
  if (art.kind === 'using')
389
388
  return;
390
- addDefinition( art, block, prefix );
389
+ addMainArtifact( art, block, prefix );
391
390
  if (art.artifacts) {
392
391
  const p = `${ art.name.id }.`;
393
392
  // path prefixes (usings) must be added before extensions in artifacts:
394
393
  addPathPrefixes( art.artifacts, p );
395
- dictForEach( art.artifacts, a => addArtifact( a, art, p ) );
394
+ dictForEach( art.artifacts, a => addBlockArtifact( a, art, p ) );
396
395
  }
397
396
  if (art.extensions) { // requires using to be known!
398
397
  art.extensions.forEach( e => e.name && addExtension( e, art ) );
@@ -426,7 +425,7 @@ function define( model ) {
426
425
  // eslint-disable-next-line no-multi-assign
427
426
  ext.$effectiveSeqNo = model.$blocks[absolute] = (model.$blocks[absolute] || 0) + 1;
428
427
  const prefix = `${ absolute }.`;
429
- dictForEach( ext.artifacts, a => addArtifact( a, ext, prefix ) );
428
+ dictForEach( ext.artifacts, a => addBlockArtifact( a, ext, prefix ) );
430
429
  }
431
430
 
432
431
  function addVocabulary( vocab, block, prefix ) {
@@ -484,7 +483,7 @@ function define( model ) {
484
483
  }
485
484
 
486
485
  // Phase 2 ("init"), top-level & main -----------------------------------------
487
- // Functions called from top-level: initNamespaceAndUsing(), initArtifact(),
486
+ // Functions called from top-level: initNamespaceAndUsing(), initMainArtifact(),
488
487
  // initVocabulary(), initExtension()
489
488
 
490
489
  function initNamespaceAndUsing( src ) {
@@ -525,7 +524,7 @@ function define( model ) {
525
524
  }
526
525
  }
527
526
 
528
- function initArtifact( art, reInit = false ) {
527
+ function initMainArtifact( art, reInit = false ) {
529
528
  if (!reInit) // not for auto-exposed entity
530
529
  initArtifactParentLink( art, model.definitions );
531
530
  checkRedefinition( art );
@@ -608,7 +607,7 @@ function define( model ) {
608
607
  }
609
608
 
610
609
  // TODO: message ids
611
- // Called by initArtifact() and initVocabulary() and for members
610
+ // Called by initMainArtifact() and initVocabulary() and for members
612
611
  function checkRedefinition( art ) {
613
612
  if (art.kind === 'annotate' || art.kind === 'extend') // move this check to call in extend.js?
614
613
  return; // extensions are merged into a super-annotate; $duplicates are only kept for LSP
@@ -983,9 +982,10 @@ function define( model ) {
983
982
  initExprForQuery( col.value, parent );
984
983
  if (col.expand || col.inline)
985
984
  initSelectItems( col, null, user ); // TODO: use col, remove 3rd param?
986
- }
987
985
 
988
- initCdlTypeCast( col, parent );
986
+ initMembers( col ); // with #13933, TODO: only for enums
987
+ checkCdlTypeCast( col );
988
+ }
989
989
  }
990
990
 
991
991
  if (hasItems && !wildcard && parent.excludingDict && !options.$recompile) {
@@ -1038,13 +1038,7 @@ function define( model ) {
1038
1038
  return col.name.id;
1039
1039
  }
1040
1040
 
1041
- function initCdlTypeCast( col, parent ) {
1042
- if (col.val)
1043
- return; // e.g. '*' column
1044
-
1045
- setMemberParent( col, col.name, parent );
1046
- initMembers( col );
1047
-
1041
+ function checkCdlTypeCast( col ) { // with #13933:
1048
1042
  // We don't allow CDL-style casts to anonymous structures. We reject it already here
1049
1043
  // and not in checks.js to ensure that it's rejected in parseCdl.
1050
1044
  // TODO: via <guard> in CdlGrammar.g4
@@ -1052,7 +1046,7 @@ function define( model ) {
1052
1046
  error( 'type-invalid-cast', [ (col.elements[$location] ?? col.location), col ],
1053
1047
  { '#': 'to-inline-structure' } );
1054
1048
  }
1055
- else if (col.expand && (col.type || col.elements || col.items)) {
1049
+ else if (col.expand && (col.type || col.items)) {
1056
1050
  const loc = (col.type?.location || col.elements?.[$location] ||
1057
1051
  col.items?.location || col.location);
1058
1052
  error( 'type-invalid-cast', [ loc, col ], { '#': 'expand' } );
@@ -1068,7 +1062,7 @@ function define( model ) {
1068
1062
  *
1069
1063
  * Param `initExtensions` is for parse.cdl - TODO delete
1070
1064
  */
1071
- function initMembers( parent, initExtensions = false ) {
1065
+ function initMembers( parent ) {
1072
1066
  const block = parent._block;
1073
1067
  let obj = initItemsLinks( parent, block );
1074
1068
  initExprAnnoBlock( parent, block );
@@ -1086,109 +1080,102 @@ function define( model ) {
1086
1080
  error( 'type-unexpected-on-condition', [ obj.on.location, parent ] );
1087
1081
  delete obj.on; // continuation semantics: not specified
1088
1082
  }
1089
- if (targetAspect.elements)
1090
- initAnonymousAspect();
1083
+ if (targetAspect.elements) // eslint-disable-next-line no-multi-assign
1084
+ parent = obj = initAnonymousAspect( parent, obj, targetAspect );
1091
1085
  }
1092
- forEachInOrder( obj, 'elements', init );
1093
- forEachGeneric( obj, 'enum', init );
1094
- forEachInOrder( obj, 'foreignKeys', init );
1095
- forEachGeneric( parent, 'actions', init );
1096
- forEachInOrder( parent, 'params', init );
1086
+ forEachInOrder( obj, 'elements', (...args) => initArtifact( parent, ...args ) );
1087
+ forEachGeneric( obj, 'enum', (...args) => initArtifact( parent, ...args ) );
1088
+ forEachInOrder( obj, 'foreignKeys', (...args) => initArtifact( parent, ...args ) );
1089
+ forEachGeneric( parent, 'actions', (...args) => initArtifact( parent, ...args ) );
1090
+ forEachInOrder( parent, 'params', (...args) => initArtifact( parent, ...args ) );
1097
1091
 
1098
1092
  const { returns } = parent;
1099
1093
  if (returns) {
1100
1094
  const { kind } = parent;
1101
1095
  returns.kind = (kind === 'extend' || kind === 'annotate') ? kind : 'param';
1102
- init( returns, '' ); // '' is special name for returns parameter
1096
+ initArtifact( parent, returns, '' ); // '' is special name for returns parameter
1103
1097
  }
1104
- return;
1105
-
1106
- function initAnonymousAspect() {
1107
- // TODO: main?
1108
- const inEntity = parent._main?.kind === 'entity';
1109
- // TODO: also allow indirectly (component in component in entity)?
1110
- setLink( targetAspect, '_outer', obj );
1111
- setLink( targetAspect, '_parent', parent._parent );
1112
- setLink( targetAspect, '_main', null ); // for name resolution
1113
-
1114
- parent = targetAspect;
1115
- targetAspect.kind = 'aspect'; // TODO: probably '$aspect' to detect
1116
- setLink( targetAspect, '_block', block );
1117
- initDollarSelf( targetAspect );
1118
- // allow ref of up_ in anonymous aspect inside entity
1119
- // (TODO: complain if used and the managed composition is included into
1120
- // another entity - might induce auto-redirection):
1121
- if (inEntity && !targetAspect.elements.up_) {
1122
- const up = {
1123
- name: { id: 'up_' },
1124
- kind: '$navElement',
1125
- location: obj.location,
1126
- };
1127
- setLink( up, '_parent', targetAspect );
1128
- setLink( up, '_main', targetAspect ); // used on main artifact
1129
- // recompilation case: both target and targetAspect allow up_ in that case, too:
1130
- const name = obj.target && resolveUncheckedPath( obj.target, 'target', obj );
1131
- const entity = name && model.definitions[name];
1132
- if (entity && entity.elements)
1133
- setLink( up, '_origin', entity.elements.up_ );
1134
- // processAspectComposition/expand() sets _origin to element of
1135
- // generated target entity
1136
- targetAspect.$tableAliases.up_ = up;
1137
- }
1138
- obj = targetAspect;
1098
+ }
1099
+
1100
+ function initAnonymousAspect( parent, obj, targetAspect ) {
1101
+ // TODO: main?
1102
+ const inEntity = parent._main?.kind === 'entity';
1103
+ // TODO: also allow indirectly (component in component in entity)?
1104
+ setLink( targetAspect, '_outer', obj );
1105
+ setLink( targetAspect, '_parent', parent._parent );
1106
+ setLink( targetAspect, '_main', null ); // for name resolution
1107
+
1108
+ targetAspect.kind = 'aspect'; // TODO: probably '$aspect' to detect
1109
+ setLink( targetAspect, '_block', parent._block );
1110
+ initDollarSelf( targetAspect );
1111
+ // allow ref of up_ in anonymous aspect inside entity
1112
+ // (TODO: complain if used and the managed composition is included into
1113
+ // another entity - might induce auto-redirection):
1114
+ if (inEntity && !targetAspect.elements.up_) {
1115
+ const up = {
1116
+ name: { id: 'up_' },
1117
+ kind: '$navElement',
1118
+ location: obj.location,
1119
+ };
1120
+ setLink( up, '_parent', targetAspect );
1121
+ setLink( up, '_main', targetAspect ); // used on main artifact
1122
+ // recompilation case: both target and targetAspect allow up_ in that case, too:
1123
+ const name = obj.target && resolveUncheckedPath( obj.target, 'target', obj );
1124
+ const entity = name && model.definitions[name];
1125
+ if (entity && entity.elements)
1126
+ setLink( up, '_origin', entity.elements.up_ );
1127
+ // processAspectComposition/expand() sets _origin to element of
1128
+ // generated target entity
1129
+ targetAspect.$tableAliases.up_ = up;
1139
1130
  }
1131
+ return targetAspect;
1132
+ }
1140
1133
 
1141
- function init( elem, name, prop ) {
1142
- if (!elem.kind) // wrong CSN input
1143
- elem.kind = dictKinds[prop];
1144
- if (!elem.name && !elem._outer) {
1145
- const ref = elem.targetElement || elem.kind === 'element' && elem.value;
1146
- if (ref && ref.path) {
1147
- elem.name = Object.assign( { $inferred: 'as' },
1148
- ref.path[ref.path.length - 1] );
1149
- }
1150
- else { // RETURNS, parser robustness
1151
- elem.name = { id: name, location: elem.location };
1152
- }
1134
+ function initArtifact( parent, elem, name, prop ) {
1135
+ if (!elem.kind) // wrong CSN input
1136
+ elem.kind = dictKinds[prop];
1137
+ if (!elem.name && !elem._outer) {
1138
+ const ref = elem.targetElement || elem.kind === 'element' && elem.value;
1139
+ if (ref && ref.path) {
1140
+ elem.name = Object.assign( { $inferred: 'as' },
1141
+ ref.path[ref.path.length - 1] );
1153
1142
  }
1154
- // if (!kindProperties[ elem.kind ]) console.log(elem.kind,elem.name)
1155
- if ((elem.kind === 'extend' || elem.kind === 'annotate') && !initExtensions) {
1156
- storeExtension( elem, name, prop, parent, block );
1157
- return;
1143
+ else { // RETURNS, parser robustness
1144
+ elem.name = { id: name, location: elem.location };
1158
1145
  }
1146
+ }
1159
1147
 
1160
- const bl = elem._block || block;
1161
- setLink( elem, '_block', bl );
1148
+ if (!elem._block)
1149
+ setLink( elem, '_block', parent._block );
1162
1150
 
1163
- // don't dump with `entity T {}; extend T with { extend e {}; e {}; e {} };`:
1164
- setMemberParent( elem, name, parent );
1165
- checkRedefinition( elem );
1166
- initMembers( elem, initExtensions );
1167
- if (elem.kind === 'action' || elem.kind === 'function')
1168
- initBoundSelfParam( elem.params, elem._main );
1151
+ // don't dump with `entity T {}; extend T with { extend e {}; e {}; e {} };`:
1152
+ setMemberParent( elem, name, parent );
1153
+ checkRedefinition( elem );
1154
+ initMembers( elem );
1155
+ if (elem.kind === 'action' || elem.kind === 'function')
1156
+ initBoundSelfParam( elem.params, elem._main );
1169
1157
 
1170
- // for a correct home path, setMemberParent needed to be called
1158
+ // for a correct home path, setMemberParent needed to be called
1171
1159
 
1172
- if (!elem.value || elem.kind !== 'element' ||
1173
- elem.$syntax === 'enum' && parent.kind === 'extend') // ambiguous in parse-cdl
1174
- return;
1175
- // -> it's a calculated element
1176
- if (!elem.type && elem.value.type) { // top-level CAST( expr AS type )
1177
- if (!elem.target)
1178
- elem.type = { ...elem.value.type, $inferred: 'cast' };
1179
- }
1180
- elem.$syntax = 'calc';
1181
- // TODO: it is not just "syntax" - maybe better test for `$calcDepElement`?
1182
- createAndLinkCalcDepElement( elem );
1183
-
1184
- // Special case (hack) for calculated elements that use composition+filter:
1185
- // See "Notes on `$enclosed`" in `ExposingAssocWithFilter.md` for details.
1186
- // TODO: hm, only for inferred type - then just do not infer it in that case
1187
- if (elem.target && elem.value.path?.[elem.value.path.length - 1]?.where) {
1188
- delete elem.type;
1189
- delete elem.on;
1190
- delete elem.target;
1191
- }
1160
+ if (!elem.value || elem.kind !== 'element' ||
1161
+ elem.$syntax === 'enum' && parent.kind === 'extend') // ambiguous in parse-cdl
1162
+ return;
1163
+ // -> it's a calculated element
1164
+ if (!elem.type && elem.value.type) { // top-level CAST( expr AS type )
1165
+ if (!elem.target)
1166
+ elem.type = { ...elem.value.type, $inferred: 'cast' };
1167
+ }
1168
+ elem.$syntax = 'calc';
1169
+ // TODO: it is not just "syntax" - maybe better test for `$calcDepElement`?
1170
+ createAndLinkCalcDepElement( elem );
1171
+
1172
+ // Special case (hack) for calculated elements that use composition+filter:
1173
+ // See "Notes on `$enclosed`" in `ExposingAssocWithFilter.md` for details.
1174
+ // TODO: hm, only for inferred type - then just do not infer it in that case
1175
+ if (elem.target && elem.value.path?.[elem.value.path.length - 1]?.where) {
1176
+ delete elem.type;
1177
+ delete elem.on;
1178
+ delete elem.target;
1192
1179
  }
1193
1180
  }
1194
1181
 
@@ -864,19 +864,18 @@ function extend( model ) {
864
864
  }
865
865
  else if (!dict?.[name]) {
866
866
  // TODO: make variant `returns` an auto-variant for ($ART) ?
867
- const inReturns = parent._parent?.returns && parent._parent;
868
- const art = inReturns || parent;
867
+ const inReturns = parent._parent?.returns;
869
868
  switch (prop) {
870
869
  case 'elements':
871
870
  if (canBeDraftMember( name, parent, draftElements ))
872
871
  break;
873
872
  notFound( `ext-undefined-element${ securityRelevant }`, ext.name.location, ext,
874
- { '#': (inReturns ? 'returns' : 'element'), art, name },
873
+ { '#': (inReturns ? 'returns' : 'element'), name },
875
874
  parent.elements );
876
875
  break;
877
876
  case 'enum': // TODO: extra msg id?
878
877
  notFound( `ext-undefined-element${ securityRelevant }`, ext.name.location, ext,
879
- { '#': (inReturns ? 'enum-returns' : 'enum'), art, name },
878
+ { '#': (inReturns ? 'enum-returns' : 'enum'), name },
880
879
  parent.enum );
881
880
  break;
882
881
  case 'foreignKeys':
@@ -885,14 +884,14 @@ function extend( model ) {
885
884
  break;
886
885
  case 'params':
887
886
  notFound( `ext-undefined-param${ securityRelevant }`, ext.name.location, ext,
888
- { '#': 'param', art: parent, name },
887
+ { '#': 'param', name },
889
888
  parent.params );
890
889
  break;
891
890
  case 'actions':
892
891
  if (canBeDraftMember( name, parent, draftBoundActions ))
893
892
  break;
894
893
  notFound( `ext-undefined-action${ securityRelevant }`, ext.name.location, ext,
895
- { '#': 'action', art: parent, name },
894
+ { '#': 'action', name },
896
895
  parent.actions );
897
896
  break;
898
897
  default:
@@ -1213,11 +1212,9 @@ function extend( model ) {
1213
1212
  *
1214
1213
  * If not for extensions: construct === parent
1215
1214
  *
1216
- * Param `initExtensions` is for parse.cdl - TODO delete
1217
- *
1218
1215
  * TODO: separate extension!
1219
1216
  */
1220
- function initMembers( construct, parent, block, initExtensions = false ) {
1217
+ function initMembers( construct, parent, block ) {
1221
1218
  // TODO: split extend from init
1222
1219
  const main = parent._main || parent;
1223
1220
  const isQueryExtension = construct.kind === 'extend' && main.query;
@@ -1347,9 +1344,12 @@ function extend( model ) {
1347
1344
  elem.name = { id: name, location: elem.location };
1348
1345
  }
1349
1346
  }
1347
+
1348
+ if (!elem._block)
1349
+ setLink( elem, '_block', block );
1350
1350
  // if (!kindProperties[ elem.kind ]) console.log(elem.kind,elem.name)
1351
- if ((elem.kind === 'extend' || elem.kind === 'annotate') && !initExtensions) {
1352
- storeExtension( elem, name, prop, parent, block );
1351
+ if ((elem.kind === 'extend' || elem.kind === 'annotate')) {
1352
+ storeExtension( elem, name, prop, parent );
1353
1353
  return;
1354
1354
  }
1355
1355
  if (isQueryExtension && elem.kind === 'element') {
@@ -1359,8 +1359,6 @@ function extend( model ) {
1359
1359
  return;
1360
1360
  }
1361
1361
 
1362
- const bl = elem._block || block;
1363
- setLink( elem, '_block', bl );
1364
1362
  const existing = parent[prop]?.[name];
1365
1363
  const add = construct !== parent && (!existing || elem.$inferred !== 'include');
1366
1364
  // don't dump with `entity T {}; extend T with { extend e {}; e {}; e {} };`:
@@ -1368,7 +1366,7 @@ function extend( model ) {
1368
1366
  elem.$duplicates = null;
1369
1367
  setMemberParent( elem, name, parent, add && prop );
1370
1368
  checkRedefinition( elem );
1371
- initMembers( elem, elem, bl, initExtensions );
1369
+ initMembers( elem, elem, elem._block );
1372
1370
  if (elem.kind === 'action' || elem.kind === 'function')
1373
1371
  initBoundSelfParam( elem.params, elem._main );
1374
1372
 
@@ -1514,7 +1512,7 @@ function extend( model ) {
1514
1512
  * Sets `_ancestors` links on `art`.
1515
1513
  *
1516
1514
  * TODO: try to set `_ancestors` only to entities (but beware “intermediate”
1517
- * non-entities).
1515
+ * non-entities - TODO: make intermediate non-entities stop chain).
1518
1516
  *
1519
1517
  * Examples:
1520
1518
  * ext === art: `entity E : F {}` => add elements of F to E
@@ -42,7 +42,7 @@ function finalizeParseCdl( model ) {
42
42
  for (const ext of late[name]._extensions) {
43
43
  ext.name.id = resolveUncheckedPath( ext.name, '_uncheckedExtension', ext );
44
44
  // Initialize members and define annotations in sub-elements.
45
- initMembers( ext, true );
45
+ initMembers( ext );
46
46
  extensions.push( ext );
47
47
  }
48
48
  }
@@ -31,7 +31,7 @@ function generate( model ) {
31
31
  const {
32
32
  resolvePath,
33
33
  resolveUncheckedPath,
34
- initArtifact,
34
+ initMainArtifact,
35
35
  extendArtifactBefore,
36
36
  applyIncludes,
37
37
  } = model.$functions;
@@ -340,7 +340,7 @@ function generate( model ) {
340
340
  for (const orig of textElems)
341
341
  addElementToTextsEntity( orig, art, fioriEnabled, assertUniqueValue );
342
342
 
343
- initArtifact( art );
343
+ initMainArtifact( art );
344
344
  if (art.includes) {
345
345
  // add elements `locale`, etc. which are required below.
346
346
  applyIncludes( art, art ); // TODO: rethink - can we avoid this if only new extend?
@@ -641,7 +641,7 @@ function generate( model ) {
641
641
  'An aspect $(TARGET) with an element named $(NAME) can\'t be used as target' );
642
642
  return false;
643
643
  }
644
- if (model.definitions[entityName]) {
644
+ if (model.definitions[entityName]) { // TODO: allow a gap (namespace)?
645
645
  error( null, [ location, elem ], { art: entityName },
646
646
  // eslint-disable-next-line @stylistic/max-len
647
647
  'Target entity $(ART) can\'t be created as there is another definition with this name' );
@@ -742,7 +742,7 @@ function generate( model ) {
742
742
 
743
743
  setLink( art, '_block', model.$internal );
744
744
  model.definitions[entityName] = art;
745
- initArtifact( art );
745
+ initMainArtifact( art );
746
746
 
747
747
  // Apply annotations to generated artifact, prepare (not apply!) element
748
748
  // annotations (remark: adding elements is not allowed for generated artifacts):
@@ -32,6 +32,9 @@ function kickStart( model ) {
32
32
  * - service: the artifact of the embedding service
33
33
  * This function must be called ordered: parent first
34
34
  *
35
+ * Remark: _service links are not already set in define.js, because we might
36
+ * have a @cds.redirection.service in the future
37
+ *
35
38
  * @param {string} name Artifact name
36
39
  */
37
40
  function setAncestorsAndService( name ) {
@@ -50,16 +53,12 @@ function kickStart( model ) {
50
53
  return;
51
54
  // To be removed when nested services are allowed
52
55
  if (!isBetaEnabled( options, 'nestedServices' ) && art.kind === 'service') {
53
- while (parent.kind !== 'service')
54
- parent = parent._parent;
55
- message( 'service-nested-service', [ art.name.location, art ], { art: parent },
56
+ message( 'service-nested-service', [ art.name.location, art ], { art: service },
56
57
  'A service can\'t be nested within a service $(ART)' );
57
58
  }
58
59
  else if (art.kind === 'context') {
59
- while (parent.kind !== 'service')
60
- parent = parent._parent;
61
60
  // TODO: remove this error
62
- message( 'service-nested-context', [ art.name.location, art ], { art: parent },
61
+ message( 'service-nested-context', [ art.name.location, art ], { art: service },
63
62
  'A context can\'t be nested within a service $(ART)' );
64
63
  }
65
64
  }
@@ -73,7 +72,9 @@ function kickStart( model ) {
73
72
  // Service1.E = projection on E
74
73
 
75
74
  // Remark: _ancestors are also set with includes, and there also for aspects,
76
- // types and events.
75
+ // types and events (TODO: entity only)
76
+ //
77
+ // Remark: _ancestors are also tested in populate.js for minmal exposure
77
78
  const chain = [];
78
79
  const autoexposed = annotationVal( art['@cds.autoexposed'] );
79
80
  // no need to set preferredRedirectionTarget in the while loop as we would
@@ -75,7 +75,7 @@ function populate( model ) {
75
75
  resolvePath,
76
76
  nestedElements,
77
77
  attachAndEmitValidNames,
78
- initArtifact,
78
+ initMainArtifact,
79
79
  extendArtifactBefore,
80
80
  extendArtifactAfter,
81
81
  } = model.$functions;
@@ -1113,9 +1113,7 @@ function populate( model ) {
1113
1113
  // To avoid repeated messages: if already tried to do autoexposure, return
1114
1114
  // auto-exposed entity when successful, or `target` otherwise (no/failed autoexposure)
1115
1115
  function minimalExposure( target, service, elemScope ) {
1116
- const descendants = scopedExposure( target._descendants &&
1117
- target._descendants[service.name.id] ||
1118
- [],
1116
+ const descendants = scopedExposure( target._descendants?.[service.name.id] || [],
1119
1117
  elemScope, target );
1120
1118
  const preferred = descendants.filter( d => annotationVal( d['@cds.redirection.target'] ) );
1121
1119
  const exposed = preferred.length ? preferred : descendants;
@@ -1172,7 +1170,7 @@ function populate( model ) {
1172
1170
  kind: 'namespace', name: { id: autoScopeName, location }, location,
1173
1171
  };
1174
1172
  model.definitions[autoScopeName] = nullScope;
1175
- initArtifact( nullScope );
1173
+ initMainArtifact( nullScope );
1176
1174
  return nullScope;
1177
1175
  }
1178
1176
 
@@ -1207,7 +1205,6 @@ function populate( model ) {
1207
1205
  function isDirectProjection( proj, base ) {
1208
1206
  return proj.kind === 'entity' && // not event
1209
1207
  // direct proj (TODO: or should we add them to another list?)
1210
- // TODO: delete ENTITY._from - maybe not...
1211
1208
  proj.query && proj.query.op && proj.query.op.val === 'SELECT' &&
1212
1209
  proj._from && proj._from.length === 1 &&
1213
1210
  base === resolvePath( proj._from[0], 'from', proj.query );
@@ -1354,7 +1351,7 @@ function populate( model ) {
1354
1351
  }
1355
1352
  setLink( art, '_service', service );
1356
1353
  setLink( art, '_block', model.$internal );
1357
- initArtifact( art, !!autoexposed );
1354
+ initMainArtifact( art, !!autoexposed );
1358
1355
  effectiveType( art );
1359
1356
  // TODO: try to set locations of elements locations of orig target elements
1360
1357
  newAutoExposed.push( art );
@@ -1669,7 +1669,8 @@ function resolve( model ) {
1669
1669
  }
1670
1670
  const symbols = type && type.enum;
1671
1671
  if (!symbols) {
1672
- if (user.kind !== '$annotation') { // TODO: better type deduction for annotations
1672
+ if ((user.kind ?? user._outer?.kind) !== '$annotation') {
1673
+ // TODO: better type deduction for annotations
1673
1674
  const msg = (user.kind === 'enum') ? 'symbolDef' : type && 'invalidType';
1674
1675
  warning( 'ref-unexpected-enum', [ expr.location, user ],
1675
1676
  { '#': msg || 'untyped', enum: sym.id, type: type || '' } );
@@ -285,10 +285,9 @@ function dependsOnSilent( user, art ) {
285
285
  user._deps.push( { art } );
286
286
  }
287
287
 
288
- function storeExtension( elem, name, prop, parent, block ) {
288
+ function storeExtension( elem, name, prop, parent ) {
289
289
  if (prop === 'enum')
290
290
  prop = 'elements';
291
- setLink( elem, '_block', block );
292
291
  const kind = `_${ elem.kind }`; // _extend or _annotate
293
292
  if (!parent[kind])
294
293
  setLink( parent, kind, {} );
@@ -245,9 +245,10 @@ const referenceSemantics = {
245
245
  inline: { lexical: justDollar, dynamic: 'inline' }, // ...using baseEnv
246
246
  ref_where: { lexical: justDollar, dynamic: 'ref-target' }, // ...using baseEnv
247
247
  on: { lexical: justDollar, dynamic: 'query' }, // assoc defs, redirected to
248
+ // there are also 'on_join' and 'on_mixin' with default semantics
249
+ $calc: { lexical: justDollar, dynamic: 'query' }, // calculation for draft
248
250
  annotation: { lexical: justDollar, dynamic: 'query' }, // anno top-level `ref`
249
251
  annotationExpr: { lexical: justDollar, dynamic: 'query' }, // annotation assignment
250
- // there are also 'on_join' and 'on_mixin' with default semantics
251
252
  orderBy_ref: { lexical: query => query, dynamic: 'query' },
252
253
  orderBy_expr: { lexical: query => query, dynamic: 'source' }, // ref in ORDER BY expression
253
254
  orderBy_set_ref: { lexical: query => query.$next, dynamic: 'query' }, // to outer SELECT (from UNION)
@@ -1075,6 +1076,7 @@ function analyseCsnPath( csnPath, csn, resolve ) {
1075
1076
  if (refCtx === 'annotation' && typeof obj === 'object') {
1076
1077
  // we do not know yet whether the annotation value is an expression or not →
1077
1078
  // loop over outer array and records (structure values):
1079
+ // TODO: add test → `@(A: [{target: ($user)}])` on query column
1078
1080
  if (Array.isArray( obj ) || !isAnnotationExpression( obj )) {
1079
1081
  obj = obj[prop];
1080
1082
  continue;
@@ -1164,6 +1166,9 @@ function analyseCsnPath( csnPath, csn, resolve ) {
1164
1166
  else if (prop[0] === '@') {
1165
1167
  refCtx = 'annotation';
1166
1168
  }
1169
+ else if (prop === '$calc') {
1170
+ refCtx = '$calc';
1171
+ }
1167
1172
  else if (prop !== 'xpr' && prop !== 'list') {
1168
1173
  // 'xpr' and 'list' do not change the ref context, all other props do:
1169
1174
  refCtx = prop;
@@ -1294,11 +1294,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
1294
1294
 
1295
1295
  // Set operation may also have an ORDER BY and LIMIT/OFFSET (in contrast to the ones belonging to
1296
1296
  // each SELECT)
1297
- // If the whole SET has an ORDER BY/LIMIT, wrap the part before that in parentheses
1298
- // (otherwise some SQL implementations (e.g. sqlite) would interpret the ORDER BY/LIMIT as belonging
1299
- // to the last SET argument, not to the whole SET)
1300
1297
  if (query.SET.orderBy || query.SET.limit) {
1301
- result = `(${ result })`;
1302
1298
  if (query.SET.orderBy) {
1303
1299
  const orderBy = query.SET.orderBy.map(entry => renderOrderByEntry(entry, env.withSubPath([ 'orderBy' ]))).join(', ');
1304
1300
  result += `\n${ env.indent }ORDER BY ${ orderBy }`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds-compiler",
3
- "version": "6.5.0",
3
+ "version": "6.5.2",
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)",