@sap/cds-compiler 6.3.6 → 6.4.6
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 +101 -3
- 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 +9 -2
- package/lib/base/messages.js +1 -1
- package/lib/base/model.js +2 -0
- package/lib/checks/existsExpressionsOnlyForeignKeys.js +16 -10
- package/lib/checks/existsMustEndInAssoc.js +1 -1
- package/lib/checks/existsMustNotStartWithDollarSelf.js +31 -0
- package/lib/checks/validator.js +4 -2
- package/lib/compiler/assert-consistency.js +3 -2
- package/lib/compiler/builtins.js +5 -6
- 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 +157 -133
- package/lib/compiler/tweak-assocs.js +87 -29
- package/lib/compiler/xpr-rewrite.js +164 -160
- 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 +1501 -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 +73 -28
- 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/assocsToQueries/transformExists.js +3 -10
- package/lib/transform/db/assocsToQueries/utils.js +0 -5
- 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 +75 -127
- 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 +3 -3
- package/doc/DeprecatedOptions_v2.md +0 -150
- package/doc/NameResolution.md +0 -837
- package/lib/transform/parseExpr.js +0 -415
|
@@ -72,10 +72,10 @@
|
|
|
72
72
|
// the path up to the last path step in the type-of.
|
|
73
73
|
// - an element definition, `$self.sub.elem` needs to be replaced by the element name.
|
|
74
74
|
// Paths without `$self` on the "type-of element" itself need to have
|
|
75
|
-
// the first path step be replaced by the
|
|
75
|
+
// the first path step be replaced by the destination element name.
|
|
76
76
|
// - a parameter, `$self.sub.elem` needs to be replaced by the parameter name.
|
|
77
77
|
// Paths without `$self` on the "type-of element" itself need to have
|
|
78
|
-
// the first path step be replaced by the
|
|
78
|
+
// the first path step be replaced by the destination parameter name.
|
|
79
79
|
// - a return parameter, `$self` needs to be rejected, because there is no way
|
|
80
80
|
// to refer to `returns`. Paths without `$self` on the "type-of element" itself
|
|
81
81
|
// (not sub-elements), need to be rejected as well.
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
// was projected or if a prefix was projected (e.g. for structures or associations).
|
|
109
109
|
// The same rules as for ON-condition rewriting apply.
|
|
110
110
|
//
|
|
111
|
-
// Furthermore, as the
|
|
111
|
+
// Furthermore, as the destination is a select item, and this select item belongs to a table
|
|
112
112
|
// alias, we should rewrite all annotation paths only to projected elements of that
|
|
113
113
|
// table alias. Cross-rewriting between table aliases should not be done.
|
|
114
114
|
// This is the same we do for association rewriting.
|
|
@@ -157,14 +157,13 @@ const { isSimpleCdlIdentifier } = require('../parsers/identifiers');
|
|
|
157
157
|
// Config object passed around all "rewrite" functions.
|
|
158
158
|
class AnnoRewriteConfig {
|
|
159
159
|
anno;
|
|
160
|
-
|
|
161
|
-
|
|
160
|
+
destination;
|
|
161
|
+
destinationRoot;
|
|
162
162
|
origin;
|
|
163
|
-
|
|
163
|
+
fromDestinationType;
|
|
164
164
|
fromCalcElement;
|
|
165
165
|
expandedRoot;
|
|
166
166
|
expandedRootType;
|
|
167
|
-
isInFilter;
|
|
168
167
|
tokenExpr;
|
|
169
168
|
}
|
|
170
169
|
|
|
@@ -181,71 +180,87 @@ function xprRewriteFns( model ) {
|
|
|
181
180
|
|
|
182
181
|
return {
|
|
183
182
|
rewriteAnnotationsRefs,
|
|
183
|
+
rewriteRefsInExpression,
|
|
184
184
|
};
|
|
185
185
|
|
|
186
186
|
/**
|
|
187
|
+
* Report anno-missing-rewrite if rewrite for annotations.
|
|
188
|
+
* Returns traverseExpr.STOP.
|
|
189
|
+
*
|
|
187
190
|
* @param expr
|
|
188
191
|
* @param {AnnoRewriteConfig} config
|
|
189
192
|
* @param {string} [variant]
|
|
190
193
|
*/
|
|
191
194
|
function reportAnnoRewriteError( expr, config, variant = 'std' ) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
195
|
+
if (config.tokenExpr) {
|
|
196
|
+
error( 'anno-missing-rewrite', [
|
|
197
|
+
weakLocation( config.destination.location ), config.destination,
|
|
198
|
+
], {
|
|
199
|
+
'#': variant,
|
|
200
|
+
anno: config.anno,
|
|
201
|
+
art: config.origin,
|
|
202
|
+
elemref: expr,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
return traverseExpr.STOP;
|
|
200
206
|
}
|
|
201
207
|
|
|
202
208
|
/**
|
|
203
|
-
* Rewrite the propagated annotation relative to the
|
|
209
|
+
* Rewrite the propagated annotation relative to the destination.
|
|
204
210
|
*
|
|
205
|
-
* @param {XSN.Artifact}
|
|
211
|
+
* @param {XSN.Artifact} destination
|
|
206
212
|
* @param {XSN.Artifact} origin
|
|
207
213
|
* @param {string} annoName
|
|
208
214
|
*/
|
|
209
|
-
function rewriteAnnotationsRefs(
|
|
215
|
+
function rewriteAnnotationsRefs( destination, origin, annoName ) {
|
|
210
216
|
// Make sure not to waste time if no inherited annotation has references:
|
|
211
217
|
if (!origin?.$contains?.$annotation?.$path)
|
|
212
218
|
return;
|
|
213
219
|
|
|
214
|
-
const anno =
|
|
220
|
+
const anno = destination[annoName];
|
|
215
221
|
// only annotations with expressions have a kind
|
|
216
222
|
// also, don't report errors twice
|
|
217
223
|
if (!anno.kind || anno.$invalidPaths)
|
|
218
224
|
return;
|
|
219
225
|
|
|
220
|
-
|
|
226
|
+
const hasError = rewriteRefsInExpression( destination, origin, annoName );
|
|
227
|
+
destination.$contains ??= {};
|
|
228
|
+
destination.$contains.$annotation ??= {};
|
|
229
|
+
destination.$contains.$annotation.$path ||= origin.$contains.$annotation.$path;
|
|
230
|
+
destination.$contains.$annotation.$self ||= origin.$contains.$annotation.$self;
|
|
231
|
+
if (hasError)
|
|
232
|
+
anno.$invalidPaths = true; // avoid subsequent errors
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function rewriteRefsInExpression( destination, origin, propName ) {
|
|
236
|
+
// Annotation comes from the destination's type. That's important to know, because
|
|
221
237
|
// path prefixes need to be adapted.
|
|
222
|
-
const
|
|
223
|
-
// Annotation comes from the
|
|
238
|
+
const fromDestinationType = destination.type?._artifact === origin;
|
|
239
|
+
// Annotation comes from the destination's calculated element.
|
|
240
|
+
// A special case propagation rule, e.g
|
|
224
241
|
// for `calcString: String = str;`. We also need to adapt path prefixes.
|
|
225
|
-
const fromCalcElement = !
|
|
226
|
-
|
|
242
|
+
const fromCalcElement = !fromDestinationType && destination.$calcDepElement &&
|
|
243
|
+
destination.value?._artifact === origin;
|
|
227
244
|
|
|
228
|
-
const { expandedRoot, expandedRootType }
|
|
245
|
+
const { expandedRoot, expandedRootType }
|
|
246
|
+
= !fromDestinationType && getExpandRoot( destination ) || {};
|
|
229
247
|
|
|
230
248
|
const config = {
|
|
231
249
|
__proto__: AnnoRewriteConfig.prototype,
|
|
232
|
-
anno:
|
|
233
|
-
|
|
234
|
-
|
|
250
|
+
anno: propName,
|
|
251
|
+
destination,
|
|
252
|
+
destinationRoot: annoRootArt( destination ),
|
|
235
253
|
origin,
|
|
236
|
-
|
|
254
|
+
fromDestinationType,
|
|
237
255
|
fromCalcElement,
|
|
238
256
|
expandedRoot,
|
|
239
257
|
expandedRootType,
|
|
240
258
|
};
|
|
241
259
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
target.$contains.$annotation.$self ||= origin.$contains.$annotation.$self;
|
|
247
|
-
if (hasError)
|
|
248
|
-
anno.$invalidPaths = true; // avoid subsequent errors
|
|
260
|
+
if (propName.charAt( 0 ) === '@')
|
|
261
|
+
return rewriteAnnotationExpr( destination[propName], config );
|
|
262
|
+
return traverseExpr( destination[propName], 'annoRewrite', destination,
|
|
263
|
+
rewriteAnnoExpr.bind( config ) );
|
|
249
264
|
}
|
|
250
265
|
|
|
251
266
|
/**
|
|
@@ -264,36 +279,37 @@ function xprRewriteFns( model ) {
|
|
|
264
279
|
else if (expr.$tokenTexts) {
|
|
265
280
|
// used to set `$tokenText` to true in case of rewritten annotation
|
|
266
281
|
config.tokenExpr = expr;
|
|
267
|
-
return traverseExpr
|
|
268
|
-
|
|
269
|
-
// eslint-disable-next-line @stylistic/max-len, @stylistic/function-paren-newline
|
|
270
|
-
(e, refCtx) => (rewriteAnnoExpr( e, config, refCtx ) ? traverseExpr.STOP : traverseExpr.SKIP) );
|
|
282
|
+
return traverseExpr( expr, 'annoRewrite', config.destination,
|
|
283
|
+
rewriteAnnoExpr.bind( config ) );
|
|
271
284
|
}
|
|
272
285
|
return false;
|
|
273
286
|
}
|
|
274
287
|
|
|
275
288
|
/**
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
* @param {string} refCtx
|
|
279
|
-
* @returns {null|true} Returns true if the expression couldn't be rewritten.
|
|
289
|
+
* Rewrite the annotation expression `expr`.
|
|
290
|
+
* Returns traverseExpr.STOP if the expression couldn't be rewritten.
|
|
280
291
|
*/
|
|
281
|
-
function rewriteAnnoExpr( expr,
|
|
292
|
+
function rewriteAnnoExpr( expr, refCtx, user ) {
|
|
293
|
+
const config = this; // we use rewriteAnnoExpr.bind( config )
|
|
282
294
|
const root = expr.path && (expr.path[0]?._navigation || expr.path[0]?._artifact);
|
|
283
295
|
if (!root || !expr._artifact)
|
|
284
296
|
return null; // invalid path
|
|
285
297
|
|
|
286
|
-
|
|
298
|
+
if (refCtx === 'filter')
|
|
299
|
+
return rewriteGenericAnnoPath( expr, refCtx, config, user );
|
|
287
300
|
|
|
288
301
|
// Report obsolete $parameters; parameters on non-actions not supported, yet.
|
|
289
|
-
if (root.kind === '$parameters' ||
|
|
290
|
-
|
|
302
|
+
if (root.kind === '$parameters' ||
|
|
303
|
+
(root.kind === 'param' && root._parent.kind !== 'action' &&
|
|
304
|
+
root._parent.kind !== 'function'))
|
|
291
305
|
return reportAnnoRewriteError( expr, config, 'unsupported' );
|
|
292
306
|
|
|
293
307
|
if (root.kind === 'key') {
|
|
294
|
-
//
|
|
295
|
-
//
|
|
296
|
-
//
|
|
308
|
+
// References starting with a foreign key name (only possible in an
|
|
309
|
+
// annotation on a foreign key) have length 1 + the foreign key cannot be
|
|
310
|
+
// renamed → nothing to do. Otherwise, foreign keys cannot be directly
|
|
311
|
+
// addressed, except via `annotate`. (Even as annotation value for
|
|
312
|
+
// foreign keys, `$self.assoc.target_id` always refers to the target side.)
|
|
297
313
|
return null;
|
|
298
314
|
}
|
|
299
315
|
|
|
@@ -303,37 +319,15 @@ function xprRewriteFns( model ) {
|
|
|
303
319
|
return null;
|
|
304
320
|
|
|
305
321
|
let hasError = false;
|
|
306
|
-
if (config.
|
|
307
|
-
hasError = adaptPathPrefixViaType( expr, config );
|
|
322
|
+
if (config.fromDestinationType || config.fromCalcElement)
|
|
323
|
+
hasError = adaptPathPrefixViaType( expr, config, user );
|
|
308
324
|
else if (config.expandedRoot)
|
|
309
|
-
hasError = adaptPathPrefixViaTypeExpansion( expr, config );
|
|
325
|
+
hasError = adaptPathPrefixViaTypeExpansion( expr, config, user );
|
|
310
326
|
|
|
311
|
-
hasError ||= rewriteGenericAnnoPath( expr, config,
|
|
327
|
+
hasError ||= rewriteGenericAnnoPath( expr, refCtx, config, user );
|
|
312
328
|
|
|
313
329
|
if (hasError)
|
|
314
|
-
return
|
|
315
|
-
|
|
316
|
-
// TODO: Remove extra loop once filter traversal is added to traverseExpr (#12068)
|
|
317
|
-
for (const step of expr.path) {
|
|
318
|
-
if (step?._artifact && step.where && !Array.isArray( step._artifact ) ) {
|
|
319
|
-
// We must not prefix `$`-renamed variables with `$self`, as it would
|
|
320
|
-
// change meaning, see (#11775). Also, the path's target changes.
|
|
321
|
-
const assocTarget = step._artifact.target._artifact;
|
|
322
|
-
if (target) {
|
|
323
|
-
const filterConfig = { ...config, target: assocTarget, isInFilter: true };
|
|
324
|
-
if (traverseExpr.STOP === traverseExpr(
|
|
325
|
-
step.where, 'filter', step,
|
|
326
|
-
// eslint-disable-next-line @stylistic/max-len
|
|
327
|
-
(e, ctx) => expr.path && (rewriteGenericAnnoPath( e, filterConfig, ctx ) ? traverseExpr.STOP : traverseExpr.SKIP)
|
|
328
|
-
))
|
|
329
|
-
return true;
|
|
330
|
-
}
|
|
331
|
-
else {
|
|
332
|
-
// can't happen: rejected earlier by compiler
|
|
333
|
-
return reportAnnoRewriteError( expr, config, 'unsupported' );
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
330
|
+
return traverseExpr.STOP;
|
|
337
331
|
|
|
338
332
|
if (expr.$tokenTexts === true) {
|
|
339
333
|
// TODO: do not do with Universal-CSN (and gensrc, but that does not matter)
|
|
@@ -341,53 +335,53 @@ function xprRewriteFns( model ) {
|
|
|
341
335
|
// only understand the string representation. But we only do so for simple strings, which
|
|
342
336
|
// is the case if this path expression has $tokenTexts. It then is of the form `@a: (path)`.
|
|
343
337
|
const isSimpleStep = step => !step.where && !step.args && isSimpleCdlIdentifier( step.id );
|
|
344
|
-
if (expr.path.every(isSimpleStep))
|
|
345
|
-
expr.$tokenTexts = expr.
|
|
338
|
+
if (expr.path.every(isSimpleStep)) {
|
|
339
|
+
expr.$tokenTexts = (expr.scope === 'param' ? ':' : '') +
|
|
340
|
+
expr.path.map( step => step.id ).join('.');
|
|
341
|
+
}
|
|
346
342
|
}
|
|
347
343
|
|
|
348
344
|
if (model.options.testMode) {
|
|
349
345
|
// re-resolve the modified path; all paths steps must match what we rewrote
|
|
350
346
|
const ref = { ...expr, path: [ ...expr.path.map(item => ({ ...item })) ] };
|
|
351
|
-
if (!resolvePath( ref, refCtx,
|
|
347
|
+
if (!resolvePath( ref, refCtx, user ))
|
|
352
348
|
throw new CompilerAssertion(`rewritten anno path must be resolvable: ${ JSON.stringify(ref.path) }`);
|
|
353
|
-
|
|
354
|
-
for (let i = 0; i < ref.path.length; ++i) {
|
|
355
|
-
const actual = ref.path[i];
|
|
356
|
-
const expected = ref.path[i];
|
|
357
|
-
if (actual._artifact !== expected._artifact) {
|
|
358
|
-
throw new CompilerAssertion(`rewritten anno path contains incorrect artifact links: ${
|
|
359
|
-
JSON.stringify(ref.path) }; step ${ i }`);
|
|
360
|
-
}
|
|
361
|
-
else if (actual._navigation !== undefined && actual._navigation !== expected._navigation) {
|
|
362
|
-
throw new CompilerAssertion(`rewritten anno path contains incorrect navigation links: ${
|
|
363
|
-
JSON.stringify(ref.path) }; step ${ i }`);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
349
|
}
|
|
367
|
-
|
|
368
350
|
return false;
|
|
369
351
|
}
|
|
370
352
|
|
|
371
353
|
/**
|
|
372
|
-
*
|
|
373
|
-
*
|
|
374
|
-
*
|
|
354
|
+
* Return the destination node of the expr rewrite (stored in param `config`), or
|
|
355
|
+
* if we are in a filter expression, the association target of the corresponding
|
|
356
|
+
* path item (provided via param `user`).
|
|
357
|
+
*
|
|
358
|
+
* TODO: in the end, we should directly use the standard expression parameter
|
|
359
|
+
* `user` in all functions below. Probably if we unify this module with
|
|
360
|
+
* rewriteCondition() of tweak-assocs.js.
|
|
375
361
|
*/
|
|
376
|
-
function
|
|
377
|
-
|
|
362
|
+
function destinationOrAssocTarget( config, user ) {
|
|
363
|
+
return (user.id && user.where)
|
|
364
|
+
? user._artifact.target._artifact
|
|
365
|
+
: config.destination;
|
|
366
|
+
}
|
|
378
367
|
|
|
368
|
+
/**
|
|
369
|
+
* TODO: what exactly is a root environment?
|
|
370
|
+
*/
|
|
371
|
+
function getRootEnv( expr, config, destination ) {
|
|
379
372
|
if (expr.scope === 'param') // path is absolute
|
|
380
|
-
return navigationEnv( config.
|
|
373
|
+
return navigationEnv( config.destinationRoot, null, null, 'nav' );
|
|
381
374
|
|
|
382
375
|
// On select items, use navigation elements or table alias
|
|
383
376
|
// TODO: Expand/inline paths don't have a `_navigation` property on their last
|
|
384
377
|
// path step, yet. We need to implement expand/inline.
|
|
385
|
-
const isSimpleSelectItem =
|
|
378
|
+
const isSimpleSelectItem = destination.value?.path && destination._main?.query &&
|
|
379
|
+
!destination._columnParent;
|
|
386
380
|
if (isSimpleSelectItem) {
|
|
387
381
|
const isSelfPath = (expr.path[0]?._navigation?.kind === '$self');
|
|
388
382
|
if (isSelfPath) {
|
|
389
383
|
// Path is absolute, use table alias to resolve it.
|
|
390
|
-
let tableAlias =
|
|
384
|
+
let tableAlias = destination.value.path[0]._navigation;
|
|
391
385
|
while (tableAlias && tableAlias.kind === '$navElement')
|
|
392
386
|
tableAlias = tableAlias._parent;
|
|
393
387
|
if (tableAlias)
|
|
@@ -395,37 +389,35 @@ function xprRewriteFns( model ) {
|
|
|
395
389
|
}
|
|
396
390
|
else {
|
|
397
391
|
// Path is relative
|
|
398
|
-
const nav =
|
|
392
|
+
const nav = destination.value.path[destination.value.path.length - 1]._navigation?._parent;
|
|
399
393
|
if (nav)
|
|
400
394
|
return nav;
|
|
401
395
|
}
|
|
402
396
|
}
|
|
403
397
|
|
|
404
398
|
if (isSimpleSelectItem && model.options.testMode)
|
|
405
|
-
throw new CompilerAssertion(`select item has no table alias: ${ JSON.stringify(
|
|
399
|
+
throw new CompilerAssertion(`select item has no table alias: ${ JSON.stringify(destination.value.path) }`);
|
|
406
400
|
|
|
407
401
|
if (isAnnoPathAbsolute( expr ))
|
|
408
|
-
return navigationEnv( config.
|
|
402
|
+
return navigationEnv( config.destinationRoot, null, null, 'nav' );
|
|
409
403
|
|
|
410
404
|
// anno path is relative / element reference (others were already rejected)
|
|
411
|
-
// if the
|
|
412
|
-
return navigationEnv( isAnnoRootArt(
|
|
405
|
+
// if the destination is a root artifact, use it. Otherwise, use its parent.
|
|
406
|
+
return navigationEnv( isAnnoRootArt( destination ) ? destination : destination._parent,
|
|
407
|
+
null, null, 'nav' );
|
|
413
408
|
}
|
|
414
409
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
* @returns {boolean}
|
|
420
|
-
*/
|
|
421
|
-
function rewriteGenericAnnoPath( expr, config, refCtx ) {
|
|
410
|
+
function rewriteGenericAnnoPath( expr, refCtx, config, user ) {
|
|
411
|
+
if (expr._artifact?.kind === 'builtin')
|
|
412
|
+
return false;
|
|
413
|
+
const destination = destinationOrAssocTarget( config, user );
|
|
422
414
|
const isAbsolute = isAnnoPathAbsolute( expr );
|
|
423
415
|
const startIndex = isAbsolute ? 1 : 0;
|
|
424
416
|
|
|
425
417
|
// We get the root environment now, even though below we resolve the root item
|
|
426
418
|
// again if it was absolute (e.g. $self). We do so, because for queries, we
|
|
427
419
|
// want to respect the select item's corresponding table alias.
|
|
428
|
-
const rootEnv = getRootEnv( expr, config );
|
|
420
|
+
const rootEnv = getRootEnv( expr, config, destination );
|
|
429
421
|
|
|
430
422
|
// reset artifact link; we'll set it again if there are no errors
|
|
431
423
|
setArtifactLink( expr, null );
|
|
@@ -437,7 +429,7 @@ function xprRewriteFns( model ) {
|
|
|
437
429
|
delete expr.path[0]._navigation;
|
|
438
430
|
// TODO: What about `up_`? Shouldn't we set `_navigation` as well?
|
|
439
431
|
// TODO: Can we handle `$self` of anonymous-composition-of-aspect?
|
|
440
|
-
const root = resolvePathRoot( expr, refCtx,
|
|
432
|
+
const root = resolvePathRoot( expr, refCtx, destination );
|
|
441
433
|
if (!root)
|
|
442
434
|
return reportAnnoRewriteError( expr, config );
|
|
443
435
|
}
|
|
@@ -454,8 +446,10 @@ function xprRewriteFns( model ) {
|
|
|
454
446
|
|
|
455
447
|
for (let i = startIndex; i < expr.path.length; ++i) {
|
|
456
448
|
if (i > startIndex && art.target) {
|
|
457
|
-
//
|
|
458
|
-
// chain from original target to new one.
|
|
449
|
+
// If the current artifact is an association, we need to respect the redirection
|
|
450
|
+
// chain from original target to new one. We need to use '_originalArtifact' due
|
|
451
|
+
// to secondary associations and their redirection chains. See comment in
|
|
452
|
+
// test3/Redirections/SecondaryAssocs/RedirectedPathRewriteOne.cds
|
|
459
453
|
// FIXME: Won't work with associations in projected structures.
|
|
460
454
|
const origTarget = expr.path[i - 1]?._originalArtifact?.target?._artifact;
|
|
461
455
|
const chain = cachedRedirectionChain( art, origTarget );
|
|
@@ -476,48 +470,50 @@ function xprRewriteFns( model ) {
|
|
|
476
470
|
setArtifactLink( expr, art );
|
|
477
471
|
|
|
478
472
|
if (startIndex === 0 && expr.path[0].id.startsWith('$')) {
|
|
479
|
-
if (
|
|
473
|
+
if (refCtx === 'filter') {
|
|
480
474
|
// In filters, we must not prepend `$self`, as that would change its meaning.
|
|
481
475
|
// We must reject it. See #11775
|
|
482
476
|
return reportAnnoRewriteError( expr, config );
|
|
483
477
|
}
|
|
484
478
|
// After rewriting, if an element starts with `$` -> add root prefix
|
|
485
|
-
prependRootPath( config.origin, config.
|
|
479
|
+
prependRootPath( config.origin, config.destinationRoot, expr );
|
|
486
480
|
}
|
|
487
481
|
|
|
488
482
|
return false;
|
|
489
483
|
}
|
|
490
484
|
|
|
491
485
|
/**
|
|
492
|
-
* Rewrite an expression that came via type
|
|
486
|
+
* Rewrite the initial part of a path in an expression that came via type
|
|
487
|
+
* propagation or via propagation with ref-only calculated element.
|
|
493
488
|
*
|
|
494
|
-
* @returns {boolean} Returns
|
|
489
|
+
* @returns {boolean} Returns true if it couldn't be rewritten.
|
|
495
490
|
*/
|
|
496
|
-
function adaptPathPrefixViaType( expr, config ) {
|
|
497
|
-
const {
|
|
498
|
-
if (!
|
|
491
|
+
function adaptPathPrefixViaType( expr, config, user ) {
|
|
492
|
+
const { destination, origin } = config;
|
|
493
|
+
if (!destination._main && !origin._main)
|
|
499
494
|
return false; // no need to rewrite; both are top-level
|
|
500
495
|
|
|
501
|
-
if (rejectOuterReference( expr, origin, config ))
|
|
496
|
+
if (rejectOuterReference( expr, origin, config, user ))
|
|
502
497
|
return true;
|
|
503
498
|
|
|
504
499
|
// $self-paths via types from/to non-main artifacts always need to be rewritten.
|
|
505
|
-
config.tokenExpr
|
|
500
|
+
if (config.tokenExpr)
|
|
501
|
+
config.tokenExpr.$tokenTexts = true;
|
|
506
502
|
|
|
507
503
|
const wasAbsolute = isAnnoPathAbsolute( expr );
|
|
508
504
|
stripAbsolutePathPrefix( expr, origin );
|
|
509
505
|
|
|
510
506
|
if (wasAbsolute) {
|
|
511
|
-
prependRootPath( origin,
|
|
507
|
+
prependRootPath( origin, destination, expr );
|
|
512
508
|
}
|
|
513
|
-
else if (!isAnnoRootArt(
|
|
514
|
-
const item = { id:
|
|
515
|
-
setArtifactLink( item,
|
|
509
|
+
else if (!isAnnoRootArt( destination )) { // destination is element
|
|
510
|
+
const item = { id: destination.name.id };
|
|
511
|
+
setArtifactLink( item, destination );
|
|
516
512
|
prependToStrippedPath( origin, expr, [ item ] );
|
|
517
513
|
}
|
|
518
|
-
else if (
|
|
514
|
+
else if (destination.kind === 'param') {
|
|
519
515
|
// annotations on parameters need a `:prefix`
|
|
520
|
-
prependRootPath( origin,
|
|
516
|
+
prependRootPath( origin, destination, expr );
|
|
521
517
|
}
|
|
522
518
|
else {
|
|
523
519
|
prependToStrippedPath( origin, expr, [ ] );
|
|
@@ -526,7 +522,16 @@ function xprRewriteFns( model ) {
|
|
|
526
522
|
return false;
|
|
527
523
|
}
|
|
528
524
|
|
|
529
|
-
|
|
525
|
+
/**
|
|
526
|
+
* Rewrite the initial part of a path in an expression starting with $self on an
|
|
527
|
+
* expanded element:
|
|
528
|
+
*
|
|
529
|
+
* - nothing to do when not starting with $self (TODO: param?)
|
|
530
|
+
* - otherwise error if options.beta.rewriteAnnotationExpressionsViaType is not set
|
|
531
|
+
*
|
|
532
|
+
* @returns {boolean} Returns true if it couldn't be rewritten.
|
|
533
|
+
*/
|
|
534
|
+
function adaptPathPrefixViaTypeExpansion( expr, config, user ) {
|
|
530
535
|
const root = expr.path[0]?._navigation;
|
|
531
536
|
if (root?.kind !== '$self') {
|
|
532
537
|
// non-self paths are always valid in expanded artifacts
|
|
@@ -539,16 +544,17 @@ function xprRewriteFns( model ) {
|
|
|
539
544
|
if (!isBetaEnabled( model.options, 'rewriteAnnotationExpressionsViaType' ))
|
|
540
545
|
return reportAnnoRewriteError( expr, config, 'unsupported' );
|
|
541
546
|
|
|
542
|
-
if (rejectOuterReference( expr, config.expandedRootType, config ))
|
|
547
|
+
if (rejectOuterReference( expr, config.expandedRootType, config, user ))
|
|
543
548
|
return true;
|
|
544
549
|
|
|
545
550
|
stripAbsolutePathPrefix( expr, config.expandedRootType );
|
|
546
551
|
prependRootPath( config.expandedRootType, config.expandedRoot, expr );
|
|
547
|
-
setExpandStatusAnnotate( config.
|
|
552
|
+
setExpandStatusAnnotate( config.destination, 'annotate' );
|
|
548
553
|
|
|
549
|
-
config.
|
|
554
|
+
config.destination[config.anno].$inferred = 'anno-rewrite';
|
|
550
555
|
// $self-paths via type expansion always need to be rewritten.
|
|
551
|
-
config.tokenExpr
|
|
556
|
+
if (config.tokenExpr)
|
|
557
|
+
config.tokenExpr.$tokenTexts = true;
|
|
552
558
|
|
|
553
559
|
return false;
|
|
554
560
|
}
|
|
@@ -586,36 +592,33 @@ function xprRewriteFns( model ) {
|
|
|
586
592
|
if (relativeRoot >= 1)
|
|
587
593
|
expr.path = expr.path.slice(relativeRoot);
|
|
588
594
|
else if (relativeRoot === -1)
|
|
589
|
-
throw new CompilerAssertion('Error while rewriting annotation');
|
|
595
|
+
throw new CompilerAssertion( 'Error while rewriting annotation' );
|
|
590
596
|
}
|
|
591
597
|
|
|
592
598
|
/**
|
|
593
|
-
* Returns false if the path can be propagated to the
|
|
594
|
-
* to any "outer" elements. It differentiates between the
|
|
599
|
+
* Returns false if the path can be propagated to the destination without referring
|
|
600
|
+
* to any "outer" elements. It differentiates between the destination being a main
|
|
595
601
|
* artifact and elements, because an element annotation referring to itself can't
|
|
596
602
|
* be propagated to a type:
|
|
597
603
|
*
|
|
598
604
|
* type T1 : { @a: (elem) elem: String; };
|
|
599
605
|
* type T2 : T1:elem; // invalid
|
|
600
606
|
*
|
|
601
|
-
* Also considers other
|
|
602
|
-
*
|
|
603
|
-
* @param {XSN.Expression} expr
|
|
604
|
-
* @param {XSN.Artifact} origin
|
|
605
|
-
* @param {AnnoRewriteConfig} config
|
|
606
|
-
* @returns {boolean}
|
|
607
|
+
* Also considers other destinations such as `returns`, etc.
|
|
607
608
|
*/
|
|
608
|
-
function rejectOuterReference( expr, origin, config ) {
|
|
609
|
+
function rejectOuterReference( expr, origin, config, user ) {
|
|
609
610
|
if (!isAnnoPathAbsolute( expr ) && !origin._main)
|
|
610
611
|
return false;
|
|
611
612
|
|
|
612
613
|
const root = expr.path[0]?._navigation;
|
|
613
614
|
const found = findRelativeRoot( expr, origin );
|
|
615
|
+
const destination = destinationOrAssocTarget( config, user );
|
|
614
616
|
const isInvalid = (found === -1) ||
|
|
615
617
|
// Can't use paths with `$self` in `returns`.
|
|
616
|
-
(root?.kind === '$self' && isReturnParam( config.
|
|
618
|
+
(root?.kind === '$self' && isReturnParam( config.destinationRoot )) ||
|
|
617
619
|
// siblings are allowed for non-main artifacts, except for 'returns'
|
|
618
|
-
(!
|
|
620
|
+
(!destination._main || isReturnParam( destination )) &&
|
|
621
|
+
(expr.path.length - found) <= 1;
|
|
619
622
|
|
|
620
623
|
if (isInvalid)
|
|
621
624
|
return reportAnnoRewriteError( expr, config );
|
|
@@ -632,7 +635,7 @@ function xprRewriteFns( model ) {
|
|
|
632
635
|
* @returns {number}
|
|
633
636
|
*/
|
|
634
637
|
function findRelativeRoot( expr, origin ) {
|
|
635
|
-
if (!origin._main) // main artifacts can't have outer references
|
|
638
|
+
if (!origin._main) // main artifacts can't have outer references: $self → 0, -1 otherwise
|
|
636
639
|
return expr.path[0]?._artifact === origin ? 0 : -1;
|
|
637
640
|
|
|
638
641
|
const { path } = expr;
|
|
@@ -689,7 +692,7 @@ function xprRewriteFns( model ) {
|
|
|
689
692
|
* @returns {*|null}
|
|
690
693
|
*/
|
|
691
694
|
function rewriteItem( expr, config, env, index ) {
|
|
692
|
-
const rewriteTarget = findRewriteTarget( expr, index, env, config.
|
|
695
|
+
const rewriteTarget = findRewriteTarget( expr, index, env, config.destination );
|
|
693
696
|
const found = setArtifactLink( expr.path[index], rewriteTarget[0] );
|
|
694
697
|
if (!found)
|
|
695
698
|
return null;
|
|
@@ -703,7 +706,8 @@ function xprRewriteFns( model ) {
|
|
|
703
706
|
const item = expr.path[index];
|
|
704
707
|
if (item.id !== found.name.id || (rewriteTarget[1] - index) !== 0) {
|
|
705
708
|
// Path was rewritten; original token text string is no longer accurate
|
|
706
|
-
config.tokenExpr
|
|
709
|
+
if (config.tokenExpr)
|
|
710
|
+
config.tokenExpr.$tokenTexts = true;
|
|
707
711
|
item.id = found.name.id;
|
|
708
712
|
}
|
|
709
713
|
|
|
@@ -752,20 +756,20 @@ function isReturnParam( art ) {
|
|
|
752
756
|
}
|
|
753
757
|
|
|
754
758
|
/**
|
|
755
|
-
* Gets the artifact (e.g. element) that was expanded.
|
|
756
|
-
* is an expanded element.
|
|
759
|
+
* Gets the artifact (e.g. element) that was expanded.
|
|
760
|
+
* `destination` is a sub-artifact of that root and is an expanded element.
|
|
757
761
|
*
|
|
758
762
|
* - expandedRoot: Top-most structure that was expanded.
|
|
759
763
|
* - expandedRootType: The type of expandedRoot.
|
|
760
764
|
*
|
|
761
|
-
* @param {XSN.Artifact}
|
|
765
|
+
* @param {XSN.Artifact} destination
|
|
762
766
|
* @returns { {expandedRoot: XSN.Artifact, expandedRootType: XSN.Artifact}}
|
|
763
767
|
*/
|
|
764
|
-
function getExpandRoot(
|
|
765
|
-
if (
|
|
768
|
+
function getExpandRoot( destination ) {
|
|
769
|
+
if (destination.$inferred !== 'expanded' && destination.$inferred !== 'rewrite')
|
|
766
770
|
return { expandedRoot: null, expandedRootType: null };
|
|
767
771
|
|
|
768
|
-
let expandedRoot =
|
|
772
|
+
let expandedRoot = destination;
|
|
769
773
|
|
|
770
774
|
// 'expanded' for structures, 'rewrite' for foreign keys
|
|
771
775
|
while (expandedRoot.$inferred === 'expanded' || expandedRoot.$inferred === 'rewrite')
|