@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.
- package/CHANGELOG.md +241 -1
- package/bin/.eslintrc.json +17 -0
- package/bin/cds_update_identifiers.js +8 -7
- package/bin/cdsc.js +180 -132
- package/bin/cdshi.js +18 -11
- package/bin/cdsse.js +38 -32
- package/bin/cdsv2m.js +8 -7
- package/doc/CHANGELOG_BETA.md +36 -1
- package/lib/api/main.js +81 -100
- package/lib/api/options.js +17 -11
- package/lib/api/validate.js +12 -8
- package/lib/backends.js +0 -81
- package/lib/base/keywords.js +32 -2
- package/lib/base/location.js +2 -2
- package/lib/base/message-registry.js +66 -4
- package/lib/base/messages.js +84 -27
- package/lib/base/model.js +2 -61
- package/lib/checks/arrayOfs.js +0 -1
- package/lib/checks/defaultValues.js +27 -2
- package/lib/checks/elements.js +1 -6
- package/lib/checks/enricher.js +8 -2
- package/lib/checks/foreignKeys.js +0 -6
- package/lib/checks/managedWithoutKeys.js +17 -0
- package/lib/checks/nonexpandableStructured.js +38 -0
- package/lib/checks/onConditions.js +9 -45
- package/lib/checks/queryNoDbArtifacts.js +27 -9
- package/lib/checks/selectItems.js +25 -2
- package/lib/checks/types.js +26 -2
- package/lib/checks/unknownMagic.js +38 -0
- package/lib/checks/utils.js +61 -0
- package/lib/checks/validator.js +66 -13
- package/lib/compiler/assert-consistency.js +24 -12
- package/lib/compiler/builtins.js +2 -0
- package/lib/compiler/checks.js +6 -4
- package/lib/compiler/definer.js +101 -39
- package/lib/compiler/index.js +88 -59
- package/lib/compiler/resolver.js +455 -209
- package/lib/compiler/shared.js +57 -33
- package/lib/edm/annotations/genericTranslation.js +183 -187
- package/lib/edm/csn2edm.js +128 -99
- package/lib/edm/edm.js +18 -21
- package/lib/edm/edmPreprocessor.js +361 -127
- package/lib/edm/edmUtils.js +103 -33
- package/lib/gen/Dictionary.json +74 -28
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +18 -4
- package/lib/gen/language.tokens +124 -118
- package/lib/gen/languageLexer.interp +13 -1
- package/lib/gen/languageLexer.js +870 -839
- package/lib/gen/languageLexer.tokens +116 -111
- package/lib/gen/languageParser.js +5894 -5614
- package/lib/json/from-csn.js +152 -67
- package/lib/json/to-csn.js +334 -135
- package/lib/language/antlrParser.js +4 -3
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +24 -14
- package/lib/language/language.g4 +188 -128
- package/lib/main.d.ts +435 -0
- package/lib/main.js +31 -7
- package/lib/model/api.js +78 -0
- package/lib/model/csnRefs.js +463 -187
- package/lib/model/csnUtils.js +280 -136
- package/lib/model/enrichCsn.js +75 -4
- package/lib/model/revealInternalProperties.js +2 -1
- package/lib/modelCompare/compare.js +70 -25
- package/lib/optionProcessor.js +13 -10
- package/lib/render/.eslintrc.json +4 -1
- package/lib/render/DuplicateChecker.js +8 -5
- package/lib/render/toCdl.js +123 -40
- package/lib/render/toHdbcds.js +156 -65
- package/lib/render/toSql.js +87 -11
- package/lib/render/utils/common.js +55 -9
- package/lib/render/utils/sql.js +3 -3
- package/lib/sql-identifier.js +6 -1
- package/lib/transform/{sql → db}/.eslintrc.json +0 -0
- package/lib/transform/{sql → db}/assertUnique.js +7 -8
- package/lib/transform/{sql → db}/constraints.js +35 -20
- package/lib/transform/db/draft.js +353 -0
- package/lib/transform/db/expansion.js +582 -0
- package/lib/transform/db/flattening.js +325 -0
- package/lib/transform/{sql → db}/groupByOrderBy.js +8 -16
- package/lib/transform/{sql → db}/helpers.js +0 -0
- package/lib/transform/{sql → db}/transformExists.js +256 -60
- package/lib/transform/forHanaNew.js +216 -765
- package/lib/transform/forOdataNew.js +60 -56
- package/lib/transform/localized.js +48 -26
- package/lib/transform/odata/attachPath.js +19 -4
- package/lib/transform/odata/expandStructKeysInAssociations.js +2 -2
- package/lib/transform/odata/generateForeignKeyElements.js +13 -12
- package/lib/transform/odata/referenceFlattener.js +60 -36
- package/lib/transform/odata/sortByAssociationDependency.js +4 -4
- package/lib/transform/odata/structuralPath.js +76 -0
- package/lib/transform/odata/structureFlattener.js +21 -22
- package/lib/transform/odata/toFinalBaseType.js +5 -5
- package/lib/transform/odata/typesExposure.js +27 -17
- package/lib/transform/odata/utils.js +2 -2
- package/lib/transform/transformUtilsNew.js +141 -77
- package/lib/transform/translateAssocsToJoins.js +17 -14
- package/lib/transform/universalCsnEnricher.js +67 -0
- package/lib/utils/file.js +0 -11
- package/lib/utils/moduleResolve.js +6 -8
- package/lib/utils/timetrace.js +6 -1
- package/package.json +2 -1
- package/lib/base/deepCopy.js +0 -66
- package/lib/json/walker.js +0 -26
- package/lib/utils/string.js +0 -17
package/lib/model/csnRefs.js
CHANGED
|
@@ -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
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
//
|
|
8
|
-
//
|
|
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
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
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
|
|
22
|
-
// array indexes which navigate from the CSN root to the
|
|
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
|
-
//
|
|
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
|
-
//
|
|
44
|
-
resolveRef.
|
|
45
|
-
return cached(
|
|
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.
|
|
48
|
-
return cached(
|
|
49
|
-
resolveRef(
|
|
50
|
-
return getCache(
|
|
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.
|
|
60
|
-
*
|
|
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
|
|
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, '
|
|
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(
|
|
145
|
-
const
|
|
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( '
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
//
|
|
164
|
-
if (
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
//
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
//
|
|
220
|
-
|
|
221
|
-
if (
|
|
222
|
-
return
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
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
|
|
431
|
+
* @param {string} [scope]
|
|
237
432
|
*/
|
|
238
|
-
function
|
|
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
|
-
|
|
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 (
|
|
458
|
+
if (fromRef)
|
|
260
459
|
art = env;
|
|
261
460
|
if (typeof last !== 'string')
|
|
262
461
|
setCache( last, '_env', env )
|
|
263
462
|
}
|
|
264
|
-
return
|
|
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
|
|
279
|
-
traverseQuery( projection, null, function memorize( query,
|
|
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(
|
|
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
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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:
|
|
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}
|
|
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,
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
query
|
|
362
|
-
traverseFrom(
|
|
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,
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
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,
|
|
638
|
+
function traverseFrom( from, fromSelect, parentQuery, callback ) {
|
|
384
639
|
if (from.ref) {
|
|
385
|
-
callback( from,
|
|
640
|
+
callback( from, fromSelect, parentQuery );
|
|
386
641
|
}
|
|
387
642
|
else if (from.args) { // join
|
|
388
|
-
from.args.forEach( arg => traverseFrom( arg,
|
|
389
|
-
if (from.on)
|
|
390
|
-
from.on.forEach( arg => traverseQuery( arg,
|
|
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,
|
|
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
|
|
686
|
+
let refCtx = null;
|
|
419
687
|
let art = null;
|
|
420
688
|
/** @type {boolean|string|number} */
|
|
421
689
|
let isName = false;
|
|
422
|
-
let
|
|
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
|
-
|
|
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
|
-
|
|
720
|
+
parent = null;
|
|
721
|
+
baseEnv = null;
|
|
722
|
+
refCtx = prop;
|
|
450
723
|
}
|
|
451
|
-
else if (prop === 'where' &&
|
|
724
|
+
else if (prop === 'where' && refCtx === 'ref') {
|
|
452
725
|
if (resolve)
|
|
453
|
-
baseEnv = resolve.
|
|
454
|
-
|
|
455
|
-
|
|
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 (
|
|
458
|
-
if (obj.ref
|
|
459
|
-
|
|
460
|
-
|
|
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
|
-
|
|
737
|
+
if (prop === 'expand')
|
|
738
|
+
isName = prop;
|
|
463
739
|
}
|
|
464
740
|
else if (prop === 'on') {
|
|
465
|
-
if (
|
|
466
|
-
|
|
467
|
-
else if (
|
|
468
|
-
|
|
741
|
+
if (refCtx === 'from')
|
|
742
|
+
refCtx = 'join_on';
|
|
743
|
+
else if (refCtx === 'mixin')
|
|
744
|
+
refCtx = 'mixin_on';
|
|
469
745
|
else
|
|
470
|
-
|
|
746
|
+
refCtx = 'on'; // will use query elements with REDIRECTED TO
|
|
471
747
|
}
|
|
472
748
|
else if (prop === 'ref') {
|
|
473
|
-
|
|
474
|
-
|
|
749
|
+
baseRef = obj; // needs to be inspected for filter conditions
|
|
750
|
+
refCtx = prop;
|
|
475
751
|
}
|
|
476
|
-
else if (prop
|
|
477
|
-
|
|
752
|
+
else if (prop === 'orderBy') {
|
|
753
|
+
refCtx = (query.SET ? 'orderBy_set' : 'orderBy');
|
|
478
754
|
}
|
|
479
|
-
else if (
|
|
480
|
-
|
|
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,
|
|
764
|
+
// console.log( 'CPATH:', csnPath, refCtx, obj, parent.$location );
|
|
489
765
|
if (!resolve)
|
|
490
766
|
return { query }; // for constructSemanticLocationFromCsnPath
|
|
491
|
-
return resolve( obj,
|
|
767
|
+
return resolve( obj, refCtx, main, query, parent, baseEnv );
|
|
492
768
|
}
|
|
493
769
|
|
|
494
770
|
module.exports = {
|