@sap/cds-compiler 3.6.2 → 3.8.0

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 (89) hide show
  1. package/CHANGELOG.md +109 -1
  2. package/README.md +3 -0
  3. package/bin/cdsc.js +12 -5
  4. package/doc/CHANGELOG_ARCHIVE.md +6 -6
  5. package/doc/CHANGELOG_BETA.md +35 -2
  6. package/doc/CHANGELOG_DEPRECATED.md +2 -2
  7. package/doc/DeprecatedOptions_v2.md +1 -1
  8. package/doc/NameResolution.md +1 -1
  9. package/lib/api/main.js +63 -23
  10. package/lib/api/options.js +1 -0
  11. package/lib/api/validate.js +5 -0
  12. package/lib/base/dictionaries.js +15 -3
  13. package/lib/base/keywords.js +2 -0
  14. package/lib/base/message-registry.js +120 -34
  15. package/lib/base/messages.js +51 -27
  16. package/lib/base/model.js +4 -2
  17. package/lib/base/shuffle.js +2 -1
  18. package/lib/checks/arrayOfs.js +1 -1
  19. package/lib/checks/defaultValues.js +1 -1
  20. package/lib/checks/elements.js +29 -1
  21. package/lib/checks/{emptyOrOnlyVirtual.js → hasPersistedElements.js} +10 -6
  22. package/lib/checks/invalidTarget.js +1 -1
  23. package/lib/checks/nonexpandableStructured.js +1 -1
  24. package/lib/checks/onConditions.js +15 -9
  25. package/lib/checks/sql-snippets.js +2 -2
  26. package/lib/checks/types.js +5 -1
  27. package/lib/checks/validator.js +7 -3
  28. package/lib/compiler/assert-consistency.js +42 -26
  29. package/lib/compiler/base.js +50 -4
  30. package/lib/compiler/builtins.js +17 -8
  31. package/lib/compiler/checks.js +241 -246
  32. package/lib/compiler/define.js +113 -146
  33. package/lib/compiler/extend.js +889 -383
  34. package/lib/compiler/finalize-parse-cdl.js +5 -58
  35. package/lib/compiler/index.js +1 -1
  36. package/lib/compiler/kick-start.js +7 -8
  37. package/lib/compiler/populate.js +297 -293
  38. package/lib/compiler/propagator.js +27 -18
  39. package/lib/compiler/resolve.js +146 -463
  40. package/lib/compiler/shared.js +36 -79
  41. package/lib/compiler/tweak-assocs.js +30 -28
  42. package/lib/compiler/utils.js +31 -5
  43. package/lib/edm/annotations/genericTranslation.js +131 -59
  44. package/lib/edm/annotations/preprocessAnnotations.js +3 -0
  45. package/lib/edm/csn2edm.js +22 -5
  46. package/lib/edm/edm.js +6 -4
  47. package/lib/edm/edmAnnoPreprocessor.js +1 -0
  48. package/lib/edm/edmPreprocessor.js +42 -26
  49. package/lib/gen/Dictionary.json +38 -2
  50. package/lib/gen/language.checksum +1 -1
  51. package/lib/gen/language.interp +3 -1
  52. package/lib/gen/languageLexer.js +1 -1
  53. package/lib/gen/languageParser.js +4828 -4472
  54. package/lib/inspect/inspectPropagation.js +20 -34
  55. package/lib/json/from-csn.js +140 -44
  56. package/lib/json/to-csn.js +114 -122
  57. package/lib/language/errorStrategy.js +2 -0
  58. package/lib/language/genericAntlrParser.js +156 -36
  59. package/lib/language/language.g4 +100 -58
  60. package/lib/language/textUtils.js +13 -0
  61. package/lib/main.d.ts +43 -3
  62. package/lib/main.js +4 -2
  63. package/lib/model/csnRefs.js +15 -3
  64. package/lib/model/csnUtils.js +12 -74
  65. package/lib/model/revealInternalProperties.js +4 -2
  66. package/lib/modelCompare/compare.js +2 -1
  67. package/lib/optionProcessor.js +3 -0
  68. package/lib/render/manageConstraints.js +5 -2
  69. package/lib/render/toCdl.js +216 -104
  70. package/lib/render/toHdbcds.js +2 -9
  71. package/lib/render/toRename.js +14 -51
  72. package/lib/render/toSql.js +4 -3
  73. package/lib/render/utils/common.js +9 -5
  74. package/lib/transform/braceExpression.js +6 -0
  75. package/lib/transform/db/assertUnique.js +2 -1
  76. package/lib/transform/db/expansion.js +2 -0
  77. package/lib/transform/db/flattening.js +37 -36
  78. package/lib/transform/db/rewriteCalculatedElements.js +600 -0
  79. package/lib/transform/db/transformExists.js +4 -0
  80. package/lib/transform/db/views.js +40 -37
  81. package/lib/transform/forOdataNew.js +20 -15
  82. package/lib/transform/forRelationalDB.js +58 -41
  83. package/lib/transform/odata/typesExposure.js +50 -15
  84. package/lib/transform/parseExpr.js +16 -8
  85. package/lib/transform/transformUtilsNew.js +42 -14
  86. package/lib/transform/translateAssocsToJoins.js +60 -37
  87. package/lib/transform/universalCsn/coreComputed.js +15 -7
  88. package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
  89. package/package.json +2 -1
@@ -24,15 +24,13 @@ 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
- const { dictLocation } = require('../base/location');
30
29
  const { weakLocation } = require('../base/messages');
31
30
  const { CompilerAssertion } = require('../base/error');
32
31
 
33
32
  const { kindProperties } = require('./base');
34
33
  const {
35
- pushLink,
36
34
  setLink,
37
35
  setArtifactLink,
38
36
  annotationVal,
@@ -42,13 +40,14 @@ const {
42
40
  splitIntoPath,
43
41
  linkToOrigin,
44
42
  setMemberParent,
43
+ proxyCopyMembers,
45
44
  dependsOn,
46
- traverseQueryPost,
47
45
  setExpandStatus,
48
46
  setExpandStatusAnnotate,
49
47
  } = require('./utils');
50
48
 
51
49
  const $inferred = Symbol.for('cds.$inferred');
50
+ const $location = Symbol.for('cds.$location');
52
51
 
53
52
 
54
53
  // Export function of this file.
@@ -62,16 +61,18 @@ function populate( model ) {
62
61
  resolvePath,
63
62
  attachAndEmitValidNames,
64
63
  initArtifact,
64
+ chooseAnnotationsInArtifact,
65
+ extendArtifactAfter,
65
66
  } = model.$functions;
66
67
  model.$volatileFunctions.environment = environment;
67
68
  Object.assign( model.$functions, {
68
69
  effectiveType,
69
- directType,
70
+ getOrigin,
70
71
  resolveType,
71
- populateQuery,
72
72
  } );
73
+ // let depth = 100;
73
74
 
74
-
75
+ let effectiveSeqNo = 0; // artifact number set after having set _effectiveType
75
76
  /** @type {any} may also be a boolean */
76
77
  let newAutoExposed = [];
77
78
 
@@ -96,146 +97,247 @@ function populate( model ) {
96
97
  newAutoExposed = true; // internal error if auto-expose after here
97
98
  return;
98
99
 
100
+ function traverseElementEnvironments( art ) {
101
+ navigationEnv( art );
102
+ if (art.$queries)
103
+ art.$queries.forEach( traverseElementEnvironments );
104
+ if (art.mixin)
105
+ dictForEach( art.mixin, navigationEnv );
106
+ if (art !== art._main?._leadingQuery) // already done
107
+ forEachMember( art, traverseElementEnvironments );
108
+ }
109
+
99
110
 
100
111
  //--------------------------------------------------------------------------
101
112
  // The central functions for path resolution - must work on-demand
102
113
  //--------------------------------------------------------------------------
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
- }
114
+ // Phase 2: call effectiveType() on-demand, which also calculates view elems
112
115
 
113
116
  // Return effective search environment provided by artifact `art`, i.e. the
114
117
  // `artifacts` or `elements` dictionary. For the latter, follow the `type`
115
118
  // chain and resolve the association `target`. View elements are calculated
116
119
  // on demand.
117
120
  function environment( art, location, user, assocSpec ) {
118
- if (!art)
119
- return Object.create(null);
120
121
  const env = navigationEnv( art, location, user, assocSpec );
122
+ if (env === 0)
123
+ return 0;
121
124
  return env && env.elements || Object.create(null);
122
125
  }
123
126
 
124
127
  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
- }
128
+ // = effectiveType() on from-path, TODO: should actually already part of
129
+ // resolvePath() on FROM
130
+ if (!art)
131
+ return undefined;
132
+ let type = effectiveType( art );
133
+ while (type?.items) // TODO: disallow navigation to many sometimes
134
+ type = effectiveType( type.items );
135
+ if (!type?.target)
136
+ return type;
137
+
138
+ if (assocSpec === false) {
135
139
  // 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;
140
+ error( null, [ location, user ], {},
141
+ 'Following an association is not allowed in an association key definition' );
142
+ } // TODO: else warning for assoc usage with falsy assocSpec
143
+ const target = resolvePath( type.target, 'target', type );
144
+ if (!target)
145
+ return target;
146
+ if (target && assocSpec && user)
147
+ dependsOn( user, target, location || user.location );
148
+ const effectiveTarget = effectiveType( target );
149
+ if (effectiveTarget === 0 && location)
150
+ dependsOn( user, user, (user.target || user.type || user.value || user).location );
151
+ // console.log('NT:',assocSpec,!!user,target)
152
+ return effectiveTarget;
146
153
  }
147
154
 
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.
155
+ /**
156
+ * Return the artifact having properties which are relevant for further name
157
+ * resolution on `art`: `target`, `elements`, `items`, also `enum`. Make sure
158
+ * that these properties actually exist, are complete and auto-corrected. Cache
159
+ * the result in property `_effectiveType`.
160
+ *
161
+ * - actions, functions: returns `art`, might have expanded `params`/`returns`
162
+ * - artifacts with direct or inherited `target`, `elements`, `items`, `enum`:
163
+ * returns `art`, these properties might have been auto-redirected / expanded
164
+ * - artifacts with direct or inherited scalar type: the built-in type
165
+ * - other artifacts: the last artifact in the origin-chain, i.e. the one which
166
+ * has neither a type nor some value path.
167
+ * - returns 0 with cyclic dependencies (with recursive element expansions, we
168
+ * have `elements: 0` instead).
169
+ * - returns null if a relevant reference points to nothing or is corrupted
170
+ * - returns false if a relevant reference points to a duplicate definition
171
+ *
172
+ * This function also infers type relevant properties:
173
+ *
174
+ * - views and queries: returns `art` with inferred query `elements`
175
+ * - column with `expand`: returns `art`, usually with inferred `elements`/`items`
176
+ * - more to come
177
+ *
178
+ * At the moment, it is assumed that includes, expansions, and localized has
179
+ * been applied earlier.
180
+ *
181
+ * Properties which are (usually) not relevant for the name resolution, like
182
+ * `length` and `cardinality`, cannot be simply accessed on the effective
183
+ * artifact. The effective artifact alone is not enough to check whether an
184
+ * artifact is an association or composition; it also does not give you the
185
+ * information about the technical base type of an enum.
186
+ *
187
+ * Calculating an effective association/composition does not imply calculating
188
+ * its effective target. Calculating an effective structure (entities, …)
189
+ * does not imply calculating the effective types of its elements. Calculating
190
+ * an effective array does not imply calculating its effective line type.
191
+ */
159
192
  function effectiveType( art ) {
160
- if ('_effectiveType' in art)
193
+ if (!art)
194
+ return art;
195
+ // if (--depth) throw Error(`ET: ${ Object.keys(art) }`)
196
+ if (art._effectiveType !== undefined)
161
197
  return art._effectiveType;
162
198
 
163
199
  // console.log(message( null, art.location, art, {}, 'Info','FT').toString())
164
200
  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 );
201
+ // console.log( 'ET-START:', art.kind, art.name )
202
+ while (art && art._effectiveType === undefined) {
169
203
  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
- }
204
+ chain.push( art );
205
+ art = getOrigin( art );
206
+ // console.log( 'ET-GO:', art?.name )
185
207
  }
208
+ if (art)
209
+ art = art._effectiveType;
210
+ if (art === 0)
211
+ return art;
212
+
186
213
  chain.reverse();
187
- if (!art) {
188
- for (const a of chain)
189
- setLink( a, '_effectiveType', art );
214
+ for (const a of chain) {
215
+ // Without type, value.path or _origin at beginning, link to itself:
216
+ chooseAnnotationsInArtifact( a );
217
+ art = populateArtifact( a, art ) || a;
218
+ if (a.elements$ || a.enum$)
219
+ mergeSpecifiedElementsOrEnum( a );
220
+ setLink( a, '_effectiveType', art );
221
+ a.$effectiveSeqNo = ++effectiveSeqNo;
222
+ // console.log( 'ET-DO:', effectiveSeqNo, a?.kind, a?.name, a._extensions?.elements?.length )
223
+ extendArtifactAfter( a ); // after setting _effectiveType (for messages)
190
224
  }
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 );
225
+ // console.log( 'ET-END:', art?.kind, art?.name )
226
+ return art;
227
+ }
228
+
229
+ function populateArtifact( art, origEffective ) {
230
+ // Name-resolution relevant properties directly at artifact:
231
+ // ‹view›.elements of input must have been moved (to elements$) before!
232
+ // console.log('Q:',art.elements,art.enum,art.items,!!art.query)
233
+ if (art.includes) // first version of includes via effectiveTpe()
234
+ art.includes.forEach( i => effectiveType( i._artifact ) );
235
+ if (art.elements != null || art.enum != null || art.items != null)
236
+ return art;
237
+ if (art.target) {
238
+ // try to implicitly redirect explicitly provided target:
239
+ if (!origEffective?.target && art.kind !== 'mixin')
240
+ redirectImplicitly( art, art );
241
+ if (!art.expand)
242
+ return art;
243
+ }
244
+
245
+ // With properties to be calculated: ----------------------------------------
246
+ if (art.query && art.kind !== '$tableAlias') { // query entity
247
+ const leading = art.$queries[0];
248
+ if (!leading) // parse error
249
+ return null;
250
+ // assert that there is no _effectiveType on leading (should be the case, as
251
+ // you cannot refer to a query of another artifact)
252
+ populateQuery( leading );
253
+ setLink( leading, '_effectiveType', leading );
254
+ leading.$effectiveSeqNo = ++effectiveSeqNo;
255
+ return art;
256
+ }
257
+ if (art.from)
258
+ return populateQuery( art );
259
+
260
+ if (art.expand) {
261
+ // TODO: test that there is no CDL-style cast with expand
262
+ // (we could allow that later: then some basic structural check is needed)
263
+ if (!art.value) {
264
+ initFromColumns( art, art.expand );
265
+ if (origEffective?.target) // consider `{ … } as x: AssocType
266
+ redirectImplicitly( art, origEffective );
267
+ }
268
+ else if (art.value.path) {
269
+ expandFromColumns( art );
205
270
  }
271
+ // TODO: if we allow CDL-style cast with expand in the future, we need to
272
+ // redirectImplicitly when casting to assoc type
273
+ return art;
206
274
  }
207
- return art;
275
+ if (!origEffective || origEffective.builtin) // TODO: builtin test needed?
276
+ return origEffective;
277
+
278
+ // With inherited auto-corrected name-resolution-relevant properties: -------
279
+ if (origEffective.target)
280
+ return redirectImplicitly( art, origEffective ) ? art : origEffective;
281
+ if (origEffective.elements)
282
+ return expandElements( art, origEffective ) ? art : origEffective;
283
+ if (origEffective.enum)
284
+ return expandEnum( art, origEffective ) ? art : origEffective;
285
+ if (origEffective.items)
286
+ return expandItems( art, origEffective ) ? art : origEffective;
287
+ if (origEffective.params || origEffective.returns)
288
+ return expandParams( art, origEffective );
289
+ return origEffective;
208
290
  }
209
291
 
210
292
  // TODO: test it in combination with top-level CAST function
211
- function directType( art ) {
293
+ // TODO: we could probably "extend" this function to all other cases where we
294
+ // set an _origin in Universal CSN
295
+
296
+ // TODO: add 2nd arg `considerSecondary` used in effectiveType(): prefers a
297
+ // predecessor without _effectiveType (includes, joins)
298
+ function getOrigin( art ) {
212
299
  // Be careful when using it with art.target or art.enum or art.elements
213
- if (art._origin || art.builtin)
300
+ if (!art)
301
+ return undefined; // TODO: null?
302
+ // if (--depth) throw Error(`GOR: ${ Object.keys(art) }`)
303
+ if (art._origin !== undefined)
214
304
  return art._origin;
215
- if (art.type)
305
+ if (art.type) // not stored in _origin
216
306
  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;
307
+ return setLink( art, '_origin', getOriginRaw( art ) );
308
+ }
309
+
310
+ function getOriginRaw( art ) {
311
+ if (!art._main) {
312
+ if (art.query)
313
+ return getOrigin( art.$queries?.[0] );
225
314
  }
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) );
315
+ else {
316
+ if (art.value?.path)
317
+ return resolvePath( art.value, 'expr', art, null );
318
+ if (art.kind === 'select')
319
+ return getOrigin( dictFirst( art.$tableAliases ) );
320
+ // init sets _origin for alias to sub query, only need to handle ref here:
321
+ if (art.kind === '$tableAlias') {
322
+ // do not use navigationEnv(): it would always call effectiveType() on the
323
+ // source → we would would have a deeper callstack
324
+ const source = resolvePath( art, 'from', art._parent );
325
+ if (!source?._main)
326
+ return source; // direct entity (or undefined)
327
+ // Before having done the resolvePath cleanup, do not rely on resolvePath
328
+ // to call effectiveType() on the last assoc of a from ref:
329
+ const assoc = effectiveType( source );
330
+ return resolvePath( assoc?.target, 'target', assoc );
331
+ }
332
+ }
333
+ return '';
234
334
  }
235
335
 
236
336
  function resolveType( ref, user ) {
237
- if ('_artifact' in ref)
337
+ if (ref._artifact !== undefined)
238
338
  return ref._artifact;
339
+ while (user._outer) // in items
340
+ user = user._outer;
239
341
  if (ref.scope === 'typeOf') {
240
342
  let struct = user;
241
343
  while (struct.kind === 'element')
@@ -255,8 +357,6 @@ function populate( model ) {
255
357
  }
256
358
  return resolvePath( ref, 'typeOf', user );
257
359
  }
258
- while (user._outer) // in items
259
- user = user._outer;
260
360
  if (user.kind === 'event')
261
361
  return resolvePath( ref, 'eventType', user );
262
362
  if (user.kind === 'param' && user._parent &&
@@ -265,18 +365,18 @@ function populate( model ) {
265
365
  return resolvePath( ref, 'type', user );
266
366
  }
267
367
 
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 );
368
+ function getCardinality( assoc ) {
369
+ while (assoc?._effectiveType) {
370
+ // if (--depth) throw Error(`GCARD: ${ Object.keys(art) }`)
371
+ if (assoc.cardinality)
372
+ return assoc.cardinality;
373
+ assoc = getOrigin( assoc );
274
374
  }
275
375
  return {};
276
376
  }
277
377
 
278
378
  function userQuery( user ) {
279
- // TODO: we need _query links set by the definer
379
+ // TODO: should we set _query links in define.js?
280
380
  while (user._main) {
281
381
  if (user.kind === 'select' || user.kind === '$join')
282
382
  return user;
@@ -285,7 +385,7 @@ function populate( model ) {
285
385
  return null;
286
386
  }
287
387
 
288
- // Expansiosn --------------------------------------------------------------
388
+ // Expansions --------------------------------------------------------------
289
389
 
290
390
 
291
391
  function expandItems( art, origin ) {
@@ -307,6 +407,10 @@ function populate( model ) {
307
407
  }
308
408
 
309
409
  function expandElements( art, struct ) {
410
+ if (art.kind === '$tableAlias') {
411
+ proxyCopyMembers( art, 'elements', struct.elements, art.path?.location, '$navElement' );
412
+ return true;
413
+ }
310
414
  if (art.elements || art.kind === '$tableAlias' || art.kind === '$inline' ||
311
415
  // no element expansions for "non-proper" types like
312
416
  // entities (as parameter types) etc:
@@ -321,15 +425,7 @@ function populate( model ) {
321
425
  const location = ref && ref.location || art.location;
322
426
  // console.log( message( null, location, art, {target:struct,art}, 'Info','EXPAND-ELEM')
323
427
  // .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
- }
428
+ proxyCopyMembers( art, 'elements', struct.elements, weakLocation( location ) );
333
429
  // Set elements expansion status (the if condition is always true, as no
334
430
  // elements expansion will take place on artifact with existing other
335
431
  // member property):
@@ -344,13 +440,7 @@ function populate( model ) {
344
440
  return false;
345
441
  const ref = art.type || art.value || art.name;
346
442
  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
- }
443
+ proxyCopyMembers( art, 'enum', origin.enum, weakLocation( location ) );
354
444
  // Set elements expansion status (the if condition is always true, as no
355
445
  // elements expansion will take place on artifact with existing other
356
446
  // member property):
@@ -360,6 +450,30 @@ function populate( model ) {
360
450
  return true;
361
451
  }
362
452
 
453
+ function expandParams( art, origin ) {
454
+ if (!origin._main)
455
+ return origin; // not with entity (should not happen)
456
+ if (origin.params)
457
+ proxyCopyMembers( art, 'params', origin.params, null );
458
+
459
+ if (origin.returns) {
460
+ // TODO: make linkToOrigin() work for returns, kind/name?
461
+ const location = weakLocation( origin.returns.location );
462
+ art.returns = {
463
+ name: Object.assign( {}, art.name, { id: '', param: '', location } ),
464
+ kind: 'param',
465
+ location,
466
+ $inferred: 'expanded',
467
+ };
468
+ setLink( art.returns, '_parent', art );
469
+ setLink( art.returns, '_main', art._main || art );
470
+ setLink( art.returns, '_origin', origin.returns );
471
+ }
472
+ if (!art.$expand)
473
+ art.$expand = 'origin'; // if value stays, elements won't appear in CSN
474
+ return art;
475
+ }
476
+
363
477
  /**
364
478
  * Return true iff `art` is from a recursive expansion, i.e. if any of its
365
479
  * expanded parents (including _outer) has the same non-expansion-origin.
@@ -403,48 +517,9 @@ function populate( model ) {
403
517
  // Views
404
518
  //--------------------------------------------------------------------------
405
519
 
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
- }
520
+ // TODO: delete XSN._entities
521
+ // TODO: delete ENTITY._from
522
+ // TODO (after on-demand ext): delete XSN.$entity
448
523
 
449
524
  /**
450
525
  * Merge _specified_ elements with _inferred_ elements in the given view/element,
@@ -499,6 +574,7 @@ function populate( model ) {
499
574
  for (const id in art.elements$) {
500
575
  const selem = art.elements$[id]; // specified element
501
576
  if (!selem.$replacement) {
577
+ // console.log( 'QED:', art.name, art.kind, art.elements )
502
578
  error( 'query-unspecified-element', [ selem.name.location, selem ], { id },
503
579
  'Element $(ID) does not result from the query' );
504
580
  }
@@ -508,7 +584,7 @@ function populate( model ) {
508
584
  function populateQuery( query ) {
509
585
  if (query._combined || !query.from || !query.$tableAliases)
510
586
  // already done or $join query or parse error
511
- return;
587
+ return query;
512
588
  setLink( query, '_combined', Object.create(null) );
513
589
  query.$inlines = [];
514
590
  forEachGeneric( query, '$tableAliases', resolveTabRef );
@@ -518,35 +594,18 @@ function populate( model ) {
518
594
  for (const name in query.excludingDict)
519
595
  resolveExcluding( name, query._combined, query.excludingDict, query );
520
596
  }
521
- return;
597
+ return query;
522
598
 
523
599
  function resolveTabRef( alias ) {
600
+ // effectiveType() must not be called on $self, is unnecessary for mixins:
601
+ // TODO: have a test for `select from E { a, $self.a as b, $self.{ b as c } }`
602
+ // TODO: have a negative test for `select from E { $self.*, assoc.* }`
603
+ // (we might have those already)
524
604
  if (alias.kind === 'mixin' || alias.kind === '$self')
525
605
  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
- }
606
+ if (!alias.elements) // could be false in hierarchical JOIN - TODO: necessary?
607
+ effectiveType( alias ); // element → $navElement expansion for $tableAlias
608
+
550
609
  forEachGeneric( { elements: alias.elements }, 'elements', ( elem, name ) => {
551
610
  if (elem.$duplicates !== true)
552
611
  dictAddArray( query._combined, name, elem, null ); // not dictAdd()
@@ -568,16 +627,20 @@ function populate( model ) {
568
627
 
569
628
  // query columns -----------------------------------------------------------
570
629
 
571
- function expandFromColumns( elem, assoc, cardinality ) {
572
- const path = elem.value && elem.value.path;
630
+ function expandFromColumns( elem ) {
631
+ const path = elem.value?.path;
573
632
  if (!path || path.broken)
574
633
  return null;
575
- if (!assoc.target)
634
+ // If we allow CDL-style casts of `expand`s to associations in the future, we
635
+ // need to ignore an explicit type, i.e. not getOrigin():
636
+ const assoc = resolvePath( elem.value, 'expr', elem, null );
637
+ if (!effectiveType( assoc )?.target)
576
638
  return initFromColumns( elem, elem.expand );
577
- const { targetMax } = path[path.length - 1].cardinality ||
578
- (cardinality instanceof Function ? cardinality() : cardinality);
579
- if (targetMax && (targetMax.val === '*' || targetMax.val > 1))
580
- elem.items = { location: dictLocation( elem.expand ) }; // TODO: array location
639
+ const { targetMax } = path[path.length - 1].cardinality || getCardinality( assoc );
640
+ if (targetMax && (targetMax.val === '*' || targetMax.val > 1)) {
641
+ elem.items = { location: elem.expand[$location] };
642
+ setLink( elem.items, '_outer', elem );
643
+ }
581
644
  return initFromColumns( elem, elem.expand );
582
645
  }
583
646
 
@@ -623,7 +686,7 @@ function populate( model ) {
623
686
  setMemberParent( col, id, query );
624
687
  }
625
688
  }
626
- forEachGeneric( query, 'elements', e => initElem( e, query ) );
689
+ forEachGeneric( query, 'elements', initElem );
627
690
  return true;
628
691
  }
629
692
 
@@ -655,45 +718,20 @@ function populate( model ) {
655
718
  return '';
656
719
  }
657
720
 
658
- function initElem( elem, query ) {
721
+ function initElem( elem ) {
722
+ // TODO: we could share code with initMembers/init() in define.js
659
723
  if (elem.type && !elem.type.$inferred)
660
- return; // explicit type -> enough or directType()
724
+ return; // explicit type -> enough or getOrigin()
661
725
  if (elem.$inferred) {
662
726
  // redirectImplicitly( elem, elem._origin );
663
727
  return;
664
728
  }
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 );
729
+ if (!elem.type && elem.value?.type) { // top-level CAST( expr AS type )
730
+ if (!elem.target) // TODO: we might issue an error if there is a target
731
+ elem.type = { ...elem.value.type, $inferred: 'cast' };
696
732
  }
733
+ if (elem.foreignKeys) // REDIRECTED with explicit foreign keys
734
+ forEachGeneric( elem, 'foreignKeys', (key, name) => initKey( key, name, elem ) );
697
735
  }
698
736
 
699
737
  function initKey( key, name, elem ) {
@@ -733,8 +771,8 @@ function populate( model ) {
733
771
  // console.log('S1:',location.line,location.col,
734
772
  // envParent&&!!envParent._origin&&envParent._origin.name)
735
773
  const env = columnEnv( envParent, query );
736
- // console.log('S2:',location.line,location.col,
737
- // envParent&&!!envParent._origin&&envParent._origin.name,
774
+ // if (envParent) console.log('S2:',location.line,location.col,
775
+ // envParent?.name,envParent?._origin?.name,
738
776
  // Object.keys(env),Object.keys(elements))
739
777
  for (const name in env) {
740
778
  const navElem = env[name];
@@ -793,8 +831,9 @@ function populate( model ) {
793
831
  }
794
832
 
795
833
  function columnEnv( envParent, query ) { // etc. wildcard._pathHead;
834
+ // if (envParent) console.log( 'CE:', envParent._origin, query );
796
835
  return (envParent)
797
- ? environment( directType( envParent ) || envParent )
836
+ ? environment( getOrigin( envParent ) ) // not the col with expand, but the referred
798
837
  : userQuery( query )._combined;
799
838
  }
800
839
 
@@ -830,9 +869,9 @@ function populate( model ) {
830
869
  setArtifactLink( path[0], origin );
831
870
  setLink( queryElem, '_origin', origin );
832
871
  // 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 );
872
+ // const alias = pathHead?.value?.path?.[0]?._navigation;
873
+ // if (alias?.kind === '$tableAlias')
874
+ // pushLink( alias.elements[name], '_projections', queryElem );
836
875
  }
837
876
 
838
877
  // called by expandWildcard():
@@ -847,7 +886,7 @@ function populate( model ) {
847
886
  setArtifactLink( path[1], sourceElem );
848
887
  // TODO: or should we set the _artifact/_effectiveType directly to the target?
849
888
  setArtifactLink( queryElem.value, sourceElem );
850
- pushLink( navElem, '_projections', queryElem );
889
+ // pushLink( navElem, '_projections', queryElem );
851
890
  // TODO: _effectiveType?
852
891
  }
853
892
 
@@ -1013,18 +1052,20 @@ function populate( model ) {
1013
1052
  const exposed = preferred.length ? preferred : descendants;
1014
1053
  if (exposed.length < 2)
1015
1054
  return exposed || [];
1016
- let min = null;
1055
+ let min = [];
1017
1056
  for (const e of exposed) {
1018
- if (!min || min._ancestors && min._ancestors.includes(e)) {
1019
- min = e;
1057
+ if (min.every( m => m._ancestors?.includes( e ))) {
1058
+ min = [ e ];
1020
1059
  }
1021
- else if (!e._ancestors || !e._ancestors.includes( min )) {
1060
+ else if (min.length !== 1 || !e._ancestors?.includes( min[0] )) {
1061
+ if (elemScope === '' && options.testMode)
1062
+ throw new CompilerAssertion( `Scope for ${ target } in service ${ service } is empty`);
1022
1063
  if (elemScope === '')
1023
1064
  return [];
1024
- return exposed;
1065
+ min.push( e );
1025
1066
  }
1026
1067
  }
1027
- return [ min ];
1068
+ return min;
1028
1069
  }
1029
1070
 
1030
1071
  // Scoped redirections -----------------------------------------------------
@@ -1253,48 +1294,11 @@ function populate( model ) {
1253
1294
  setLink( art, '_service', service );
1254
1295
  setLink( art, '_block', model.$internal );
1255
1296
  initArtifact( art, !!autoexposed );
1256
- // populate view (phase 2 of resolver has to be repeated as the view was created afterwards)
1257
- populateView( art );
1297
+ effectiveType( art );
1258
1298
  // TODO: try to set locations of elements locations of orig target elements
1259
1299
  newAutoExposed.push( art );
1260
1300
  return art;
1261
1301
  }
1262
1302
  }
1263
1303
 
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
1304
  module.exports = populate;