@sap/cds-compiler 3.6.2 → 3.7.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 (68) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +3 -0
  3. package/bin/cdsc.js +9 -5
  4. package/doc/CHANGELOG_BETA.md +20 -2
  5. package/doc/CHANGELOG_DEPRECATED.md +2 -2
  6. package/lib/api/main.js +2 -1
  7. package/lib/base/dictionaries.js +10 -0
  8. package/lib/base/message-registry.js +56 -12
  9. package/lib/base/messages.js +39 -20
  10. package/lib/base/model.js +1 -0
  11. package/lib/base/shuffle.js +2 -1
  12. package/lib/checks/elements.js +29 -1
  13. package/lib/checks/{emptyOrOnlyVirtual.js → hasPersistedElements.js} +9 -5
  14. package/lib/checks/nonexpandableStructured.js +1 -1
  15. package/lib/checks/onConditions.js +8 -5
  16. package/lib/checks/types.js +6 -1
  17. package/lib/checks/validator.js +7 -3
  18. package/lib/compiler/assert-consistency.js +20 -23
  19. package/lib/compiler/base.js +1 -2
  20. package/lib/compiler/builtins.js +2 -2
  21. package/lib/compiler/checks.js +237 -242
  22. package/lib/compiler/define.js +63 -75
  23. package/lib/compiler/extend.js +325 -22
  24. package/lib/compiler/finalize-parse-cdl.js +1 -55
  25. package/lib/compiler/kick-start.js +6 -7
  26. package/lib/compiler/populate.js +284 -288
  27. package/lib/compiler/propagator.js +15 -13
  28. package/lib/compiler/resolve.js +136 -306
  29. package/lib/compiler/shared.js +42 -44
  30. package/lib/compiler/tweak-assocs.js +29 -27
  31. package/lib/compiler/utils.js +29 -3
  32. package/lib/edm/annotations/genericTranslation.js +7 -13
  33. package/lib/edm/annotations/preprocessAnnotations.js +3 -0
  34. package/lib/edm/csn2edm.js +0 -4
  35. package/lib/edm/edm.js +6 -4
  36. package/lib/edm/edmAnnoPreprocessor.js +1 -0
  37. package/lib/edm/edmPreprocessor.js +1 -5
  38. package/lib/gen/Dictionary.json +34 -2
  39. package/lib/gen/language.checksum +1 -1
  40. package/lib/gen/language.interp +1 -1
  41. package/lib/gen/languageParser.js +2429 -2401
  42. package/lib/inspect/inspectPropagation.js +2 -0
  43. package/lib/json/from-csn.js +87 -41
  44. package/lib/json/to-csn.js +47 -16
  45. package/lib/language/errorStrategy.js +1 -0
  46. package/lib/language/genericAntlrParser.js +109 -28
  47. package/lib/language/language.g4 +20 -4
  48. package/lib/model/csnRefs.js +1 -1
  49. package/lib/model/csnUtils.js +1 -0
  50. package/lib/model/revealInternalProperties.js +1 -2
  51. package/lib/modelCompare/compare.js +2 -1
  52. package/lib/render/manageConstraints.js +5 -2
  53. package/lib/render/toCdl.js +20 -7
  54. package/lib/render/toHdbcds.js +2 -8
  55. package/lib/render/toSql.js +4 -3
  56. package/lib/render/utils/common.js +9 -5
  57. package/lib/transform/db/assertUnique.js +2 -1
  58. package/lib/transform/db/expansion.js +2 -0
  59. package/lib/transform/db/flattening.js +37 -36
  60. package/lib/transform/db/rewriteCalculatedElements.js +559 -0
  61. package/lib/transform/db/transformExists.js +4 -0
  62. package/lib/transform/db/views.js +40 -37
  63. package/lib/transform/forRelationalDB.js +38 -28
  64. package/lib/transform/odata/typesExposure.js +50 -15
  65. package/lib/transform/parseExpr.js +14 -8
  66. package/lib/transform/transformUtilsNew.js +6 -5
  67. package/lib/transform/translateAssocsToJoins.js +49 -33
  68. package/package.json +1 -1
@@ -1050,6 +1050,7 @@ annotateArtifact[ art, outer ] locals[ name = {}, elemName = {} ]
1050
1050
  ')' { this.finalizeDictOrArray( $art.params ); }
1051
1051
  { this.checkExtensionDict( $art.params ); }
1052
1052
  (
1053
+ // TODO: set proper $art.returns
1053
1054
  RETURNS { $art['$'+'syntax'] = 'returns'; }
1054
1055
  '{' { $art.elements = this.createDict(); }
1055
1056
  annotateElement[ $art ]*
@@ -1060,6 +1061,7 @@ annotateArtifact[ art, outer ] locals[ name = {}, elemName = {} ]
1060
1061
  requiredSemi
1061
1062
  )
1062
1063
  |
1064
+ // TODO: set proper $art.returns
1063
1065
  RETURNS { $art['$'+'syntax'] = 'returns'; }
1064
1066
  '{' { $art.elements = this.createDict(); }
1065
1067
  annotateElement[ $art ]*
@@ -1111,7 +1113,9 @@ annotateAction [ outer ] locals [ art = {} ]
1111
1113
  { this.checkExtensionDict( $art.params ); }
1112
1114
  )?
1113
1115
  (
1114
- RETURNS '{' { $art.elements = this.createDict(); }
1116
+ // TODO: set proper $art.returns
1117
+ RETURNS { $art['$'+'syntax'] = 'returns'; }
1118
+ '{' { $art.elements = this.createDict(); }
1115
1119
  annotateElement[ $art ]*
1116
1120
  '}' { this.finalizeDictOrArray( $art.elements ); }
1117
1121
  { this.checkExtensionDict( $art.elements ); }
@@ -1511,6 +1515,7 @@ typeRefOptArgs[ art ]
1511
1515
  ;
1512
1516
 
1513
1517
  typeRefArgs[ art ]
1518
+ @after { this.checkTypeArgs($art); }
1514
1519
  :
1515
1520
  paren='(' { $art['$'+'typeArgs'] = this.createArray(); }
1516
1521
  (
@@ -2181,10 +2186,9 @@ expressionTerm returns [ expr ] locals [ args = [] ]
2181
2186
  END { this.pushXprToken( $args ); }
2182
2187
  |
2183
2188
  ne=NEW { this.pushXprToken( $args ); } // token rewrite for NEW
2184
- // please note: there will be no compiler-supported code completion after NEW
2185
- { $expr = { op: this.valueWithTokenLocation( 'new', $ne ), args: [] };
2186
- this.error( 'syntax-unsupported-new', $ne, { keyword: $ne.text }, '$(KEYWORD) is not supported' ); }
2187
2189
  nqp=valuePath[ 'ref', null ]
2190
+ { $args.push( this.valuePathAst( $nqp.qp ) ); }
2191
+ { this.fixNewKeywordPlacement( $args ); }
2188
2192
  |
2189
2193
  vp=valuePath[ 'ref', null ] { $args.push( this.valuePathAst( $vp.qp ) ); }
2190
2194
  { this.setLocalTokenIfBefore( 'OVER', 'OVER', /^\($/i ); }
@@ -2752,6 +2756,18 @@ annoValueBase[ assignment ] locals [ seenEllipsis = false ]
2752
2756
  |
2753
2757
  ( plus='+' | min='-' ) num=Number
2754
2758
  { Object.assign( $assignment, this.numberLiteral( $num, $plus||$min ) ); }
2759
+ |
2760
+ '('
2761
+ cond=condition // 'condition' is also used in 'expression' inside '()'.
2762
+ // TODO: (1,2,3) not supported, yet, only ((1,2,3)); we could support it via:
2763
+ // (',' condition)*
2764
+ { $assignment.value = $cond.cond;
2765
+ if (!this.isBetaEnabled(this.options, 'annotationExpressions')) {
2766
+ this.error( 'syntax-unsupported-expression', [ $cond.cond.location ], {},
2767
+ 'Expressions in annotation values are not supported' );
2768
+ }
2769
+ }
2770
+ ')'
2755
2771
  ;
2756
2772
 
2757
2773
  flattenedValue[ assignment ] locals[ val = { name: {} } ]
@@ -657,7 +657,7 @@ function csnRefs( csn, universalReady ) {
657
657
  if (!art) {
658
658
  const { env } = links[i - 1];
659
659
  const loc = env.name && env.name.$location || env.$location;
660
- throw new ModelError( `Path item ${ i }=${ pathId( path[i] ) } on ${ locationString( loc ) } refers to nothing` );
660
+ throw new ModelError( `Path item ${ i }=${ pathId( path[i] ) } refers to nothing; in ${ locationString( loc ) }; path=${ JSON.stringify(path) }` );
661
661
  }
662
662
  links[i].art = art;
663
663
  }
@@ -423,6 +423,7 @@ function getUtils( model, universalReady ) {
423
423
  // - r({ref: ['\\', '\\', '\\\\'] }) != r({ref: ['\\', '\\\\', '\\'] })
424
424
  // - r({ref: ['\\', '\\', '\\\\'] }) != r({ref: ['\\', '\\\\2:\\\\'] })
425
425
  const resolvedKey = (typeof type === 'object')
426
+ // eslint-disable-next-line sonarjs/no-nested-template-literals
426
427
  ? `ref[${ type.ref.length }]:${ type.ref.map((val, i) => `${ i }:${ val }`).join('\\') }`
427
428
  : `type:${ type }`;
428
429
 
@@ -170,8 +170,7 @@ function revealInternalProperties( model, nameOrPath ) {
170
170
  if (!Array.isArray(deps))
171
171
  return primOrString( deps );
172
172
  return deps
173
- .filter( d => d.location )
174
- .map( d => artifactIdentifier( d.art ) );
173
+ .map( d => `${ d.location ? '' : '-' }${ artifactIdentifier( d.art ) }`);
175
174
  }
176
175
 
177
176
  function layerExtends( dict ) {
@@ -120,6 +120,7 @@ function getArtifactComparator(otherModel, options, addedEntities, deletedEntiti
120
120
  // Arguments are interchanged in this case: `artifact` from beforeModel and `otherArtifact` from afterModel.
121
121
  if (isPersisted && !isPersistedOther) {
122
122
  deletedEntities[name] = artifact;
123
+ // eslint-disable-next-line sonarjs/no-duplicated-branches
123
124
  } else if(isPersistedAsView(artifact) && isPersistedOther) { // view turned into table - need to render a drop for the view
124
125
  deletedEntities[name] = artifact;
125
126
  }
@@ -254,7 +255,7 @@ module.exports = {
254
255
  /**
255
256
  * A ModelDiff encapsulates the changes between two models ("before" and "after"). It contains information
256
257
  * about changes to .elements and removed artifacts.
257
- *
258
+ *
258
259
  * @typedef {object} ModelDiff
259
260
  * @property {CSN.Definitions} definitions The artifacts present in the "after" model
260
261
  * @property {CSN.Definitions} deletions The artifacts present in the "before", but not in the "after"
@@ -23,12 +23,15 @@ const { sortCsn } = require('../json/to-csn');
23
23
  * @param {CSN.Options} options
24
24
  */
25
25
  function alterConstraintsWithCsn( csn, options ) {
26
- const { error } = makeMessageFunction(csn, options, 'manageConstraints');
26
+ const { error, warning } = makeMessageFunction(csn, options, 'manageConstraints');
27
27
 
28
28
  const {
29
- drop, alter, src, violations,
29
+ drop, alter, src, violations, sqlDialect,
30
30
  } = options || {};
31
31
 
32
+ if (!sqlDialect || sqlDialect === 'h2' || sqlDialect === 'plain')
33
+ warning(null, null, { prop: sqlDialect || 'plain' }, 'Referential Constraints are not available for sql dialect $(PROP)');
34
+
32
35
  if (drop && alter)
33
36
  error(null, null, 'Option “--drop” can\'t be combined with “--alter”');
34
37
 
@@ -1380,17 +1380,28 @@ function csnToCdl( csn, options ) {
1380
1380
  }
1381
1381
 
1382
1382
  /**
1383
- * Render an annotation value (somewhat like a simplified expression, with slightly different
1384
- * representation)
1383
+ * Render an annotation value, which is either
1384
+ * - a normal expressions
1385
+ * - a somewhat simplified expression, with slightly different representation
1385
1386
  *
1386
- * @param {any} x
1387
+ * @param {any} annoValue
1387
1388
  * @param {CdlRenderEnvironment} env
1388
1389
  */
1389
- function renderAnnotationValue( x, env ) {
1390
+ function renderAnnotationValue( annoValue, env ) {
1391
+ const isXpr = annoValue?.['='] !== undefined && (Object.keys(annoValue).length > 1) &&
1392
+ isBetaEnabled(options, 'annotationExpressions');
1393
+ if (isXpr) {
1394
+ const xpr = exprRenderer.renderExpr(annoValue, env);
1395
+ return `( ${xpr} )`;
1396
+ }
1397
+ return renderSimpleAnnotationValue(annoValue, env);
1398
+ }
1399
+
1400
+ function renderSimpleAnnotationValue( x, env ) {
1390
1401
  if (Array.isArray(x)) {
1391
1402
  // Render array parts as values. Spaces required if last array value is
1392
1403
  // a delimited identifier.
1393
- return `[ ${x.map(item => renderAnnotationValue(item, env)).join(', ')} ]`;
1404
+ return `[ ${x.map(item => renderSimpleAnnotationValue(item, env)).join(', ')} ]`;
1394
1405
  }
1395
1406
  else if (typeof x === 'object' && x !== null) {
1396
1407
  // Enum symbol
@@ -1405,7 +1416,7 @@ function csnToCdl( csn, options ) {
1405
1416
  else if (x['...']) {
1406
1417
  if (x['...'] === true)
1407
1418
  return '...';
1408
- return `... up to ${renderAnnotationValue(x['...'], env)}`;
1419
+ return `... up to ${renderSimpleAnnotationValue(x['...'], env)}`;
1409
1420
  }
1410
1421
 
1411
1422
  // Struct value (can currently only occur within an array)
@@ -1413,7 +1424,7 @@ function csnToCdl( csn, options ) {
1413
1424
  // struct if there are more and use nicer indentation.
1414
1425
  const keys = Object.keys(x);
1415
1426
  const childEnv = keys.length <= 1 ? env : increaseIndent(env);
1416
- const values = keys.map(key => `${quoteAnnotationPathIfRequired(key)}: ${renderAnnotationValue(x[key], childEnv)}`);
1427
+ const values = keys.map(key => `${quoteAnnotationPathIfRequired(key)}: ${renderSimpleAnnotationValue(x[key], childEnv)}`);
1417
1428
  if (values.length <= 1)
1418
1429
  return `{ ${values.join(', ')} }`;
1419
1430
  const valueList = values.join(`,\n${childEnv.indent}`);
@@ -1814,6 +1825,8 @@ function csnToCdl( csn, options ) {
1814
1825
  if (keywords.cdl_functions.includes(x.func.toUpperCase()) && !x.args)
1815
1826
  return x.func;
1816
1827
  const name = smartFunctionId(x.func);
1828
+ if (!x.args) // e.g. for methods without arguments, `args` is not set at all.
1829
+ return `${name}`;
1817
1830
  return `${name}(${renderArguments( x, '=>', this.env )})`;
1818
1831
  },
1819
1832
  xpr(x) {
@@ -1684,25 +1684,19 @@ function toHdbcdsSource( csn, options ) {
1684
1684
  }
1685
1685
 
1686
1686
  /**
1687
- * Return an id 'id' with appropriate "-quotes
1687
+ * Return an id 'id' with appropriate double-quotes
1688
1688
  *
1689
1689
  * @param {string} id Identifier to quote
1690
1690
  * @returns {string} Properly quoted identifier
1691
1691
  */
1692
1692
  function quoteId( id ) {
1693
- // Should only ever be called for real IDs (i.e. no dots inside)
1694
- if (id.indexOf('.') !== -1)
1695
- throw new ModelError(`HDBCDS: Tried to quote id with dot: ${id}`);
1696
-
1697
-
1698
1693
  switch (options.sqlMapping) {
1699
1694
  case 'plain':
1700
1695
  return smartId(id, 'hdbcds');
1701
1696
  case 'quoted':
1702
1697
  case 'hdbcds':
1703
- return delimitedId(id, 'hdbcds');
1704
1698
  default:
1705
- return null;
1699
+ return delimitedId(id, 'hdbcds');
1706
1700
  }
1707
1701
  }
1708
1702
 
@@ -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;
@@ -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
  };