@sap/cds-compiler 3.6.2 → 3.7.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 +49 -0
- package/README.md +3 -0
- package/bin/cdsc.js +9 -5
- package/doc/CHANGELOG_BETA.md +20 -2
- package/doc/CHANGELOG_DEPRECATED.md +2 -2
- package/lib/api/main.js +2 -1
- package/lib/base/dictionaries.js +10 -0
- package/lib/base/message-registry.js +56 -12
- package/lib/base/messages.js +39 -20
- package/lib/base/model.js +1 -0
- package/lib/base/shuffle.js +2 -1
- package/lib/checks/elements.js +29 -1
- package/lib/checks/{emptyOrOnlyVirtual.js → hasPersistedElements.js} +9 -5
- package/lib/checks/nonexpandableStructured.js +1 -1
- package/lib/checks/onConditions.js +8 -5
- package/lib/checks/types.js +6 -1
- package/lib/checks/validator.js +7 -3
- package/lib/compiler/assert-consistency.js +20 -23
- package/lib/compiler/base.js +1 -2
- package/lib/compiler/builtins.js +2 -2
- package/lib/compiler/checks.js +237 -242
- package/lib/compiler/define.js +63 -75
- package/lib/compiler/extend.js +325 -22
- package/lib/compiler/finalize-parse-cdl.js +1 -55
- package/lib/compiler/kick-start.js +6 -7
- package/lib/compiler/populate.js +284 -288
- package/lib/compiler/propagator.js +15 -13
- package/lib/compiler/resolve.js +136 -306
- package/lib/compiler/shared.js +42 -44
- package/lib/compiler/tweak-assocs.js +29 -27
- package/lib/compiler/utils.js +29 -3
- package/lib/edm/annotations/genericTranslation.js +7 -13
- package/lib/edm/annotations/preprocessAnnotations.js +3 -0
- package/lib/edm/csn2edm.js +0 -4
- package/lib/edm/edm.js +6 -4
- package/lib/edm/edmAnnoPreprocessor.js +1 -0
- package/lib/edm/edmPreprocessor.js +1 -5
- package/lib/gen/Dictionary.json +34 -2
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +2429 -2401
- package/lib/inspect/inspectPropagation.js +2 -0
- package/lib/json/from-csn.js +87 -41
- package/lib/json/to-csn.js +47 -16
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +109 -28
- package/lib/language/language.g4 +20 -4
- package/lib/model/csnRefs.js +1 -1
- package/lib/model/csnUtils.js +1 -0
- package/lib/model/revealInternalProperties.js +1 -2
- package/lib/modelCompare/compare.js +2 -1
- package/lib/render/manageConstraints.js +5 -2
- package/lib/render/toCdl.js +20 -7
- package/lib/render/toHdbcds.js +2 -8
- package/lib/render/toSql.js +4 -3
- package/lib/render/utils/common.js +9 -5
- package/lib/transform/db/assertUnique.js +2 -1
- package/lib/transform/db/expansion.js +2 -0
- package/lib/transform/db/flattening.js +37 -36
- package/lib/transform/db/rewriteCalculatedElements.js +559 -0
- package/lib/transform/db/transformExists.js +4 -0
- package/lib/transform/db/views.js +40 -37
- package/lib/transform/forRelationalDB.js +38 -28
- package/lib/transform/odata/typesExposure.js +50 -15
- package/lib/transform/parseExpr.js +14 -8
- package/lib/transform/transformUtilsNew.js +6 -5
- package/lib/transform/translateAssocsToJoins.js +49 -33
- package/package.json +1 -1
package/lib/compiler/populate.js
CHANGED
|
@@ -24,7 +24,7 @@ const {
|
|
|
24
24
|
forEachGeneric,
|
|
25
25
|
} = require('../base/model');
|
|
26
26
|
const {
|
|
27
|
-
dictAdd, dictAddArray,
|
|
27
|
+
dictAdd, dictAddArray, dictFirst, dictForEach,
|
|
28
28
|
} = require('../base/dictionaries');
|
|
29
29
|
const { dictLocation } = require('../base/location');
|
|
30
30
|
const { weakLocation } = require('../base/messages');
|
|
@@ -32,7 +32,6 @@ const { CompilerAssertion } = require('../base/error');
|
|
|
32
32
|
|
|
33
33
|
const { kindProperties } = require('./base');
|
|
34
34
|
const {
|
|
35
|
-
pushLink,
|
|
36
35
|
setLink,
|
|
37
36
|
setArtifactLink,
|
|
38
37
|
annotationVal,
|
|
@@ -42,8 +41,8 @@ const {
|
|
|
42
41
|
splitIntoPath,
|
|
43
42
|
linkToOrigin,
|
|
44
43
|
setMemberParent,
|
|
44
|
+
proxyCopyMembers,
|
|
45
45
|
dependsOn,
|
|
46
|
-
traverseQueryPost,
|
|
47
46
|
setExpandStatus,
|
|
48
47
|
setExpandStatusAnnotate,
|
|
49
48
|
} = require('./utils');
|
|
@@ -66,12 +65,12 @@ function populate( model ) {
|
|
|
66
65
|
model.$volatileFunctions.environment = environment;
|
|
67
66
|
Object.assign( model.$functions, {
|
|
68
67
|
effectiveType,
|
|
69
|
-
|
|
68
|
+
getOrigin,
|
|
70
69
|
resolveType,
|
|
71
|
-
populateQuery,
|
|
72
70
|
} );
|
|
71
|
+
// let depth = 100;
|
|
73
72
|
|
|
74
|
-
|
|
73
|
+
let effectiveSeqNo = 0; // artifact number set after having set _effectiveType
|
|
75
74
|
/** @type {any} may also be a boolean */
|
|
76
75
|
let newAutoExposed = [];
|
|
77
76
|
|
|
@@ -96,146 +95,243 @@ function populate( model ) {
|
|
|
96
95
|
newAutoExposed = true; // internal error if auto-expose after here
|
|
97
96
|
return;
|
|
98
97
|
|
|
98
|
+
function traverseElementEnvironments( art ) {
|
|
99
|
+
navigationEnv( art );
|
|
100
|
+
if (art.$queries)
|
|
101
|
+
art.$queries.forEach( traverseElementEnvironments );
|
|
102
|
+
if (art.mixin)
|
|
103
|
+
dictForEach( art.mixin, navigationEnv );
|
|
104
|
+
if (art !== art._main?._leadingQuery) // already done
|
|
105
|
+
forEachMember( art, traverseElementEnvironments );
|
|
106
|
+
}
|
|
107
|
+
|
|
99
108
|
|
|
100
109
|
//--------------------------------------------------------------------------
|
|
101
110
|
// The central functions for path resolution - must work on-demand
|
|
102
111
|
//--------------------------------------------------------------------------
|
|
103
|
-
// Phase 2: call
|
|
104
|
-
|
|
105
|
-
function traverseElementEnvironments( art ) {
|
|
106
|
-
populateView( art );
|
|
107
|
-
environment( art );
|
|
108
|
-
if (art.elements$ || art.enum$)
|
|
109
|
-
mergeSpecifiedElementsOrEnum(art);
|
|
110
|
-
forEachMember( art, traverseElementEnvironments );
|
|
111
|
-
}
|
|
112
|
+
// Phase 2: call effectiveType() on-demand, which also calculates view elems
|
|
112
113
|
|
|
113
114
|
// Return effective search environment provided by artifact `art`, i.e. the
|
|
114
115
|
// `artifacts` or `elements` dictionary. For the latter, follow the `type`
|
|
115
116
|
// chain and resolve the association `target`. View elements are calculated
|
|
116
117
|
// on demand.
|
|
117
118
|
function environment( art, location, user, assocSpec ) {
|
|
118
|
-
if (!art)
|
|
119
|
-
return Object.create(null);
|
|
120
119
|
const env = navigationEnv( art, location, user, assocSpec );
|
|
120
|
+
if (env === 0)
|
|
121
|
+
return 0;
|
|
121
122
|
return env && env.elements || Object.create(null);
|
|
122
123
|
}
|
|
123
124
|
|
|
124
125
|
function navigationEnv( art, location, user, assocSpec ) {
|
|
125
|
-
|
|
126
|
-
//
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
126
|
+
// = effectiveType() on from-path, TODO: should actually already part of
|
|
127
|
+
// resolvePath() on FROM
|
|
128
|
+
if (!art)
|
|
129
|
+
return undefined;
|
|
130
|
+
let type = effectiveType( art );
|
|
131
|
+
while (type?.items) // TODO: disallow navigation to many sometimes
|
|
132
|
+
type = effectiveType( type.items );
|
|
133
|
+
if (!type?.target)
|
|
134
|
+
return type;
|
|
135
|
+
|
|
136
|
+
if (assocSpec === false) {
|
|
135
137
|
// TODO: combine this with setTargetReferenceKey&Co in getPathItem?
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
138
|
+
error( null, [ location, user ], {},
|
|
139
|
+
'Following an association is not allowed in an association key definition' );
|
|
140
|
+
} // TODO: else warning for assoc usage with falsy assocSpec
|
|
141
|
+
const target = resolvePath( type.target, 'target', type );
|
|
142
|
+
if (!target)
|
|
143
|
+
return target;
|
|
144
|
+
if (target && assocSpec && user)
|
|
145
|
+
dependsOn( user, target, location || user.location );
|
|
146
|
+
const effectiveTarget = effectiveType( target );
|
|
147
|
+
if (effectiveTarget === 0 && location)
|
|
148
|
+
dependsOn( user, user, (user.target || user.type || user.value || user).location );
|
|
149
|
+
// console.log('NT:',assocSpec,!!user,target)
|
|
150
|
+
return effectiveTarget;
|
|
146
151
|
}
|
|
147
152
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
153
|
+
/**
|
|
154
|
+
* Return the artifact having properties which are relevant for further name
|
|
155
|
+
* resolution on `art`: `target`, `elements`, `items`, also `enum`. Make sure
|
|
156
|
+
* that these properties actually exist, are complete and auto-corrected. Cache
|
|
157
|
+
* the result in property `_effectiveType`.
|
|
158
|
+
*
|
|
159
|
+
* - actions, functions: returns `art`, might have expanded `params`/`returns`
|
|
160
|
+
* - artifacts with direct or inherited `target`, `elements`, `items`, `enum`:
|
|
161
|
+
* returns `art`, these properties might have been auto-redirected / expanded
|
|
162
|
+
* - artifacts with direct or inherited scalar type: the built-in type
|
|
163
|
+
* - other artifacts: the last artifact in the origin-chain, i.e. the one which
|
|
164
|
+
* has neither a type nor some value path.
|
|
165
|
+
* - returns 0 with cyclic dependencies (with recursive element expansions, we
|
|
166
|
+
* have `elements: 0` instead).
|
|
167
|
+
* - returns null if a relevant reference points to nothing or is corrupted
|
|
168
|
+
* - returns false if a relevant reference points to a duplicate definition
|
|
169
|
+
*
|
|
170
|
+
* This function also infers type relevant properties:
|
|
171
|
+
*
|
|
172
|
+
* - views and queries: returns `art` with inferred query `elements`
|
|
173
|
+
* - column with `expand`: returns `art`, usually with inferred `elements`/`items`
|
|
174
|
+
* - more to come
|
|
175
|
+
*
|
|
176
|
+
* At the moment, it is assumed that includes, expansions, and localized has
|
|
177
|
+
* been applied earlier.
|
|
178
|
+
*
|
|
179
|
+
* Properties which are (usually) not relevant for the name resolution, like
|
|
180
|
+
* `length` and `cardinality`, cannot be simply accessed on the effective
|
|
181
|
+
* artifact. The effective artifact alone is not enough to check whether an
|
|
182
|
+
* artifact is an association or composition; it also does not give you the
|
|
183
|
+
* information about the technical base type of an enum.
|
|
184
|
+
*
|
|
185
|
+
* Calculating an effective association/composition does not imply calculating
|
|
186
|
+
* its effective target. Calculating an effective structure (entities, …)
|
|
187
|
+
* does not imply calculating the effective types of its elements. Calculating
|
|
188
|
+
* an effective array does not imply calculating its effective line type.
|
|
189
|
+
*/
|
|
159
190
|
function effectiveType( art ) {
|
|
191
|
+
if (!art)
|
|
192
|
+
return art;
|
|
193
|
+
// if (--depth) throw Error(`ET: ${ Object.keys(art) }`)
|
|
160
194
|
if ('_effectiveType' in art)
|
|
161
195
|
return art._effectiveType;
|
|
162
196
|
|
|
163
197
|
// console.log(message( null, art.location, art, {}, 'Info','FT').toString())
|
|
164
198
|
const chain = [];
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
!art.target && !art.enum && !art.elements && !art.items) {
|
|
168
|
-
chain.push( art );
|
|
199
|
+
// console.log( 'ET-START:', art.kind, art.name )
|
|
200
|
+
while (art && !('_effectiveType' in art)) {
|
|
169
201
|
setLink( art, '_effectiveType', 0 ); // initial setting in case of cycles
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if ('_effectiveType' in art) { // is the case for builtins
|
|
174
|
-
art = art._effectiveType;
|
|
175
|
-
}
|
|
176
|
-
else {
|
|
177
|
-
setLink( art, '_effectiveType', art );
|
|
178
|
-
if (art.expand && !art.value && !art.elements)
|
|
179
|
-
initFromColumns( art, art.expand );
|
|
180
|
-
// When not blocked (by future origin = false) and not REDIRECTED TO and not MIXIN
|
|
181
|
-
// try to implicitly redirect explicitly provided target:
|
|
182
|
-
else if (art.target && art._origin == null && !art.value && art.kind !== 'mixin')
|
|
183
|
-
redirectImplicitly( art, art );
|
|
184
|
-
}
|
|
202
|
+
chain.push( art );
|
|
203
|
+
art = getOrigin( art );
|
|
204
|
+
// console.log( 'ET-GO:', art?.name )
|
|
185
205
|
}
|
|
206
|
+
if (art)
|
|
207
|
+
art = art._effectiveType;
|
|
208
|
+
if (art === 0)
|
|
209
|
+
return art;
|
|
210
|
+
|
|
186
211
|
chain.reverse();
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
212
|
+
for (const a of chain) {
|
|
213
|
+
// console.log( 'ET-DO:', a.name, art?.kind )
|
|
214
|
+
// Without type, value.path or _origin at beginning, link to itself:
|
|
215
|
+
art = populateArtifact( a, art ) || a;
|
|
216
|
+
if (a.elements$ || a.enum$)
|
|
217
|
+
mergeSpecifiedElementsOrEnum( a );
|
|
218
|
+
setLink( a, '_effectiveType', art );
|
|
219
|
+
a.$effectiveSeqNo = ++effectiveSeqNo;
|
|
190
220
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
221
|
+
// console.log( 'ET-END:', art?.kind, art?.name )
|
|
222
|
+
return art;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function populateArtifact( art, origEffective ) {
|
|
226
|
+
// Name-resolution relevant properties directly at artifact:
|
|
227
|
+
// ‹view›.elements of input must have been moved (to elements$) before!
|
|
228
|
+
// console.log('Q:',art.elements,art.enum,art.items,!!art.query)
|
|
229
|
+
if (art.elements != null || art.enum != null || art.items != null)
|
|
230
|
+
return art;
|
|
231
|
+
if (art.target) {
|
|
232
|
+
// try to implicitly redirect explicitly provided target:
|
|
233
|
+
if (!origEffective?.target && art.kind !== 'mixin')
|
|
234
|
+
redirectImplicitly( art, art );
|
|
235
|
+
if (!art.expand)
|
|
236
|
+
return art;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// With properties to be calculated: ----------------------------------------
|
|
240
|
+
if (art.query && art.kind !== '$tableAlias') { // query entity
|
|
241
|
+
const leading = art.$queries[0];
|
|
242
|
+
if (!leading) // parse error
|
|
243
|
+
return null;
|
|
244
|
+
// assert that there is no _effectiveType on leading (should be the case, as
|
|
245
|
+
// you cannot refer to a query of another artifact)
|
|
246
|
+
populateQuery( leading );
|
|
247
|
+
setLink( leading, '_effectiveType', leading );
|
|
248
|
+
leading.$effectiveSeqNo = ++effectiveSeqNo;
|
|
249
|
+
return art;
|
|
250
|
+
}
|
|
251
|
+
if (art.from)
|
|
252
|
+
return populateQuery( art );
|
|
253
|
+
|
|
254
|
+
if (art.expand) {
|
|
255
|
+
// TODO: test that there is no CDL-style cast with expand
|
|
256
|
+
// (we could allow that later: then some basic structural check is needed)
|
|
257
|
+
if (!art.value) {
|
|
258
|
+
initFromColumns( art, art.expand );
|
|
259
|
+
if (origEffective?.target) // consider `{ … } as x: AssocType
|
|
260
|
+
redirectImplicitly( art, origEffective );
|
|
205
261
|
}
|
|
262
|
+
else if (art.value.path) {
|
|
263
|
+
expandFromColumns( art );
|
|
264
|
+
}
|
|
265
|
+
// TODO: if we allow CDL-style cast with expand in the future, we need to
|
|
266
|
+
// redirectImplicitly when casting to assoc type
|
|
267
|
+
return art;
|
|
206
268
|
}
|
|
207
|
-
|
|
269
|
+
if (!origEffective || origEffective.builtin) // TODO: builtin test needed?
|
|
270
|
+
return origEffective;
|
|
271
|
+
|
|
272
|
+
// With inherited auto-corrected name-resolution-relevant properties: -------
|
|
273
|
+
if (origEffective.target)
|
|
274
|
+
return redirectImplicitly( art, origEffective ) ? art : origEffective;
|
|
275
|
+
if (origEffective.elements)
|
|
276
|
+
return expandElements( art, origEffective ) ? art : origEffective;
|
|
277
|
+
if (origEffective.enum)
|
|
278
|
+
return expandEnum( art, origEffective ) ? art : origEffective;
|
|
279
|
+
if (origEffective.items)
|
|
280
|
+
return expandItems( art, origEffective ) ? art : origEffective;
|
|
281
|
+
if (origEffective.params || origEffective.returns)
|
|
282
|
+
return expandParams( art, origEffective );
|
|
283
|
+
return origEffective;
|
|
208
284
|
}
|
|
209
285
|
|
|
210
286
|
// TODO: test it in combination with top-level CAST function
|
|
211
|
-
function
|
|
287
|
+
// TODO: we could probably "extend" this function to all other cases where we
|
|
288
|
+
// set an _origin in Universal CSN
|
|
289
|
+
|
|
290
|
+
// TODO: add 2nd arg `considerSecondary` used in effectiveType(): prefers a
|
|
291
|
+
// predecessor without _effectiveType (includes, joins)
|
|
292
|
+
function getOrigin( art ) {
|
|
212
293
|
// Be careful when using it with art.target or art.enum or art.elements
|
|
213
|
-
if (art
|
|
294
|
+
if (!art)
|
|
295
|
+
return undefined; // TODO: null?
|
|
296
|
+
// if (--depth) throw Error(`GOR: ${ Object.keys(art) }`)
|
|
297
|
+
if ('_origin' in art)
|
|
214
298
|
return art._origin;
|
|
215
|
-
if (art.type)
|
|
299
|
+
if (art.type) // not stored in _origin
|
|
216
300
|
return resolveType( art.type, art );
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
return art._origin;
|
|
301
|
+
return setLink( art, '_origin', getOriginRaw( art ) );
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function getOriginRaw( art ) {
|
|
305
|
+
if (!art._main) {
|
|
306
|
+
if (art.query)
|
|
307
|
+
return getOrigin( art.$queries?.[0] );
|
|
225
308
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
309
|
+
else {
|
|
310
|
+
if (art.value?.path)
|
|
311
|
+
return resolvePath( art.value, 'expr', art, null );
|
|
312
|
+
if (art.kind === 'select')
|
|
313
|
+
return getOrigin( dictFirst( art.$tableAliases ) );
|
|
314
|
+
// init sets _origin for alias to sub query, only need to handle ref here:
|
|
315
|
+
if (art.kind === '$tableAlias') {
|
|
316
|
+
// do not use navigationEnv(): it would always call effectiveType() on the
|
|
317
|
+
// source → we would would have a deeper callstack
|
|
318
|
+
const source = resolvePath( art, 'from', art._parent );
|
|
319
|
+
if (!source?._main)
|
|
320
|
+
return source; // direct entity (or undefined)
|
|
321
|
+
// Before having done the resolvePath cleanup, do not rely on resolvePath
|
|
322
|
+
// to call effectiveType() on the last assoc of a from ref:
|
|
323
|
+
const assoc = effectiveType( source );
|
|
324
|
+
return resolvePath( assoc?.target, 'target', assoc );
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return '';
|
|
234
328
|
}
|
|
235
329
|
|
|
236
330
|
function resolveType( ref, user ) {
|
|
237
331
|
if ('_artifact' in ref)
|
|
238
332
|
return ref._artifact;
|
|
333
|
+
while (user._outer) // in items
|
|
334
|
+
user = user._outer;
|
|
239
335
|
if (ref.scope === 'typeOf') {
|
|
240
336
|
let struct = user;
|
|
241
337
|
while (struct.kind === 'element')
|
|
@@ -255,8 +351,6 @@ function populate( model ) {
|
|
|
255
351
|
}
|
|
256
352
|
return resolvePath( ref, 'typeOf', user );
|
|
257
353
|
}
|
|
258
|
-
while (user._outer) // in items
|
|
259
|
-
user = user._outer;
|
|
260
354
|
if (user.kind === 'event')
|
|
261
355
|
return resolvePath( ref, 'eventType', user );
|
|
262
356
|
if (user.kind === 'param' && user._parent &&
|
|
@@ -265,18 +359,18 @@ function populate( model ) {
|
|
|
265
359
|
return resolvePath( ref, 'type', user );
|
|
266
360
|
}
|
|
267
361
|
|
|
268
|
-
function getCardinality(
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
if (
|
|
272
|
-
return
|
|
273
|
-
|
|
362
|
+
function getCardinality( assoc ) {
|
|
363
|
+
while (assoc?._effectiveType) {
|
|
364
|
+
// if (--depth) throw Error(`GCARD: ${ Object.keys(art) }`)
|
|
365
|
+
if (assoc.cardinality)
|
|
366
|
+
return assoc.cardinality;
|
|
367
|
+
assoc = getOrigin( assoc );
|
|
274
368
|
}
|
|
275
369
|
return {};
|
|
276
370
|
}
|
|
277
371
|
|
|
278
372
|
function userQuery( user ) {
|
|
279
|
-
// TODO: we
|
|
373
|
+
// TODO: should we set _query links in define.js?
|
|
280
374
|
while (user._main) {
|
|
281
375
|
if (user.kind === 'select' || user.kind === '$join')
|
|
282
376
|
return user;
|
|
@@ -285,7 +379,7 @@ function populate( model ) {
|
|
|
285
379
|
return null;
|
|
286
380
|
}
|
|
287
381
|
|
|
288
|
-
//
|
|
382
|
+
// Expansions --------------------------------------------------------------
|
|
289
383
|
|
|
290
384
|
|
|
291
385
|
function expandItems( art, origin ) {
|
|
@@ -307,6 +401,10 @@ function populate( model ) {
|
|
|
307
401
|
}
|
|
308
402
|
|
|
309
403
|
function expandElements( art, struct ) {
|
|
404
|
+
if (art.kind === '$tableAlias') {
|
|
405
|
+
proxyCopyMembers( art, 'elements', struct.elements, art.path?.location, '$navElement' );
|
|
406
|
+
return true;
|
|
407
|
+
}
|
|
310
408
|
if (art.elements || art.kind === '$tableAlias' || art.kind === '$inline' ||
|
|
311
409
|
// no element expansions for "non-proper" types like
|
|
312
410
|
// entities (as parameter types) etc:
|
|
@@ -321,15 +419,7 @@ function populate( model ) {
|
|
|
321
419
|
const location = ref && ref.location || art.location;
|
|
322
420
|
// console.log( message( null, location, art, {target:struct,art}, 'Info','EXPAND-ELEM')
|
|
323
421
|
// .toString(), Object.keys(struct.elements))
|
|
324
|
-
|
|
325
|
-
const orig = struct.elements[name];
|
|
326
|
-
// const orig = elem.kind === '$navElement'
|
|
327
|
-
if (Array.isArray( orig )) // redefinitions
|
|
328
|
-
continue;
|
|
329
|
-
linkToOrigin( orig, name, art, 'elements', weakLocation( location ), true )
|
|
330
|
-
// or should we use orig.location? - TODO: try to find test to see message
|
|
331
|
-
.$inferred = 'expanded';
|
|
332
|
-
}
|
|
422
|
+
proxyCopyMembers( art, 'elements', struct.elements, weakLocation( location ) );
|
|
333
423
|
// Set elements expansion status (the if condition is always true, as no
|
|
334
424
|
// elements expansion will take place on artifact with existing other
|
|
335
425
|
// member property):
|
|
@@ -344,13 +434,7 @@ function populate( model ) {
|
|
|
344
434
|
return false;
|
|
345
435
|
const ref = art.type || art.value || art.name;
|
|
346
436
|
const location = weakLocation( ref && ref.location || art.location );
|
|
347
|
-
art.enum
|
|
348
|
-
for (const name in origin.enum) {
|
|
349
|
-
const orig = origin.enum[name];
|
|
350
|
-
linkToOrigin( orig, name, art, 'enum', location, true )
|
|
351
|
-
// or should we use orig.location? - TODO: try to find test to see message
|
|
352
|
-
.$inferred = 'expanded';
|
|
353
|
-
}
|
|
437
|
+
proxyCopyMembers( art, 'enum', origin.enum, weakLocation( location ) );
|
|
354
438
|
// Set elements expansion status (the if condition is always true, as no
|
|
355
439
|
// elements expansion will take place on artifact with existing other
|
|
356
440
|
// member property):
|
|
@@ -360,6 +444,30 @@ function populate( model ) {
|
|
|
360
444
|
return true;
|
|
361
445
|
}
|
|
362
446
|
|
|
447
|
+
function expandParams( art, origin ) {
|
|
448
|
+
if (!origin._main)
|
|
449
|
+
return origin; // not with entity (should not happen)
|
|
450
|
+
if (origin.params)
|
|
451
|
+
proxyCopyMembers( art, 'params', origin.params, null );
|
|
452
|
+
|
|
453
|
+
if (origin.returns) {
|
|
454
|
+
// TODO: make linkToOrigin() work for returns, kind/name?
|
|
455
|
+
const location = weakLocation( origin.returns.location );
|
|
456
|
+
art.returns = {
|
|
457
|
+
name: Object.assign( {}, art.name, { id: '', param: '', location } ),
|
|
458
|
+
kind: 'param',
|
|
459
|
+
location,
|
|
460
|
+
$inferred: 'expanded',
|
|
461
|
+
};
|
|
462
|
+
setLink( art.returns, '_parent', art );
|
|
463
|
+
setLink( art.returns, '_main', art._main || art );
|
|
464
|
+
setLink( art.returns, '_origin', origin.returns );
|
|
465
|
+
}
|
|
466
|
+
if (!art.$expand)
|
|
467
|
+
art.$expand = 'origin'; // if value stays, elements won't appear in CSN
|
|
468
|
+
return art;
|
|
469
|
+
}
|
|
470
|
+
|
|
363
471
|
/**
|
|
364
472
|
* Return true iff `art` is from a recursive expansion, i.e. if any of its
|
|
365
473
|
* expanded parents (including _outer) has the same non-expansion-origin.
|
|
@@ -403,48 +511,9 @@ function populate( model ) {
|
|
|
403
511
|
// Views
|
|
404
512
|
//--------------------------------------------------------------------------
|
|
405
513
|
|
|
406
|
-
//
|
|
407
|
-
//
|
|
408
|
-
//
|
|
409
|
-
function populateView( art ) {
|
|
410
|
-
if (!art._from || art._status === '_query')
|
|
411
|
-
return;
|
|
412
|
-
const resolveChain = [];
|
|
413
|
-
const fromChain = [ art ];
|
|
414
|
-
while (fromChain.length) {
|
|
415
|
-
const view = fromChain.pop();
|
|
416
|
-
if (view._status === '_query') // already fully resolved (status at def)
|
|
417
|
-
continue;
|
|
418
|
-
resolveChain.push( view );
|
|
419
|
-
for (const from of view._from) {
|
|
420
|
-
if (from._status) // status at the ref -> illegal recursion -> stop
|
|
421
|
-
continue;
|
|
422
|
-
setLink( from, '_status', '_query' );
|
|
423
|
-
// setLink before resolvePath - Cycle: view V as select from V.toV
|
|
424
|
-
let source = resolvePath( from, 'from', view ); // filter and args in resolveQuery
|
|
425
|
-
// console.log('ST:',msgName(source),from._status)
|
|
426
|
-
if (source && source._main) { // element -> should be assoc
|
|
427
|
-
const type = effectiveType( source );
|
|
428
|
-
source = type && type.target;
|
|
429
|
-
}
|
|
430
|
-
if (source && source._from && source._status !== '_query')
|
|
431
|
-
fromChain.push( source );
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
// console.log( resolveChain.map( v => msgName(v)+v._status ) );
|
|
435
|
-
resolveChain.reverse();
|
|
436
|
-
for (const view of resolveChain) {
|
|
437
|
-
if (view._status !== '_query' ) { // not already resolved
|
|
438
|
-
setLink( view, '_status', '_query' );
|
|
439
|
-
// must be run in order “sub query in FROM first”:
|
|
440
|
-
traverseQueryPost( view.query, null, populateQuery );
|
|
441
|
-
if (!view.$entity) {
|
|
442
|
-
model._entities.push( view );
|
|
443
|
-
view.$entity = ++model.$entity;
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
}
|
|
514
|
+
// TODO: delete XSN._entities
|
|
515
|
+
// TODO: delete ENTITY._from
|
|
516
|
+
// TODO (after on-demand ext): delete XSN.$entity
|
|
448
517
|
|
|
449
518
|
/**
|
|
450
519
|
* Merge _specified_ elements with _inferred_ elements in the given view/element,
|
|
@@ -499,6 +568,7 @@ function populate( model ) {
|
|
|
499
568
|
for (const id in art.elements$) {
|
|
500
569
|
const selem = art.elements$[id]; // specified element
|
|
501
570
|
if (!selem.$replacement) {
|
|
571
|
+
// console.log( 'QED:', art.name, art.kind, art.elements )
|
|
502
572
|
error( 'query-unspecified-element', [ selem.name.location, selem ], { id },
|
|
503
573
|
'Element $(ID) does not result from the query' );
|
|
504
574
|
}
|
|
@@ -508,7 +578,7 @@ function populate( model ) {
|
|
|
508
578
|
function populateQuery( query ) {
|
|
509
579
|
if (query._combined || !query.from || !query.$tableAliases)
|
|
510
580
|
// already done or $join query or parse error
|
|
511
|
-
return;
|
|
581
|
+
return query;
|
|
512
582
|
setLink( query, '_combined', Object.create(null) );
|
|
513
583
|
query.$inlines = [];
|
|
514
584
|
forEachGeneric( query, '$tableAliases', resolveTabRef );
|
|
@@ -518,35 +588,18 @@ function populate( model ) {
|
|
|
518
588
|
for (const name in query.excludingDict)
|
|
519
589
|
resolveExcluding( name, query._combined, query.excludingDict, query );
|
|
520
590
|
}
|
|
521
|
-
return;
|
|
591
|
+
return query;
|
|
522
592
|
|
|
523
593
|
function resolveTabRef( alias ) {
|
|
594
|
+
// effectiveType() must not be called on $self, is unnecessary for mixins:
|
|
595
|
+
// TODO: have a test for `select from E { a, $self.a as b, $self.{ b as c } }`
|
|
596
|
+
// TODO: have a negative test for `select from E { $self.*, assoc.* }`
|
|
597
|
+
// (we might have those already)
|
|
524
598
|
if (alias.kind === 'mixin' || alias.kind === '$self')
|
|
525
599
|
return;
|
|
526
|
-
if (!alias.elements)
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
// // if (tab) setLink( alias, '_effectiveType', alias );
|
|
530
|
-
// const elements = alias.query ? alias.query.elements : environment( tab );
|
|
531
|
-
if (!('_origin' in alias) && alias.path) {
|
|
532
|
-
const tab = resolvePath( alias, 'from', query );
|
|
533
|
-
setLink( alias, '_origin', tab && navigationEnv( tab ) );
|
|
534
|
-
}
|
|
535
|
-
alias.elements = Object.create(null); // Set explicitly, as...
|
|
536
|
-
// ...with circular dep to source, no elements can be found.
|
|
537
|
-
const location = alias.path && alias.path.location;
|
|
538
|
-
const qtab = alias._origin;
|
|
539
|
-
if (!qtab || !qtab.elements)
|
|
540
|
-
return;
|
|
541
|
-
forEachGeneric( qtab, 'elements', ( origin, name ) => {
|
|
542
|
-
const elem = linkToOrigin( origin, name, alias, 'elements',
|
|
543
|
-
location || origin.name.location );
|
|
544
|
-
elem.kind = '$navElement';
|
|
545
|
-
// elem.name.select = query.name.select;
|
|
546
|
-
if (origin.masked)
|
|
547
|
-
elem.masked = Object.assign( { $inferred: 'nav' }, origin.masked );
|
|
548
|
-
});
|
|
549
|
-
}
|
|
600
|
+
if (!alias.elements) // could be false in hierarchical JOIN - TODO: necessary?
|
|
601
|
+
effectiveType( alias ); // element → $navElement expansion for $tableAlias
|
|
602
|
+
|
|
550
603
|
forEachGeneric( { elements: alias.elements }, 'elements', ( elem, name ) => {
|
|
551
604
|
if (elem.$duplicates !== true)
|
|
552
605
|
dictAddArray( query._combined, name, elem, null ); // not dictAdd()
|
|
@@ -568,14 +621,16 @@ function populate( model ) {
|
|
|
568
621
|
|
|
569
622
|
// query columns -----------------------------------------------------------
|
|
570
623
|
|
|
571
|
-
function expandFromColumns( elem
|
|
572
|
-
const path = elem.value
|
|
624
|
+
function expandFromColumns( elem ) {
|
|
625
|
+
const path = elem.value?.path;
|
|
573
626
|
if (!path || path.broken)
|
|
574
627
|
return null;
|
|
575
|
-
|
|
628
|
+
// If we allow CDL-style casts of `expand`s to associations in the future, we
|
|
629
|
+
// need to ignore an explicit type, i.e. not getOrigin():
|
|
630
|
+
const assoc = resolvePath( elem.value, 'expr', elem, null );
|
|
631
|
+
if (!effectiveType( assoc )?.target)
|
|
576
632
|
return initFromColumns( elem, elem.expand );
|
|
577
|
-
const { targetMax } = path[path.length - 1].cardinality ||
|
|
578
|
-
(cardinality instanceof Function ? cardinality() : cardinality);
|
|
633
|
+
const { targetMax } = path[path.length - 1].cardinality || getCardinality( assoc );
|
|
579
634
|
if (targetMax && (targetMax.val === '*' || targetMax.val > 1))
|
|
580
635
|
elem.items = { location: dictLocation( elem.expand ) }; // TODO: array location
|
|
581
636
|
return initFromColumns( elem, elem.expand );
|
|
@@ -623,7 +678,7 @@ function populate( model ) {
|
|
|
623
678
|
setMemberParent( col, id, query );
|
|
624
679
|
}
|
|
625
680
|
}
|
|
626
|
-
forEachGeneric( query, 'elements',
|
|
681
|
+
forEachGeneric( query, 'elements', initElem );
|
|
627
682
|
return true;
|
|
628
683
|
}
|
|
629
684
|
|
|
@@ -655,45 +710,20 @@ function populate( model ) {
|
|
|
655
710
|
return '';
|
|
656
711
|
}
|
|
657
712
|
|
|
658
|
-
function initElem( elem
|
|
713
|
+
function initElem( elem ) {
|
|
714
|
+
// TODO: we could share code with initMembers/init() in define.js
|
|
659
715
|
if (elem.type && !elem.type.$inferred)
|
|
660
|
-
return; // explicit type -> enough or
|
|
716
|
+
return; // explicit type -> enough or getOrigin()
|
|
661
717
|
if (elem.$inferred) {
|
|
662
718
|
// redirectImplicitly( elem, elem._origin );
|
|
663
719
|
return;
|
|
664
720
|
}
|
|
665
|
-
if (!elem.
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
const env = columnEnv( elem._pathHead, query );
|
|
670
|
-
const origin = setLink( elem, '_origin',
|
|
671
|
-
resolvePath( elem.value, 'expr', elem, env ) );
|
|
672
|
-
// console.log( message( null, elem.location, elem, {art:query}, 'Info','RED').toString(),
|
|
673
|
-
// elem.value)
|
|
674
|
-
// TODO: make this resolvePath() also part of directType() ?!
|
|
675
|
-
if (!origin || elem.expand)
|
|
676
|
-
return;
|
|
677
|
-
// TODO: or should we push elems with `expand` sibling to extra list for better messages?
|
|
678
|
-
if (elem.foreignKeys) { // REDIRECTED with explicit foreign keys
|
|
679
|
-
forEachGeneric( elem, 'foreignKeys', (key, name) => initKey( key, name, elem ) );
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
// now set things which are necessary for later sub phases:
|
|
683
|
-
const nav = pathNavigation( elem.value );
|
|
684
|
-
const item = elem.value.path[elem.value.path.length - 1];
|
|
685
|
-
if (nav.navigation && nav.item === item) {
|
|
686
|
-
// sourceElem, alias.sourceElem, mixin:
|
|
687
|
-
// redirectImplicitly( elem, origin );
|
|
688
|
-
pushLink( nav.navigation, '_projections', elem );
|
|
689
|
-
}
|
|
690
|
-
else if (elem._pathHead?.kind === '$inline' && elem.value.path.length === 1) {
|
|
691
|
-
const hpath = elem._pathHead.value?.path;
|
|
692
|
-
const head = hpath?.length === 1 && hpath[0]._navigation;
|
|
693
|
-
// Alias .{ elem } - but not with Alias.{ $magic }: consider for ON-rewrite
|
|
694
|
-
if (head?.kind === '$tableAlias' && item.id.charAt(0) !== '$')
|
|
695
|
-
pushLink( head.elements[item.id], '_projections', elem );
|
|
721
|
+
if (!elem.type && elem.value?.type) { // top-level CAST( expr AS type )
|
|
722
|
+
if (!elem.target) // TODO: we might issue an error if there is a target
|
|
723
|
+
elem.type = { ...elem.value.type, $inferred: 'cast' };
|
|
696
724
|
}
|
|
725
|
+
if (elem.foreignKeys) // REDIRECTED with explicit foreign keys
|
|
726
|
+
forEachGeneric( elem, 'foreignKeys', (key, name) => initKey( key, name, elem ) );
|
|
697
727
|
}
|
|
698
728
|
|
|
699
729
|
function initKey( key, name, elem ) {
|
|
@@ -733,8 +763,8 @@ function populate( model ) {
|
|
|
733
763
|
// console.log('S1:',location.line,location.col,
|
|
734
764
|
// envParent&&!!envParent._origin&&envParent._origin.name)
|
|
735
765
|
const env = columnEnv( envParent, query );
|
|
736
|
-
// console.log('S2:',location.line,location.col,
|
|
737
|
-
// envParent
|
|
766
|
+
// if (envParent) console.log('S2:',location.line,location.col,
|
|
767
|
+
// envParent?.name,envParent?._origin?.name,
|
|
738
768
|
// Object.keys(env),Object.keys(elements))
|
|
739
769
|
for (const name in env) {
|
|
740
770
|
const navElem = env[name];
|
|
@@ -793,8 +823,9 @@ function populate( model ) {
|
|
|
793
823
|
}
|
|
794
824
|
|
|
795
825
|
function columnEnv( envParent, query ) { // etc. wildcard._pathHead;
|
|
826
|
+
// if (envParent) console.log( 'CE:', envParent._origin, query );
|
|
796
827
|
return (envParent)
|
|
797
|
-
? environment(
|
|
828
|
+
? environment( getOrigin( envParent ) ) // not the col with expand, but the referred
|
|
798
829
|
: userQuery( query )._combined;
|
|
799
830
|
}
|
|
800
831
|
|
|
@@ -830,9 +861,9 @@ function populate( model ) {
|
|
|
830
861
|
setArtifactLink( path[0], origin );
|
|
831
862
|
setLink( queryElem, '_origin', origin );
|
|
832
863
|
// set _projections when inline with table alias:
|
|
833
|
-
const alias = pathHead?.value?.path?.[0]?._navigation;
|
|
834
|
-
if (alias?.kind === '$tableAlias')
|
|
835
|
-
|
|
864
|
+
// const alias = pathHead?.value?.path?.[0]?._navigation;
|
|
865
|
+
// if (alias?.kind === '$tableAlias')
|
|
866
|
+
// pushLink( alias.elements[name], '_projections', queryElem );
|
|
836
867
|
}
|
|
837
868
|
|
|
838
869
|
// called by expandWildcard():
|
|
@@ -847,7 +878,7 @@ function populate( model ) {
|
|
|
847
878
|
setArtifactLink( path[1], sourceElem );
|
|
848
879
|
// TODO: or should we set the _artifact/_effectiveType directly to the target?
|
|
849
880
|
setArtifactLink( queryElem.value, sourceElem );
|
|
850
|
-
pushLink( navElem, '_projections', queryElem );
|
|
881
|
+
// pushLink( navElem, '_projections', queryElem );
|
|
851
882
|
// TODO: _effectiveType?
|
|
852
883
|
}
|
|
853
884
|
|
|
@@ -1013,18 +1044,20 @@ function populate( model ) {
|
|
|
1013
1044
|
const exposed = preferred.length ? preferred : descendants;
|
|
1014
1045
|
if (exposed.length < 2)
|
|
1015
1046
|
return exposed || [];
|
|
1016
|
-
let min =
|
|
1047
|
+
let min = [];
|
|
1017
1048
|
for (const e of exposed) {
|
|
1018
|
-
if (
|
|
1019
|
-
min = e;
|
|
1049
|
+
if (min.every( m => m._ancestors?.includes( e ))) {
|
|
1050
|
+
min = [ e ];
|
|
1020
1051
|
}
|
|
1021
|
-
else if (
|
|
1052
|
+
else if (min.length !== 1 || !e._ancestors?.includes( min[0] )) {
|
|
1053
|
+
if (elemScope === '' && options.testMode)
|
|
1054
|
+
throw new CompilerAssertion( `Scope for ${ target } in service ${ service } is empty`);
|
|
1022
1055
|
if (elemScope === '')
|
|
1023
1056
|
return [];
|
|
1024
|
-
|
|
1057
|
+
min.push( e );
|
|
1025
1058
|
}
|
|
1026
1059
|
}
|
|
1027
|
-
return
|
|
1060
|
+
return min;
|
|
1028
1061
|
}
|
|
1029
1062
|
|
|
1030
1063
|
// Scoped redirections -----------------------------------------------------
|
|
@@ -1253,48 +1286,11 @@ function populate( model ) {
|
|
|
1253
1286
|
setLink( art, '_service', service );
|
|
1254
1287
|
setLink( art, '_block', model.$internal );
|
|
1255
1288
|
initArtifact( art, !!autoexposed );
|
|
1256
|
-
|
|
1257
|
-
populateView( art );
|
|
1289
|
+
effectiveType( art ); // TODO: necessary? see traverseElementEnvironments()
|
|
1258
1290
|
// TODO: try to set locations of elements locations of orig target elements
|
|
1259
1291
|
newAutoExposed.push( art );
|
|
1260
1292
|
return art;
|
|
1261
1293
|
}
|
|
1262
1294
|
}
|
|
1263
1295
|
|
|
1264
|
-
// Return condensed info about reference in select item
|
|
1265
|
-
// - tableAlias.elem -> { navigation: navElem, item: path[1], tableAlias }
|
|
1266
|
-
// - sourceElem (in query) -> { navigation: navElem, item: path[0], tableAlias }
|
|
1267
|
-
// - mixinElem -> { navigation: mixinElement, item: path[0] }
|
|
1268
|
-
// - $projection.elem -> also $self.item -> { item: path[1], tableAlias: $self }
|
|
1269
|
-
// - $self -> { item: undefined, tableAlias: $self }
|
|
1270
|
-
// - $parameters.P, :P -> {}
|
|
1271
|
-
// - $now, current_date -> {}
|
|
1272
|
-
// - undef, redef -> {}
|
|
1273
|
-
// With 'navigation': store that navigation._artifact is projected
|
|
1274
|
-
// With 'navigation': rewrite its ON condition
|
|
1275
|
-
// With navigation: Do KEY propagation
|
|
1276
|
-
//
|
|
1277
|
-
// TODO: copy of fn in resolve.js; used just once here - do it differently here
|
|
1278
|
-
// and then delete the function here
|
|
1279
|
-
function pathNavigation( ref ) {
|
|
1280
|
-
// currently, indirectly projectable elements are not included - we might
|
|
1281
|
-
// keep it this way! If we want them to be included - be aware: cycles
|
|
1282
|
-
if (!ref._artifact)
|
|
1283
|
-
return {};
|
|
1284
|
-
let item = ref.path && ref.path[0];
|
|
1285
|
-
const root = item && item._navigation;
|
|
1286
|
-
if (!root)
|
|
1287
|
-
return {};
|
|
1288
|
-
if (root.kind === '$navElement')
|
|
1289
|
-
return { navigation: root, item, tableAlias: root._parent };
|
|
1290
|
-
if (root.kind === 'mixin')
|
|
1291
|
-
return { navigation: root, item };
|
|
1292
|
-
item = ref.path[1];
|
|
1293
|
-
if (root.kind === '$self')
|
|
1294
|
-
return { item, tableAlias: root };
|
|
1295
|
-
if (root.kind !== '$tableAlias' || ref.path.length < 2)
|
|
1296
|
-
return {}; // should not happen
|
|
1297
|
-
return { navigation: root.elements[item.id], item, tableAlias: root };
|
|
1298
|
-
}
|
|
1299
|
-
|
|
1300
1296
|
module.exports = populate;
|