@sap/cds-compiler 3.6.2 → 3.8.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.
Files changed (89) hide show
  1. package/CHANGELOG.md +109 -1
  2. package/README.md +3 -0
  3. package/bin/cdsc.js +12 -5
  4. package/doc/CHANGELOG_ARCHIVE.md +6 -6
  5. package/doc/CHANGELOG_BETA.md +35 -2
  6. package/doc/CHANGELOG_DEPRECATED.md +2 -2
  7. package/doc/DeprecatedOptions_v2.md +1 -1
  8. package/doc/NameResolution.md +1 -1
  9. package/lib/api/main.js +63 -23
  10. package/lib/api/options.js +1 -0
  11. package/lib/api/validate.js +5 -0
  12. package/lib/base/dictionaries.js +15 -3
  13. package/lib/base/keywords.js +2 -0
  14. package/lib/base/message-registry.js +120 -34
  15. package/lib/base/messages.js +51 -27
  16. package/lib/base/model.js +4 -2
  17. package/lib/base/shuffle.js +2 -1
  18. package/lib/checks/arrayOfs.js +1 -1
  19. package/lib/checks/defaultValues.js +1 -1
  20. package/lib/checks/elements.js +29 -1
  21. package/lib/checks/{emptyOrOnlyVirtual.js → hasPersistedElements.js} +10 -6
  22. package/lib/checks/invalidTarget.js +1 -1
  23. package/lib/checks/nonexpandableStructured.js +1 -1
  24. package/lib/checks/onConditions.js +15 -9
  25. package/lib/checks/sql-snippets.js +2 -2
  26. package/lib/checks/types.js +5 -1
  27. package/lib/checks/validator.js +7 -3
  28. package/lib/compiler/assert-consistency.js +42 -26
  29. package/lib/compiler/base.js +50 -4
  30. package/lib/compiler/builtins.js +17 -8
  31. package/lib/compiler/checks.js +241 -246
  32. package/lib/compiler/define.js +113 -146
  33. package/lib/compiler/extend.js +889 -383
  34. package/lib/compiler/finalize-parse-cdl.js +5 -58
  35. package/lib/compiler/index.js +1 -1
  36. package/lib/compiler/kick-start.js +7 -8
  37. package/lib/compiler/populate.js +297 -293
  38. package/lib/compiler/propagator.js +27 -18
  39. package/lib/compiler/resolve.js +146 -463
  40. package/lib/compiler/shared.js +36 -79
  41. package/lib/compiler/tweak-assocs.js +30 -28
  42. package/lib/compiler/utils.js +31 -5
  43. package/lib/edm/annotations/genericTranslation.js +131 -59
  44. package/lib/edm/annotations/preprocessAnnotations.js +3 -0
  45. package/lib/edm/csn2edm.js +22 -5
  46. package/lib/edm/edm.js +6 -4
  47. package/lib/edm/edmAnnoPreprocessor.js +1 -0
  48. package/lib/edm/edmPreprocessor.js +42 -26
  49. package/lib/gen/Dictionary.json +38 -2
  50. package/lib/gen/language.checksum +1 -1
  51. package/lib/gen/language.interp +3 -1
  52. package/lib/gen/languageLexer.js +1 -1
  53. package/lib/gen/languageParser.js +4828 -4472
  54. package/lib/inspect/inspectPropagation.js +20 -34
  55. package/lib/json/from-csn.js +140 -44
  56. package/lib/json/to-csn.js +114 -122
  57. package/lib/language/errorStrategy.js +2 -0
  58. package/lib/language/genericAntlrParser.js +156 -36
  59. package/lib/language/language.g4 +100 -58
  60. package/lib/language/textUtils.js +13 -0
  61. package/lib/main.d.ts +43 -3
  62. package/lib/main.js +4 -2
  63. package/lib/model/csnRefs.js +15 -3
  64. package/lib/model/csnUtils.js +12 -74
  65. package/lib/model/revealInternalProperties.js +4 -2
  66. package/lib/modelCompare/compare.js +2 -1
  67. package/lib/optionProcessor.js +3 -0
  68. package/lib/render/manageConstraints.js +5 -2
  69. package/lib/render/toCdl.js +216 -104
  70. package/lib/render/toHdbcds.js +2 -9
  71. package/lib/render/toRename.js +14 -51
  72. package/lib/render/toSql.js +4 -3
  73. package/lib/render/utils/common.js +9 -5
  74. package/lib/transform/braceExpression.js +6 -0
  75. package/lib/transform/db/assertUnique.js +2 -1
  76. package/lib/transform/db/expansion.js +2 -0
  77. package/lib/transform/db/flattening.js +37 -36
  78. package/lib/transform/db/rewriteCalculatedElements.js +600 -0
  79. package/lib/transform/db/transformExists.js +4 -0
  80. package/lib/transform/db/views.js +40 -37
  81. package/lib/transform/forOdataNew.js +20 -15
  82. package/lib/transform/forRelationalDB.js +58 -41
  83. package/lib/transform/odata/typesExposure.js +50 -15
  84. package/lib/transform/parseExpr.js +16 -8
  85. package/lib/transform/transformUtilsNew.js +42 -14
  86. package/lib/transform/translateAssocsToJoins.js +60 -37
  87. package/lib/transform/universalCsn/coreComputed.js +15 -7
  88. package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
  89. package/package.json +2 -1
@@ -3,10 +3,11 @@
3
3
 
4
4
  const { makeMessageFunction } = require('../base/messages');
5
5
  const { checkCSNVersion } = require('../json/csnVersion');
6
- const { getNamespace, forEachDefinition } = require('../model/csnUtils');
6
+ const { forEachDefinition } = require('../model/csnUtils');
7
7
  const { optionProcessor } = require('../optionProcessor');
8
8
  const { isBetaEnabled } = require('../base/model');
9
9
  const { transformForRelationalDBWithCsn } = require('../transform/forRelationalDB');
10
+ const { getIdentifierUtils } = require('./utils/sql');
10
11
 
11
12
 
12
13
  /**
@@ -46,6 +47,9 @@ function toRename( inputCsn, options ) {
46
47
 
47
48
  // FIXME: Currently, 'toRename' implies transformation for HANA (transferring the options to forRelationalDB)
48
49
  const csn = transformForRelationalDBWithCsn(inputCsn, options, 'to.rename');
50
+ const hdbcdsOrQuotedIdentifiers = getIdentifierUtils(csn, options);
51
+ const plainIdentifiers = getIdentifierUtils(csn, { sqlDialect: 'hana', sqlMapping: 'plain' });
52
+
49
53
  // forRelationalDB looses empty contexts and services, add them again so that toRename can calculate the namespaces
50
54
  forEachDefinition(csn, (artifact, artifactName) => {
51
55
  if ((artifact.kind === 'context' || artifact.kind === 'service') && csn.definitions[artifactName] === undefined)
@@ -82,10 +86,12 @@ function toRename( inputCsn, options ) {
82
86
  function renameTableAndColumns( artifactName, art ) {
83
87
  let resultStr = '';
84
88
  if (art.kind === 'entity' && !art.query) {
85
- const beforeTableName = quoteSqlId(absoluteCdsName(artifactName));
86
- const afterTableName = plainSqlId(artifactName);
89
+ const beforeTableName = hdbcdsOrQuotedIdentifiers.renderArtifactName(artifactName);
90
+ const afterTableName = plainIdentifiers.renderArtifactName(artifactName);
87
91
 
88
- if (beforeTableName !== afterTableName)
92
+ if (beforeTableName.toUpperCase() === `"${afterTableName}"`)
93
+ resultStr += ` --EXEC 'RENAME TABLE ${beforeTableName} TO ${afterTableName}';\n`;
94
+ else if (beforeTableName !== afterTableName)
89
95
  resultStr += ` EXEC 'RENAME TABLE ${beforeTableName} TO ${afterTableName}';\n`;
90
96
 
91
97
 
@@ -93,13 +99,14 @@ function toRename( inputCsn, options ) {
93
99
  const e = art.elements[name];
94
100
  let str = '';
95
101
 
96
- const beforeColumnName = quoteSqlId(name);
97
- const afterColumnName = plainSqlId(name);
102
+ const beforeColumnName = hdbcdsOrQuotedIdentifiers.quoteSqlId(name);
103
+ const afterColumnName = plainIdentifiers.quoteSqlId(name);
98
104
 
99
105
  if (!e._ignore) {
100
106
  if (e.target)
101
107
  str = ` EXEC 'ALTER TABLE ${afterTableName} DROP ASSOCIATION ${beforeColumnName}';\n`;
102
-
108
+ else if (beforeColumnName.toUpperCase() === `"${afterColumnName}"` ) // Basically a no-op - render commented out
109
+ str = ` --EXEC 'RENAME COLUMN ${afterTableName}.${beforeColumnName} TO ${afterColumnName}';\n`;
103
110
  else if (beforeColumnName !== afterColumnName)
104
111
  str = ` EXEC 'RENAME COLUMN ${afterTableName}.${beforeColumnName} TO ${afterColumnName}';\n`;
105
112
  }
@@ -108,50 +115,6 @@ function toRename( inputCsn, options ) {
108
115
  }
109
116
  return resultStr;
110
117
  }
111
-
112
- /**
113
- * Return 'name' in the form of an absolute CDS name - for the 'hdbcds' naming convention,
114
- * this means converting '.' to '::' on the border between namespace and top-level artifact.
115
- * For all other naming conventions, this is a no-op.
116
- *
117
- * @param {string} name Name to absolutify
118
- * @returns {string} Absolute name
119
- */
120
- function absoluteCdsName( name ) {
121
- if (options.sqlMapping !== 'hdbcds')
122
- return name;
123
-
124
- const namespaceName = getNamespace(inputCsn, name);
125
- if (namespaceName)
126
- return `${namespaceName}::${name.substring(namespaceName.length + 1)}`;
127
-
128
- return name;
129
- }
130
-
131
- /**
132
- * Return 'name' with appropriate "-quotes, also replacing '::' by '.' if 'options.sqlMapping'
133
- * is 'quoted'
134
- *
135
- * @param {string} name Name to quote
136
- * @returns {string} Quoted string
137
- */
138
- function quoteSqlId( name ) {
139
- if (options.sqlMapping === 'quoted')
140
- name = name.replace(/::/g, '.');
141
-
142
- return `"${name.replace(/"/g, '""')}"`;
143
- }
144
-
145
- /**
146
- * Return 'name' with uppercasing and appropriate "-quotes, also replacing '::' and '.' by '_'
147
- * (to be used by 'plain' naming convention).
148
- *
149
- * @param {string} name Name to turn into a plain identifier
150
- * @returns {string} A plain SQL identifier
151
- */
152
- function plainSqlId( name ) {
153
- return `"${name.toUpperCase().replace(/(::|\.)/g, '_').replace(/"/g, '""')}"`;
154
- }
155
118
  }
156
119
 
157
120
  module.exports = {
@@ -93,10 +93,11 @@ function toSqlDdl( csn, options ) {
93
93
  return `CAST(${this.renderExpr(withoutCast(x))} AS ${typeRef})`;
94
94
  },
95
95
  val: renderExpressionLiteral,
96
- enum: (x) => {
97
- // TODO: Signal is not covered by tests + better location
96
+ enum(x) {
97
+ // TODO: better location; callers should set env.$path
98
98
  // FIXME: We can't do enums yet because they are not resolved (and we don't bother finding their value by hand)
99
- error(null, x.$location, 'Enum values are not yet supported for conversion to SQL');
99
+ const loc = x.$location || this.env?._artifact?.$location;
100
+ error('expr-unexpected-enum', [ loc, null ], 'Enum values are not yet supported for conversion to SQL');
100
101
  return '';
101
102
  },
102
103
  ref: renderExpressionRef,
@@ -54,10 +54,10 @@ function funcWithoutParen( node, dialect ) {
54
54
  }
55
55
 
56
56
  /**
57
- * Process already rendered expression parts by joining them nicely
58
- *
59
- * @param {Array} tokens Array of expression tokens
57
+ * Process already rendered expression parts by joining them nicely.
58
+ * For example, it adds spaces around operators such as `+`, but not around `.`.
60
59
  *
60
+ * @param {any[]} tokens Array of expression tokens
61
61
  * @returns {string} The rendered xpr
62
62
  */
63
63
  function beautifyExprArray( tokens ) {
@@ -65,8 +65,12 @@ function beautifyExprArray( tokens ) {
65
65
  let result = '';
66
66
  for (let i = 0; i < tokens.length; i++) {
67
67
  result += tokens[i];
68
- // No space after last token, after opening parentheses, before closing parentheses, before comma
69
- if (i !== tokens.length - 1 && tokens[i] !== '(' && ![ ')', ',' ].includes(tokens[i + 1]))
68
+ // No space after last token, after opening parentheses, before closing parentheses, before comma, before and after dot
69
+ if (i !== tokens.length - 1 &&
70
+ // current token
71
+ tokens[i] !== '(' && tokens[i] !== '.' &&
72
+ // next token
73
+ tokens[i + 1] !== ')' && tokens[i + 1] !== '.' && tokens[i + 1] !== ',')
70
74
  result += ' ';
71
75
  }
72
76
  return result;
@@ -1,3 +1,9 @@
1
+ // Currently unused, but may become useful again if HDBCDS -> HDBTABLE
2
+ // handover becomes more prominent. Historically used with the no longer
3
+ // existent option `--compatibility`.
4
+ // If necessary, more complex expressions could be parsed with parseExpr.js
5
+ // and then stringified with parentheses again.
6
+
1
7
  'use strict';
2
8
 
3
9
  function isAlreadyBraced(expression, start, end){
@@ -19,11 +19,12 @@ const { pathName } = require('../../compiler/utils');
19
19
  * @param {CSN.Options} options Options
20
20
  * @param {Function} error Message function for errors
21
21
  * @param {Function} info Message function for info
22
+ * @returns {Function} forEachDefinition callback
22
23
  */
23
24
  function processAssertUnique( csn, options, error, info ) {
24
25
  const { resolvePath, flattenPath } = getTransformers(csn, options);
25
26
 
26
- forEachDefinition(csn, handleAssertUnique);
27
+ return handleAssertUnique;
27
28
  /**
28
29
  * The detailed processing - see comment above for what is going on here
29
30
  *
@@ -361,6 +361,8 @@ function expandStructureReferences( csn, options, pathDelimiter, { error, info,
361
361
  expanded.push(Object.assign({}, current, { as: currentAlias.join(pathDelimiter) } ));
362
362
  }
363
363
  else { // preserve stuff like .cast for redirection
364
+ if (base[currentAlias[currentAlias.length - 1]]?.value)
365
+ error('query-unsupported-calc', col.$path, { '#': 'inside' });
364
366
  expanded.push(Object.assign({}, current, { ref: currentRef, as: currentAlias.join(pathDelimiter) } ));
365
367
  }
366
368
  }
@@ -293,7 +293,7 @@ function flattenElements( csn, options, pathDelimiter, error, iterateOptions = {
293
293
  // TODO: use $ignore - _ is for links
294
294
  element._ignore = true;
295
295
 
296
- const branches = getBranches(element, elementName);
296
+ const branches = getBranches(element, elementName, effectiveType, pathDelimiter);
297
297
  const flatElems = flattenStructuredElement(element, elementName, [], path.concat([ 'elements', elementName ]));
298
298
 
299
299
  for (const flatElemName in flatElems) {
@@ -308,7 +308,7 @@ function flattenElements( csn, options, pathDelimiter, error, iterateOptions = {
308
308
  const flatElement = flatElems[flatElemName];
309
309
 
310
310
  // Check if we have a valid notNull chain
311
- const branch = branches[flatElemName];
311
+ const branch = branches[flatElemName].steps;
312
312
  if (flatElement.notNull !== false && !branch.some(s => !s.notNull))
313
313
  flatElement.notNull = true;
314
314
 
@@ -357,49 +357,49 @@ function flattenElements( csn, options, pathDelimiter, error, iterateOptions = {
357
357
  return elements;
358
358
  }, Object.create(null));
359
359
  }
360
+ }
360
361
 
361
-
362
+ /**
363
+ * Get not just the leaves, but all branches of a structured element.
364
+ *
365
+ * @param {object} element Structured element
366
+ * @param {string} elementName Name of the structured element
367
+ * @param {Function} effectiveType
368
+ * @param {string} pathDelimiter
369
+ * @returns {object} Returns a dictionary, where the key is the flat name of the branch and the value is an array of element-steps.
370
+ */
371
+ function getBranches( element, elementName, effectiveType, pathDelimiter ) {
372
+ const branches = {};
373
+ const subbranchNames = [];
374
+ const subbranchElements = [];
375
+ walkElements(element, elementName);
362
376
  /**
363
- * Get not just the leafs, but all the branches of a structured element
377
+ * Walk the element chain
364
378
  *
365
- * @param {object} element Structured element
366
- * @param {string} elementName Name of the structured element
367
- * @returns {object} Returns a dictionary, where the key is the flat name of the branch and the value is an array of element-steps.
379
+ * @param {object} e
380
+ * @param {string} name
368
381
  */
369
- function getBranches( element, elementName ) {
370
- const branches = {};
371
- const subbranchNames = [];
372
- const subbranchElements = [];
373
- walkElements(element, elementName);
374
- /**
375
- * Walk the element chain
376
- *
377
- * @param {object} e
378
- * @param {string} name
379
- */
380
- function walkElements( e, name ) {
381
- if (isBuiltinType(e.type)) {
382
- branches[subbranchNames.concat(name).join(pathDelimiter)] = subbranchElements.concat(e);
382
+ function walkElements( e, name ) {
383
+ if (isBuiltinType(e.type)) {
384
+ branches[subbranchNames.concat(name).join(pathDelimiter)] = { steps: subbranchElements.concat(e), ref: subbranchNames.concat(name) };
385
+ }
386
+ else {
387
+ const subelements = e.elements || effectiveType(e).elements;
388
+ if (subelements) {
389
+ subbranchElements.push(e);
390
+ subbranchNames.push(name);
391
+ for (const [ subelementName, subelement ] of Object.entries(subelements))
392
+ walkElements(subelement, subelementName);
393
+
394
+ subbranchNames.pop();
395
+ subbranchElements.pop();
383
396
  }
384
397
  else {
385
- const eType = effectiveType(e);
386
- const subelements = e.elements || eType.elements;
387
- if (subelements) {
388
- subbranchElements.push(e);
389
- subbranchNames.push(name);
390
- for (const [ subelementName, subelement ] of Object.entries(subelements))
391
- walkElements(subelement, subelementName);
392
-
393
- subbranchNames.pop();
394
- subbranchElements.pop();
395
- }
396
- else {
397
- branches[subbranchNames.concat(name).join(pathDelimiter)] = subbranchElements.concat(e);
398
- }
398
+ branches[subbranchNames.concat(name).join(pathDelimiter)] = { steps: subbranchElements.concat(e), ref: subbranchNames.concat(name) };
399
399
  }
400
400
  }
401
- return branches;
402
401
  }
402
+ return branches;
403
403
  }
404
404
 
405
405
  /**
@@ -792,4 +792,5 @@ module.exports = {
792
792
  flattenElements,
793
793
  removeLeadingSelf,
794
794
  handleManagedAssociationsAndCreateForeignKeys,
795
+ getBranches,
795
796
  };