@sap/cds-compiler 2.5.2 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/CHANGELOG.md +235 -9
  2. package/bin/cdsc.js +44 -27
  3. package/bin/cdsse.js +1 -0
  4. package/doc/CHANGELOG_BETA.md +37 -3
  5. package/lib/api/.eslintrc.json +2 -0
  6. package/lib/api/main.js +37 -123
  7. package/lib/api/options.js +27 -15
  8. package/lib/api/validate.js +34 -9
  9. package/lib/backends.js +9 -89
  10. package/lib/base/dictionaries.js +2 -1
  11. package/lib/base/keywords.js +32 -2
  12. package/lib/base/message-registry.js +73 -11
  13. package/lib/base/messages.js +86 -30
  14. package/lib/base/model.js +6 -6
  15. package/lib/base/optionProcessorHelper.js +56 -22
  16. package/lib/checks/defaultValues.js +27 -2
  17. package/lib/checks/elements.js +1 -6
  18. package/lib/checks/foreignKeys.js +0 -6
  19. package/lib/checks/managedWithoutKeys.js +17 -0
  20. package/lib/checks/nonexpandableStructured.js +38 -0
  21. package/lib/checks/onConditions.js +9 -45
  22. package/lib/checks/queryNoDbArtifacts.js +25 -7
  23. package/lib/checks/selectItems.js +29 -2
  24. package/lib/checks/types.js +26 -2
  25. package/lib/checks/unknownMagic.js +41 -0
  26. package/lib/checks/utils.js +61 -0
  27. package/lib/checks/validator.js +60 -7
  28. package/lib/compiler/assert-consistency.js +23 -7
  29. package/lib/compiler/base.js +65 -0
  30. package/lib/compiler/builtins.js +30 -1
  31. package/lib/compiler/checks.js +8 -5
  32. package/lib/compiler/definer.js +157 -133
  33. package/lib/compiler/index.js +89 -31
  34. package/lib/compiler/propagator.js +5 -2
  35. package/lib/compiler/resolver.js +375 -185
  36. package/lib/compiler/shared.js +49 -202
  37. package/lib/compiler/utils.js +173 -0
  38. package/lib/edm/annotations/genericTranslation.js +183 -187
  39. package/lib/edm/csn2edm.js +104 -108
  40. package/lib/edm/edm.js +18 -21
  41. package/lib/edm/edmPreprocessor.js +388 -146
  42. package/lib/edm/edmUtils.js +104 -34
  43. package/lib/gen/Dictionary.json +22 -0
  44. package/lib/gen/language.checksum +1 -1
  45. package/lib/gen/language.interp +28 -1
  46. package/lib/gen/language.tokens +79 -69
  47. package/lib/gen/languageLexer.interp +28 -1
  48. package/lib/gen/languageLexer.js +879 -805
  49. package/lib/gen/languageLexer.tokens +71 -62
  50. package/lib/gen/languageParser.js +5330 -4300
  51. package/lib/json/from-csn.js +110 -52
  52. package/lib/json/to-csn.js +434 -120
  53. package/lib/language/antlrParser.js +15 -3
  54. package/lib/language/errorStrategy.js +1 -0
  55. package/lib/language/genericAntlrParser.js +93 -26
  56. package/lib/language/language.g4 +172 -31
  57. package/lib/main.d.ts +216 -19
  58. package/lib/main.js +32 -7
  59. package/lib/model/api.js +78 -0
  60. package/lib/model/csnRefs.js +413 -149
  61. package/lib/model/csnUtils.js +286 -75
  62. package/lib/model/enrichCsn.js +50 -6
  63. package/lib/model/revealInternalProperties.js +22 -5
  64. package/lib/modelCompare/compare.js +39 -21
  65. package/lib/optionProcessor.js +35 -18
  66. package/lib/render/.eslintrc.json +4 -1
  67. package/lib/render/DuplicateChecker.js +9 -6
  68. package/lib/render/toCdl.js +121 -36
  69. package/lib/render/toHdbcds.js +148 -98
  70. package/lib/render/toSql.js +114 -43
  71. package/lib/render/utils/common.js +8 -13
  72. package/lib/render/utils/sql.js +3 -3
  73. package/lib/sql-identifier.js +6 -1
  74. package/lib/transform/db/assertUnique.js +5 -6
  75. package/lib/transform/db/constraints.js +281 -106
  76. package/lib/transform/db/draft.js +11 -8
  77. package/lib/transform/db/expansion.js +584 -0
  78. package/lib/transform/db/flattening.js +341 -0
  79. package/lib/transform/db/groupByOrderBy.js +2 -2
  80. package/lib/transform/db/transformExists.js +345 -65
  81. package/lib/transform/db/views.js +438 -0
  82. package/lib/transform/forHanaNew.js +131 -793
  83. package/lib/transform/forOdataNew.js +30 -24
  84. package/lib/transform/localized.js +39 -10
  85. package/lib/transform/odata/attachPath.js +19 -4
  86. package/lib/transform/odata/generateForeignKeyElements.js +11 -10
  87. package/lib/transform/odata/referenceFlattener.js +60 -39
  88. package/lib/transform/odata/sortByAssociationDependency.js +2 -2
  89. package/lib/transform/odata/structuralPath.js +72 -0
  90. package/lib/transform/odata/structureFlattener.js +19 -18
  91. package/lib/transform/odata/typesExposure.js +22 -12
  92. package/lib/transform/transformUtilsNew.js +144 -78
  93. package/lib/transform/translateAssocsToJoins.js +22 -27
  94. package/lib/transform/universalCsnEnricher.js +67 -0
  95. package/lib/utils/file.js +5 -14
  96. package/lib/utils/moduleResolve.js +6 -8
  97. package/lib/utils/term.js +65 -42
  98. package/lib/utils/timetrace.js +48 -26
  99. package/package.json +1 -1
  100. package/lib/json/walker.js +0 -26
  101. package/lib/transform/sqlite +0 -0
  102. 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,16 +225,19 @@ 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)
91
- return setCache( art, '_effectiveType', art );
92
228
 
93
229
  const chain = [];
94
- while (getCache( art, '_effectiveType' ) === undefined && art.type &&
95
- !art.elements && !art.target && !art.targetAspect && !art.enum) {
230
+ let origin;
231
+ while (getCache( art, '_effectiveType' ) === undefined &&
232
+ (origin = cached( art, '_origin', getOriginRaw )) &&
233
+ !art.elements && !art.target && !art.targetAspect && !art.enum && !art.items) {
96
234
  chain.push( art );
97
235
  setCache( art, '_effectiveType', 0 ); // initial setting in case of cycles
98
- art = artifactRef( art.type, BUILTIN_TYPE );
236
+ art = origin;
99
237
  }
238
+ if (!chain.length)
239
+ return setCache( art, '_effectiveType', art );
240
+
100
241
  if (getCache( art, '_effectiveType' ) === 0)
101
242
  throw new Error( 'Circular type reference');
102
243
  const type = getCache( art, '_effectiveType' ) || art;
@@ -112,10 +253,16 @@ function csnRefs( csn ) {
112
253
  // here, we do not care whether it is semantically ok to navigate into sub
113
254
  // elements of array items (that is the task of the core compiler /
114
255
  // semantic check)
115
- while (type.items)
116
- type = type.items;
256
+ while (type.items) {
257
+ cached( type, '$origin', _a => setImplicitOrigin( type, origin ) );
258
+ type = effectiveType( type.items );
259
+ }
117
260
  // cannot navigate along targetAspect!
118
- return (type.target) ? csn.definitions[type.target] : type;
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;
119
266
  }
120
267
 
121
268
  /**
@@ -140,11 +287,104 @@ function csnRefs( csn ) {
140
287
  function artifactPathRef( ref ) {
141
288
  const [ head, ...tail ] = ref.ref;
142
289
  let art = csn.definitions[pathId( head )];
143
- for (const elem of tail)
144
- art = navigationEnv( art ).elements[pathId( elem )];
290
+ for (const elem of tail) {
291
+ const env = navigationEnv( art );
292
+ art = env.elements[pathId( elem )];
293
+ }
145
294
  return art;
146
295
  }
147
296
 
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;
302
+ }
303
+
304
+ function getOriginRaw( art ) {
305
+ if (art.type) // TODO: make robust against "linked" = only direct
306
+ 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;
320
+ }
321
+ return origin;
322
+ }
323
+
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;
376
+ }
377
+
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 );
385
+ }
386
+ }
387
+
148
388
  /**
149
389
  * Return the entity we select from
150
390
  *
@@ -158,100 +398,114 @@ function csnRefs( csn ) {
158
398
  /**
159
399
  * @param {CSN.Path} csnPath
160
400
  *
401
+ * - return value `art`: the “resulting” CSN of the reference
402
+ *
403
+ * - return value `links`: array of { art, env } in length of ref.path where
404
+ * art = the definition or element reached by the ref path so far
405
+ * env = the “navigation environment” provided by `art`
406
+ * (not set for last item, except for `from` reference or with filter)
407
+ *
161
408
  * - return value `scope`
162
409
  * global: first item is name of definition
163
410
  * param: first item is parameter of definition (with param: true)
164
411
  * parent: first item is elem of parent (definition or outer elem)
165
412
  * target: first item is elem in target (for keys of assocs)
166
413
  * $magic: magic variable (path starts with $magic, see also $self)
414
+ * $self: first item is $self or $projection
167
415
  * // now values only in queries:
168
416
  * mixin: first item is mixin
169
417
  * alias: first item is table alias
170
- * $self: first item is $self or $projection
171
- * source: first item is element in a query source
418
+ * source: first item is element in a source of the current query
172
419
  * query: first item is element of current query
173
420
  * ref-target: first item is element of target of outer ref item
174
421
  * (used for filter condition)
175
- * expand: ref is "path continuation" of an EXPAND
176
- * inline: ref is "path continuation" of an INLINE
422
+ * expand: ref is "path continuation" of a ref with EXPAND
423
+ * inline: ref is "path continuation" of a ref with INLINE
424
+ *
425
+ * - return value `$env` is set with certain values of `scope`:
426
+ * with 'alias': the query number _n_ (the _n_th SELECT)
427
+ * with 'source': the table alias name for the source entity
177
428
  */
178
429
  function inspectRef( csnPath ) {
179
430
  return analyseCsnPath( csnPath, csn, resolveRef );
180
431
  }
181
432
 
182
- function resolveRef( obj, parent, query, scope, baseEnv, main ) {
183
- const path = (typeof obj === 'string') ? [ obj ] : obj.ref;
433
+ function resolveRef( ref, refCtx, main, query, parent, baseEnv ) {
434
+ const path = (typeof ref === 'string') ? [ ref ] : ref.ref;
184
435
  if (!Array.isArray( path ))
185
- throw new Error( 'Value references must look like {ref:[...]}' );
436
+ throw new Error( 'References must look like {ref:[...]}' );
186
437
 
187
438
  const head = pathId( path[0] );
188
- if (obj.param)
189
- return expandRefPath( path, main.params[head], 'param' );
439
+ if (ref.param)
440
+ return resolvePath( path, main.params[head], 'param' );
190
441
 
191
- const spec = scopeSpecs[scope] || {};
192
- if (spec.global || obj.global)
193
- return expandRefPath( path, csn.definitions[head], 'global', scope === 'from' );
442
+ const semantics = referenceSemantics[refCtx] || {};
443
+ if (semantics.dynamic === 'global' || ref.global)
444
+ return resolvePath( path, csn.definitions[head], 'global', refCtx === 'from' );
194
445
 
446
+ const origin = cached( main, '_origin', getOriginRaw )
447
+ if (origin)
448
+ cached( main, '$origin', _a => setImplicitOrigin( main, origin ) );
195
449
  cached( main, '$queries', allQueries );
196
- let qenv = query && cache.get( query.projection || query );
450
+ let qcache = query && cache.get( query.projection || query );
197
451
  // BACKEND ISSUE: you cannot call csnRefs(), inspect some refs, change the
198
452
  // CSN and again inspect some refs without calling csnRefs() before!
199
453
  // WORKAROUND: if no cached query, a backend has changed the CSN - re-eval cache
200
- if (query && !qenv) {
454
+ if (query && !qcache) {
201
455
  setCache( main, '$queries', allQueries( main ) );
202
- qenv = cache.get( query.projection || query );
456
+ qcache = cache.get( query.projection || query );
203
457
  }
204
458
  // 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];
459
+ if (semantics.lexical !== false) {
460
+ 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];
210
464
  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;
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;
216
470
  }
217
471
  if (head.charAt(0) === '$') {
218
472
  if (head !== '$self' && head !== '$projection')
219
473
  return { scope: '$magic' };
220
- const self = qenv && qenv.$queryNumber > 1 ? qenv._select : main;
221
- return expandRefPath( path, self, '$self' );
474
+ const self = qcache && qcache.$queryNumber > 1 ? qcache._select : main;
475
+ return resolvePath( path, self, '$self' );
222
476
  }
223
477
  }
224
478
  // now the dynamic environment: ------------------------------------------
225
- if (spec.dynamic === 'target') { // ref in keys
479
+ if (semantics.dynamic === 'target') { // ref in keys
226
480
  // not selecting the corresponding element for a select column works,
227
481
  // because explicit keys can only be provided with explicit redirection
228
482
  // target
229
483
  const target = csn.definitions[parent.target || parent.cast.target];
230
- return expandRefPath( path, target.elements[head], 'target' );
484
+ return resolvePath( path, target.elements[head], 'target' );
231
485
  }
232
486
  if (baseEnv) // ref-target (filter condition), expand, inline
233
- return expandRefPath( path, baseEnv.elements[head], scope );
487
+ return resolvePath( path, baseEnv.elements[head], semantics.dynamic );
234
488
  if (!query) // outside queries - TODO: items?
235
- return expandRefPath( path, parent.elements[head], 'parent' );
489
+ return resolvePath( path, parent.elements[head], 'parent' );
236
490
 
237
- if (spec.dynamic === 'query')
491
+ if (semantics.dynamic === 'query')
238
492
  // 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];
493
+ return resolvePath( path, qcache.elements[head], 'query' );
494
+ for (const name in qcache.$aliases) {
495
+ const found = qcache.$aliases[name].elements[head];
242
496
  if (found)
243
- return expandRefPath( path, found, 'source', name )
497
+ return resolvePath( path, found, 'source', name )
244
498
  }
245
- // console.log(query.SELECT,qenv,qenv.$next,main)
246
- throw new Error ( `Path item ${ 0 }=${ head } refers to nothing, scope: ${ scope }` );
499
+ // console.log(query.SELECT,qcache,qcache.$next,main)
500
+ throw new Error ( `Path item ${ 0 }=${ head } refers to nothing, refCtx: ${ refCtx }` );
247
501
  }
248
502
 
249
503
  /**
250
504
  * @param {CSN.Path} path
251
505
  * @param {CSN.Artifact} art
252
- * @param {string | null} [scope]
506
+ * @param {string} [scope]
253
507
  */
254
- function expandRefPath( path, art, scope, extraInfo ) {
508
+ function resolvePath( path, art, scope, extraInfo ) {
255
509
  /** @type {{idx, art?, env?}[]} */
256
510
  const links = path.map( (_v, idx) => ({ idx }) );
257
511
  // TODO: backends should be changed to enable uncommenting:
@@ -302,59 +556,64 @@ function csnRefs( csn ) {
302
556
  if (query.ref) { // ref in from
303
557
  // console.log('SQ:',query,cache.get(query))
304
558
  const as = query.as || implicitAs( query.ref );
305
- getCache( fromSelect, 'aliases' )[as] = fromRef( query );
559
+ const _ref = fromRef( query );
560
+ getCache( fromSelect, '$aliases' )[as] = { _ref, elements: _ref.elements };
306
561
  }
307
562
  else {
308
- const qenv = initQueryEnv( query !== main && query, parentQuery );
563
+ const qcache = getQueryCache( parentQuery );
564
+ if (query !== main)
565
+ cache.set( query, qcache );
566
+
309
567
  if (fromSelect)
310
- getCache( fromSelect, 'aliases' )[query.as] = qenv;
568
+ getCache( fromSelect, '$aliases' )[query.as] = qcache;
311
569
  const select = query.SELECT || query.projection;
312
570
  if (select) {
313
- cache.set( select, qenv ); // query and query.SELECT have the same cache qenv
314
- qenv._select = select;
315
- all.push( qenv );
571
+ cache.set( select, qcache ); // query and query.SELECT have the same cache qcache
572
+ qcache._select = select;
573
+ all.push( qcache );
316
574
  }
317
575
  }
318
576
  } );
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 ) );
577
+ all.forEach( function initElements( qcache, index ) {
578
+ qcache.$queryNumber = index + 1;
579
+ qcache.elements = (index ? qcache._select : main).elements;
580
+ const columns = qcache._select.columns;
581
+ if (qcache.elements && columns)
582
+ columns.map( c => initColumnElement( c, qcache ) );
325
583
  } );
326
584
  return all;
327
585
  }
328
586
 
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;
587
+ /**
588
+ * Return the cache object for a new query.
589
+ * Might re-use cache object with the `parentQuery`, or use `parentQuery`
590
+ * for link to next lexical environment.
591
+ */
592
+
593
+ function getQueryCache( parentQuery ) {
594
+ if (!parentQuery)
595
+ return { $aliases: Object.create(null) };
596
+ const pcache = cache.get( parentQuery );
597
+ if (!parentQuery.SET) // SELECT / projection: real sub query
598
+ return { $aliases: Object.create(null), $next: pcache };
599
+ // the parent query is a SET: that is not a sub query
600
+ // (works, as no sub queries are allowed in ORDER BY)
601
+ return (!pcache._select) // no leading query yet
602
+ ? pcache // share cache with parent query
603
+ : { $aliases: Object.create(null), $next: pcache.$next };
345
604
  }
346
605
 
347
- function initColumnElement( col, parentElementOrQueryEnv ) {
606
+ function initColumnElement( col, parentElementOrQueryCache ) {
348
607
  if (col === '*')
349
608
  return;
350
609
  if (col.inline) {
351
- col.inline.map( c => initColumnElement( c, parentElementOrQueryEnv ) );
610
+ col.inline.map( c => initColumnElement( c, parentElementOrQueryCache ) );
352
611
  return;
353
612
  }
354
613
  setCache( col, '_parent', // not set for query (has property _select)
355
- !parentElementOrQueryEnv._select && parentElementOrQueryEnv );
614
+ !parentElementOrQueryCache._select && parentElementOrQueryCache );
356
615
  const as = col.as || col.func || implicitAs( col.ref );
357
- let type = parentElementOrQueryEnv;
616
+ let type = parentElementOrQueryCache;
358
617
  while (type.items)
359
618
  type = type.items;
360
619
  const elem = setCache( col, '_element', type.elements[as] );
@@ -398,6 +657,7 @@ function csnRefs( csn ) {
398
657
 
399
658
  // Return value of a query SELECT for the query node, or the main artifact,
400
659
  // i.e. a value with an `elements` property.
660
+ // TODO: only used in forHanaNew - move somewhere else
401
661
  /**
402
662
  * @param {CSN.Query} query node (object with SET or SELECT property)
403
663
  * @param {CSN.Definition} main
@@ -423,15 +683,16 @@ function queryOrMain( query, main ) {
423
683
  *
424
684
  * @param {CSN.Query} query
425
685
  * @param {CSN.QuerySelect} fromSelect
686
+ * @param {CSN.Query} parentQuery
426
687
  * @param {(query: CSN.Query&CSN.QueryFrom, select: CSN.QuerySelectEnriched) => void} callback
427
688
  */
428
689
  function traverseQuery( query, fromSelect, parentQuery, callback ) {
429
- if (query.SELECT || query.projection) {
690
+ const select = query.SELECT || query.projection;
691
+ if (select) {
430
692
  callback( query, fromSelect, parentQuery );
431
- const select = query.SELECT || query.projection;
432
693
  traverseFrom( select.from, select, parentQuery, callback );
433
694
  for (const prop of [ 'columns', 'where', 'having' ]) {
434
- // all properties which could have sub queries
695
+ // all properties which can have sub queries (`join-on` also can)
435
696
  const expr = select[prop];
436
697
  if (expr)
437
698
  expr.forEach( q => traverseExpr( q, query, callback ) );
@@ -440,19 +701,16 @@ function traverseQuery( query, fromSelect, parentQuery, callback ) {
440
701
  else if (query.SET) {
441
702
  callback( query, fromSelect, parentQuery );
442
703
  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
- }
704
+ for (const q of args || [])
705
+ traverseQuery( q, null, query, callback );
449
706
  }
450
707
  }
451
708
 
452
709
  /**
453
710
  * @param {CSN.QueryFrom} from
454
- * @param {CSN.QuerySelect} select
455
- * @param {(from: CSN.QueryFrom, select: CSN.QuerySelect) => void} callback
711
+ * @param {CSN.QuerySelect} fromSelect
712
+ * @param {CSN.Query} parentQuery
713
+ * @param {(from: CSN.QueryFrom, select: CSN.QuerySelect, parentQuery: CSN.Query) => void} callback
456
714
  */
457
715
  function traverseFrom( from, fromSelect, parentQuery, callback ) {
458
716
  if (from.ref) {
@@ -502,12 +760,13 @@ function analyseCsnPath( csnPath, csn, resolve ) {
502
760
  let obj = csn;
503
761
  let parent = null;
504
762
  let query = null;
505
- let scope = null;
763
+ let refCtx = null;
506
764
  let art = null;
507
765
  /** @type {boolean|string|number} */
508
766
  let isName = false;
509
- let refObj = null;
767
+ let baseRef = null;
510
768
  let baseEnv = null;
769
+ let main = csn.definitions[csnPath[1]];
511
770
 
512
771
  for (let index = 0; index < csnPath.length; index++) {
513
772
  const prop = csnPath[index];
@@ -520,8 +779,12 @@ function analyseCsnPath( csnPath, csn, resolve ) {
520
779
  isName = false;
521
780
  }
522
781
  else if (artifactProperties.includes( String(prop) )) {
782
+ if (refCtx === 'target' || refCtx === 'targetAspect') { // with 'elements'
783
+ main = art = obj; // $self refers to the anonymous aspect
784
+ parent = null;
785
+ }
523
786
  isName = prop;
524
- scope = prop;
787
+ refCtx = prop;
525
788
  }
526
789
  else if (prop === 'items' || prop === 'returns') {
527
790
  art = obj[prop];
@@ -532,41 +795,42 @@ function analyseCsnPath( csnPath, csn, resolve ) {
532
795
  else if (prop === 'SELECT' || prop === 'SET' || prop === 'projection') {
533
796
  query = obj;
534
797
  parent = null;
535
- scope = prop;
798
+ baseEnv = null;
799
+ refCtx = prop;
536
800
  }
537
- else if (prop === 'where' && scope === 'ref') {
801
+ else if (prop === 'where' && refCtx === 'ref') {
538
802
  if (resolve)
539
- baseEnv = resolve.refWhere( refObj, obj, parent, query, scope, baseEnv,
540
- csn.definitions[csnPath[1]] );
541
- scope = 'ref-target';
803
+ baseEnv = resolve.ref_where( obj, baseRef, refCtx, csn.definitions[csnPath[1]],
804
+ query, parent, baseEnv );
805
+ refCtx = 'ref_where';
542
806
  }
543
807
  else if (prop === 'expand' || prop === 'inline') {
544
808
  if (obj.ref) {
545
809
  if (resolve)
546
- baseEnv = resolve.expand( obj, parent, query, scope, baseEnv,
547
- csn.definitions[csnPath[1]] );
548
- scope = prop;
810
+ baseEnv = resolve.expandInline( obj, refCtx, csn.definitions[csnPath[1]],
811
+ query, parent, baseEnv );
812
+ refCtx = prop;
549
813
  }
550
814
  if (prop === 'expand')
551
815
  isName = prop;
552
816
  }
553
817
  else if (prop === 'on') {
554
- if (scope === 'from')
555
- scope = 'join-on';
556
- else if (scope === 'mixin')
557
- scope = 'mixin-on';
818
+ if (refCtx === 'from')
819
+ refCtx = 'join_on';
820
+ else if (refCtx === 'mixin')
821
+ refCtx = 'mixin_on';
558
822
  else
559
- scope = 'on'; // will use query elements with REDIRECTED TO
823
+ refCtx = 'on'; // will use query elements with REDIRECTED TO
560
824
  }
561
825
  else if (prop === 'ref') {
562
- refObj = obj;
563
- scope = prop;
826
+ baseRef = obj; // needs to be inspected for filter conditions
827
+ refCtx = prop;
564
828
  }
565
829
  else if (prop === 'orderBy') {
566
- scope = (query.SET ? 'orderBy-set' : 'orderBy');
830
+ refCtx = (query.SET ? 'orderBy_set' : 'orderBy');
567
831
  }
568
832
  else if (prop !== 'xpr') {
569
- scope = prop;
833
+ refCtx = prop;
570
834
  }
571
835
 
572
836
  obj = obj[prop];
@@ -574,10 +838,10 @@ function analyseCsnPath( csnPath, csn, resolve ) {
574
838
  // For the semantic location, use current object as best guess
575
839
  break;
576
840
  }
577
- // console.log( 'CPATH:', csnPath, scope, obj, parent.$location );
841
+ // console.log( 'CPATH:', csnPath, refCtx, obj, parent.$location );
578
842
  if (!resolve)
579
843
  return { query }; // for constructSemanticLocationFromCsnPath
580
- return resolve( obj, parent, query, scope, baseEnv, csn.definitions[csnPath[1]] );
844
+ return resolve( obj, refCtx, main, query, parent, baseEnv );
581
845
  }
582
846
 
583
847
  module.exports = {