@sap/cds-compiler 6.3.6 → 6.4.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 +48 -0
- package/LICENSE +32 -0
- package/README.md +14 -2
- package/bin/cdsse.js +0 -3
- package/doc/CHANGELOG_BETA.md +1 -1
- package/doc/CHANGELOG_DEPRECATED.md +1 -1
- package/lib/base/message-registry.js +7 -0
- package/lib/base/messages.js +1 -1
- package/lib/base/model.js +2 -0
- package/lib/compiler/assert-consistency.js +1 -0
- package/lib/compiler/checks.js +37 -26
- package/lib/compiler/define.js +1 -1
- package/lib/compiler/extend.js +39 -50
- package/lib/compiler/finalize-parse-cdl.js +1 -1
- package/lib/compiler/lsp-api.js +1 -1
- package/lib/compiler/populate.js +2 -2
- package/lib/compiler/propagator.js +29 -6
- package/lib/compiler/resolve.js +13 -3
- package/lib/compiler/shared.js +31 -25
- package/lib/compiler/tweak-assocs.js +86 -28
- package/lib/compiler/xpr-rewrite.js +70 -38
- package/lib/edm/annotations/edmJson.js +206 -37
- package/lib/edm/csn2edm.js +13 -0
- package/lib/edm/edmUtils.js +2 -2
- package/lib/gen/BaseParser.js +106 -72
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +1500 -1509
- package/lib/json/to-csn.js +8 -5
- package/lib/language/genericAntlrParser.js +0 -0
- package/lib/main.js +19 -16
- package/lib/model/csnRefs.js +589 -521
- package/lib/model/csnUtils.js +8 -5
- package/lib/model/enrichCsn.js +1 -0
- package/lib/parsers/AstBuildingParser.js +72 -27
- package/lib/render/toCdl.js +2 -1
- package/lib/render/toHdbcds.js +6 -3
- package/lib/render/toSql.js +5 -0
- package/lib/transform/db/applyTransformations.js +1 -1
- package/lib/transform/db/assertUnique.js +4 -1
- package/lib/transform/db/cdsPersistence.js +17 -18
- package/lib/transform/db/expansion.js +179 -3
- package/lib/transform/db/flattening.js +16 -5
- package/lib/transform/db/rewriteCalculatedElements.js +79 -283
- package/lib/transform/effective/main.js +8 -1
- package/lib/transform/forOdata.js +1 -1
- package/lib/transform/forRelationalDB.js +21 -80
- package/lib/transform/localized.js +65 -110
- package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +89 -63
- package/lib/transform/transformUtils.js +23 -21
- package/lib/transform/translateAssocsToJoins.js +7 -5
- package/lib/transform/tupleExpansion.js +16 -3
- package/package.json +1 -1
- package/doc/DeprecatedOptions_v2.md +0 -150
- package/doc/NameResolution.md +0 -837
- package/lib/transform/parseExpr.js +0 -415
|
@@ -181,6 +181,7 @@ function xprRewriteFns( model ) {
|
|
|
181
181
|
|
|
182
182
|
return {
|
|
183
183
|
rewriteAnnotationsRefs,
|
|
184
|
+
rewriteRefsInExpression,
|
|
184
185
|
};
|
|
185
186
|
|
|
186
187
|
/**
|
|
@@ -189,14 +190,15 @@ function xprRewriteFns( model ) {
|
|
|
189
190
|
* @param {string} [variant]
|
|
190
191
|
*/
|
|
191
192
|
function reportAnnoRewriteError( expr, config, variant = 'std' ) {
|
|
192
|
-
return
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
193
|
+
return !config.tokenExpr || // TODO: some info message ?
|
|
194
|
+
error( 'anno-missing-rewrite', [
|
|
195
|
+
weakLocation( config.target.location ), config.target,
|
|
196
|
+
], {
|
|
197
|
+
'#': variant,
|
|
198
|
+
anno: config.anno,
|
|
199
|
+
art: config.origin,
|
|
200
|
+
elemref: expr,
|
|
201
|
+
});
|
|
200
202
|
}
|
|
201
203
|
|
|
202
204
|
/**
|
|
@@ -217,21 +219,33 @@ function xprRewriteFns( model ) {
|
|
|
217
219
|
if (!anno.kind || anno.$invalidPaths)
|
|
218
220
|
return;
|
|
219
221
|
|
|
220
|
-
|
|
222
|
+
const hasError = rewriteRefsInExpression( target, origin, annoName );
|
|
223
|
+
target.$contains ??= {};
|
|
224
|
+
target.$contains.$annotation ??= {};
|
|
225
|
+
target.$contains.$annotation.$path ||= origin.$contains.$annotation.$path;
|
|
226
|
+
target.$contains.$annotation.$self ||= origin.$contains.$annotation.$self;
|
|
227
|
+
if (hasError)
|
|
228
|
+
anno.$invalidPaths = true; // avoid subsequent errors
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function rewriteRefsInExpression( destination, origin, propName ) {
|
|
232
|
+
// Annotation comes from the destination's type. That's important to know, because
|
|
221
233
|
// path prefixes need to be adapted.
|
|
222
|
-
const fromTargetType =
|
|
223
|
-
// Annotation comes from the
|
|
234
|
+
const fromTargetType = destination.type?._artifact === origin;
|
|
235
|
+
// Annotation comes from the destination's calculated element.
|
|
236
|
+
// A special case propagation rule, e.g
|
|
224
237
|
// for `calcString: String = str;`. We also need to adapt path prefixes.
|
|
225
|
-
const fromCalcElement = !fromTargetType &&
|
|
226
|
-
|
|
238
|
+
const fromCalcElement = !fromTargetType && destination.$calcDepElement &&
|
|
239
|
+
destination.value?._artifact === origin;
|
|
227
240
|
|
|
228
|
-
const { expandedRoot, expandedRootType }
|
|
241
|
+
const { expandedRoot, expandedRootType }
|
|
242
|
+
= !fromTargetType && getExpandRoot( destination ) || {};
|
|
229
243
|
|
|
230
244
|
const config = {
|
|
231
245
|
__proto__: AnnoRewriteConfig.prototype,
|
|
232
|
-
anno:
|
|
233
|
-
target,
|
|
234
|
-
targetRoot: annoRootArt(
|
|
246
|
+
anno: propName,
|
|
247
|
+
target: destination,
|
|
248
|
+
targetRoot: annoRootArt( destination ),
|
|
235
249
|
origin,
|
|
236
250
|
fromTargetType,
|
|
237
251
|
fromCalcElement,
|
|
@@ -239,13 +253,12 @@ function xprRewriteFns( model ) {
|
|
|
239
253
|
expandedRootType,
|
|
240
254
|
};
|
|
241
255
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
anno.$invalidPaths = true; // avoid subsequent errors
|
|
256
|
+
if (propName.charAt( 0 ) === '@')
|
|
257
|
+
return rewriteAnnotationExpr( destination[propName], config );
|
|
258
|
+
return traverseExpr.STOP === traverseExpr(
|
|
259
|
+
destination[propName], 'annoRewrite', destination,
|
|
260
|
+
// eslint-disable-next-line @stylistic/max-len, @stylistic/function-paren-newline
|
|
261
|
+
(e, refCtx) => (rewriteAnnoExpr( e, config, refCtx ) ? traverseExpr.STOP : traverseExpr.SKIP) );
|
|
249
262
|
}
|
|
250
263
|
|
|
251
264
|
/**
|
|
@@ -291,9 +304,11 @@ function xprRewriteFns( model ) {
|
|
|
291
304
|
return reportAnnoRewriteError( expr, config, 'unsupported' );
|
|
292
305
|
|
|
293
306
|
if (root.kind === 'key') {
|
|
294
|
-
//
|
|
295
|
-
//
|
|
296
|
-
//
|
|
307
|
+
// References starting with a foreign key name (only possible in an
|
|
308
|
+
// annotation on a foreign key) have length 1 + the foreign key cannot be
|
|
309
|
+
// renamed → nothing to do. Otherwise, foreign keys cannot be directly
|
|
310
|
+
// addressed, except as `annotate` target. (Even as annotation value for
|
|
311
|
+
// foreign keys, `$self.assoc.target_id` always refers to the target side.)
|
|
297
312
|
return null;
|
|
298
313
|
}
|
|
299
314
|
|
|
@@ -341,8 +356,10 @@ function xprRewriteFns( model ) {
|
|
|
341
356
|
// only understand the string representation. But we only do so for simple strings, which
|
|
342
357
|
// is the case if this path expression has $tokenTexts. It then is of the form `@a: (path)`.
|
|
343
358
|
const isSimpleStep = step => !step.where && !step.args && isSimpleCdlIdentifier( step.id );
|
|
344
|
-
if (expr.path.every(isSimpleStep))
|
|
345
|
-
expr.$tokenTexts = expr.
|
|
359
|
+
if (expr.path.every(isSimpleStep)) {
|
|
360
|
+
expr.$tokenTexts = (expr.scope === 'param' ? ':' : '') +
|
|
361
|
+
expr.path.map( step => step.id ).join('.');
|
|
362
|
+
}
|
|
346
363
|
}
|
|
347
364
|
|
|
348
365
|
if (model.options.testMode) {
|
|
@@ -454,8 +471,10 @@ function xprRewriteFns( model ) {
|
|
|
454
471
|
|
|
455
472
|
for (let i = startIndex; i < expr.path.length; ++i) {
|
|
456
473
|
if (i > startIndex && art.target) {
|
|
457
|
-
//
|
|
458
|
-
// chain from original target to new one.
|
|
474
|
+
// If the current artifact is an association, we need to respect the redirection
|
|
475
|
+
// chain from original target to new one. We need to use '_originalArtifact' due
|
|
476
|
+
// to secondary associations and their redirection chains. See comment in
|
|
477
|
+
// test3/Redirections/SecondaryAssocs/RedirectedPathRewriteOne.cds
|
|
459
478
|
// FIXME: Won't work with associations in projected structures.
|
|
460
479
|
const origTarget = expr.path[i - 1]?._originalArtifact?.target?._artifact;
|
|
461
480
|
const chain = cachedRedirectionChain( art, origTarget );
|
|
@@ -489,9 +508,10 @@ function xprRewriteFns( model ) {
|
|
|
489
508
|
}
|
|
490
509
|
|
|
491
510
|
/**
|
|
492
|
-
* Rewrite an expression that came via type
|
|
511
|
+
* Rewrite the initial part of a path in an expression that came via type
|
|
512
|
+
* propagation or via propagation with ref-only calculated element.
|
|
493
513
|
*
|
|
494
|
-
* @returns {boolean} Returns
|
|
514
|
+
* @returns {boolean} Returns true if it couldn't be rewritten.
|
|
495
515
|
*/
|
|
496
516
|
function adaptPathPrefixViaType( expr, config ) {
|
|
497
517
|
const { target, origin } = config;
|
|
@@ -502,7 +522,8 @@ function xprRewriteFns( model ) {
|
|
|
502
522
|
return true;
|
|
503
523
|
|
|
504
524
|
// $self-paths via types from/to non-main artifacts always need to be rewritten.
|
|
505
|
-
config.tokenExpr
|
|
525
|
+
if (config.tokenExpr)
|
|
526
|
+
config.tokenExpr.$tokenTexts = true;
|
|
506
527
|
|
|
507
528
|
const wasAbsolute = isAnnoPathAbsolute( expr );
|
|
508
529
|
stripAbsolutePathPrefix( expr, origin );
|
|
@@ -526,6 +547,15 @@ function xprRewriteFns( model ) {
|
|
|
526
547
|
return false;
|
|
527
548
|
}
|
|
528
549
|
|
|
550
|
+
/**
|
|
551
|
+
* Rewrite the initial part of a path in an expression starting with $self on an
|
|
552
|
+
* expanded element:
|
|
553
|
+
*
|
|
554
|
+
* - nothing to do when not starting with $self (TODO: param?)
|
|
555
|
+
* - otherwise error if options.beta.rewriteAnnotationExpressionsViaType is not set
|
|
556
|
+
*
|
|
557
|
+
* @returns {boolean} Returns true if it couldn't be rewritten.
|
|
558
|
+
*/
|
|
529
559
|
function adaptPathPrefixViaTypeExpansion( expr, config ) {
|
|
530
560
|
const root = expr.path[0]?._navigation;
|
|
531
561
|
if (root?.kind !== '$self') {
|
|
@@ -548,7 +578,8 @@ function xprRewriteFns( model ) {
|
|
|
548
578
|
|
|
549
579
|
config.target[config.anno].$inferred = 'anno-rewrite';
|
|
550
580
|
// $self-paths via type expansion always need to be rewritten.
|
|
551
|
-
config.tokenExpr
|
|
581
|
+
if (config.tokenExpr)
|
|
582
|
+
config.tokenExpr.$tokenTexts = true;
|
|
552
583
|
|
|
553
584
|
return false;
|
|
554
585
|
}
|
|
@@ -586,7 +617,7 @@ function xprRewriteFns( model ) {
|
|
|
586
617
|
if (relativeRoot >= 1)
|
|
587
618
|
expr.path = expr.path.slice(relativeRoot);
|
|
588
619
|
else if (relativeRoot === -1)
|
|
589
|
-
throw new CompilerAssertion('Error while rewriting annotation');
|
|
620
|
+
throw new CompilerAssertion( 'Error while rewriting annotation' );
|
|
590
621
|
}
|
|
591
622
|
|
|
592
623
|
/**
|
|
@@ -632,7 +663,7 @@ function xprRewriteFns( model ) {
|
|
|
632
663
|
* @returns {number}
|
|
633
664
|
*/
|
|
634
665
|
function findRelativeRoot( expr, origin ) {
|
|
635
|
-
if (!origin._main) // main artifacts can't have outer references
|
|
666
|
+
if (!origin._main) // main artifacts can't have outer references: $self → 0, -1 otherwise
|
|
636
667
|
return expr.path[0]?._artifact === origin ? 0 : -1;
|
|
637
668
|
|
|
638
669
|
const { path } = expr;
|
|
@@ -703,7 +734,8 @@ function xprRewriteFns( model ) {
|
|
|
703
734
|
const item = expr.path[index];
|
|
704
735
|
if (item.id !== found.name.id || (rewriteTarget[1] - index) !== 0) {
|
|
705
736
|
// Path was rewritten; original token text string is no longer accurate
|
|
706
|
-
config.tokenExpr
|
|
737
|
+
if (config.tokenExpr)
|
|
738
|
+
config.tokenExpr.$tokenTexts = true;
|
|
707
739
|
item.id = found.name.id;
|
|
708
740
|
}
|
|
709
741
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const edmUtils = require('../edmUtils.js');
|
|
4
|
-
const { parseExpr } = require('../../transform/parseExpr');
|
|
5
4
|
const {
|
|
6
5
|
EdmTypeFacetMap,
|
|
7
6
|
EdmTypeFacetNames,
|
|
@@ -9,6 +8,7 @@ const {
|
|
|
9
8
|
} = require('../EdmPrimitiveTypeDefinitions.js');
|
|
10
9
|
const { isBuiltinType, isAnnotationExpression } = require('../../base/builtins');
|
|
11
10
|
const { transformExpression } = require('../../transform/db/applyTransformations.js');
|
|
11
|
+
const { conditionAsTree, expressionAsTree } = require('../../model/xprAsTree');
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Translate a given token stream expression into an edmJson representation
|
|
@@ -77,7 +77,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
77
77
|
};
|
|
78
78
|
//----------------------------------
|
|
79
79
|
// Error transformer
|
|
80
|
-
const notADynExpr = (parent, op, xpr, csnPath,
|
|
80
|
+
const notADynExpr = (parent, op, xpr, csnPath, parentParent, parentProp, txt) => {
|
|
81
81
|
error('odata-anno-xpr', location, {
|
|
82
82
|
anno, op: txt ?? op, '#': 'notadynexpr',
|
|
83
83
|
});
|
|
@@ -104,15 +104,15 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
104
104
|
|
|
105
105
|
//----------------------------------
|
|
106
106
|
// list is a $Collection => []
|
|
107
|
-
transform.list = (parent, prop, xpr, csnPath,
|
|
108
|
-
|
|
109
|
-
transformExpression(
|
|
107
|
+
transform.list = (parent, prop, xpr, csnPath, parentParent, parentProp) => {
|
|
108
|
+
parentParent[parentProp] = xpr.filter(a => a);
|
|
109
|
+
transformExpression(parentParent, parentProp, transform);
|
|
110
110
|
};
|
|
111
111
|
// XPR
|
|
112
|
-
transform.xpr = (parent, prop, xpr, csnPath,
|
|
112
|
+
transform.xpr = (parent, prop, xpr, csnPath, parentParent, parentProp) => {
|
|
113
113
|
// eliminate 'xpr' node by pulling up xpr node to its parent
|
|
114
|
-
|
|
115
|
-
transformExpression(
|
|
114
|
+
parentParent[parentProp] = xpr;
|
|
115
|
+
transformExpression(parentParent, parentProp, transform);
|
|
116
116
|
};
|
|
117
117
|
//----------------------------------
|
|
118
118
|
// CASE
|
|
@@ -150,7 +150,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
150
150
|
};
|
|
151
151
|
//----------------------------------
|
|
152
152
|
// Cast => $Cast
|
|
153
|
-
transform.cast = (parent, prop, castExpr, csnPath,
|
|
153
|
+
transform.cast = (parent, prop, castExpr, csnPath, parentParent, parentProp) => {
|
|
154
154
|
const csnType = castExpr[0];
|
|
155
155
|
// try to resolve to final scalar base type and use that instead of derived type
|
|
156
156
|
if (!isBuiltinType(csnType.type)) {
|
|
@@ -184,8 +184,8 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
184
184
|
typeFunc.args.push( { func: 'Scale', args: [ { val: csnType.scale } ] });
|
|
185
185
|
|
|
186
186
|
|
|
187
|
-
|
|
188
|
-
transformExpression(
|
|
187
|
+
parentParent[parentProp] = castFunc;
|
|
188
|
+
transformExpression(parentParent, parentProp, transform);
|
|
189
189
|
};
|
|
190
190
|
//----------------------------------
|
|
191
191
|
const evalArgs = (argDef, args, propName) => {
|
|
@@ -261,11 +261,11 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
261
261
|
transformExpression(parent, undefined, transform);
|
|
262
262
|
};
|
|
263
263
|
transform.$In = noOp;
|
|
264
|
-
transform.between = (parent, prop, xpr, csnPath,
|
|
264
|
+
transform.between = (parent, prop, xpr, csnPath, parentParent, parentProp) => {
|
|
265
265
|
evalArgs({ exact: 2 }, xpr.slice(1), prop);
|
|
266
266
|
transformExpression(xpr, undefined, transform);
|
|
267
267
|
delete parent[prop];
|
|
268
|
-
|
|
268
|
+
parentParent[parentProp]
|
|
269
269
|
= {
|
|
270
270
|
$And: [
|
|
271
271
|
{ $Le: [ xpr[1], xpr[0] ] },
|
|
@@ -273,23 +273,23 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
273
273
|
],
|
|
274
274
|
};
|
|
275
275
|
};
|
|
276
|
-
transform['||'] = (parent, prop, xpr, csnPath,
|
|
276
|
+
transform['||'] = (parent, prop, xpr, csnPath, parentParent, parentProp) => {
|
|
277
277
|
evalArgs({ exact: 2 }, xpr, prop);
|
|
278
278
|
transformExpression(xpr, undefined, transform);
|
|
279
279
|
delete parent[prop];
|
|
280
|
-
|
|
281
|
-
|
|
280
|
+
parentParent[parentProp].$Apply = xpr;
|
|
281
|
+
parentParent[parentProp].$Function = 'odata.concat';
|
|
282
282
|
};
|
|
283
283
|
//----------------------------------
|
|
284
284
|
// ARITHMETICAL AND UNARY
|
|
285
|
-
transform['+'] = (parent, prop, xpr, csnPath,
|
|
285
|
+
transform['+'] = (parent, prop, xpr, csnPath, parentParent, parentProp) => {
|
|
286
286
|
if (Array.isArray(xpr)) {
|
|
287
287
|
op('$Add')(parent, prop, xpr);
|
|
288
288
|
}
|
|
289
289
|
else {
|
|
290
290
|
delete parent[prop];
|
|
291
|
-
|
|
292
|
-
transformExpression(
|
|
291
|
+
parentParent[parentProp] = xpr;
|
|
292
|
+
transformExpression(parentParent, parentProp, transform);
|
|
293
293
|
}
|
|
294
294
|
};
|
|
295
295
|
transform.$Add = noOp;
|
|
@@ -305,19 +305,19 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
305
305
|
// $Div, $Mod are functions
|
|
306
306
|
//----------------------------------
|
|
307
307
|
// LITERALS
|
|
308
|
-
transform.val = (parent, prop, xpr, csnPath,
|
|
308
|
+
transform.val = (parent, prop, xpr, csnPath, parentParent, parentProp) => {
|
|
309
309
|
if (xpr === null)
|
|
310
310
|
parent.$Null = true;
|
|
311
311
|
else
|
|
312
|
-
|
|
312
|
+
parentParent[parentProp] = xpr;
|
|
313
313
|
};
|
|
314
|
-
transform['#'] = (parent, prop, xpr, csnPath, parentParent,
|
|
314
|
+
transform['#'] = (parent, prop, xpr, csnPath, parentParent, parentProp, txt) => {
|
|
315
315
|
if (parent['#'] && parent.val) // enum reference that was resolved by the compiler
|
|
316
316
|
delete parent['#'];
|
|
317
317
|
else
|
|
318
|
-
notADynExpr(parent, prop, xpr, csnPath, parentParent,
|
|
318
|
+
notADynExpr(parent, prop, xpr, csnPath, parentParent, parentProp, txt);
|
|
319
319
|
};
|
|
320
|
-
transform.ref = (parent, prop, xpr, csnPath,
|
|
320
|
+
transform.ref = (parent, prop, xpr, csnPath, parentParent, parentProp) => {
|
|
321
321
|
// until empty filter syntax is introduced for the annotation expressions,
|
|
322
322
|
// we ignore the filters in order to generate EDMX
|
|
323
323
|
if (xpr.some(ps => ps.args/* || ps.where */)) {
|
|
@@ -328,11 +328,11 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
328
328
|
const [ head, ...tail ] = xpr;
|
|
329
329
|
if ((head.id || head) === '$self')
|
|
330
330
|
xpr = tail;
|
|
331
|
-
|
|
331
|
+
parentParent[parentProp] = { $Path: xpr.map(ps => ps.id || ps).join('/') };
|
|
332
332
|
};
|
|
333
333
|
//----------------------------------
|
|
334
334
|
// Functions
|
|
335
|
-
transform.func = (parent, prop, xpr, csnPath,
|
|
335
|
+
transform.func = (parent, prop, xpr, csnPath, parentParent, parentProp) => {
|
|
336
336
|
const rewriteArgs = (argDefs, evalVal = true) => {
|
|
337
337
|
Object.entries(argDefs).forEach(([ argName, argDef ]) => {
|
|
338
338
|
const [ foundProps, newArgs ]
|
|
@@ -722,8 +722,8 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
722
722
|
UrlRef: [ oneArg, dollar ],
|
|
723
723
|
// $Record ???
|
|
724
724
|
$Collection: () => {
|
|
725
|
-
standard(
|
|
726
|
-
transformExpression(
|
|
725
|
+
standard(parentParent, parentProp);
|
|
726
|
+
transformExpression(parentParent, parentProp, transform);
|
|
727
727
|
},
|
|
728
728
|
$Path: () => {
|
|
729
729
|
oneArg(parent, xpr);
|
|
@@ -733,7 +733,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
733
733
|
anno, op: `${ xpr }(…)`, meta: 'string', '#': 'wrongval_meta',
|
|
734
734
|
});
|
|
735
735
|
}
|
|
736
|
-
transformExpression(
|
|
736
|
+
transformExpression(parentParent, parentProp, transform);
|
|
737
737
|
},
|
|
738
738
|
$Null: () => {
|
|
739
739
|
parent[xpr] = true;
|
|
@@ -766,11 +766,11 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
766
766
|
}
|
|
767
767
|
else {
|
|
768
768
|
evalArgs(argDef, parent.args, xpr);
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
delete
|
|
772
|
-
delete
|
|
773
|
-
transformExpression(
|
|
769
|
+
parentParent[parentProp].$Apply = [ ...(parent.args || []) ];
|
|
770
|
+
parentParent[parentProp].$Function = funcName;
|
|
771
|
+
delete parentParent[parentProp].func;
|
|
772
|
+
delete parentParent[parentProp].args;
|
|
773
|
+
transformExpression(parentParent, parentProp, transform);
|
|
774
774
|
}
|
|
775
775
|
}
|
|
776
776
|
else {
|
|
@@ -783,13 +783,182 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
783
783
|
};
|
|
784
784
|
|
|
785
785
|
return transformExpression(carrier, anno, {
|
|
786
|
-
'=': (parent, prop, xpr, csnPath,
|
|
786
|
+
'=': (parent, prop, xpr, csnPath, parentParent, parentProp) => {
|
|
787
787
|
if (isAnnotationExpression(parent)) {
|
|
788
788
|
delete parent['='];
|
|
789
|
-
|
|
789
|
+
const edmJson = preTransformXpr(parent);
|
|
790
|
+
parentParent[parentProp] = transformExpression({ $edmJson: edmJson }, undefined, transform);
|
|
790
791
|
}
|
|
791
792
|
},
|
|
792
|
-
});
|
|
793
|
+
}, location);
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* Pre-transform the CSN expression into a tree structure for further EDM processing.
|
|
797
|
+
* Example:
|
|
798
|
+
* `[…, '+' …]` -> `{'+': [ …, … ]}`
|
|
799
|
+
*
|
|
800
|
+
* @param {object|Array} xpr
|
|
801
|
+
*/
|
|
802
|
+
function preTransformXpr( xpr ) {
|
|
803
|
+
xpr = Array.isArray(xpr) ? conditionAsTree(xpr) : expressionAsTree(xpr);
|
|
804
|
+
xpr = preTransformExpression(xpr);
|
|
805
|
+
if (Array.isArray(xpr)) {
|
|
806
|
+
xpr = xpr.flat(Infinity);
|
|
807
|
+
return (xpr.length === 1) ? xpr[0] : xpr;
|
|
808
|
+
}
|
|
809
|
+
return xpr;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* Reject any invalid expression such as `1 +`.
|
|
814
|
+
*/
|
|
815
|
+
function rejectInvalidExpression( expr ) {
|
|
816
|
+
if (expr.length === 3 && expr[1] === 'is' && expr[2] === 'null') {
|
|
817
|
+
messageFunctions.error('odata-anno-xpr', location, {
|
|
818
|
+
'#': 'notadynexpr', anno, op: 'is null',
|
|
819
|
+
});
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
if (expr.length === 4 && expr[1] === 'is' && expr[2] === 'not' && expr[3] === 'null') {
|
|
823
|
+
messageFunctions.error('odata-anno-xpr', location, {
|
|
824
|
+
'#': 'notadynexpr', anno, op: 'is not null',
|
|
825
|
+
});
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
if (expr.length === 4 && expr[1] === 'not' && expr[2] === 'like') {
|
|
829
|
+
messageFunctions.error('odata-anno-xpr', location, {
|
|
830
|
+
'#': 'notadynexpr', anno, op: 'not like',
|
|
831
|
+
});
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
if (expr.length === 3 && expr[1] === 'like') {
|
|
835
|
+
messageFunctions.error('odata-anno-xpr', location, {
|
|
836
|
+
'#': 'notadynexpr', anno, op: 'not like',
|
|
837
|
+
});
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// Any left-over operator is not recognized: reject it
|
|
842
|
+
for (let i = 0; i < expr.length; i++) {
|
|
843
|
+
if (expr[i] === 'not' && typeof expr[i + 1] === 'string')
|
|
844
|
+
++i;
|
|
845
|
+
|
|
846
|
+
if (typeof expr[i] === 'string') {
|
|
847
|
+
messageFunctions.error('odata-anno-xpr', location, {
|
|
848
|
+
'#': 'invalid',
|
|
849
|
+
anno,
|
|
850
|
+
op: expr[i],
|
|
851
|
+
});
|
|
852
|
+
break;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
/**
|
|
858
|
+
* Pre-transform a _structurized_ expression.
|
|
859
|
+
*/
|
|
860
|
+
function preTransformExpression( xpr ) {
|
|
861
|
+
if (!xpr || typeof xpr !== 'object')
|
|
862
|
+
return xpr;
|
|
863
|
+
|
|
864
|
+
if (Array.isArray(xpr)) {
|
|
865
|
+
xpr = xpr.map(preTransformExpression); // not `preTransformXpr`, as that would re-structurize the array
|
|
866
|
+
|
|
867
|
+
if (xpr.length === 1)
|
|
868
|
+
return xpr[0]; // single entry, e.g. via structurizer
|
|
869
|
+
|
|
870
|
+
if (xpr[0]?.toLowerCase?.() === 'case')
|
|
871
|
+
return preTransformCase(xpr);
|
|
872
|
+
|
|
873
|
+
if (xpr[1]?.toLowerCase?.() === 'between')
|
|
874
|
+
return preTransformBetween(xpr);
|
|
875
|
+
|
|
876
|
+
if (xpr[1]?.toLowerCase?.() === 'not' &&
|
|
877
|
+
xpr[2]?.toLowerCase?.() === 'between')
|
|
878
|
+
return { not: preTransformExpression([ xpr[0], ...xpr.slice(2) ]) };
|
|
879
|
+
|
|
880
|
+
if (xpr[1]?.toLowerCase?.() === 'not' &&
|
|
881
|
+
xpr[2]?.toLowerCase?.() === 'in')
|
|
882
|
+
return { not: preTransformExpression([ xpr[0], ...xpr.slice(2) ]) };
|
|
883
|
+
|
|
884
|
+
// unary operators
|
|
885
|
+
if (xpr.length === 2 && (xpr[0] === '+' || xpr[0] === '-' || xpr[0] === 'not' || xpr[0] === 'new'))
|
|
886
|
+
return { [xpr[0]]: xpr[1] };
|
|
887
|
+
|
|
888
|
+
if (xpr.length === 3 && xpr[1] === 'is' && xpr[2] === 'null') {
|
|
889
|
+
rejectInvalidExpression(xpr);
|
|
890
|
+
return xpr;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// binary operators: '=', '<>', 'like', ...
|
|
894
|
+
if (xpr.length === 3 && typeof xpr[1] === 'string')
|
|
895
|
+
return { [xpr[1]]: [ xpr[0], xpr[2] ] };
|
|
896
|
+
|
|
897
|
+
rejectInvalidExpression(xpr);
|
|
898
|
+
|
|
899
|
+
return xpr;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
if (xpr.list)
|
|
903
|
+
xpr.list.forEach( preTransformExpression );
|
|
904
|
+
|
|
905
|
+
if (xpr.args) {
|
|
906
|
+
if (!Array.isArray( xpr.args ))
|
|
907
|
+
Object.values( xpr.args ).forEach(preTransformExpression );
|
|
908
|
+
else if (xpr.args.length)
|
|
909
|
+
xpr.args.forEach( preTransformExpression );
|
|
910
|
+
}
|
|
911
|
+
if (xpr.ref)
|
|
912
|
+
xpr.ref.forEach( preTransformExpression );
|
|
913
|
+
|
|
914
|
+
if (xpr.xpr)
|
|
915
|
+
xpr.xpr = preTransformExpression( xpr.xpr );
|
|
916
|
+
|
|
917
|
+
if (xpr.cast) {
|
|
918
|
+
const castKeys = Object.keys(xpr).filter(k => k !== 'cast');
|
|
919
|
+
if (castKeys.length === 1)
|
|
920
|
+
return { cast: [ xpr.cast, { [castKeys[0]]: xpr[castKeys[0]] } ] };
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
return xpr;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
function preTransformBetween( xpr ) {
|
|
927
|
+
return {
|
|
928
|
+
between: [
|
|
929
|
+
xpr[0],
|
|
930
|
+
xpr[2],
|
|
931
|
+
xpr[4],
|
|
932
|
+
],
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
function preTransformCase( xpr ) {
|
|
937
|
+
const caseObj = [ ];
|
|
938
|
+
let pos = 1;
|
|
939
|
+
|
|
940
|
+
// CASE val WHEN val THEN …
|
|
941
|
+
if (xpr[pos]?.toLowerCase?.() !== 'when') {
|
|
942
|
+
caseObj.push( xpr[pos] );
|
|
943
|
+
pos++;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
while (xpr[pos]?.toLowerCase?.() === 'when') {
|
|
947
|
+
if (xpr[pos + 2]?.toLowerCase?.() !== 'then')
|
|
948
|
+
return xpr;
|
|
949
|
+
caseObj.push({ when: [ xpr[pos + 1], xpr[pos + 3] ] });
|
|
950
|
+
pos += 4;
|
|
951
|
+
}
|
|
952
|
+
if (xpr[pos]?.toLowerCase?.() === 'else' ) {
|
|
953
|
+
caseObj.push(xpr[pos + 1]);
|
|
954
|
+
pos += 2;
|
|
955
|
+
}
|
|
956
|
+
if (xpr[pos++]?.toLowerCase?.() !== 'end')
|
|
957
|
+
return xpr;
|
|
958
|
+
return {
|
|
959
|
+
case: caseObj,
|
|
960
|
+
};
|
|
961
|
+
}
|
|
793
962
|
}
|
|
794
963
|
|
|
795
964
|
// Not everything that can occur in OData annotations can be expressed with
|
package/lib/edm/csn2edm.js
CHANGED
|
@@ -688,6 +688,11 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
688
688
|
props.push(new Edm.Property(v, { Name: elementName }, elementCsn));
|
|
689
689
|
}
|
|
690
690
|
}
|
|
691
|
+
|
|
692
|
+
if (options.isV2() && isBetaEnabled(options, 'draftAdminDataHiddenFilter')) {
|
|
693
|
+
if (elementCsn._edmParentCsn.name === `${ elementCsn._edmParentCsn.$mySchemaName }.DraftAdministrativeData`)
|
|
694
|
+
elementCsn['@UI.HiddenFilter'] ??= true;
|
|
695
|
+
}
|
|
691
696
|
});
|
|
692
697
|
}
|
|
693
698
|
if (options.isV2()) {
|
|
@@ -723,6 +728,8 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
723
728
|
|
|
724
729
|
// add bound/unbound actions/functions for V4
|
|
725
730
|
function createActionV4( actionCsn, _name, entityCsn = undefined ) {
|
|
731
|
+
if (actionCsn['@cds.api.ignore'])
|
|
732
|
+
return;
|
|
726
733
|
const iAmAnAction = actionCsn.kind === 'action';
|
|
727
734
|
const actionName = edmUtils.getBaseName(actionCsn.name);
|
|
728
735
|
const attributes = { Name: actionName, IsBound: false };
|
|
@@ -837,6 +844,8 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
837
844
|
// Parameter Nodes
|
|
838
845
|
if (actionCsn.params) {
|
|
839
846
|
forEach(actionCsn.params, ( parameterName, parameterCsn ) => {
|
|
847
|
+
if (parameterCsn['@cds.api.ignore'])
|
|
848
|
+
return;
|
|
840
849
|
const p = new Edm.Parameter(v, { Name: parameterName }, parameterCsn );
|
|
841
850
|
const pLoc = [ ...location, 'params', p._edmAttributes.Name ];
|
|
842
851
|
if (!edmUtils.isODataSimpleIdentifier(parameterName))
|
|
@@ -861,6 +870,8 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
861
870
|
|
|
862
871
|
// add bound/unbound actions/functions for V2
|
|
863
872
|
function createActionV2( actionCsn, name, entityCsn = undefined ) {
|
|
873
|
+
if (actionCsn['@cds.api.ignore'])
|
|
874
|
+
return;
|
|
864
875
|
/** @type {object} */
|
|
865
876
|
const attributes = { Name: name.replace(schemaNamePrefix, '') };
|
|
866
877
|
const functionImport = new Edm.FunctionImport(v, attributes );
|
|
@@ -924,6 +935,8 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
924
935
|
// the client assume that Nullable is false.... Correct Nullable Handling is done inside Parameter constructor
|
|
925
936
|
if (actionCsn.params) {
|
|
926
937
|
Object.entries(actionCsn.params).forEach(([ parameterName, parameterCsn ], i) => {
|
|
938
|
+
if (parameterCsn['@cds.api.ignore'])
|
|
939
|
+
return;
|
|
927
940
|
const type = parameterCsn?.items?.type || parameterCsn?.type;
|
|
928
941
|
if (i === 0 && type === special$self) {
|
|
929
942
|
// skip and remove the first parameter if it is a $self binding parameter to
|
package/lib/edm/edmUtils.js
CHANGED
|
@@ -292,7 +292,7 @@ function finalizeReferentialConstraints( csn, assocCsn, options, info ) {
|
|
|
292
292
|
} );
|
|
293
293
|
}
|
|
294
294
|
// Remove all target elements that are not key in the principal entity
|
|
295
|
-
// and all elements that annotated with '@cds.api.ignore'
|
|
295
|
+
// and all elements that are annotated with '@cds.api.ignore'
|
|
296
296
|
const remainingPrincipalRefs = [];
|
|
297
297
|
foreach(assocCsn._constraints.constraints,
|
|
298
298
|
(c) => {
|
|
@@ -359,7 +359,7 @@ function finalizeReferentialConstraints( csn, assocCsn, options, info ) {
|
|
|
359
359
|
// If FK is key in target => constraint
|
|
360
360
|
// Don't consider primary key associations (fks become keys on the source entity) as
|
|
361
361
|
// this would impose a constraint against the target.
|
|
362
|
-
// Filter out all elements that annotated with '@cds.api.ignore'
|
|
362
|
+
// Filter out all elements that are annotated with '@cds.api.ignore'
|
|
363
363
|
|
|
364
364
|
// In structured format, foreign keys of managed associations are never rendered, so
|
|
365
365
|
// there are no constraints for them.
|