@sap/cds-compiler 2.11.2 → 2.13.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/CHANGELOG.md +175 -2
  2. package/bin/.eslintrc.json +1 -2
  3. package/bin/cds_update_identifiers.js +10 -8
  4. package/bin/cdsc.js +23 -17
  5. package/bin/cdsse.js +2 -2
  6. package/bin/cdsv2m.js +3 -2
  7. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  8. package/doc/CHANGELOG_BETA.md +25 -6
  9. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  10. package/doc/NameResolution.md +21 -16
  11. package/lib/api/main.js +32 -79
  12. package/lib/api/options.js +3 -2
  13. package/lib/api/validate.js +2 -1
  14. package/lib/backends.js +16 -26
  15. package/lib/base/dictionaries.js +0 -8
  16. package/lib/base/error.js +26 -0
  17. package/lib/base/keywords.js +10 -19
  18. package/lib/base/location.js +9 -4
  19. package/lib/base/message-registry.js +75 -9
  20. package/lib/base/messages.js +31 -35
  21. package/lib/base/model.js +2 -62
  22. package/lib/base/optionProcessorHelper.js +246 -183
  23. package/lib/checks/.eslintrc.json +2 -0
  24. package/lib/checks/actionsFunctions.js +2 -1
  25. package/lib/checks/annotationsOData.js +1 -1
  26. package/lib/checks/cdsPersistence.js +2 -1
  27. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  28. package/lib/checks/enricher.js +17 -1
  29. package/lib/checks/foreignKeys.js +4 -4
  30. package/lib/checks/invalidTarget.js +3 -1
  31. package/lib/checks/managedInType.js +4 -4
  32. package/lib/checks/managedWithoutKeys.js +3 -1
  33. package/lib/checks/queryNoDbArtifacts.js +1 -3
  34. package/lib/checks/selectItems.js +4 -4
  35. package/lib/checks/sql-snippets.js +94 -0
  36. package/lib/checks/types.js +1 -1
  37. package/lib/checks/unknownMagic.js +1 -1
  38. package/lib/checks/validator.js +12 -7
  39. package/lib/compiler/assert-consistency.js +12 -8
  40. package/lib/compiler/base.js +0 -1
  41. package/lib/compiler/builtins.js +42 -21
  42. package/lib/compiler/checks.js +46 -12
  43. package/lib/compiler/cycle-detector.js +1 -1
  44. package/lib/compiler/define.js +1103 -0
  45. package/lib/compiler/extend.js +983 -0
  46. package/lib/compiler/finalize-parse-cdl.js +231 -0
  47. package/lib/compiler/index.js +46 -39
  48. package/lib/compiler/kick-start.js +190 -0
  49. package/lib/compiler/moduleLayers.js +4 -4
  50. package/lib/compiler/populate.js +1226 -0
  51. package/lib/compiler/propagator.js +113 -47
  52. package/lib/compiler/resolve.js +1433 -0
  53. package/lib/compiler/shared.js +100 -65
  54. package/lib/compiler/tweak-assocs.js +529 -0
  55. package/lib/compiler/utils.js +215 -33
  56. package/lib/edm/.eslintrc.json +5 -0
  57. package/lib/edm/annotations/genericTranslation.js +38 -25
  58. package/lib/edm/annotations/preprocessAnnotations.js +3 -3
  59. package/lib/edm/csn2edm.js +10 -9
  60. package/lib/edm/edm.js +19 -20
  61. package/lib/edm/edmPreprocessor.js +166 -95
  62. package/lib/edm/edmUtils.js +127 -34
  63. package/lib/gen/Dictionary.json +92 -43
  64. package/lib/gen/language.checksum +1 -1
  65. package/lib/gen/language.interp +11 -1
  66. package/lib/gen/language.tokens +86 -82
  67. package/lib/gen/languageLexer.interp +18 -1
  68. package/lib/gen/languageLexer.js +925 -847
  69. package/lib/gen/languageLexer.tokens +78 -74
  70. package/lib/gen/languageParser.js +5434 -4298
  71. package/lib/json/from-csn.js +59 -17
  72. package/lib/json/to-csn.js +189 -71
  73. package/lib/language/antlrParser.js +3 -3
  74. package/lib/language/docCommentParser.js +3 -3
  75. package/lib/language/errorStrategy.js +26 -8
  76. package/lib/language/genericAntlrParser.js +144 -53
  77. package/lib/language/language.g4 +424 -200
  78. package/lib/language/multiLineStringParser.js +536 -0
  79. package/lib/main.d.ts +550 -61
  80. package/lib/main.js +38 -11
  81. package/lib/model/api.js +3 -1
  82. package/lib/model/csnRefs.js +322 -198
  83. package/lib/model/csnUtils.js +226 -370
  84. package/lib/model/enrichCsn.js +124 -69
  85. package/lib/model/revealInternalProperties.js +29 -7
  86. package/lib/model/sortViews.js +10 -2
  87. package/lib/modelCompare/compare.js +17 -12
  88. package/lib/optionProcessor.js +8 -3
  89. package/lib/render/.eslintrc.json +1 -2
  90. package/lib/render/DuplicateChecker.js +1 -1
  91. package/lib/render/manageConstraints.js +36 -33
  92. package/lib/render/toCdl.js +174 -275
  93. package/lib/render/toHdbcds.js +203 -122
  94. package/lib/render/toRename.js +7 -10
  95. package/lib/render/toSql.js +161 -82
  96. package/lib/render/utils/common.js +22 -8
  97. package/lib/render/utils/sql.js +10 -7
  98. package/lib/render/utils/stringEscapes.js +111 -0
  99. package/lib/sql-identifier.js +1 -1
  100. package/lib/transform/.eslintrc.json +5 -0
  101. package/lib/transform/braceExpression.js +4 -2
  102. package/lib/transform/db/.eslintrc.json +2 -0
  103. package/lib/transform/db/applyTransformations.js +212 -0
  104. package/lib/transform/db/assertUnique.js +1 -1
  105. package/lib/transform/db/associations.js +187 -0
  106. package/lib/transform/db/cdsPersistence.js +150 -0
  107. package/lib/transform/db/constraints.js +61 -56
  108. package/lib/transform/db/expansion.js +50 -29
  109. package/lib/transform/db/flattening.js +556 -106
  110. package/lib/transform/db/groupByOrderBy.js +3 -1
  111. package/lib/transform/db/temporal.js +236 -0
  112. package/lib/transform/db/transformExists.js +103 -28
  113. package/lib/transform/db/views.js +92 -44
  114. package/lib/transform/draft/.eslintrc.json +38 -0
  115. package/lib/transform/{db/draft.js → draft/db.js} +9 -7
  116. package/lib/transform/draft/odata.js +227 -0
  117. package/lib/transform/forHanaNew.js +98 -783
  118. package/lib/transform/forOdataNew.js +22 -175
  119. package/lib/transform/localized.js +36 -32
  120. package/lib/transform/odata/generateForeignKeyElements.js +3 -3
  121. package/lib/transform/odata/referenceFlattener.js +95 -89
  122. package/lib/transform/odata/structureFlattener.js +1 -1
  123. package/lib/transform/odata/toFinalBaseType.js +86 -12
  124. package/lib/transform/odata/typesExposure.js +5 -5
  125. package/lib/transform/odata/utils.js +2 -2
  126. package/lib/transform/transformUtilsNew.js +47 -33
  127. package/lib/transform/translateAssocsToJoins.js +13 -30
  128. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  129. package/lib/transform/universalCsn/coreComputed.js +170 -0
  130. package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
  131. package/lib/transform/universalCsn/utils.js +63 -0
  132. package/lib/utils/file.js +8 -3
  133. package/lib/utils/objectUtils.js +30 -0
  134. package/lib/utils/timetrace.js +8 -2
  135. package/package.json +1 -1
  136. package/share/messages/README.md +26 -0
  137. package/lib/compiler/definer.js +0 -2349
  138. package/lib/compiler/resolver.js +0 -2922
  139. package/lib/transform/db/helpers.js +0 -58
  140. 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,125 +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
- return (art.elements || art.enum || (art.targetAspect || art.target).elements)[elem];
347
- }
348
-
349
- // From the current CSN object, set implicit origin for the next navigation step
350
- // Currently (TODO: ?) `elements` only, i.e. what is needed for name resolution.
351
- function setImplicitOrigin( art, origin ) {
352
- setMembersImplicit( art.actions, origin.actions );
353
- setMembersImplicit( art.params, origin.params );
354
- if (art.returns) {
355
- art = art.returns;
356
- if (art.type || typeof art.$origin === 'object') // null, […], {…}
357
- return true; // not implicit or shortened
358
- origin = effectiveType( origin.returns );
359
- setCache( art, '_origin', origin );
360
- return true;
361
- }
362
- while (art.items) {
363
- art = art.items;
364
- if (art.type || typeof art.$origin === 'object') // null, […], {…}
365
- return true; // not implicit or shortened
366
- origin = effectiveType( origin.items );
367
- setCache( art, '_origin', origin );
368
- }
369
- setMembersImplicit( art.elements, origin.elements );
370
- // The enum base type is _not_ where we find the origins of the enum symbols.
371
- // A derived type of an enum type with individual annotations on a symbol
372
- // has both 'type' and '$origin'.
373
- if (!art.type || art.$origin)
374
- setMembersImplicit( art.enum, origin.enum );
375
- 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 );
376
406
  }
377
407
 
378
- function setMembersImplicit( members, originMembers ) {
379
- if (!members)
380
- return;
381
- for (const name in members) {
382
- const elem = members[name];
383
- if (!elem.type && typeof elem.$origin !== 'object') // undefined or string
384
- 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
+ }
385
450
  }
451
+ return main;
386
452
  }
387
453
 
388
- /**
389
- * Return the entity we select from
390
- *
391
- * @param {CSN.ArtifactReferencePath} ref
392
- * @returns {CSN.Definition}
393
- */
394
- function fromRef( ref ) {
395
- 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 }) );
396
466
  }
397
467
 
398
468
  /**
@@ -433,111 +503,108 @@ function csnRefs( csn ) {
433
503
  function resolveRef( ref, refCtx, main, query, parent, baseEnv ) {
434
504
  const path = (typeof ref === 'string') ? [ ref ] : ref.ref;
435
505
  if (!Array.isArray( path ))
436
- throw new Error( 'References must look like {ref:[...]}' );
506
+ throw new ModelError( 'References must look like {ref:[...]}' );
437
507
 
438
508
  const head = pathId( path[0] );
509
+ if (main) // TODO: improve, for csnpath starting with art
510
+ initDefinition( main );
439
511
  if (ref.param)
440
- return resolvePath( path, main.params[head], 'param' );
512
+ return resolvePath( path, main.params[head], main, 'param' );
441
513
 
442
514
  const semantics = referenceSemantics[refCtx] || {};
443
515
  if (semantics.dynamic === 'global' || ref.global)
444
- return resolvePath( path, csn.definitions[head], 'global', refCtx === 'from' );
516
+ return resolvePath( path, csn.definitions[head], null, 'global', semantics.assoc );
517
+
445
518
 
446
- const origin = cached( main, '_origin', getOriginRaw )
447
- if (origin)
448
- cached( main, '$origin', _a => setImplicitOrigin( main, origin ) );
449
- cached( main, '$queries', allQueries );
450
519
  let qcache = query && cache.get( query.projection || query );
451
- // BACKEND ISSUE: you cannot call csnRefs(), inspect some refs, change the
452
- // CSN and again inspect some refs without calling csnRefs() before!
453
- // WORKAROUND: if no cached query, a backend has changed the CSN - re-eval cache
454
- if (query && !qcache) {
455
- setCache( main, '$queries', allQueries( main ) );
456
- qcache = cache.get( query.projection || query );
457
- }
458
520
  // first the lexical scopes (due to query hierarchy) and $magic: ---------
459
521
  if (semantics.lexical !== false) {
460
522
  const tryAlias = path.length > 1 || ref.expand || ref.inline;
461
- let cache = qcache && (semantics.lexical ? semantics.lexical( qcache ) : qcache);
462
- while (cache) {
463
- 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];
464
526
  if (alias)
465
- return resolvePath( path, alias._select || alias._ref, 'alias', cache.$queryNumber );
466
- const mixin = cache._select.mixin && cache._select.mixin[head];
467
- if (mixin && {}.hasOwnProperty.call( cache._select.mixin, head ))
468
- return resolvePath( path, mixin, 'mixin', cache.$queryNumber );
469
- 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;
470
535
  }
471
536
  if (head.charAt(0) === '$') {
472
537
  if (head !== '$self' && head !== '$projection')
473
538
  return { scope: '$magic' };
474
539
  const self = qcache && qcache.$queryNumber > 1 ? qcache._select : main;
475
- return resolvePath( path, self, '$self' );
540
+ return resolvePath( path, self, null, '$self' );
476
541
  }
477
542
  }
478
543
  // now the dynamic environment: ------------------------------------------
479
544
  if (semantics.dynamic === 'target') { // ref in keys
480
- // not selecting the corresponding element for a select column works,
481
- // because explicit keys can only be provided with explicit redirection
482
- // target
483
- const target = csn.definitions[parent.target || parent.cast.target];
484
- return resolvePath( path, target.elements[head], 'target' );
545
+ const target = assocTarget( parent, refCtx );
546
+ return resolvePath( path, target.elements[head], target, 'target' );
485
547
  }
486
548
  if (baseEnv) // ref-target (filter condition), expand, inline
487
- return resolvePath( path, baseEnv.elements[head], semantics.dynamic );
549
+ return resolvePath( path, baseEnv.elements[head], baseEnv, semantics.dynamic );
488
550
  if (!query) // outside queries - TODO: items?
489
- return resolvePath( path, parent.elements[head], 'parent' );
551
+ return resolvePath( path, parent.elements[head], parent, 'parent' );
490
552
 
491
553
  if (semantics.dynamic === 'query')
492
554
  // TODO: for ON condition in expand, would need to use cached _element
493
- return resolvePath( path, qcache.elements[head], 'query' );
555
+ return resolvePath( path, qcache.elements[head], null, 'query' );
494
556
  for (const name in qcache.$aliases) {
495
- const found = qcache.$aliases[name].elements[head];
557
+ const alias = qcache.$aliases[name];
558
+ const found = alias.elements[head];
496
559
  if (found)
497
- return resolvePath( path, found, 'source', name )
560
+ return resolvePath( path, found, alias._ref, 'source', name )
498
561
  }
499
562
  // console.log(query.SELECT,qcache,qcache.$next,main)
500
- 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 }` );
501
564
  }
502
565
 
503
566
  /**
504
567
  * @param {CSN.Path} path
505
568
  * @param {CSN.Artifact} art
506
569
  * @param {string} [scope]
570
+ * @param [extraInfo]
507
571
  */
508
- function resolvePath( path, art, scope, extraInfo ) {
572
+ function resolvePath( path, art, parent, scope, extraInfo ) {
573
+ const staticAssoc = extraInfo === 'static' && scope === 'global';
509
574
  /** @type {{idx, art?, env?}[]} */
510
575
  const links = path.map( (_v, idx) => ({ idx }) );
511
576
  // TODO: backends should be changed to enable uncommenting:
512
577
  // if (!art) // does not work with test3/Associations/KeylessManagedAssociation/
513
- // 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 }`);
514
579
  links[0].art = art;
515
580
  for (let i = 1; i < links.length; ++i) { // yes, starting at 1, links[0] is set above
516
- art = navigationEnv( art );
517
- links[i - 1].env = art;
581
+ parent = navigationEnv( art, staticAssoc );
582
+ links[i - 1].env = parent;
518
583
  if (typeof path[i - 1] !== 'string')
519
- setCache( path[i - 1], '_env', art );
520
- art = art.elements[pathId( path[i] )];
584
+ setCache( path[i - 1], '_env', parent );
585
+ art = parent.elements[pathId( path[i] )];
521
586
  if (!art) {
522
587
  const env = links[i - 1].env;
523
588
  const loc = env.name && env.name.$location || env.$location;
524
- 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` );
525
590
  }
526
591
  links[i].art = art;
527
592
  }
528
593
  const last = path[path.length - 1];
529
- const fromRef = scope === 'global' && extraInfo;
594
+ const fromRef = scope === 'global' && extraInfo === 'target';
530
595
  if (fromRef || typeof last !== 'string') {
531
596
  const env = navigationEnv( art );
532
597
  links[links.length - 1].env = env;
533
- if (fromRef)
598
+ if (fromRef) {
534
599
  art = env;
600
+ parent = null;
601
+ }
535
602
  if (typeof last !== 'string')
536
603
  setCache( last, '_env', env )
537
604
  }
538
- return (extraInfo && !fromRef)
539
- ? { links, art, scope, $env: extraInfo }
540
- : { links, art, scope };
605
+ return (extraInfo && scope !== 'global')
606
+ ? { links, art, parent, scope, $env: extraInfo }
607
+ : { links, art, parent, scope };
541
608
  }
542
609
 
543
610
  /**
@@ -556,8 +623,8 @@ function csnRefs( csn ) {
556
623
  if (query.ref) { // ref in from
557
624
  // console.log('SQ:',query,cache.get(query))
558
625
  const as = query.as || implicitAs( query.ref );
559
- const _ref = fromRef( query );
560
- 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 };
561
628
  }
562
629
  else {
563
630
  const qcache = getQueryCache( parentQuery );
@@ -570,15 +637,18 @@ function csnRefs( csn ) {
570
637
  if (select) {
571
638
  cache.set( select, qcache ); // query and query.SELECT have the same cache qcache
572
639
  qcache._select = select;
640
+ qcache._parent = main;
573
641
  all.push( qcache );
574
642
  }
575
643
  }
576
644
  } );
577
645
  all.forEach( function initElements( qcache, index ) {
646
+ qcache._parent = main;
578
647
  qcache.$queryNumber = index + 1;
579
- qcache.elements = (index ? qcache._select : main).elements;
580
- const columns = qcache._select.columns;
581
- if (qcache.elements && columns)
648
+ const { elements } = (index ? qcache._select : main);
649
+ qcache.elements = elements;
650
+ const { columns } = qcache._select;
651
+ if (elements && columns)
582
652
  columns.map( c => initColumnElement( c, qcache ) );
583
653
  } );
584
654
  return all;
@@ -593,7 +663,7 @@ function csnRefs( csn ) {
593
663
  function getQueryCache( parentQuery ) {
594
664
  if (!parentQuery)
595
665
  return { $aliases: Object.create(null) };
596
- const pcache = cache.get( parentQuery );
666
+ const pcache = cache.get( parentQuery.projection || parentQuery );
597
667
  if (!parentQuery.SET) // SELECT / projection: real sub query
598
668
  return { $aliases: Object.create(null), $next: pcache };
599
669
  // the parent query is a SET: that is not a sub query
@@ -617,7 +687,8 @@ function csnRefs( csn ) {
617
687
  while (type.items)
618
688
  type = type.items;
619
689
  const elem = setCache( col, '_element', type.elements[as] );
620
- // 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 );
621
692
  if (col.expand)
622
693
  col.expand.map( c => initColumnElement( c, elem ) );
623
694
  }
@@ -659,8 +730,8 @@ function csnRefs( csn ) {
659
730
  // i.e. a value with an `elements` property.
660
731
  // TODO: only used in forHanaNew - move somewhere else
661
732
  /**
662
- * @param {CSN.Query} query node (object with SET or SELECT property)
663
- * @param {CSN.Definition} main
733
+ * @param {object} query node (object with SET or SELECT property)
734
+ * @param {object} main definition
664
735
  */
665
736
  function queryOrMain( query, main ) {
666
737
  while (query.SET)
@@ -675,7 +746,7 @@ function queryOrMain( query, main ) {
675
746
  // 'projection'), but `leading` can be its `query` property:
676
747
  if ((leading === query || leading === query.query) && main.elements)
677
748
  return main;
678
- throw new Error( `Query elements not available: ${ Object.keys( query ).join('+') }`);
749
+ throw new ModelError( `Query elements not available: ${ Object.keys( query ).join('+') }`);
679
750
  }
680
751
 
681
752
  /**
@@ -684,7 +755,7 @@ function queryOrMain( query, main ) {
684
755
  * @param {CSN.Query} query
685
756
  * @param {CSN.QuerySelect} fromSelect
686
757
  * @param {CSN.Query} parentQuery
687
- * @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
688
759
  */
689
760
  function traverseQuery( query, fromSelect, parentQuery, callback ) {
690
761
  const select = query.SELECT || query.projection;
@@ -739,6 +810,52 @@ function traverseExpr( expr, parentQuery, callback ) {
739
810
  }
740
811
  }
741
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
+
742
859
  function pathId( item ) {
743
860
  return (typeof item === 'string') ? item : item.id;
744
861
  }
@@ -748,31 +865,43 @@ function implicitAs( ref ) {
748
865
  return id.substring( id.lastIndexOf('.') + 1 );
749
866
  }
750
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
+
751
880
  /**
752
881
  * @param {CSN.Path} csnPath
753
882
  * @param {CSN.Model} csn
754
883
  */
755
884
  function analyseCsnPath( csnPath, csn, resolve ) {
756
- if (csnPath[0] !== 'definitions')
757
- throw new Error( 'References outside definitions not supported yet');
758
-
759
885
  /** @type {object} */
760
- let obj = csn;
761
- let parent = null;
762
886
  let query = null;
763
887
  let refCtx = null;
764
- let art = null;
765
888
  /** @type {boolean|string|number} */
766
889
  let isName = false;
767
890
  let baseRef = null;
768
891
  let baseEnv = null;
769
- 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;
770
899
 
771
- for (let index = 0; index < csnPath.length; index++) {
772
900
  const prop = csnPath[index];
773
901
  // array item, name/index of artifact/member, (named) argument
774
- if (isName || Array.isArray( obj )) {
775
- 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') {
776
905
  parent = art;
777
906
  art = obj[prop];
778
907
  }
@@ -784,7 +913,8 @@ function analyseCsnPath( csnPath, csn, resolve ) {
784
913
  parent = null;
785
914
  }
786
915
  isName = prop;
787
- 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;
788
918
  }
789
919
  else if (prop === 'items' || prop === 'returns') {
790
920
  art = obj[prop];
@@ -800,15 +930,13 @@ function analyseCsnPath( csnPath, csn, resolve ) {
800
930
  }
801
931
  else if (prop === 'where' && refCtx === 'ref') {
802
932
  if (resolve)
803
- baseEnv = resolve.ref_where( obj, baseRef, refCtx, csn.definitions[csnPath[1]],
804
- query, parent, baseEnv );
933
+ baseEnv = resolve.ref_where( obj, baseRef, refCtx, main, query, parent, baseEnv );
805
934
  refCtx = 'ref_where';
806
935
  }
807
936
  else if (prop === 'expand' || prop === 'inline') {
808
937
  if (obj.ref) {
809
938
  if (resolve)
810
- baseEnv = resolve.expandInline( obj, refCtx, csn.definitions[csnPath[1]],
811
- query, parent, baseEnv );
939
+ baseEnv = resolve.expandInline( obj, refCtx, main, query, parent, baseEnv );
812
940
  refCtx = prop;
813
941
  }
814
942
  if (prop === 'expand')
@@ -816,9 +944,9 @@ function analyseCsnPath( csnPath, csn, resolve ) {
816
944
  }
817
945
  else if (prop === 'on') {
818
946
  if (refCtx === 'from')
819
- refCtx = 'join_on';
947
+ refCtx = 'on_join';
820
948
  else if (refCtx === 'mixin')
821
- refCtx = 'mixin_on';
949
+ refCtx = 'on_mixin';
822
950
  else
823
951
  refCtx = 'on'; // will use query elements with REDIRECTED TO
824
952
  }
@@ -832,11 +960,7 @@ function analyseCsnPath( csnPath, csn, resolve ) {
832
960
  else if (prop !== 'xpr') {
833
961
  refCtx = prop;
834
962
  }
835
-
836
963
  obj = obj[prop];
837
- if (!obj && !resolve)
838
- // For the semantic location, use current object as best guess
839
- break;
840
964
  }
841
965
  // console.log( 'CPATH:', csnPath, refCtx, obj, parent.$location );
842
966
  if (!resolve)