@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.
Files changed (68) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +3 -0
  3. package/bin/cdsc.js +9 -5
  4. package/doc/CHANGELOG_BETA.md +20 -2
  5. package/doc/CHANGELOG_DEPRECATED.md +2 -2
  6. package/lib/api/main.js +2 -1
  7. package/lib/base/dictionaries.js +10 -0
  8. package/lib/base/message-registry.js +56 -12
  9. package/lib/base/messages.js +39 -20
  10. package/lib/base/model.js +1 -0
  11. package/lib/base/shuffle.js +2 -1
  12. package/lib/checks/elements.js +29 -1
  13. package/lib/checks/{emptyOrOnlyVirtual.js → hasPersistedElements.js} +9 -5
  14. package/lib/checks/nonexpandableStructured.js +1 -1
  15. package/lib/checks/onConditions.js +8 -5
  16. package/lib/checks/types.js +6 -1
  17. package/lib/checks/validator.js +7 -3
  18. package/lib/compiler/assert-consistency.js +20 -23
  19. package/lib/compiler/base.js +1 -2
  20. package/lib/compiler/builtins.js +2 -2
  21. package/lib/compiler/checks.js +237 -242
  22. package/lib/compiler/define.js +63 -75
  23. package/lib/compiler/extend.js +325 -22
  24. package/lib/compiler/finalize-parse-cdl.js +1 -55
  25. package/lib/compiler/kick-start.js +6 -7
  26. package/lib/compiler/populate.js +284 -288
  27. package/lib/compiler/propagator.js +15 -13
  28. package/lib/compiler/resolve.js +136 -306
  29. package/lib/compiler/shared.js +42 -44
  30. package/lib/compiler/tweak-assocs.js +29 -27
  31. package/lib/compiler/utils.js +29 -3
  32. package/lib/edm/annotations/genericTranslation.js +7 -13
  33. package/lib/edm/annotations/preprocessAnnotations.js +3 -0
  34. package/lib/edm/csn2edm.js +0 -4
  35. package/lib/edm/edm.js +6 -4
  36. package/lib/edm/edmAnnoPreprocessor.js +1 -0
  37. package/lib/edm/edmPreprocessor.js +1 -5
  38. package/lib/gen/Dictionary.json +34 -2
  39. package/lib/gen/language.checksum +1 -1
  40. package/lib/gen/language.interp +1 -1
  41. package/lib/gen/languageParser.js +2429 -2401
  42. package/lib/inspect/inspectPropagation.js +2 -0
  43. package/lib/json/from-csn.js +87 -41
  44. package/lib/json/to-csn.js +47 -16
  45. package/lib/language/errorStrategy.js +1 -0
  46. package/lib/language/genericAntlrParser.js +109 -28
  47. package/lib/language/language.g4 +20 -4
  48. package/lib/model/csnRefs.js +1 -1
  49. package/lib/model/csnUtils.js +1 -0
  50. package/lib/model/revealInternalProperties.js +1 -2
  51. package/lib/modelCompare/compare.js +2 -1
  52. package/lib/render/manageConstraints.js +5 -2
  53. package/lib/render/toCdl.js +20 -7
  54. package/lib/render/toHdbcds.js +2 -8
  55. package/lib/render/toSql.js +4 -3
  56. package/lib/render/utils/common.js +9 -5
  57. package/lib/transform/db/assertUnique.js +2 -1
  58. package/lib/transform/db/expansion.js +2 -0
  59. package/lib/transform/db/flattening.js +37 -36
  60. package/lib/transform/db/rewriteCalculatedElements.js +559 -0
  61. package/lib/transform/db/transformExists.js +4 -0
  62. package/lib/transform/db/views.js +40 -37
  63. package/lib/transform/forRelationalDB.js +38 -28
  64. package/lib/transform/odata/typesExposure.js +50 -15
  65. package/lib/transform/parseExpr.js +14 -8
  66. package/lib/transform/transformUtilsNew.js +6 -5
  67. package/lib/transform/translateAssocsToJoins.js +49 -33
  68. package/package.json +1 -1
@@ -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
- directType,
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 populateView(), which also works on-demand
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
- let type = effectiveType(art) || art;
126
- // console.log( info(null, [ art.location, art ], { art, type }, 'ENV')
127
- // .toString(), art.elements && Object.keys(art.elements))
128
- if (type.target) {
129
- type = resolvePath( type.target, 'target', type );
130
- if (!type) {
131
- if (type === 0 && location)
132
- dependsOn( art, art, (art.target || art.type).location );
133
- return type;
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
- else if (assocSpec === false) { // TODO: else warning for assoc usage
137
- error( null, [ location, user ], {},
138
- 'Following an association is not allowed in an association key definition' );
139
- }
140
- else if (assocSpec && user) {
141
- dependsOn( user, type, location );
142
- }
143
- }
144
- populateView( type );
145
- return type;
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
- // Follow the `type` chain, i.e. derived types and TYPE OF, stop just before
149
- // built-in types (otherwise, we would loose type parameters). Return that
150
- // type and set it as property `_effectiveType` on all artifacts on the chain.
151
- // TODO: clarify for (query) elements without type: self, not undefined - also for entities!
152
- // TODO: directly "propagate" (with implicit redirection the targets), also
153
- // "proxy-copy" elements
154
-
155
- // In v2, the name-resolution relevant properties (elements,items,target) are
156
- // proxy-copied, i.e. the effectiveType of an artifact is always the artifact
157
- // itself. Except for the situation with recursive element expansions; then
158
- // we have element: 0, and use original final type.
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
- while (art && !('_effectiveType' in art) &&
166
- (art.type || art._origin || art.value?.path || art.value?.type) &&
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
- art = directType( art );
171
- }
172
- if (art) {
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
- if (!art) {
188
- for (const a of chain)
189
- setLink( a, '_effectiveType', art );
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
- else {
192
- // collect the "latest" cardinality (calculate lazily if necessary)
193
- let cardinality = art.cardinality ||
194
- art._effectiveType && (() => getCardinality( art._effectiveType ));
195
- for (const a of chain) {
196
- if (a.cardinality)
197
- cardinality = a.cardinality;
198
- if (a.expand && expandFromColumns( a, art, cardinality ) ||
199
- art.target && redirectImplicitly( a, art ) ||
200
- art.elements && expandElements( a, art ) ||
201
- art.items && expandItems( a, art ) ||
202
- art.enum && expandEnum( a, art ))
203
- art = a;
204
- setLink( a, '_effectiveType', art );
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
- return art;
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 directType( art ) {
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._origin || art.builtin)
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
- if (art.value?.type)
218
- return resolveType( art.value.type, art );
219
- // console.log( 'EXPR-IN', art.kind, refString(art.name) )
220
- if (!art._main || !art.value || !art.value.path)
221
- return undefined;
222
- if (art.value.path) {
223
- setLink( art, '_origin', resolvePath( art.value, 'expr', art, null ) );
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
- const query = userQuery( art ) || art._parent;
227
- if (query.kind !== 'select')
228
- return undefined;
229
- // Reached an element in a query which is a simple ref -> return referred artifact
230
- // TODO: remember that we still have to resolve path arguments and filters
231
- return setLink( art, '_origin',
232
- resolvePath( art.value, 'expr', art, query._combined ) );
233
- // console.log( 'EXPR-OUT', art.value._artifact.kind, refString(art.val ue._artifact.name) );
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( type ) {
269
- // only to be called without cycles
270
- while (type) {
271
- if (type.cardinality)
272
- return type.cardinality;
273
- type = directType( type );
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 need _query links set by the definer
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
- // Expansiosn --------------------------------------------------------------
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
- for (const name in struct.elements) {
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 = Object.create(null);
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
- // Make a view to have elements (remember: wildcard), and prepare that their
407
- // final type can be resolved, i.e. we know how to resolve select item refs.
408
- // We do so by first populate views in the FROM clause, then the view query.
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) { // could be false in hierarchical JOIN
527
- // if (main._block.$frontend!=='json') console.log('TABREF:',alias.name,main,main._block)
528
- // const tab = alias.path && resolvePath( alias, 'from', query );
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, assoc, cardinality ) {
572
- const path = elem.value && elem.value.path;
624
+ function expandFromColumns( elem ) {
625
+ const path = elem.value?.path;
573
626
  if (!path || path.broken)
574
627
  return null;
575
- if (!assoc.target)
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', e => initElem( e, query ) );
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, query ) {
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 directType()
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.value || !elem.value.path) // TODO: test $inferred
666
- return; // no value ref or $inferred
667
- // TODO: what about SELECT from E { $projection.a as a1, a } !!!!!!
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&&!!envParent._origin&&envParent._origin.name,
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( directType( envParent ) || envParent )
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
- pushLink( alias.elements[name], '_projections', queryElem );
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 = null;
1047
+ let min = [];
1017
1048
  for (const e of exposed) {
1018
- if (!min || min._ancestors && min._ancestors.includes(e)) {
1019
- min = e;
1049
+ if (min.every( m => m._ancestors?.includes( e ))) {
1050
+ min = [ e ];
1020
1051
  }
1021
- else if (!e._ancestors || !e._ancestors.includes( min )) {
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
- return exposed;
1057
+ min.push( e );
1025
1058
  }
1026
1059
  }
1027
- return [ min ];
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
- // populate view (phase 2 of resolver has to be repeated as the view was created afterwards)
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;