@sap/cds-compiler 2.11.4 → 2.13.8

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 (133) hide show
  1. package/CHANGELOG.md +159 -1
  2. package/bin/cds_update_identifiers.js +7 -7
  3. package/bin/cdsc.js +22 -23
  4. package/bin/cdsse.js +2 -2
  5. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  6. package/doc/CHANGELOG_BETA.md +25 -6
  7. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  8. package/doc/NameResolution.md +21 -16
  9. package/lib/api/main.js +30 -63
  10. package/lib/api/options.js +5 -5
  11. package/lib/api/validate.js +0 -5
  12. package/lib/backends.js +15 -23
  13. package/lib/base/dictionaries.js +0 -8
  14. package/lib/base/error.js +26 -0
  15. package/lib/base/keywords.js +7 -17
  16. package/lib/base/location.js +9 -4
  17. package/lib/base/message-registry.js +52 -2
  18. package/lib/base/messages.js +16 -26
  19. package/lib/base/model.js +2 -62
  20. package/lib/base/optionProcessorHelper.js +246 -183
  21. package/lib/checks/.eslintrc.json +2 -0
  22. package/lib/checks/actionsFunctions.js +2 -1
  23. package/lib/checks/annotationsOData.js +1 -1
  24. package/lib/checks/cdsPersistence.js +2 -1
  25. package/lib/checks/enricher.js +17 -1
  26. package/lib/checks/foreignKeys.js +4 -4
  27. package/lib/checks/invalidTarget.js +3 -1
  28. package/lib/checks/managedInType.js +4 -4
  29. package/lib/checks/managedWithoutKeys.js +3 -1
  30. package/lib/checks/queryNoDbArtifacts.js +1 -3
  31. package/lib/checks/selectItems.js +4 -4
  32. package/lib/checks/sql-snippets.js +94 -0
  33. package/lib/checks/types.js +1 -1
  34. package/lib/checks/validator.js +12 -7
  35. package/lib/compiler/assert-consistency.js +10 -6
  36. package/lib/compiler/base.js +0 -1
  37. package/lib/compiler/builtins.js +8 -6
  38. package/lib/compiler/checks.js +46 -12
  39. package/lib/compiler/cycle-detector.js +1 -1
  40. package/lib/compiler/define.js +1103 -0
  41. package/lib/compiler/extend.js +983 -0
  42. package/lib/compiler/finalize-parse-cdl.js +231 -0
  43. package/lib/compiler/index.js +33 -14
  44. package/lib/compiler/kick-start.js +190 -0
  45. package/lib/compiler/moduleLayers.js +4 -4
  46. package/lib/compiler/populate.js +1226 -0
  47. package/lib/compiler/propagator.js +113 -47
  48. package/lib/compiler/resolve.js +1433 -0
  49. package/lib/compiler/shared.js +76 -38
  50. package/lib/compiler/tweak-assocs.js +529 -0
  51. package/lib/compiler/utils.js +204 -33
  52. package/lib/edm/.eslintrc.json +5 -0
  53. package/lib/edm/annotations/genericTranslation.js +38 -25
  54. package/lib/edm/annotations/preprocessAnnotations.js +3 -3
  55. package/lib/edm/csn2edm.js +10 -9
  56. package/lib/edm/edm.js +19 -20
  57. package/lib/edm/edmPreprocessor.js +166 -95
  58. package/lib/edm/edmUtils.js +127 -34
  59. package/lib/gen/Dictionary.json +92 -43
  60. package/lib/gen/language.checksum +1 -1
  61. package/lib/gen/language.interp +11 -1
  62. package/lib/gen/language.tokens +86 -82
  63. package/lib/gen/languageLexer.interp +18 -1
  64. package/lib/gen/languageLexer.js +925 -847
  65. package/lib/gen/languageLexer.tokens +78 -74
  66. package/lib/gen/languageParser.js +5434 -4298
  67. package/lib/json/from-csn.js +59 -17
  68. package/lib/json/to-csn.js +143 -71
  69. package/lib/language/antlrParser.js +3 -3
  70. package/lib/language/docCommentParser.js +3 -3
  71. package/lib/language/genericAntlrParser.js +144 -54
  72. package/lib/language/language.g4 +424 -203
  73. package/lib/language/multiLineStringParser.js +536 -0
  74. package/lib/main.d.ts +472 -61
  75. package/lib/main.js +38 -11
  76. package/lib/model/api.js +3 -1
  77. package/lib/model/csnRefs.js +321 -204
  78. package/lib/model/csnUtils.js +224 -263
  79. package/lib/model/enrichCsn.js +97 -40
  80. package/lib/model/revealInternalProperties.js +27 -6
  81. package/lib/model/sortViews.js +2 -1
  82. package/lib/modelCompare/compare.js +17 -12
  83. package/lib/optionProcessor.js +7 -6
  84. package/lib/render/DuplicateChecker.js +1 -1
  85. package/lib/render/manageConstraints.js +36 -33
  86. package/lib/render/toCdl.js +174 -275
  87. package/lib/render/toHdbcds.js +201 -115
  88. package/lib/render/toRename.js +7 -10
  89. package/lib/render/toSql.js +149 -75
  90. package/lib/render/utils/common.js +22 -8
  91. package/lib/render/utils/sql.js +10 -7
  92. package/lib/render/utils/stringEscapes.js +111 -0
  93. package/lib/sql-identifier.js +1 -1
  94. package/lib/transform/.eslintrc.json +5 -0
  95. package/lib/transform/braceExpression.js +4 -2
  96. package/lib/transform/db/.eslintrc.json +2 -0
  97. package/lib/transform/db/applyTransformations.js +35 -12
  98. package/lib/transform/db/assertUnique.js +1 -1
  99. package/lib/transform/db/associations.js +187 -0
  100. package/lib/transform/db/cdsPersistence.js +150 -0
  101. package/lib/transform/db/constraints.js +61 -56
  102. package/lib/transform/db/expansion.js +50 -29
  103. package/lib/transform/db/flattening.js +552 -105
  104. package/lib/transform/db/groupByOrderBy.js +3 -1
  105. package/lib/transform/db/temporal.js +236 -0
  106. package/lib/transform/db/transformExists.js +94 -28
  107. package/lib/transform/db/views.js +5 -4
  108. package/lib/transform/draft/.eslintrc.json +38 -0
  109. package/lib/transform/{db/draft.js → draft/db.js} +9 -7
  110. package/lib/transform/draft/odata.js +227 -0
  111. package/lib/transform/forHanaNew.js +94 -801
  112. package/lib/transform/forOdataNew.js +22 -175
  113. package/lib/transform/localized.js +36 -32
  114. package/lib/transform/odata/generateForeignKeyElements.js +3 -3
  115. package/lib/transform/odata/referenceFlattener.js +95 -89
  116. package/lib/transform/odata/structureFlattener.js +1 -1
  117. package/lib/transform/odata/toFinalBaseType.js +86 -12
  118. package/lib/transform/odata/typesExposure.js +5 -5
  119. package/lib/transform/odata/utils.js +2 -2
  120. package/lib/transform/transformUtilsNew.js +47 -33
  121. package/lib/transform/translateAssocsToJoins.js +10 -27
  122. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  123. package/lib/transform/universalCsn/coreComputed.js +170 -0
  124. package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
  125. package/lib/transform/universalCsn/utils.js +63 -0
  126. package/lib/utils/file.js +2 -1
  127. package/lib/utils/objectUtils.js +30 -0
  128. package/lib/utils/timetrace.js +8 -2
  129. package/package.json +1 -1
  130. package/share/messages/README.md +26 -0
  131. package/lib/compiler/definer.js +0 -2340
  132. package/lib/compiler/resolver.js +0 -2988
  133. package/lib/transform/universalCsnEnricher.js +0 -67
@@ -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,10 +159,29 @@
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 = {};
163
183
  const { locationString } = require('../base/location');
184
+ const { ModelError, CompilerAssertion } = require("../base/error");
164
185
 
165
186
  // Properties in which artifact or members are defined - next property in the
166
187
  // "csnPath" is the name or index of that property; 'args' (its value can be a
@@ -171,19 +192,24 @@ const artifactProperties = [ 'elements', 'columns', 'keys', 'mixin', 'enum',
171
192
  // Mapping the “reference context string” to the reference semantics
172
193
  // - lexical: false | Function - determines where to look first for “lexical names”
173
194
  // - dynamic: String - describes the dynamic environment (if in query)
195
+ // - assoc: String, with dynamic: 'global' - what to do with assoc steps
196
+ // * 'static': visit elements of anonymous aspect if not last ref item
197
+ // * 'target': always follow target, including last ref item
198
+ // * other (& not provided) = follow target if not last ref item
174
199
  const referenceSemantics = {
175
- type: { lexical: false, dynamic: 'global' },
176
- includes: { lexical: false, dynamic: 'global' },
177
- target: { lexical: false, dynamic: 'global' },
178
- targetAspect: { lexical: false, dynamic: 'global' },
179
- from: { lexical: false, dynamic: 'global' },
200
+ type: { lexical: false, dynamic: 'global' }, // TODO: assoc: 'static', see Issue #8458
201
+ includes: { lexical: false, dynamic: 'global', assoc: 'static' }, // no elem ref anyway
202
+ target: { lexical: false, dynamic: 'global', assoc: 'static' }, // no elem ref anyway
203
+ targetAspect: { lexical: false, dynamic: 'global', assoc: 'static' },
204
+ from: { lexical: false, dynamic: 'global', assoc: 'target' },
180
205
  keys: { lexical: false, dynamic: 'target' },
206
+ keys_origin: { lexical: false, dynamic: 'target' },
181
207
  excluding: { lexical: false, dynamic: 'source' },
182
208
  expand: { lexical: justDollar, dynamic: 'expand' }, // ...using baseEnv
183
209
  inline: { lexical: justDollar, dynamic: 'inline' }, // ...using baseEnv
184
210
  ref_where: { lexical: justDollar , dynamic: 'ref-target'}, // ...using baseEnv
185
211
  on: { lexical: justDollar, dynamic: 'query' }, // assoc defs, redirected to
186
- // there are also 'join_on' and 'mixin_on' with default semantics
212
+ // there are also 'on_join' and 'on_mixin' with default semantics
187
213
  orderBy: { lexical: query => query, dynamic: 'query' },
188
214
  orderBy_set: { lexical: query => query.$next, dynamic: 'query' }, // to outer SELECT (from UNION)
189
215
  // default: { lexical: query => query, dynamic: 'source' }
@@ -195,10 +221,15 @@ function justDollar() {
195
221
 
196
222
  /**
197
223
  * @param {CSN.Model} csn
224
+ * @param {boolean|string} [universalReady]
198
225
  */
199
- function csnRefs( csn ) {
226
+ function csnRefs( csn, universalReady ) {
200
227
  const cache = new WeakMap();
201
-
228
+ setCache( BUILTIN_TYPE, '_origin', null );
229
+ if (universalReady === 'init-all') {
230
+ for (const art of Object.values( csn.definitions || {}))
231
+ initDefinition( art );
232
+ }
202
233
  // Functions which set the new `baseEnv`:
203
234
  resolveRef.expandInline = function resolve_expandInline( ref, ...args ) {
204
235
  return cached( ref, '_env', () => navigationEnv( resolveRef( ref, ...args ).art ) );
@@ -210,7 +241,15 @@ function csnRefs( csn ) {
210
241
  } );
211
242
  }
212
243
  return {
213
- effectiveType, artifactRef, getOrigin, inspectRef, queryOrMain,
244
+ effectiveType,
245
+ artifactRef,
246
+ getOrigin,
247
+ inspectRef,
248
+ queryOrMain,
249
+ getColumn: ( elem ) => getCache( elem, '_column' ),
250
+ getElement: ( col ) => getCache( col, '_element' ),
251
+ initDefinition,
252
+ targetAspect,
214
253
  __getCache_forEnrichCsnDebugging: obj => cache.get( obj ),
215
254
  };
216
255
 
@@ -239,7 +278,7 @@ function csnRefs( csn ) {
239
278
  return setCache( art, '_effectiveType', art );
240
279
 
241
280
  if (getCache( art, '_effectiveType' ) === 0)
242
- throw new Error( 'Circular type reference');
281
+ throw new ModelError( 'Circular type reference');
243
282
  const type = getCache( art, '_effectiveType' ) || art;
244
283
  chain.forEach( a => setCache( a, '_effectiveType', type ) );
245
284
  return type;
@@ -248,21 +287,19 @@ function csnRefs( csn ) {
248
287
  /**
249
288
  * @param {CSN.Artifact} art
250
289
  */
251
- function navigationEnv( art ) {
252
- let type = effectiveType( art );
290
+ function navigationEnv( art, staticAssoc ) {
291
+ let env = effectiveType( art );
253
292
  // here, we do not care whether it is semantically ok to navigate into sub
254
293
  // elements of array items (that is the task of the core compiler /
255
294
  // 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;
295
+ while (env.items)
296
+ env = effectiveType( env.items );
297
+ const target = (staticAssoc ? targetAspect( env ) : env.target);
298
+ if (typeof target !== 'string')
299
+ return target || env;
300
+ const def = csn.definitions[target];
301
+ initDefinition( def );
302
+ return def;
266
303
  }
267
304
 
268
305
  /**
@@ -274,132 +311,158 @@ function csnRefs( csn ) {
274
311
  * could not be found.
275
312
  */
276
313
  function artifactRef( ref, notFound ) {
277
- const art = (typeof ref === 'string')
278
- ? csn.definitions[ref]
279
- : cached( ref, '_ref', artifactPathRef );
280
- if (art)
281
- return art;
314
+ // TODO: what about type ref?
315
+ if (typeof ref === 'string') {
316
+ const main = csn.definitions[ref];
317
+ if (main)
318
+ return initDefinition( main );
319
+ }
320
+ else {
321
+ const art = cached( ref, '_ref', artifactPathRef );
322
+ if (art)
323
+ return art;
324
+ }
325
+ if (notFound !== undefined && typeof ref === 'string')
326
+ return notFound; // is only meant for builtins
327
+ // Backend bug workaround, TODO: delete next 2 lines
282
328
  if (notFound !== undefined)
283
329
  return notFound;
284
- throw new Error( 'Undefined reference' );
330
+ throw new ModelError( 'Undefined reference' );
285
331
  }
286
332
 
287
333
  function artifactPathRef( ref ) {
288
334
  const [ head, ...tail ] = ref.ref;
289
335
  let art = csn.definitions[pathId( head )];
336
+ initDefinition( art );
290
337
  for (const elem of tail) {
291
- const env = navigationEnv( art );
338
+ const env = navigationEnv( art ); // TODO: second argument true, see Issue #8458
292
339
  art = env.elements[pathId( elem )];
293
340
  }
294
341
  return art;
295
342
  }
296
343
 
297
- function getOrigin( art, alsoType ) {
298
- const origin = cached( art, '_origin', getOriginRaw );
299
- if (origin && origin !== BUILTIN_TYPE)
300
- cached( art, '$origin', _a => setImplicitOrigin( art, origin ) );
301
- return art.type && !alsoType ? undefined : origin;
344
+ function artifactFromRef( ref ) {
345
+ const [ head, ...tail ] = ref.ref;
346
+ let art = csn.definitions[pathId( head )];
347
+ initDefinition( art );
348
+ for (const elem of tail) {
349
+ const env = navigationEnv( art );
350
+ art = env.elements[pathId( elem )];
351
+ }
352
+ return navigationEnv( art );
353
+ }
354
+
355
+ // Return target when resolving references in 'keys'
356
+ function assocTarget( art, refCtx ) {
357
+ // Call contexts:
358
+ // 1. normal definition of association with explicit foreign keys
359
+ // 2. auto-redirected association with renaming of foreign keys
360
+ // (currently: `keys` always available on inherited associations)
361
+ // 3. user-induced redirection (in 'cast') with explicit foreign keys
362
+ // 4. original provided association def inside $origin with explicit foreign keys
363
+ // (outside $origin like 2)
364
+ const targetName = refCtx !== 'keys_origin' && art.target
365
+ || art.$origin && art.$origin.target
366
+ || art.cast.target;
367
+ const target = csn.definitions[targetName];
368
+ initDefinition( target );
369
+ return target;
370
+ }
371
+
372
+ function getOrigin( art ) {
373
+ return cached( art, '_origin', getOriginRaw );
302
374
  }
303
375
 
304
376
  function getOriginRaw( art ) {
305
377
  if (art.type) // TODO: make robust against "linked" = only direct
306
378
  return artifactRef( art.type, BUILTIN_TYPE );
307
- if (!art.$origin) // implicit $origin should have been set
308
- return null;
309
- // art.$origin must not be a string here - shortened refs should already
310
- // have been used to set the _origin cache
311
- if (!Array.isArray( art.$origin )) // anonymous prototype in $origin
312
- return cached( art.$origin, '_origin', getOriginRaw );
313
- const [ head, ...tail ] = art.$origin;
314
- let origin = csn.definitions[head];
315
- // allow shorter $origin ref for actions/functions, just using a string:
316
- let isAction = art.kind === 'action' || art.kind === 'function';
317
- for (const elem of tail) {
318
- origin = originNavigation( origin, elem, isAction );
319
- isAction = false;
379
+ if (typeof art.$origin === 'object') // null, […], {…}
380
+ return getOriginExplicit( art.$origin );
381
+
382
+ const parent = getCache( art, '_parent' );
383
+ if (parent === undefined && universalReady) {
384
+ const { $location } = art;
385
+ const location = $location &&
386
+ (typeof $location === 'string' ? $location : locationString( $location ));
387
+ const def = Object.keys( art ).join('+') + (location ? ':' + location : '');
388
+ throw new Error( `Inspecting non-initialized CSN node {${def}}` );
320
389
  }
321
- return origin;
390
+ const step = getCache( art, '$origin$step' );
391
+ if (!step)
392
+ return null;
393
+ const origin = cached( parent, '_origin', getOriginRaw );
394
+ return originNavigation( origin, step );
322
395
  }
323
396
 
324
- function originNavigation( art, elem, isAction ) {
325
- if (typeof elem !== 'string') {
326
- if (elem.action)
327
- return art.actions[elem.action];
328
- if (elem.param)
329
- return art.params[elem.param];
330
- if (elem.returns)
331
- return art.returns;
332
- }
333
- if (isAction)
334
- return art.actions[elem];
335
- // TODO: if we use effectiveType(), we might have more implicit prototypes
336
- // we might then need a function like effectiveArtifact,
337
- // which cares about actions/params
338
- // let origin = cached( art, '_origin', getOriginRaw );
339
- // while (art.items) {
340
- // cached( art, '$origin', _a => setImplicitOrigin( art, origin ) );
341
- // art = art.items;
342
- // origin = cached( art, '_origin', getOriginRaw );
343
- // }
344
- // if (origin)
345
- // cached( art, '$origin', _a => setImplicitOrigin( art, origin ) );
346
- // console.log(art)
347
- return (art.elements || art.enum || targetAspect( art ).elements)[elem];
348
- }
349
-
350
- function targetAspect( art ) {
351
- const { $origin } = art;
352
- return art.targetAspect ||
353
- $origin && typeof $origin === 'object' && !Array.isArray( $origin ) && $origin.target ||
354
- art.target;
355
- }
356
-
357
- // From the current CSN object, set implicit origin for the next navigation step
358
- function setImplicitOrigin( art, origin ) {
359
- setMembersImplicit( art.actions, origin.actions );
360
- setMembersImplicit( art.params, origin.params );
361
- if (art.returns) {
362
- art = art.returns;
363
- if (art.type || typeof art.$origin === 'object') // null, […], {…}
364
- return true; // not implicit or shortened
365
- origin = effectiveType( origin.returns );
366
- setCache( art, '_origin', origin );
367
- return true;
368
- }
369
- while (art.items) {
370
- art = art.items;
371
- if (art.type || typeof art.$origin === 'object') // null, […], {…}
372
- return true; // not implicit or shortened
373
- origin = effectiveType( origin.items );
374
- setCache( art, '_origin', origin );
375
- }
376
- setMembersImplicit( art.elements, origin.elements );
377
- // The enum base type is _not_ where we find the origins of the enum symbols.
378
- // A derived type of an enum type with individual annotations on a symbol
379
- // has both 'type' and '$origin'.
380
- if (!art.type || art.$origin)
381
- setMembersImplicit( art.enum, origin.enum );
382
- return true;
397
+ function getOriginExplicit( $origin ) { // null, […], {…}
398
+ if (!$origin)
399
+ return null;
400
+ if (!Array.isArray( $origin )) // anonymous prototype in $origin
401
+ return getOriginExplicit( $origin.$origin );
402
+ const [ head, ...tail ] = $origin;
403
+ const main = csn.definitions[head];
404
+ initDefinition( main );
405
+ return tail.reduce( originNavigation, main );
383
406
  }
384
407
 
385
- function setMembersImplicit( members, originMembers ) {
386
- if (!members)
387
- return;
388
- for (const name in members) {
389
- const elem = members[name];
390
- if (!elem.type && typeof elem.$origin !== 'object') // undefined or string
391
- setCache( elem, '_origin', originMembers[elem.$origin || name] || false );
408
+ function originNavigation( art, step ) {
409
+ if (!step)
410
+ return null;
411
+ if (!effectiveType( art ))
412
+ throw new TypeError( 'Cyclic type definition' );
413
+ if (typeof step === 'string')
414
+ return navigationEnv( art, true ).elements[step];
415
+
416
+ if (step.action)
417
+ return effectiveArtFor( art, 'actions' )[step.action];
418
+ if (step.param)
419
+ return effectiveArtFor( art, 'params' )[step.param];
420
+ if (step.returns)
421
+ return effectiveArtFor( art, 'returns' );
422
+ if (step.enum)
423
+ return navigationEnv( art, true ).enum[step.enum];
424
+ if (step.items)
425
+ return effectiveType( art ).items;
426
+ if (step.target)
427
+ return targetAspect( effectiveType( art ) );
428
+ throw Error( `Illegal navigation step ${ Object.keys(step)[0]}` );
429
+ }
430
+
431
+ function effectiveArtFor( art, property ) {
432
+ while (!art[property])
433
+ art = getOrigin( art );
434
+ return art[property];
435
+ }
436
+
437
+ function initDefinition( main ) {
438
+ // TODO: some --test-mode check that the argument is in ‹csn›.definitions ?
439
+ if (getCache( main, '$queries' ) !== undefined) // already computed
440
+ return main;
441
+ traverseDef( main, null, null, null, initNode );
442
+ const queries = cached( main, '$queries', allQueries );
443
+ for (const qcache of queries || []) {
444
+ const { _select } = qcache;
445
+ traverseType( _select, main, null, null, initNode ); // also inits elements
446
+ if (_select.mixin) {
447
+ for (const n of Object.keys( _select.mixin ))
448
+ setCache( _select.mixin[n], '_parent', _select ); // relevant initNode() part
449
+ }
392
450
  }
451
+ return main;
393
452
  }
394
453
 
395
- /**
396
- * Return the entity we select from
397
- *
398
- * @param {CSN.ArtifactReferencePath} ref
399
- * @returns {CSN.Definition}
400
- */
401
- function fromRef( ref ) {
402
- return navigationEnv( artifactRef( ref ));
454
+ function initNode( art, parent, kind, name ) {
455
+ setCache( art, '_parent', parent );
456
+ if (art.type || !kind || kind === 'target') // with type, top-level, query or mixin
457
+ return;
458
+ const { $origin } = art;
459
+ if (typeof $origin === 'object') // null, […], {…}
460
+ return;
461
+ const step = $origin || name;
462
+ if (parent.$origin ||
463
+ parent.type && kind !== 'enum' && parent.$origin !== null ||
464
+ getCache( parent, '$origin$step' ))
465
+ setCache( art, '$origin$step', (kind === 'element' ? step : { [kind]: step }) );
403
466
  }
404
467
 
405
468
  /**
@@ -440,111 +503,108 @@ function csnRefs( csn ) {
440
503
  function resolveRef( ref, refCtx, main, query, parent, baseEnv ) {
441
504
  const path = (typeof ref === 'string') ? [ ref ] : ref.ref;
442
505
  if (!Array.isArray( path ))
443
- throw new Error( 'References must look like {ref:[...]}' );
506
+ throw new ModelError( 'References must look like {ref:[...]}' );
444
507
 
445
508
  const head = pathId( path[0] );
509
+ if (main) // TODO: improve, for csnpath starting with art
510
+ initDefinition( main );
446
511
  if (ref.param)
447
- return resolvePath( path, main.params[head], 'param' );
512
+ return resolvePath( path, main.params[head], main, 'param' );
448
513
 
449
514
  const semantics = referenceSemantics[refCtx] || {};
450
515
  if (semantics.dynamic === 'global' || ref.global)
451
- return resolvePath( path, csn.definitions[head], 'global', refCtx === 'from' );
516
+ return resolvePath( path, csn.definitions[head], null, 'global', semantics.assoc );
517
+
452
518
 
453
- const origin = cached( main, '_origin', getOriginRaw )
454
- if (origin)
455
- cached( main, '$origin', _a => setImplicitOrigin( main, origin ) );
456
- cached( main, '$queries', allQueries );
457
519
  let qcache = query && cache.get( query.projection || query );
458
- // BACKEND ISSUE: you cannot call csnRefs(), inspect some refs, change the
459
- // CSN and again inspect some refs without calling csnRefs() before!
460
- // WORKAROUND: if no cached query, a backend has changed the CSN - re-eval cache
461
- if (query && !qcache) {
462
- setCache( main, '$queries', allQueries( main ) );
463
- qcache = cache.get( query.projection || query );
464
- }
465
520
  // first the lexical scopes (due to query hierarchy) and $magic: ---------
466
521
  if (semantics.lexical !== false) {
467
522
  const tryAlias = path.length > 1 || ref.expand || ref.inline;
468
- let cache = qcache && (semantics.lexical ? semantics.lexical( qcache ) : qcache);
469
- while (cache) {
470
- const alias = tryAlias && cache.$aliases[head];
523
+ let ncache = qcache && (semantics.lexical ? semantics.lexical( qcache ) : qcache);
524
+ while (ncache) {
525
+ const alias = tryAlias && ncache.$aliases[head];
471
526
  if (alias)
472
- return resolvePath( path, alias._select || alias._ref, 'alias', cache.$queryNumber );
473
- const mixin = cache._select.mixin && cache._select.mixin[head];
474
- if (mixin && {}.hasOwnProperty.call( cache._select.mixin, head ))
475
- return resolvePath( path, mixin, 'mixin', cache.$queryNumber );
476
- cache = cache.$next;
527
+ return resolvePath( path, alias._select || alias._ref, null,
528
+ 'alias', ncache.$queryNumber );
529
+ const mixin = ncache._select.mixin && ncache._select.mixin[head];
530
+ if (mixin && {}.hasOwnProperty.call( ncache._select.mixin, head )) {
531
+ setCache( mixin, '_parent', qcache._select );
532
+ return resolvePath( path, mixin, null, 'mixin', ncache.$queryNumber );
533
+ }
534
+ ncache = ncache.$next;
477
535
  }
478
536
  if (head.charAt(0) === '$') {
479
537
  if (head !== '$self' && head !== '$projection')
480
538
  return { scope: '$magic' };
481
539
  const self = qcache && qcache.$queryNumber > 1 ? qcache._select : main;
482
- return resolvePath( path, self, '$self' );
540
+ return resolvePath( path, self, null, '$self' );
483
541
  }
484
542
  }
485
543
  // now the dynamic environment: ------------------------------------------
486
544
  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' );
545
+ const target = assocTarget( parent, refCtx );
546
+ return resolvePath( path, target.elements[head], target, 'target' );
492
547
  }
493
548
  if (baseEnv) // ref-target (filter condition), expand, inline
494
- return resolvePath( path, baseEnv.elements[head], semantics.dynamic );
549
+ return resolvePath( path, baseEnv.elements[head], baseEnv, semantics.dynamic );
495
550
  if (!query) // outside queries - TODO: items?
496
- return resolvePath( path, parent.elements[head], 'parent' );
551
+ return resolvePath( path, parent.elements[head], parent, 'parent' );
497
552
 
498
553
  if (semantics.dynamic === 'query')
499
554
  // TODO: for ON condition in expand, would need to use cached _element
500
- return resolvePath( path, qcache.elements[head], 'query' );
555
+ return resolvePath( path, qcache.elements[head], null, 'query' );
501
556
  for (const name in qcache.$aliases) {
502
- const found = qcache.$aliases[name].elements[head];
557
+ const alias = qcache.$aliases[name];
558
+ const found = alias.elements[head];
503
559
  if (found)
504
- return resolvePath( path, found, 'source', name )
560
+ return resolvePath( path, found, alias._ref, 'source', name )
505
561
  }
506
562
  // console.log(query.SELECT,qcache,qcache.$next,main)
507
- throw new Error ( `Path item ${ 0 }=${ head } refers to nothing, refCtx: ${ refCtx }` );
563
+ throw new ModelError ( `Path item ${ 0 }=${ head } refers to nothing, refCtx: ${ refCtx }` );
508
564
  }
509
565
 
510
566
  /**
511
567
  * @param {CSN.Path} path
512
568
  * @param {CSN.Artifact} art
513
569
  * @param {string} [scope]
570
+ * @param [extraInfo]
514
571
  */
515
- function resolvePath( path, art, scope, extraInfo ) {
572
+ function resolvePath( path, art, parent, scope, extraInfo ) {
573
+ const staticAssoc = extraInfo === 'static' && scope === 'global';
516
574
  /** @type {{idx, art?, env?}[]} */
517
575
  const links = path.map( (_v, idx) => ({ idx }) );
518
576
  // TODO: backends should be changed to enable uncommenting:
519
577
  // if (!art) // does not work with test3/Associations/KeylessManagedAssociation/
520
- // throw new Error ( `Path item ${ 0 }=${ pathId( path[0] ) } refers to nothing, scope: ${ scope }`);
578
+ // throw new ModelError ( `Path item ${ 0 }=${ pathId( path[0] ) } refers to nothing, scope: ${ scope }`);
521
579
  links[0].art = art;
522
580
  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;
581
+ parent = navigationEnv( art, staticAssoc );
582
+ links[i - 1].env = parent;
525
583
  if (typeof path[i - 1] !== 'string')
526
- setCache( path[i - 1], '_env', art );
527
- art = art.elements[pathId( path[i] )];
584
+ setCache( path[i - 1], '_env', parent );
585
+ art = parent.elements[pathId( path[i] )];
528
586
  if (!art) {
529
587
  const env = links[i - 1].env;
530
588
  const loc = env.name && env.name.$location || env.$location;
531
- throw new Error ( `Path item ${ i }=${ pathId( path[i] ) } on ${ locationString( loc ) } refers to nothing` );
589
+ throw new ModelError ( `Path item ${ i }=${ pathId( path[i] ) } on ${ locationString( loc ) } refers to nothing` );
532
590
  }
533
591
  links[i].art = art;
534
592
  }
535
593
  const last = path[path.length - 1];
536
- const fromRef = scope === 'global' && extraInfo;
594
+ const fromRef = scope === 'global' && extraInfo === 'target';
537
595
  if (fromRef || typeof last !== 'string') {
538
596
  const env = navigationEnv( art );
539
597
  links[links.length - 1].env = env;
540
- if (fromRef)
598
+ if (fromRef) {
541
599
  art = env;
600
+ parent = null;
601
+ }
542
602
  if (typeof last !== 'string')
543
603
  setCache( last, '_env', env )
544
604
  }
545
- return (extraInfo && !fromRef)
546
- ? { links, art, scope, $env: extraInfo }
547
- : { links, art, scope };
605
+ return (extraInfo && scope !== 'global')
606
+ ? { links, art, parent, scope, $env: extraInfo }
607
+ : { links, art, parent, scope };
548
608
  }
549
609
 
550
610
  /**
@@ -563,8 +623,8 @@ function csnRefs( csn ) {
563
623
  if (query.ref) { // ref in from
564
624
  // console.log('SQ:',query,cache.get(query))
565
625
  const as = query.as || implicitAs( query.ref );
566
- const _ref = fromRef( query );
567
- getCache( fromSelect, '$aliases' )[as] = { _ref, elements: _ref.elements };
626
+ const _ref = cached( query, '_from', artifactFromRef )
627
+ getCache( fromSelect, '$aliases' )[as] = { _ref, elements: _ref.elements, _parent: query };
568
628
  }
569
629
  else {
570
630
  const qcache = getQueryCache( parentQuery );
@@ -577,15 +637,18 @@ function csnRefs( csn ) {
577
637
  if (select) {
578
638
  cache.set( select, qcache ); // query and query.SELECT have the same cache qcache
579
639
  qcache._select = select;
640
+ qcache._parent = main;
580
641
  all.push( qcache );
581
642
  }
582
643
  }
583
644
  } );
584
645
  all.forEach( function initElements( qcache, index ) {
646
+ qcache._parent = main;
585
647
  qcache.$queryNumber = index + 1;
586
- qcache.elements = (index ? qcache._select : main).elements;
587
- const columns = qcache._select.columns;
588
- if (qcache.elements && columns)
648
+ const { elements } = (index ? qcache._select : main);
649
+ qcache.elements = elements;
650
+ const { columns } = qcache._select;
651
+ if (elements && columns)
589
652
  columns.map( c => initColumnElement( c, qcache ) );
590
653
  } );
591
654
  return all;
@@ -624,7 +687,8 @@ function csnRefs( csn ) {
624
687
  while (type.items)
625
688
  type = type.items;
626
689
  const elem = setCache( col, '_element', type.elements[as] );
627
- // if requested, we could set a _column link in element
690
+ if (elem) // TODO to.sql: something is strange if this is not set
691
+ setCache( elem, '_column', col );
628
692
  if (col.expand)
629
693
  col.expand.map( c => initColumnElement( c, elem ) );
630
694
  }
@@ -666,8 +730,8 @@ function csnRefs( csn ) {
666
730
  // i.e. a value with an `elements` property.
667
731
  // TODO: only used in forHanaNew - move somewhere else
668
732
  /**
669
- * @param {CSN.Query} query node (object with SET or SELECT property)
670
- * @param {CSN.Definition} main
733
+ * @param {object} query node (object with SET or SELECT property)
734
+ * @param {object} main definition
671
735
  */
672
736
  function queryOrMain( query, main ) {
673
737
  while (query.SET)
@@ -682,7 +746,7 @@ function queryOrMain( query, main ) {
682
746
  // 'projection'), but `leading` can be its `query` property:
683
747
  if ((leading === query || leading === query.query) && main.elements)
684
748
  return main;
685
- throw new Error( `Query elements not available: ${ Object.keys( query ).join('+') }`);
749
+ throw new ModelError( `Query elements not available: ${ Object.keys( query ).join('+') }`);
686
750
  }
687
751
 
688
752
  /**
@@ -691,7 +755,7 @@ function queryOrMain( query, main ) {
691
755
  * @param {CSN.Query} query
692
756
  * @param {CSN.QuerySelect} fromSelect
693
757
  * @param {CSN.Query} parentQuery
694
- * @param {(query: CSN.Query&CSN.QueryFrom, select: CSN.QuerySelectEnriched) => void} callback
758
+ * @param {(query: CSN.Query&CSN.QueryFrom, select: CSN.QuerySelectEnriched, parentQuery: CSN.Query) => void} callback
695
759
  */
696
760
  function traverseQuery( query, fromSelect, parentQuery, callback ) {
697
761
  const select = query.SELECT || query.projection;
@@ -746,6 +810,52 @@ function traverseExpr( expr, parentQuery, callback ) {
746
810
  }
747
811
  }
748
812
 
813
+ function traverseDef( node, parent, kind, name, callback ) {
814
+ callback ( node, parent, kind, name );
815
+ if (node.params) {
816
+ for (const n of Object.keys( node.params ))
817
+ traverseType( node.params[n], node, 'param', n, callback );
818
+ }
819
+ if (node.returns)
820
+ traverseType( node.returns, node, 'returns', true, callback );
821
+ traverseType( node, parent, kind, name, callback );
822
+ if (node.actions) {
823
+ for (const n of Object.keys( node.actions ))
824
+ traverseDef( node.actions[n], node, 'action', n, callback )
825
+ }
826
+ }
827
+
828
+ function traverseType( node, parent, kind, name, callback ) {
829
+ callback ( node, parent, kind, name );
830
+ const target = targetAspect( node );
831
+ if (target && typeof target === 'object' && target.elements) {
832
+ callback ( target, node, 'target', true );
833
+ node = target;
834
+ }
835
+ else if (node.items) {
836
+ let items = 0;
837
+ while (node.items) {
838
+ callback ( node.items, node, 'items', ++items );
839
+ node = node.items;
840
+ }
841
+ }
842
+ if (node.elements) {
843
+ for (const n of Object.keys( node.elements ))
844
+ traverseDef( node.elements[n], node, 'element', n, callback )
845
+ }
846
+ if (node.enum) {
847
+ for (const n of Object.keys( node.enum ))
848
+ traverseDef( node.enum[n], node, 'enum', n, callback )
849
+ }
850
+ }
851
+
852
+ function targetAspect( art ) {
853
+ const { $origin } = art;
854
+ return art.targetAspect ||
855
+ $origin && typeof $origin === 'object' && !Array.isArray( $origin ) && $origin.target ||
856
+ art.target;
857
+ }
858
+
749
859
  function pathId( item ) {
750
860
  return (typeof item === 'string') ? item : item.id;
751
861
  }
@@ -755,31 +865,43 @@ function implicitAs( ref ) {
755
865
  return id.substring( id.lastIndexOf('.') + 1 );
756
866
  }
757
867
 
868
+ function startCsnPath( csnPath, csn ) {
869
+ const head = csnPath[0];
870
+ if (typeof head !== 'string') {
871
+ const { main, parent, art } = head;
872
+ return { index: 1, main, parent, art };
873
+ }
874
+ if (csnPath.length < 2 || csnPath[0] !== 'definitions')
875
+ throw new CompilerAssertion( 'References outside definitions not supported yet');
876
+ const art = csn.definitions[csnPath[1]];
877
+ return { index: 2, main: art, parent: null, art };
878
+ }
879
+
758
880
  /**
759
881
  * @param {CSN.Path} csnPath
760
882
  * @param {CSN.Model} csn
761
883
  */
762
884
  function analyseCsnPath( csnPath, csn, resolve ) {
763
- if (csnPath[0] !== 'definitions')
764
- throw new Error( 'References outside definitions not supported yet');
765
-
766
885
  /** @type {object} */
767
- let obj = csn;
768
- let parent = null;
769
886
  let query = null;
770
887
  let refCtx = null;
771
- let art = null;
772
888
  /** @type {boolean|string|number} */
773
889
  let isName = false;
774
890
  let baseRef = null;
775
891
  let baseEnv = null;
776
- let main = csn.definitions[csnPath[1]];
892
+ let { index, main, parent, art } = startCsnPath( csnPath, csn );
893
+ let obj = art;
894
+
895
+ for (; index < csnPath.length; index++) {
896
+ if (!obj && !resolve)
897
+ // For the semantic location, use current object as best guess
898
+ break;
777
899
 
778
- for (let index = 0; index < csnPath.length; index++) {
779
900
  const prop = csnPath[index];
780
901
  // array item, name/index of artifact/member, (named) argument
781
- if (isName || Array.isArray( obj )) {
782
- if (typeof isName === 'string') {
902
+ if (isName || Array.isArray( obj ) || prop === 'returns') {
903
+ // TODO: call some kind of resolve.setOrigin()
904
+ if (typeof isName === 'string' || prop === 'returns') {
783
905
  parent = art;
784
906
  art = obj[prop];
785
907
  }
@@ -791,7 +913,8 @@ function analyseCsnPath( csnPath, csn, resolve ) {
791
913
  parent = null;
792
914
  }
793
915
  isName = prop;
794
- refCtx = prop;
916
+ // if we want to allow auto-redirect of user-provided target with renamed keys:
917
+ refCtx = (refCtx === '$origin' && prop === 'keys') ? 'keys_origin' : prop;
795
918
  }
796
919
  else if (prop === 'items' || prop === 'returns') {
797
920
  art = obj[prop];
@@ -807,15 +930,13 @@ function analyseCsnPath( csnPath, csn, resolve ) {
807
930
  }
808
931
  else if (prop === 'where' && refCtx === 'ref') {
809
932
  if (resolve)
810
- baseEnv = resolve.ref_where( obj, baseRef, refCtx, csn.definitions[csnPath[1]],
811
- query, parent, baseEnv );
933
+ baseEnv = resolve.ref_where( obj, baseRef, refCtx, main, query, parent, baseEnv );
812
934
  refCtx = 'ref_where';
813
935
  }
814
936
  else if (prop === 'expand' || prop === 'inline') {
815
937
  if (obj.ref) {
816
938
  if (resolve)
817
- baseEnv = resolve.expandInline( obj, refCtx, csn.definitions[csnPath[1]],
818
- query, parent, baseEnv );
939
+ baseEnv = resolve.expandInline( obj, refCtx, main, query, parent, baseEnv );
819
940
  refCtx = prop;
820
941
  }
821
942
  if (prop === 'expand')
@@ -823,9 +944,9 @@ function analyseCsnPath( csnPath, csn, resolve ) {
823
944
  }
824
945
  else if (prop === 'on') {
825
946
  if (refCtx === 'from')
826
- refCtx = 'join_on';
947
+ refCtx = 'on_join';
827
948
  else if (refCtx === 'mixin')
828
- refCtx = 'mixin_on';
949
+ refCtx = 'on_mixin';
829
950
  else
830
951
  refCtx = 'on'; // will use query elements with REDIRECTED TO
831
952
  }
@@ -839,11 +960,7 @@ function analyseCsnPath( csnPath, csn, resolve ) {
839
960
  else if (prop !== 'xpr') {
840
961
  refCtx = prop;
841
962
  }
842
-
843
963
  obj = obj[prop];
844
- if (!obj && !resolve)
845
- // For the semantic location, use current object as best guess
846
- break;
847
964
  }
848
965
  // console.log( 'CPATH:', csnPath, refCtx, obj, parent.$location );
849
966
  if (!resolve)