@sap/cds-compiler 2.5.0 → 2.10.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +191 -9
- package/bin/cdsc.js +2 -2
- package/doc/CHANGELOG_BETA.md +33 -3
- package/lib/api/main.js +29 -101
- package/lib/api/options.js +15 -11
- package/lib/api/validate.js +12 -8
- package/lib/backends.js +0 -81
- package/lib/base/keywords.js +32 -2
- package/lib/base/message-registry.js +63 -9
- package/lib/base/messages.js +63 -21
- package/lib/base/model.js +2 -3
- package/lib/checks/defaultValues.js +27 -2
- package/lib/checks/elements.js +1 -6
- 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 +25 -7
- 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 +60 -7
- package/lib/compiler/assert-consistency.js +16 -7
- package/lib/compiler/builtins.js +2 -0
- package/lib/compiler/checks.js +6 -4
- package/lib/compiler/definer.js +99 -42
- package/lib/compiler/index.js +73 -27
- package/lib/compiler/resolver.js +288 -157
- package/lib/compiler/shared.js +31 -11
- package/lib/edm/annotations/genericTranslation.js +182 -186
- package/lib/edm/csn2edm.js +103 -108
- package/lib/edm/edm.js +18 -21
- package/lib/edm/edmPreprocessor.js +361 -114
- package/lib/edm/edmUtils.js +103 -33
- package/lib/gen/Dictionary.json +22 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +12 -1
- package/lib/gen/language.tokens +57 -53
- package/lib/gen/languageLexer.interp +10 -1
- package/lib/gen/languageLexer.js +770 -744
- package/lib/gen/languageLexer.tokens +49 -46
- package/lib/gen/languageParser.js +4713 -4279
- package/lib/json/from-csn.js +103 -45
- package/lib/json/to-csn.js +296 -117
- package/lib/language/antlrParser.js +4 -3
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +21 -12
- package/lib/language/language.g4 +99 -31
- package/lib/main.d.ts +81 -3
- package/lib/main.js +30 -7
- package/lib/model/api.js +78 -0
- package/lib/model/csnRefs.js +329 -142
- package/lib/model/csnUtils.js +235 -58
- package/lib/model/enrichCsn.js +18 -1
- package/lib/model/revealInternalProperties.js +2 -1
- package/lib/modelCompare/compare.js +37 -20
- package/lib/optionProcessor.js +9 -3
- package/lib/render/.eslintrc.json +4 -1
- package/lib/render/DuplicateChecker.js +8 -5
- package/lib/render/toCdl.js +112 -33
- package/lib/render/toHdbcds.js +134 -64
- package/lib/render/toSql.js +91 -38
- package/lib/render/utils/common.js +8 -13
- package/lib/render/utils/sql.js +3 -3
- package/lib/sql-identifier.js +6 -1
- package/lib/transform/db/assertUnique.js +5 -6
- package/lib/transform/db/constraints.js +29 -13
- package/lib/transform/db/draft.js +8 -6
- package/lib/transform/db/expansion.js +582 -0
- package/lib/transform/db/flattening.js +325 -0
- package/lib/transform/db/groupByOrderBy.js +2 -2
- package/lib/transform/db/transformExists.js +284 -63
- package/lib/transform/forHanaNew.js +98 -381
- package/lib/transform/forOdataNew.js +21 -22
- package/lib/transform/localized.js +37 -10
- package/lib/transform/odata/attachPath.js +19 -4
- package/lib/transform/odata/generateForeignKeyElements.js +11 -10
- package/lib/transform/odata/referenceFlattener.js +60 -39
- package/lib/transform/odata/sortByAssociationDependency.js +2 -2
- package/lib/transform/odata/structuralPath.js +72 -0
- package/lib/transform/odata/structureFlattener.js +19 -18
- package/lib/transform/odata/typesExposure.js +22 -12
- package/lib/transform/transformUtilsNew.js +134 -78
- 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/package.json +1 -1
- package/lib/json/walker.js +0 -26
- package/lib/transform/sqlite +0 -0
- 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 -----------------------------------------------
|
|
23
75
|
|
|
24
|
-
//
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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 },
|
|
45
|
-
inline: { lexical: justDollar },
|
|
46
|
-
|
|
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
|
-
|
|
49
|
-
|
|
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
|
-
//
|
|
64
|
-
resolveRef.
|
|
65
|
-
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 ) );
|
|
66
205
|
}
|
|
67
|
-
resolveRef.
|
|
68
|
-
return cached(
|
|
69
|
-
resolveRef(
|
|
70
|
-
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' );
|
|
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.
|
|
81
|
-
* confusion with the "base type", we do not use the term "final type".
|
|
219
|
+
* `target`, `elements`, `enum` property, or no `type` property.
|
|
82
220
|
* (This function could be simplified if we would use JS prototypes for type refs.)
|
|
83
221
|
*
|
|
84
222
|
* @param {CSN.ArtifactWithRefs} art
|
|
@@ -87,15 +225,16 @@ function csnRefs( csn ) {
|
|
|
87
225
|
const cachedType = getCache( art, '_effectiveType' );
|
|
88
226
|
if (cachedType !== undefined)
|
|
89
227
|
return cachedType;
|
|
90
|
-
else if (!art.type
|
|
228
|
+
else if (!art.type && !art.$origin ||
|
|
229
|
+
art.elements || art.target || art.targetAspect || art.enum)
|
|
91
230
|
return setCache( art, '_effectiveType', art );
|
|
92
231
|
|
|
93
232
|
const chain = [];
|
|
94
|
-
while (getCache( art, '_effectiveType' ) === undefined && art.type &&
|
|
95
|
-
!art.elements && !art.target && !art.targetAspect && !art.enum) {
|
|
233
|
+
while (getCache( art, '_effectiveType' ) === undefined && (art.type || art.$origin) &&
|
|
234
|
+
!art.elements && !art.target && !art.targetAspect && !art.enum && !art.items) {
|
|
96
235
|
chain.push( art );
|
|
97
236
|
setCache( art, '_effectiveType', 0 ); // initial setting in case of cycles
|
|
98
|
-
art = artifactRef( art.type, BUILTIN_TYPE );
|
|
237
|
+
art = (art.$origin) ? getOrigin( art ) : artifactRef( art.type, BUILTIN_TYPE );
|
|
99
238
|
}
|
|
100
239
|
if (getCache( art, '_effectiveType' ) === 0)
|
|
101
240
|
throw new Error( 'Circular type reference');
|
|
@@ -113,7 +252,7 @@ function csnRefs( csn ) {
|
|
|
113
252
|
// elements of array items (that is the task of the core compiler /
|
|
114
253
|
// semantic check)
|
|
115
254
|
while (type.items)
|
|
116
|
-
type = type.items;
|
|
255
|
+
type = effectiveType( type.items );
|
|
117
256
|
// cannot navigate along targetAspect!
|
|
118
257
|
return (type.target) ? csn.definitions[type.target] : type;
|
|
119
258
|
}
|
|
@@ -145,6 +284,35 @@ function csnRefs( csn ) {
|
|
|
145
284
|
return art;
|
|
146
285
|
}
|
|
147
286
|
|
|
287
|
+
function getOrigin( def ) {
|
|
288
|
+
const art = cached( def, '_origin', originPathRef );
|
|
289
|
+
if (art)
|
|
290
|
+
return art;
|
|
291
|
+
throw new Error( 'Undefined origin reference' );
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function originPathRef( def ) {
|
|
295
|
+
const [ head, ...tail ] = def.$origin;
|
|
296
|
+
let art = csn.definitions[head];
|
|
297
|
+
for (const elem of tail)
|
|
298
|
+
art = originNavigation( art, elem );
|
|
299
|
+
return art;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function originNavigation( art, elem ) {
|
|
303
|
+
if (typeof elem !== 'string') {
|
|
304
|
+
if (elem.action)
|
|
305
|
+
return art.actions[elem.action]
|
|
306
|
+
if (elem.param)
|
|
307
|
+
return (elem.param ? art.params[elem.param] : art.returns);
|
|
308
|
+
}
|
|
309
|
+
if (art.returns)
|
|
310
|
+
art = art.returns;
|
|
311
|
+
while (art.items)
|
|
312
|
+
art = art.items;
|
|
313
|
+
return (art.elements || art.enum || (art.targetAspect || art.target).elements)[elem];
|
|
314
|
+
}
|
|
315
|
+
|
|
148
316
|
/**
|
|
149
317
|
* Return the entity we select from
|
|
150
318
|
*
|
|
@@ -158,100 +326,111 @@ function csnRefs( csn ) {
|
|
|
158
326
|
/**
|
|
159
327
|
* @param {CSN.Path} csnPath
|
|
160
328
|
*
|
|
329
|
+
* - return value `art`: the “resulting” CSN of the reference
|
|
330
|
+
*
|
|
331
|
+
* - return value `links`: array of { art, env } in length of ref.path where
|
|
332
|
+
* art = the definition or element reached by the ref path so far
|
|
333
|
+
* env = the “navigation environment” provided by `art`
|
|
334
|
+
* (not set for last item, except for `from` reference or with filter)
|
|
335
|
+
*
|
|
161
336
|
* - return value `scope`
|
|
162
337
|
* global: first item is name of definition
|
|
163
338
|
* param: first item is parameter of definition (with param: true)
|
|
164
339
|
* parent: first item is elem of parent (definition or outer elem)
|
|
165
340
|
* target: first item is elem in target (for keys of assocs)
|
|
166
341
|
* $magic: magic variable (path starts with $magic, see also $self)
|
|
342
|
+
* $self: first item is $self or $projection
|
|
167
343
|
* // now values only in queries:
|
|
168
344
|
* mixin: first item is mixin
|
|
169
345
|
* alias: first item is table alias
|
|
170
|
-
*
|
|
171
|
-
* source: first item is element in a query source
|
|
346
|
+
* source: first item is element in a source of the current query
|
|
172
347
|
* query: first item is element of current query
|
|
173
348
|
* ref-target: first item is element of target of outer ref item
|
|
174
349
|
* (used for filter condition)
|
|
175
|
-
* expand: ref is "path continuation" of
|
|
176
|
-
* inline: ref is "path continuation" of
|
|
350
|
+
* expand: ref is "path continuation" of a ref with EXPAND
|
|
351
|
+
* inline: ref is "path continuation" of a ref with INLINE
|
|
352
|
+
*
|
|
353
|
+
* - return value `$env` is set with certain values of `scope`:
|
|
354
|
+
* with 'alias': the query number _n_ (the _n_th SELECT)
|
|
355
|
+
* with 'source': the table alias name for the source entity
|
|
177
356
|
*/
|
|
178
357
|
function inspectRef( csnPath ) {
|
|
179
358
|
return analyseCsnPath( csnPath, csn, resolveRef );
|
|
180
359
|
}
|
|
181
360
|
|
|
182
|
-
function resolveRef(
|
|
183
|
-
const path = (typeof
|
|
361
|
+
function resolveRef( ref, refCtx, main, query, parent, baseEnv ) {
|
|
362
|
+
const path = (typeof ref === 'string') ? [ ref ] : ref.ref;
|
|
184
363
|
if (!Array.isArray( path ))
|
|
185
|
-
throw new Error( '
|
|
364
|
+
throw new Error( 'References must look like {ref:[...]}' );
|
|
186
365
|
|
|
187
366
|
const head = pathId( path[0] );
|
|
188
|
-
if (
|
|
189
|
-
return
|
|
367
|
+
if (ref.param)
|
|
368
|
+
return resolvePath( path, main.params[head], 'param' );
|
|
190
369
|
|
|
191
|
-
const
|
|
192
|
-
if (
|
|
193
|
-
return
|
|
370
|
+
const semantics = referenceSemantics[refCtx] || {};
|
|
371
|
+
if (semantics.dynamic === 'global' || ref.global)
|
|
372
|
+
return resolvePath( path, csn.definitions[head], 'global', refCtx === 'from' );
|
|
194
373
|
|
|
195
374
|
cached( main, '$queries', allQueries );
|
|
196
|
-
let
|
|
375
|
+
let qcache = query && cache.get( query.projection || query );
|
|
197
376
|
// BACKEND ISSUE: you cannot call csnRefs(), inspect some refs, change the
|
|
198
377
|
// CSN and again inspect some refs without calling csnRefs() before!
|
|
199
378
|
// WORKAROUND: if no cached query, a backend has changed the CSN - re-eval cache
|
|
200
|
-
if (query && !
|
|
379
|
+
if (query && !qcache) {
|
|
201
380
|
setCache( main, '$queries', allQueries( main ) );
|
|
202
|
-
|
|
381
|
+
qcache = cache.get( query.projection || query );
|
|
203
382
|
}
|
|
204
383
|
// first the lexical scopes (due to query hierarchy) and $magic: ---------
|
|
205
|
-
if (
|
|
206
|
-
const tryAlias = path.length > 1 ||
|
|
207
|
-
let
|
|
208
|
-
while (
|
|
209
|
-
const alias = tryAlias &&
|
|
384
|
+
if (semantics.lexical !== false) {
|
|
385
|
+
const tryAlias = path.length > 1 || ref.expand || ref.inline;
|
|
386
|
+
let cache = qcache && (semantics.lexical ? semantics.lexical( qcache ) : qcache);
|
|
387
|
+
while (cache) {
|
|
388
|
+
const alias = tryAlias && cache.$aliases[head];
|
|
210
389
|
if (alias)
|
|
211
|
-
return
|
|
212
|
-
const mixin =
|
|
213
|
-
if (mixin && {}.hasOwnProperty.call(
|
|
214
|
-
return
|
|
215
|
-
|
|
390
|
+
return resolvePath( path, alias._select || alias, 'alias', cache.$queryNumber );
|
|
391
|
+
const mixin = cache._select.mixin && cache._select.mixin[head];
|
|
392
|
+
if (mixin && {}.hasOwnProperty.call( cache._select.mixin, head ))
|
|
393
|
+
return resolvePath( path, mixin, 'mixin', cache.$queryNumber );
|
|
394
|
+
cache = cache.$next;
|
|
216
395
|
}
|
|
217
396
|
if (head.charAt(0) === '$') {
|
|
218
397
|
if (head !== '$self' && head !== '$projection')
|
|
219
398
|
return { scope: '$magic' };
|
|
220
|
-
const self =
|
|
221
|
-
return
|
|
399
|
+
const self = qcache && qcache.$queryNumber > 1 ? qcache._select : main;
|
|
400
|
+
return resolvePath( path, self, '$self' );
|
|
222
401
|
}
|
|
223
402
|
}
|
|
224
403
|
// now the dynamic environment: ------------------------------------------
|
|
225
|
-
if (
|
|
404
|
+
if (semantics.dynamic === 'target') { // ref in keys
|
|
226
405
|
// not selecting the corresponding element for a select column works,
|
|
227
406
|
// because explicit keys can only be provided with explicit redirection
|
|
228
407
|
// target
|
|
229
408
|
const target = csn.definitions[parent.target || parent.cast.target];
|
|
230
|
-
return
|
|
409
|
+
return resolvePath( path, target.elements[head], 'target' );
|
|
231
410
|
}
|
|
232
411
|
if (baseEnv) // ref-target (filter condition), expand, inline
|
|
233
|
-
return
|
|
412
|
+
return resolvePath( path, baseEnv.elements[head], semantics.dynamic );
|
|
234
413
|
if (!query) // outside queries - TODO: items?
|
|
235
|
-
return
|
|
414
|
+
return resolvePath( path, parent.elements[head], 'parent' );
|
|
236
415
|
|
|
237
|
-
if (
|
|
416
|
+
if (semantics.dynamic === 'query')
|
|
238
417
|
// TODO: for ON condition in expand, would need to use cached _element
|
|
239
|
-
return
|
|
240
|
-
for (const name in
|
|
241
|
-
const found =
|
|
418
|
+
return resolvePath( path, qcache.elements[head], 'query' );
|
|
419
|
+
for (const name in qcache.$aliases) {
|
|
420
|
+
const found = qcache.$aliases[name].elements[head];
|
|
242
421
|
if (found)
|
|
243
|
-
return
|
|
422
|
+
return resolvePath( path, found, 'source', name )
|
|
244
423
|
}
|
|
245
|
-
// console.log(query.SELECT,
|
|
246
|
-
throw new Error ( `Path item ${ 0 }=${ head } refers to nothing,
|
|
424
|
+
// console.log(query.SELECT,qcache,qcache.$next,main)
|
|
425
|
+
throw new Error ( `Path item ${ 0 }=${ head } refers to nothing, refCtx: ${ refCtx }` );
|
|
247
426
|
}
|
|
248
427
|
|
|
249
428
|
/**
|
|
250
429
|
* @param {CSN.Path} path
|
|
251
430
|
* @param {CSN.Artifact} art
|
|
252
|
-
* @param {string
|
|
431
|
+
* @param {string} [scope]
|
|
253
432
|
*/
|
|
254
|
-
function
|
|
433
|
+
function resolvePath( path, art, scope, extraInfo ) {
|
|
255
434
|
/** @type {{idx, art?, env?}[]} */
|
|
256
435
|
const links = path.map( (_v, idx) => ({ idx }) );
|
|
257
436
|
// TODO: backends should be changed to enable uncommenting:
|
|
@@ -302,59 +481,63 @@ function csnRefs( csn ) {
|
|
|
302
481
|
if (query.ref) { // ref in from
|
|
303
482
|
// console.log('SQ:',query,cache.get(query))
|
|
304
483
|
const as = query.as || implicitAs( query.ref );
|
|
305
|
-
getCache( fromSelect, 'aliases' )[as] = fromRef( query );
|
|
484
|
+
getCache( fromSelect, '$aliases' )[as] = fromRef( query );
|
|
306
485
|
}
|
|
307
486
|
else {
|
|
308
|
-
const
|
|
487
|
+
const qcache = getQueryCache( parentQuery );
|
|
488
|
+
if (query !== main)
|
|
489
|
+
cache.set( query, qcache );
|
|
490
|
+
|
|
309
491
|
if (fromSelect)
|
|
310
|
-
getCache( fromSelect, 'aliases' )[query.as] =
|
|
492
|
+
getCache( fromSelect, '$aliases' )[query.as] = qcache;
|
|
311
493
|
const select = query.SELECT || query.projection;
|
|
312
494
|
if (select) {
|
|
313
|
-
cache.set( select,
|
|
314
|
-
|
|
315
|
-
all.push(
|
|
495
|
+
cache.set( select, qcache ); // query and query.SELECT have the same cache qcache
|
|
496
|
+
qcache._select = select;
|
|
497
|
+
all.push( qcache );
|
|
316
498
|
}
|
|
317
499
|
}
|
|
318
500
|
} );
|
|
319
|
-
all.forEach( function initElements(
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
const columns =
|
|
323
|
-
if (
|
|
324
|
-
columns.map( c => initColumnElement( c,
|
|
501
|
+
all.forEach( function initElements( qcache, index ) {
|
|
502
|
+
qcache.$queryNumber = index + 1;
|
|
503
|
+
qcache.elements = (index ? qcache._select : main).elements;
|
|
504
|
+
const columns = qcache._select.columns;
|
|
505
|
+
if (qcache.elements && columns)
|
|
506
|
+
columns.map( c => initColumnElement( c, qcache ) );
|
|
325
507
|
} );
|
|
326
508
|
return all;
|
|
327
509
|
}
|
|
328
510
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
511
|
+
/**
|
|
512
|
+
* Return the cache object for a new query.
|
|
513
|
+
* Might re-use cache object with the `parentQuery`, or use `parentQuery`
|
|
514
|
+
* for link to next lexical environment.
|
|
515
|
+
*/
|
|
516
|
+
|
|
517
|
+
function getQueryCache( parentQuery ) {
|
|
518
|
+
if (!parentQuery)
|
|
519
|
+
return { $aliases: Object.create(null) };
|
|
520
|
+
const pcache = cache.get( parentQuery );
|
|
521
|
+
if (!parentQuery.SET) // SELECT / projection: real sub query
|
|
522
|
+
return { $aliases: Object.create(null), $next: pcache };
|
|
523
|
+
// the parent query is a SET: that is not a sub query
|
|
524
|
+
// (works, as no sub queries are allowed in ORDER BY)
|
|
525
|
+
return (!pcache._select) // no leading query yet
|
|
526
|
+
? pcache // share cache with parent query
|
|
527
|
+
: { $aliases: Object.create(null), $next: pcache.$next };
|
|
345
528
|
}
|
|
346
529
|
|
|
347
|
-
function initColumnElement( col,
|
|
530
|
+
function initColumnElement( col, parentElementOrQueryCache ) {
|
|
348
531
|
if (col === '*')
|
|
349
532
|
return;
|
|
350
533
|
if (col.inline) {
|
|
351
|
-
col.inline.map( c => initColumnElement( c,
|
|
534
|
+
col.inline.map( c => initColumnElement( c, parentElementOrQueryCache ) );
|
|
352
535
|
return;
|
|
353
536
|
}
|
|
354
537
|
setCache( col, '_parent', // not set for query (has property _select)
|
|
355
|
-
!
|
|
538
|
+
!parentElementOrQueryCache._select && parentElementOrQueryCache );
|
|
356
539
|
const as = col.as || col.func || implicitAs( col.ref );
|
|
357
|
-
let type =
|
|
540
|
+
let type = parentElementOrQueryCache;
|
|
358
541
|
while (type.items)
|
|
359
542
|
type = type.items;
|
|
360
543
|
const elem = setCache( col, '_element', type.elements[as] );
|
|
@@ -398,6 +581,7 @@ function csnRefs( csn ) {
|
|
|
398
581
|
|
|
399
582
|
// Return value of a query SELECT for the query node, or the main artifact,
|
|
400
583
|
// i.e. a value with an `elements` property.
|
|
584
|
+
// TODO: only used in forHanaNew - move somewhere else
|
|
401
585
|
/**
|
|
402
586
|
* @param {CSN.Query} query node (object with SET or SELECT property)
|
|
403
587
|
* @param {CSN.Definition} main
|
|
@@ -423,15 +607,16 @@ function queryOrMain( query, main ) {
|
|
|
423
607
|
*
|
|
424
608
|
* @param {CSN.Query} query
|
|
425
609
|
* @param {CSN.QuerySelect} fromSelect
|
|
610
|
+
* @param {CSN.Query} parentQuery
|
|
426
611
|
* @param {(query: CSN.Query&CSN.QueryFrom, select: CSN.QuerySelectEnriched) => void} callback
|
|
427
612
|
*/
|
|
428
613
|
function traverseQuery( query, fromSelect, parentQuery, callback ) {
|
|
429
|
-
|
|
614
|
+
const select = query.SELECT || query.projection;
|
|
615
|
+
if (select) {
|
|
430
616
|
callback( query, fromSelect, parentQuery );
|
|
431
|
-
const select = query.SELECT || query.projection;
|
|
432
617
|
traverseFrom( select.from, select, parentQuery, callback );
|
|
433
618
|
for (const prop of [ 'columns', 'where', 'having' ]) {
|
|
434
|
-
// all properties which
|
|
619
|
+
// all properties which can have sub queries (`join-on` also can)
|
|
435
620
|
const expr = select[prop];
|
|
436
621
|
if (expr)
|
|
437
622
|
expr.forEach( q => traverseExpr( q, query, callback ) );
|
|
@@ -440,12 +625,8 @@ function traverseQuery( query, fromSelect, parentQuery, callback ) {
|
|
|
440
625
|
else if (query.SET) {
|
|
441
626
|
callback( query, fromSelect, parentQuery );
|
|
442
627
|
const { args } = query.SET;
|
|
443
|
-
for (const q of args || [])
|
|
444
|
-
|
|
445
|
-
traverseQuery( q, fromSelect, query, callback );
|
|
446
|
-
else
|
|
447
|
-
traverseQuery( q, null, query, callback );
|
|
448
|
-
}
|
|
628
|
+
for (const q of args || [])
|
|
629
|
+
traverseQuery( q, null, query, callback );
|
|
449
630
|
}
|
|
450
631
|
}
|
|
451
632
|
|
|
@@ -502,12 +683,13 @@ function analyseCsnPath( csnPath, csn, resolve ) {
|
|
|
502
683
|
let obj = csn;
|
|
503
684
|
let parent = null;
|
|
504
685
|
let query = null;
|
|
505
|
-
let
|
|
686
|
+
let refCtx = null;
|
|
506
687
|
let art = null;
|
|
507
688
|
/** @type {boolean|string|number} */
|
|
508
689
|
let isName = false;
|
|
509
|
-
let
|
|
690
|
+
let baseRef = null;
|
|
510
691
|
let baseEnv = null;
|
|
692
|
+
let main = csn.definitions[csnPath[1]];
|
|
511
693
|
|
|
512
694
|
for (let index = 0; index < csnPath.length; index++) {
|
|
513
695
|
const prop = csnPath[index];
|
|
@@ -520,8 +702,12 @@ function analyseCsnPath( csnPath, csn, resolve ) {
|
|
|
520
702
|
isName = false;
|
|
521
703
|
}
|
|
522
704
|
else if (artifactProperties.includes( String(prop) )) {
|
|
705
|
+
if (refCtx === 'target' || refCtx === 'targetAspect') { // with 'elements'
|
|
706
|
+
main = art = obj; // $self refers to the anonymous aspect
|
|
707
|
+
parent = null;
|
|
708
|
+
}
|
|
523
709
|
isName = prop;
|
|
524
|
-
|
|
710
|
+
refCtx = prop;
|
|
525
711
|
}
|
|
526
712
|
else if (prop === 'items' || prop === 'returns') {
|
|
527
713
|
art = obj[prop];
|
|
@@ -532,41 +718,42 @@ function analyseCsnPath( csnPath, csn, resolve ) {
|
|
|
532
718
|
else if (prop === 'SELECT' || prop === 'SET' || prop === 'projection') {
|
|
533
719
|
query = obj;
|
|
534
720
|
parent = null;
|
|
535
|
-
|
|
721
|
+
baseEnv = null;
|
|
722
|
+
refCtx = prop;
|
|
536
723
|
}
|
|
537
|
-
else if (prop === 'where' &&
|
|
724
|
+
else if (prop === 'where' && refCtx === 'ref') {
|
|
538
725
|
if (resolve)
|
|
539
|
-
baseEnv = resolve.
|
|
540
|
-
|
|
541
|
-
|
|
726
|
+
baseEnv = resolve.ref_where( obj, baseRef, refCtx, csn.definitions[csnPath[1]],
|
|
727
|
+
query, parent, baseEnv );
|
|
728
|
+
refCtx = 'ref_where';
|
|
542
729
|
}
|
|
543
730
|
else if (prop === 'expand' || prop === 'inline') {
|
|
544
731
|
if (obj.ref) {
|
|
545
732
|
if (resolve)
|
|
546
|
-
baseEnv = resolve.
|
|
547
|
-
|
|
548
|
-
|
|
733
|
+
baseEnv = resolve.expandInline( obj, refCtx, csn.definitions[csnPath[1]],
|
|
734
|
+
query, parent, baseEnv );
|
|
735
|
+
refCtx = prop;
|
|
549
736
|
}
|
|
550
737
|
if (prop === 'expand')
|
|
551
738
|
isName = prop;
|
|
552
739
|
}
|
|
553
740
|
else if (prop === 'on') {
|
|
554
|
-
if (
|
|
555
|
-
|
|
556
|
-
else if (
|
|
557
|
-
|
|
741
|
+
if (refCtx === 'from')
|
|
742
|
+
refCtx = 'join_on';
|
|
743
|
+
else if (refCtx === 'mixin')
|
|
744
|
+
refCtx = 'mixin_on';
|
|
558
745
|
else
|
|
559
|
-
|
|
746
|
+
refCtx = 'on'; // will use query elements with REDIRECTED TO
|
|
560
747
|
}
|
|
561
748
|
else if (prop === 'ref') {
|
|
562
|
-
|
|
563
|
-
|
|
749
|
+
baseRef = obj; // needs to be inspected for filter conditions
|
|
750
|
+
refCtx = prop;
|
|
564
751
|
}
|
|
565
752
|
else if (prop === 'orderBy') {
|
|
566
|
-
|
|
753
|
+
refCtx = (query.SET ? 'orderBy_set' : 'orderBy');
|
|
567
754
|
}
|
|
568
755
|
else if (prop !== 'xpr') {
|
|
569
|
-
|
|
756
|
+
refCtx = prop;
|
|
570
757
|
}
|
|
571
758
|
|
|
572
759
|
obj = obj[prop];
|
|
@@ -574,10 +761,10 @@ function analyseCsnPath( csnPath, csn, resolve ) {
|
|
|
574
761
|
// For the semantic location, use current object as best guess
|
|
575
762
|
break;
|
|
576
763
|
}
|
|
577
|
-
// console.log( 'CPATH:', csnPath,
|
|
764
|
+
// console.log( 'CPATH:', csnPath, refCtx, obj, parent.$location );
|
|
578
765
|
if (!resolve)
|
|
579
766
|
return { query }; // for constructSemanticLocationFromCsnPath
|
|
580
|
-
return resolve( obj,
|
|
767
|
+
return resolve( obj, refCtx, main, query, parent, baseEnv );
|
|
581
768
|
}
|
|
582
769
|
|
|
583
770
|
module.exports = {
|