@sap/cds-compiler 2.11.4 → 2.12.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 +58 -1
- package/bin/cds_update_identifiers.js +7 -7
- package/bin/cdsc.js +9 -10
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +12 -0
- package/lib/api/main.js +2 -0
- package/lib/api/options.js +2 -2
- package/lib/base/message-registry.js +31 -2
- package/lib/base/model.js +1 -0
- package/lib/base/optionProcessorHelper.js +97 -69
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/checks/actionsFunctions.js +2 -1
- package/lib/checks/foreignKeys.js +4 -4
- package/lib/checks/managedInType.js +4 -4
- package/lib/checks/queryNoDbArtifacts.js +1 -3
- package/lib/checks/sql-snippets.js +93 -0
- package/lib/checks/validator.js +8 -0
- package/lib/compiler/assert-consistency.js +5 -3
- package/lib/compiler/base.js +0 -1
- package/lib/compiler/checks.js +32 -9
- package/lib/compiler/definer.js +25 -4
- package/lib/compiler/index.js +1 -1
- package/lib/compiler/propagator.js +3 -2
- package/lib/compiler/resolver.js +97 -6
- package/lib/compiler/shared.js +12 -1
- package/lib/compiler/utils.js +7 -0
- package/lib/edm/annotations/genericTranslation.js +34 -17
- package/lib/edm/annotations/preprocessAnnotations.js +1 -1
- package/lib/edm/csn2edm.js +1 -1
- package/lib/edm/edm.js +8 -8
- package/lib/edm/edmPreprocessor.js +30 -23
- package/lib/edm/edmUtils.js +11 -12
- package/lib/gen/Dictionary.json +82 -40
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -1
- package/lib/gen/language.tokens +15 -14
- package/lib/gen/languageLexer.interp +9 -1
- package/lib/gen/languageLexer.js +830 -779
- package/lib/gen/languageLexer.tokens +7 -6
- package/lib/gen/languageParser.js +2401 -2282
- package/lib/json/from-csn.js +47 -16
- package/lib/json/to-csn.js +17 -5
- package/lib/language/antlrParser.js +3 -3
- package/lib/language/docCommentParser.js +1 -1
- package/lib/language/genericAntlrParser.js +68 -51
- package/lib/language/language.g4 +128 -74
- package/lib/language/multiLineStringParser.js +536 -0
- package/lib/main.d.ts +5 -3
- package/lib/main.js +3 -2
- package/lib/model/csnRefs.js +116 -68
- package/lib/model/csnUtils.js +40 -48
- package/lib/model/enrichCsn.js +30 -14
- package/lib/optionProcessor.js +3 -3
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/manageConstraints.js +1 -1
- package/lib/render/toCdl.js +193 -79
- package/lib/render/toHdbcds.js +179 -95
- package/lib/render/toRename.js +7 -10
- package/lib/render/toSql.js +57 -40
- package/lib/render/utils/common.js +24 -5
- package/lib/render/utils/sql.js +6 -4
- package/lib/transform/braceExpression.js +4 -2
- package/lib/transform/db/associations.js +389 -0
- package/lib/transform/db/cdsPersistence.js +150 -0
- package/lib/transform/db/constraints.js +6 -4
- package/lib/transform/db/draft.js +3 -2
- package/lib/transform/db/expansion.js +4 -5
- package/lib/transform/db/flattening.js +5 -6
- package/lib/transform/db/temporal.js +236 -0
- package/lib/transform/db/transformExists.js +36 -23
- package/lib/transform/forHanaNew.js +35 -626
- package/lib/transform/forOdataNew.js +5 -4
- package/lib/transform/localized.js +3 -14
- package/lib/transform/odata/generateForeignKeyElements.js +2 -2
- package/lib/transform/transformUtilsNew.js +13 -13
- package/lib/transform/translateAssocsToJoins.js +8 -8
- package/lib/transform/universalCsnEnricher.js +217 -47
- package/lib/utils/file.js +2 -1
- package/lib/utils/timetrace.js +8 -2
- package/package.json +1 -1
package/lib/model/csnRefs.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
// a reference is context-dependent, especially if queries are involved. This
|
|
5
5
|
// module provides the corresponding resolve/inspect functions.
|
|
6
6
|
//
|
|
7
|
+
// This module should work with both client-style CSN and Universal CSN.
|
|
8
|
+
//
|
|
7
9
|
// See below for preconditions / things to consider – the functions in this
|
|
8
10
|
// module do not issue user-friendly messages for invalid references in a CSN,
|
|
9
11
|
// such messages are (hopefully) issued by the compile() function.
|
|
@@ -41,18 +43,18 @@
|
|
|
41
43
|
//
|
|
42
44
|
// 1. a well-formed CSN with valid references;
|
|
43
45
|
// 2. a compiled model, i.e. a CSN with all inferred information provided by
|
|
44
|
-
// the compile() function
|
|
45
|
-
//
|
|
46
|
+
// the compile() function for the CSN flavors `client` or `universal`
|
|
47
|
+
// (including the (non-)enumerable `elements` property in `client` CSN);
|
|
46
48
|
// 3. no (relevant) CSN changes between the calls of the same instance of
|
|
47
49
|
// inspectRef() - to enable caching.
|
|
48
50
|
//
|
|
49
51
|
// If any of these conditions are not given, our functions usually simply
|
|
50
52
|
// throws an exception (which might even be a plain TypeError), but it might
|
|
51
|
-
// also
|
|
53
|
+
// also just return any value. CSN processors can provide user-friendly error
|
|
52
54
|
// messages by calling the Core Compiler in case of exceptions. For details,
|
|
53
55
|
// see internalDoc/CoreCompiler.md#use-of-the-core-compiler-for-csn-processors.
|
|
54
56
|
|
|
55
|
-
// During a transformation, care must be
|
|
57
|
+
// During a transformation, care must be taken to adhere to these conditions.
|
|
56
58
|
// E.g. a structure flattening function cannot create an element `s_x` and
|
|
57
59
|
// delete `s` and then still expects inspectRef() to be able to resolve a
|
|
58
60
|
// reference `['s', 'x']`.
|
|
@@ -93,7 +95,7 @@
|
|
|
93
95
|
//
|
|
94
96
|
// The names in further path items are searched in the “navigation” environment
|
|
95
97
|
// of the path so far - it does not need to depend on the reference context (as
|
|
96
|
-
// we do not check the
|
|
98
|
+
// we do not check the validity here):
|
|
97
99
|
//
|
|
98
100
|
// 1. We search in the elements of the target entity for associations and
|
|
99
101
|
// compositions, and in the elements of the current object otherwise.
|
|
@@ -157,6 +159,24 @@
|
|
|
157
159
|
// hierarchy, query number, table aliases and links from a column to its
|
|
158
160
|
// respective inferred element.
|
|
159
161
|
|
|
162
|
+
// Properties in cache:
|
|
163
|
+
//
|
|
164
|
+
// - _effectiveType on def/member/items: cached result of effectiveType()
|
|
165
|
+
// - _origin on def/member/items: the "prototype"
|
|
166
|
+
// - $origin on def/member/items: whether implicit _origin refs have been set for direct members
|
|
167
|
+
// - _parent: not yet used (for debugging)
|
|
168
|
+
//
|
|
169
|
+
// - _env on non-string path item: environment provided by the ref so far,
|
|
170
|
+
// next path item is element in it
|
|
171
|
+
// - _ref on non-string `type` or `from` ref, or on alias: the referred def/elem
|
|
172
|
+
//
|
|
173
|
+
// - $queries on def: array of all queries of an entity
|
|
174
|
+
// - $queryNumber: the index position +1 of a query inside the $queries array
|
|
175
|
+
// - $aliases on query: dictionary of alias names to cache with _ref/_select and elements
|
|
176
|
+
// - _select: value of the `SELECT` property of a query (or value of `projection`)
|
|
177
|
+
// - elements: the elements of the query (original CSN elements from query or main)
|
|
178
|
+
// - _element on query column: the corresponding element
|
|
179
|
+
|
|
160
180
|
'use strict';
|
|
161
181
|
|
|
162
182
|
const BUILTIN_TYPE = {};
|
|
@@ -178,12 +198,13 @@ const referenceSemantics = {
|
|
|
178
198
|
targetAspect: { lexical: false, dynamic: 'global' },
|
|
179
199
|
from: { lexical: false, dynamic: 'global' },
|
|
180
200
|
keys: { lexical: false, dynamic: 'target' },
|
|
201
|
+
keys_origin: { lexical: false, dynamic: 'target' },
|
|
181
202
|
excluding: { lexical: false, dynamic: 'source' },
|
|
182
203
|
expand: { lexical: justDollar, dynamic: 'expand' }, // ...using baseEnv
|
|
183
204
|
inline: { lexical: justDollar, dynamic: 'inline' }, // ...using baseEnv
|
|
184
205
|
ref_where: { lexical: justDollar , dynamic: 'ref-target'}, // ...using baseEnv
|
|
185
206
|
on: { lexical: justDollar, dynamic: 'query' }, // assoc defs, redirected to
|
|
186
|
-
// there are also '
|
|
207
|
+
// there are also 'on_join' and 'on_mixin' with default semantics
|
|
187
208
|
orderBy: { lexical: query => query, dynamic: 'query' },
|
|
188
209
|
orderBy_set: { lexical: query => query.$next, dynamic: 'query' }, // to outer SELECT (from UNION)
|
|
189
210
|
// default: { lexical: query => query, dynamic: 'source' }
|
|
@@ -249,20 +270,18 @@ function csnRefs( csn ) {
|
|
|
249
270
|
* @param {CSN.Artifact} art
|
|
250
271
|
*/
|
|
251
272
|
function navigationEnv( art ) {
|
|
252
|
-
let
|
|
273
|
+
let env = effectiveType( art );
|
|
274
|
+
getOrigin( env ); // also set implicit origins
|
|
253
275
|
// here, we do not care whether it is semantically ok to navigate into sub
|
|
254
276
|
// elements of array items (that is the task of the core compiler /
|
|
255
277
|
// semantic check)
|
|
256
|
-
while (
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
if (origin && origin !== BUILTIN_TYPE)
|
|
264
|
-
cached( env, '$origin', _a => setImplicitOrigin( env, origin ) );
|
|
265
|
-
return env;
|
|
278
|
+
while (env.items)
|
|
279
|
+
env = effectiveType( env.items );
|
|
280
|
+
if (!env.target)
|
|
281
|
+
return env;
|
|
282
|
+
const target = csn.definitions[env.target];
|
|
283
|
+
getOrigin( target );
|
|
284
|
+
return target;
|
|
266
285
|
}
|
|
267
286
|
|
|
268
287
|
/**
|
|
@@ -294,11 +313,28 @@ function csnRefs( csn ) {
|
|
|
294
313
|
return art;
|
|
295
314
|
}
|
|
296
315
|
|
|
297
|
-
|
|
316
|
+
// Return target when resolving references in 'keys'
|
|
317
|
+
function assocTarget( art, refCtx ) {
|
|
318
|
+
// Call contexts:
|
|
319
|
+
// 1. normal definition of association with explicit foreign keys
|
|
320
|
+
// 2. auto-redirected association with renaming of foreign keys
|
|
321
|
+
// (currently: `keys` always available on inherited associations)
|
|
322
|
+
// 3. user-induced redirection (in 'cast') with explicit foreign keys
|
|
323
|
+
// 4. original provided association def inside $origin with explicit foreign keys
|
|
324
|
+
// (outside $origin like 2)
|
|
325
|
+
const targetName = refCtx !== 'keys_origin' && art.target
|
|
326
|
+
|| art.$origin && art.$origin.target
|
|
327
|
+
|| art.cast.target;
|
|
328
|
+
const target = csn.definitions[targetName];
|
|
329
|
+
getOrigin( target ); // induce implicit $origin on target
|
|
330
|
+
return target;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function getOrigin( art ) {
|
|
298
334
|
const origin = cached( art, '_origin', getOriginRaw );
|
|
299
335
|
if (origin && origin !== BUILTIN_TYPE)
|
|
300
336
|
cached( art, '$origin', _a => setImplicitOrigin( art, origin ) );
|
|
301
|
-
return
|
|
337
|
+
return origin;
|
|
302
338
|
}
|
|
303
339
|
|
|
304
340
|
function getOriginRaw( art ) {
|
|
@@ -308,6 +344,7 @@ function csnRefs( csn ) {
|
|
|
308
344
|
return null;
|
|
309
345
|
// art.$origin must not be a string here - shortened refs should already
|
|
310
346
|
// have been used to set the _origin cache
|
|
347
|
+
// If $origin object, return $origin of that:
|
|
311
348
|
if (!Array.isArray( art.$origin )) // anonymous prototype in $origin
|
|
312
349
|
return cached( art.$origin, '_origin', getOriginRaw );
|
|
313
350
|
const [ head, ...tail ] = art.$origin;
|
|
@@ -344,6 +381,8 @@ function csnRefs( csn ) {
|
|
|
344
381
|
// if (origin)
|
|
345
382
|
// cached( art, '$origin', _a => setImplicitOrigin( art, origin ) );
|
|
346
383
|
// console.log(art)
|
|
384
|
+
if (art.returns) // for ["main", {action: "act"}, "elem"]
|
|
385
|
+
art = art.returns;
|
|
347
386
|
return (art.elements || art.enum || targetAspect( art ).elements)[elem];
|
|
348
387
|
}
|
|
349
388
|
|
|
@@ -444,16 +483,16 @@ function csnRefs( csn ) {
|
|
|
444
483
|
|
|
445
484
|
const head = pathId( path[0] );
|
|
446
485
|
if (ref.param)
|
|
447
|
-
return resolvePath( path, main.params[head], 'param' );
|
|
486
|
+
return resolvePath( path, main.params[head], main, 'param' );
|
|
448
487
|
|
|
449
488
|
const semantics = referenceSemantics[refCtx] || {};
|
|
450
489
|
if (semantics.dynamic === 'global' || ref.global)
|
|
451
|
-
return resolvePath( path, csn.definitions[head], 'global', refCtx === 'from' );
|
|
490
|
+
return resolvePath( path, csn.definitions[head], null, 'global', refCtx === 'from' );
|
|
452
491
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
cached( main, '$
|
|
456
|
-
|
|
492
|
+
if (main) {
|
|
493
|
+
getOrigin( main );
|
|
494
|
+
cached( main, '$queries', allQueries );
|
|
495
|
+
}
|
|
457
496
|
let qcache = query && cache.get( query.projection || query );
|
|
458
497
|
// BACKEND ISSUE: you cannot call csnRefs(), inspect some refs, change the
|
|
459
498
|
// CSN and again inspect some refs without calling csnRefs() before!
|
|
@@ -469,39 +508,38 @@ function csnRefs( csn ) {
|
|
|
469
508
|
while (cache) {
|
|
470
509
|
const alias = tryAlias && cache.$aliases[head];
|
|
471
510
|
if (alias)
|
|
472
|
-
return resolvePath( path, alias._select || alias._ref,
|
|
511
|
+
return resolvePath( path, alias._select || alias._ref, null,
|
|
512
|
+
'alias', cache.$queryNumber );
|
|
473
513
|
const mixin = cache._select.mixin && cache._select.mixin[head];
|
|
474
514
|
if (mixin && {}.hasOwnProperty.call( cache._select.mixin, head ))
|
|
475
|
-
return resolvePath( path, mixin, 'mixin', cache.$queryNumber );
|
|
515
|
+
return resolvePath( path, mixin, null, 'mixin', cache.$queryNumber );
|
|
476
516
|
cache = cache.$next;
|
|
477
517
|
}
|
|
478
518
|
if (head.charAt(0) === '$') {
|
|
479
519
|
if (head !== '$self' && head !== '$projection')
|
|
480
520
|
return { scope: '$magic' };
|
|
481
521
|
const self = qcache && qcache.$queryNumber > 1 ? qcache._select : main;
|
|
482
|
-
return resolvePath( path, self, '$self' );
|
|
522
|
+
return resolvePath( path, self, null, '$self' );
|
|
483
523
|
}
|
|
484
524
|
}
|
|
485
525
|
// now the dynamic environment: ------------------------------------------
|
|
486
526
|
if (semantics.dynamic === 'target') { // ref in keys
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
// target
|
|
490
|
-
const target = csn.definitions[parent.target || parent.$origin && parent.$origin.target || parent.cast.target];
|
|
491
|
-
return resolvePath( path, target.elements[head], 'target' );
|
|
527
|
+
const target = assocTarget( parent, refCtx );
|
|
528
|
+
return resolvePath( path, target.elements[head], target, 'target' );
|
|
492
529
|
}
|
|
493
530
|
if (baseEnv) // ref-target (filter condition), expand, inline
|
|
494
|
-
return resolvePath( path, baseEnv.elements[head], semantics.dynamic );
|
|
531
|
+
return resolvePath( path, baseEnv.elements[head], baseEnv, semantics.dynamic );
|
|
495
532
|
if (!query) // outside queries - TODO: items?
|
|
496
|
-
return resolvePath( path, parent.elements[head], 'parent' );
|
|
533
|
+
return resolvePath( path, parent.elements[head], parent, 'parent' );
|
|
497
534
|
|
|
498
535
|
if (semantics.dynamic === 'query')
|
|
499
536
|
// TODO: for ON condition in expand, would need to use cached _element
|
|
500
|
-
return resolvePath( path, qcache.elements[head], 'query' );
|
|
537
|
+
return resolvePath( path, qcache.elements[head], null, 'query' );
|
|
501
538
|
for (const name in qcache.$aliases) {
|
|
502
|
-
const
|
|
539
|
+
const alias = qcache.$aliases[name];
|
|
540
|
+
const found = alias.elements[head];
|
|
503
541
|
if (found)
|
|
504
|
-
return resolvePath( path, found, 'source', name )
|
|
542
|
+
return resolvePath( path, found, alias._ref, 'source', name )
|
|
505
543
|
}
|
|
506
544
|
// console.log(query.SELECT,qcache,qcache.$next,main)
|
|
507
545
|
throw new Error ( `Path item ${ 0 }=${ head } refers to nothing, refCtx: ${ refCtx }` );
|
|
@@ -511,8 +549,9 @@ function csnRefs( csn ) {
|
|
|
511
549
|
* @param {CSN.Path} path
|
|
512
550
|
* @param {CSN.Artifact} art
|
|
513
551
|
* @param {string} [scope]
|
|
552
|
+
* @param [extraInfo]
|
|
514
553
|
*/
|
|
515
|
-
function resolvePath( path, art, scope, extraInfo ) {
|
|
554
|
+
function resolvePath( path, art, parent, scope, extraInfo ) {
|
|
516
555
|
/** @type {{idx, art?, env?}[]} */
|
|
517
556
|
const links = path.map( (_v, idx) => ({ idx }) );
|
|
518
557
|
// TODO: backends should be changed to enable uncommenting:
|
|
@@ -520,11 +559,11 @@ function csnRefs( csn ) {
|
|
|
520
559
|
// throw new Error ( `Path item ${ 0 }=${ pathId( path[0] ) } refers to nothing, scope: ${ scope }`);
|
|
521
560
|
links[0].art = art;
|
|
522
561
|
for (let i = 1; i < links.length; ++i) { // yes, starting at 1, links[0] is set above
|
|
523
|
-
|
|
524
|
-
links[i - 1].env =
|
|
562
|
+
parent = navigationEnv( art );
|
|
563
|
+
links[i - 1].env = parent;
|
|
525
564
|
if (typeof path[i - 1] !== 'string')
|
|
526
|
-
setCache( path[i - 1], '_env',
|
|
527
|
-
art =
|
|
565
|
+
setCache( path[i - 1], '_env', parent );
|
|
566
|
+
art = parent.elements[pathId( path[i] )];
|
|
528
567
|
if (!art) {
|
|
529
568
|
const env = links[i - 1].env;
|
|
530
569
|
const loc = env.name && env.name.$location || env.$location;
|
|
@@ -537,14 +576,16 @@ function csnRefs( csn ) {
|
|
|
537
576
|
if (fromRef || typeof last !== 'string') {
|
|
538
577
|
const env = navigationEnv( art );
|
|
539
578
|
links[links.length - 1].env = env;
|
|
540
|
-
if (fromRef)
|
|
579
|
+
if (fromRef) {
|
|
541
580
|
art = env;
|
|
581
|
+
parent = null;
|
|
582
|
+
}
|
|
542
583
|
if (typeof last !== 'string')
|
|
543
584
|
setCache( last, '_env', env )
|
|
544
585
|
}
|
|
545
586
|
return (extraInfo && !fromRef)
|
|
546
|
-
? { links, art, scope, $env: extraInfo }
|
|
547
|
-
: { links, art, scope };
|
|
587
|
+
? { links, art, parent, scope, $env: extraInfo }
|
|
588
|
+
: { links, art, parent, scope };
|
|
548
589
|
}
|
|
549
590
|
|
|
550
591
|
/**
|
|
@@ -666,8 +707,8 @@ function csnRefs( csn ) {
|
|
|
666
707
|
// i.e. a value with an `elements` property.
|
|
667
708
|
// TODO: only used in forHanaNew - move somewhere else
|
|
668
709
|
/**
|
|
669
|
-
* @param {
|
|
670
|
-
* @param {
|
|
710
|
+
* @param {object} query node (object with SET or SELECT property)
|
|
711
|
+
* @param {object} main definition
|
|
671
712
|
*/
|
|
672
713
|
function queryOrMain( query, main ) {
|
|
673
714
|
while (query.SET)
|
|
@@ -691,7 +732,7 @@ function queryOrMain( query, main ) {
|
|
|
691
732
|
* @param {CSN.Query} query
|
|
692
733
|
* @param {CSN.QuerySelect} fromSelect
|
|
693
734
|
* @param {CSN.Query} parentQuery
|
|
694
|
-
* @param {(query: CSN.Query&CSN.QueryFrom, select: CSN.QuerySelectEnriched) => void} callback
|
|
735
|
+
* @param {(query: CSN.Query&CSN.QueryFrom, select: CSN.QuerySelectEnriched, parentQuery: CSN.Query) => void} callback
|
|
695
736
|
*/
|
|
696
737
|
function traverseQuery( query, fromSelect, parentQuery, callback ) {
|
|
697
738
|
const select = query.SELECT || query.projection;
|
|
@@ -755,30 +796,42 @@ function implicitAs( ref ) {
|
|
|
755
796
|
return id.substring( id.lastIndexOf('.') + 1 );
|
|
756
797
|
}
|
|
757
798
|
|
|
799
|
+
function startCsnPath( csnPath, csn ) {
|
|
800
|
+
const head = csnPath[0];
|
|
801
|
+
if (typeof head !== 'string') {
|
|
802
|
+
const { main, parent, art } = head;
|
|
803
|
+
return { index: 1, main, parent, art };
|
|
804
|
+
}
|
|
805
|
+
if (csnPath.length < 2 || csnPath[0] !== 'definitions')
|
|
806
|
+
throw new Error( 'References outside definitions not supported yet');
|
|
807
|
+
const art = csn.definitions[csnPath[1]];
|
|
808
|
+
return { index: 2, main: art, parent: null, art };
|
|
809
|
+
}
|
|
810
|
+
|
|
758
811
|
/**
|
|
759
812
|
* @param {CSN.Path} csnPath
|
|
760
813
|
* @param {CSN.Model} csn
|
|
761
814
|
*/
|
|
762
815
|
function analyseCsnPath( csnPath, csn, resolve ) {
|
|
763
|
-
if (csnPath[0] !== 'definitions')
|
|
764
|
-
throw new Error( 'References outside definitions not supported yet');
|
|
765
|
-
|
|
766
816
|
/** @type {object} */
|
|
767
|
-
let obj = csn;
|
|
768
|
-
let parent = null;
|
|
769
817
|
let query = null;
|
|
770
818
|
let refCtx = null;
|
|
771
|
-
let art = null;
|
|
772
819
|
/** @type {boolean|string|number} */
|
|
773
820
|
let isName = false;
|
|
774
821
|
let baseRef = null;
|
|
775
822
|
let baseEnv = null;
|
|
776
|
-
let main = csn
|
|
823
|
+
let { index, main, parent, art } = startCsnPath( csnPath, csn );
|
|
824
|
+
let obj = art;
|
|
825
|
+
|
|
826
|
+
for (; index < csnPath.length; index++) {
|
|
827
|
+
if (!obj && !resolve)
|
|
828
|
+
// For the semantic location, use current object as best guess
|
|
829
|
+
break;
|
|
777
830
|
|
|
778
|
-
for (let index = 0; index < csnPath.length; index++) {
|
|
779
831
|
const prop = csnPath[index];
|
|
780
832
|
// array item, name/index of artifact/member, (named) argument
|
|
781
833
|
if (isName || Array.isArray( obj )) {
|
|
834
|
+
// TODO: call some kind of resolve.setOrigin()
|
|
782
835
|
if (typeof isName === 'string') {
|
|
783
836
|
parent = art;
|
|
784
837
|
art = obj[prop];
|
|
@@ -791,7 +844,8 @@ function analyseCsnPath( csnPath, csn, resolve ) {
|
|
|
791
844
|
parent = null;
|
|
792
845
|
}
|
|
793
846
|
isName = prop;
|
|
794
|
-
|
|
847
|
+
// if we want to allow auto-redirect of user-provided target with renamed keys:
|
|
848
|
+
refCtx = (refCtx === '$origin' && prop === 'keys') ? 'keys_origin' : prop;
|
|
795
849
|
}
|
|
796
850
|
else if (prop === 'items' || prop === 'returns') {
|
|
797
851
|
art = obj[prop];
|
|
@@ -807,15 +861,13 @@ function analyseCsnPath( csnPath, csn, resolve ) {
|
|
|
807
861
|
}
|
|
808
862
|
else if (prop === 'where' && refCtx === 'ref') {
|
|
809
863
|
if (resolve)
|
|
810
|
-
baseEnv = resolve.ref_where( obj, baseRef, refCtx,
|
|
811
|
-
query, parent, baseEnv );
|
|
864
|
+
baseEnv = resolve.ref_where( obj, baseRef, refCtx, main, query, parent, baseEnv );
|
|
812
865
|
refCtx = 'ref_where';
|
|
813
866
|
}
|
|
814
867
|
else if (prop === 'expand' || prop === 'inline') {
|
|
815
868
|
if (obj.ref) {
|
|
816
869
|
if (resolve)
|
|
817
|
-
baseEnv = resolve.expandInline( obj, refCtx,
|
|
818
|
-
query, parent, baseEnv );
|
|
870
|
+
baseEnv = resolve.expandInline( obj, refCtx, main, query, parent, baseEnv );
|
|
819
871
|
refCtx = prop;
|
|
820
872
|
}
|
|
821
873
|
if (prop === 'expand')
|
|
@@ -823,9 +875,9 @@ function analyseCsnPath( csnPath, csn, resolve ) {
|
|
|
823
875
|
}
|
|
824
876
|
else if (prop === 'on') {
|
|
825
877
|
if (refCtx === 'from')
|
|
826
|
-
refCtx = '
|
|
878
|
+
refCtx = 'on_join';
|
|
827
879
|
else if (refCtx === 'mixin')
|
|
828
|
-
refCtx = '
|
|
880
|
+
refCtx = 'on_mixin';
|
|
829
881
|
else
|
|
830
882
|
refCtx = 'on'; // will use query elements with REDIRECTED TO
|
|
831
883
|
}
|
|
@@ -839,11 +891,7 @@ function analyseCsnPath( csnPath, csn, resolve ) {
|
|
|
839
891
|
else if (prop !== 'xpr') {
|
|
840
892
|
refCtx = prop;
|
|
841
893
|
}
|
|
842
|
-
|
|
843
894
|
obj = obj[prop];
|
|
844
|
-
if (!obj && !resolve)
|
|
845
|
-
// For the semantic location, use current object as best guess
|
|
846
|
-
break;
|
|
847
895
|
}
|
|
848
896
|
// console.log( 'CPATH:', csnPath, refCtx, obj, parent.$location );
|
|
849
897
|
if (!resolve)
|
package/lib/model/csnUtils.js
CHANGED
|
@@ -61,6 +61,7 @@ function getUtils(model) {
|
|
|
61
61
|
effectiveType,
|
|
62
62
|
get$combined,
|
|
63
63
|
getOrigin,
|
|
64
|
+
getQueryPrimarySource,
|
|
64
65
|
};
|
|
65
66
|
|
|
66
67
|
/**
|
|
@@ -176,10 +177,10 @@ function getUtils(model) {
|
|
|
176
177
|
}
|
|
177
178
|
}
|
|
178
179
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
180
|
+
/**
|
|
181
|
+
* Return the name part of the artifact name - no namespace etc.
|
|
182
|
+
* @param {string|object} name Absolute name of the artifact
|
|
183
|
+
*/
|
|
183
184
|
function getBaseName(name) {
|
|
184
185
|
if (!name)
|
|
185
186
|
return name;
|
|
@@ -191,6 +192,27 @@ function getUtils(model) {
|
|
|
191
192
|
}
|
|
192
193
|
}
|
|
193
194
|
|
|
195
|
+
/**
|
|
196
|
+
* Return the left-most, primary source of the given query.
|
|
197
|
+
* @param {*} query Definition's query object
|
|
198
|
+
*/
|
|
199
|
+
function getQueryPrimarySource(query) {
|
|
200
|
+
if (!query)
|
|
201
|
+
return undefined;
|
|
202
|
+
else if (query.SELECT) {
|
|
203
|
+
return getQueryPrimarySource(query.SELECT);
|
|
204
|
+
} else if (query.SET) {
|
|
205
|
+
return getQueryPrimarySource(query.SET);
|
|
206
|
+
} else if (query.from) {
|
|
207
|
+
return getQueryPrimarySource(query.from);
|
|
208
|
+
} else if (query.ref) {
|
|
209
|
+
return query;
|
|
210
|
+
} else if (query.args) {
|
|
211
|
+
return getQueryPrimarySource(query.args[0]);
|
|
212
|
+
}
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
|
|
194
216
|
|
|
195
217
|
/**
|
|
196
218
|
* Create an object to track visited objects identified by a unique string.
|
|
@@ -230,7 +252,7 @@ function getUtils(model) {
|
|
|
230
252
|
* Returns true if an artifact is a structured type
|
|
231
253
|
* or a typedef of a structured type.
|
|
232
254
|
*
|
|
233
|
-
* @param {
|
|
255
|
+
* @param {object} obj
|
|
234
256
|
*/
|
|
235
257
|
function isStructured(obj) {
|
|
236
258
|
return obj.elements ||
|
|
@@ -397,7 +419,7 @@ function getUtils(model) {
|
|
|
397
419
|
function getServiceName(artifactName) {
|
|
398
420
|
for(;;) {
|
|
399
421
|
let idx = artifactName.lastIndexOf('.');
|
|
400
|
-
if (idx
|
|
422
|
+
if (idx === -1) return null;
|
|
401
423
|
artifactName = artifactName.substring(0, idx);
|
|
402
424
|
let artifact = model.definitions[artifactName];
|
|
403
425
|
if (artifact && artifact.kind === 'service') {
|
|
@@ -859,11 +881,14 @@ function forAllElements(artifact, artifactName, cb, includeActions = false){
|
|
|
859
881
|
* @param {CSN.Artifact} artifact
|
|
860
882
|
* @param {string} annotationName Name of the annotation (including the at-sign)
|
|
861
883
|
* @param {any} expected
|
|
884
|
+
* @param {boolean} caseInsensitive
|
|
862
885
|
* @returns {boolean}
|
|
863
886
|
*/
|
|
864
|
-
function hasAnnotationValue(artifact, annotationName, expected = true) {
|
|
887
|
+
function hasAnnotationValue(artifact, annotationName, expected = true, caseInsensitive = false) {
|
|
865
888
|
if(expected === false)
|
|
866
889
|
return artifact[annotationName] === expected || artifact[annotationName] === null;
|
|
890
|
+
else if (typeof artifact[annotationName] === 'string' && caseInsensitive === true)
|
|
891
|
+
return artifact[annotationName].toLowerCase() === expected.toLowerCase();
|
|
867
892
|
else
|
|
868
893
|
return artifact[annotationName] === expected;
|
|
869
894
|
}
|
|
@@ -1232,38 +1257,6 @@ function mergeOptions(...optionsObjects) {
|
|
|
1232
1257
|
}
|
|
1233
1258
|
}
|
|
1234
1259
|
|
|
1235
|
-
// Return the name of the top-level artifact surrounding the artifact 'name'
|
|
1236
|
-
// in 'model'.
|
|
1237
|
-
// We define "top-level artifact" to be an artifact that has either no parent or only
|
|
1238
|
-
// ancestors of kind 'namespace'. Note that it is possible for a non-top-level artifact
|
|
1239
|
-
// to have a namespace as parent and e.g. a context as grandparent (weird but true).
|
|
1240
|
-
// Will return the artifact 'name' if it is a top-level artifact itself, and 'undefined'
|
|
1241
|
-
// if there is no artifact surrounding 'name' in the model
|
|
1242
|
-
// TODO: to be checked by author: still intended behaviour with 'cds' prefix?
|
|
1243
|
-
// TODO: Can this be replaced by getRootArtifactName? Or maybe not rely on namespace-hacking...
|
|
1244
|
-
// FIXME: This only works with namespace-hacking, i.e. adding them as artifacts...
|
|
1245
|
-
function getTopLevelArtifactNameOf(name, model) {
|
|
1246
|
-
let dotIdx = name.indexOf('.');
|
|
1247
|
-
if (dotIdx == -1) {
|
|
1248
|
-
// No '.' in the name, i.e. no parent - this is a top-level artifact (if it exists)
|
|
1249
|
-
return model.definitions[name] ? name : undefined;
|
|
1250
|
-
}
|
|
1251
|
-
// If the first name part is not in the model, there is nothing to find
|
|
1252
|
-
if (!model.definitions[name.substring(0, dotIdx)]) {
|
|
1253
|
-
return undefined;
|
|
1254
|
-
}
|
|
1255
|
-
// Skip forward through '.'s until finding a non-namespace
|
|
1256
|
-
while (dotIdx != -1 && (!model.definitions[name.substring(0, dotIdx)] || model.definitions[name.substring(0, dotIdx)].kind === 'namespace')) {
|
|
1257
|
-
dotIdx = name.indexOf('.', dotIdx + 1);
|
|
1258
|
-
}
|
|
1259
|
-
if (dotIdx == -1) {
|
|
1260
|
-
// This is a top-level artifact
|
|
1261
|
-
return name;
|
|
1262
|
-
}
|
|
1263
|
-
// The skipped part of 'name' is the top-level artifact name
|
|
1264
|
-
return name.substring(0, dotIdx);
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1267
1260
|
/**
|
|
1268
1261
|
* If the artifact with the name given is part of a context (or multiple), return the top-most context.
|
|
1269
1262
|
* Else, return the artifact itself. Namespaces are not of concern here.
|
|
@@ -1473,8 +1466,8 @@ function isSkipped(artifact) {
|
|
|
1473
1466
|
function walkCsnPath(csn, path) {
|
|
1474
1467
|
/** @type {object} */
|
|
1475
1468
|
let obj = csn;
|
|
1476
|
-
for(
|
|
1477
|
-
obj = obj[
|
|
1469
|
+
for(const segment of path){
|
|
1470
|
+
obj = obj[segment];
|
|
1478
1471
|
}
|
|
1479
1472
|
|
|
1480
1473
|
return obj;
|
|
@@ -1483,16 +1476,16 @@ function walkCsnPath(csn, path) {
|
|
|
1483
1476
|
/**
|
|
1484
1477
|
* If provided, get the replacement string for the given magic variable ref.
|
|
1485
1478
|
* No validation is done that the ref is actually magic!
|
|
1486
|
-
*
|
|
1487
|
-
* @param {array} ref
|
|
1488
|
-
* @param {CSN.Options} options
|
|
1489
|
-
* @returns {string|null}
|
|
1479
|
+
*
|
|
1480
|
+
* @param {array} ref
|
|
1481
|
+
* @param {CSN.Options} options
|
|
1482
|
+
* @returns {string|null}
|
|
1490
1483
|
*/
|
|
1491
1484
|
function getVariableReplacement(ref, options) {
|
|
1492
1485
|
if(options && options.variableReplacements) {
|
|
1493
1486
|
let replacement = options.variableReplacements;
|
|
1494
|
-
for(
|
|
1495
|
-
replacement = replacement[
|
|
1487
|
+
for(const segment of ref) {
|
|
1488
|
+
replacement = replacement[segment];
|
|
1496
1489
|
if(replacement === undefined)
|
|
1497
1490
|
return null;
|
|
1498
1491
|
}
|
|
@@ -1534,7 +1527,6 @@ module.exports = {
|
|
|
1534
1527
|
generatedByCompilerVersion,
|
|
1535
1528
|
getNormalizedQuery,
|
|
1536
1529
|
mergeOptions,
|
|
1537
|
-
getTopLevelArtifactNameOf,
|
|
1538
1530
|
getRootArtifactName,
|
|
1539
1531
|
getLastPartOfRef,
|
|
1540
1532
|
getParentNamesOf,
|
package/lib/model/enrichCsn.js
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
|
|
8
8
|
// * `File.cds:3:5` if the original CSN has a non-enumerable `$location` property
|
|
9
9
|
// with value `{file: "File.cds", line: 3, col: 5}`.
|
|
10
|
-
// * `File.cds:3:5
|
|
10
|
+
// * `File.cds:3:5^` if the original CSN has _no_ `$location` property, for an
|
|
11
11
|
// inferred member of a main artifact or member with `$location: `File.cds:3:5`;
|
|
12
|
-
// the number of
|
|
12
|
+
// the number of `^`s in the suffix is the member depth.
|
|
13
13
|
|
|
14
14
|
// Other enumerable properties in the JSON for non-enumerable properties in the
|
|
15
15
|
// original CSN:
|
|
@@ -25,11 +25,17 @@
|
|
|
25
25
|
// * `_type`, `_includes` and `_targets` have as values the `$location`s of the
|
|
26
26
|
// referred artifacts which are returned by function `artifactRef`.
|
|
27
27
|
// * `_links` and `_art` as sibling properties of `ref` have as values the
|
|
28
|
-
// `$locations` of the artifacts/members returned by function `inspectRef
|
|
28
|
+
// `$locations` of the artifacts/members returned by function `inspectRef` (`_art`
|
|
29
|
+
// for ref in `from` only, where it is different to the last item of `_links`).
|
|
29
30
|
// * `_scope` and `_env` as sibling properties of `ref` have (string) values,
|
|
30
31
|
// returned by function `inspectRef`, giving add/ info about the “ref base”.
|
|
31
32
|
// * `_origin` (in Universal CSN only) has as value the `$location` of the
|
|
32
33
|
// prototype returned by function getOrigin().
|
|
34
|
+
// * `_test.inspect.csnpath` as sibling property of `ref` has an object value
|
|
35
|
+
// with properties `_links` and `_scope` of a further `inspectRef` call;
|
|
36
|
+
// it is only called, with `[‹art›, ...‹csnpath›]` as argument, if `‹art›`,
|
|
37
|
+
// which is the referred artifact returned by the first `inspectRef` call,
|
|
38
|
+
// has an annotation `@$test.inspect.csnpath` with array value `‹csnpath›`.
|
|
33
39
|
|
|
34
40
|
'use strict';
|
|
35
41
|
|
|
@@ -92,7 +98,7 @@ function enrichCsn( csn, options = {} ) {
|
|
|
92
98
|
// call getOrigin() before standard() to set implicit protos inside standard():
|
|
93
99
|
const origin = handleError( err => err ? err.toString() : getOrigin( obj ) );
|
|
94
100
|
standard( parent, prop, obj );
|
|
95
|
-
if (obj.$origin === undefined && origin != null)
|
|
101
|
+
if (obj.$origin === undefined && !obj.type && origin != null)
|
|
96
102
|
obj._origin = refLocation( origin );
|
|
97
103
|
}
|
|
98
104
|
|
|
@@ -142,16 +148,18 @@ function enrichCsn( csn, options = {} ) {
|
|
|
142
148
|
handleError( err => {
|
|
143
149
|
if (err)
|
|
144
150
|
parent._origin = err.toString();
|
|
145
|
-
else if (Array.isArray( ref ) || typeof ref === 'string') // $origin: […]
|
|
146
|
-
parent._origin = refLocation( getOrigin( parent
|
|
147
|
-
else if ( ref )
|
|
151
|
+
else if (Array.isArray( ref ) || typeof ref === 'string') // $origin: […] / "short-ref"
|
|
152
|
+
parent._origin = refLocation( getOrigin( parent ) );
|
|
153
|
+
else if ( ref ) // $origin: {…}
|
|
148
154
|
standard( parent, prop, ref );
|
|
149
155
|
} );
|
|
150
156
|
}
|
|
151
157
|
|
|
152
|
-
function pathRef( parent, prop, path ) {
|
|
153
|
-
const
|
|
154
|
-
|
|
158
|
+
function pathRef( parent, prop, path, inspectionPath = csnPath ) {
|
|
159
|
+
const inspection = handleError( (err) => {
|
|
160
|
+
return (err) ? { scope: err.toString() } : inspectRef( inspectionPath );
|
|
161
|
+
});
|
|
162
|
+
const { links, art, scope, $env } = inspection;
|
|
155
163
|
if (links)
|
|
156
164
|
parent._links = links.map( l => refLocation( l.art ) );
|
|
157
165
|
if (links && links[links.length-1].art !== art)
|
|
@@ -160,6 +168,15 @@ function enrichCsn( csn, options = {} ) {
|
|
|
160
168
|
if ($env)
|
|
161
169
|
parent._env = $env;
|
|
162
170
|
|
|
171
|
+
if (!prop) // recursive call for @$test.inspect.csnpath
|
|
172
|
+
return;
|
|
173
|
+
const testPath = art && art['@$test.inspect.csnpath'];
|
|
174
|
+
if (testPath && parent.ref) {
|
|
175
|
+
const further = {};
|
|
176
|
+
pathRef( further, null, null, [ inspection, ...testPath ] );
|
|
177
|
+
parent['_test.inspect.csnpath'] = further;
|
|
178
|
+
}
|
|
179
|
+
|
|
163
180
|
csnPath.push( prop );
|
|
164
181
|
path.forEach( function step( s, i ) {
|
|
165
182
|
if (s && typeof s === 'object') {
|
|
@@ -256,12 +273,11 @@ function setLocations( node, prop, loc ) {
|
|
|
256
273
|
return;
|
|
257
274
|
const isMember = artifactProperties.includes( prop );
|
|
258
275
|
if (!isMember && node.$location) {
|
|
259
|
-
|
|
260
|
-
reveal( node, '$location',
|
|
261
|
-
loc = value + '-';
|
|
276
|
+
loc = locationString( node.$location, true );
|
|
277
|
+
reveal( node, '$location', loc );
|
|
262
278
|
}
|
|
263
279
|
else if (prop === true) {
|
|
264
|
-
loc += '
|
|
280
|
+
loc += '^';
|
|
265
281
|
node.$location = loc;
|
|
266
282
|
}
|
|
267
283
|
if (Array.isArray( node )) {
|