@sap/cds-compiler 2.10.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 (103) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/bin/.eslintrc.json +1 -2
  3. package/bin/cds_update_identifiers.js +10 -8
  4. package/bin/cdsc.js +58 -35
  5. package/bin/cdsse.js +1 -0
  6. package/bin/cdsv2m.js +3 -2
  7. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  8. package/doc/CHANGELOG_BETA.md +16 -0
  9. package/lib/api/.eslintrc.json +2 -0
  10. package/lib/api/main.js +10 -36
  11. package/lib/api/options.js +17 -8
  12. package/lib/api/validate.js +30 -3
  13. package/lib/backends.js +12 -13
  14. package/lib/base/dictionaries.js +2 -1
  15. package/lib/base/keywords.js +3 -2
  16. package/lib/base/message-registry.js +64 -11
  17. package/lib/base/messages.js +38 -18
  18. package/lib/base/model.js +6 -4
  19. package/lib/base/optionProcessorHelper.js +148 -86
  20. package/lib/checks/.eslintrc.json +2 -0
  21. package/lib/checks/actionsFunctions.js +2 -1
  22. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  23. package/lib/checks/foreignKeys.js +4 -4
  24. package/lib/checks/managedInType.js +4 -4
  25. package/lib/checks/queryNoDbArtifacts.js +1 -3
  26. package/lib/checks/selectItems.js +4 -0
  27. package/lib/checks/sql-snippets.js +93 -0
  28. package/lib/checks/unknownMagic.js +6 -3
  29. package/lib/checks/validator.js +8 -0
  30. package/lib/compiler/assert-consistency.js +14 -5
  31. package/lib/compiler/base.js +64 -0
  32. package/lib/compiler/builtins.js +62 -16
  33. package/lib/compiler/checks.js +34 -10
  34. package/lib/compiler/definer.js +91 -112
  35. package/lib/compiler/index.js +30 -30
  36. package/lib/compiler/propagator.js +8 -4
  37. package/lib/compiler/resolver.js +279 -63
  38. package/lib/compiler/shared.js +65 -230
  39. package/lib/compiler/utils.js +191 -0
  40. package/lib/edm/annotations/genericTranslation.js +35 -18
  41. package/lib/edm/annotations/preprocessAnnotations.js +1 -1
  42. package/lib/edm/csn2edm.js +4 -3
  43. package/lib/edm/edm.js +8 -8
  44. package/lib/edm/edmPreprocessor.js +61 -59
  45. package/lib/edm/edmUtils.js +14 -15
  46. package/lib/gen/Dictionary.json +82 -40
  47. package/lib/gen/language.checksum +1 -1
  48. package/lib/gen/language.interp +19 -1
  49. package/lib/gen/language.tokens +80 -73
  50. package/lib/gen/languageLexer.interp +27 -1
  51. package/lib/gen/languageLexer.js +925 -826
  52. package/lib/gen/languageLexer.tokens +72 -65
  53. package/lib/gen/languageParser.js +4817 -4102
  54. package/lib/json/from-csn.js +57 -26
  55. package/lib/json/to-csn.js +244 -51
  56. package/lib/language/antlrParser.js +12 -1
  57. package/lib/language/docCommentParser.js +1 -1
  58. package/lib/language/errorStrategy.js +26 -8
  59. package/lib/language/genericAntlrParser.js +106 -30
  60. package/lib/language/language.g4 +200 -70
  61. package/lib/language/multiLineStringParser.js +536 -0
  62. package/lib/main.d.ts +220 -21
  63. package/lib/main.js +6 -3
  64. package/lib/model/api.js +2 -2
  65. package/lib/model/csnRefs.js +218 -86
  66. package/lib/model/csnUtils.js +99 -178
  67. package/lib/model/enrichCsn.js +84 -43
  68. package/lib/model/revealInternalProperties.js +25 -8
  69. package/lib/model/sortViews.js +8 -1
  70. package/lib/modelCompare/compare.js +2 -1
  71. package/lib/optionProcessor.js +33 -18
  72. package/lib/render/.eslintrc.json +1 -2
  73. package/lib/render/DuplicateChecker.js +2 -2
  74. package/lib/render/manageConstraints.js +1 -1
  75. package/lib/render/toCdl.js +202 -82
  76. package/lib/render/toHdbcds.js +194 -135
  77. package/lib/render/toRename.js +7 -10
  78. package/lib/render/toSql.js +91 -51
  79. package/lib/render/utils/common.js +24 -5
  80. package/lib/render/utils/sql.js +6 -4
  81. package/lib/transform/braceExpression.js +4 -2
  82. package/lib/transform/db/applyTransformations.js +189 -0
  83. package/lib/transform/db/associations.js +389 -0
  84. package/lib/transform/db/cdsPersistence.js +150 -0
  85. package/lib/transform/db/constraints.js +275 -119
  86. package/lib/transform/db/draft.js +6 -4
  87. package/lib/transform/db/expansion.js +10 -9
  88. package/lib/transform/db/flattening.js +23 -8
  89. package/lib/transform/db/temporal.js +236 -0
  90. package/lib/transform/db/transformExists.js +106 -25
  91. package/lib/transform/db/views.js +485 -0
  92. package/lib/transform/forHanaNew.js +90 -1036
  93. package/lib/transform/forOdataNew.js +11 -3
  94. package/lib/transform/localized.js +5 -14
  95. package/lib/transform/odata/generateForeignKeyElements.js +2 -2
  96. package/lib/transform/transformUtilsNew.js +34 -20
  97. package/lib/transform/translateAssocsToJoins.js +15 -23
  98. package/lib/transform/universalCsnEnricher.js +217 -47
  99. package/lib/utils/file.js +13 -6
  100. package/lib/utils/term.js +65 -42
  101. package/lib/utils/timetrace.js +55 -27
  102. package/package.json +1 -1
  103. package/lib/transform/db/helpers.js +0 -58
@@ -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' }
@@ -225,17 +246,19 @@ function csnRefs( csn ) {
225
246
  const cachedType = getCache( art, '_effectiveType' );
226
247
  if (cachedType !== undefined)
227
248
  return cachedType;
228
- else if (!art.type && !art.$origin ||
229
- art.elements || art.target || art.targetAspect || art.enum)
230
- return setCache( art, '_effectiveType', art );
231
249
 
232
250
  const chain = [];
233
- while (getCache( art, '_effectiveType' ) === undefined && (art.type || art.$origin) &&
251
+ let origin;
252
+ while (getCache( art, '_effectiveType' ) === undefined &&
253
+ (origin = cached( art, '_origin', getOriginRaw )) &&
234
254
  !art.elements && !art.target && !art.targetAspect && !art.enum && !art.items) {
235
255
  chain.push( art );
236
256
  setCache( art, '_effectiveType', 0 ); // initial setting in case of cycles
237
- art = (art.$origin) ? getOrigin( art ) : artifactRef( art.type, BUILTIN_TYPE );
257
+ art = origin;
238
258
  }
259
+ if (!chain.length)
260
+ return setCache( art, '_effectiveType', art );
261
+
239
262
  if (getCache( art, '_effectiveType' ) === 0)
240
263
  throw new Error( 'Circular type reference');
241
264
  const type = getCache( art, '_effectiveType' ) || art;
@@ -247,14 +270,18 @@ function csnRefs( csn ) {
247
270
  * @param {CSN.Artifact} art
248
271
  */
249
272
  function navigationEnv( art ) {
250
- let type = effectiveType( art );
273
+ let env = effectiveType( art );
274
+ getOrigin( env ); // also set implicit origins
251
275
  // here, we do not care whether it is semantically ok to navigate into sub
252
276
  // elements of array items (that is the task of the core compiler /
253
277
  // semantic check)
254
- while (type.items)
255
- type = effectiveType( type.items );
256
- // cannot navigate along targetAspect!
257
- return (type.target) ? csn.definitions[type.target] : type;
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;
258
285
  }
259
286
 
260
287
  /**
@@ -279,38 +306,129 @@ function csnRefs( csn ) {
279
306
  function artifactPathRef( ref ) {
280
307
  const [ head, ...tail ] = ref.ref;
281
308
  let art = csn.definitions[pathId( head )];
282
- for (const elem of tail)
283
- art = navigationEnv( art ).elements[pathId( elem )];
309
+ for (const elem of tail) {
310
+ const env = navigationEnv( art );
311
+ art = env.elements[pathId( elem )];
312
+ }
284
313
  return art;
285
314
  }
286
315
 
287
- function getOrigin( def ) {
288
- const art = cached( def, '_origin', originPathRef );
289
- if (art)
290
- return art;
291
- throw new Error( 'Undefined origin reference' );
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;
292
331
  }
293
332
 
294
- function originPathRef( def ) {
295
- const [ head, ...tail ] = def.$origin;
296
- let art = csn.definitions[head];
297
- for (const elem of tail)
298
- art = originNavigation( art, elem );
299
- return art;
333
+ function getOrigin( art ) {
334
+ const origin = cached( art, '_origin', getOriginRaw );
335
+ if (origin && origin !== BUILTIN_TYPE)
336
+ cached( art, '$origin', _a => setImplicitOrigin( art, origin ) );
337
+ return origin;
338
+ }
339
+
340
+ function getOriginRaw( art ) {
341
+ if (art.type) // TODO: make robust against "linked" = only direct
342
+ return artifactRef( art.type, BUILTIN_TYPE );
343
+ if (!art.$origin) // implicit $origin should have been set
344
+ return null;
345
+ // art.$origin must not be a string here - shortened refs should already
346
+ // have been used to set the _origin cache
347
+ // If $origin object, return $origin of that:
348
+ if (!Array.isArray( art.$origin )) // anonymous prototype in $origin
349
+ return cached( art.$origin, '_origin', getOriginRaw );
350
+ const [ head, ...tail ] = art.$origin;
351
+ let origin = csn.definitions[head];
352
+ // allow shorter $origin ref for actions/functions, just using a string:
353
+ let isAction = art.kind === 'action' || art.kind === 'function';
354
+ for (const elem of tail) {
355
+ origin = originNavigation( origin, elem, isAction );
356
+ isAction = false;
357
+ }
358
+ return origin;
300
359
  }
301
360
 
302
- function originNavigation( art, elem ) {
361
+ function originNavigation( art, elem, isAction ) {
303
362
  if (typeof elem !== 'string') {
304
363
  if (elem.action)
305
- return art.actions[elem.action]
364
+ return art.actions[elem.action];
306
365
  if (elem.param)
307
- return (elem.param ? art.params[elem.param] : art.returns);
366
+ return art.params[elem.param];
367
+ if (elem.returns)
368
+ return art.returns;
308
369
  }
309
- if (art.returns)
370
+ if (isAction)
371
+ return art.actions[elem];
372
+ // TODO: if we use effectiveType(), we might have more implicit prototypes
373
+ // we might then need a function like effectiveArtifact,
374
+ // which cares about actions/params
375
+ // let origin = cached( art, '_origin', getOriginRaw );
376
+ // while (art.items) {
377
+ // cached( art, '$origin', _a => setImplicitOrigin( art, origin ) );
378
+ // art = art.items;
379
+ // origin = cached( art, '_origin', getOriginRaw );
380
+ // }
381
+ // if (origin)
382
+ // cached( art, '$origin', _a => setImplicitOrigin( art, origin ) );
383
+ // console.log(art)
384
+ if (art.returns) // for ["main", {action: "act"}, "elem"]
310
385
  art = art.returns;
311
- while (art.items)
386
+ return (art.elements || art.enum || targetAspect( art ).elements)[elem];
387
+ }
388
+
389
+ function targetAspect( art ) {
390
+ const { $origin } = art;
391
+ return art.targetAspect ||
392
+ $origin && typeof $origin === 'object' && !Array.isArray( $origin ) && $origin.target ||
393
+ art.target;
394
+ }
395
+
396
+ // From the current CSN object, set implicit origin for the next navigation step
397
+ function setImplicitOrigin( art, origin ) {
398
+ setMembersImplicit( art.actions, origin.actions );
399
+ setMembersImplicit( art.params, origin.params );
400
+ if (art.returns) {
401
+ art = art.returns;
402
+ if (art.type || typeof art.$origin === 'object') // null, […], {…}
403
+ return true; // not implicit or shortened
404
+ origin = effectiveType( origin.returns );
405
+ setCache( art, '_origin', origin );
406
+ return true;
407
+ }
408
+ while (art.items) {
312
409
  art = art.items;
313
- return (art.elements || art.enum || (art.targetAspect || art.target).elements)[elem];
410
+ if (art.type || typeof art.$origin === 'object') // null, [], {…}
411
+ return true; // not implicit or shortened
412
+ origin = effectiveType( origin.items );
413
+ setCache( art, '_origin', origin );
414
+ }
415
+ setMembersImplicit( art.elements, origin.elements );
416
+ // The enum base type is _not_ where we find the origins of the enum symbols.
417
+ // A derived type of an enum type with individual annotations on a symbol
418
+ // has both 'type' and '$origin'.
419
+ if (!art.type || art.$origin)
420
+ setMembersImplicit( art.enum, origin.enum );
421
+ return true;
422
+ }
423
+
424
+ function setMembersImplicit( members, originMembers ) {
425
+ if (!members)
426
+ return;
427
+ for (const name in members) {
428
+ const elem = members[name];
429
+ if (!elem.type && typeof elem.$origin !== 'object') // undefined or string
430
+ setCache( elem, '_origin', originMembers[elem.$origin || name] || false );
431
+ }
314
432
  }
315
433
 
316
434
  /**
@@ -365,13 +483,16 @@ function csnRefs( csn ) {
365
483
 
366
484
  const head = pathId( path[0] );
367
485
  if (ref.param)
368
- return resolvePath( path, main.params[head], 'param' );
486
+ return resolvePath( path, main.params[head], main, 'param' );
369
487
 
370
488
  const semantics = referenceSemantics[refCtx] || {};
371
489
  if (semantics.dynamic === 'global' || ref.global)
372
- return resolvePath( path, csn.definitions[head], 'global', refCtx === 'from' );
490
+ return resolvePath( path, csn.definitions[head], null, 'global', refCtx === 'from' );
373
491
 
374
- cached( main, '$queries', allQueries );
492
+ if (main) {
493
+ getOrigin( main );
494
+ cached( main, '$queries', allQueries );
495
+ }
375
496
  let qcache = query && cache.get( query.projection || query );
376
497
  // BACKEND ISSUE: you cannot call csnRefs(), inspect some refs, change the
377
498
  // CSN and again inspect some refs without calling csnRefs() before!
@@ -387,39 +508,38 @@ function csnRefs( csn ) {
387
508
  while (cache) {
388
509
  const alias = tryAlias && cache.$aliases[head];
389
510
  if (alias)
390
- return resolvePath( path, alias._select || alias, 'alias', cache.$queryNumber );
511
+ return resolvePath( path, alias._select || alias._ref, null,
512
+ 'alias', cache.$queryNumber );
391
513
  const mixin = cache._select.mixin && cache._select.mixin[head];
392
514
  if (mixin && {}.hasOwnProperty.call( cache._select.mixin, head ))
393
- return resolvePath( path, mixin, 'mixin', cache.$queryNumber );
515
+ return resolvePath( path, mixin, null, 'mixin', cache.$queryNumber );
394
516
  cache = cache.$next;
395
517
  }
396
518
  if (head.charAt(0) === '$') {
397
519
  if (head !== '$self' && head !== '$projection')
398
520
  return { scope: '$magic' };
399
521
  const self = qcache && qcache.$queryNumber > 1 ? qcache._select : main;
400
- return resolvePath( path, self, '$self' );
522
+ return resolvePath( path, self, null, '$self' );
401
523
  }
402
524
  }
403
525
  // now the dynamic environment: ------------------------------------------
404
526
  if (semantics.dynamic === 'target') { // ref in keys
405
- // not selecting the corresponding element for a select column works,
406
- // because explicit keys can only be provided with explicit redirection
407
- // target
408
- const target = csn.definitions[parent.target || parent.cast.target];
409
- return resolvePath( path, target.elements[head], 'target' );
527
+ const target = assocTarget( parent, refCtx );
528
+ return resolvePath( path, target.elements[head], target, 'target' );
410
529
  }
411
530
  if (baseEnv) // ref-target (filter condition), expand, inline
412
- return resolvePath( path, baseEnv.elements[head], semantics.dynamic );
531
+ return resolvePath( path, baseEnv.elements[head], baseEnv, semantics.dynamic );
413
532
  if (!query) // outside queries - TODO: items?
414
- return resolvePath( path, parent.elements[head], 'parent' );
533
+ return resolvePath( path, parent.elements[head], parent, 'parent' );
415
534
 
416
535
  if (semantics.dynamic === 'query')
417
536
  // TODO: for ON condition in expand, would need to use cached _element
418
- return resolvePath( path, qcache.elements[head], 'query' );
537
+ return resolvePath( path, qcache.elements[head], null, 'query' );
419
538
  for (const name in qcache.$aliases) {
420
- const found = qcache.$aliases[name].elements[head];
539
+ const alias = qcache.$aliases[name];
540
+ const found = alias.elements[head];
421
541
  if (found)
422
- return resolvePath( path, found, 'source', name )
542
+ return resolvePath( path, found, alias._ref, 'source', name )
423
543
  }
424
544
  // console.log(query.SELECT,qcache,qcache.$next,main)
425
545
  throw new Error ( `Path item ${ 0 }=${ head } refers to nothing, refCtx: ${ refCtx }` );
@@ -429,8 +549,9 @@ function csnRefs( csn ) {
429
549
  * @param {CSN.Path} path
430
550
  * @param {CSN.Artifact} art
431
551
  * @param {string} [scope]
552
+ * @param [extraInfo]
432
553
  */
433
- function resolvePath( path, art, scope, extraInfo ) {
554
+ function resolvePath( path, art, parent, scope, extraInfo ) {
434
555
  /** @type {{idx, art?, env?}[]} */
435
556
  const links = path.map( (_v, idx) => ({ idx }) );
436
557
  // TODO: backends should be changed to enable uncommenting:
@@ -438,11 +559,11 @@ function csnRefs( csn ) {
438
559
  // throw new Error ( `Path item ${ 0 }=${ pathId( path[0] ) } refers to nothing, scope: ${ scope }`);
439
560
  links[0].art = art;
440
561
  for (let i = 1; i < links.length; ++i) { // yes, starting at 1, links[0] is set above
441
- art = navigationEnv( art );
442
- links[i - 1].env = art;
562
+ parent = navigationEnv( art );
563
+ links[i - 1].env = parent;
443
564
  if (typeof path[i - 1] !== 'string')
444
- setCache( path[i - 1], '_env', art );
445
- art = art.elements[pathId( path[i] )];
565
+ setCache( path[i - 1], '_env', parent );
566
+ art = parent.elements[pathId( path[i] )];
446
567
  if (!art) {
447
568
  const env = links[i - 1].env;
448
569
  const loc = env.name && env.name.$location || env.$location;
@@ -455,14 +576,16 @@ function csnRefs( csn ) {
455
576
  if (fromRef || typeof last !== 'string') {
456
577
  const env = navigationEnv( art );
457
578
  links[links.length - 1].env = env;
458
- if (fromRef)
579
+ if (fromRef) {
459
580
  art = env;
581
+ parent = null;
582
+ }
460
583
  if (typeof last !== 'string')
461
584
  setCache( last, '_env', env )
462
585
  }
463
586
  return (extraInfo && !fromRef)
464
- ? { links, art, scope, $env: extraInfo }
465
- : { links, art, scope };
587
+ ? { links, art, parent, scope, $env: extraInfo }
588
+ : { links, art, parent, scope };
466
589
  }
467
590
 
468
591
  /**
@@ -481,7 +604,8 @@ function csnRefs( csn ) {
481
604
  if (query.ref) { // ref in from
482
605
  // console.log('SQ:',query,cache.get(query))
483
606
  const as = query.as || implicitAs( query.ref );
484
- getCache( fromSelect, '$aliases' )[as] = fromRef( query );
607
+ const _ref = fromRef( query );
608
+ getCache( fromSelect, '$aliases' )[as] = { _ref, elements: _ref.elements };
485
609
  }
486
610
  else {
487
611
  const qcache = getQueryCache( parentQuery );
@@ -517,7 +641,7 @@ function csnRefs( csn ) {
517
641
  function getQueryCache( parentQuery ) {
518
642
  if (!parentQuery)
519
643
  return { $aliases: Object.create(null) };
520
- const pcache = cache.get( parentQuery );
644
+ const pcache = cache.get( parentQuery.projection || parentQuery );
521
645
  if (!parentQuery.SET) // SELECT / projection: real sub query
522
646
  return { $aliases: Object.create(null), $next: pcache };
523
647
  // the parent query is a SET: that is not a sub query
@@ -583,8 +707,8 @@ function csnRefs( csn ) {
583
707
  // i.e. a value with an `elements` property.
584
708
  // TODO: only used in forHanaNew - move somewhere else
585
709
  /**
586
- * @param {CSN.Query} query node (object with SET or SELECT property)
587
- * @param {CSN.Definition} main
710
+ * @param {object} query node (object with SET or SELECT property)
711
+ * @param {object} main definition
588
712
  */
589
713
  function queryOrMain( query, main ) {
590
714
  while (query.SET)
@@ -607,8 +731,8 @@ function queryOrMain( query, main ) {
607
731
  *
608
732
  * @param {CSN.Query} query
609
733
  * @param {CSN.QuerySelect} fromSelect
610
- * @param {CSN.Query} parentQuery
611
- * @param {(query: CSN.Query&CSN.QueryFrom, select: CSN.QuerySelectEnriched) => void} callback
734
+ * @param {CSN.Query} parentQuery
735
+ * @param {(query: CSN.Query&CSN.QueryFrom, select: CSN.QuerySelectEnriched, parentQuery: CSN.Query) => void} callback
612
736
  */
613
737
  function traverseQuery( query, fromSelect, parentQuery, callback ) {
614
738
  const select = query.SELECT || query.projection;
@@ -632,8 +756,9 @@ function traverseQuery( query, fromSelect, parentQuery, callback ) {
632
756
 
633
757
  /**
634
758
  * @param {CSN.QueryFrom} from
635
- * @param {CSN.QuerySelect} select
636
- * @param {(from: CSN.QueryFrom, select: CSN.QuerySelect) => void} callback
759
+ * @param {CSN.QuerySelect} fromSelect
760
+ * @param {CSN.Query} parentQuery
761
+ * @param {(from: CSN.QueryFrom, select: CSN.QuerySelect, parentQuery: CSN.Query) => void} callback
637
762
  */
638
763
  function traverseFrom( from, fromSelect, parentQuery, callback ) {
639
764
  if (from.ref) {
@@ -671,30 +796,42 @@ function implicitAs( ref ) {
671
796
  return id.substring( id.lastIndexOf('.') + 1 );
672
797
  }
673
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
+
674
811
  /**
675
812
  * @param {CSN.Path} csnPath
676
813
  * @param {CSN.Model} csn
677
814
  */
678
815
  function analyseCsnPath( csnPath, csn, resolve ) {
679
- if (csnPath[0] !== 'definitions')
680
- throw new Error( 'References outside definitions not supported yet');
681
-
682
816
  /** @type {object} */
683
- let obj = csn;
684
- let parent = null;
685
817
  let query = null;
686
818
  let refCtx = null;
687
- let art = null;
688
819
  /** @type {boolean|string|number} */
689
820
  let isName = false;
690
821
  let baseRef = null;
691
822
  let baseEnv = null;
692
- 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;
693
830
 
694
- for (let index = 0; index < csnPath.length; index++) {
695
831
  const prop = csnPath[index];
696
832
  // array item, name/index of artifact/member, (named) argument
697
833
  if (isName || Array.isArray( obj )) {
834
+ // TODO: call some kind of resolve.setOrigin()
698
835
  if (typeof isName === 'string') {
699
836
  parent = art;
700
837
  art = obj[prop];
@@ -707,7 +844,8 @@ function analyseCsnPath( csnPath, csn, resolve ) {
707
844
  parent = null;
708
845
  }
709
846
  isName = prop;
710
- 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;
711
849
  }
712
850
  else if (prop === 'items' || prop === 'returns') {
713
851
  art = obj[prop];
@@ -723,15 +861,13 @@ function analyseCsnPath( csnPath, csn, resolve ) {
723
861
  }
724
862
  else if (prop === 'where' && refCtx === 'ref') {
725
863
  if (resolve)
726
- baseEnv = resolve.ref_where( obj, baseRef, refCtx, csn.definitions[csnPath[1]],
727
- query, parent, baseEnv );
864
+ baseEnv = resolve.ref_where( obj, baseRef, refCtx, main, query, parent, baseEnv );
728
865
  refCtx = 'ref_where';
729
866
  }
730
867
  else if (prop === 'expand' || prop === 'inline') {
731
868
  if (obj.ref) {
732
869
  if (resolve)
733
- baseEnv = resolve.expandInline( obj, refCtx, csn.definitions[csnPath[1]],
734
- query, parent, baseEnv );
870
+ baseEnv = resolve.expandInline( obj, refCtx, main, query, parent, baseEnv );
735
871
  refCtx = prop;
736
872
  }
737
873
  if (prop === 'expand')
@@ -739,9 +875,9 @@ function analyseCsnPath( csnPath, csn, resolve ) {
739
875
  }
740
876
  else if (prop === 'on') {
741
877
  if (refCtx === 'from')
742
- refCtx = 'join_on';
878
+ refCtx = 'on_join';
743
879
  else if (refCtx === 'mixin')
744
- refCtx = 'mixin_on';
880
+ refCtx = 'on_mixin';
745
881
  else
746
882
  refCtx = 'on'; // will use query elements with REDIRECTED TO
747
883
  }
@@ -755,11 +891,7 @@ function analyseCsnPath( csnPath, csn, resolve ) {
755
891
  else if (prop !== 'xpr') {
756
892
  refCtx = prop;
757
893
  }
758
-
759
894
  obj = obj[prop];
760
- if (!obj && !resolve)
761
- // For the semantic location, use current object as best guess
762
- break;
763
895
  }
764
896
  // console.log( 'CPATH:', csnPath, refCtx, obj, parent.$location );
765
897
  if (!resolve)