@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.
Files changed (80) hide show
  1. package/CHANGELOG.md +58 -1
  2. package/bin/cds_update_identifiers.js +7 -7
  3. package/bin/cdsc.js +9 -10
  4. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  5. package/doc/CHANGELOG_BETA.md +12 -0
  6. package/lib/api/main.js +2 -0
  7. package/lib/api/options.js +2 -2
  8. package/lib/base/message-registry.js +31 -2
  9. package/lib/base/model.js +1 -0
  10. package/lib/base/optionProcessorHelper.js +97 -69
  11. package/lib/checks/.eslintrc.json +2 -0
  12. package/lib/checks/actionsFunctions.js +2 -1
  13. package/lib/checks/foreignKeys.js +4 -4
  14. package/lib/checks/managedInType.js +4 -4
  15. package/lib/checks/queryNoDbArtifacts.js +1 -3
  16. package/lib/checks/sql-snippets.js +93 -0
  17. package/lib/checks/validator.js +8 -0
  18. package/lib/compiler/assert-consistency.js +5 -3
  19. package/lib/compiler/base.js +0 -1
  20. package/lib/compiler/checks.js +32 -9
  21. package/lib/compiler/definer.js +25 -4
  22. package/lib/compiler/index.js +1 -1
  23. package/lib/compiler/propagator.js +3 -2
  24. package/lib/compiler/resolver.js +97 -6
  25. package/lib/compiler/shared.js +12 -1
  26. package/lib/compiler/utils.js +7 -0
  27. package/lib/edm/annotations/genericTranslation.js +34 -17
  28. package/lib/edm/annotations/preprocessAnnotations.js +1 -1
  29. package/lib/edm/csn2edm.js +1 -1
  30. package/lib/edm/edm.js +8 -8
  31. package/lib/edm/edmPreprocessor.js +30 -23
  32. package/lib/edm/edmUtils.js +11 -12
  33. package/lib/gen/Dictionary.json +82 -40
  34. package/lib/gen/language.checksum +1 -1
  35. package/lib/gen/language.interp +3 -1
  36. package/lib/gen/language.tokens +15 -14
  37. package/lib/gen/languageLexer.interp +9 -1
  38. package/lib/gen/languageLexer.js +830 -779
  39. package/lib/gen/languageLexer.tokens +7 -6
  40. package/lib/gen/languageParser.js +2401 -2282
  41. package/lib/json/from-csn.js +47 -16
  42. package/lib/json/to-csn.js +17 -5
  43. package/lib/language/antlrParser.js +3 -3
  44. package/lib/language/docCommentParser.js +1 -1
  45. package/lib/language/genericAntlrParser.js +68 -51
  46. package/lib/language/language.g4 +128 -74
  47. package/lib/language/multiLineStringParser.js +536 -0
  48. package/lib/main.d.ts +5 -3
  49. package/lib/main.js +3 -2
  50. package/lib/model/csnRefs.js +116 -68
  51. package/lib/model/csnUtils.js +40 -48
  52. package/lib/model/enrichCsn.js +30 -14
  53. package/lib/optionProcessor.js +3 -3
  54. package/lib/render/DuplicateChecker.js +1 -1
  55. package/lib/render/manageConstraints.js +1 -1
  56. package/lib/render/toCdl.js +193 -79
  57. package/lib/render/toHdbcds.js +179 -95
  58. package/lib/render/toRename.js +7 -10
  59. package/lib/render/toSql.js +57 -40
  60. package/lib/render/utils/common.js +24 -5
  61. package/lib/render/utils/sql.js +6 -4
  62. package/lib/transform/braceExpression.js +4 -2
  63. package/lib/transform/db/associations.js +389 -0
  64. package/lib/transform/db/cdsPersistence.js +150 -0
  65. package/lib/transform/db/constraints.js +6 -4
  66. package/lib/transform/db/draft.js +3 -2
  67. package/lib/transform/db/expansion.js +4 -5
  68. package/lib/transform/db/flattening.js +5 -6
  69. package/lib/transform/db/temporal.js +236 -0
  70. package/lib/transform/db/transformExists.js +36 -23
  71. package/lib/transform/forHanaNew.js +35 -626
  72. package/lib/transform/forOdataNew.js +5 -4
  73. package/lib/transform/localized.js +3 -14
  74. package/lib/transform/odata/generateForeignKeyElements.js +2 -2
  75. package/lib/transform/transformUtilsNew.js +13 -13
  76. package/lib/transform/translateAssocsToJoins.js +8 -8
  77. package/lib/transform/universalCsnEnricher.js +217 -47
  78. package/lib/utils/file.js +2 -1
  79. package/lib/utils/timetrace.js +8 -2
  80. package/package.json +1 -1
@@ -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, including the (non-)enumerable `elements`
45
- // property of sub `SELECT`s in a FROM;
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 jsut return any value. CSN processors can provide user-friendly error
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 taked to adhere to these conditions.
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 validility here):
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 'join_on' and 'mixin_on' with default semantics
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 type = effectiveType( art );
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 (type.items) {
257
- cached( type, '$origin', _a => setImplicitOrigin( type, origin ) );
258
- type = effectiveType( type.items );
259
- }
260
- // cannot navigate along targetAspect!
261
- const env = (type.target) ? csn.definitions[type.target] : type;
262
- const origin = cached( env, '_origin', getOriginRaw );
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
- function getOrigin( art, alsoType ) {
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 art.type && !alsoType ? undefined : origin;
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
- const origin = cached( main, '_origin', getOriginRaw )
454
- if (origin)
455
- cached( main, '$origin', _a => setImplicitOrigin( main, origin ) );
456
- cached( main, '$queries', allQueries );
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, 'alias', cache.$queryNumber );
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
- // not selecting the corresponding element for a select column works,
488
- // because explicit keys can only be provided with explicit redirection
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 found = qcache.$aliases[name].elements[head];
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
- art = navigationEnv( art );
524
- links[i - 1].env = art;
562
+ parent = navigationEnv( art );
563
+ links[i - 1].env = parent;
525
564
  if (typeof path[i - 1] !== 'string')
526
- setCache( path[i - 1], '_env', art );
527
- art = art.elements[pathId( path[i] )];
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 {CSN.Query} query node (object with SET or SELECT property)
670
- * @param {CSN.Definition} main
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.definitions[csnPath[1]];
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
- refCtx = prop;
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, csn.definitions[csnPath[1]],
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, csn.definitions[csnPath[1]],
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 = 'join_on';
878
+ refCtx = 'on_join';
827
879
  else if (refCtx === 'mixin')
828
- refCtx = 'mixin_on';
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)
@@ -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
- * Return the name part of the artifact name - no namespace etc.
181
- * @param {string|object} name Absolute name of the artifact
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 {CSN.Artifact} obj
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 == -1) return null;
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(let i = 0; i < path.length; i++){
1477
- obj = obj[path[i]];
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(let i = 0; i < ref.length; i++) {
1495
- replacement = replacement[ref[i]];
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,
@@ -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-1` if the original CSN has _no_ `$location` property, for an
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 digits in the `-1` suffix is the member depth.
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: […], not $origin: {…}
146
- parent._origin = refLocation( getOrigin( parent, true ) );
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 { links, art, scope, $env }
154
- = handleError( err => (err) ? { scope: err.toString() } : inspectRef( csnPath ) );
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
- const value = locationString( node.$location, true );
260
- reveal( node, '$location', value );
261
- loc = value + '-';
276
+ loc = locationString( node.$location, true );
277
+ reveal( node, '$location', loc );
262
278
  }
263
279
  else if (prop === true) {
264
- loc += '1';
280
+ loc += '^';
265
281
  node.$location = loc;
266
282
  }
267
283
  if (Array.isArray( node )) {