@sap/cds-compiler 2.5.0 → 2.10.4

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 (92) hide show
  1. package/CHANGELOG.md +191 -9
  2. package/bin/cdsc.js +2 -2
  3. package/doc/CHANGELOG_BETA.md +33 -3
  4. package/lib/api/main.js +29 -101
  5. package/lib/api/options.js +15 -11
  6. package/lib/api/validate.js +12 -8
  7. package/lib/backends.js +0 -81
  8. package/lib/base/keywords.js +32 -2
  9. package/lib/base/message-registry.js +63 -9
  10. package/lib/base/messages.js +63 -21
  11. package/lib/base/model.js +2 -3
  12. package/lib/checks/defaultValues.js +27 -2
  13. package/lib/checks/elements.js +1 -6
  14. package/lib/checks/foreignKeys.js +0 -6
  15. package/lib/checks/managedWithoutKeys.js +17 -0
  16. package/lib/checks/nonexpandableStructured.js +38 -0
  17. package/lib/checks/onConditions.js +9 -45
  18. package/lib/checks/queryNoDbArtifacts.js +25 -7
  19. package/lib/checks/selectItems.js +25 -2
  20. package/lib/checks/types.js +26 -2
  21. package/lib/checks/unknownMagic.js +38 -0
  22. package/lib/checks/utils.js +61 -0
  23. package/lib/checks/validator.js +60 -7
  24. package/lib/compiler/assert-consistency.js +16 -7
  25. package/lib/compiler/builtins.js +2 -0
  26. package/lib/compiler/checks.js +6 -4
  27. package/lib/compiler/definer.js +99 -42
  28. package/lib/compiler/index.js +73 -27
  29. package/lib/compiler/resolver.js +288 -157
  30. package/lib/compiler/shared.js +31 -11
  31. package/lib/edm/annotations/genericTranslation.js +182 -186
  32. package/lib/edm/csn2edm.js +103 -108
  33. package/lib/edm/edm.js +18 -21
  34. package/lib/edm/edmPreprocessor.js +361 -114
  35. package/lib/edm/edmUtils.js +103 -33
  36. package/lib/gen/Dictionary.json +22 -0
  37. package/lib/gen/language.checksum +1 -1
  38. package/lib/gen/language.interp +12 -1
  39. package/lib/gen/language.tokens +57 -53
  40. package/lib/gen/languageLexer.interp +10 -1
  41. package/lib/gen/languageLexer.js +770 -744
  42. package/lib/gen/languageLexer.tokens +49 -46
  43. package/lib/gen/languageParser.js +4713 -4279
  44. package/lib/json/from-csn.js +103 -45
  45. package/lib/json/to-csn.js +296 -117
  46. package/lib/language/antlrParser.js +4 -3
  47. package/lib/language/errorStrategy.js +1 -0
  48. package/lib/language/genericAntlrParser.js +21 -12
  49. package/lib/language/language.g4 +99 -31
  50. package/lib/main.d.ts +81 -3
  51. package/lib/main.js +30 -7
  52. package/lib/model/api.js +78 -0
  53. package/lib/model/csnRefs.js +329 -142
  54. package/lib/model/csnUtils.js +235 -58
  55. package/lib/model/enrichCsn.js +18 -1
  56. package/lib/model/revealInternalProperties.js +2 -1
  57. package/lib/modelCompare/compare.js +37 -20
  58. package/lib/optionProcessor.js +9 -3
  59. package/lib/render/.eslintrc.json +4 -1
  60. package/lib/render/DuplicateChecker.js +8 -5
  61. package/lib/render/toCdl.js +112 -33
  62. package/lib/render/toHdbcds.js +134 -64
  63. package/lib/render/toSql.js +91 -38
  64. package/lib/render/utils/common.js +8 -13
  65. package/lib/render/utils/sql.js +3 -3
  66. package/lib/sql-identifier.js +6 -1
  67. package/lib/transform/db/assertUnique.js +5 -6
  68. package/lib/transform/db/constraints.js +29 -13
  69. package/lib/transform/db/draft.js +8 -6
  70. package/lib/transform/db/expansion.js +582 -0
  71. package/lib/transform/db/flattening.js +325 -0
  72. package/lib/transform/db/groupByOrderBy.js +2 -2
  73. package/lib/transform/db/transformExists.js +284 -63
  74. package/lib/transform/forHanaNew.js +98 -381
  75. package/lib/transform/forOdataNew.js +21 -22
  76. package/lib/transform/localized.js +37 -10
  77. package/lib/transform/odata/attachPath.js +19 -4
  78. package/lib/transform/odata/generateForeignKeyElements.js +11 -10
  79. package/lib/transform/odata/referenceFlattener.js +60 -39
  80. package/lib/transform/odata/sortByAssociationDependency.js +2 -2
  81. package/lib/transform/odata/structuralPath.js +72 -0
  82. package/lib/transform/odata/structureFlattener.js +19 -18
  83. package/lib/transform/odata/typesExposure.js +22 -12
  84. package/lib/transform/transformUtilsNew.js +134 -78
  85. package/lib/transform/translateAssocsToJoins.js +17 -14
  86. package/lib/transform/universalCsnEnricher.js +67 -0
  87. package/lib/utils/file.js +0 -11
  88. package/lib/utils/moduleResolve.js +6 -8
  89. package/package.json +1 -1
  90. package/lib/json/walker.js +0 -26
  91. package/lib/transform/sqlite +0 -0
  92. package/lib/utils/string.js +0 -17
@@ -1,27 +1,161 @@
1
1
  // CSN functionality for resolving references
2
+
3
+ // Resolving references in a CSN can be a bit tricky, because the semantics of
4
+ // a reference is context-dependent, especially if queries are involved. This
5
+ // module provides the corresponding resolve/inspect functions.
2
6
  //
3
- // The functions in this module expect a well-formed CSN with valid references.
4
- // If that is not the case, it simply throws an error (which might even be a
5
- // plain TypeError) without any claim for the error message to be
6
- // user-friendly. CSN processors can provide user-friendly error messages by
7
- // calling the Core Compiler in that case. For details, see
8
- // internalDoc/CoreCompiler.md#use-of-the-core-compiler-for-csn-processors.
7
+ // See below for preconditions / things to consider the functions in this
8
+ // module do not issue user-friendly messages for invalid references in a CSN,
9
+ // such messages are (hopefully) issued by the compile() function.
10
+
11
+ // The main export function `csnRefs` of this module is called with a CSN as
12
+ // input and returns functions which analyse references in the provided CSN:
9
13
  //
10
- // For details about the name resolution in CSN, see
11
- // internalDoc/CsnSyntax.md#helper-property-for-simplified-name-resolution
12
- // and doc/NameResolution.md.
14
+ // const { csnRefs } = require('../model/csnRefs');
15
+ // function myCsnAnalyser( csn ) {
16
+ // const { inspectRef } = csnRefs( csn );
17
+ // …
18
+ // const { links, art } = inspectRef( csnPath );
19
+ // // → art is the CSN node which is referred to by the reference
20
+ // // → links provides some info about each reference path step
21
+ // …
22
+ // }
13
23
  //
24
+ // You can see the results of the CSN refs functions by using our client tool:
25
+ // cdsc --enrich-csn MyModel.cds
26
+ // It is also used by our references tests, for details see ./enrichCsn.js.
27
+
14
28
  // Terminology used in this file:
15
29
  //
16
30
  // - ref (reference): a { ref: <path> } object (or sometimes also a string)
17
- // referring an artifact or member
31
+ // referring an artifact or member (element, …)
18
32
  // - path: an array of strings or { id: … } objects for the dot-connected names
19
33
  // used as reference
20
34
  // - csnPath: an array of strings and numbers (e.g. ['definitions', 'S.E',
21
- // 'query', 'SELECT', 'from', 'ref', 0]); they are the property names and
22
- // array indexes which navigate from the CSN root to the current node.
35
+ // 'query', 'SELECT', 'from', 'ref', 0, 'where', 2]); they are the property
36
+ // names and array indexes which navigate from the CSN root to the reference.
37
+
38
+ // ## PRECONDITIONS / THINGS TO CONSIDER -------------------------------------
39
+
40
+ // The functions in this module expect
41
+ //
42
+ // 1. a well-formed CSN with valid references;
43
+ // 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
+ // 3. no (relevant) CSN changes between the calls of the same instance of
47
+ // inspectRef() - to enable caching.
48
+ //
49
+ // If any of these conditions are not given, our functions usually simply
50
+ // 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
52
+ // messages by calling the Core Compiler in case of exceptions. For details,
53
+ // see internalDoc/CoreCompiler.md#use-of-the-core-compiler-for-csn-processors.
54
+
55
+ // During a transformation, care must be taked to adhere to these conditions.
56
+ // E.g. a structure flattening function cannot create an element `s_x` and
57
+ // delete `s` and then still expects inspectRef() to be able to resolve a
58
+ // reference `['s', 'x']`.
59
+
60
+ // The functions in this module also use an internal cache. The second call of
61
+ // inspectRef() in the following example might lead to a wrong result or an
62
+ // exception if the assignment to `inspectRef` is not uncommented:
63
+ //
64
+ // let { inspectRef } = csnRefs( csn );
65
+ // const csnPath = ['definitions','P','projection','columns',0];
66
+ // const subElement = inspectRef( csnPath ); // type T is involved
67
+ // csn.definitions.T.type = 'some.other.type';
68
+ // // ({ inspectRef } = csnRefs( csn )); // invalidate caches
69
+ // … = inspectRef( csnPath ); // type T - using the cached or the new?
70
+ //
71
+ // On request, we might add a functions for individual cache invalidations or
72
+ // low-level versions of inspectRef() for performance.
73
+
74
+ // ## NAME RESOLUTION OVERVIEW -----------------------------------------------
23
75
 
24
- // TODO: think of using the term `query` for the thing inside SELECT/SET.
76
+ // The most interesting part of a reference is always: where to search for the
77
+ // name in its first path item? The general search is always as follows, with
78
+ // the exact behavior being dependent on the “reference context” (e.g. “reference
79
+ // in a `on` condition of a `mixin` definition”):
80
+ //
81
+ // 1. We search in environments constructed by “defining” names “around” the
82
+ // lexical position of the reference. In a CSN, these could be the
83
+ // (explicit and implicit) table alias names and `mixin` definitions of the
84
+ // current query and its parent queries (according to the query hiearchy).
85
+ // 2. If the search according to (1) was not successful and the name starts
86
+ // with a `$`, we could consider the name to be a “magic” variable with
87
+ // `$self` (and `$projection`) being a special magic variable.
88
+ // 3. Otherwise, we would search in a “dynamic” environment, which could be
89
+ // `‹csn›.definitions` for global references like `type`, the elements of
90
+ // the current element's parent, the combined elements of the query source
91
+ // entities, the resulting elements of the current query, or something
92
+ // special (elements of the association's target, …).
93
+ //
94
+ // The names in further path items are searched in the “navigation” environment
95
+ // of the path so far - it does not need to depend on the reference context (as
96
+ // we do not check the validility here):
97
+ //
98
+ // 1. We search in the elements of the target entity for associations and
99
+ // compositions, and in the elements of the current object otherwise.
100
+ // 2. If there is an `items`, we check for `elements`/`target` inside `items`.
101
+ // 3. `elements`/`target`/`items` inherited from the “effective type” are also
102
+ // considered.
103
+
104
+ // For details about the name resolution in CSN, see
105
+ // internalDoc/CsnSyntax.md#helper-property-for-simplified-name-resolution
106
+ // and doc/NameResolution.md. Here comes a summary.
107
+
108
+ // ## IMPLEMENTATION OVERVIEW ------------------------------------------------
109
+
110
+ // The main function `inspectRef` works as follows:
111
+ //
112
+ // 1. For ease of use, the input is the “CSN path” as explained above, e.g.
113
+ // ['definitions', 'P', 'query', 'SELECT', 'from', 'ref', 0, 'where', 2]
114
+ // 2. This is condensed into a “reference context” string, e.g. `ref_where`;
115
+ // that might also depend on sibling properties along the way, e.g.
116
+ // ['definitions', 'P', 'query', 'SELECT', 'columns', 0, 'expand', 0] leads
117
+ // to `expand` if there is a `‹csn›.definitions.P.query.SELECT.columns[0].ref`
118
+ // and to `columns` otherwise.
119
+ // 3. Additionally, other useful CSN nodes are collected like the current query;
120
+ // the queries of a definition are also prepared for further inspection.
121
+ // 4. If applicable, a “base environment” is calculated; e.g. references in
122
+ // `ref_where` are resolved against the elements of the entity referred to
123
+ // by the outer `ref`.
124
+ // 5. We look up the “reference semantics” in constant `referenceSemantics`
125
+ // using the “reference context” string as key.
126
+ // 6. The property `lexical` determines whether to search in “lexical
127
+ // environments” (table aliases and `mixin`s) starting from which query, and
128
+ // whether to do something special for names starting with `$`.
129
+ // 7. The property `dynamic` determines where to search if the lexical search
130
+ // was not successful.
131
+ // 8. The remaining reference path is resolved as well - the final referred CSN
132
+ // node is returned as well as information about each path step.
133
+
134
+ // We usually cache calculated data. For the following reasons, we now use a
135
+ // WeakMap as cache instead of adding non-enumerable properties to the CSN:
136
+ //
137
+ // - CSN consumers should not have access to the cached data, as we might
138
+ // change the way how we calculate things.
139
+ // - Avoid memory leaks.
140
+ // - Natural cache invalidation if there is no handle anymore to the functions
141
+ // returned by `csnRefs`.
142
+
143
+ // Our cache looks like follows:
144
+
145
+ // - Each object in the CSN could have an cache entry which itself is an object
146
+ // which contains cached data. Such data can be a link to a CSN node (like
147
+ // `_effectiveType`/`elements`), scalar (like `$queryNumber`) or link to
148
+ // another cache object (like `$next`).
149
+ // - Usually, each CSN object has an individual cache object.
150
+ // - For CSN queries nodes, cache objects are _shared_: both the CSN nodes
151
+ // `‹query› = { SELECT: ‹select›, … }` and `‹select›` share the same cache
152
+ // object; a UNION `‹set_query› = { SET: args: [‹query1›, …] }` and ‹query1›
153
+ // (which can itself be a `SELECT` or `SET`) share also the same cache
154
+ // object; this way, the relevant query elements are directly available.
155
+ // - The cache objects for all queries of an entity are initialized as soon as
156
+ // any reference in the entity is inspected: with data for the query
157
+ // hierarchy, query number, table aliases and links from a column to its
158
+ // respective inferred element.
25
159
 
26
160
  'use strict';
27
161
 
@@ -34,19 +168,24 @@ const { locationString } = require('../base/location');
34
168
  const artifactProperties = [ 'elements', 'columns', 'keys', 'mixin', 'enum',
35
169
  'params', 'actions', 'definitions', 'extensions' ]; // + 'args', see above
36
170
 
37
- const scopeSpecs = {
38
- type: { global: true },
39
- includes: { global: true },
40
- target: { global: true },
41
- from: { global: true },
171
+ // Mapping the “reference context string” to the reference semantics
172
+ // - lexical: false | Function - determines where to look first for “lexical names”
173
+ // - dynamic: String - describes the dynamic environment (if in query)
174
+ 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' },
42
180
  keys: { lexical: false, dynamic: 'target' },
43
- excluding: { lexical: false },
44
- expand: { lexical: justDollar }, // dynamic: 'baseEnv-or-source'
45
- inline: { lexical: justDollar }, // dynamic: 'baseEnv'
46
- 'ref-target': { lexical: justDollar }, // dynamic: 'baseEnv'
181
+ excluding: { lexical: false, dynamic: 'source' },
182
+ expand: { lexical: justDollar, dynamic: 'expand' }, // ...using baseEnv
183
+ inline: { lexical: justDollar, dynamic: 'inline' }, // ...using baseEnv
184
+ ref_where: { lexical: justDollar , dynamic: 'ref-target'}, // ...using baseEnv
47
185
  on: { lexical: justDollar, dynamic: 'query' }, // assoc defs, redirected to
48
- orderBy: { dynamic: 'query' },
49
- 'orderBy-set': { lexical: query => query.$next, dynamic: 'query' }, // to outer SELECT (from UNION)
186
+ // there are also 'join_on' and 'mixin_on' with default semantics
187
+ orderBy: { lexical: query => query, dynamic: 'query' },
188
+ orderBy_set: { lexical: query => query.$next, dynamic: 'query' }, // to outer SELECT (from UNION)
50
189
  // default: { lexical: query => query, dynamic: 'source' }
51
190
  }
52
191
 
@@ -60,25 +199,24 @@ function justDollar() {
60
199
  function csnRefs( csn ) {
61
200
  const cache = new WeakMap();
62
201
 
63
- // TODO: code cleanup after getting rid of $env
64
- resolveRef.expand = function resolveRefExpand( obj, ...args ) {
65
- return cached( obj, '_env', () => navigationEnv( resolveRef( obj, ...args ).art ) );
202
+ // Functions which set the new `baseEnv`:
203
+ resolveRef.expandInline = function resolve_expandInline( ref, ...args ) {
204
+ return cached( ref, '_env', () => navigationEnv( resolveRef( ref, ...args ).art ) );
66
205
  }
67
- resolveRef.refWhere = function resolveRefWhere( refObj, obj, ...args ) {
68
- return cached( obj, '_env', () => {
69
- resolveRef( refObj, ...args ); // sets _env cache for non-string ref items
70
- return getCache( obj, '_env' );
206
+ resolveRef.ref_where = function resolve_ref_where( pathItem, baseRef, ...args ) {
207
+ return cached( pathItem, '_env', () => {
208
+ resolveRef( baseRef, ...args ); // sets _env cache for non-string ref items
209
+ return getCache( pathItem, '_env' );
71
210
  } );
72
211
  }
73
212
  return {
74
- effectiveType, artifactRef, inspectRef, queryOrMain,
213
+ effectiveType, artifactRef, getOrigin, inspectRef, queryOrMain,
75
214
  __getCache_forEnrichCsnDebugging: obj => cache.get( obj ),
76
215
  };
77
216
 
78
217
  /**
79
218
  * Return the type relevant for name resolution, i.e. the object which has a
80
- * `target`, `elements`, `enum` property, or no `type` property. To avoid
81
- * confusion with the "base type", we do not use the term "final type".
219
+ * `target`, `elements`, `enum` property, or no `type` property.
82
220
  * (This function could be simplified if we would use JS prototypes for type refs.)
83
221
  *
84
222
  * @param {CSN.ArtifactWithRefs} art
@@ -87,15 +225,16 @@ function csnRefs( csn ) {
87
225
  const cachedType = getCache( art, '_effectiveType' );
88
226
  if (cachedType !== undefined)
89
227
  return cachedType;
90
- else if (!art.type || art.elements || art.target || art.targetAspect || art.enum)
228
+ else if (!art.type && !art.$origin ||
229
+ art.elements || art.target || art.targetAspect || art.enum)
91
230
  return setCache( art, '_effectiveType', art );
92
231
 
93
232
  const chain = [];
94
- while (getCache( art, '_effectiveType' ) === undefined && art.type &&
95
- !art.elements && !art.target && !art.targetAspect && !art.enum) {
233
+ while (getCache( art, '_effectiveType' ) === undefined && (art.type || art.$origin) &&
234
+ !art.elements && !art.target && !art.targetAspect && !art.enum && !art.items) {
96
235
  chain.push( art );
97
236
  setCache( art, '_effectiveType', 0 ); // initial setting in case of cycles
98
- art = artifactRef( art.type, BUILTIN_TYPE );
237
+ art = (art.$origin) ? getOrigin( art ) : artifactRef( art.type, BUILTIN_TYPE );
99
238
  }
100
239
  if (getCache( art, '_effectiveType' ) === 0)
101
240
  throw new Error( 'Circular type reference');
@@ -113,7 +252,7 @@ function csnRefs( csn ) {
113
252
  // elements of array items (that is the task of the core compiler /
114
253
  // semantic check)
115
254
  while (type.items)
116
- type = type.items;
255
+ type = effectiveType( type.items );
117
256
  // cannot navigate along targetAspect!
118
257
  return (type.target) ? csn.definitions[type.target] : type;
119
258
  }
@@ -145,6 +284,35 @@ function csnRefs( csn ) {
145
284
  return art;
146
285
  }
147
286
 
287
+ function getOrigin( def ) {
288
+ const art = cached( def, '_origin', originPathRef );
289
+ if (art)
290
+ return art;
291
+ throw new Error( 'Undefined origin reference' );
292
+ }
293
+
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;
300
+ }
301
+
302
+ function originNavigation( art, elem ) {
303
+ if (typeof elem !== 'string') {
304
+ if (elem.action)
305
+ return art.actions[elem.action]
306
+ if (elem.param)
307
+ return (elem.param ? art.params[elem.param] : art.returns);
308
+ }
309
+ if (art.returns)
310
+ art = art.returns;
311
+ while (art.items)
312
+ art = art.items;
313
+ return (art.elements || art.enum || (art.targetAspect || art.target).elements)[elem];
314
+ }
315
+
148
316
  /**
149
317
  * Return the entity we select from
150
318
  *
@@ -158,100 +326,111 @@ function csnRefs( csn ) {
158
326
  /**
159
327
  * @param {CSN.Path} csnPath
160
328
  *
329
+ * - return value `art`: the “resulting” CSN of the reference
330
+ *
331
+ * - return value `links`: array of { art, env } in length of ref.path where
332
+ * art = the definition or element reached by the ref path so far
333
+ * env = the “navigation environment” provided by `art`
334
+ * (not set for last item, except for `from` reference or with filter)
335
+ *
161
336
  * - return value `scope`
162
337
  * global: first item is name of definition
163
338
  * param: first item is parameter of definition (with param: true)
164
339
  * parent: first item is elem of parent (definition or outer elem)
165
340
  * target: first item is elem in target (for keys of assocs)
166
341
  * $magic: magic variable (path starts with $magic, see also $self)
342
+ * $self: first item is $self or $projection
167
343
  * // now values only in queries:
168
344
  * mixin: first item is mixin
169
345
  * alias: first item is table alias
170
- * $self: first item is $self or $projection
171
- * source: first item is element in a query source
346
+ * source: first item is element in a source of the current query
172
347
  * query: first item is element of current query
173
348
  * ref-target: first item is element of target of outer ref item
174
349
  * (used for filter condition)
175
- * expand: ref is "path continuation" of an EXPAND
176
- * inline: ref is "path continuation" of an INLINE
350
+ * expand: ref is "path continuation" of a ref with EXPAND
351
+ * inline: ref is "path continuation" of a ref with INLINE
352
+ *
353
+ * - return value `$env` is set with certain values of `scope`:
354
+ * with 'alias': the query number _n_ (the _n_th SELECT)
355
+ * with 'source': the table alias name for the source entity
177
356
  */
178
357
  function inspectRef( csnPath ) {
179
358
  return analyseCsnPath( csnPath, csn, resolveRef );
180
359
  }
181
360
 
182
- function resolveRef( obj, parent, query, scope, baseEnv, main ) {
183
- const path = (typeof obj === 'string') ? [ obj ] : obj.ref;
361
+ function resolveRef( ref, refCtx, main, query, parent, baseEnv ) {
362
+ const path = (typeof ref === 'string') ? [ ref ] : ref.ref;
184
363
  if (!Array.isArray( path ))
185
- throw new Error( 'Value references must look like {ref:[...]}' );
364
+ throw new Error( 'References must look like {ref:[...]}' );
186
365
 
187
366
  const head = pathId( path[0] );
188
- if (obj.param)
189
- return expandRefPath( path, main.params[head], 'param' );
367
+ if (ref.param)
368
+ return resolvePath( path, main.params[head], 'param' );
190
369
 
191
- const spec = scopeSpecs[scope] || {};
192
- if (spec.global || obj.global)
193
- return expandRefPath( path, csn.definitions[head], 'global', scope === 'from' );
370
+ const semantics = referenceSemantics[refCtx] || {};
371
+ if (semantics.dynamic === 'global' || ref.global)
372
+ return resolvePath( path, csn.definitions[head], 'global', refCtx === 'from' );
194
373
 
195
374
  cached( main, '$queries', allQueries );
196
- let qenv = query && cache.get( query.projection || query );
375
+ let qcache = query && cache.get( query.projection || query );
197
376
  // BACKEND ISSUE: you cannot call csnRefs(), inspect some refs, change the
198
377
  // CSN and again inspect some refs without calling csnRefs() before!
199
378
  // WORKAROUND: if no cached query, a backend has changed the CSN - re-eval cache
200
- if (query && !qenv) {
379
+ if (query && !qcache) {
201
380
  setCache( main, '$queries', allQueries( main ) );
202
- qenv = cache.get( query.projection || query );
381
+ qcache = cache.get( query.projection || query );
203
382
  }
204
383
  // first the lexical scopes (due to query hierarchy) and $magic: ---------
205
- if (spec.lexical !== false) {
206
- const tryAlias = path.length > 1 || obj.expand || obj.inline;
207
- let env = (qenv && spec.lexical) ? spec.lexical( qenv ) : qenv;
208
- while (env) {
209
- const alias = tryAlias && env.aliases[head];
384
+ if (semantics.lexical !== false) {
385
+ const tryAlias = path.length > 1 || ref.expand || ref.inline;
386
+ let cache = qcache && (semantics.lexical ? semantics.lexical( qcache ) : qcache);
387
+ while (cache) {
388
+ const alias = tryAlias && cache.$aliases[head];
210
389
  if (alias)
211
- return expandRefPath( path, alias._select || alias, 'alias', env.$queryNumber );
212
- const mixin = env._select.mixin && env._select.mixin[head];
213
- if (mixin && {}.hasOwnProperty.call( env._select.mixin, head ))
214
- return expandRefPath( path, mixin, 'mixin', env.$queryNumber );
215
- env = env.$next;
390
+ return resolvePath( path, alias._select || alias, 'alias', cache.$queryNumber );
391
+ const mixin = cache._select.mixin && cache._select.mixin[head];
392
+ if (mixin && {}.hasOwnProperty.call( cache._select.mixin, head ))
393
+ return resolvePath( path, mixin, 'mixin', cache.$queryNumber );
394
+ cache = cache.$next;
216
395
  }
217
396
  if (head.charAt(0) === '$') {
218
397
  if (head !== '$self' && head !== '$projection')
219
398
  return { scope: '$magic' };
220
- const self = qenv && qenv.$queryNumber > 1 ? qenv._select : main;
221
- return expandRefPath( path, self, '$self' );
399
+ const self = qcache && qcache.$queryNumber > 1 ? qcache._select : main;
400
+ return resolvePath( path, self, '$self' );
222
401
  }
223
402
  }
224
403
  // now the dynamic environment: ------------------------------------------
225
- if (spec.dynamic === 'target') { // ref in keys
404
+ if (semantics.dynamic === 'target') { // ref in keys
226
405
  // not selecting the corresponding element for a select column works,
227
406
  // because explicit keys can only be provided with explicit redirection
228
407
  // target
229
408
  const target = csn.definitions[parent.target || parent.cast.target];
230
- return expandRefPath( path, target.elements[head], 'target' );
409
+ return resolvePath( path, target.elements[head], 'target' );
231
410
  }
232
411
  if (baseEnv) // ref-target (filter condition), expand, inline
233
- return expandRefPath( path, baseEnv.elements[head], scope );
412
+ return resolvePath( path, baseEnv.elements[head], semantics.dynamic );
234
413
  if (!query) // outside queries - TODO: items?
235
- return expandRefPath( path, parent.elements[head], 'parent' );
414
+ return resolvePath( path, parent.elements[head], 'parent' );
236
415
 
237
- if (spec.dynamic === 'query')
416
+ if (semantics.dynamic === 'query')
238
417
  // TODO: for ON condition in expand, would need to use cached _element
239
- return expandRefPath( path, qenv.elements[head], 'query' );
240
- for (const name in qenv.aliases) {
241
- const found = qenv.aliases[name].elements[head];
418
+ return resolvePath( path, qcache.elements[head], 'query' );
419
+ for (const name in qcache.$aliases) {
420
+ const found = qcache.$aliases[name].elements[head];
242
421
  if (found)
243
- return expandRefPath( path, found, 'source', name )
422
+ return resolvePath( path, found, 'source', name )
244
423
  }
245
- // console.log(query.SELECT,qenv,qenv.$next,main)
246
- throw new Error ( `Path item ${ 0 }=${ head } refers to nothing, scope: ${ scope }` );
424
+ // console.log(query.SELECT,qcache,qcache.$next,main)
425
+ throw new Error ( `Path item ${ 0 }=${ head } refers to nothing, refCtx: ${ refCtx }` );
247
426
  }
248
427
 
249
428
  /**
250
429
  * @param {CSN.Path} path
251
430
  * @param {CSN.Artifact} art
252
- * @param {string | null} [scope]
431
+ * @param {string} [scope]
253
432
  */
254
- function expandRefPath( path, art, scope, extraInfo ) {
433
+ function resolvePath( path, art, scope, extraInfo ) {
255
434
  /** @type {{idx, art?, env?}[]} */
256
435
  const links = path.map( (_v, idx) => ({ idx }) );
257
436
  // TODO: backends should be changed to enable uncommenting:
@@ -302,59 +481,63 @@ function csnRefs( csn ) {
302
481
  if (query.ref) { // ref in from
303
482
  // console.log('SQ:',query,cache.get(query))
304
483
  const as = query.as || implicitAs( query.ref );
305
- getCache( fromSelect, 'aliases' )[as] = fromRef( query );
484
+ getCache( fromSelect, '$aliases' )[as] = fromRef( query );
306
485
  }
307
486
  else {
308
- const qenv = initQueryEnv( query !== main && query, parentQuery );
487
+ const qcache = getQueryCache( parentQuery );
488
+ if (query !== main)
489
+ cache.set( query, qcache );
490
+
309
491
  if (fromSelect)
310
- getCache( fromSelect, 'aliases' )[query.as] = qenv;
492
+ getCache( fromSelect, '$aliases' )[query.as] = qcache;
311
493
  const select = query.SELECT || query.projection;
312
494
  if (select) {
313
- cache.set( select, qenv ); // query and query.SELECT have the same cache qenv
314
- qenv._select = select;
315
- all.push( qenv );
495
+ cache.set( select, qcache ); // query and query.SELECT have the same cache qcache
496
+ qcache._select = select;
497
+ all.push( qcache );
316
498
  }
317
499
  }
318
500
  } );
319
- all.forEach( function initElements( qenv, index ) {
320
- qenv.$queryNumber = index + 1;
321
- qenv.elements = (index ? qenv._select : main).elements;
322
- const columns = qenv._select.columns;
323
- if (qenv.elements && columns)
324
- columns.map( c => initColumnElement( c, qenv ) );
501
+ all.forEach( function initElements( qcache, index ) {
502
+ qcache.$queryNumber = index + 1;
503
+ qcache.elements = (index ? qcache._select : main).elements;
504
+ const columns = qcache._select.columns;
505
+ if (qcache.elements && columns)
506
+ columns.map( c => initColumnElement( c, qcache ) );
325
507
  } );
326
508
  return all;
327
509
  }
328
510
 
329
- function initQueryEnv( query, parentQuery ) {
330
- let qenv; // = query && cache.get( query )
331
- // if (qenv)
332
- // return qenv;
333
- const penv = parentQuery && parentQuery.SET && cache.get( parentQuery );
334
- if (penv && !penv._select) { // SELECT not yet reached
335
- qenv = penv; // for leading query
336
- }
337
- else {
338
- qenv = { aliases: Object.create(null) };
339
- if (parentQuery)
340
- qenv.$next = cache.get( parentQuery );
341
- }
342
- if (query)
343
- cache.set( query, qenv );
344
- return qenv;
511
+ /**
512
+ * Return the cache object for a new query.
513
+ * Might re-use cache object with the `parentQuery`, or use `parentQuery`
514
+ * for link to next lexical environment.
515
+ */
516
+
517
+ function getQueryCache( parentQuery ) {
518
+ if (!parentQuery)
519
+ return { $aliases: Object.create(null) };
520
+ const pcache = cache.get( parentQuery );
521
+ if (!parentQuery.SET) // SELECT / projection: real sub query
522
+ return { $aliases: Object.create(null), $next: pcache };
523
+ // the parent query is a SET: that is not a sub query
524
+ // (works, as no sub queries are allowed in ORDER BY)
525
+ return (!pcache._select) // no leading query yet
526
+ ? pcache // share cache with parent query
527
+ : { $aliases: Object.create(null), $next: pcache.$next };
345
528
  }
346
529
 
347
- function initColumnElement( col, parentElementOrQueryEnv ) {
530
+ function initColumnElement( col, parentElementOrQueryCache ) {
348
531
  if (col === '*')
349
532
  return;
350
533
  if (col.inline) {
351
- col.inline.map( c => initColumnElement( c, parentElementOrQueryEnv ) );
534
+ col.inline.map( c => initColumnElement( c, parentElementOrQueryCache ) );
352
535
  return;
353
536
  }
354
537
  setCache( col, '_parent', // not set for query (has property _select)
355
- !parentElementOrQueryEnv._select && parentElementOrQueryEnv );
538
+ !parentElementOrQueryCache._select && parentElementOrQueryCache );
356
539
  const as = col.as || col.func || implicitAs( col.ref );
357
- let type = parentElementOrQueryEnv;
540
+ let type = parentElementOrQueryCache;
358
541
  while (type.items)
359
542
  type = type.items;
360
543
  const elem = setCache( col, '_element', type.elements[as] );
@@ -398,6 +581,7 @@ function csnRefs( csn ) {
398
581
 
399
582
  // Return value of a query SELECT for the query node, or the main artifact,
400
583
  // i.e. a value with an `elements` property.
584
+ // TODO: only used in forHanaNew - move somewhere else
401
585
  /**
402
586
  * @param {CSN.Query} query node (object with SET or SELECT property)
403
587
  * @param {CSN.Definition} main
@@ -423,15 +607,16 @@ function queryOrMain( query, main ) {
423
607
  *
424
608
  * @param {CSN.Query} query
425
609
  * @param {CSN.QuerySelect} fromSelect
610
+ * @param {CSN.Query} parentQuery
426
611
  * @param {(query: CSN.Query&CSN.QueryFrom, select: CSN.QuerySelectEnriched) => void} callback
427
612
  */
428
613
  function traverseQuery( query, fromSelect, parentQuery, callback ) {
429
- if (query.SELECT || query.projection) {
614
+ const select = query.SELECT || query.projection;
615
+ if (select) {
430
616
  callback( query, fromSelect, parentQuery );
431
- const select = query.SELECT || query.projection;
432
617
  traverseFrom( select.from, select, parentQuery, callback );
433
618
  for (const prop of [ 'columns', 'where', 'having' ]) {
434
- // all properties which could have sub queries
619
+ // all properties which can have sub queries (`join-on` also can)
435
620
  const expr = select[prop];
436
621
  if (expr)
437
622
  expr.forEach( q => traverseExpr( q, query, callback ) );
@@ -440,12 +625,8 @@ function traverseQuery( query, fromSelect, parentQuery, callback ) {
440
625
  else if (query.SET) {
441
626
  callback( query, fromSelect, parentQuery );
442
627
  const { args } = query.SET;
443
- for (const q of args || []) {
444
- if (q === args[0]) // leading query
445
- traverseQuery( q, fromSelect, query, callback );
446
- else
447
- traverseQuery( q, null, query, callback );
448
- }
628
+ for (const q of args || [])
629
+ traverseQuery( q, null, query, callback );
449
630
  }
450
631
  }
451
632
 
@@ -502,12 +683,13 @@ function analyseCsnPath( csnPath, csn, resolve ) {
502
683
  let obj = csn;
503
684
  let parent = null;
504
685
  let query = null;
505
- let scope = null;
686
+ let refCtx = null;
506
687
  let art = null;
507
688
  /** @type {boolean|string|number} */
508
689
  let isName = false;
509
- let refObj = null;
690
+ let baseRef = null;
510
691
  let baseEnv = null;
692
+ let main = csn.definitions[csnPath[1]];
511
693
 
512
694
  for (let index = 0; index < csnPath.length; index++) {
513
695
  const prop = csnPath[index];
@@ -520,8 +702,12 @@ function analyseCsnPath( csnPath, csn, resolve ) {
520
702
  isName = false;
521
703
  }
522
704
  else if (artifactProperties.includes( String(prop) )) {
705
+ if (refCtx === 'target' || refCtx === 'targetAspect') { // with 'elements'
706
+ main = art = obj; // $self refers to the anonymous aspect
707
+ parent = null;
708
+ }
523
709
  isName = prop;
524
- scope = prop;
710
+ refCtx = prop;
525
711
  }
526
712
  else if (prop === 'items' || prop === 'returns') {
527
713
  art = obj[prop];
@@ -532,41 +718,42 @@ function analyseCsnPath( csnPath, csn, resolve ) {
532
718
  else if (prop === 'SELECT' || prop === 'SET' || prop === 'projection') {
533
719
  query = obj;
534
720
  parent = null;
535
- scope = prop;
721
+ baseEnv = null;
722
+ refCtx = prop;
536
723
  }
537
- else if (prop === 'where' && scope === 'ref') {
724
+ else if (prop === 'where' && refCtx === 'ref') {
538
725
  if (resolve)
539
- baseEnv = resolve.refWhere( refObj, obj, parent, query, scope, baseEnv,
540
- csn.definitions[csnPath[1]] );
541
- scope = 'ref-target';
726
+ baseEnv = resolve.ref_where( obj, baseRef, refCtx, csn.definitions[csnPath[1]],
727
+ query, parent, baseEnv );
728
+ refCtx = 'ref_where';
542
729
  }
543
730
  else if (prop === 'expand' || prop === 'inline') {
544
731
  if (obj.ref) {
545
732
  if (resolve)
546
- baseEnv = resolve.expand( obj, parent, query, scope, baseEnv,
547
- csn.definitions[csnPath[1]] );
548
- scope = prop;
733
+ baseEnv = resolve.expandInline( obj, refCtx, csn.definitions[csnPath[1]],
734
+ query, parent, baseEnv );
735
+ refCtx = prop;
549
736
  }
550
737
  if (prop === 'expand')
551
738
  isName = prop;
552
739
  }
553
740
  else if (prop === 'on') {
554
- if (scope === 'from')
555
- scope = 'join-on';
556
- else if (scope === 'mixin')
557
- scope = 'mixin-on';
741
+ if (refCtx === 'from')
742
+ refCtx = 'join_on';
743
+ else if (refCtx === 'mixin')
744
+ refCtx = 'mixin_on';
558
745
  else
559
- scope = 'on'; // will use query elements with REDIRECTED TO
746
+ refCtx = 'on'; // will use query elements with REDIRECTED TO
560
747
  }
561
748
  else if (prop === 'ref') {
562
- refObj = obj;
563
- scope = prop;
749
+ baseRef = obj; // needs to be inspected for filter conditions
750
+ refCtx = prop;
564
751
  }
565
752
  else if (prop === 'orderBy') {
566
- scope = (query.SET ? 'orderBy-set' : 'orderBy');
753
+ refCtx = (query.SET ? 'orderBy_set' : 'orderBy');
567
754
  }
568
755
  else if (prop !== 'xpr') {
569
- scope = prop;
756
+ refCtx = prop;
570
757
  }
571
758
 
572
759
  obj = obj[prop];
@@ -574,10 +761,10 @@ function analyseCsnPath( csnPath, csn, resolve ) {
574
761
  // For the semantic location, use current object as best guess
575
762
  break;
576
763
  }
577
- // console.log( 'CPATH:', csnPath, scope, obj, parent.$location );
764
+ // console.log( 'CPATH:', csnPath, refCtx, obj, parent.$location );
578
765
  if (!resolve)
579
766
  return { query }; // for constructSemanticLocationFromCsnPath
580
- return resolve( obj, parent, query, scope, baseEnv, csn.definitions[csnPath[1]] );
767
+ return resolve( obj, refCtx, main, query, parent, baseEnv );
581
768
  }
582
769
 
583
770
  module.exports = {