@sap/cds-compiler 6.4.6 → 6.5.0
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 +34 -1156
- package/README.md +1 -10
- package/doc/IncompatibleChanges_v5.md +436 -0
- package/doc/IncompatibleChanges_v6.md +659 -0
- package/doc/Versioning.md +3 -7
- package/lib/api/main.js +1 -0
- package/lib/api/options.js +5 -0
- package/lib/api/validate.js +3 -0
- package/lib/base/message-registry.js +23 -0
- package/lib/base/messages.js +1 -1
- package/lib/base/model.js +3 -2
- package/lib/checks/actionsFunctions.js +6 -3
- package/lib/checks/existsInForbiddenPlaces.js +32 -0
- package/lib/checks/validator.js +2 -0
- package/lib/compiler/assert-consistency.js +3 -5
- package/lib/compiler/checks.js +4 -8
- package/lib/compiler/define.js +244 -459
- package/lib/compiler/extend.js +297 -11
- package/lib/compiler/finalize-parse-cdl.js +2 -10
- package/lib/compiler/generate.js +29 -1
- package/lib/compiler/populate.js +21 -63
- package/lib/compiler/propagator.js +1 -2
- package/lib/compiler/resolve.js +2 -12
- package/lib/compiler/shared.js +18 -5
- package/lib/compiler/tweak-assocs.js +13 -9
- package/lib/compiler/utils.js +97 -0
- package/lib/compiler/xpr-rewrite.js +2 -1
- package/lib/edm/annotations/edmJson.js +9 -6
- package/lib/edm/annotations/genericTranslation.js +8 -4
- package/lib/edm/csn2edm.js +3 -4
- package/lib/edm/edmInboundChecks.js +1 -2
- package/lib/edm/edmPreprocessor.js +3 -3
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +1 -1
- package/lib/gen/Dictionary.json +16 -1
- package/lib/json/from-csn.js +4 -6
- package/lib/json/to-csn.js +3 -3
- package/lib/model/csnRefs.js +13 -4
- package/lib/model/enrichCsn.js +4 -2
- package/lib/optionProcessor.js +8 -4
- package/lib/render/utils/sql.js +3 -2
- package/lib/transform/db/applyTransformations.js +1 -1
- package/lib/transform/db/assertUnique.js +3 -3
- package/lib/transform/db/assocsToQueries/normalizeFrom.js +33 -0
- package/lib/transform/db/assocsToQueries/transformExists.js +14 -3
- package/lib/transform/db/assocsToQueries/utils.js +1 -1
- package/lib/transform/db/backlinks.js +4 -4
- package/lib/transform/db/cdsPersistence.js +4 -4
- package/lib/transform/db/constraints.js +4 -4
- package/lib/transform/db/expansion.js +5 -5
- package/lib/transform/db/flattening.js +4 -5
- package/lib/transform/db/rewriteCalculatedElements.js +3 -3
- package/lib/transform/db/temporal.js +11 -11
- package/lib/transform/draft/db.js +2 -0
- package/lib/transform/draft/odata.js +5 -7
- package/lib/transform/effective/flattening.js +1 -2
- package/lib/transform/forOdata.js +3 -3
- package/lib/transform/forRelationalDB.js +1 -1
- package/lib/transform/odata/createForeignKeys.js +1 -2
- package/lib/transform/odata/flattening.js +1 -2
- package/lib/transform/odata/toFinalBaseType.js +52 -55
- package/lib/transform/transformUtils.js +3 -4
- package/package.json +1 -1
- package/doc/CHANGELOG_BETA.md +0 -464
- package/doc/CHANGELOG_DEPRECATED.md +0 -235
package/lib/compiler/resolve.js
CHANGED
|
@@ -75,7 +75,6 @@ const $location = Symbol.for( 'cds.$location' );
|
|
|
75
75
|
const $inferred = Symbol.for( 'cds.$inferred' );
|
|
76
76
|
|
|
77
77
|
// TODO: make this part of specExpected in shared.js
|
|
78
|
-
// (standard: not possible on last if !ref.$expected → ref.scope: '$exists')
|
|
79
78
|
const expWithFilter = [ 'from', 'expand', 'inline' ];
|
|
80
79
|
|
|
81
80
|
// Export function of this file. Resolve type references in augmented CSN
|
|
@@ -1557,16 +1556,6 @@ function resolve( model ) {
|
|
|
1557
1556
|
}
|
|
1558
1557
|
|
|
1559
1558
|
function resolveExprPath( expr, expected, user ) {
|
|
1560
|
-
// TODO: re-think this $expected: 'exists' thing
|
|
1561
|
-
if (expr.$expected === 'exists') {
|
|
1562
|
-
if (expected !== 'annotation') { // `exists e[…]` allowed in annotation expressions
|
|
1563
|
-
error( 'expr-unexpected-exists', [ expr.location, user ], {},
|
|
1564
|
-
'An EXISTS predicate is not expected here' );
|
|
1565
|
-
}
|
|
1566
|
-
// We complain about the EXISTS before, as EXISTS subquery is also not supported
|
|
1567
|
-
// TODO: location of EXISTS, TODO: really do this in define.js
|
|
1568
|
-
expr.$expected = 'approved-exists'; // only complain once
|
|
1569
|
-
}
|
|
1570
1559
|
const ref = resolvePath( expr, expected, user );
|
|
1571
1560
|
|
|
1572
1561
|
if (expected === 'annotation') {
|
|
@@ -1588,9 +1577,10 @@ function resolve( model ) {
|
|
|
1588
1577
|
last._navigation?.kind === '$tableAlias') // error already reported
|
|
1589
1578
|
return ref;
|
|
1590
1579
|
|
|
1591
|
-
if (expr.$
|
|
1580
|
+
if (expr.$syntax === 'after-exists') {
|
|
1592
1581
|
if (last.where?.args?.length === 0) {
|
|
1593
1582
|
// at the moment, empty filter is not allowed on last path step
|
|
1583
|
+
// TODO: allow it at some places
|
|
1594
1584
|
reportUnexpectedArgsAndFilter( last, expected, user, null, 'last-empty-filter' );
|
|
1595
1585
|
}
|
|
1596
1586
|
return ref;
|
package/lib/compiler/shared.js
CHANGED
|
@@ -64,6 +64,17 @@ function fns( model ) {
|
|
|
64
64
|
notFound: undefinedForAnnotate,
|
|
65
65
|
accept: extendableArtifact,
|
|
66
66
|
},
|
|
67
|
+
'annotate-sec': {
|
|
68
|
+
isMainRef: 'all',
|
|
69
|
+
lexical: userBlock,
|
|
70
|
+
dynamic: modelDefinitions,
|
|
71
|
+
notFound: undefinedDefinition,
|
|
72
|
+
messageMap: {
|
|
73
|
+
'ref-undefined-art': 'ext-undefined-art-sec',
|
|
74
|
+
'ref-undefined-def': 'ext-undefined-def-sec',
|
|
75
|
+
},
|
|
76
|
+
accept: extendableArtifact,
|
|
77
|
+
},
|
|
67
78
|
extend: {
|
|
68
79
|
isMainRef: 'no-generated',
|
|
69
80
|
lexical: userBlock,
|
|
@@ -233,6 +244,7 @@ function fns( model ) {
|
|
|
233
244
|
check: checkAssocOn,
|
|
234
245
|
param: () => '$scopePar', // TODO: check that assocs containing param in ON is not published
|
|
235
246
|
},
|
|
247
|
+
'rewrite-on': {}, // only for traversal when rewriting on condition
|
|
236
248
|
'orderBy-ref': {
|
|
237
249
|
lexical: tableAliasesAndSelf,
|
|
238
250
|
dollar: true,
|
|
@@ -382,7 +394,7 @@ function fns( model ) {
|
|
|
382
394
|
}
|
|
383
395
|
if (expr.args) {
|
|
384
396
|
const args = Array.isArray( expr.args ) ? expr.args : Object.values( expr.args );
|
|
385
|
-
for (const arg of args
|
|
397
|
+
for (const arg of args) {
|
|
386
398
|
if (traverseExpr( arg, exprCtx, user, callback ) === traverseExpr.STOP)
|
|
387
399
|
return traverseExpr.STOP;
|
|
388
400
|
}
|
|
@@ -1283,11 +1295,11 @@ function fns( model ) {
|
|
|
1283
1295
|
|
|
1284
1296
|
// Functions called via semantics.notFound: -----------------------------------
|
|
1285
1297
|
|
|
1286
|
-
function undefinedDefinition( user, item, valid, _dict, prev ) {
|
|
1298
|
+
function undefinedDefinition( user, item, valid, _dict, prev, _path, semantics ) {
|
|
1287
1299
|
// in a CSN source or for `using`, only one env was tested (valid.length 1) :
|
|
1288
1300
|
const art = (!prev) ? item.id : searchName( prev, item.id, 'absolute' );
|
|
1289
1301
|
signalNotFound( (valid.length > 1 ? 'ref-undefined-art' : 'ref-undefined-def'),
|
|
1290
|
-
[ item.location, user ], valid, { art } );
|
|
1302
|
+
[ item.location, user ], valid, { art }, semantics );
|
|
1291
1303
|
// TODO: improve text, use text variant for: "or builtin" or "definitions" or none
|
|
1292
1304
|
}
|
|
1293
1305
|
|
|
@@ -1876,8 +1888,9 @@ function fns( model ) {
|
|
|
1876
1888
|
}
|
|
1877
1889
|
const index = userTargetElementPathIndex( user, path );
|
|
1878
1890
|
checkOnlyForeignKeyNavigation( user, path, index );
|
|
1891
|
+
// TODO: did we check for no filter/args, no `exists` etc?
|
|
1879
1892
|
const last = path[path.length - 1];
|
|
1880
|
-
if (!last.where && ref._artifact?.on) { // filter already complained about
|
|
1893
|
+
if (!last.where && ref._artifact?.on) { // filter already complained about - TODO: where?
|
|
1881
1894
|
const target = index > 0 && index < path.length && ref._artifact?.target;
|
|
1882
1895
|
const msg = (target?._artifact === user._main) ? 'self' : 'unmanaged';
|
|
1883
1896
|
error( 'ref-unexpected-assoc', [ last.location, user ],
|
|
@@ -2016,7 +2029,7 @@ function fns( model ) {
|
|
|
2016
2029
|
}
|
|
2017
2030
|
|
|
2018
2031
|
function checkNoUnmanaged( ref, user, self, messageVariant = 'unmanaged' ) {
|
|
2019
|
-
if (ref._artifact?.on &&
|
|
2032
|
+
if (ref._artifact?.on && ref.$syntax !== 'after-exists') {
|
|
2020
2033
|
const { path } = ref;
|
|
2021
2034
|
const last = path[path.length - 1];
|
|
2022
2035
|
if (self && last.where) // already complained about filter
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
const {
|
|
6
6
|
forEachGeneric,
|
|
7
7
|
forEachInOrder,
|
|
8
|
-
isBetaEnabled,
|
|
9
8
|
} = require('../base/model');
|
|
10
9
|
const { dictLocation, weakLocation, weakRefLocation } = require('../base/location');
|
|
11
10
|
|
|
@@ -138,10 +137,10 @@ function tweakAssocs( model ) {
|
|
|
138
137
|
|
|
139
138
|
function handleQueryElements( column ) {
|
|
140
139
|
rewriteAssociationCheck( column );
|
|
141
|
-
if (
|
|
142
|
-
return;
|
|
140
|
+
if (model.options.noDollarCalc || column._columnParent)
|
|
141
|
+
return; // no $calc supported with inline
|
|
143
142
|
const { value } = column; // `value` = column expression
|
|
144
|
-
if (!value ||
|
|
143
|
+
if (!value || value.path) // not with references
|
|
145
144
|
return;
|
|
146
145
|
// TODO: what about non-simple refs (assocs, even with filter/args)?
|
|
147
146
|
|
|
@@ -946,6 +945,8 @@ function tweakAssocs( model ) {
|
|
|
946
945
|
* for item.
|
|
947
946
|
*/
|
|
948
947
|
function rewriteColumnPath( ref, column ) {
|
|
948
|
+
if (ref.query)
|
|
949
|
+
return traverseExpr.STOP; // sub queries rewrite not supported
|
|
949
950
|
if (!ref._artifact)
|
|
950
951
|
return null;
|
|
951
952
|
const root = ref.path?.[0];
|
|
@@ -1010,9 +1011,12 @@ function tweakAssocs( model ) {
|
|
|
1010
1011
|
state = setArtifactLink( i, elem );
|
|
1011
1012
|
}
|
|
1012
1013
|
else {
|
|
1013
|
-
state = rewriteItem( state, i, assoc );
|
|
1014
|
-
if (
|
|
1015
|
-
|
|
1014
|
+
state = rewriteItem( state, i, assoc, !location );
|
|
1015
|
+
if (state && state !== true)
|
|
1016
|
+
continue;
|
|
1017
|
+
if (!state && !location)
|
|
1018
|
+
return true;
|
|
1019
|
+
break;
|
|
1016
1020
|
}
|
|
1017
1021
|
}
|
|
1018
1022
|
if (state !== true)
|
|
@@ -1032,7 +1036,7 @@ function tweakAssocs( model ) {
|
|
|
1032
1036
|
* @param item Path segment to rewrite.
|
|
1033
1037
|
* @param assoc Published association of query.
|
|
1034
1038
|
*/
|
|
1035
|
-
function rewriteItem( elem, item, assoc ) {
|
|
1039
|
+
function rewriteItem( elem, item, assoc, noError ) {
|
|
1036
1040
|
if (!elem._redirected)
|
|
1037
1041
|
return true;
|
|
1038
1042
|
let name = item.id;
|
|
@@ -1055,7 +1059,7 @@ function tweakAssocs( model ) {
|
|
|
1055
1059
|
if (env?.target)
|
|
1056
1060
|
env = env.target._artifact?._effectiveType;
|
|
1057
1061
|
const found = setArtifactLink( item, env?.elements?.[name] );
|
|
1058
|
-
if (found)
|
|
1062
|
+
if (found || noError)
|
|
1059
1063
|
return found;
|
|
1060
1064
|
|
|
1061
1065
|
const isExplicit = elem.target && !elem.target.$inferred;
|
package/lib/compiler/utils.js
CHANGED
|
@@ -134,6 +134,28 @@ function proxyCopyMembers( art, dictProp, originDict, location, kind, tmpDepreca
|
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
// Initialization (define.js), shared with extend.js: ---------------------------
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Initialize artifact links inside `obj.items` (for nested ones as well).
|
|
141
|
+
* Does nothing, it `obj.items` does not exist.
|
|
142
|
+
*
|
|
143
|
+
* @param {XSN.Artifact} obj
|
|
144
|
+
* @param {object} block
|
|
145
|
+
* @return {XSN.Artifact}
|
|
146
|
+
*/
|
|
147
|
+
function initItemsLinks( obj, block ) {
|
|
148
|
+
let { items } = obj;
|
|
149
|
+
while (items) {
|
|
150
|
+
setLink( items, '_outer', obj );
|
|
151
|
+
setLink( items, '_parent', obj._parent );
|
|
152
|
+
setLink( items, '_block', block );
|
|
153
|
+
obj = items;
|
|
154
|
+
items = obj.items;
|
|
155
|
+
}
|
|
156
|
+
return obj;
|
|
157
|
+
}
|
|
158
|
+
|
|
137
159
|
/**
|
|
138
160
|
* Set the member `elem` to have a _parent link to `parent` and a corresponding
|
|
139
161
|
* _main link. Also set the member's name accordingly, where argument `name`
|
|
@@ -162,6 +184,76 @@ function createAndLinkCalcDepElement( elem ) {
|
|
|
162
184
|
setLink( r, '_outer', elem );
|
|
163
185
|
}
|
|
164
186
|
|
|
187
|
+
function initExprAnnoBlock( art, block ) {
|
|
188
|
+
// remark: `art` could also be the extension
|
|
189
|
+
for (const prop in art) {
|
|
190
|
+
if (prop.charAt(0) !== '@')
|
|
191
|
+
continue;
|
|
192
|
+
const anno = art[prop];
|
|
193
|
+
// _block links needed for `cast( … as Type )`, `[ ..., cast( … as Type ) ]`
|
|
194
|
+
if (anno.literal === 'array') {
|
|
195
|
+
anno.kind = '$annotation';
|
|
196
|
+
for (const item of anno.val)
|
|
197
|
+
setLink( item, '_block', block );
|
|
198
|
+
}
|
|
199
|
+
else if (anno.$tokenTexts) {
|
|
200
|
+
// remark: it wouldn't hurt to set it always...
|
|
201
|
+
anno.kind = '$annotation';
|
|
202
|
+
setLink( anno, '_block', block );
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function initDollarSelf( art ) {
|
|
208
|
+
// TODO: use setMemberParent() ?
|
|
209
|
+
const self = {
|
|
210
|
+
name: { id: '$self', location: art.location },
|
|
211
|
+
kind: '$self',
|
|
212
|
+
location: art.location,
|
|
213
|
+
};
|
|
214
|
+
setLink( self, '_parent', art );
|
|
215
|
+
setLink( self, '_main', art ); // used on main artifact
|
|
216
|
+
setLink( self, '_origin', art );
|
|
217
|
+
art.$tableAliases = Object.create( null );
|
|
218
|
+
art.$tableAliases.$self = self;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function initDollarParameters( art ) {
|
|
222
|
+
// TODO: use setMemberParent() ?
|
|
223
|
+
const parameters = {
|
|
224
|
+
name: { id: '$parameters' },
|
|
225
|
+
kind: '$parameters',
|
|
226
|
+
location: art.location,
|
|
227
|
+
deprecated: true, // hide in code completion
|
|
228
|
+
};
|
|
229
|
+
setLink( parameters, '_parent', art );
|
|
230
|
+
setLink( parameters, '_main', art );
|
|
231
|
+
// Search for :const after :param. If there will be a possibility in the
|
|
232
|
+
// future that we can extend <query>.columns, we must be sure to use
|
|
233
|
+
// _block of that new column after :param (or just allow $parameters there).
|
|
234
|
+
setLink( parameters, '_block', art._block );
|
|
235
|
+
if (art.params) {
|
|
236
|
+
parameters.elements = art.params;
|
|
237
|
+
parameters.$tableAliases = art.params; // TODO: find better name - $lexical?
|
|
238
|
+
}
|
|
239
|
+
art.$tableAliases.$parameters = parameters;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function initBoundSelfParam( params, main ) {
|
|
243
|
+
if (!params)
|
|
244
|
+
return;
|
|
245
|
+
const first = params[Object.keys( params )[0] || ''];
|
|
246
|
+
const type = first?.type || first?.items?.type; // this sequence = no derived type
|
|
247
|
+
const path = type?.path;
|
|
248
|
+
if (path?.length === 1 && path[0]?.id === '$self') { // TODO: no where: ?
|
|
249
|
+
const $self = main.$tableAliases?.$self ||
|
|
250
|
+
main.kind === 'extend' && { name: { id: '$self' } };
|
|
251
|
+
// remark: an 'extend' has no "table alias" `$self` (relevant for parse-cdl)
|
|
252
|
+
setLink( type, '_artifact', $self );
|
|
253
|
+
setLink( path[0], '_artifact', $self );
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
165
257
|
/**
|
|
166
258
|
* Adds a dependency user -> art with the given location.
|
|
167
259
|
*
|
|
@@ -702,8 +794,13 @@ module.exports = {
|
|
|
702
794
|
proxyCopyMembers,
|
|
703
795
|
dependsOn,
|
|
704
796
|
dependsOnSilent,
|
|
797
|
+
initItemsLinks,
|
|
705
798
|
setMemberParent,
|
|
706
799
|
createAndLinkCalcDepElement,
|
|
800
|
+
initExprAnnoBlock,
|
|
801
|
+
initDollarSelf,
|
|
802
|
+
initDollarParameters,
|
|
803
|
+
initBoundSelfParam,
|
|
707
804
|
storeExtension,
|
|
708
805
|
withAssociation,
|
|
709
806
|
pathName,
|
|
@@ -395,7 +395,8 @@ function xprRewriteFns( model ) {
|
|
|
395
395
|
}
|
|
396
396
|
}
|
|
397
397
|
|
|
398
|
-
if (isSimpleSelectItem && model.options.testMode
|
|
398
|
+
if (isSimpleSelectItem && model.options.testMode &&
|
|
399
|
+
destination.value?.path[0]._navigation?.kind !== 'mixin')
|
|
399
400
|
throw new CompilerAssertion(`select item has no table alias: ${ JSON.stringify(destination.value.path) }`);
|
|
400
401
|
|
|
401
402
|
if (isAnnoPathAbsolute( expr ))
|
|
@@ -93,8 +93,6 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
93
93
|
//----------------------------------
|
|
94
94
|
// operators not supported as dynamic expression
|
|
95
95
|
'.': notADynExpr,
|
|
96
|
-
isNull: (p, o) => notADynExpr(p, o, null, null, null, null, 'is null'),
|
|
97
|
-
isNotNull: (p, o) => notADynExpr(p, o, null, null, null, null, 'is not null'),
|
|
98
96
|
exists: notADynExpr,
|
|
99
97
|
SELECT: notADynExpr,
|
|
100
98
|
SET: (p, o) => notADynExpr(p, o, null, null, null, null, 'UNION'),
|
|
@@ -302,6 +300,8 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
302
300
|
transform.$Mul = noOp;
|
|
303
301
|
transform['/'] = op('$DivBy');
|
|
304
302
|
transform.$DivBy = noOp;
|
|
303
|
+
transform.isNull = op('$Eq');
|
|
304
|
+
transform.isNotNull = op('$Ne');
|
|
305
305
|
// $Div, $Mod are functions
|
|
306
306
|
//----------------------------------
|
|
307
307
|
// LITERALS
|
|
@@ -885,10 +885,13 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
885
885
|
if (xpr.length === 2 && (xpr[0] === '+' || xpr[0] === '-' || xpr[0] === 'not' || xpr[0] === 'new'))
|
|
886
886
|
return { [xpr[0]]: xpr[1] };
|
|
887
887
|
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
return xpr;
|
|
891
|
-
|
|
888
|
+
// is null
|
|
889
|
+
if (xpr.length === 3 && xpr[1] === 'is' && xpr[2] === 'null')
|
|
890
|
+
return { isNull: [ xpr[0], { val: null } ] };
|
|
891
|
+
|
|
892
|
+
// is not null
|
|
893
|
+
if (xpr.length === 4 && xpr[1] === 'is' && xpr[2] === 'not' && xpr[3] === 'null')
|
|
894
|
+
return { isNotNull: [ xpr[0], { val: null } ] };
|
|
892
895
|
|
|
893
896
|
// binary operators: '=', '<>', 'like', ...
|
|
894
897
|
if (xpr.length === 3 && typeof xpr[1] === 'string')
|
|
@@ -916,8 +916,8 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
|
|
|
916
916
|
}
|
|
917
917
|
// expression
|
|
918
918
|
const res = handleExpression(cAnnoValue['='], dTypeName);
|
|
919
|
-
oTarget.setXml(
|
|
920
|
-
oTarget.setJSON(
|
|
919
|
+
oTarget.setXml({ [res.name]: res.value });
|
|
920
|
+
oTarget.setJSON({ [res.name]: res.value });
|
|
921
921
|
}
|
|
922
922
|
else if (cAnnoValue['#'] !== undefined) {
|
|
923
923
|
const enumSymbol = cAnnoValue['#'];
|
|
@@ -968,9 +968,13 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
|
|
|
968
968
|
}
|
|
969
969
|
else if (cAnnoValue.$edmJson) {
|
|
970
970
|
// "pseudo-structure" used for embedding a piece of JSON that represents "OData CSDL, JSON Representation"
|
|
971
|
-
|
|
971
|
+
const edmNode = handleEdmJson(cAnnoValue.$edmJson, msg);
|
|
972
|
+
if (edmNode && edmNode._kind === 'Path' && typeof edmNode._value === 'string' && !edmNode._children.length)
|
|
973
|
+
oTarget.setXml({ [edmNode._kind]: edmNode._value });
|
|
974
|
+
else
|
|
975
|
+
oTarget.append(edmNode);
|
|
972
976
|
}
|
|
973
|
-
else if (
|
|
977
|
+
else if (Object.keys(cAnnoValue).filter( x => x[0] !== '@' ).length === 0) {
|
|
974
978
|
// object consists only of properties starting with "@", no $value
|
|
975
979
|
setProp(oTarget, '$isInvalid', true);
|
|
976
980
|
message('odata-anno-value', msg.location,
|
package/lib/edm/csn2edm.js
CHANGED
|
@@ -49,7 +49,6 @@ function csn2edm( _csn, serviceName, _options, messageFunctions ) {
|
|
|
49
49
|
function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
50
50
|
// get us a fresh model copy that we can work with
|
|
51
51
|
const csn = cloneFullCsn(_csn, _options);
|
|
52
|
-
const special$self = !csn?.definitions?.$self && '$self';
|
|
53
52
|
messageFunctions.setModel(csn);
|
|
54
53
|
|
|
55
54
|
const {
|
|
@@ -689,7 +688,7 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
689
688
|
}
|
|
690
689
|
}
|
|
691
690
|
|
|
692
|
-
if (options.isV2()
|
|
691
|
+
if (options.isV2()) {
|
|
693
692
|
if (elementCsn._edmParentCsn.name === `${ elementCsn._edmParentCsn.$mySchemaName }.DraftAdministrativeData`)
|
|
694
693
|
elementCsn['@UI.HiddenFilter'] ??= true;
|
|
695
694
|
}
|
|
@@ -762,7 +761,7 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
762
761
|
const entries = Object.entries(actionCsn.params);
|
|
763
762
|
const firstParam = entries[0][1];
|
|
764
763
|
const type = firstParam?.items?.type || firstParam?.type;
|
|
765
|
-
if (type ===
|
|
764
|
+
if (type === '$self') {
|
|
766
765
|
bpName = entries[0][0];
|
|
767
766
|
setProp(actionCsn, '$bindingParam', firstParam);
|
|
768
767
|
// preserve the original type (as it is the key to reqDefs.defintions)
|
|
@@ -938,7 +937,7 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
938
937
|
if (parameterCsn['@cds.api.ignore'])
|
|
939
938
|
return;
|
|
940
939
|
const type = parameterCsn?.items?.type || parameterCsn?.type;
|
|
941
|
-
if (i === 0 && type ===
|
|
940
|
+
if (i === 0 && type === '$self') {
|
|
942
941
|
// skip and remove the first parameter if it is a $self binding parameter to
|
|
943
942
|
// omit annotation rendering later on
|
|
944
943
|
setProp(actionCsn, '$bindingParam', parameterCsn);
|
|
@@ -130,12 +130,11 @@ function inboundQualificationChecks( csn, options, messageFunctions,
|
|
|
130
130
|
// we need to know if the first path step is the bindind param
|
|
131
131
|
// for the rejection of V2 paths where the BP is ignored
|
|
132
132
|
function markBindingParamPaths( action, loc ) {
|
|
133
|
-
const special$self = !csn?.definitions?.$self && '$self';
|
|
134
133
|
if (action.params) {
|
|
135
134
|
const params = Object.entries(action.params);
|
|
136
135
|
const firstParam = params[0][1];
|
|
137
136
|
const type = firstParam?.items?.type || firstParam?.type;
|
|
138
|
-
if (type ===
|
|
137
|
+
if (type === '$self') {
|
|
139
138
|
const bindingParamName = params[0][0];
|
|
140
139
|
const markBindingParam = {
|
|
141
140
|
ref: (parent, prop, xpr) => {
|
|
@@ -40,7 +40,6 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
|
|
|
40
40
|
const {
|
|
41
41
|
info, warning, error, message,
|
|
42
42
|
} = messageFunctions;
|
|
43
|
-
const special$self = !csn?.definitions?.$self && '$self';
|
|
44
43
|
const csnUtils = getUtils(csn);
|
|
45
44
|
|
|
46
45
|
// proxies are merged into the final model after all proxy elements are collected
|
|
@@ -2077,7 +2076,8 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
|
|
|
2077
2076
|
|
|
2078
2077
|
function iterateParams( action, location ) {
|
|
2079
2078
|
let optPns = [];
|
|
2080
|
-
const isBP = p => (p.items?.type || p.type) ===
|
|
2079
|
+
const isBP = p => (p.items?.type || p.type) === '$self' &&
|
|
2080
|
+
p === Object.values( action.params )[0];
|
|
2081
2081
|
|
|
2082
2082
|
if (action.params) {
|
|
2083
2083
|
Object.entries(action.params).forEach(([ pn, p ]) => {
|
|
@@ -2126,7 +2126,7 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
|
|
|
2126
2126
|
}
|
|
2127
2127
|
else if (action.kind === 'function') {
|
|
2128
2128
|
// this is a mandatory parameter, warn about all previously collected optional parameters
|
|
2129
|
-
if (optPns.
|
|
2129
|
+
if (optPns.length)
|
|
2130
2130
|
error('odata-parameter-order', location.concat(pn));
|
|
2131
2131
|
optPns = [];
|
|
2132
2132
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
2af41f8c84ca1fa40a50b8c58903dadc
|
package/lib/gen/CdlParser.js
CHANGED
|
@@ -3827,7 +3827,7 @@ case 645:this.s=651;{ this.attachLocation( $.expr ); }continue
|
|
|
3827
3827
|
case 646:if(this.valuePath(_={},651)){e=_.expr; this.valuePathAstWithNew( $.expr, e ); }continue
|
|
3828
3828
|
case 647:switch(this.l()){
|
|
3829
3829
|
case'(':if(this.c(648))open=this.lb();continue
|
|
3830
|
-
case'Id':if(this.valuePath(_={},651)){e=_.expr; e = this.valuePathAst( e ); e.$
|
|
3830
|
+
case'Id':if(this.valuePath(_={},651)){e=_.expr; e = this.valuePathAst( e ); e.$syntax = 'after-exists';
|
|
3831
3831
|
$.expr.args.push( e ); this.attachLocation( $.expr ); }continue
|
|
3832
3832
|
case'?':if(this.c(651)){ this.csnParseOnly( 'syntax-unsupported-param', -1, { '#': 'dynamic', code: '?' } );
|
|
3833
3833
|
$.expr.args.push( { param: this.valueWithLocation(), scope: 'param' } ); this.attachLocation( $.expr ); }continue
|
package/lib/gen/Dictionary.json
CHANGED
|
@@ -596,7 +596,7 @@
|
|
|
596
596
|
"Property",
|
|
597
597
|
"Parameter"
|
|
598
598
|
],
|
|
599
|
-
"Type": "Edm.
|
|
599
|
+
"Type": "Edm.PrimitiveType"
|
|
600
600
|
},
|
|
601
601
|
"Common.FieldControl": {
|
|
602
602
|
"AppliesTo": [
|
|
@@ -910,6 +910,13 @@
|
|
|
910
910
|
"$deprecationText": "Use terms [Aggregation.RecursiveHierarchy](https://github.com/oasis-tcs/odata-vocabularies/blob/main/vocabularies/Org.OData.Aggregation.V1.md#RecursiveHierarchy) and [Hierarchy.RecursiveHierarchy](https://github.com/SAP/odata-vocabularies/blob/main/vocabularies/Hierarchy.md#RecursiveHierarchy) instead",
|
|
911
911
|
"Type": "Common.RecursiveHierarchyType"
|
|
912
912
|
},
|
|
913
|
+
"Common.ReferentialConstraint": {
|
|
914
|
+
"$experimental": true,
|
|
915
|
+
"AppliesTo": [
|
|
916
|
+
"NavigationProperty"
|
|
917
|
+
],
|
|
918
|
+
"Type": "Collection(Common.ReferentialConstraintType)"
|
|
919
|
+
},
|
|
913
920
|
"Common.RelatedRecursiveHierarchy": {
|
|
914
921
|
"AppliesTo": [
|
|
915
922
|
"Property"
|
|
@@ -3345,6 +3352,14 @@
|
|
|
3345
3352
|
"NodeDrillStateProperty": "Edm.PropertyPath"
|
|
3346
3353
|
}
|
|
3347
3354
|
},
|
|
3355
|
+
"Common.ReferentialConstraintType": {
|
|
3356
|
+
"$experimental": true,
|
|
3357
|
+
"$kind": "ComplexType",
|
|
3358
|
+
"Properties": {
|
|
3359
|
+
"Property": "Edm.PropertyPath",
|
|
3360
|
+
"ReferencedProperty": "Edm.PropertyPath"
|
|
3361
|
+
}
|
|
3362
|
+
},
|
|
3348
3363
|
"Common.SAPObjectNodeTypeType": {
|
|
3349
3364
|
"$experimental": true,
|
|
3350
3365
|
"$kind": "ComplexType",
|
package/lib/json/from-csn.js
CHANGED
|
@@ -1682,15 +1682,13 @@ function exprOrString( val, spec ) {
|
|
|
1682
1682
|
: expr( val, spec );
|
|
1683
1683
|
}
|
|
1684
1684
|
|
|
1685
|
-
// mark path argument of 'exists' predicate with $
|
|
1685
|
+
// mark path argument of 'exists' predicate with $syntax:'after-exists'
|
|
1686
1686
|
function exprArgs( cond, spec ) {
|
|
1687
1687
|
const rxsn = arrayOf( exprOrString )( cond, spec );
|
|
1688
|
-
// TODO: do that in definer.js, neither here nor in CDL parser
|
|
1689
1688
|
if (Array.isArray( rxsn )) {
|
|
1690
|
-
for (let i = 0; i < rxsn.length - 1;
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
rxsn[++i].$expected = 'exists';
|
|
1689
|
+
for (let i = 0; i < rxsn.length - 1;) {
|
|
1690
|
+
if (cond[i++] === 'exists' && rxsn[i].path)
|
|
1691
|
+
rxsn[i].$syntax = 'after-exists';
|
|
1694
1692
|
}
|
|
1695
1693
|
}
|
|
1696
1694
|
return rxsn;
|
package/lib/json/to-csn.js
CHANGED
|
@@ -1172,15 +1172,15 @@ function enumValueOrCalc( v, csn, node, prop ) {
|
|
|
1172
1172
|
if (node.kind === 'enum') {
|
|
1173
1173
|
Object.assign( csn, expression( v ) );
|
|
1174
1174
|
}
|
|
1175
|
-
else if (
|
|
1176
|
-
return true
|
|
1175
|
+
else if (prop === '$calc') {
|
|
1176
|
+
return v === true || expression( v );
|
|
1177
1177
|
}
|
|
1178
1178
|
// In XSN, there are combined elem/column objects: do not represent column
|
|
1179
1179
|
// expression when presented as element in CSN
|
|
1180
1180
|
|
|
1181
1181
|
// node.$syntax set in define.el(!), but not inside an `extend`, a _parent might
|
|
1182
1182
|
// not be set always for parse-only, especially with CSN input
|
|
1183
|
-
else if (node.$syntax === 'calc' ||
|
|
1183
|
+
else if (node.$syntax === 'calc' ||
|
|
1184
1184
|
!node._parent || node._parent.kind === 'extend') {
|
|
1185
1185
|
const stored = v.stored ? { stored: value(v.stored) } : {};
|
|
1186
1186
|
return Object.assign( stored, expression( v ) );
|
package/lib/model/csnRefs.js
CHANGED
|
@@ -519,6 +519,8 @@ function csnRefs( csn, universalReady ) {
|
|
|
519
519
|
* Return the object pointing to by the artifact reference (in 'type',
|
|
520
520
|
* 'includes', 'target'). For `from`, use artifactRefFrom()!
|
|
521
521
|
*
|
|
522
|
+
* Warning: do not use it for the binding parameter if the ref is `$self`.
|
|
523
|
+
*
|
|
522
524
|
* @param {CSN.ArtifactReferencePath|string} ref
|
|
523
525
|
* @param {any} [notFound] Value that is returned in case the artifact reference
|
|
524
526
|
* could not be found.
|
|
@@ -612,9 +614,16 @@ function csnRefs( csn, universalReady ) {
|
|
|
612
614
|
|
|
613
615
|
function getOriginRaw( art ) {
|
|
614
616
|
if (art.type) { // TODO: make robust against "linked" = only direct
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
617
|
+
if (art.type !== '$self')
|
|
618
|
+
return artifactRef( art.type, BUILTIN_TYPE );
|
|
619
|
+
const action = boundActionOrMain( art );
|
|
620
|
+
const binding = action?.params && Object.values( action.params )[0];
|
|
621
|
+
// binding parameter must be typed with `$self` or `many $self`:
|
|
622
|
+
const entity = binding && (art === binding || art === binding.items) &&
|
|
623
|
+
getCache( action, '_parent' );
|
|
624
|
+
// if name-deprecated-$self is a non-config error, this could be simplified to:
|
|
625
|
+
// const entity = action?.params && getCache( action, '_parent' );
|
|
626
|
+
return entity || artifactRef( art.type, BUILTIN_TYPE );
|
|
618
627
|
}
|
|
619
628
|
if (typeof art.$origin === 'object') // null, […], {…}
|
|
620
629
|
return getOriginExplicit( art.$origin );
|
|
@@ -798,7 +807,7 @@ function csnRefs( csn, universalReady ) {
|
|
|
798
807
|
}
|
|
799
808
|
|
|
800
809
|
if (!qcache)
|
|
801
|
-
throw new CompilerAssertion( `
|
|
810
|
+
throw new CompilerAssertion( `For semantics '${ refCtx }', query not in cache at: ${ locationString(query.$location) }` );
|
|
802
811
|
|
|
803
812
|
if (semantics.dynamic === 'query') {
|
|
804
813
|
// TODO: for ON condition in expand, would need to use cached _element
|
package/lib/model/enrichCsn.js
CHANGED
|
@@ -69,7 +69,6 @@ function enrichCsn( csn, options = {} ) {
|
|
|
69
69
|
// options.enrichCsn = 'DEBUG';
|
|
70
70
|
let $$cacheObjectNumber = 0; // for debugging
|
|
71
71
|
const debugLocationInfo = options.enrichCsn === 'DEBUG' && Object.create(null);
|
|
72
|
-
const special$self = !csn.definitions.$self && '$self';
|
|
73
72
|
|
|
74
73
|
setLocations( csn, false, null );
|
|
75
74
|
const {
|
|
@@ -180,7 +179,10 @@ function enrichCsn( csn, options = {} ) {
|
|
|
180
179
|
parent[`_${ prop }`] = ref.map( r => refLocation( artifactRef( r, notFound ) ) );
|
|
181
180
|
}
|
|
182
181
|
else if (typeof ref === 'string') {
|
|
183
|
-
|
|
182
|
+
// Don't use `artifactRef( 'self' )` for binding parameter. Because it is not
|
|
183
|
+
// easily recognizable here, always omit `_type` property for `$self` even if
|
|
184
|
+
// there is a type named `$self` (which would induce a configurable error):
|
|
185
|
+
if (!ref.startsWith( 'cds.') && ref !== '$self')
|
|
184
186
|
parent[`_${ prop }`] = refLocation( artifactRef( ref, notFound ) );
|
|
185
187
|
}
|
|
186
188
|
else if (!ref.elements) {
|
package/lib/optionProcessor.js
CHANGED
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
/* eslint @stylistic/max-len: 0 */
|
|
4
4
|
|
|
5
|
-
// Remarks
|
|
6
|
-
// - The specification is client-tool centric (bin/cdsc.js):
|
|
5
|
+
// Remarks (`git blame` on these remarks does not reveal the options designer)
|
|
6
|
+
// - The specification is client-tool centric (../bin/cdsc.js):
|
|
7
7
|
// an option named `fooBar` is “produced” by `.option(' --foo-bar')`.
|
|
8
8
|
// - Also list the option in the `help` text, used with `cdsc -h`.
|
|
9
|
-
// -
|
|
9
|
+
// - All options must also be added to ./api/options.js.
|
|
10
|
+
// - Specify valid values for non-boolean options in ./api/validate.js.
|
|
11
|
+
// - Beta and deprecated options are specified in ./base/model.js.
|
|
10
12
|
|
|
11
13
|
'use strict';
|
|
12
14
|
|
|
@@ -54,7 +56,8 @@ optionProcessor
|
|
|
54
56
|
.option(' --add-texts-language-assoc')
|
|
55
57
|
.option(' --localized-without-coalesce')
|
|
56
58
|
.option(' --tenant-discriminator')
|
|
57
|
-
.option(' --no-composition-includes')
|
|
59
|
+
.option(' --no-composition-includes') // TODO: missed to be removed in #13762
|
|
60
|
+
.option(' --no-dollar-calc') // for grep: option `noDollarCalc`
|
|
58
61
|
.option(' --default-binary-length <length>')
|
|
59
62
|
.option(' --default-string-length <length>')
|
|
60
63
|
.option(' --struct-xpr')
|
|
@@ -157,6 +160,7 @@ optionProcessor
|
|
|
157
160
|
to "sap.common.Languages" if it exists
|
|
158
161
|
--localized-without-coalesce Omit coalesce in localized convenience views
|
|
159
162
|
--no-composition-includes Do NOT add named aspects to 'includes' property of generated composition entity.
|
|
163
|
+
--no-dollar-calc Don't set $calc property in CSN
|
|
160
164
|
--no-recompile Don't recompile in case of internal errors
|
|
161
165
|
--struct-xpr Write structured expressions to the compiler CSN output (possibly then
|
|
162
166
|
used as input for backends)
|
package/lib/render/utils/sql.js
CHANGED
|
@@ -198,10 +198,11 @@ function _artifactIsProjectionView( csn, artifact ) {
|
|
|
198
198
|
referencedElements[column.ref.at(-1)] = true;
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
-
// Full primary key needs to be projected according to HANA SQL spec
|
|
201
|
+
// Full primary key/not null chain needs to be projected according to HANA SQL spec
|
|
202
|
+
// https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-sql-reference-guide/create-projection-view-statement-data-definition
|
|
202
203
|
for (const elementName in source.elements) {
|
|
203
204
|
const element = source.elements[elementName];
|
|
204
|
-
if (element.key && !referencedElements[elementName])
|
|
205
|
+
if ((element.notNull || element.key) && !referencedElements[elementName])
|
|
205
206
|
return false;
|
|
206
207
|
}
|
|
207
208
|
|
|
@@ -211,7 +211,7 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
|
|
|
211
211
|
*
|
|
212
212
|
* @param {object | Array} node the thing that has _prop
|
|
213
213
|
* @param {string|number} _prop the name of the current property
|
|
214
|
-
* @param {
|
|
214
|
+
* @param {Array} _path The value of node[_prop]
|
|
215
215
|
*/
|
|
216
216
|
function pathRef( node, _prop, _path ) {
|
|
217
217
|
csnPath.push( _prop );
|