@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 +8 -0
- package/bin/cdsc.js +1 -1
- package/lib/base/message-registry.js +13 -13
- package/lib/compiler/define.js +101 -114
- package/lib/compiler/extend.js +13 -15
- package/lib/compiler/finalize-parse-cdl.js +1 -1
- package/lib/compiler/generate.js +4 -4
- package/lib/compiler/kick-start.js +8 -7
- package/lib/compiler/populate.js +4 -7
- package/lib/compiler/resolve.js +2 -1
- package/lib/compiler/utils.js +1 -2
- package/lib/model/csnRefs.js +6 -1
- package/lib/render/toSql.js +0 -4
- package/package.json +1 -1
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
|
-
|
|
963
|
-
|
|
964
|
-
returns: '
|
|
965
|
-
|
|
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 $(
|
|
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 $(
|
|
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 $(
|
|
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 $(
|
|
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
|
package/lib/compiler/define.js
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
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
|
-
//
|
|
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) { //
|
|
267
|
+
if (src.artifacts) { // addBlockArtifact needs usings for context extensions
|
|
269
268
|
src.artifacts = shuffleDict( src.artifacts );
|
|
270
|
-
dictForEach( src.artifacts, a =>
|
|
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 =>
|
|
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
|
|
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
|
|
386
|
+
function addBlockArtifact( art, block, prefix ) {
|
|
388
387
|
if (art.kind === 'using')
|
|
389
388
|
return;
|
|
390
|
-
|
|
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 =>
|
|
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 =>
|
|
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(),
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
|
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',
|
|
1093
|
-
forEachGeneric( obj, 'enum',
|
|
1094
|
-
forEachInOrder( obj, 'foreignKeys',
|
|
1095
|
-
forEachGeneric( parent, 'actions',
|
|
1096
|
-
forEachInOrder( parent, 'params',
|
|
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
|
-
|
|
1096
|
+
initArtifact( parent, returns, '' ); // '' is special name for returns parameter
|
|
1103
1097
|
}
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
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
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
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
|
-
//
|
|
1155
|
-
|
|
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
|
-
|
|
1161
|
-
setLink( elem, '_block',
|
|
1148
|
+
if (!elem._block)
|
|
1149
|
+
setLink( elem, '_block', parent._block );
|
|
1162
1150
|
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
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
|
-
|
|
1158
|
+
// for a correct home path, setMemberParent needed to be called
|
|
1171
1159
|
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
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
|
|
package/lib/compiler/extend.js
CHANGED
|
@@ -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
|
|
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'),
|
|
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'),
|
|
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',
|
|
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',
|
|
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
|
|
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')
|
|
1352
|
-
storeExtension( elem, name, prop, parent
|
|
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,
|
|
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
|
|
45
|
+
initMembers( ext );
|
|
46
46
|
extensions.push( ext );
|
|
47
47
|
}
|
|
48
48
|
}
|
package/lib/compiler/generate.js
CHANGED
|
@@ -31,7 +31,7 @@ function generate( model ) {
|
|
|
31
31
|
const {
|
|
32
32
|
resolvePath,
|
|
33
33
|
resolveUncheckedPath,
|
|
34
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
package/lib/compiler/populate.js
CHANGED
|
@@ -75,7 +75,7 @@ function populate( model ) {
|
|
|
75
75
|
resolvePath,
|
|
76
76
|
nestedElements,
|
|
77
77
|
attachAndEmitValidNames,
|
|
78
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 );
|
package/lib/compiler/resolve.js
CHANGED
|
@@ -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') {
|
|
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 || '' } );
|
package/lib/compiler/utils.js
CHANGED
|
@@ -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
|
|
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, {} );
|
package/lib/model/csnRefs.js
CHANGED
|
@@ -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;
|
package/lib/render/toSql.js
CHANGED
|
@@ -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 }`;
|