@sap/cds-compiler 2.10.2 → 2.10.4

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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,16 @@
7
7
  Note: `beta` fixes, changes and features are usually not listed in this ChangeLog but [here](doc/CHANGELOG_BETA.md).
8
8
  The compiler behavior concerning `beta` features can change at any time without notice.
9
9
 
10
+ ## Version 2.10.4 - 2021-11-05
11
+
12
+ ### Fixed
13
+
14
+ - to.sql/hdi/hdbcds:
15
+ + Correctly complain about `exists` in conjunction with non-associations/compositions
16
+ + Don't resolve types in action returns, as this causes issues with $self-resolution
17
+
18
+ - to.edm(x): Be robust against transitively untyped keys in stacked view hierarchies
19
+
10
20
  ## Version 2.10.2 - 2021-10-29
11
21
 
12
22
  ### Fixed
@@ -38,13 +48,9 @@ The compiler behavior concerning `beta` features can change at any time without
38
48
  + `exists` can now be followed by more than one association step.
39
49
  `exists assoc.anotherassoc.moreassoc` is semantically equivalent to `exists assoc[exists anotherassoc[exists moreassoc]]`
40
50
 
41
- ### Removed
42
-
43
51
  ### Changed
44
52
 
45
- - to.odata: Inform when overwriting draft action annotations like @Common.DraftRoot.ActivationAction.
46
-
47
- ### Fixed
53
+ - to.odata: Inform when overwriting draft action annotations like `@Common.DraftRoot.ActivationAction`.
48
54
 
49
55
  ## Version 2.9.0 - 2021-10-15
50
56
 
@@ -8,6 +8,12 @@ Note: `beta` fixes, changes and features are listed in this ChangeLog just for i
8
8
  The compiler behavior concerning `beta` features can change at any time without notice.
9
9
  **Don't use `beta` fixes, changes and features in productive mode.**
10
10
 
11
+ ## Version 2.10.4
12
+
13
+ ### Fixed `nestedProjections`
14
+
15
+ - to.sql/hdi/hdbcds: Correctly handle a `*` at the not-first place in the query
16
+
11
17
  ## Version 2.6.0
12
18
 
13
19
  ### Removed `pretransformedCSN`
@@ -541,7 +541,8 @@ function resolve( model ) {
541
541
  if (art.elements || art.kind === '$tableAlias' ||
542
542
  // no element expansions for "non-proper" types like
543
543
  // entities (as parameter types) etc:
544
- struct.kind !== 'type' && struct.kind !== 'element' && !struct._outer)
544
+ struct.kind !== 'type' && struct.kind !== 'element' && struct.kind !== 'param' &&
545
+ !struct._outer)
545
546
  return false;
546
547
  if (struct.elements === 0 || isInParents( art, eType )) {
547
548
  art.elements = 0; // circular
@@ -1586,6 +1587,11 @@ function resolve( model ) {
1586
1587
  }
1587
1588
  }
1588
1589
  if (art && art._annotate) {
1590
+ if (art.kind === 'action' || art.kind === 'function') {
1591
+ expandParameters( art );
1592
+ if (art.returns)
1593
+ effectiveType( art.returns );
1594
+ }
1589
1595
  const aor = art.returns || art;
1590
1596
  const obj = aor.items || aor.targetAspect || aor;
1591
1597
  // Currently(?), effectiveType() does not calculate the effective type of
@@ -1620,6 +1626,42 @@ function resolve( model ) {
1620
1626
  annotateMembers( env && env[n], dict[n], prop, n, parent, kind );
1621
1627
  }
1622
1628
  }
1629
+ function expandParameters( action ) {
1630
+ // see also expandElements()
1631
+ if (!enableExpandElements || !effectiveType( action ))
1632
+ return;
1633
+ const chain = [];
1634
+ // Should we be able to consider params and returns separately?
1635
+ // Probably not, let to-csn omit unchanged params/returns.
1636
+ while (action._origin && !action.params) {
1637
+ chain.push( action );
1638
+ action = action._origin;
1639
+ }
1640
+ chain.reverse();
1641
+ for (const art of chain) {
1642
+ const origin = art._origin;
1643
+ if (!art.params && origin.params) {
1644
+ for (const name in origin.params) {
1645
+ // TODO: we could check _annotate here to decide whether we really
1646
+ // not to create proxies
1647
+ const orig = origin.params[name];
1648
+ linkToOrigin( orig, name, art, 'params', weakLocation( orig.location ), true )
1649
+ .$inferred = 'expand-param';
1650
+ }
1651
+ }
1652
+ if (!art.returns && origin.returns) {
1653
+ // TODO: make linkToOrigin() work for returns, kind/name?
1654
+ const location = weakLocation( origin.returns.location );
1655
+ art.returns = {
1656
+ name: Object.assign( {}, art.name, { id: '', param: '', location } ),
1657
+ kind: 'param',
1658
+ location,
1659
+ $inferred: 'expand-param',
1660
+ };
1661
+ setProp( art.returns, '_origin', origin.returns );
1662
+ }
1663
+ }
1664
+ }
1623
1665
 
1624
1666
  function extensionFor( art ) {
1625
1667
  if (art.kind === 'annotate')
@@ -1351,7 +1351,8 @@ function initializeModel(csn, _options, messageFunctions)
1351
1351
  // OData requires all elements along the path to be nullable: false (that is either key or notNull)
1352
1352
 
1353
1353
  const finalType = getFinalTypeDef(eltCsn.items && eltCsn.items.type || eltCsn.type);
1354
- const elements = eltCsn.elements || eltCsn.items && eltCsn.items.elements|| finalType.elements || finalType.items && finalType.items.elements;
1354
+ const elements = eltCsn.elements || eltCsn.items && eltCsn.items.elements ||
1355
+ (finalType && (finalType.elements || finalType.items && finalType.items.elements));
1355
1356
  if(elements) {
1356
1357
  Object.entries(elements).forEach(([eltName, elt]) => {
1357
1358
  const newRefs = produceKeyRefPaths(elt, prefix + options.pathDelimiter + eltName);
@@ -1400,7 +1401,7 @@ function initializeModel(csn, _options, messageFunctions)
1400
1401
  {name: pathSegment, '#': pathSegment ? 'std' : 'scalar'});
1401
1402
  }
1402
1403
  // many
1403
- let type = elt.items || getFinalTypeDef(elt.type).items;
1404
+ let type = elt.items || elt.type && !isBuiltinType(elt.type) && getFinalTypeDef(elt.type).items;
1404
1405
  if(type) {
1405
1406
  error('odata-spec-violation-key-array', location,
1406
1407
  {name: pathSegment, '#': pathSegment ? 'std' : 'scalar'});
@@ -1107,6 +1107,9 @@ function applyTransformations( csn, customTransformers={}, artifactTransformers=
1107
1107
  }
1108
1108
 
1109
1109
  function dictionary( node, prop, dict ) {
1110
+ // Allow skipping dicts like actions in forHanaNew
1111
+ if(options.skipDict && options.skipDict[prop])
1112
+ return;
1110
1113
  csnPath.push( prop );
1111
1114
  for (let name of Object.getOwnPropertyNames( dict )) {
1112
1115
  standard( dict, name, dict[name] );
@@ -715,7 +715,7 @@ function toCdsSourceCsn(csn, options) {
715
715
 
716
716
  // Even the first step might have parameters and/or a filter
717
717
  if (path.ref[0].args)
718
- result += `(${renderArgs(path.ref[0].args, ':', env)})`;
718
+ result += `(${renderArgs(path.ref[0], ':', env)})`;
719
719
 
720
720
  if (path.ref[0].where)
721
721
  result += `[${path.ref[0].cardinality ? (`${path.ref[0].cardinality.max}: `) : ''}${renderExpr(path.ref[0].where, env, true, true)}]`;
@@ -1474,13 +1474,13 @@ function toCdsSourceCsn(csn, options) {
1474
1474
 
1475
1475
  // Not really a path step but an object-like function call
1476
1476
  if (s.func)
1477
- return `${s.func}(${renderArgs(s.args, '=>', env)})`;
1477
+ return `${s.func}(${renderArgs(s, '=>', env)})`;
1478
1478
 
1479
1479
  // Path step, possibly with view parameters and/or filters
1480
1480
  let result = `${quoteOrUppercaseId(s.id)}`;
1481
1481
  if (s.args) {
1482
1482
  // View parameters
1483
- result += `(${renderArgs(s.args, ':', env)})`;
1483
+ result += `(${renderArgs(s, ':', env)})`;
1484
1484
  }
1485
1485
  if (s.where) {
1486
1486
  // Filter, possibly with cardinality
@@ -1498,12 +1498,13 @@ function toCdsSourceCsn(csn, options) {
1498
1498
  * Render function arguments or view parameters (positional if array, named if object/dict),
1499
1499
  * using 'sep' as separator for positional parameters
1500
1500
  *
1501
- * @param {object[]|object} args (Un)Named arguments
1501
+ * @param {object} node with `args` to render
1502
1502
  * @param {string} sep
1503
1503
  * @param {CdlRenderEnvironment} env
1504
1504
  * @returns {string}
1505
1505
  */
1506
- function renderArgs(args, sep, env) {
1506
+ function renderArgs(node, sep, env) {
1507
+ const args = node.args ? node.args : {};
1507
1508
  // Positional arguments
1508
1509
  if (Array.isArray(args))
1509
1510
  return args.map(arg => renderExpr(arg, env)).join(', ');
@@ -630,7 +630,7 @@ function toHdbcdsSource(csn, options) {
630
630
 
631
631
  // Even the first step might have parameters and/or a filter
632
632
  if (path.ref[0].args)
633
- result += `(${renderArgs(path.ref[0].args, ':', env)})`;
633
+ result += `(${renderArgs(path.ref[0], ':', env)})`;
634
634
 
635
635
  if (path.ref[0].where)
636
636
  result += `[${path.ref[0].cardinality ? (`${path.ref[0].cardinality.max}: `) : ''}${renderExpr(path.ref[0].where, env, true, true)}]`;
@@ -1321,13 +1321,13 @@ function toHdbcdsSource(csn, options) {
1321
1321
 
1322
1322
  // Not really a path step but an object-like function call
1323
1323
  if (s.func)
1324
- return `${s.func}(${renderArgs(s.args, '=>', env)})`;
1324
+ return `${s.func}(${renderArgs(s, '=>', env)})`;
1325
1325
 
1326
1326
  // Path step, possibly with view parameters and/or filters
1327
1327
  let result = `${formatIdentifier(s.id)}`;
1328
1328
  if (s.args) {
1329
1329
  // View parameters
1330
- result += `(${renderArgs(s.args, ':', env)})`;
1330
+ result += `(${renderArgs(s, ':', env)})`;
1331
1331
  }
1332
1332
  if (s.where) {
1333
1333
  // Filter, possibly with cardinality
@@ -1344,19 +1344,21 @@ function toHdbcdsSource(csn, options) {
1344
1344
  * Render function arguments or view parameters (positional if array, named if object/dict),
1345
1345
  * using 'sep' as separator for positional parameters
1346
1346
  *
1347
- * @param {object[]|object} args (Un)Named arguments
1347
+ * @param {object} node with `args` to render
1348
1348
  * @param {string} sep Seperator between arguments
1349
1349
  * @param {CdlRenderEnvironment} env Environment
1350
1350
  * @returns {string} Rendered arguments
1351
1351
  */
1352
- function renderArgs(args, sep, env) {
1352
+ function renderArgs(node, sep, env) {
1353
+ const args = node.args ? node.args : {};
1353
1354
  // Positional arguments
1354
1355
  if (Array.isArray(args))
1355
1356
  return args.map(arg => renderExpr(arg, env)).join(', ');
1356
1357
 
1357
1358
  // Named arguments (object/dict)
1358
1359
  else if (typeof args === 'object')
1359
- return Object.keys(args).map(key => `${formatIdentifier(key)} ${sep} ${renderExpr(args[key], env)}`).join(', ');
1360
+ // if this is a function param which is not a reference to the model, we must not quote it
1361
+ return Object.keys(args).map(key => `${node.func ? key : formatIdentifier(key)} ${sep} ${renderExpr(args[key], env)}`).join(', ');
1360
1362
 
1361
1363
 
1362
1364
  throw new Error(`Unknown args: ${JSON.stringify(args)}`);
@@ -385,7 +385,7 @@ function toSqlDdl(csn, options) {
385
385
  function reducesTypeSize(def) {
386
386
  // HANA does not allow decreasing the value of any of those type parameters.
387
387
  return def.old.type === def.new.type &&
388
- [ 'length', 'precision', 'scale' ].some(param => def.new[param] < def.old[param]);
388
+ [ 'length', 'precision', 'scale' ].some(param => def.new[param] < def.old[param]);
389
389
  }
390
390
  function getEltStr(defVariant, eltName) {
391
391
  return defVariant.target
@@ -525,12 +525,12 @@ function toSqlDdl(csn, options) {
525
525
  referentialConstraints[fileName] = renderReferentialConstraint(referentialConstraint, childEnv.indent, false, csn, options);
526
526
  });
527
527
  if (renderReferentialConstraintsAsHdbconstraint) {
528
- Object.entries(referentialConstraints).forEach( ([ fileName, constraint ]) => {
528
+ Object.entries(referentialConstraints).forEach(([ fileName, constraint ]) => {
529
529
  resultObj.hdbconstraint[fileName] = constraint;
530
530
  });
531
531
  }
532
532
  else {
533
- Object.values(referentialConstraints).forEach( (constraint) => {
533
+ Object.values(referentialConstraints).forEach((constraint) => {
534
534
  result += `,\n${constraint}`;
535
535
  });
536
536
  }
@@ -546,8 +546,7 @@ function toSqlDdl(csn, options) {
546
546
  = `UNIQUE INVERTED INDEX ${renderArtifactName(`${artifactName}_${cn}`)} ON ${tableName} (${c.map(cpath => quoteSqlId(cpath.ref[0])).join(', ')})`;
547
547
  }
548
548
  else {
549
- result += `,\n${childEnv.indent}CONSTRAINT ${renderArtifactName(`${artifactName}_${cn}`)} UNIQUE (${
550
- c.map(cpath => quoteSqlId(cpath.ref[0])).join(', ')})`;
549
+ result += `,\n${childEnv.indent}CONSTRAINT ${renderArtifactName(`${artifactName}_${cn}`)} UNIQUE (${c.map(cpath => quoteSqlId(cpath.ref[0])).join(', ')})`;
551
550
  }
552
551
  }
553
552
  result += `${env.indent}\n)`;
@@ -659,8 +658,7 @@ function toSqlDdl(csn, options) {
659
658
  if (duplicateChecker)
660
659
  duplicateChecker.addElement(quotedElementName, elm.$location, elementName);
661
660
 
662
- let result = `${env.indent + quotedElementName} ${
663
- renderTypeReference(artifactName, elementName, elm)
661
+ let result = `${env.indent + quotedElementName} ${renderTypeReference(artifactName, elementName, elm)
664
662
  }${renderNullability(elm, true)}`;
665
663
  if (elm.default)
666
664
  result += ` DEFAULT ${renderExpr(elm.default, env)}`;
@@ -956,7 +954,7 @@ function toSqlDdl(csn, options) {
956
954
  // An empty actual parameter list is rendered as `()`.
957
955
  const ref = csn.definitions[path.ref[0].id] || csn.definitions[path.ref[0]];
958
956
  if (ref && ref.params) {
959
- result += `(${renderArgs(path.ref[0].args || {}, '=>', env, syntax)})`;
957
+ result += `(${renderArgs(path.ref[0] || {}, '=>', env, syntax)})`;
960
958
  }
961
959
  else if ([ 'udf' ].includes(syntax)) {
962
960
  // if syntax is user defined function, render empty argument list
@@ -978,21 +976,23 @@ function toSqlDdl(csn, options) {
978
976
  * Render function arguments or view parameters (positional if array, named if object/dict),
979
977
  * using 'sep' as separator for positional parameters
980
978
  *
981
- * @param {Array|object} args Arguments to render
979
+ * @param {object} node with `args` to render
982
980
  * @param {string} sep Separator between args
983
981
  * @param {object} env Render environment
984
982
  * @param {string} syntax Some magic A2J paramter - for calcview parameter rendering
985
983
  * @returns {string} Rendered arguments
986
984
  * @throws Throws if args is not an array or object.
987
985
  */
988
- function renderArgs(args, sep, env, syntax) {
986
+ function renderArgs(node, sep, env, syntax) {
987
+ const args = node.args ? node.args : {};
989
988
  // Positional arguments
990
989
  if (Array.isArray(args))
991
990
  return args.map(arg => renderExpr(arg, env)).join(', ');
992
991
 
993
992
  // Named arguments (object/dict)
994
993
  else if (typeof args === 'object')
995
- return Object.keys(args).map(key => `${decorateParameter(key, syntax)} ${sep} ${renderExpr(args[key], env)}`).join(', ');
994
+ // if this is a function param which is not a reference to the model, we must not quote it
995
+ return Object.keys(args).map(key => `${node.func ? key : decorateParameter(key, syntax)} ${sep} ${renderExpr(args[key], env)}`).join(', ');
996
996
 
997
997
 
998
998
  throw new Error(`Unknown args: ${JSON.stringify(args)}`);
@@ -1151,12 +1151,11 @@ function toSqlDdl(csn, options) {
1151
1151
  const childEnv = increaseIndent(env);
1152
1152
  result += `SELECT${select.distinct ? ' DISTINCT' : ''}`;
1153
1153
  // FIXME: We probably also need to consider `excluding` here ?
1154
- result += `\n${
1155
- (select.columns || [ '*' ])
1156
- .filter(col => !(select.mixin || Object.create(null))[firstPathStepId(col.ref)]) // No mixin columns
1157
- .map(col => renderViewColumn(col, childEnv))
1158
- .filter(s => s !== '')
1159
- .join(',\n')}\n`;
1154
+ result += `\n${(select.columns || [ '*' ])
1155
+ .filter(col => !(select.mixin || Object.create(null))[firstPathStepId(col.ref)]) // No mixin columns
1156
+ .map(col => renderViewColumn(col, childEnv))
1157
+ .filter(s => s !== '')
1158
+ .join(',\n')}\n`;
1160
1159
  result += `${env.indent}FROM ${renderViewSource(artifactName, select.from, env)}`;
1161
1160
  if (select.where)
1162
1161
  result += `\n${env.indent}WHERE ${renderExpr(select.where, env, true, true)}`;
@@ -1412,23 +1411,23 @@ function toSqlDdl(csn, options) {
1412
1411
  case 'number':
1413
1412
  case 'boolean':
1414
1413
  case 'null':
1415
- // 17.42, NULL, TRUE
1414
+ // 17.42, NULL, TRUE
1416
1415
  return String(x.val).toUpperCase();
1417
1416
  case 'x':
1418
- // x'f000'
1417
+ // x'f000'
1419
1418
  return `${x.literal}'${x.val}'`;
1420
1419
  case 'date':
1421
1420
  case 'time':
1422
1421
  case 'timestamp':
1423
1422
  if (options.toSql.dialect === 'sqlite') {
1424
- // simple string literal '2017-11-02'
1423
+ // simple string literal '2017-11-02'
1425
1424
  return `'${x.val}'`;
1426
1425
  }
1427
1426
  // date'2017-11-02'
1428
1427
  return `${x.literal}'${x.val}'`;
1429
1428
 
1430
1429
  case 'string':
1431
- // 'foo', with proper escaping
1430
+ // 'foo', with proper escaping
1432
1431
  return `'${x.val.replace(/'/g, '\'\'')}'`;
1433
1432
  case 'object':
1434
1433
  if (x.val === null)
@@ -1593,13 +1592,13 @@ function toSqlDdl(csn, options) {
1593
1592
 
1594
1593
  // Not really a path step but an object-like function call
1595
1594
  if (s.func)
1596
- return `${s.func}(${renderArgs(s.args, '=>', env, null)})`;
1595
+ return `${s.func}(${renderArgs(s, '=>', env, null)})`;
1597
1596
 
1598
1597
  // Path step, possibly with view parameters and/or filters
1599
1598
  let result = `${quoteSqlId(s.id)}`;
1600
1599
  if (s.args) {
1601
1600
  // View parameters
1602
- result += `(${renderArgs(s.args, '=>', env, null)})`;
1601
+ result += `(${renderArgs(s, '=>', env, null)})`;
1603
1602
  }
1604
1603
  if (s.where) {
1605
1604
  // Filter, possibly with cardinality
@@ -33,7 +33,7 @@ const { implicitAs } = require('../../model/csnRefs');
33
33
  function renderFunc( funcName, node, dialect, renderArgs) {
34
34
  if (funcWithoutParen( node, dialect ))
35
35
  return funcName;
36
- return `${funcName}(${renderArgs( node.args )})`;
36
+ return `${funcName}(${renderArgs( node )})`;
37
37
  }
38
38
 
39
39
  /**
@@ -552,7 +552,7 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
552
552
  for (const part of Object.keys(base)) {
553
553
  if (excluding.indexOf(part) === -1) {
554
554
  // The thing is shadowed - ignore names present because of .inline, as those "disappear"
555
- if (names[part] && !subs[names[part]].inline) {
555
+ if (names[part] !== undefined && !subs[names[part]].inline) {
556
556
  replaced[part] = true;
557
557
  star.push(subs[names[part]]);
558
558
  }
@@ -103,7 +103,7 @@ function resolveTypeReferences(csn, options, resolved, pathDelimiter) {
103
103
 
104
104
  definitions[artifactName] = dummy;
105
105
  }
106
- } ]);
106
+ } ], true, { skipDict: { actions: true } });
107
107
  }
108
108
 
109
109
  /**
@@ -249,6 +249,15 @@ function handleExists(csn, options, error) {
249
249
  const {
250
250
  ref, head, tail,
251
251
  } = getFirstAssoc(current, exprPath.concat(i));
252
+
253
+ const lastAssoc = getLastAssoc(current, exprPath.concat(i));
254
+ // toE.toF.id -> we must not end on a non-assoc - this will also be caught downstream by
255
+ // '“EXISTS” can only be used with associations/compositions, found $(TYPE)'
256
+ // But the error might not be clear, since it could be because of our rewritten stuff. The later check
257
+ // checks for exists id -> our rewrite turns toE.toF.id into toE[exists toF[exists id]], leading to the same error
258
+ if (lastAssoc.tail.length > 0)
259
+ error(null, current.$path, { id: lastAssoc.tail[0].id ? lastAssoc.tail[0].id : lastAssoc.tail[0], name: lastAssoc.ref.id ? lastAssoc.ref.id : lastAssoc.ref }, 'Unexpected path step $(ID) after association $(NAME) in "EXISTS"');
260
+
252
261
  const newThing = [ ...head, nestFilters(head.length + 1, ref, tail, exprPath.concat([ i ])) ];
253
262
  expr[i].ref = newThing;
254
263
  }
@@ -278,15 +287,10 @@ function handleExists(csn, options, error) {
278
287
  const current = expr[i];
279
288
  const isPrefixedWithTableAlias = firstLinkIsEntityOrQuerySource(exprPath.concat(i));
280
289
  const base = getBase(queryBase, isPrefixedWithTableAlias, current, exprPath.concat(i));
281
- const { root, ref, tail } = getFirstAssoc(current, exprPath.concat(i));
282
-
283
- if (tail.length > 0) {
284
- error(null, current.$path, { id: tail[0].id ? tail[0].id : tail[0], name: ref.id ? ref.id : ref }, 'Unexpected path step $(ID) after association $(NAME) in "EXISTS"');
285
- continue;
286
- }
290
+ const { root, ref } = getFirstAssoc(current, exprPath.concat(i));
287
291
 
288
292
  if (!root.target) {
289
- error(null, current.$path, { type: root.type }, '“EXISTS” can only be used with associations/compositions, found $(TYPE)');
293
+ error(null, exprPath.concat(i), { type: root.type }, '“EXISTS” can only be used with associations/compositions, found $(TYPE)');
290
294
  return { result: [], leftovers: [] };
291
295
  }
292
296
 
@@ -477,6 +481,27 @@ function handleExists(csn, options, error) {
477
481
  };
478
482
  }
479
483
 
484
+ /**
485
+ * Get the last association from the expression part - similar to getFirstAssoc
486
+ *
487
+ * @param {object} xprPart
488
+ * @param {CSN.Path} path
489
+ * @returns {{head: Array, root: CSN.Element, ref: string|object, tail: Array}} The last assoc (root), the corresponding ref (ref), anything before the ref (head) and the rest of the ref (tail).
490
+ */
491
+ function getLastAssoc(xprPart, path) {
492
+ const { links, art } = inspectRef(path);
493
+ for (let i = xprPart.ref.length - 1; i > -1; i--) {
494
+ if (links[i].art && links[i].art.target) {
495
+ return {
496
+ head: (i === 0 ? [] : xprPart.ref.slice(0, i)), root: links[i].art, ref: xprPart.ref[i], tail: xprPart.ref.slice(i + 1),
497
+ };
498
+ }
499
+ }
500
+ return {
501
+ head: (xprPart.ref.length === 1 ? [] : xprPart.ref.slice(0, xprPart.ref.length - 1)), root: art, ref: xprPart.ref[xprPart.ref.length - 1], tail: [],
502
+ };
503
+ }
504
+
480
505
  /**
481
506
  * Check (using inspectRef -> links), wether the first path step is an entity or query source
482
507
  *
@@ -10,7 +10,7 @@ const structuralNodeHandlers = {
10
10
  returns: traverseTyped,
11
11
  on: traverseArray,
12
12
  keys: traverseArray,
13
- ref: traverseRef,
13
+ ref: traverseArray,
14
14
  query: traverseTyped,
15
15
  SELECT: traverseTyped,
16
16
  SET: traverseTyped,
@@ -33,10 +33,6 @@ function structuralPath(csn, path) {
33
33
  return traverseDict(csn.definitions, path, 1, ['definitions']);
34
34
  }
35
35
 
36
- function traverseRef(obj, path, index, typeStack) {
37
- return traverseArray(obj, path, index, typeStack);
38
- }
39
-
40
36
  function traverseArray(obj, path, index, typeStack) {
41
37
  if(!Array.isArray(obj)) return typeStack;
42
38
  const name = path[index];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds-compiler",
3
- "version": "2.10.2",
3
+ "version": "2.10.4",
4
4
  "description": "CDS (Core Data Services) compiler and backends",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "author": "SAP SE (https://www.sap.com)",