@sap/cds-compiler 2.4.4 → 2.10.2

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 (106) hide show
  1. package/CHANGELOG.md +241 -1
  2. package/bin/.eslintrc.json +17 -0
  3. package/bin/cds_update_identifiers.js +8 -7
  4. package/bin/cdsc.js +180 -132
  5. package/bin/cdshi.js +18 -11
  6. package/bin/cdsse.js +38 -32
  7. package/bin/cdsv2m.js +8 -7
  8. package/doc/CHANGELOG_BETA.md +36 -1
  9. package/lib/api/main.js +81 -100
  10. package/lib/api/options.js +17 -11
  11. package/lib/api/validate.js +12 -8
  12. package/lib/backends.js +0 -81
  13. package/lib/base/keywords.js +32 -2
  14. package/lib/base/location.js +2 -2
  15. package/lib/base/message-registry.js +66 -4
  16. package/lib/base/messages.js +84 -27
  17. package/lib/base/model.js +2 -61
  18. package/lib/checks/arrayOfs.js +0 -1
  19. package/lib/checks/defaultValues.js +27 -2
  20. package/lib/checks/elements.js +1 -6
  21. package/lib/checks/enricher.js +8 -2
  22. package/lib/checks/foreignKeys.js +0 -6
  23. package/lib/checks/managedWithoutKeys.js +17 -0
  24. package/lib/checks/nonexpandableStructured.js +38 -0
  25. package/lib/checks/onConditions.js +9 -45
  26. package/lib/checks/queryNoDbArtifacts.js +27 -9
  27. package/lib/checks/selectItems.js +25 -2
  28. package/lib/checks/types.js +26 -2
  29. package/lib/checks/unknownMagic.js +38 -0
  30. package/lib/checks/utils.js +61 -0
  31. package/lib/checks/validator.js +66 -13
  32. package/lib/compiler/assert-consistency.js +24 -12
  33. package/lib/compiler/builtins.js +2 -0
  34. package/lib/compiler/checks.js +6 -4
  35. package/lib/compiler/definer.js +101 -39
  36. package/lib/compiler/index.js +88 -59
  37. package/lib/compiler/resolver.js +455 -209
  38. package/lib/compiler/shared.js +57 -33
  39. package/lib/edm/annotations/genericTranslation.js +183 -187
  40. package/lib/edm/csn2edm.js +128 -99
  41. package/lib/edm/edm.js +18 -21
  42. package/lib/edm/edmPreprocessor.js +361 -127
  43. package/lib/edm/edmUtils.js +103 -33
  44. package/lib/gen/Dictionary.json +74 -28
  45. package/lib/gen/language.checksum +1 -1
  46. package/lib/gen/language.interp +18 -4
  47. package/lib/gen/language.tokens +124 -118
  48. package/lib/gen/languageLexer.interp +13 -1
  49. package/lib/gen/languageLexer.js +870 -839
  50. package/lib/gen/languageLexer.tokens +116 -111
  51. package/lib/gen/languageParser.js +5894 -5614
  52. package/lib/json/from-csn.js +152 -67
  53. package/lib/json/to-csn.js +334 -135
  54. package/lib/language/antlrParser.js +4 -3
  55. package/lib/language/errorStrategy.js +1 -0
  56. package/lib/language/genericAntlrParser.js +24 -14
  57. package/lib/language/language.g4 +188 -128
  58. package/lib/main.d.ts +435 -0
  59. package/lib/main.js +31 -7
  60. package/lib/model/api.js +78 -0
  61. package/lib/model/csnRefs.js +463 -187
  62. package/lib/model/csnUtils.js +280 -136
  63. package/lib/model/enrichCsn.js +75 -4
  64. package/lib/model/revealInternalProperties.js +2 -1
  65. package/lib/modelCompare/compare.js +70 -25
  66. package/lib/optionProcessor.js +13 -10
  67. package/lib/render/.eslintrc.json +4 -1
  68. package/lib/render/DuplicateChecker.js +8 -5
  69. package/lib/render/toCdl.js +123 -40
  70. package/lib/render/toHdbcds.js +156 -65
  71. package/lib/render/toSql.js +87 -11
  72. package/lib/render/utils/common.js +55 -9
  73. package/lib/render/utils/sql.js +3 -3
  74. package/lib/sql-identifier.js +6 -1
  75. package/lib/transform/{sql → db}/.eslintrc.json +0 -0
  76. package/lib/transform/{sql → db}/assertUnique.js +7 -8
  77. package/lib/transform/{sql → db}/constraints.js +35 -20
  78. package/lib/transform/db/draft.js +353 -0
  79. package/lib/transform/db/expansion.js +582 -0
  80. package/lib/transform/db/flattening.js +325 -0
  81. package/lib/transform/{sql → db}/groupByOrderBy.js +8 -16
  82. package/lib/transform/{sql → db}/helpers.js +0 -0
  83. package/lib/transform/{sql → db}/transformExists.js +256 -60
  84. package/lib/transform/forHanaNew.js +216 -765
  85. package/lib/transform/forOdataNew.js +60 -56
  86. package/lib/transform/localized.js +48 -26
  87. package/lib/transform/odata/attachPath.js +19 -4
  88. package/lib/transform/odata/expandStructKeysInAssociations.js +2 -2
  89. package/lib/transform/odata/generateForeignKeyElements.js +13 -12
  90. package/lib/transform/odata/referenceFlattener.js +60 -36
  91. package/lib/transform/odata/sortByAssociationDependency.js +4 -4
  92. package/lib/transform/odata/structuralPath.js +76 -0
  93. package/lib/transform/odata/structureFlattener.js +21 -22
  94. package/lib/transform/odata/toFinalBaseType.js +5 -5
  95. package/lib/transform/odata/typesExposure.js +27 -17
  96. package/lib/transform/odata/utils.js +2 -2
  97. package/lib/transform/transformUtilsNew.js +141 -77
  98. package/lib/transform/translateAssocsToJoins.js +17 -14
  99. package/lib/transform/universalCsnEnricher.js +67 -0
  100. package/lib/utils/file.js +0 -11
  101. package/lib/utils/moduleResolve.js +6 -8
  102. package/lib/utils/timetrace.js +6 -1
  103. package/package.json +2 -1
  104. package/lib/base/deepCopy.js +0 -66
  105. package/lib/json/walker.js +0 -26
  106. 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 -----------------------------------------------
75
+
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 ------------------------------------------------
23
109
 
24
- // TODO: think of using the term `query` for the thing inside SELECT/SET.
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
 
@@ -30,9 +164,34 @@ const { locationString } = require('../base/location');
30
164
 
31
165
  // Properties in which artifact or members are defined - next property in the
32
166
  // "csnPath" is the name or index of that property; 'args' (its value can be a
33
- // dictionary) is handled extra here.
167
+ // dictionary) is handled extra here, also 'expand' and 'inline'
34
168
  const artifactProperties = [ 'elements', 'columns', 'keys', 'mixin', 'enum',
35
- 'params', 'actions', 'definitions', 'extensions' ];
169
+ 'params', 'actions', 'definitions', 'extensions' ]; // + 'args', see above
170
+
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' },
180
+ keys: { lexical: false, dynamic: 'target' },
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
185
+ on: { lexical: justDollar, dynamic: 'query' }, // assoc defs, redirected to
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)
189
+ // default: { lexical: query => query, dynamic: 'source' }
190
+ }
191
+
192
+ function justDollar() {
193
+ return null;
194
+ }
36
195
 
37
196
  /**
38
197
  * @param {CSN.Model} csn
@@ -40,25 +199,25 @@ const artifactProperties = [ 'elements', 'columns', 'keys', 'mixin', 'enum',
40
199
  function csnRefs( csn ) {
41
200
  const cache = new WeakMap();
42
201
 
43
- // TODO: code cleanup after getting rid of $env
44
- resolveRef.expand = function resolveRefExpand( obj, ...args ) {
45
- 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 ) );
46
205
  }
47
- resolveRef.refWhere = function resolveRefWhere( refObj, obj, ...args ) {
48
- return cached( obj, '_env', () => {
49
- resolveRef( refObj, ...args ); // sets _env cache for non-string ref items
50
- 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' );
51
210
  } );
52
211
  }
53
212
  return {
54
- effectiveType, artifactRef, inspectRef, queryOrMain,
213
+ effectiveType, artifactRef, getOrigin, inspectRef, queryOrMain,
214
+ __getCache_forEnrichCsnDebugging: obj => cache.get( obj ),
55
215
  };
56
216
 
57
217
  /**
58
218
  * Return the type relevant for name resolution, i.e. the object which has a
59
- * `target`, `elements`, `enum` property, or no `type` property. To avoid
60
- * confusion with the "base type", we do not use the term "final type".
61
- * (This function could be omitted if we would use JS prototypes for type refs.)
219
+ * `target`, `elements`, `enum` property, or no `type` property.
220
+ * (This function could be simplified if we would use JS prototypes for type refs.)
62
221
  *
63
222
  * @param {CSN.ArtifactWithRefs} art
64
223
  */
@@ -66,15 +225,16 @@ function csnRefs( csn ) {
66
225
  const cachedType = getCache( art, '_effectiveType' );
67
226
  if (cachedType !== undefined)
68
227
  return cachedType;
69
- 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)
70
230
  return setCache( art, '_effectiveType', art );
71
231
 
72
232
  const chain = [];
73
- while (getCache( art, '_effectiveType' ) === undefined && art.type &&
74
- !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) {
75
235
  chain.push( art );
76
236
  setCache( art, '_effectiveType', 0 ); // initial setting in case of cycles
77
- art = artifactRef( art.type, BUILTIN_TYPE );
237
+ art = (art.$origin) ? getOrigin( art ) : artifactRef( art.type, BUILTIN_TYPE );
78
238
  }
79
239
  if (getCache( art, '_effectiveType' ) === 0)
80
240
  throw new Error( 'Circular type reference');
@@ -92,7 +252,7 @@ function csnRefs( csn ) {
92
252
  // elements of array items (that is the task of the core compiler /
93
253
  // semantic check)
94
254
  while (type.items)
95
- type = type.items;
255
+ type = effectiveType( type.items );
96
256
  // cannot navigate along targetAspect!
97
257
  return (type.target) ? csn.definitions[type.target] : type;
98
258
  }
@@ -108,7 +268,7 @@ function csnRefs( csn ) {
108
268
  function artifactRef( ref, notFound ) {
109
269
  const art = (typeof ref === 'string')
110
270
  ? csn.definitions[ref]
111
- : cached( ref, 'ref', artifactPathRef );
271
+ : cached( ref, '_ref', artifactPathRef );
112
272
  if (art)
113
273
  return art;
114
274
  if (notFound !== undefined)
@@ -124,6 +284,35 @@ function csnRefs( csn ) {
124
284
  return art;
125
285
  }
126
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
+
127
316
  /**
128
317
  * Return the entity we select from
129
318
  *
@@ -136,108 +325,117 @@ function csnRefs( csn ) {
136
325
 
137
326
  /**
138
327
  * @param {CSN.Path} csnPath
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
+ *
336
+ * - return value `scope`
337
+ * global: first item is name of definition
338
+ * param: first item is parameter of definition (with param: true)
339
+ * parent: first item is elem of parent (definition or outer elem)
340
+ * target: first item is elem in target (for keys of assocs)
341
+ * $magic: magic variable (path starts with $magic, see also $self)
342
+ * $self: first item is $self or $projection
343
+ * // now values only in queries:
344
+ * mixin: first item is mixin
345
+ * alias: first item is table alias
346
+ * source: first item is element in a source of the current query
347
+ * query: first item is element of current query
348
+ * ref-target: first item is element of target of outer ref item
349
+ * (used for filter condition)
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
139
356
  */
140
357
  function inspectRef( csnPath ) {
141
358
  return analyseCsnPath( csnPath, csn, resolveRef );
142
359
  }
143
360
 
144
- function resolveRef( obj, parent, query, scope, baseEnv, main ) {
145
- const queries = cached( main, '$queries', allQueries );
146
-
147
- 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;
148
363
  if (!Array.isArray( path ))
149
- throw new Error( 'Value references must look like {ref:[...]}' );
150
- let head = pathId( path[0] );
151
-
152
- // 1,2: with 'param' or 'global' property, in `keys`
153
- if (obj.param) {
154
- return expandRefPath( path, main.params[head], 'param' );
155
- }
156
- else if (obj.global || [ 'type', 'includes', 'target', 'from' ].includes( scope )) {
157
- return expandRefPath( path, csn.definitions[head], scope );
158
- }
159
- else if (scope === 'keys') {
160
- const target = csn.definitions[parent.target || parent.cast.target];
161
- return expandRefPath( path, target.elements[head], 'keys' );
364
+ throw new Error( 'References must look like {ref:[...]}' );
365
+
366
+ const head = pathId( path[0] );
367
+ if (ref.param)
368
+ return resolvePath( path, main.params[head], 'param' );
369
+
370
+ const semantics = referenceSemantics[refCtx] || {};
371
+ if (semantics.dynamic === 'global' || ref.global)
372
+ return resolvePath( path, csn.definitions[head], 'global', refCtx === 'from' );
373
+
374
+ cached( main, '$queries', allQueries );
375
+ let qcache = query && cache.get( query.projection || query );
376
+ // BACKEND ISSUE: you cannot call csnRefs(), inspect some refs, change the
377
+ // CSN and again inspect some refs without calling csnRefs() before!
378
+ // WORKAROUND: if no cached query, a backend has changed the CSN - re-eval cache
379
+ if (query && !qcache) {
380
+ setCache( main, '$queries', allQueries( main ) );
381
+ qcache = cache.get( query.projection || query );
162
382
  }
163
- // 3: $magic
164
- if (head.charAt(0) === '$') {
165
- if (head === '$self' || head === '$projection') {
166
- const self = query ? queryOrMain( query, main ) : main;
167
- return expandRefPath( path, self, '$self' );
383
+ // first the lexical scopes (due to query hierarchy) and $magic: ---------
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];
389
+ if (alias)
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;
395
+ }
396
+ if (head.charAt(0) === '$') {
397
+ if (head !== '$self' && head !== '$projection')
398
+ return { scope: '$magic' };
399
+ const self = qcache && qcache.$queryNumber > 1 ? qcache._select : main;
400
+ return resolvePath( path, self, '$self' );
168
401
  }
169
-
170
- return { scope: '$magic' };
171
- }
172
- // 4: where inside ref, expand, inline
173
- if (baseEnv) {
174
- return expandRefPath( path, baseEnv.elements[head], scope );
175
- }
176
- // 5,6,7: outside queries, in queries where inferred elements are referred to
177
- if (!query)
178
- return expandRefPath( path, (parent || main).elements[head], scope );
179
- const select = query.SELECT || query.projection;
180
- const obj$env = obj.$env;
181
- if (!select || obj$env === true)
182
- // TODO: do not do this if current query has a parent query (except with obj.$env)
183
- // TODO: also consider expand/inline
184
- return expandRefPath( path, queryOrMain( query, main ).elements[head] );
185
-
186
- // With explicitly provided $env:
187
- if (typeof obj$env === 'number') { // head is mixin or table alias name
188
- const s = (obj$env) ? queries[obj$env - 1] : select;
189
- const m = s.mixin && s.mixin[head];
190
- return expandRefPath( path, m || getCache( s, '_sources' )[head], (m ? 'mixin' : 'alias') );
191
- }
192
- else if (typeof obj$env === 'string') {
193
- const source = getCache( select, '_sources' )[obj$env];
194
- // Had a case where a obj.$env was the name of a mixin - TODO: should not be - example?
195
- if (source)
196
- return expandRefPath( path, source.elements[head], 'source' );
197
- else if (select.mixin && select.mixin[obj$env])
198
- return expandRefPath( path, select.mixin[head], 'source' );
199
- throw new Error('No source found!');
200
- }
201
-
202
- // ON ref is to be searched only in the query elements
203
- if (scope === 'on') // TODO: ok with expand/inline? Probably not with the latter
204
- return expandRefPath( path, queryOrMain( query, main ).elements[head], scope );
205
-
206
- // 8: try to search in MIXIN section (not in ON of JOINs)
207
- if (scope !== 'from-on' && scope !== 'orderBy' && select.mixin) {
208
- const art = select.mixin[head];
209
- if (art)
210
- return expandRefPath( path, art, 'mixin' );
211
402
  }
212
- // 9: try to search for table aliases (partially in ON of JOINs)
213
- const alias = getCache( select, '$alias' );
214
- if (path.length > 1 && (alias || scope !== 'from-on')) {
215
- const art = getCache( select, '_sources' )[head];
216
- if (art)
217
- return expandRefPath( path, art, 'alias' );
403
+ // now the dynamic environment: ------------------------------------------
404
+ if (semantics.dynamic === 'target') { // ref in keys
405
+ // not selecting the corresponding element for a select column works,
406
+ // because explicit keys can only be provided with explicit redirection
407
+ // target
408
+ const target = csn.definitions[parent.target || parent.cast.target];
409
+ return resolvePath( path, target.elements[head], 'target' );
218
410
  }
219
- // ORDER BY ref might have been a table alias (CSN not always has an $env),
220
- // otherwise query elements
221
- if (scope === 'orderBy')
222
- return expandRefPath( path, queryOrMain( query, main ).elements[head], scope );
223
-
224
- // 10: search in elements of source entity
225
- // TODO: do not do this if current query has a parent query !!!
226
- if (typeof alias === 'string') { // with unique source
227
- const source = getCache( select, '_sources' )[alias];
228
- return expandRefPath( path, source.elements[head], 'source' );
411
+ if (baseEnv) // ref-target (filter condition), expand, inline
412
+ return resolvePath( path, baseEnv.elements[head], semantics.dynamic );
413
+ if (!query) // outside queries - TODO: items?
414
+ return resolvePath( path, parent.elements[head], 'parent' );
415
+
416
+ if (semantics.dynamic === 'query')
417
+ // TODO: for ON condition in expand, would need to use cached _element
418
+ return resolvePath( path, qcache.elements[head], 'query' );
419
+ for (const name in qcache.$aliases) {
420
+ const found = qcache.$aliases[name].elements[head];
421
+ if (found)
422
+ return resolvePath( path, found, 'source', name )
229
423
  }
230
- throw new Error( `Missing helper property $env: ${ scope }` );
424
+ // console.log(query.SELECT,qcache,qcache.$next,main)
425
+ throw new Error ( `Path item ${ 0 }=${ head } refers to nothing, refCtx: ${ refCtx }` );
231
426
  }
232
427
 
233
428
  /**
234
429
  * @param {CSN.Path} path
235
430
  * @param {CSN.Artifact} art
236
- * @param {string | null} [scope]
431
+ * @param {string} [scope]
237
432
  */
238
- function expandRefPath( path, art, scope = null ) {
433
+ function resolvePath( path, art, scope, extraInfo ) {
239
434
  /** @type {{idx, art?, env?}[]} */
240
435
  const links = path.map( (_v, idx) => ({ idx }) );
436
+ // TODO: backends should be changed to enable uncommenting:
437
+ // if (!art) // does not work with test3/Associations/KeylessManagedAssociation/
438
+ // throw new Error ( `Path item ${ 0 }=${ pathId( path[0] ) } refers to nothing, scope: ${ scope }`);
241
439
  links[0].art = art;
242
440
  for (let i = 1; i < links.length; ++i) { // yes, starting at 1, links[0] is set above
243
441
  art = navigationEnv( art );
@@ -252,16 +450,19 @@ function csnRefs( csn ) {
252
450
  }
253
451
  links[i].art = art;
254
452
  }
255
- const last = path[path.length - 1]
256
- if (scope === 'from' || typeof last !== 'string') {
453
+ const last = path[path.length - 1];
454
+ const fromRef = scope === 'global' && extraInfo;
455
+ if (fromRef || typeof last !== 'string') {
257
456
  const env = navigationEnv( art );
258
457
  links[links.length - 1].env = env;
259
- if (scope === 'from')
458
+ if (fromRef)
260
459
  art = env;
261
460
  if (typeof last !== 'string')
262
461
  setCache( last, '_env', env )
263
462
  }
264
- return { links, art, scope };
463
+ return (extraInfo && !fromRef)
464
+ ? { links, art, scope, $env: extraInfo }
465
+ : { links, art, scope };
265
466
  }
266
467
 
267
468
  /**
@@ -275,29 +476,80 @@ function csnRefs( csn ) {
275
476
  const all = [];
276
477
  const projection = main.query || main.projection && main;
277
478
  if (!projection)
278
- return all;
279
- traverseQuery( projection, null, function memorize( query, select ) {
479
+ return null;
480
+ traverseQuery( projection, null, null, function memorize( query, fromSelect, parentQuery ) {
280
481
  if (query.ref) { // ref in from
482
+ // console.log('SQ:',query,cache.get(query))
281
483
  const as = query.as || implicitAs( query.ref );
282
- getCache( select, '_sources' )[as] = fromRef( query );
283
- const alias = getCache( select, '$alias' ) // alias of unique source
284
- setCache( select, '$alias', (alias != null) ? typeof alias === 'string' : as);
484
+ getCache( fromSelect, '$aliases' )[as] = fromRef( query );
285
485
  }
286
- else if (select && query.as) { // sub query in FROM
287
- const { as } = query;
288
- getCache( select, '_sources' )[as] = queryOrMain( query, main );
289
- const alias = getCache( select, '$alias' ) // alias of unique source
290
- setCache( select, '$alias', (alias != null) ? typeof alias === 'string' : as); }
291
- const proj = query.SELECT || query.projection;
292
- if (proj) { // every SELECT query -- TODO: remember number?
293
- setCache( proj, '_sources', Object.create(null) );
294
- setCache( proj, '$alias', null );
295
- all.push( proj );
486
+ else {
487
+ const qcache = getQueryCache( parentQuery );
488
+ if (query !== main)
489
+ cache.set( query, qcache );
490
+
491
+ if (fromSelect)
492
+ getCache( fromSelect, '$aliases' )[query.as] = qcache;
493
+ const select = query.SELECT || query.projection;
494
+ if (select) {
495
+ cache.set( select, qcache ); // query and query.SELECT have the same cache qcache
496
+ qcache._select = select;
497
+ all.push( qcache );
498
+ }
296
499
  }
297
500
  } );
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 ) );
507
+ } );
298
508
  return all;
299
509
  }
300
510
 
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 };
528
+ }
529
+
530
+ function initColumnElement( col, parentElementOrQueryCache ) {
531
+ if (col === '*')
532
+ return;
533
+ if (col.inline) {
534
+ col.inline.map( c => initColumnElement( c, parentElementOrQueryCache ) );
535
+ return;
536
+ }
537
+ setCache( col, '_parent', // not set for query (has property _select)
538
+ !parentElementOrQueryCache._select && parentElementOrQueryCache );
539
+ const as = col.as || col.func || implicitAs( col.ref );
540
+ let type = parentElementOrQueryCache;
541
+ while (type.items)
542
+ type = type.items;
543
+ const elem = setCache( col, '_element', type.elements[as] );
544
+ // if requested, we could set a _column link in element
545
+ if (col.expand)
546
+ col.expand.map( c => initColumnElement( c, elem ) );
547
+ }
548
+
549
+ // property name convention in cache:
550
+ // - $name: to other cache object (with proto), dictionary (w/o proto), or scalar
551
+ // - _name, name: to CSN object value (_name) or dictionary (name)
552
+
301
553
  function setCache( obj, prop, val ) {
302
554
  let hidden = cache.get( obj );
303
555
  if (!hidden) {
@@ -327,11 +579,11 @@ function csnRefs( csn ) {
327
579
  }
328
580
  }
329
581
 
330
- // Return value of a query SELECT for the query, or the main artifact,
582
+ // Return value of a query SELECT for the query node, or the main artifact,
331
583
  // i.e. a value with an `elements` property.
332
- // TODO: avoid the term Query, use QuerySelect or QueryNode
584
+ // TODO: only used in forHanaNew - move somewhere else
333
585
  /**
334
- * @param {CSN.Query} query
586
+ * @param {CSN.Query} query node (object with SET or SELECT property)
335
587
  * @param {CSN.Definition} main
336
588
  */
337
589
  function queryOrMain( query, main ) {
@@ -351,27 +603,30 @@ function queryOrMain( query, main ) {
351
603
  }
352
604
 
353
605
  /**
606
+ * Traverse query in pre-order
607
+ *
354
608
  * @param {CSN.Query} query
355
- * @param {CSN.QuerySelect} select
609
+ * @param {CSN.QuerySelect} fromSelect
610
+ * @param {CSN.Query} parentQuery
356
611
  * @param {(query: CSN.Query&CSN.QueryFrom, select: CSN.QuerySelectEnriched) => void} callback
357
612
  */
358
- function traverseQuery( query, select, callback ) {
359
- if (query.SELECT || query.projection) {
360
- callback( query, select );
361
- query = query.SELECT || query.projection;
362
- traverseFrom( query.from, query, callback );
613
+ function traverseQuery( query, fromSelect, parentQuery, callback ) {
614
+ const select = query.SELECT || query.projection;
615
+ if (select) {
616
+ callback( query, fromSelect, parentQuery );
617
+ traverseFrom( select.from, select, parentQuery, callback );
618
+ for (const prop of [ 'columns', 'where', 'having' ]) {
619
+ // all properties which can have sub queries (`join-on` also can)
620
+ const expr = select[prop];
621
+ if (expr)
622
+ expr.forEach( q => traverseExpr( q, query, callback ) );
623
+ }
363
624
  }
364
625
  else if (query.SET) {
365
- callback( query, select );
366
- query = query.SET;
367
- }
368
- for (const prop of [ 'args', 'xpr', 'columns', 'where', 'having' ]) {
369
- // all properties which could have sub queries (directly or indirectly)
370
- const expr = query[prop];
371
- if (expr && typeof expr === 'object') {
372
- const args = Array.isArray( expr ) ? expr : Object.values( expr );
373
- args.forEach( q => traverseQuery( q, null, callback ) );
374
- }
626
+ callback( query, fromSelect, parentQuery );
627
+ const { args } = query.SET;
628
+ for (const q of args || [])
629
+ traverseQuery( q, null, query, callback );
375
630
  }
376
631
  }
377
632
 
@@ -380,17 +635,30 @@ function traverseQuery( query, select, callback ) {
380
635
  * @param {CSN.QuerySelect} select
381
636
  * @param {(from: CSN.QueryFrom, select: CSN.QuerySelect) => void} callback
382
637
  */
383
- function traverseFrom( from, select, callback ) {
638
+ function traverseFrom( from, fromSelect, parentQuery, callback ) {
384
639
  if (from.ref) {
385
- callback( from, select );
640
+ callback( from, fromSelect, parentQuery );
386
641
  }
387
642
  else if (from.args) { // join
388
- from.args.forEach( arg => traverseFrom( arg, select, callback ) );
389
- if (from.on) // join
390
- from.on.forEach( arg => traverseQuery( arg, select, callback ) );
643
+ from.args.forEach( arg => traverseFrom( arg, fromSelect, parentQuery, callback ) );
644
+ if (from.on) // join-on, potentially having a sub query
645
+ from.on.forEach( arg => traverseQuery( arg, null, fromSelect, callback ) );
391
646
  }
392
647
  else { // sub query in FROM
393
- traverseQuery( from, select, callback );
648
+ traverseQuery( from, fromSelect, parentQuery, callback );
649
+ }
650
+ }
651
+
652
+ function traverseExpr( expr, parentQuery, callback ) {
653
+ if (expr.SELECT || expr.SET)
654
+ traverseQuery( expr, null, parentQuery, callback )
655
+ for (const prop of [ 'args', 'xpr' ]) {
656
+ // all properties which could have sub queries (directly or indirectly),
657
+ const val = expr[prop];
658
+ if (val && typeof val === 'object') {
659
+ const args = Array.isArray( val ) ? val : Object.values( val );
660
+ args.forEach( e => traverseExpr( e, parentQuery, callback ) );
661
+ }
394
662
  }
395
663
  }
396
664
 
@@ -415,12 +683,13 @@ function analyseCsnPath( csnPath, csn, resolve ) {
415
683
  let obj = csn;
416
684
  let parent = null;
417
685
  let query = null;
418
- let scope = null;
686
+ let refCtx = null;
419
687
  let art = null;
420
688
  /** @type {boolean|string|number} */
421
689
  let isName = false;
422
- let refObj = null;
690
+ let baseRef = null;
423
691
  let baseEnv = null;
692
+ let main = csn.definitions[csnPath[1]];
424
693
 
425
694
  for (let index = 0; index < csnPath.length; index++) {
426
695
  const prop = csnPath[index];
@@ -433,51 +702,58 @@ function analyseCsnPath( csnPath, csn, resolve ) {
433
702
  isName = false;
434
703
  }
435
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
+ }
436
709
  isName = prop;
437
- scope = prop;
710
+ refCtx = prop;
438
711
  }
439
712
  else if (prop === 'items' || prop === 'returns') {
440
713
  art = obj[prop];
441
714
  }
442
715
  else if (prop === 'args') {
443
716
  isName = true; // for named arguments
444
- if (scope === 'orderBy')
445
- scope = 'orderBy-xpr'; // no need to extra 'orderBy-args'
446
717
  }
447
718
  else if (prop === 'SELECT' || prop === 'SET' || prop === 'projection') {
448
719
  query = obj;
449
- scope = prop;
720
+ parent = null;
721
+ baseEnv = null;
722
+ refCtx = prop;
450
723
  }
451
- else if (prop === 'where' && scope === 'ref') {
724
+ else if (prop === 'where' && refCtx === 'ref') {
452
725
  if (resolve)
453
- baseEnv = resolve.refWhere( refObj, obj, parent, query, scope, baseEnv,
454
- csn.definitions[csnPath[1]] );
455
- scope = 'ref-where';
726
+ baseEnv = resolve.ref_where( obj, baseRef, refCtx, csn.definitions[csnPath[1]],
727
+ query, parent, baseEnv );
728
+ refCtx = 'ref_where';
456
729
  }
457
- else if ((prop === 'expand' || prop === 'inline') && obj.ref) {
458
- if (obj.ref && resolve) {
459
- baseEnv = resolve.expand( obj, parent, query, scope, baseEnv,
460
- csn.definitions[csnPath[1]] );
730
+ else if (prop === 'expand' || prop === 'inline') {
731
+ if (obj.ref) {
732
+ if (resolve)
733
+ baseEnv = resolve.expandInline( obj, refCtx, csn.definitions[csnPath[1]],
734
+ query, parent, baseEnv );
735
+ refCtx = prop;
461
736
  }
462
- scope = prop;
737
+ if (prop === 'expand')
738
+ isName = prop;
463
739
  }
464
740
  else if (prop === 'on') {
465
- if (scope === 'from')
466
- scope = 'from-on';
467
- else if (scope === 'mixin')
468
- scope = 'mixin-on';
741
+ if (refCtx === 'from')
742
+ refCtx = 'join_on';
743
+ else if (refCtx === 'mixin')
744
+ refCtx = 'mixin_on';
469
745
  else
470
- scope = 'on';
746
+ refCtx = 'on'; // will use query elements with REDIRECTED TO
471
747
  }
472
748
  else if (prop === 'ref') {
473
- refObj = obj;
474
- scope = prop;
749
+ baseRef = obj; // needs to be inspected for filter conditions
750
+ refCtx = prop;
475
751
  }
476
- else if (prop !== 'xpr') {
477
- scope = prop;
752
+ else if (prop === 'orderBy') {
753
+ refCtx = (query.SET ? 'orderBy_set' : 'orderBy');
478
754
  }
479
- else if (scope === 'orderBy') {
480
- scope = 'orderBy-xpr';
755
+ else if (prop !== 'xpr') {
756
+ refCtx = prop;
481
757
  }
482
758
 
483
759
  obj = obj[prop];
@@ -485,10 +761,10 @@ function analyseCsnPath( csnPath, csn, resolve ) {
485
761
  // For the semantic location, use current object as best guess
486
762
  break;
487
763
  }
488
- // console.log( 'CPATH:', csnPath, scope, obj, parent.$location );
764
+ // console.log( 'CPATH:', csnPath, refCtx, obj, parent.$location );
489
765
  if (!resolve)
490
766
  return { query }; // for constructSemanticLocationFromCsnPath
491
- return resolve( obj, parent, query, scope, baseEnv, csn.definitions[csnPath[1]] );
767
+ return resolve( obj, refCtx, main, query, parent, baseEnv );
492
768
  }
493
769
 
494
770
  module.exports = {