@sap/cds-compiler 4.0.2 → 4.1.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 (84) hide show
  1. package/CHANGELOG.md +100 -5
  2. package/bin/cdsc.js +12 -12
  3. package/doc/CHANGELOG_BETA.md +11 -0
  4. package/lib/api/main.js +31 -11
  5. package/lib/api/validate.js +1 -1
  6. package/lib/base/location.js +6 -7
  7. package/lib/base/message-registry.js +84 -38
  8. package/lib/base/messages.js +11 -10
  9. package/lib/base/model.js +6 -2
  10. package/lib/checks/defaultValues.js +6 -6
  11. package/lib/checks/foreignKeys.js +0 -5
  12. package/lib/checks/onConditions.js +17 -12
  13. package/lib/checks/queryNoDbArtifacts.js +132 -72
  14. package/lib/checks/sql-snippets.js +15 -4
  15. package/lib/checks/types.js +3 -3
  16. package/lib/checks/utils.js +1 -1
  17. package/lib/compiler/assert-consistency.js +44 -16
  18. package/lib/compiler/base.js +1 -0
  19. package/lib/compiler/builtins.js +7 -8
  20. package/lib/compiler/checks.js +274 -197
  21. package/lib/compiler/classes.js +62 -0
  22. package/lib/compiler/cycle-detector.js +3 -3
  23. package/lib/compiler/define.js +63 -50
  24. package/lib/compiler/extend.js +38 -20
  25. package/lib/compiler/finalize-parse-cdl.js +2 -1
  26. package/lib/compiler/generate.js +0 -8
  27. package/lib/compiler/index.js +9 -7
  28. package/lib/compiler/kick-start.js +2 -0
  29. package/lib/compiler/populate.js +139 -110
  30. package/lib/compiler/propagator.js +4 -3
  31. package/lib/compiler/resolve.js +157 -126
  32. package/lib/compiler/shared.js +706 -404
  33. package/lib/compiler/tweak-assocs.js +21 -10
  34. package/lib/compiler/utils.js +228 -36
  35. package/lib/edm/annotations/genericTranslation.js +1 -1
  36. package/lib/edm/edm.js +4 -1
  37. package/lib/edm/edmPreprocessor.js +5 -4
  38. package/lib/edm/edmUtils.js +2 -4
  39. package/lib/gen/Dictionary.json +34 -10
  40. package/lib/gen/language.checksum +1 -1
  41. package/lib/gen/language.interp +1 -1
  42. package/lib/gen/languageParser.js +3987 -3963
  43. package/lib/json/from-csn.js +43 -47
  44. package/lib/json/to-csn.js +11 -11
  45. package/lib/language/antlrParser.js +2 -1
  46. package/lib/language/genericAntlrParser.js +52 -43
  47. package/lib/language/language.g4 +59 -59
  48. package/lib/language/multiLineStringParser.js +2 -0
  49. package/lib/main.d.ts +5 -0
  50. package/lib/model/csnRefs.js +37 -19
  51. package/lib/model/csnUtils.js +20 -16
  52. package/lib/model/revealInternalProperties.js +29 -21
  53. package/lib/modelCompare/compare.js +112 -39
  54. package/lib/modelCompare/utils/filter.js +54 -24
  55. package/lib/optionProcessor.js +6 -6
  56. package/lib/render/manageConstraints.js +20 -17
  57. package/lib/render/toCdl.js +34 -20
  58. package/lib/render/toHdbcds.js +2 -2
  59. package/lib/render/toRename.js +4 -9
  60. package/lib/render/toSql.js +77 -26
  61. package/lib/render/utils/common.js +3 -3
  62. package/lib/render/utils/unique.js +52 -0
  63. package/lib/transform/db/applyTransformations.js +61 -20
  64. package/lib/transform/db/assertUnique.js +7 -8
  65. package/lib/transform/db/associations.js +2 -2
  66. package/lib/transform/db/cdsPersistence.js +8 -8
  67. package/lib/transform/db/expansion.js +17 -21
  68. package/lib/transform/db/flattening.js +23 -23
  69. package/lib/transform/db/rewriteCalculatedElements.js +20 -14
  70. package/lib/transform/db/temporal.js +1 -1
  71. package/lib/transform/db/transformExists.js +8 -7
  72. package/lib/transform/db/views.js +73 -33
  73. package/lib/transform/draft/db.js +11 -9
  74. package/lib/transform/draft/odata.js +1 -1
  75. package/lib/transform/{forOdataNew.js → forOdata.js} +6 -6
  76. package/lib/transform/forRelationalDB.js +69 -75
  77. package/lib/transform/localized.js +6 -5
  78. package/lib/transform/odata/toFinalBaseType.js +3 -3
  79. package/lib/transform/{transformUtilsNew.js → transformUtils.js} +4 -101
  80. package/lib/transform/translateAssocsToJoins.js +14 -28
  81. package/package.json +1 -1
  82. package/share/messages/check-proper-type-of.md +1 -1
  83. package/share/messages/{check-proper-type.md → def-missing-type.md} +3 -5
  84. package/share/messages/message-explanations.json +1 -1
@@ -41,9 +41,10 @@ const {
41
41
  linkToOrigin,
42
42
  setMemberParent,
43
43
  proxyCopyMembers,
44
- dependsOn,
45
44
  setExpandStatus,
46
45
  setExpandStatusAnnotate,
46
+ dependsOnSilent,
47
+ columnRefStartsWithSelf,
47
48
  } = require('./utils');
48
49
  const { typeParameters } = require('./builtins');
49
50
 
@@ -73,6 +74,7 @@ function populate( model ) {
73
74
  } = model.$messageFunctions;
74
75
  const {
75
76
  resolvePath,
77
+ nestedElements,
76
78
  attachAndEmitValidNames,
77
79
  initArtifact,
78
80
  extendArtifactBefore,
@@ -81,8 +83,7 @@ function populate( model ) {
81
83
  Object.assign( model.$functions, {
82
84
  effectiveType,
83
85
  getOrigin,
84
- resolveType,
85
- navigationEnv,
86
+ getInheritedProp,
86
87
  } );
87
88
  // let depth = 100;
88
89
 
@@ -111,12 +112,17 @@ function populate( model ) {
111
112
  newAutoExposed = true; // internal error if auto-expose after here
112
113
  return;
113
114
 
115
+ /** Make sure that effectiveType() is called on all members and items */
114
116
  function traverseElementEnvironments( art ) {
115
- navigationEnv( art );
117
+ let type = effectiveType( art );
118
+ while (type?.items)
119
+ type = effectiveType( type.items );
116
120
  if (art.$queries)
117
121
  art.$queries.forEach( traverseElementEnvironments );
118
122
  if (art.mixin)
119
- dictForEach( art.mixin, navigationEnv );
123
+ dictForEach( art.mixin, effectiveType );
124
+ if (art.targetAspect?.elements)
125
+ effectiveType( art.targetAspect );
120
126
  if (art !== art._main?._leadingQuery) // already done
121
127
  forEachMember( art, traverseElementEnvironments );
122
128
  }
@@ -125,37 +131,6 @@ function populate( model ) {
125
131
  //--------------------------------------------------------------------------
126
132
  // The central functions for path resolution - must work on-demand
127
133
  //--------------------------------------------------------------------------
128
- // Phase 2: call effectiveType() on-demand, which also calculates view elems
129
-
130
- // TODO: move setting dependencies and complaining about assocs in keys
131
- // to resolvePath - then remove params: location, user
132
- function navigationEnv( art, location, user, assocSpec ) {
133
- // = effectiveType() on from-path, TODO: should actually already part of
134
- // resolvePath() on FROM
135
- if (!art)
136
- return undefined;
137
- let type = effectiveType( art );
138
- while (type?.items) // TODO: disallow navigation to many sometimes
139
- type = effectiveType( type.items );
140
- if (!type?.target || assocSpec === 'targetAspectOnly')
141
- return type;
142
-
143
- if (assocSpec === false) {
144
- // TODO: combine this with setTargetReferenceKey&Co in getPathItem?
145
- error( null, [ location, user ], {},
146
- 'Following an association is not allowed in an association key definition' );
147
- } // TODO: else warning for assoc usage with falsy assocSpec
148
- const target = resolvePath( type.target, 'target', type );
149
- if (!target)
150
- return target;
151
- if (target && assocSpec && user)
152
- dependsOn( user, target, location || user.location );
153
- const effectiveTarget = effectiveType( target );
154
- if (effectiveTarget === 0 && location)
155
- dependsOn( user, user, (user.target || user.type || user.value || user).location );
156
- // console.log('NT:',assocSpec,!!user,target)
157
- return effectiveTarget;
158
- }
159
134
 
160
135
  /**
161
136
  * Return the artifact having properties which are relevant for further name
@@ -189,10 +164,11 @@ function populate( model ) {
189
164
  * artifact is an association or composition; it also does not give you the
190
165
  * information about the technical base type of an enum.
191
166
  *
192
- * Calculating an effective association/composition does not imply calculating
193
- * its effective target. Calculating an effective structure (entities, …)
194
- * does not imply calculating the effective types of its elements. Calculating
195
- * an effective array does not imply calculating its effective line type.
167
+ * Calculating an effective association/composition implies calculating its
168
+ * target entity (including redirections), but not induce calculating the
169
+ * target's elements. Calculating an effective structure (entities, …) does
170
+ * not imply calculating the effective types of its elements. Calculating an
171
+ * effective array does not imply calculating its effective line type.
196
172
  */
197
173
  function effectiveType( art ) {
198
174
  if (!art)
@@ -212,16 +188,28 @@ function populate( model ) {
212
188
  }
213
189
  if (art)
214
190
  art = art._effectiveType;
215
- if (art === 0)
191
+ if (art === 0) {
192
+ model.$assert = 'cycle';
193
+ // throw Error(`CYCLE: ${ chain.length }`);
216
194
  return art;
217
-
195
+ }
218
196
  chain.reverse();
219
197
  for (const a of chain) {
198
+ // Ensure that the _effectiveType of the parent has been calculated. This
199
+ // is usually the case, but might not be for elements of anonymous target
200
+ // aspects. Without it, extensions/annotations might get lost.
201
+ // For a query and its parent (usually the query entity!), it is the other way
202
+ // around: to calculate the _effectiveType of the query entity, we might need
203
+ // to calculate the _effectiveType of a query in FROM first.
204
+ if (a.kind !== 'select')
205
+ effectiveType( a._outer || a._main && a._parent );
206
+ // TODO: forbid $self+$self.elem inline, see expandWildcard()
220
207
  // Without type, value.path or _origin at beginning, link to itself:
221
208
  extendArtifactBefore( a );
222
209
  art = populateArtifact( a, art ) || a;
223
210
  setLink( a, '_effectiveType', art );
224
211
  a.$effectiveSeqNo = ++effectiveSeqNo;
212
+ // console.log('PE:',require('../model/revealInternalProperties').ref(a))
225
213
  if (a.elements$ || a.enum$)
226
214
  mergeSpecifiedElementsOrEnum( a );
227
215
  // console.log( 'ET-DO:', effectiveSeqNo, a?.kind, a?.name, a._extensions?.elements?.length )
@@ -237,13 +225,16 @@ function populate( model ) {
237
225
  // Name-resolution relevant properties directly at artifact:
238
226
  // ‹view›.elements of input must have been moved (to elements$) before!
239
227
  // console.log('Q:',art.elements,art.enum,art.items,!!art.query)
228
+ // console.log('PA:',require('../model/revealInternalProperties').ref(art))
240
229
  if (art.includes) // first version of includes via effectiveTpe()
241
230
  art.includes.forEach( i => effectiveType( i._artifact ) );
242
231
  if (art.elements != null || art.enum != null || art.items != null)
243
232
  return art;
244
233
  if (art.target) {
234
+ // make sure that target._artifact is set:
235
+ const target = resolvePath( art.target, 'target', art );
245
236
  // try to implicitly redirect explicitly provided target:
246
- if (!origEffective?.target && art.kind !== 'mixin')
237
+ if (target && !origEffective?.target && art.kind !== 'mixin')
247
238
  redirectImplicitly( art, art );
248
239
  if (!art.expand)
249
240
  return art;
@@ -257,8 +248,14 @@ function populate( model ) {
257
248
  const leading = art.$queries[0];
258
249
  if (!leading) // parse error
259
250
  return null;
260
- // assert that there is no _effectiveType on leading (should be the case, as
261
- // you cannot refer to a query of another artifact)
251
+ if (leading._effectiveType !== undefined) {
252
+ // You cannot refer to a query of another artifact:
253
+ throw new CompilerAssertion(
254
+ `Unexpected _effectiveType on leading query of ${ art.name.absolute }`
255
+ );
256
+ }
257
+ // TODO: try just return (effectiveType( leading )) === 0 ? 0 : art;
258
+ setLink( leading, '_effectiveType', 0 ); // relevant to detect invalid $self.*
262
259
  populateQuery( leading );
263
260
  setLink( leading, '_effectiveType', leading );
264
261
  leading.$effectiveSeqNo = ++effectiveSeqNo;
@@ -313,7 +310,7 @@ function populate( model ) {
313
310
  if (art._origin !== undefined)
314
311
  return art._origin;
315
312
  if (art.type) // not stored in _origin
316
- return resolveType( art.type, art );
313
+ return resolvePath( art.type, 'type', art );
317
314
  return setLink( art, '_origin', getOriginRaw( art ) );
318
315
  }
319
316
 
@@ -321,17 +318,23 @@ function populate( model ) {
321
318
  if (!art._main) {
322
319
  if (art.query)
323
320
  return getOrigin( art.$queries?.[0] );
321
+ // TODO: if we add the `includes` mechanism, use resolveUncheckedPath() for
322
+ // includes here, because the accept function for includes requires the
323
+ // elements to have been calculated!
324
324
  }
325
325
  else {
326
326
  // TODO: write checks for path in enum?
327
327
  if (art.value?.path)
328
- return resolvePath( art.value, (art.$syntax === 'calc' ? 'calc' : 'expr'), art );
329
- if (art.kind === 'select')
330
- return getOrigin( dictFirst( art.$tableAliases ) );
328
+ return resolvePath( art.value, (art.$syntax === 'calc' ? 'calc' : 'column'), art );
329
+ if (art.kind === 'select') {
330
+ const alias = dictFirst( art.$tableAliases );
331
+ // With parse errors, the first “alias” might be $self. Using its origin
332
+ // would lead to a cyclic processing dependency.
333
+ return (alias.kind === '$tableAlias') ? getOrigin( alias ) : null;
334
+ }
331
335
  // init sets _origin for alias to sub query, only need to handle ref here:
332
336
  if (art.kind === '$tableAlias') {
333
- // do not use navigationEnv(): it would always call effectiveType() on the
334
- // source → we would have a deeper callstack
337
+ // do not call effectiveType() on the source to avoid a deeper callstack
335
338
  const source = resolvePath( art, 'from', art._parent );
336
339
  if (!source?._main)
337
340
  return source; // direct entity (or undefined)
@@ -339,33 +342,19 @@ function populate( model ) {
339
342
  // to call effectiveType() on the last assoc of a from ref:
340
343
  // TODO: check this with test3/Queries/DollarSelf/CorruptedSource.err.cds
341
344
  const assoc = effectiveType( source );
342
- return resolvePath( assoc?.target, 'target', assoc );
345
+ return assoc?.target._artifact;
343
346
  }
344
347
  }
345
348
  return '';
346
349
  }
347
350
 
348
- function resolveType( ref, user ) {
349
- if (ref._artifact !== undefined)
350
- return ref._artifact;
351
- while (user._outer) // in items
352
- user = user._outer;
353
- if (user.kind === 'event')
354
- return resolvePath( ref, 'eventType', user );
355
- if (user.kind === 'param' && user._parent &&
356
- (user._parent.kind === 'action' || user._parent.kind === 'function'))
357
- return resolvePath( ref, 'actionParamType', user );
358
- return resolvePath( ref, 'type', user );
359
- }
360
-
361
- function getCardinality( assoc ) {
362
- while (assoc?._effectiveType) {
363
- // if (--depth) throw Error(`GCARD: ${ Object.keys(art) }`)
364
- if (assoc.cardinality)
365
- return assoc.cardinality;
366
- assoc = getOrigin( assoc );
351
+ function getInheritedProp( art, prop ) {
352
+ while (art?._effectiveType) {
353
+ if (art[prop] !== undefined)
354
+ return art[prop];
355
+ art = getOrigin( art );
367
356
  }
368
- return {};
357
+ return undefined;
369
358
  }
370
359
 
371
360
  function userQuery( user ) {
@@ -511,7 +500,7 @@ function populate( model ) {
511
500
  //--------------------------------------------------------------------------
512
501
 
513
502
  // TODO: delete XSN._entities
514
- // TODO: delete ENTITY._from
503
+ // TODO: delete ENTITY._from - use _origin? instead _from[0]
515
504
  // TODO (after on-demand ext): delete XSN.$entity
516
505
 
517
506
  /**
@@ -533,8 +522,12 @@ function populate( model ) {
533
522
  const ielem = art.elements ? art.elements[id] : art.enum[id]; // inferred element
534
523
  const selem = art.elements$ ? art.elements$[id] : art.enum$[id]; // specified element
535
524
  if (!selem) {
536
- info( 'query-missing-element', [ ielem.name.location, art ], { id },
537
- 'Element $(ID) is missing in specified elements' );
525
+ info( 'query-missing-element', [ ielem.name.location, art ], {
526
+ '#': ielem.kind === 'enum' ? 'enum' : 'std', id,
527
+ }, {
528
+ std: 'Element $(ID) is missing in specified elements',
529
+ enum: 'Enum $(ID) is missing in specified enum values',
530
+ } );
538
531
  }
539
532
  else {
540
533
  for (const prop in selem) {
@@ -543,8 +536,6 @@ function populate( model ) {
543
536
  ielem[prop] = selem[prop];
544
537
  // required for gensrc mode of to-csn.js, otherwise the annotation
545
538
  // may be lost during recompilation.
546
- // TODO: Clarify: Should gensrc add this annotation to the column or as an
547
- // annotate statement? Currently it's at the column.
548
539
  ielem[prop].$priority = 'annotate';
549
540
  wasAnnotated = true;
550
541
  }
@@ -552,7 +543,7 @@ function populate( model ) {
552
543
  if (!ielem.typeProps$)
553
544
  setLink(ielem, 'typeProps$', Object.create(null));
554
545
  // Note: At this point in time, effectiveType() was likely not called on the
555
- // element, yet. Setting it here, we can't compare it to it's value from _origin.
546
+ // element, yet. Setting it here, we can't compare it to its value from _origin.
556
547
  ielem.typeProps$[prop] = selem[prop];
557
548
  }
558
549
  }
@@ -602,17 +593,19 @@ function populate( model ) {
602
593
 
603
594
  function populateQuery( query ) {
604
595
  if (query._combined || !query.from || !query.$tableAliases)
605
- // already done or $join query or parse error
596
+ // already done (TODO: re-check!) or $join query or parse error
606
597
  return query;
607
598
  setLink( query, '_combined', Object.create(null) );
608
599
  query.$inlines = [];
609
600
  forEachGeneric( query, '$tableAliases', resolveTabRef );
610
-
611
601
  initFromColumns( query, query.columns );
612
602
  if (query.excludingDict) {
613
603
  for (const name in query.excludingDict)
614
604
  resolveExcluding( name, query._combined, query.excludingDict, query );
615
605
  }
606
+ // TODO: should we to set some falsy values? E.g. with $self.*, cyclic from?
607
+ // Yes, when element names cannot fully be determined (wrong source ref,
608
+ // cyclic, ...) BTW, similar with `includes`
616
609
  return query;
617
610
 
618
611
  function resolveTabRef( alias ) {
@@ -652,10 +645,11 @@ function populate( model ) {
652
645
  return null;
653
646
  // If we allow CDL-style casts of `expand`s to associations in the future, we
654
647
  // need to ignore an explicit type, i.e. not getOrigin():
655
- const assoc = resolvePath( elem.value, 'expr', elem ); // TODO: extra 'column'?
648
+ const assoc = resolvePath( elem.value, 'column', elem );
656
649
  if (!effectiveType( assoc )?.target)
657
650
  return initFromColumns( elem, elem.expand );
658
- const { targetMax } = path[path.length - 1].cardinality || getCardinality( assoc );
651
+ const { targetMax } = path[path.length - 1].cardinality ||
652
+ getInheritedProp( assoc, 'cardinality' ) || {};
659
653
  if (targetMax && (targetMax.val === '*' || targetMax.val > 1)) {
660
654
  elem.items = { location: elem.expand[$location] };
661
655
  setLink( elem.items, '_outer', elem );
@@ -666,9 +660,9 @@ function populate( model ) {
666
660
  // TODO: make this function shorter - make part of this (e.g. setting
667
661
  // parent/name) also be part of definer.js
668
662
  // TODO: query is actually the elemParent, where the new elements are added to
669
- // top-level: just query, columns
670
- // inline: + elements (TODO: remove), colParent
671
- // expand: just query (which is a column/element), columns=array of expand
663
+ // top-level: ( query, query.columns )
664
+ // inline: ( queryOrColParent, col.inline, col )
665
+ // expand: ( col, col.expand )
672
666
  function initFromColumns( query, columns, inlineHead = undefined ) {
673
667
  const elemsParent = query.items || query;
674
668
  if (!inlineHead) {
@@ -677,7 +671,11 @@ function populate( model ) {
677
671
  query._main.elements = elemsParent.elements;
678
672
  }
679
673
 
680
- for (const col of columns || [ { val: '*' } ]) {
674
+ if (!columns)
675
+ columns = [ { val: '*' } ];
676
+
677
+ for (let i = 0; i < columns.length; ++i) {
678
+ const col = columns[i];
681
679
  if (col.val === '*') {
682
680
  const siblings = wildcardSiblings( columns, query );
683
681
  expandWildcard( col, siblings, inlineHead, query );
@@ -689,15 +687,16 @@ function populate( model ) {
689
687
  col.kind = '$inline';
690
688
  col.name = {};
691
689
  // a name for this internal symtab entry (e.g. '.2' to avoid clashes
692
- // with real elements) is only relevant for for `cdsc -R`/debugging
690
+ // with real elements) is only relevant for `cdsc -R`/debugging
693
691
  const q = userQuery( query );
694
692
  q.$inlines.push( col );
693
+ dependsOnSilent( q, col );
695
694
  // or use userQuery( query ) in the following, too?
696
695
  setMemberParent( col, `.${ q.$inlines.length }`, query );
697
696
  initFromColumns( query, col.inline, col );
698
697
  }
699
698
  else if (!col.$replacement) {
700
- const id = ensureColumnName( col, query );
699
+ const id = ensureColumnName( col, i, query );
701
700
  col.kind = 'element';
702
701
  dictAdd( elemsParent.elements, id, col, ( name, location ) => {
703
702
  error( 'duplicate-definition', [ location, query ], { name, '#': 'element' } );
@@ -710,25 +709,36 @@ function populate( model ) {
710
709
  }
711
710
 
712
711
  // TODO: probably do this already in definer.js
713
- function ensureColumnName( col, query ) {
712
+ function ensureColumnName( col, colIndex, query ) {
714
713
  if (col.name)
715
714
  return col.name.id;
716
715
  if (col.inline || col.val === '*')
717
716
  return '';
718
717
  const path = col.value &&
719
- (col.value.path || !col.value.args && col.value.func && col.value.func.path);
720
- if (path && path.length) {
721
- const last = !path.broken && path.length && path[path.length - 1];
718
+ (col.value.path || !col.value.args && col.value.func?.path);
719
+ if (path) {
720
+ const last = path.length && !path.broken && path[path.length - 1];
722
721
  if (last) {
723
722
  col.name = { id: last.id || '', location: last.location, $inferred: 'as' };
724
723
  return col.name.id;
725
724
  }
726
725
  }
727
- else if (col.value || col.expand) {
728
- error( 'query-req-name', [ col.value && col.value.location || col.location, query ], {},
726
+ else if (col.expand || col.value && (col._pathHead || query._parent.kind !== 'select')) {
727
+ // _pathHead => inline/expand; _parent -> only allowed in sub-selects
728
+ error( 'query-req-name', [ col.value?.location || col.location, query ], {},
729
729
  'Alias name is required for this select item' );
730
730
  }
731
- // invent a name for code completion in expression
731
+ else if (col.value) {
732
+ col.name = {
733
+ // NOTE: If the alias is changed, corresponding name-clash tests must be updated as well!
734
+ id: `$_column_${ colIndex + 1 }`,
735
+ location: col.value.location || col.location,
736
+ $inferred: '$internal',
737
+ };
738
+ col.$inferred = '$internal';
739
+ return col.name.id;
740
+ }
741
+ // invent a name for code completion in expression, see also #10596
732
742
  col.name = {
733
743
  id: '',
734
744
  location: col.value && col.value.location || col.location,
@@ -768,8 +778,9 @@ function populate( model ) {
768
778
  return siblings;
769
779
 
770
780
  let seenWildcard = null;
781
+ let colIndex = 0;
771
782
  for (const col of columns) {
772
- const id = ensureColumnName( col, query );
783
+ const id = ensureColumnName( col, colIndex, query );
773
784
  if (id) {
774
785
  col.$replacement = !seenWildcard;
775
786
  siblings[id] = !(id in siblings) && col;
@@ -777,6 +788,7 @@ function populate( model ) {
777
788
  else if (col.val === '*') {
778
789
  seenWildcard = true;
779
790
  }
791
+ ++colIndex;
780
792
  }
781
793
  return siblings;
782
794
  }
@@ -788,10 +800,13 @@ function populate( model ) {
788
800
  const inferred = query._main.$inferred;
789
801
  const excludingDict = (colParent || query).excludingDict || Object.create(null);
790
802
 
791
- const envParent = wildcard._pathHead; // TODO: rename _pathHead to _pathEnv
803
+ const envParent = wildcard._pathHead; // TODO: rename _pathHead to _columnParent
792
804
  // console.log('S1:',location.line,location.col,
793
805
  // envParent&&!!envParent._origin&&envParent._origin.name)
794
- const env = columnEnv( envParent, query );
806
+ const env = wildcardColumnEnv( wildcard, query );
807
+ if (!env)
808
+ return;
809
+
795
810
  // if (envParent) console.log('S2:',location.line,location.col,
796
811
  // envParent?.name,envParent?._origin?.name,
797
812
  // Object.keys(env),Object.keys(elements))
@@ -851,14 +866,25 @@ function populate( model ) {
851
866
  }
852
867
  }
853
868
 
854
- function columnEnv( envParent, query ) { // etc. wildcard._pathHead;
869
+ function wildcardColumnEnv( wildcard, query ) { // etc. wildcard._pathHead;
855
870
  // if (envParent) console.log( 'CE:', envParent._origin, query );
856
- if (!envParent)
857
- return userQuery( query )._combined;
858
- const env = navigationEnv( getOrigin( envParent ) );
859
- if (env === 0)
860
- return 0;
861
- return env?.elements || Object.create(null);
871
+ const colParent = wildcard._pathHead;
872
+ if (!colParent)
873
+ return userQuery( query )._combined; // see combinedSourcesOrParentElements
874
+
875
+ const head = resolvePath( colParent.value, 'column', colParent );
876
+ // eslint-disable-next-line no-nested-ternary
877
+ if (!head
878
+ ? !columnRefStartsWithSelf( colParent )
879
+ : head._main
880
+ ? userQuery( head ) !== userQuery( query )
881
+ : head._main !== query._main)
882
+ return nestedElements( wildcard );
883
+
884
+ error( 'def-unexpected-wildcard', [ wildcard.location, colParent ], { code: '*' },
885
+ 'Unexpected $(CODE) (wildcard) after $self/association to self reference' );
886
+ model.$assert = null; // explains cyclic dependencies
887
+ return null;
862
888
  }
863
889
 
864
890
  function reportReplacement( sibling, navElem, query ) {
@@ -902,7 +928,7 @@ function populate( model ) {
902
928
  function setElementOrigin( queryElem, navElem, name, location ) {
903
929
  const sourceElem = navElem._origin;
904
930
  const alias = navElem._parent;
905
- // always expand * to path with table alias (reason: columns current_date etc)
931
+ // always expand * to path with table alias (reason: columns $user etc)
906
932
  const path = [ { id: alias.name.id, location }, { id: name, location } ];
907
933
  queryElem.value = { path, location };
908
934
  setLink( path[0], '_navigation', alias );
@@ -931,7 +957,7 @@ function populate( model ) {
931
957
  // TODO: Custom kind?
932
958
  if (elem.$isSpecifiedElement)
933
959
  return false;
934
- const assocTarget = resolvePath( assoc.target, 'target', assoc );
960
+ const assocTarget = assoc.target._artifact;
935
961
  let target = assocTarget;
936
962
  // console.log( info( null, [ elem.location, elem ], {target,art:assoc,name:''+assoc.target},
937
963
  // 'RED').toString())
@@ -1139,9 +1165,10 @@ function populate( model ) {
1139
1165
  function isDirectProjection( proj, base ) {
1140
1166
  return proj.kind === 'entity' && // not event
1141
1167
  // direct proj (TODO: or should we add them to another list?)
1168
+ // TODO: delete ENTITY._from
1142
1169
  proj.query && proj.query.op && proj.query.op.val === 'SELECT' &&
1143
1170
  proj._from && proj._from.length === 1 &&
1144
- base === resolvePath( proj._from[0], 'from', proj );
1171
+ base === resolvePath( proj._from[0], 'from', proj.query );
1145
1172
  }
1146
1173
 
1147
1174
  // Auto-exposure -----------------------------------------------------------
@@ -1152,7 +1179,8 @@ function populate( model ) {
1152
1179
  return target.$autoexpose;
1153
1180
  const origTarget = target;
1154
1181
  const chain = [];
1155
- let source = target._from && resolvePath( target._from[0], 'from', target );
1182
+ const alias1 = target._from?.[0]; // TODO: delete ENTITY._from ?
1183
+ let source = alias1 && resolvePath( alias1, 'from', alias1._parent );
1156
1184
  // query source ref might not have been resolved yet, cycle avoided as
1157
1185
  // setAutoExposed() sets $autoexpose and a second call on same art would
1158
1186
  // return false
@@ -1160,7 +1188,8 @@ function populate( model ) {
1160
1188
  // stop at first ancestor with annotation or at non-query entity
1161
1189
  chain.push( target );
1162
1190
  target = source;
1163
- source = target._from && resolvePath( target._from[0], 'from', target );
1191
+ const alias = target._from?.[0]; // TODO: delete ENTITY._from ?
1192
+ source = alias && resolvePath( alias, 'from', alias._parent );
1164
1193
  }
1165
1194
  const autoexpose = target.$autoexpose;
1166
1195
  if (typeof autoexpose === 'boolean') {
@@ -159,7 +159,8 @@ function propagate( model ) {
159
159
  (!target.type.$inferred || target.type.$inferred === 'cast');
160
160
  const keys = Object.keys( source );
161
161
  for (const prop of keys) {
162
- if (prop in target) // TODO: warning with competing props from multi-includes
162
+ // TODO: warning with competing props from multi-includes
163
+ if (target[prop] !== undefined || source[prop] === undefined)
163
164
  continue;
164
165
  const transformer = props[prop] || props[prop.charAt(0)];
165
166
  if (transformer)
@@ -174,9 +175,9 @@ function propagate( model ) {
174
175
  while (elem._parent.kind === 'element')
175
176
  elem = elem._parent;
176
177
  if (elem !== source) {
177
- if (!('notNull' in target) && ('notNull' in elem))
178
+ if (target.notNull === undefined && elem.notNull !== undefined)
178
179
  props.notNull( 'notNull', target, elem );
179
- if (!('virtual' in target) && ('virtual' in elem))
180
+ if (target.virtual === undefined && elem.virtual !== undefined)
180
181
  props.virtual( 'virtual', target, elem );
181
182
  }
182
183
  }