@sap/cds-compiler 4.0.2 → 4.2.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 (101) hide show
  1. package/CHANGELOG.md +200 -5
  2. package/bin/cdsc.js +18 -15
  3. package/doc/CHANGELOG_BETA.md +16 -0
  4. package/doc/CHANGELOG_DEPRECATED.md +15 -0
  5. package/lib/api/main.js +33 -13
  6. package/lib/api/options.js +2 -2
  7. package/lib/api/validate.js +25 -25
  8. package/lib/base/location.js +6 -7
  9. package/lib/base/message-registry.js +123 -42
  10. package/lib/base/messages.js +18 -10
  11. package/lib/base/model.js +43 -10
  12. package/lib/checks/defaultValues.js +6 -6
  13. package/lib/checks/elements.js +11 -10
  14. package/lib/checks/foreignKeys.js +0 -5
  15. package/lib/checks/manyNavigations.js +33 -0
  16. package/lib/checks/onConditions.js +22 -14
  17. package/lib/checks/queryNoDbArtifacts.js +132 -73
  18. package/lib/checks/selectItems.js +4 -55
  19. package/lib/checks/sql-snippets.js +15 -4
  20. package/lib/checks/types.js +3 -3
  21. package/lib/checks/utils.js +4 -3
  22. package/lib/checks/validator.js +3 -1
  23. package/lib/compiler/.eslintrc.json +2 -1
  24. package/lib/compiler/assert-consistency.js +71 -40
  25. package/lib/compiler/base.js +7 -2
  26. package/lib/compiler/builtins.js +40 -41
  27. package/lib/compiler/checks.js +415 -367
  28. package/lib/compiler/classes.js +62 -0
  29. package/lib/compiler/cycle-detector.js +9 -9
  30. package/lib/compiler/define.js +124 -90
  31. package/lib/compiler/extend.js +115 -88
  32. package/lib/compiler/finalize-parse-cdl.js +26 -25
  33. package/lib/compiler/generate.js +57 -49
  34. package/lib/compiler/index.js +56 -56
  35. package/lib/compiler/kick-start.js +10 -7
  36. package/lib/compiler/moduleLayers.js +1 -1
  37. package/lib/compiler/populate.js +180 -144
  38. package/lib/compiler/propagator.js +10 -9
  39. package/lib/compiler/resolve.js +321 -246
  40. package/lib/compiler/shared.js +812 -433
  41. package/lib/compiler/tweak-assocs.js +114 -50
  42. package/lib/compiler/utils.js +241 -46
  43. package/lib/edm/.eslintrc.json +40 -1
  44. package/lib/edm/annotations/genericTranslation.js +721 -707
  45. package/lib/edm/annotations/preprocessAnnotations.js +88 -77
  46. package/lib/edm/csn2edm.js +389 -378
  47. package/lib/edm/edm.js +679 -770
  48. package/lib/edm/edmAnnoPreprocessor.js +132 -146
  49. package/lib/edm/edmInboundChecks.js +29 -27
  50. package/lib/edm/edmPreprocessor.js +689 -648
  51. package/lib/edm/edmUtils.js +279 -300
  52. package/lib/gen/Dictionary.json +34 -10
  53. package/lib/gen/language.checksum +1 -1
  54. package/lib/gen/language.interp +1 -1
  55. package/lib/gen/languageParser.js +2857 -2856
  56. package/lib/json/from-csn.js +77 -51
  57. package/lib/json/to-csn.js +15 -15
  58. package/lib/language/antlrParser.js +2 -1
  59. package/lib/language/genericAntlrParser.js +52 -43
  60. package/lib/language/language.g4 +61 -64
  61. package/lib/language/multiLineStringParser.js +2 -0
  62. package/lib/main.d.ts +65 -0
  63. package/lib/model/csnRefs.js +37 -19
  64. package/lib/model/csnUtils.js +51 -18
  65. package/lib/model/revealInternalProperties.js +30 -22
  66. package/lib/modelCompare/compare.js +149 -41
  67. package/lib/modelCompare/utils/filter.js +55 -25
  68. package/lib/optionProcessor.js +21 -9
  69. package/lib/render/manageConstraints.js +20 -17
  70. package/lib/render/toCdl.js +63 -23
  71. package/lib/render/toHdbcds.js +2 -2
  72. package/lib/render/toRename.js +4 -9
  73. package/lib/render/toSql.js +82 -35
  74. package/lib/render/utils/common.js +11 -9
  75. package/lib/render/utils/unique.js +52 -0
  76. package/lib/transform/db/applyTransformations.js +62 -21
  77. package/lib/transform/db/assertUnique.js +7 -8
  78. package/lib/transform/db/associations.js +2 -2
  79. package/lib/transform/db/cdsPersistence.js +9 -9
  80. package/lib/transform/db/constraints.js +47 -17
  81. package/lib/transform/db/expansion.js +138 -68
  82. package/lib/transform/db/flattening.js +98 -30
  83. package/lib/transform/db/rewriteCalculatedElements.js +20 -14
  84. package/lib/transform/db/temporal.js +1 -1
  85. package/lib/transform/db/transformExists.js +8 -7
  86. package/lib/transform/db/views.js +73 -33
  87. package/lib/transform/draft/db.js +11 -9
  88. package/lib/transform/draft/odata.js +1 -1
  89. package/lib/transform/{forOdataNew.js → forOdata.js} +10 -7
  90. package/lib/transform/forRelationalDB.js +148 -136
  91. package/lib/transform/localized.js +92 -54
  92. package/lib/transform/odata/toFinalBaseType.js +3 -3
  93. package/lib/transform/{transformUtilsNew.js → transformUtils.js} +13 -111
  94. package/lib/transform/translateAssocsToJoins.js +14 -28
  95. package/lib/utils/file.js +7 -7
  96. package/lib/utils/moduleResolve.js +210 -121
  97. package/lib/utils/objectUtils.js +1 -1
  98. package/package.json +5 -5
  99. package/share/messages/check-proper-type-of.md +1 -1
  100. package/share/messages/{check-proper-type.md → def-missing-type.md} +3 -5
  101. package/share/messages/message-explanations.json +1 -1
@@ -41,14 +41,15 @@ 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
 
50
- const $inferred = Symbol.for('cds.$inferred');
51
- const $location = Symbol.for('cds.$location');
51
+ const $inferred = Symbol.for( 'cds.$inferred' );
52
+ const $location = Symbol.for( 'cds.$location' );
52
53
 
53
54
  /**
54
55
  * These properties are copied from specified elements.
@@ -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,22 +188,34 @@ 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 )
228
216
  extendArtifactAfter( a ); // after setting _effectiveType (for messages)
229
217
  if (a.typeProps$)
230
- setSpecifiedElementTypeProperties(a);
218
+ setSpecifiedElementTypeProperties( a );
231
219
  }
232
220
  // console.log( 'ET-END:', art?.kind, art?.name )
233
221
  return art;
@@ -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 ) {
@@ -404,7 +393,7 @@ function populate( model ) {
404
393
  proxyCopyMembers( art, 'elements', struct.elements, art.path?.location, '$navElement' );
405
394
  return true;
406
395
  }
407
- if (art.elements || art.kind === '$tableAlias' || art.kind === '$inline' ||
396
+ if (art.elements || art.kind === '$inline' ||
408
397
  // no element expansions for "non-proper" types like
409
398
  // entities (as parameter types) etc:
410
399
  struct.kind !== 'type' && struct.kind !== 'element' && struct.kind !== 'param' &&
@@ -418,7 +407,8 @@ function populate( model ) {
418
407
  const location = ref && ref.location || art.location;
419
408
  // console.log( message( null, location, art, {target:struct,art}, 'Info','EXPAND-ELEM')
420
409
  // .toString(), Object.keys(struct.elements))
421
- proxyCopyMembers( art, 'elements', struct.elements, weakLocation( location ) );
410
+ proxyCopyMembers( art, 'elements', struct.elements, weakLocation( location ),
411
+ null, isDeprecatedEnabled( options, 'noKeyPropagationWithExpansions' ) );
422
412
  // Set elements expansion status (the if condition is always true, as no
423
413
  // elements expansion will take place on artifact with existing other
424
414
  // member property):
@@ -511,7 +501,7 @@ function populate( model ) {
511
501
  //--------------------------------------------------------------------------
512
502
 
513
503
  // TODO: delete XSN._entities
514
- // TODO: delete ENTITY._from
504
+ // TODO: delete ENTITY._from - use _origin? instead _from[0]
515
505
  // TODO (after on-demand ext): delete XSN.$entity
516
506
 
517
507
  /**
@@ -533,8 +523,12 @@ function populate( model ) {
533
523
  const ielem = art.elements ? art.elements[id] : art.enum[id]; // inferred element
534
524
  const selem = art.elements$ ? art.elements$[id] : art.enum$[id]; // specified element
535
525
  if (!selem) {
536
- info( 'query-missing-element', [ ielem.name.location, art ], { id },
537
- 'Element $(ID) is missing in specified elements' );
526
+ info( 'query-missing-element', [ ielem.name.location, art ], {
527
+ '#': ielem.kind === 'enum' ? 'enum' : 'std', id,
528
+ }, {
529
+ std: 'Element $(ID) is missing in specified elements',
530
+ enum: 'Enum $(ID) is missing in specified enum values',
531
+ } );
538
532
  }
539
533
  else {
540
534
  for (const prop in selem) {
@@ -543,30 +537,28 @@ function populate( model ) {
543
537
  ielem[prop] = selem[prop];
544
538
  // required for gensrc mode of to-csn.js, otherwise the annotation
545
539
  // 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
540
  ielem[prop].$priority = 'annotate';
549
541
  wasAnnotated = true;
550
542
  }
551
543
  else if (typePropertiesFromSpecifiedElements[prop]) {
552
544
  if (!ielem.typeProps$)
553
- setLink(ielem, 'typeProps$', Object.create(null));
545
+ setLink( ielem, 'typeProps$', Object.create( null ) );
554
546
  // 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.
547
+ // element, yet. Setting it here, we can't compare it to its value from _origin.
556
548
  ielem.typeProps$[prop] = selem[prop];
557
549
  }
558
550
  }
559
551
 
560
552
  selem.$replacement = true;
561
553
  if (selem.elements)
562
- setLink(ielem, 'elements$', selem.elements);
554
+ setLink( ielem, 'elements$', selem.elements );
563
555
  if (selem.enum)
564
- setLink(ielem, 'enum$', selem.enum);
556
+ setLink( ielem, 'enum$', selem.enum );
565
557
  }
566
558
  }
567
559
 
568
560
  if (wasAnnotated)
569
- setExpandStatusAnnotate(art, 'annotate');
561
+ setExpandStatusAnnotate( art, 'annotate' );
570
562
 
571
563
  // TODO: We don't check enum$, yet! We first need to fix expansion for
572
564
  // `cast(elem as EnumType)` (see #9421)
@@ -586,8 +578,8 @@ function populate( model ) {
586
578
  for (const prop in art.typeProps$) {
587
579
  let o = art;
588
580
  if (o._effectiveType !== 0) { // cyclic
589
- while (!o[prop] && getOrigin(o))
590
- o = getOrigin(o);
581
+ while (!o[prop] && getOrigin( o ))
582
+ o = getOrigin( o );
591
583
  }
592
584
 
593
585
  if (typePropertiesFromSpecifiedElements[prop] === 'if-undefined') {
@@ -602,17 +594,19 @@ function populate( model ) {
602
594
 
603
595
  function populateQuery( query ) {
604
596
  if (query._combined || !query.from || !query.$tableAliases)
605
- // already done or $join query or parse error
597
+ // already done (TODO: re-check!) or $join query or parse error
606
598
  return query;
607
- setLink( query, '_combined', Object.create(null) );
599
+ setLink( query, '_combined', Object.create( null ) );
608
600
  query.$inlines = [];
609
601
  forEachGeneric( query, '$tableAliases', resolveTabRef );
610
-
611
602
  initFromColumns( query, query.columns );
612
603
  if (query.excludingDict) {
613
604
  for (const name in query.excludingDict)
614
605
  resolveExcluding( name, query._combined, query.excludingDict, query );
615
606
  }
607
+ // TODO: should we to set some falsy values? E.g. with $self.*, cyclic from?
608
+ // Yes, when element names cannot fully be determined (wrong source ref,
609
+ // cyclic, ...) BTW, similar with `includes`
616
610
  return query;
617
611
 
618
612
  function resolveTabRef( alias ) {
@@ -628,7 +622,7 @@ function populate( model ) {
628
622
  forEachGeneric( { elements: alias.elements }, 'elements', ( elem, name ) => {
629
623
  if (elem.$duplicates !== true)
630
624
  dictAddArray( query._combined, name, elem, null ); // not dictAdd()
631
- });
625
+ } );
632
626
  }
633
627
  }
634
628
 
@@ -652,10 +646,11 @@ function populate( model ) {
652
646
  return null;
653
647
  // If we allow CDL-style casts of `expand`s to associations in the future, we
654
648
  // need to ignore an explicit type, i.e. not getOrigin():
655
- const assoc = resolvePath( elem.value, 'expr', elem ); // TODO: extra 'column'?
649
+ const assoc = resolvePath( elem.value, 'column', elem );
656
650
  if (!effectiveType( assoc )?.target)
657
651
  return initFromColumns( elem, elem.expand );
658
- const { targetMax } = path[path.length - 1].cardinality || getCardinality( assoc );
652
+ const { targetMax } = path[path.length - 1].cardinality ||
653
+ getInheritedProp( assoc, 'cardinality' ) || {};
659
654
  if (targetMax && (targetMax.val === '*' || targetMax.val > 1)) {
660
655
  elem.items = { location: elem.expand[$location] };
661
656
  setLink( elem.items, '_outer', elem );
@@ -666,18 +661,22 @@ function populate( model ) {
666
661
  // TODO: make this function shorter - make part of this (e.g. setting
667
662
  // parent/name) also be part of definer.js
668
663
  // 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
664
+ // top-level: ( query, query.columns )
665
+ // inline: ( queryOrColParent, col.inline, col )
666
+ // expand: ( col, col.expand )
672
667
  function initFromColumns( query, columns, inlineHead = undefined ) {
673
668
  const elemsParent = query.items || query;
674
669
  if (!inlineHead) {
675
- elemsParent.elements = Object.create(null);
670
+ elemsParent.elements = Object.create( null );
676
671
  if (query._main._leadingQuery === query) // never the case for 'expand'
677
672
  query._main.elements = elemsParent.elements;
678
673
  }
679
674
 
680
- for (const col of columns || [ { val: '*' } ]) {
675
+ if (!columns)
676
+ columns = [ { val: '*' } ];
677
+
678
+ for (let i = 0; i < columns.length; ++i) {
679
+ const col = columns[i];
681
680
  if (col.val === '*') {
682
681
  const siblings = wildcardSiblings( columns, query );
683
682
  expandWildcard( col, siblings, inlineHead, query );
@@ -689,19 +688,20 @@ function populate( model ) {
689
688
  col.kind = '$inline';
690
689
  col.name = {};
691
690
  // 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
691
+ // with real elements) is only relevant for `cdsc -R`/debugging
693
692
  const q = userQuery( query );
694
693
  q.$inlines.push( col );
694
+ dependsOnSilent( q, col );
695
695
  // or use userQuery( query ) in the following, too?
696
696
  setMemberParent( col, `.${ q.$inlines.length }`, query );
697
697
  initFromColumns( query, col.inline, col );
698
698
  }
699
699
  else if (!col.$replacement) {
700
- const id = ensureColumnName( col, query );
700
+ const id = ensureColumnName( col, i, query );
701
701
  col.kind = 'element';
702
702
  dictAdd( elemsParent.elements, id, col, ( name, location ) => {
703
703
  error( 'duplicate-definition', [ location, query ], { name, '#': 'element' } );
704
- });
704
+ } );
705
705
  setMemberParent( col, id, query );
706
706
  }
707
707
  }
@@ -710,25 +710,36 @@ function populate( model ) {
710
710
  }
711
711
 
712
712
  // TODO: probably do this already in definer.js
713
- function ensureColumnName( col, query ) {
713
+ function ensureColumnName( col, colIndex, query ) {
714
714
  if (col.name)
715
715
  return col.name.id;
716
716
  if (col.inline || col.val === '*')
717
717
  return '';
718
718
  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];
719
+ (col.value.path || !col.value.args && col.value.func?.path);
720
+ if (path) {
721
+ const last = path.length && !path.broken && path[path.length - 1];
722
722
  if (last) {
723
723
  col.name = { id: last.id || '', location: last.location, $inferred: 'as' };
724
724
  return col.name.id;
725
725
  }
726
726
  }
727
- else if (col.value || col.expand) {
728
- error( 'query-req-name', [ col.value && col.value.location || col.location, query ], {},
727
+ else if (col.expand || col.value && (col._pathHead || query._parent.kind !== 'select')) {
728
+ // _pathHead => inline/expand; _parent -> only allowed in sub-selects
729
+ error( 'query-req-name', [ col.value?.location || col.location, query ], {},
729
730
  'Alias name is required for this select item' );
730
731
  }
731
- // invent a name for code completion in expression
732
+ else if (col.value) {
733
+ col.name = {
734
+ // NOTE: If the alias is changed, corresponding name-clash tests must be updated as well!
735
+ id: `$_column_${ colIndex + 1 }`,
736
+ location: col.value.location || col.location,
737
+ $inferred: '$internal',
738
+ };
739
+ col.$inferred = '$internal';
740
+ return col.name.id;
741
+ }
742
+ // invent a name for code completion in expression, see also #10596
732
743
  col.name = {
733
744
  id: '',
734
745
  location: col.value && col.value.location || col.location,
@@ -763,13 +774,14 @@ function populate( model ) {
763
774
  // col ($replacement set before *)
764
775
  // false if two cols have same name
765
776
  function wildcardSiblings( columns, query ) {
766
- const siblings = Object.create(null);
777
+ const siblings = Object.create( null );
767
778
  if (!columns)
768
779
  return siblings;
769
780
 
770
781
  let seenWildcard = null;
782
+ let colIndex = 0;
771
783
  for (const col of columns) {
772
- const id = ensureColumnName( col, query );
784
+ const id = ensureColumnName( col, colIndex, query );
773
785
  if (id) {
774
786
  col.$replacement = !seenWildcard;
775
787
  siblings[id] = !(id in siblings) && col;
@@ -777,6 +789,7 @@ function populate( model ) {
777
789
  else if (col.val === '*') {
778
790
  seenWildcard = true;
779
791
  }
792
+ ++colIndex;
780
793
  }
781
794
  return siblings;
782
795
  }
@@ -786,12 +799,15 @@ function populate( model ) {
786
799
  const { elements } = query.items || query;
787
800
  let location = wildcard.location || query.from && query.from.location || query.location;
788
801
  const inferred = query._main.$inferred;
789
- const excludingDict = (colParent || query).excludingDict || Object.create(null);
802
+ const excludingDict = (colParent || query).excludingDict || Object.create( null );
790
803
 
791
- const envParent = wildcard._pathHead; // TODO: rename _pathHead to _pathEnv
804
+ const envParent = wildcard._pathHead; // TODO: rename _pathHead to _columnParent
792
805
  // console.log('S1:',location.line,location.col,
793
806
  // envParent&&!!envParent._origin&&envParent._origin.name)
794
- const env = columnEnv( envParent, query );
807
+ const env = wildcardColumnEnv( wildcard, query );
808
+ if (!env)
809
+ return;
810
+
795
811
  // if (envParent) console.log('S2:',location.line,location.col,
796
812
  // envParent?.name,envParent?._origin?.name,
797
813
  // Object.keys(env),Object.keys(elements))
@@ -810,15 +826,15 @@ function populate( model ) {
810
826
  dictAdd( elements, name, sibling, ( _name, loc ) => {
811
827
  // there can be a definition from a previous inline with the same name:
812
828
  error( 'duplicate-definition', [ loc, query ], { name, '#': 'element' } );
813
- });
829
+ } );
814
830
  setMemberParent( sibling, name, query );
815
831
  }
816
832
  // else {
817
833
  // sibling.$inferred = 'query';
818
834
  // }
819
835
  }
820
- else if (Array.isArray(navElem)) {
821
- const names = navElem.filter( e => !e.$duplicates)
836
+ else if (Array.isArray( navElem )) {
837
+ const names = navElem.filter( e => !e.$duplicates )
822
838
  .map( e => `${ e.name.alias }.${ e.name.element }` );
823
839
  if (names.length) {
824
840
  error( 'wildcard-ambiguous', [ location, query ], { id: name, names },
@@ -833,7 +849,7 @@ function populate( model ) {
833
849
  dictAdd( elements, name, elem, ( _name, loc ) => {
834
850
  // there can be a definition from a previous inline with the same name:
835
851
  error( 'duplicate-definition', [ loc, query ], { name, '#': 'element' } );
836
- });
852
+ } );
837
853
  elem.$inferred = '*';
838
854
  elem.name.$inferred = '*';
839
855
  if (envParent)
@@ -851,14 +867,25 @@ function populate( model ) {
851
867
  }
852
868
  }
853
869
 
854
- function columnEnv( envParent, query ) { // etc. wildcard._pathHead;
870
+ function wildcardColumnEnv( wildcard, query ) { // etc. wildcard._pathHead;
855
871
  // 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);
872
+ const colParent = wildcard._pathHead;
873
+ if (!colParent)
874
+ return userQuery( query )._combined; // see combinedSourcesOrParentElements
875
+
876
+ const head = resolvePath( colParent.value, 'column', colParent );
877
+ // eslint-disable-next-line no-nested-ternary
878
+ if (!head
879
+ ? !columnRefStartsWithSelf( colParent )
880
+ : head._main
881
+ ? userQuery( head ) !== userQuery( query )
882
+ : head._main !== query._main)
883
+ return nestedElements( wildcard );
884
+
885
+ error( 'def-unexpected-wildcard', [ wildcard.location, colParent ], { code: '*' },
886
+ 'Unexpected $(CODE) (wildcard) after $self/association to self reference' );
887
+ model.$assert = null; // explains cyclic dependencies
888
+ return null;
862
889
  }
863
890
 
864
891
  function reportReplacement( sibling, navElem, query ) {
@@ -869,7 +896,7 @@ function populate( model ) {
869
896
  if (!sibling.target || sibling.target.$inferred || // not explicit REDIRECTED TO
870
897
  path && path[path.length - 1].id !== sibling.name.id) { // or renamed
871
898
  const { id } = sibling.name;
872
- if (Array.isArray(navElem)) {
899
+ if (Array.isArray( navElem )) {
873
900
  // ID published! Used in stakeholder project; if renamed, add to oldMessageIds
874
901
  info( 'wildcard-excluding-many', [ sibling.name.location, query ],
875
902
  { id, keyword: 'excluding' },
@@ -902,7 +929,7 @@ function populate( model ) {
902
929
  function setElementOrigin( queryElem, navElem, name, location ) {
903
930
  const sourceElem = navElem._origin;
904
931
  const alias = navElem._parent;
905
- // always expand * to path with table alias (reason: columns current_date etc)
932
+ // always expand * to path with table alias (reason: columns $user etc)
906
933
  const path = [ { id: alias.name.id, location }, { id: name, location } ];
907
934
  queryElem.value = { path, location };
908
935
  setLink( path[0], '_navigation', alias );
@@ -931,7 +958,7 @@ function populate( model ) {
931
958
  // TODO: Custom kind?
932
959
  if (elem.$isSpecifiedElement)
933
960
  return false;
934
- const assocTarget = resolvePath( assoc.target, 'target', assoc );
961
+ const assocTarget = assoc.target._artifact;
935
962
  let target = assocTarget;
936
963
  // console.log( info( null, [ elem.location, elem ], {target,art:assoc,name:''+assoc.target},
937
964
  // 'RED').toString())
@@ -974,6 +1001,12 @@ function populate( model ) {
974
1001
 
975
1002
  function redirectImplicitlyDo( elem, assoc, target, service ) {
976
1003
  // console.log('ES:',elem.name.absolute,elem.name.element);
1004
+ if (assoc._main === target && elem._main?.kind === 'entity' &&
1005
+ elem._main?._ancestors?.includes( target )) {
1006
+ // source and target of the model association are the same entity, and
1007
+ // the current main artifact is a suitable auto-redirection target → return it
1008
+ return elem._main;
1009
+ }
977
1010
  const elemScope = scopedRedirections && // null if no scoped redirections
978
1011
  preferredElemScope( target, service, elem, assoc._main || assoc );
979
1012
  const exposed = minimalExposure( target, service, elemScope );
@@ -983,7 +1016,7 @@ function populate( model ) {
983
1016
  if (isAutoExposed( target ))
984
1017
  target = createAutoExposed( origTarget, service, elemScope );
985
1018
  const desc = origTarget._descendants ||
986
- setLink( origTarget, '_descendants', Object.create(null) );
1019
+ setLink( origTarget, '_descendants', Object.create( null ) );
987
1020
  if (!desc[service.name.absolute]) // could be the target itself (no repeated msgs)!
988
1021
  desc[service.name.absolute] = [ target ];
989
1022
  else
@@ -1005,16 +1038,16 @@ function populate( model ) {
1005
1038
  std: 'Replace target $(TARGET) by one of $(SORTED_ARTS); can\'t auto-redirect this association if multiple projections exist in this service',
1006
1039
  // eslint-disable-next-line max-len
1007
1040
  two: 'Replace target $(TARGET) by $(SORTED_ARTS) or $(SECOND); can\'t auto-redirect this association if multiple projections exist in this service',
1008
- });
1041
+ } );
1009
1042
  // continuation semantics: no auto-redirection
1010
1043
  }
1011
1044
  else {
1012
1045
  // referred (and probably inferred) assoc (without a user-provided target at that place)
1013
1046
  // HINT: consider bin/cdsv2m.js when changing the following message text
1014
- // No grouped and sub messages yet (TODO v3): mention at all target places with all assocs
1047
+ // No grouped and sub messages yet (TODO v5): mention at all target places with all assocs
1015
1048
  const withAnno = annotationVal( exposed[0]['@cds.redirection.target'] );
1016
1049
  for (const proj of exposed) {
1017
- // TODO: def-ambiguous-target (just v3, as the current is infamous and used in options),
1050
+ // TODO: def-ambiguous-target (just v5, as the current is infamous and used in options),
1018
1051
  message( 'redirected-implicitly-ambiguous',
1019
1052
  [ weakLocation( proj.name.location ), proj ],
1020
1053
  {
@@ -1038,7 +1071,7 @@ function populate( model ) {
1038
1071
  return target;
1039
1072
  }
1040
1073
 
1041
- // Return projections of `target` in `service`. Shorted by
1074
+ // Return projections of `target` in `service`. Sorted by
1042
1075
  // - first, only consider projections with @cds.redirection.target=true
1043
1076
  // - exclude all indirect projections, i.e. those which are projection on others in list
1044
1077
  //
@@ -1055,7 +1088,7 @@ function populate( model ) {
1055
1088
  return exposed || [];
1056
1089
  let min = [];
1057
1090
  for (const e of exposed) {
1058
- if (min.every( m => m._ancestors?.includes( e ))) {
1091
+ if (min.every( m => m._ancestors?.includes( e ) )) {
1059
1092
  min = [ e ];
1060
1093
  }
1061
1094
  else if (min.length !== 1 || !e._ancestors?.includes( min[0] )) {
@@ -1139,9 +1172,10 @@ function populate( model ) {
1139
1172
  function isDirectProjection( proj, base ) {
1140
1173
  return proj.kind === 'entity' && // not event
1141
1174
  // direct proj (TODO: or should we add them to another list?)
1175
+ // TODO: delete ENTITY._from
1142
1176
  proj.query && proj.query.op && proj.query.op.val === 'SELECT' &&
1143
1177
  proj._from && proj._from.length === 1 &&
1144
- base === resolvePath( proj._from[0], 'from', proj );
1178
+ base === resolvePath( proj._from[0], 'from', proj.query );
1145
1179
  }
1146
1180
 
1147
1181
  // Auto-exposure -----------------------------------------------------------
@@ -1152,7 +1186,8 @@ function populate( model ) {
1152
1186
  return target.$autoexpose;
1153
1187
  const origTarget = target;
1154
1188
  const chain = [];
1155
- let source = target._from && resolvePath( target._from[0], 'from', target );
1189
+ const alias1 = target._from?.[0]; // TODO: delete ENTITY._from ?
1190
+ let source = alias1 && resolvePath( alias1, 'from', alias1._parent );
1156
1191
  // query source ref might not have been resolved yet, cycle avoided as
1157
1192
  // setAutoExposed() sets $autoexpose and a second call on same art would
1158
1193
  // return false
@@ -1160,7 +1195,8 @@ function populate( model ) {
1160
1195
  // stop at first ancestor with annotation or at non-query entity
1161
1196
  chain.push( target );
1162
1197
  target = source;
1163
- source = target._from && resolvePath( target._from[0], 'from', target );
1198
+ const alias = target._from?.[0]; // TODO: delete ENTITY._from ?
1199
+ source = alias && resolvePath( alias, 'from', alias._parent );
1164
1200
  }
1165
1201
  const autoexpose = target.$autoexpose;
1166
1202
  if (typeof autoexpose === 'boolean') {
@@ -1199,7 +1235,7 @@ function populate( model ) {
1199
1235
  return `${ service.name.absolute }.${ absolute }`;
1200
1236
  const base = definitionScope( target );
1201
1237
  if (base === target)
1202
- return `${ service.name.absolute }.${ absolute.substring( absolute.lastIndexOf('.') + 1 ) }`;
1238
+ return `${ service.name.absolute }.${ absolute.substring( absolute.lastIndexOf( '.' ) + 1 ) }`;
1203
1239
  // for scoped (e.g. calculated) entities, use exposed name of base:
1204
1240
  const exposed = minimalExposure( base, service, elemScope );
1205
1241
  // console.log(exposed.map( a => a.name.absolute ));
@@ -1271,18 +1307,18 @@ function populate( model ) {
1271
1307
  };
1272
1308
  // forward target parameters to projection
1273
1309
  if (target.params) {
1274
- art.params = Object.create(null);
1310
+ art.params = Object.create( null );
1275
1311
  // is art.query.from.path[0].$syntax: ':' required?
1276
- art.query.from.path[0].args = Object.create(null);
1277
- forEachGeneric(target, 'params', (p, pn) => {
1278
- art.params[pn] = linkToOrigin(p, pn, art, 'params', p.location);
1312
+ art.query.from.path[0].args = Object.create( null );
1313
+ forEachGeneric( target, 'params', (p, pn) => {
1314
+ art.params[pn] = linkToOrigin( p, pn, art, 'params', p.location );
1279
1315
  art.query.from.path[0].args[pn] = {
1280
1316
  name: { id: p.name.id, location: p.location },
1281
1317
  location: p.location,
1282
1318
  scope: 'param',
1283
1319
  path: [ { id: pn, location: p.location } ],
1284
1320
  };
1285
- });
1321
+ } );
1286
1322
  }
1287
1323
  // TODO: do we need to tag the generated entity with elemScope = 'auto'?
1288
1324
  if (autoexposed) {