@sap/cds-compiler 2.7.0 → 2.10.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 (63) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/lib/api/main.js +8 -10
  3. package/lib/api/options.js +13 -9
  4. package/lib/api/validate.js +11 -8
  5. package/lib/base/keywords.js +32 -2
  6. package/lib/base/message-registry.js +16 -0
  7. package/lib/base/messages.js +2 -0
  8. package/lib/base/model.js +1 -0
  9. package/lib/checks/onConditions.js +5 -0
  10. package/lib/checks/types.js +26 -2
  11. package/lib/checks/unknownMagic.js +38 -0
  12. package/lib/checks/validator.js +7 -2
  13. package/lib/compiler/assert-consistency.js +11 -5
  14. package/lib/compiler/builtins.js +2 -0
  15. package/lib/compiler/checks.js +3 -1
  16. package/lib/compiler/definer.js +87 -29
  17. package/lib/compiler/resolver.js +75 -16
  18. package/lib/compiler/shared.js +29 -9
  19. package/lib/edm/annotations/genericTranslation.js +182 -186
  20. package/lib/edm/csn2edm.js +93 -98
  21. package/lib/edm/edm.js +16 -20
  22. package/lib/edm/edmPreprocessor.js +274 -83
  23. package/lib/edm/edmUtils.js +29 -10
  24. package/lib/gen/language.checksum +1 -1
  25. package/lib/gen/language.interp +12 -1
  26. package/lib/gen/language.tokens +57 -53
  27. package/lib/gen/languageLexer.interp +10 -1
  28. package/lib/gen/languageLexer.js +770 -744
  29. package/lib/gen/languageLexer.tokens +49 -46
  30. package/lib/gen/languageParser.js +4727 -4323
  31. package/lib/json/from-csn.js +52 -23
  32. package/lib/json/to-csn.js +185 -71
  33. package/lib/language/errorStrategy.js +1 -0
  34. package/lib/language/genericAntlrParser.js +9 -0
  35. package/lib/language/language.g4 +90 -31
  36. package/lib/main.js +4 -0
  37. package/lib/model/api.js +78 -0
  38. package/lib/model/csnRefs.js +7 -1
  39. package/lib/model/csnUtils.js +5 -4
  40. package/lib/optionProcessor.js +7 -1
  41. package/lib/render/.eslintrc.json +3 -1
  42. package/lib/render/toCdl.js +45 -9
  43. package/lib/render/toHdbcds.js +100 -34
  44. package/lib/render/toSql.js +12 -4
  45. package/lib/render/utils/common.js +5 -9
  46. package/lib/sql-identifier.js +6 -1
  47. package/lib/transform/db/draft.js +6 -4
  48. package/lib/transform/db/expansion.js +14 -4
  49. package/lib/transform/db/flattening.js +13 -5
  50. package/lib/transform/db/transformExists.js +252 -58
  51. package/lib/transform/forHanaNew.js +7 -1
  52. package/lib/transform/forOdataNew.js +12 -8
  53. package/lib/transform/odata/attachPath.js +19 -4
  54. package/lib/transform/odata/generateForeignKeyElements.js +11 -10
  55. package/lib/transform/odata/referenceFlattener.js +44 -38
  56. package/lib/transform/odata/sortByAssociationDependency.js +2 -2
  57. package/lib/transform/odata/structuralPath.js +76 -0
  58. package/lib/transform/odata/structureFlattener.js +13 -10
  59. package/lib/transform/odata/typesExposure.js +22 -12
  60. package/lib/transform/transformUtilsNew.js +33 -1
  61. package/lib/transform/translateAssocsToJoins.js +6 -4
  62. package/lib/transform/universalCsnEnricher.js +67 -0
  63. package/package.json +1 -1
@@ -3,12 +3,12 @@
3
3
  const {
4
4
  getParentNameOf, getLastPartOf, getLastPartOfRef,
5
5
  hasValidSkipOrExists, isBuiltinType, generatedByCompilerVersion, getNormalizedQuery,
6
- getRootArtifactName, getResultingName, getNamespace,
6
+ getRootArtifactName, getResultingName, getNamespace, forEachMember,
7
7
  } = require('../model/csnUtils');
8
8
  const keywords = require('../base/keywords');
9
9
  const {
10
- renderFunc, processExprArray, getRealName, addContextMarkers, addIntermediateContexts, cdsToSqlTypes,
11
- hasHanaComment, getHanaComment, findElement,
10
+ renderFunc, beautifyExprArray, getRealName, addContextMarkers, addIntermediateContexts, cdsToSqlTypes,
11
+ hasHanaComment, getHanaComment, findElement, funcWithoutParen,
12
12
  } = require('./utils/common');
13
13
  const {
14
14
  renderReferentialConstraint,
@@ -19,6 +19,8 @@ const { checkCSNVersion } = require('../json/csnVersion');
19
19
  const { makeMessageFunction } = require('../base/messages');
20
20
  const timetrace = require('../utils/timetrace');
21
21
 
22
+ const { smartId, delimitedId } = require('../sql-identifier');
23
+
22
24
  const $PROJECTION = '$projection';
23
25
  const $SELF = '$self';
24
26
 
@@ -402,6 +404,9 @@ function toHdbcdsSource(csn, options) {
402
404
  const duplicateChecker = new DuplicateChecker(); // registry for all artifact names and element names
403
405
  duplicateChecker.addArtifact(artifactName, env.path, artifactName);
404
406
  childEnv.path = env.path.concat('elements');
407
+ // calculate __aliases which must be used in case an association
408
+ // has the same identifier as it's target
409
+ createTopLevelAliasesForArtifact(artifactName, art, env);
405
410
  for (const name in art.elements)
406
411
  result += renderElement(name, art.elements[name], childEnv, duplicateChecker);
407
412
 
@@ -411,6 +416,33 @@ function toHdbcdsSource(csn, options) {
411
416
  return result;
412
417
  }
413
418
 
419
+ /**
420
+ * If an association/composition has the same identifier as it's target
421
+ * we must render an "using target as __target" and use the alias to refer to the target
422
+ *
423
+ * @param {string} artName
424
+ * @param {CSN.Artifact} art
425
+ * @param {CdlRenderEnvironment} env
426
+ */
427
+ function createTopLevelAliasesForArtifact(artName, art, env) {
428
+ forEachMember(art, (element) => {
429
+ if (!element.target)
430
+ return;
431
+
432
+ let alias = element['@cds.persistence.name'];
433
+ if (uppercaseAndUnderscore(element.target) === element['@cds.persistence.name']) {
434
+ alias = createTopLevelAliasName(element['@cds.persistence.name']);
435
+ // calculate new alias if it would conflict with other csn.Artifact
436
+ while (csn.definitions[alias])
437
+ alias = createTopLevelAliasName(alias);
438
+ env.topLevelAliases[element['@cds.persistence.name']] = {
439
+ quotedName: formatIdentifier(element['@cds.persistence.name']),
440
+ quotedAlias: formatIdentifier(alias),
441
+ };
442
+ }
443
+ });
444
+ }
445
+
414
446
  /**
415
447
  * Render the 'technical configuration { ... }' section 'tc' of an entity.
416
448
  *
@@ -516,7 +548,7 @@ function toHdbcdsSource(csn, options) {
516
548
  // Special handling for HANA CDS: Must omit the ':' before anonymous structured types (for historical reasons)
517
549
  const omitColon = (!elm.type && elm.elements);
518
550
  let result = '';
519
- const quotedElementName = quoteOrUppercaseId(elementName, env.path);
551
+ const quotedElementName = formatIdentifier(elementName);
520
552
  if (duplicateChecker)
521
553
  duplicateChecker.addElement(quotedElementName, env.path, elementName);
522
554
 
@@ -548,7 +580,7 @@ function toHdbcdsSource(csn, options) {
548
580
  if (source.SELECT || source.SET) {
549
581
  let result = `(${renderQuery(source, false, increaseIndent(env))})`;
550
582
  if (source.as)
551
- result += ` as ${quoteOrUppercaseId(source.as, env.path)}`;
583
+ result += ` as ${formatIdentifier(source.as)}`;
552
584
 
553
585
  return result;
554
586
  }
@@ -627,11 +659,11 @@ function toHdbcdsSource(csn, options) {
627
659
  const implicitAlias = path.ref.length === 0 ? getLastPartOf(getResultingName(csn, options.sqlMapping, path.ref[0])) : getLastPartOfRef(path.ref);
628
660
  if (path.as) {
629
661
  // Source had an alias - render it
630
- result += ` as ${quoteOrUppercaseId(path.as, env.path)}`;
662
+ result += ` as ${formatIdentifier(path.as)}`;
631
663
  }
632
- else if (getLastPartOf(result) !== quoteOrUppercaseId(implicitAlias)) {
664
+ else if (getLastPartOf(result) !== formatIdentifier(implicitAlias)) {
633
665
  // Render an artificial alias if the result would produce a different one
634
- result += ` as ${quoteOrUppercaseId(implicitAlias, env.path)}`;
666
+ result += ` as ${formatIdentifier(implicitAlias)}`;
635
667
  }
636
668
  return result;
637
669
  }
@@ -655,7 +687,7 @@ function toHdbcdsSource(csn, options) {
655
687
 
656
688
  if (element && element.virtual || env._artifact.elements[leaf] && env._artifact.elements[leaf].virtual) {
657
689
  if (isDeprecatedEnabled(options, 'renderVirtualElements'))
658
- return `${result}${env.indent}null as ${quoteOrUppercaseId(leaf, env.path)}`;
690
+ return `${result}${env.indent}null as ${formatIdentifier(leaf)}`;
659
691
  }
660
692
  else {
661
693
  return renderNonVirtualColumn();
@@ -683,7 +715,7 @@ function toHdbcdsSource(csn, options) {
683
715
  alias = leaf;
684
716
 
685
717
  if (alias)
686
- result += ` as ${quoteOrUppercaseId(alias, env.path)}`;
718
+ result += ` as ${formatIdentifier(alias)}`;
687
719
 
688
720
  // Explicit type provided for the view element?
689
721
  if (col.cast && col.cast.target) {
@@ -797,7 +829,7 @@ function toHdbcdsSource(csn, options) {
797
829
  result += `${env.indent}}`;
798
830
  }
799
831
  if (select.excluding) {
800
- result += ` excluding {\n${select.excluding.map(id => `${childEnv.indent}${quoteOrUppercaseId(id, env.path)}`).join(',\n')}\n`;
832
+ result += ` excluding {\n${select.excluding.map(id => `${childEnv.indent}${formatIdentifier(id)}`).join(',\n')}\n`;
801
833
  result += `${env.indent}}`;
802
834
  }
803
835
 
@@ -895,7 +927,7 @@ function toHdbcdsSource(csn, options) {
895
927
  function renderParameter(parName, par, env) {
896
928
  if (par.notNull === true || par.notNull === false)
897
929
  info(null, env.path.concat([ 'params', parName ]), 'Not Null constraints on HDBCDS view parameters are not allowed and are ignored');
898
- return `${env.indent + quoteOrUppercaseId(parName, env.path)} : ${renderTypeReference(par, env)}`;
930
+ return `${env.indent + formatParamIdentifier(parName, env.path.concat([ 'params', parName ]))} : ${renderTypeReference(par, env)}`;
899
931
  }
900
932
 
901
933
  /**
@@ -1012,10 +1044,19 @@ function toHdbcdsSource(csn, options) {
1012
1044
 
1013
1045
  result += `${renderCardinality(elm.cardinality)} to `;
1014
1046
 
1047
+
1015
1048
  // normal target or named aspect
1016
1049
  if (elm.target || elm.targetAspect && typeof elm.targetAspect === 'string') {
1017
- result += plainNames ? renderAbsoluteNamePlain(elm.target || elm.targetAspect, env)
1018
- : renderAbsoluteNameWithQuotes(elm.target || elm.targetAspect, env);
1050
+ // we might have a "using target as __target"
1051
+ const targetArtifact = csn.definitions[elm.target];
1052
+ const targetAlias = env.topLevelAliases[targetArtifact['@cds.persistence.name']];
1053
+ if (targetAlias) {
1054
+ result += targetAlias.quotedAlias;
1055
+ }
1056
+ else {
1057
+ result += plainNames ? renderAbsoluteNamePlain(elm.target || elm.targetAspect, env)
1058
+ : renderAbsoluteNameWithQuotes(elm.target || elm.targetAspect, env);
1059
+ }
1019
1060
  }
1020
1061
 
1021
1062
  // ON-condition (if any)
@@ -1087,7 +1128,7 @@ function toHdbcdsSource(csn, options) {
1087
1128
  function renderExpr(x, env, inline = true, inExpr = false) {
1088
1129
  // Compound expression
1089
1130
  if (Array.isArray(x))
1090
- return processExprArray(x, renderExpr, env, inline, inExpr);
1131
+ return beautifyExprArray(x.map(item => renderExpr(item, env, inline, inExpr)));
1091
1132
 
1092
1133
  if (typeof x === 'object' && x !== null) {
1093
1134
  if (inExpr && x.cast && x.cast.type)
@@ -1129,6 +1170,9 @@ function toHdbcdsSource(csn, options) {
1129
1170
 
1130
1171
  const regex = RegExp(/^[a-zA-Z][\w#$]*$/, 'g');
1131
1172
  const funcName = regex.test(x.func) ? x.func : quoteId(x.func);
1173
+ // we can't quote functions with parens, issue warning if it is a reserved keyword
1174
+ if (!funcWithoutParen(x, 'hana') && keywords.hdbcds.includes(uppercaseAndUnderscore(funcName)))
1175
+ warning(null, x.$location, `The identifier “${uppercaseAndUnderscore(funcName)}“ is a SAP HANA keyword`);
1132
1176
  return renderFunc( funcName, x, 'hana', a => renderArgs(a, '=>', env) );
1133
1177
  }
1134
1178
  // Nested expression
@@ -1267,7 +1311,7 @@ function toHdbcdsSource(csn, options) {
1267
1311
  [ $SELF, $PROJECTION, '$session' ].includes(s))
1268
1312
  return s;
1269
1313
 
1270
- return quoteOrUppercaseId(s, env.path);
1314
+ return formatIdentifier(s);
1271
1315
  }
1272
1316
  // ID with filters or parameters
1273
1317
  else if (typeof s === 'object') {
@@ -1280,7 +1324,7 @@ function toHdbcdsSource(csn, options) {
1280
1324
  return `${s.func}(${renderArgs(s.args, '=>', env)})`;
1281
1325
 
1282
1326
  // Path step, possibly with view parameters and/or filters
1283
- let result = `${quoteOrUppercaseId(s.id)}`;
1327
+ let result = `${formatIdentifier(s.id)}`;
1284
1328
  if (s.args) {
1285
1329
  // View parameters
1286
1330
  result += `(${renderArgs(s.args, ':', env)})`;
@@ -1312,7 +1356,7 @@ function toHdbcdsSource(csn, options) {
1312
1356
 
1313
1357
  // Named arguments (object/dict)
1314
1358
  else if (typeof args === 'object')
1315
- return Object.keys(args).map(key => `${quoteOrUppercaseId(key)} ${sep} ${renderExpr(args[key], env)}`).join(', ');
1359
+ return Object.keys(args).map(key => `${formatIdentifier(key)} ${sep} ${renderExpr(args[key], env)}`).join(', ');
1316
1360
 
1317
1361
 
1318
1362
  throw new Error(`Unknown args: ${JSON.stringify(args)}`);
@@ -1405,10 +1449,10 @@ function toHdbcdsSource(csn, options) {
1405
1449
  function renderAbsoluteNamePlain(absName, env) {
1406
1450
  // Add using declaration
1407
1451
  env.topLevelAliases[absName] = {
1408
- quotedName: uppercaseAndUnderscore(absName),
1409
- quotedAlias: uppercaseAndUnderscore(absName),
1452
+ quotedName: formatIdentifier(uppercaseAndUnderscore(absName)),
1453
+ quotedAlias: formatIdentifier(uppercaseAndUnderscore(absName)),
1410
1454
  };
1411
- return uppercaseAndUnderscore(absName);
1455
+ return formatIdentifier(uppercaseAndUnderscore(absName));
1412
1456
  }
1413
1457
 
1414
1458
  /**
@@ -1493,7 +1537,7 @@ function toHdbcdsSource(csn, options) {
1493
1537
  function renderUsings(artifactName, env) {
1494
1538
  const distinct = {};
1495
1539
  Object.keys(env.topLevelAliases)
1496
- .filter(name => !(plainNames && env.topLevelAliases[name].quotedName === uppercaseAndUnderscore(artifactName))) // avoid "using FOO as FOO" in FOO.cds
1540
+ .filter(name => env.topLevelAliases[name].quotedAlias !== formatIdentifier(uppercaseAndUnderscore(artifactName))) // avoid "using FOO as FOO" in FOO.cds
1497
1541
  .forEach((name) => {
1498
1542
  distinct[`using ${env.topLevelAliases[name].quotedName} as ${env.topLevelAliases[name].quotedAlias};\n`] = '';
1499
1543
  });
@@ -1635,7 +1679,16 @@ function toHdbcdsSource(csn, options) {
1635
1679
  return id;
1636
1680
 
1637
1681
 
1638
- return `"${id.replace(/"/g, '""')}"`;
1682
+ switch (options.forHana.names) {
1683
+ case 'plain':
1684
+ return smartId(id, 'hdbcds');
1685
+ case 'quoted':
1686
+ return delimitedId(id, 'hdbcds');
1687
+ case 'hdbcds':
1688
+ return delimitedId(id, 'hdbcds');
1689
+ default:
1690
+ return null;
1691
+ }
1639
1692
  }
1640
1693
 
1641
1694
  /*
@@ -1656,21 +1709,34 @@ function toHdbcdsSource(csn, options) {
1656
1709
  }
1657
1710
 
1658
1711
  /**
1659
- * Quote or uppercase an identifier 'id', depending on naming strategy
1712
+ * Quote and/or uppercase an identifier 'id', depending on naming strategy
1660
1713
  *
1661
1714
  * @param {string} id Identifier
1662
- * @param {CSN.Location} [location] Optional location for the warning.
1663
1715
  * @returns {string} Quoted/uppercased id
1664
1716
  */
1665
- function quoteOrUppercaseId(id, location = null) {
1666
- if (plainNames) {
1667
- const result = uppercaseAndUnderscore(id);
1668
- // Warn if colliding with HANA keyword
1669
- if (keywords.hana.includes(result))
1670
- warning(null, location, `The identifier "${id}" is a SAP HANA keyword`);
1717
+ function formatIdentifier(id) {
1718
+ id = options.forHana.names === 'plain' ? id.toUpperCase() : id;
1719
+ return quoteId(id);
1720
+ }
1721
+
1722
+ /**
1723
+ * Quote or uppercase a parameter identifier 'id', depending on naming strategy
1724
+ * Smart quoting cannot be applied to the parameter identifiers, issue warning instead.
1725
+ *
1726
+ *
1727
+ * @param {string} id Identifier
1728
+ * @param {CSN.Path} [location] Optional location for the warning.
1729
+ * @returns {string} Quoted/uppercased id
1730
+ */
1731
+ function formatParamIdentifier(id, location) {
1732
+ // Warn if colliding with HANA keyword, but do not quote for plain
1733
+ // --> quoted reserved words as param lead to a weird deployment error
1734
+ if (keywords.hdbcds.includes(uppercaseAndUnderscore(id)))
1735
+ warning(null, location, { id }, 'The identifier $(ID) is a SAP HANA keyword');
1736
+
1737
+ if (plainNames)
1738
+ return uppercaseAndUnderscore(id);
1671
1739
 
1672
- return result;
1673
- }
1674
1740
  return quoteId(id);
1675
1741
  }
1676
1742
 
@@ -1693,7 +1759,7 @@ function toHdbcdsSource(csn, options) {
1693
1759
  */
1694
1760
  function renderArtifactName(artifactName, env, fallthrough = false) {
1695
1761
  if (plainNames && !fallthrough)
1696
- return uppercaseAndUnderscore(artifactName);
1762
+ return formatIdentifier(uppercaseAndUnderscore(artifactName));
1697
1763
  // hdbcds with quoted or hdbcds naming
1698
1764
  return env.namePrefix + quoteId(getRealName(csn, artifactName).replace(/\./g, '_'));
1699
1765
  }
@@ -7,7 +7,7 @@ const {
7
7
  forEachDefinition, getResultingName,
8
8
  } = require('../model/csnUtils');
9
9
  const {
10
- renderFunc, processExprArray, cdsToSqlTypes, getHanaComment, hasHanaComment,
10
+ renderFunc, beautifyExprArray, cdsToSqlTypes, getHanaComment, hasHanaComment,
11
11
  } = require('./utils/common');
12
12
  const {
13
13
  renderReferentialConstraint, getIdentifierUtils,
@@ -1088,8 +1088,15 @@ function toSqlDdl(csn, options) {
1088
1088
  const p = params[pn];
1089
1089
  if (p.notNull === true || p.notNull === false)
1090
1090
  info(null, [ 'definitions', artifactName, 'params', pn ], 'Not Null constraints on SQL view parameters are not allowed and are ignored');
1091
-
1092
- let pstr = `IN ${prepareIdentifier(pn)} ${renderTypeReference(artifactName, pn, p)}`;
1091
+ // do not quote parameter identifiers for naming mode "quoted" / "hdbcds"
1092
+ // this would be an incompatible change, as non-uppercased, quoted identifiers
1093
+ // are rejected by the HANA compiler.
1094
+ let pIdentifier;
1095
+ if (options.toSql.names === 'quoted' || options.toSql.names === 'hdbcds')
1096
+ pIdentifier = prepareIdentifier(pn);
1097
+ else
1098
+ pIdentifier = quoteSqlId(pn);
1099
+ let pstr = `IN ${pIdentifier} ${renderTypeReference(artifactName, pn, p)}`;
1093
1100
  if (p.default)
1094
1101
  pstr += ` DEFAULT ${renderExpr(p.default)}`;
1095
1102
 
@@ -1336,7 +1343,8 @@ function toSqlDdl(csn, options) {
1336
1343
  function renderExpr(x, env, inline = true, nestedExpr = false) {
1337
1344
  // Compound expression
1338
1345
  if (Array.isArray(x)) {
1339
- return processExprArray(x, renderExpr, env, inline, nestedExpr);
1346
+ const tokens = x.map(item => renderExpr(item, env, inline, nestedExpr));
1347
+ return beautifyExprArray(tokens);
1340
1348
  }
1341
1349
  else if (typeof x === 'object' && x !== null) {
1342
1350
  if (nestedExpr && x.cast && x.cast.type)
@@ -53,19 +53,14 @@ function funcWithoutParen( node, dialect ) {
53
53
  }
54
54
 
55
55
  /**
56
- * Process an expression array, rendering each subexpression and joining them appropriately
56
+ * Process already rendered expression parts by joining them nicely
57
57
  *
58
- * @param {Array} xpr Expression array
59
- * @param {Function} renderExpr Function to render expressions
60
- * @param {CdlRenderEnvironment} env Environment for rendering
61
- * @param {boolean} inline Wether to render the expression inline
62
- * @param {boolean} inExpr Wether to render as if a subexpression
58
+ * @param {Array} tokens Array of expression tokens
63
59
  *
64
60
  * @returns {string} The rendered xpr
65
61
  */
66
- function processExprArray(xpr, renderExpr, env, inline, inExpr) {
62
+ function beautifyExprArray(tokens) {
67
63
  // Simply concatenate array parts with spaces (with a tiny bit of beautification)
68
- const tokens = xpr.map(item => renderExpr(item, env, inline, inExpr));
69
64
  let result = '';
70
65
  for (let i = 0; i < tokens.length; i++) {
71
66
  result += tokens[i];
@@ -378,7 +373,7 @@ function getHanaComment(obj) {
378
373
 
379
374
  module.exports = {
380
375
  renderFunc,
381
- processExprArray,
376
+ beautifyExprArray,
382
377
  getNamespace,
383
378
  getRealName,
384
379
  addIntermediateContexts,
@@ -387,4 +382,5 @@ module.exports = {
387
382
  hasHanaComment,
388
383
  getHanaComment,
389
384
  findElement,
385
+ funcWithoutParen,
390
386
  };
@@ -51,7 +51,12 @@ const sqlDialects = {
51
51
  effectiveName: name => name.toUpperCase(),
52
52
  asDelimitedId: name => `"${ name.replace(/"/g, '""')}"`,
53
53
  },
54
- // TODO: hdbcds for HANA CDS
54
+ hdbcds: {
55
+ regularRegex: /^[A-Za-z_][A-Za-z_0-9]*$/,
56
+ reservedWords: keywords.hdbcds,
57
+ effectiveName: name => name,
58
+ asDelimitedId: name => `"${ name.replace(/"/g, '""')}"`,
59
+ },
55
60
  };
56
61
 
57
62
  function smartId( name, dialect ) {
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  const {
4
- hasAnnotationValue, forEachGeneric, getUtils, getServiceNames, forEachDefinition,
5
- getResultingName,
4
+ hasAnnotationValue, getUtils, getServiceNames, forEachDefinition,
5
+ getResultingName, forEachMemberRecursively,
6
6
  } = require('../../model/csnUtils');
7
7
  const { setProp, isDeprecatedEnabled } = require('../../base/model');
8
8
  const { getTransformers } = require('../transformUtilsNew');
@@ -117,10 +117,12 @@ function generateDrafts(csn, options, pathDelimiter, messageFunctions) {
117
117
 
118
118
  // extract keys for UUID inspection
119
119
  const keys = [];
120
- forEachGeneric( artifact, 'elements', (elt) => {
120
+ forEachMemberRecursively(artifact, (elt, name, prop, path) => {
121
+ if (!elt.elements && !elt.type && !elt.virtual) // only check leafs
122
+ error(null, path, 'Expecting element to have a type when used in a draft-enabled artifact');
121
123
  if (elt.key && elt.key === true && !elt.virtual)
122
124
  keys.push(elt);
123
- });
125
+ }, [ 'definitions', artifactName ], true, { elementsOnly: true });
124
126
 
125
127
  // In contrast to EDM, the DB entity may have more than one technical keys but should have idealy exactly one key of type cds.UUID
126
128
  if (keys.length !== 1)
@@ -446,11 +446,15 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
446
446
  if (col.ref && col.$scope !== '$magic') {
447
447
  const _art = col._art || inspectRef(path.concat(i)).art;
448
448
  if (_art && isStructured(_art))
449
- newThing.push(...expandRef(_art, col.ref, [ col.as || col.ref[col.ref.length - 1] ], col.key || false, withAlias));
449
+ newThing.push(...expandRef(_art, col.ref, col.as, col.key || false, withAlias));
450
450
 
451
451
  else
452
452
  newThing.push(col);
453
453
  }
454
+ else if (col.ref && col.$scope === '$magic' && col.ref[0] === '$user' && !col.as) {
455
+ col.as = implicitAs(col.ref);
456
+ newThing.push(col);
457
+ }
454
458
  else {
455
459
  newThing.push(col);
456
460
  }
@@ -473,7 +477,7 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
473
477
  */
474
478
  function expandRef(art, ref, alias, isKey, withAlias) {
475
479
  const expanded = [];
476
- const stack = [ [ art, ref, alias ] ];
480
+ const stack = [ [ art, ref, [ alias || ref[ref.length - 1] ] ] ];
477
481
  while (stack.length > 0) {
478
482
  const [ current, currentRef, currentAlias ] = stack.pop();
479
483
  if (isStructured(current)) {
@@ -482,8 +486,14 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
482
486
  }
483
487
  else {
484
488
  const obj = { ref: currentRef };
485
- if (withAlias)
486
- obj.as = currentAlias.join(pathDelimiter);
489
+ if (withAlias) {
490
+ const newAlias = currentAlias.join(pathDelimiter);
491
+ // if (alias !== undefined) // explicit alias
492
+ obj.as = newAlias;
493
+ // alias was implicit - to later distinguish expanded s -> s.a from explicitly written s.a
494
+ if (alias === undefined)
495
+ setProp(obj, '$implicitAlias', true);
496
+ }
487
497
  if (isKey)
488
498
  obj.key = true;
489
499
  expanded.push(obj);
@@ -69,8 +69,12 @@ function resolveTypeReferences(csn, options, resolved, pathDelimiter) {
69
69
  }
70
70
  }
71
71
  const { toFinalBaseType } = transformUtils.getTransformers(csn, options, pathDelimiter);
72
- // (030) - For all elements, replace derived types by final base type
73
72
  applyTransformations(csn, {
73
+ cast: (parent) => {
74
+ // Resolve cast already - we otherwise lose .localized
75
+ if (parent.cast.type && !isBuiltinType(parent.cast.type))
76
+ toFinalBaseType(parent.cast, resolved, true);
77
+ },
74
78
  type: (parent, prop, type) => {
75
79
  if (!isBuiltinType(type)) {
76
80
  const directLocalized = parent.localized || false;
@@ -143,13 +147,17 @@ function flattenAllStructStepsInRefs(csn, options, resolved, pathDelimiter) {
143
147
 
144
148
  parent.ref = flattenStructStepsInRef(ref, scopedPath, links, scope, resolvedLinkTypes );
145
149
  resolved.set(parent, { links, art, scope });
146
- // Because of expansion we added alias to everything - strip off superflous alias
147
- if (parent.as && parent.as === parent.ref[parent.ref.length - 1] && scope !== '$magic')
148
- delete parent.as;
149
150
  // Explicitly set implicit alias for things that are now flattened - but only in columns
150
151
  // TODO: Can this be done elegantly during expand phase already?
151
- else if (parent.ref[ref.length - 1] !== lastRef && (insideColumns(scopedPath) || insideKeys(scopedPath)) && !parent.as)
152
+ if (parent.$implicitAlias) { // an expanded s -> s.a is marked with this - do not add implicit alias "a" there, we want s_a
153
+ if (parent.ref[parent.ref.length - 1] === parent.as) // for a simple s that was expanded - for s.substructure this would not apply
154
+ delete parent.as;
155
+ delete parent.$implicitAlias;
156
+ }
157
+ // To handle explicitly written s.a - add implicit alias a, since after flattening it would otherwise be s_a
158
+ else if (parent.ref[parent.ref.length - 1] !== lastRef && (insideColumns(scopedPath) || insideKeys(scopedPath)) && !parent.as) {
152
159
  parent.as = lastRef;
160
+ }
153
161
  };
154
162
  // adapt queries later
155
163
  adaptRefs.push(fn);