@sap/cds-compiler 5.5.2 → 5.6.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.
@@ -1114,6 +1114,13 @@
1114
1114
  ],
1115
1115
  "$experimental": true
1116
1116
  },
1117
+ "Common.WebSocketChannel": {
1118
+ "Type": "Edm.String",
1119
+ "AppliesTo": [
1120
+ "EntityContainer"
1121
+ ],
1122
+ "$experimental": true
1123
+ },
1117
1124
  "Communication.Contact": {
1118
1125
  "Type": "Communication.ContactType",
1119
1126
  "AppliesTo": [
@@ -1463,6 +1470,61 @@
1463
1470
  "EntitySet"
1464
1471
  ]
1465
1472
  },
1473
+ "EntityRelationship.entityType": {
1474
+ "Type": "Edm.String",
1475
+ "AppliesTo": [
1476
+ "EntityType"
1477
+ ],
1478
+ "$experimental": true
1479
+ },
1480
+ "EntityRelationship.propertyType": {
1481
+ "Type": "Edm.String",
1482
+ "AppliesTo": [
1483
+ "Property"
1484
+ ],
1485
+ "$experimental": true
1486
+ },
1487
+ "EntityRelationship.entityIds": {
1488
+ "Type": "Collection(EntityRelationship.entityId)",
1489
+ "AppliesTo": [
1490
+ "EntityType"
1491
+ ],
1492
+ "$experimental": true
1493
+ },
1494
+ "EntityRelationship.reference": {
1495
+ "Type": "EntityRelationship.singleReference",
1496
+ "AppliesTo": [
1497
+ "Property"
1498
+ ]
1499
+ },
1500
+ "EntityRelationship.compositeReferences": {
1501
+ "Type": "Collection(EntityRelationship.compositeReference)",
1502
+ "AppliesTo": [
1503
+ "EntityType"
1504
+ ],
1505
+ "$experimental": true
1506
+ },
1507
+ "EntityRelationship.temporalIds": {
1508
+ "Type": "Collection(EntityRelationship.temporalId)",
1509
+ "AppliesTo": [
1510
+ "EntityType"
1511
+ ],
1512
+ "$experimental": true
1513
+ },
1514
+ "EntityRelationship.temporalReferences": {
1515
+ "Type": "Collection(EntityRelationship.temporalReference)",
1516
+ "AppliesTo": [
1517
+ "EntityType"
1518
+ ],
1519
+ "$experimental": true
1520
+ },
1521
+ "EntityRelationship.referencesWithConstantIds": {
1522
+ "Type": "Collection(EntityRelationship.referencesWithConstantId)",
1523
+ "AppliesTo": [
1524
+ "EntityType"
1525
+ ],
1526
+ "$experimental": true
1527
+ },
1466
1528
  "Graph.traceId": {
1467
1529
  "Type": "Edm.String",
1468
1530
  "$experimental": true
@@ -1719,6 +1781,7 @@
1719
1781
  "EntityType",
1720
1782
  "Action",
1721
1783
  "Function",
1784
+ "ActionImport",
1722
1785
  "FunctionImport"
1723
1786
  ]
1724
1787
  },
@@ -1783,7 +1846,8 @@
1783
1846
  "Type": "Collection(UI.CriticalityLabelType)",
1784
1847
  "AppliesTo": [
1785
1848
  "Property",
1786
- "EntityType"
1849
+ "EntityType",
1850
+ "TypeDefinition"
1787
1851
  ],
1788
1852
  "$experimental": true
1789
1853
  },
@@ -1823,6 +1887,16 @@
1823
1887
  "EntityType"
1824
1888
  ]
1825
1889
  },
1890
+ "UI.OperationParameterFacets": {
1891
+ "Type": "Collection(UI.ReferenceFacet)",
1892
+ "AppliesTo": [
1893
+ "Action",
1894
+ "Function",
1895
+ "ActionImport",
1896
+ "FunctionImport"
1897
+ ],
1898
+ "$experimental": true
1899
+ },
1826
1900
  "UI.SelectionPresentationVariant": {
1827
1901
  "Type": "UI.SelectionPresentationVariantType",
1828
1902
  "AppliesTo": [
@@ -1878,14 +1952,16 @@
1878
1952
  "Type": "Core.Tag",
1879
1953
  "AppliesTo": [
1880
1954
  "Property",
1881
- "Term"
1955
+ "Term",
1956
+ "TypeDefinition"
1882
1957
  ]
1883
1958
  },
1884
1959
  "UI.IsImage": {
1885
1960
  "Type": "Core.Tag",
1886
1961
  "AppliesTo": [
1887
1962
  "Property",
1888
- "EntityType"
1963
+ "EntityType",
1964
+ "TypeDefinition"
1889
1965
  ],
1890
1966
  "$experimental": true
1891
1967
  },
@@ -1894,7 +1970,8 @@
1894
1970
  "AppliesTo": [
1895
1971
  "Property",
1896
1972
  "PropertyValue",
1897
- "Parameter"
1973
+ "Parameter",
1974
+ "TypeDefinition"
1898
1975
  ]
1899
1976
  },
1900
1977
  "UI.Placeholder": {
@@ -2070,7 +2147,8 @@
2070
2147
  "Type": "UI.RecommendationListType",
2071
2148
  "AppliesTo": [
2072
2149
  "Property",
2073
- "Parameter"
2150
+ "Parameter",
2151
+ "TypeDefinition"
2074
2152
  ]
2075
2153
  },
2076
2154
  "UI.Recommendations": {
@@ -2089,7 +2167,8 @@
2089
2167
  "UI.DoNotCheckScaleOfMeasuredQuantity": {
2090
2168
  "Type": "Edm.Boolean",
2091
2169
  "AppliesTo": [
2092
- "Property"
2170
+ "Property",
2171
+ "TypeDefinition"
2093
2172
  ],
2094
2173
  "$experimental": true
2095
2174
  },
@@ -3301,6 +3380,7 @@
3301
3380
  "Properties": {
3302
3381
  "SourceProperties": "Collection(Edm.PropertyPath)",
3303
3382
  "SourceEntities": "Collection(Edm.NavigationPropertyPath)",
3383
+ "SourceEvents": "Collection(Edm.String)",
3304
3384
  "TargetProperties": "Collection(Edm.String)",
3305
3385
  "TargetEntities": "Collection(Edm.NavigationPropertyPath)",
3306
3386
  "EffectTypes": "Common.EffectType",
@@ -3777,6 +3857,105 @@
3777
3857
  "DELETE"
3778
3858
  ]
3779
3859
  },
3860
+ "EntityRelationship.singleReference": {
3861
+ "$kind": "ComplexType",
3862
+ "Properties": {
3863
+ "name": "Edm.String",
3864
+ "referencedEntityType": "EntityRelationship.entityTypeID",
3865
+ "referencedPropertyType": "EntityRelationship.propertyTypeID"
3866
+ }
3867
+ },
3868
+ "EntityRelationship.entityId": {
3869
+ "$kind": "ComplexType",
3870
+ "Properties": {
3871
+ "name": "Edm.String",
3872
+ "description": "Edm.String",
3873
+ "propertyTypes": "Collection(EntityRelationship.propertyTypeID)"
3874
+ }
3875
+ },
3876
+ "EntityRelationship.compositeReference": {
3877
+ "$kind": "ComplexType",
3878
+ "Properties": {
3879
+ "name": "Edm.String",
3880
+ "referencedEntityType": "EntityRelationship.entityTypeID",
3881
+ "referencedPropertyType": "Collection(EntityRelationship.referencedPropertyType)"
3882
+ }
3883
+ },
3884
+ "EntityRelationship.referencedPropertyType": {
3885
+ "$kind": "ComplexType",
3886
+ "Properties": {
3887
+ "referencedPropertyType": "EntityRelationship.propertyTypeID",
3888
+ "localPropertyName": "Edm.PropertyPath"
3889
+ }
3890
+ },
3891
+ "EntityRelationship.temporalId": {
3892
+ "$kind": "ComplexType",
3893
+ "Properties": {
3894
+ "name": "Edm.String",
3895
+ "description": "Edm.String",
3896
+ "propertyTypes": "Collection(EntityRelationship.propertyTypeID)",
3897
+ "temporalIntervalType": "EntityRelationship.temporalIntervalTypeEnum",
3898
+ "temporalType": "EntityRelationship.temporalTypeEnum",
3899
+ "temporalIntervalStartProperty": "Edm.PropertyPath",
3900
+ "temporalIntervalEndProperty": "Edm.PropertyPath"
3901
+ }
3902
+ },
3903
+ "EntityRelationship.temporalReference": {
3904
+ "$kind": "ComplexType",
3905
+ "Properties": {
3906
+ "name": "Edm.String",
3907
+ "referencedEntityType": "EntityRelationship.entityTypeID",
3908
+ "referencedPropertyType": "Collection(EntityRelationship.referencedPropertyType)",
3909
+ "category": "EntityRelationship.temporalCategoryEnum",
3910
+ "selectionDateProperty": "Edm.PropertyPath"
3911
+ }
3912
+ },
3913
+ "EntityRelationship.referenceWithConstantId": {
3914
+ "$kind": "ComplexType",
3915
+ "Properties": {
3916
+ "name": "Edm.String",
3917
+ "referencedEntityType": "EntityRelationship.entityTypeID",
3918
+ "referencedPropertyType": "Collection(EntityRelationship.referencedPropertyTypeWithConstantId)"
3919
+ }
3920
+ },
3921
+ "EntityRelationship.referencedPropertyTypeWithConstantId": {
3922
+ "$kind": "ComplexType",
3923
+ "Properties": {
3924
+ "referencedPropertyType": "EntityRelationship.propertyTypeID",
3925
+ "localPropertyName": "Edm.PropertyPath",
3926
+ "constantValue": "Edm.String"
3927
+ }
3928
+ },
3929
+ "EntityRelationship.temporalIntervalTypeEnum": {
3930
+ "$kind": "EnumType",
3931
+ "Members": [
3932
+ "CLOSED_CLOSED",
3933
+ "OPEN_OPEN",
3934
+ "OPEN_CLOSED",
3935
+ "CLOSED_OPEN"
3936
+ ]
3937
+ },
3938
+ "EntityRelationship.temporalTypeEnum": {
3939
+ "$kind": "EnumType",
3940
+ "Members": [
3941
+ "DATE",
3942
+ "DATETIME"
3943
+ ]
3944
+ },
3945
+ "EntityRelationship.temporalCategoryEnum": {
3946
+ "$kind": "EnumType",
3947
+ "Members": [
3948
+ "TEMPORAL_DATE"
3949
+ ]
3950
+ },
3951
+ "EntityRelationship.propertyTypeID": {
3952
+ "$kind": "TypeDefinition",
3953
+ "UnderlyingType": "Edm.String"
3954
+ },
3955
+ "EntityRelationship.entityTypeID": {
3956
+ "$kind": "TypeDefinition",
3957
+ "UnderlyingType": "Edm.String"
3958
+ },
3780
3959
  "Graph.DetailsType": {
3781
3960
  "$kind": "ComplexType",
3782
3961
  "Properties": {
@@ -40,6 +40,8 @@ const PRECEDENCE_OF_IN_PREDICATE = 10;
40
40
  const PRECEDENCE_OF_EQUAL = 10;
41
41
 
42
42
  class AstBuildingParser extends BaseParser {
43
+ leanConditions = { afterBrace: true };
44
+
43
45
  constructor( lexer, keywords, table, options, messageFunctions ) {
44
46
  super( lexer, keywords, table ); // lexer has file
45
47
  this.options = options;
@@ -212,6 +214,16 @@ class AstBuildingParser extends BaseParser {
212
214
  return next !== '*' && next !== '{';
213
215
  }
214
216
 
217
+ notAfterEntityArgOrFilter( mode ) {
218
+ if (mode !== 'M')
219
+ return true;
220
+ const { type } = this.lb();
221
+ if (type !== ')' && type !== ']')
222
+ return true;
223
+ const { followState } = this.stack.at( -1 );
224
+ return !this.table[followState][':'];
225
+ }
226
+
215
227
  // <prec=10, postfix=once> + test that the next token is not `null`; TODO: code
216
228
  // completion for `… default 3 not ~;` → currently just `null` but hey
217
229
  isNegatedRelation( _test, prec ) {
@@ -253,6 +265,23 @@ class AstBuildingParser extends BaseParser {
253
265
  return !this.dynamic_.inBlock;
254
266
  }
255
267
 
268
+ /**
269
+ * Restrictions according to the expression of a select column.
270
+ * Currently only to restrict it to a single `Id` for published associations.
271
+ */
272
+ columnExpr( mode, arg ) {
273
+ if (mode)
274
+ return this.columnExpr$;
275
+ // TODO: should we use (text of) syntax-unexpected-assoc somewhere ?
276
+ if (arg)
277
+ this.columnExpr$ = this.tokenIdx;
278
+ else if (this.columnExpr$ !== this.tokenIdx - 1 ||
279
+ this.lb().type !== 'Id' ||
280
+ [ 'true', 'false', 'null' ].includes( this.lb().keyword ) )
281
+ this.columnExpr$ = null;
282
+ return null;
283
+ }
284
+
256
285
  /**
257
286
  * Prepare element restrictions and check validility of final anno assignments.
258
287
  *
@@ -321,13 +350,36 @@ class AstBuildingParser extends BaseParser {
321
350
  return true;
322
351
  }
323
352
 
353
+ noRepeatedCardinality( mode ) {
354
+ if (this.tokens[this.tokenIdx - 2]?.type !== ']')
355
+ return true;
356
+ if (mode === 'M')
357
+ return false;
358
+ // currently just warning if same cardinality provided twice
359
+ const same = { one: '1', many: '*' }[this.la().keyword];
360
+ return this.tokens[this.tokenIdx - 3]?.text === same;
361
+ }
362
+
324
363
  /**
325
364
  * `;` between statements is optional only after a `}` (ex braces of structure
326
- * values for annotations).
365
+ * values for annotations and foreign key specifications).
366
+ *
367
+ * Beware: mentioned in leanConditions, i.e. executed in predictions!
327
368
  */
328
369
  afterBrace( test ) {
329
370
  if (!test)
330
371
  this.afterBrace$ = this.tokenIdx;
372
+ // TODO TOOL: the following test belongs to the BaseParser.js:
373
+ if (this.conditionTokenIdx === this.tokenIdx && // tested on same
374
+ this.conditionStackLength == null && // after error recover
375
+ test !== 'M')
376
+ return true;
377
+ // Strange optional `;` after PROJECTION ON source: the rule exit prediction
378
+ // for fromRefWithOptAlias etc now checks C(afterBrace):
379
+ if (test === 'E' && this.afterBrace$ > 0 &&
380
+ this.tokens[this.afterBrace$ - 1]?.keyword === 'projection' &&
381
+ this.tokens[this.afterBrace$].keyword === 'on')
382
+ return true;
331
383
  return this.afterBrace$ === this.tokenIdx;
332
384
  }
333
385
 
@@ -838,7 +890,7 @@ class AstBuildingParser extends BaseParser {
838
890
  // TODO: `literal` needed?
839
891
  if (art.cardinality) {
840
892
  this.reportDuplicateClause( 'cardinality', targetMax, art.cardinality.targetMax,
841
- card.keyword, true );
893
+ card.keyword );
842
894
  }
843
895
  else {
844
896
  art.cardinality = { targetMax, location: targetMax.location };
@@ -846,9 +898,9 @@ class AstBuildingParser extends BaseParser {
846
898
  return target;
847
899
  }
848
900
 
901
+ // TODO: as condition
849
902
  reportExpandInline( column, isInline ) {
850
903
  // called before matching `{`
851
- const { name } = column;
852
904
  if (column.value && !column.value.path) {
853
905
  // improve error location when using "inline" `.{…}` after ref (arguments and
854
906
  // filters not covered, not worth the effort); after an expression where
@@ -864,18 +916,9 @@ class AstBuildingParser extends BaseParser {
864
916
  // - no errors for refs inside expand/inline, but for refs in sibling expr
865
917
  // - think about: reference to these (sub) elements from other view
866
918
  }
867
- if (isInline && name) {
868
- const alias = this.tokens[this.tokenIdx - 2];
869
- const location = (isInline === true)
870
- ? alias.location
871
- : this.combineLocation( isInline, alias );
872
- this.error( 'syntax-unexpected-alias', location, { code: '.{ ‹inline› }' },
873
- 'Unexpected alias name before $(CODE)' );
874
- // continuation semantics: ignore AS
875
- }
876
919
  }
877
920
 
878
- reportDuplicateClause( prop, erroneous, chosen, code, literalValIfNotEq ) {
921
+ reportDuplicateClause( prop, erroneous, chosen, code ) {
879
922
  // probably easier for message linters not to use (?:) for the message id...?
880
923
  const args = {
881
924
  '#': prop,
@@ -887,11 +930,7 @@ class AstBuildingParser extends BaseParser {
887
930
  // TODO v6: duplicate clause = error, independently whether it is the same
888
931
  this.warning( 'syntax-duplicate-equal-clause', erroneous.location, args );
889
932
  }
890
- else if (prop !== 'notNull') { // already via guard in grammar
891
- if (literalValIfNotEq)
892
- args.code = chosen.val;
893
- this.message( 'syntax-duplicate-clause', erroneous.location, args );
894
- }
933
+ // TODO extra msg text 'syntax-duplicate-clause' for noRepeatedCardinality()
895
934
  }
896
935
 
897
936
  setTypeFacet( art, name, value ) {
@@ -1090,20 +1129,16 @@ class AstBuildingParser extends BaseParser {
1090
1129
  };
1091
1130
  }
1092
1131
 
1132
+ // TODO: as condition
1093
1133
  associationInSelectItem( art ) {
1134
+ if (art.name)
1135
+ return;
1094
1136
  this.classifyImplicitName( 'ItemAssoc', art.value );
1095
1137
  const path = art.value?.path;
1096
- // we cannot compare "just one token before `:`" because there might be annos
1097
- if (path && path.length === 1 && !art.name && !art.expand && !art.inline) {
1098
- const name = path[0];
1099
- if (path.length === 1 && !name.args && !name.cardinality && !name.where) {
1100
- art.name = name;
1101
- delete art.value;
1102
- return;
1103
- }
1138
+ if (path?.length) {
1139
+ art.name = path.at( -1 ); // usually length 1, but make it also work during error recovery
1140
+ delete art.value;
1104
1141
  }
1105
- this.error( 'syntax-unexpected-assoc', this.la(), {},
1106
- 'Unexpected association definition in select item' );
1107
1142
  }
1108
1143
 
1109
1144
  // must be in action directly after having parsed '{', '(`, or a keyword before
@@ -507,7 +507,8 @@ mixinElementDef[ outer ] locals[ art = new XsnArtifact() ]
507
507
  ( assoc=ASSOCIATION cardinality[ $art ]? TO
508
508
  | assoc=COMPOSITION cardinality[ $art ]? OF
509
509
  )
510
- card=ONE/MANY? target=simplePath
510
+ ( <cond=noRepeatedCardinality> card=ONE/MANY )?
511
+ target=simplePath
511
512
  { this.setAssocAndComposition( $art, $assoc, $card, $target ); }
512
513
  ON expr=condition { $art.on = $expr; }
513
514
  ;
@@ -866,11 +867,13 @@ typeExpression[ art ]
866
867
  typeProperties[ $art ]?
867
868
  |
868
869
  assoc=ASSOCIATION <prepare=elementRestriction, arg=calc>
869
- cardinality[ $art ]? TO card=ONE/MANY?
870
+ cardinality[ $art ]? TO
871
+ ( <cond=noRepeatedCardinality> card=ONE/MANY )?
870
872
  typeAssocProperties[ $art, $assoc, $card ]
871
873
  |
872
874
  assoc=COMPOSITION <prepare=elementRestriction, arg=calc>
873
- cardinality[ $art ]? OF card=ONE/MANY?
875
+ cardinality[ $art ]? OF
876
+ ( <cond=noRepeatedCardinality> card=ONE/MANY )?
874
877
  ( typeAssocProperties[ $art, $assoc, $card ]
875
878
  | elementsBlock[ this.setAssocAndComposition( $art, $assoc, $card ) ]
876
879
  { $art.target.location = $art.target.elements[Symbol.for('cds.$location')]; }
@@ -989,6 +992,7 @@ typeNamedArgsList[ art ]
989
992
 
990
993
  typeNamedArg[ art ]
991
994
  :
995
+ // TODO: or keywords with guards for better code completion?
992
996
  name=Id['typeparamname']
993
997
  ':'
994
998
  ( Number
@@ -1055,12 +1059,11 @@ projectionSpec returns[ default query = {} ]
1055
1059
  @finally{ this.attachLocation($query); }
1056
1060
  :
1057
1061
  // TODO, currently just with simple ref
1058
- PROJECTION { $query = { op: this.valueWithLocation( 'SELECT' ) }; }
1062
+ PROJECTION <prepare=afterBrace> // v6: remove <prepare>
1063
+ { $query = { op: this.valueWithLocation( 'SELECT' ) }; }
1059
1064
  ON
1060
- tab=fromRefWithOptAlias
1065
+ tab=fromRefWithOptAlias <prepare=afterBrace>
1061
1066
  // TODO: this <prepare=afterBrace> is extremely strange... v6 forbid.
1062
- // Deliberately set this via action (→ interpreter will not accept this)
1063
- { this.afterBrace(); }<always>
1064
1067
  { $query.from = tab; }
1065
1068
  selectItemsList[ $query ]?
1066
1069
  excludingClause[ $query ]?
@@ -1225,8 +1228,7 @@ fromRefWithOptAlias returns[ default expr = {} ]
1225
1228
  (
1226
1229
  AS Id['FromAlias'] { $expr.name = this.identAst(); }
1227
1230
  |
1228
- <cond=tableWithoutAs>
1229
- // TODO: probably not necessary, TOOL already uses `default: this.giR()`
1231
+ // <cond=tableWithoutAs> not necessary, tool uses `default: this.giR()`
1230
1232
  Id_restricted['FromAlias']
1231
1233
  { $expr.name = this.fragileAlias(); }
1232
1234
  |
@@ -1241,6 +1243,7 @@ fromPath[ table, category ] locals[ pathItem ]
1241
1243
  Id[ $category ] { $table.path.push( $pathItem = this.identAst() ); }
1242
1244
  ( fromArgumentsAndFilter[ $pathItem ] { $pathItem = null; } )?
1243
1245
  (
1246
+ <cond=notAfterEntityArgOrFilter> // TODO TOOL: allow <hide=method>
1244
1247
  '.' { if (!$pathItem && !$table.scope) {
1245
1248
  $table.scope = $table.path.length; $category = 'ref';
1246
1249
  this.warning( 'syntax-invalid-path-separator', this.lb(),
@@ -1356,24 +1359,24 @@ selectItemDef[ columns ] locals[ art = new XsnArtifact(), alias ]
1356
1359
  { $columns.push( $art ); } // TODO: probably too early
1357
1360
  { this.docComment( $art ); } annoAssignCol[ $art ]*
1358
1361
  ( <cond=modifierRestriction> VIRTUAL
1359
- { $art.virtual = this.valueWithLocation( true ); } )?
1362
+ { $art.virtual = this.valueWithLocation( true ); }
1363
+ )?
1364
+ {;} <prepare=columnExpr, arg=key> // TOOL TODO: disappears without {;}
1360
1365
  ( <cond=modifierRestriction> KEY
1361
- { $art.key = this.valueWithLocation( true ); } )?
1366
+ { $art.key = this.valueWithLocation( true ); }
1367
+ )?
1362
1368
  (
1363
1369
  expr=expression { $art.value = $expr; }
1364
1370
  ( as=AS Id['ItemAlias'] { $art.name = this.identAst(); }
1365
1371
  | Id_restricted['ItemAlias'] { $art.name = this.fragileAlias( true ); }
1366
1372
  | { $alias = this.classifyImplicitName( 'ItemImplicit', $expr ); }
1367
- )
1368
- // TODO: <cond> instead `reportExpandInline` for "expand/inline only w/ ref"
1369
- (
1370
- { this.reportExpandInline( $art, false ); }
1371
- nestedSelectItemsList[ $art, 'expand' ]
1372
- excludingClause[ $art ]?
1373
1373
  |
1374
+ // TODO: guard for ref-only expression can probably replace reportExpandInline
1374
1375
  '.'
1375
1376
  { this.reportUnexpectedSpace( this.lb(), this.la().location, true ); } // TODO: no ERR
1376
1377
  { this.reportExpandInline( $art, $as || true ); }
1378
+ // no extra 'syntax-unexpected-alias' anymore,
1379
+ // 'syntax-unexpected-anno' reported in define.js
1377
1380
  { if ($alias) $alias.token.parsedAs = $alias.parsedAs; }
1378
1381
  (
1379
1382
  nestedSelectItemsList[ $art, 'inline' ]
@@ -1381,13 +1384,21 @@ selectItemDef[ columns ] locals[ art = new XsnArtifact(), alias ]
1381
1384
  |
1382
1385
  '*' { $art.inline = [ this.valueWithLocation() ]; }
1383
1386
  )
1387
+ <exitRule>
1388
+ )
1389
+ // TODO: <cond> instead `reportExpandInline` for "expand/inline only w/ ref"
1390
+ (
1391
+ // TODO: guard for ref-only expression can probably replace:
1392
+ { this.reportExpandInline( $art, false ); }
1393
+ nestedSelectItemsList[ $art, 'expand' ]
1394
+ excludingClause[ $art ]?
1384
1395
  )?
1385
1396
  |
1386
1397
  nestedSelectItemsList[ $art, 'expand' ]
1387
1398
  excludingClause[ $art ]?
1388
1399
  AS Id['ItemAlias'] { $art.name = this.identAst(); }
1389
1400
  )
1390
- { this.docComment( $art ); } annoAssignMid[ $art ]*
1401
+ { this.docComment( $art ); } <prepare=columnExpr> annoAssignMid[ $art ]*
1391
1402
  (
1392
1403
  ':'
1393
1404
  (
@@ -1396,18 +1407,21 @@ selectItemDef[ columns ] locals[ art = new XsnArtifact(), alias ]
1396
1407
  typeRefOptArgs[ $art ]
1397
1408
  )
1398
1409
  |
1410
+ // TODO: guard for ref-only expression ?
1399
1411
  REDIRECTED TO target=simplePath { $art.target = $target; }
1400
1412
  ( ON cond=condition { $art.on = $cond; }
1401
1413
  | foreignKeysBlock[ $art ]
1402
1414
  )?
1403
1415
  |
1404
- // TODO: condition for this
1405
- ( assoc=ASSOCIATION { this.associationInSelectItem( $art ); }
1416
+ ( <cond=columnExpr> // arg=singleId
1417
+ assoc=ASSOCIATION { this.associationInSelectItem( $art ); }
1406
1418
  cardinality[ $art ]? TO
1407
- | assoc=COMPOSITION { this.associationInSelectItem( $art ); }
1419
+ | <cond=columnExpr> // arg=singleId
1420
+ assoc=COMPOSITION { this.associationInSelectItem( $art ); }
1408
1421
  cardinality[ $art ]? OF
1409
1422
  )
1410
- card=ONE/MANY? target=simplePath
1423
+ ( <cond=noRepeatedCardinality> card=ONE/MANY )?
1424
+ target=simplePath
1411
1425
  { this.setAssocAndComposition( $art, $assoc, $card, $target ); }
1412
1426
  ON expr=condition { $art.on = $expr; }
1413
1427
  )
@@ -652,6 +652,9 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
652
652
  if (typeof root.$env === 'string' && (isComplexOrNestedQuery || options.transformation !== 'odata' || root.$env === pathId(obj.ref[0])))
653
653
  obj.ref = [ root.$env, ...obj.ref ];
654
654
 
655
+ if (iterateOptions.keepKeysOrigin)
656
+ setProp(obj, '$originalKeyRef', { ref: root.ref, as: root.as });
657
+
655
658
  return obj;
656
659
  });
657
660
  }
@@ -188,7 +188,7 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
188
188
  if (!structuredOData) {
189
189
  expansion.expandStructureReferences(csn, options, '_',
190
190
  { error, info, throwWithAnyError }, csnUtils,
191
- { skipArtifact: isExternalServiceMember });
191
+ { skipArtifact: isExternalServiceMember, keepKeysOrigin: true });
192
192
  }
193
193
 
194
194
  createForeignKeyElements(csn, options, messageFunctions, csnUtils, { skipArtifact: isExternalServiceMember });