@sap/cds-compiler 2.7.0 → 2.11.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 (87) hide show
  1. package/CHANGELOG.md +167 -0
  2. package/bin/cdsc.js +42 -25
  3. package/bin/cdsse.js +1 -0
  4. package/doc/CHANGELOG_BETA.md +10 -0
  5. package/lib/api/.eslintrc.json +2 -0
  6. package/lib/api/main.js +17 -33
  7. package/lib/api/options.js +25 -13
  8. package/lib/api/validate.js +33 -9
  9. package/lib/backends.js +9 -8
  10. package/lib/base/dictionaries.js +2 -1
  11. package/lib/base/keywords.js +32 -2
  12. package/lib/base/message-registry.js +26 -2
  13. package/lib/base/messages.js +25 -9
  14. package/lib/base/model.js +5 -3
  15. package/lib/base/optionProcessorHelper.js +56 -22
  16. package/lib/checks/onConditions.js +5 -0
  17. package/lib/checks/selectItems.js +4 -0
  18. package/lib/checks/types.js +26 -2
  19. package/lib/checks/unknownMagic.js +41 -0
  20. package/lib/checks/validator.js +7 -2
  21. package/lib/compiler/assert-consistency.js +18 -5
  22. package/lib/compiler/base.js +65 -0
  23. package/lib/compiler/builtins.js +30 -1
  24. package/lib/compiler/checks.js +5 -2
  25. package/lib/compiler/definer.js +145 -120
  26. package/lib/compiler/index.js +16 -4
  27. package/lib/compiler/propagator.js +5 -2
  28. package/lib/compiler/resolver.js +207 -47
  29. package/lib/compiler/shared.js +47 -200
  30. package/lib/compiler/utils.js +173 -0
  31. package/lib/edm/annotations/genericTranslation.js +183 -187
  32. package/lib/edm/csn2edm.js +94 -98
  33. package/lib/edm/edm.js +16 -20
  34. package/lib/edm/edmPreprocessor.js +302 -115
  35. package/lib/edm/edmUtils.js +31 -12
  36. package/lib/gen/language.checksum +1 -1
  37. package/lib/gen/language.interp +28 -1
  38. package/lib/gen/language.tokens +79 -69
  39. package/lib/gen/languageLexer.interp +28 -1
  40. package/lib/gen/languageLexer.js +879 -805
  41. package/lib/gen/languageLexer.tokens +71 -62
  42. package/lib/gen/languageParser.js +5308 -4308
  43. package/lib/json/from-csn.js +59 -30
  44. package/lib/json/to-csn.js +354 -105
  45. package/lib/language/antlrParser.js +11 -0
  46. package/lib/language/errorStrategy.js +1 -0
  47. package/lib/language/genericAntlrParser.js +81 -14
  48. package/lib/language/language.g4 +163 -31
  49. package/lib/main.d.ts +136 -17
  50. package/lib/main.js +7 -1
  51. package/lib/model/api.js +78 -0
  52. package/lib/model/csnRefs.js +115 -32
  53. package/lib/model/csnUtils.js +71 -33
  54. package/lib/model/enrichCsn.js +36 -9
  55. package/lib/model/revealInternalProperties.js +20 -4
  56. package/lib/modelCompare/compare.js +2 -1
  57. package/lib/optionProcessor.js +33 -16
  58. package/lib/render/.eslintrc.json +3 -1
  59. package/lib/render/DuplicateChecker.js +1 -1
  60. package/lib/render/toCdl.js +60 -17
  61. package/lib/render/toHdbcds.js +122 -74
  62. package/lib/render/toSql.js +57 -32
  63. package/lib/render/utils/common.js +6 -10
  64. package/lib/sql-identifier.js +6 -1
  65. package/lib/transform/db/constraints.js +273 -119
  66. package/lib/transform/db/draft.js +9 -6
  67. package/lib/transform/db/expansion.js +19 -7
  68. package/lib/transform/db/flattening.js +31 -7
  69. package/lib/transform/db/transformExists.js +344 -66
  70. package/lib/transform/db/views.js +438 -0
  71. package/lib/transform/forHanaNew.js +65 -436
  72. package/lib/transform/forOdataNew.js +21 -10
  73. package/lib/transform/localized.js +2 -0
  74. package/lib/transform/odata/attachPath.js +19 -4
  75. package/lib/transform/odata/generateForeignKeyElements.js +11 -10
  76. package/lib/transform/odata/referenceFlattener.js +44 -38
  77. package/lib/transform/odata/sortByAssociationDependency.js +2 -2
  78. package/lib/transform/odata/structuralPath.js +72 -0
  79. package/lib/transform/odata/structureFlattener.js +13 -10
  80. package/lib/transform/odata/typesExposure.js +22 -12
  81. package/lib/transform/transformUtilsNew.js +55 -9
  82. package/lib/transform/translateAssocsToJoins.js +11 -17
  83. package/lib/transform/universalCsnEnricher.js +67 -0
  84. package/lib/utils/file.js +5 -3
  85. package/lib/utils/term.js +65 -42
  86. package/lib/utils/timetrace.js +48 -26
  87. package/package.json +1 -1
@@ -4,10 +4,10 @@
4
4
  const {
5
5
  getLastPartOf, getLastPartOfRef,
6
6
  hasValidSkipOrExists, isBuiltinType, generatedByCompilerVersion, getNormalizedQuery,
7
- forEachDefinition, getResultingName,
7
+ forEachDefinition, getResultingName, getVariableReplacement,
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,
@@ -15,7 +15,7 @@ const {
15
15
  const DuplicateChecker = require('./DuplicateChecker');
16
16
  const { checkCSNVersion } = require('../json/csnVersion');
17
17
  const { makeMessageFunction } = require('../base/messages');
18
- const timetrace = require('../utils/timetrace');
18
+ const { timetrace } = require('../utils/timetrace');
19
19
  const { isBetaEnabled, isDeprecatedEnabled } = require('../base/model');
20
20
  const { smartFuncId } = require('../sql-identifier');
21
21
  const { sortCsn } = require('../json/to-csn');
@@ -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)`;
@@ -568,7 +567,7 @@ function toSqlDdl(csn, options) {
568
567
  if (options.toSql.dialect === 'hana')
569
568
  renderIndexesInto(art.technicalConfig && art.technicalConfig.hana.indexes, artifactName, resultObj, env);
570
569
 
571
- if (options.toSql.dialect === 'hana' && hasHanaComment(art, options, art))
570
+ if (options.toSql.dialect === 'hana' && hasHanaComment(art, options))
572
571
  result += ` COMMENT '${getHanaComment(art)}'`;
573
572
 
574
573
  resultObj.hdbtable[artifactName] = result;
@@ -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)}`);
@@ -1054,7 +1054,7 @@ function toSqlDdl(csn, options) {
1054
1054
  definitionsDuplicateChecker.addArtifact(art['@cds.persistence.name'], art && art.$location, artifactName);
1055
1055
  let result = `VIEW ${viewName}`;
1056
1056
 
1057
- if (options.toSql.dialect === 'hana' && hasHanaComment(art, options, art))
1057
+ if (options.toSql.dialect === 'hana' && hasHanaComment(art, options))
1058
1058
  result += ` COMMENT '${getHanaComment(art)}'`;
1059
1059
 
1060
1060
  result += renderParameterDefinitions(artifactName, art.params);
@@ -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
 
@@ -1144,12 +1151,11 @@ function toSqlDdl(csn, options) {
1144
1151
  const childEnv = increaseIndent(env);
1145
1152
  result += `SELECT${select.distinct ? ' DISTINCT' : ''}`;
1146
1153
  // FIXME: We probably also need to consider `excluding` here ?
1147
- result += `\n${
1148
- (select.columns || [ '*' ])
1149
- .filter(col => !(select.mixin || Object.create(null))[firstPathStepId(col.ref)]) // No mixin columns
1150
- .map(col => renderViewColumn(col, childEnv))
1151
- .filter(s => s !== '')
1152
- .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`;
1153
1159
  result += `${env.indent}FROM ${renderViewSource(artifactName, select.from, env)}`;
1154
1160
  if (select.where)
1155
1161
  result += `\n${env.indent}WHERE ${renderExpr(select.where, env, true, true)}`;
@@ -1336,7 +1342,8 @@ function toSqlDdl(csn, options) {
1336
1342
  function renderExpr(x, env, inline = true, nestedExpr = false) {
1337
1343
  // Compound expression
1338
1344
  if (Array.isArray(x)) {
1339
- return processExprArray(x, renderExpr, env, inline, nestedExpr);
1345
+ const tokens = x.map(item => renderExpr(item, env, inline, nestedExpr));
1346
+ return beautifyExprArray(tokens);
1340
1347
  }
1341
1348
  else if (typeof x === 'object' && x !== null) {
1342
1349
  if (nestedExpr && x.cast && x.cast.type)
@@ -1376,6 +1383,8 @@ function toSqlDdl(csn, options) {
1376
1383
  // Function call, possibly with args (use '=>' for named args)
1377
1384
  else if (x.func) {
1378
1385
  const funcName = smartFuncId(prepareIdentifier(x.func), options.toSql.dialect);
1386
+ if (x.xpr)
1387
+ return renderWindowFunction(funcName, x, env);
1379
1388
  return renderFunc(funcName, x, options.toSql.dialect, a => renderArgs(a, '=>', env, null));
1380
1389
  }
1381
1390
  // Nested expression
@@ -1398,29 +1407,36 @@ function toSqlDdl(csn, options) {
1398
1407
  throw new Error(`Unknown expression: ${JSON.stringify(x)}`);
1399
1408
  }
1400
1409
 
1410
+ function renderWindowFunction(funcName, node, env) {
1411
+ const suffix = node.xpr.shift(); // OVER
1412
+ let r = `${funcName}(${renderArgs(node, '=>', env, null)})`;
1413
+ r += ` ${suffix} (${renderExpr(node.xpr, env)})`;
1414
+ return r;
1415
+ }
1416
+
1401
1417
  function renderExpressionLiteral(x) {
1402
1418
  // Literal value, possibly with explicit 'literal' property
1403
1419
  switch (x.literal || typeof x.val) {
1404
1420
  case 'number':
1405
1421
  case 'boolean':
1406
1422
  case 'null':
1407
- // 17.42, NULL, TRUE
1423
+ // 17.42, NULL, TRUE
1408
1424
  return String(x.val).toUpperCase();
1409
1425
  case 'x':
1410
- // x'f000'
1426
+ // x'f000'
1411
1427
  return `${x.literal}'${x.val}'`;
1412
1428
  case 'date':
1413
1429
  case 'time':
1414
1430
  case 'timestamp':
1415
1431
  if (options.toSql.dialect === 'sqlite') {
1416
- // simple string literal '2017-11-02'
1432
+ // simple string literal '2017-11-02'
1417
1433
  return `'${x.val}'`;
1418
1434
  }
1419
1435
  // date'2017-11-02'
1420
1436
  return `${x.literal}'${x.val}'`;
1421
1437
 
1422
1438
  case 'string':
1423
- // 'foo', with proper escaping
1439
+ // 'foo', with proper escaping
1424
1440
  return `'${x.val.replace(/'/g, '\'\'')}'`;
1425
1441
  case 'object':
1426
1442
  if (x.val === null)
@@ -1434,7 +1450,12 @@ function toSqlDdl(csn, options) {
1434
1450
 
1435
1451
  function renderExpressionRef(x) {
1436
1452
  if (!x.param && !x.global) {
1453
+ const magicReplacement = getVariableReplacement(x.ref, options);
1454
+
1437
1455
  if (x.ref[0] === '$user') {
1456
+ if (magicReplacement !== null)
1457
+ return `'${magicReplacement}'`;
1458
+
1438
1459
  const result = render$user(x);
1439
1460
  // Invalid second path step doesn't cause a return
1440
1461
  if (result)
@@ -1446,6 +1467,9 @@ function toSqlDdl(csn, options) {
1446
1467
  if (result)
1447
1468
  return result;
1448
1469
  }
1470
+ else if (x.ref[0] === '$session' && magicReplacement !== null) {
1471
+ return `'${magicReplacement}'`;
1472
+ }
1449
1473
  }
1450
1474
  // FIXME: We currently cannot distinguish whether '$parameters' was quoted or not - we
1451
1475
  // assume that it was not if the path has length 2 (
@@ -1468,6 +1492,7 @@ function toSqlDdl(csn, options) {
1468
1492
  function render$user(x) {
1469
1493
  // FIXME: this is all not enough: we might need an explicit select item alias
1470
1494
  if (x.ref[1] === 'id') {
1495
+ // Keep the old-style for compatibilty with magicVars.id - instead of magicVars.user.id...
1471
1496
  if (options.toSql.user && typeof options.toSql.user === 'string' || options.toSql.user instanceof String)
1472
1497
  return `'${options.toSql.user}'`;
1473
1498
 
@@ -1487,7 +1512,7 @@ function toSqlDdl(csn, options) {
1487
1512
  }
1488
1513
  return 'SESSION_CONTEXT(\'LOCALE\')';
1489
1514
  }
1490
- // Basically: Second path step was invalid, do nothing
1515
+ // Basically: Second path step was invalid, do nothing - should not happen, see 'unknownMagic.js'
1491
1516
  return null;
1492
1517
  }
1493
1518
  /**
@@ -1585,13 +1610,13 @@ function toSqlDdl(csn, options) {
1585
1610
 
1586
1611
  // Not really a path step but an object-like function call
1587
1612
  if (s.func)
1588
- return `${s.func}(${renderArgs(s.args, '=>', env, null)})`;
1613
+ return `${s.func}(${renderArgs(s, '=>', env, null)})`;
1589
1614
 
1590
1615
  // Path step, possibly with view parameters and/or filters
1591
1616
  let result = `${quoteSqlId(s.id)}`;
1592
1617
  if (s.args) {
1593
1618
  // View parameters
1594
- result += `(${renderArgs(s.args, '=>', env, null)})`;
1619
+ result += `(${renderArgs(s, '=>', env, null)})`;
1595
1620
  }
1596
1621
  if (s.where) {
1597
1622
  // 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
  /**
@@ -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 ) {