@sap/cds-compiler 2.11.4 → 2.12.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 (80) hide show
  1. package/CHANGELOG.md +58 -1
  2. package/bin/cds_update_identifiers.js +7 -7
  3. package/bin/cdsc.js +9 -10
  4. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  5. package/doc/CHANGELOG_BETA.md +12 -0
  6. package/lib/api/main.js +2 -0
  7. package/lib/api/options.js +2 -2
  8. package/lib/base/message-registry.js +31 -2
  9. package/lib/base/model.js +1 -0
  10. package/lib/base/optionProcessorHelper.js +97 -69
  11. package/lib/checks/.eslintrc.json +2 -0
  12. package/lib/checks/actionsFunctions.js +2 -1
  13. package/lib/checks/foreignKeys.js +4 -4
  14. package/lib/checks/managedInType.js +4 -4
  15. package/lib/checks/queryNoDbArtifacts.js +1 -3
  16. package/lib/checks/sql-snippets.js +93 -0
  17. package/lib/checks/validator.js +8 -0
  18. package/lib/compiler/assert-consistency.js +5 -3
  19. package/lib/compiler/base.js +0 -1
  20. package/lib/compiler/checks.js +32 -9
  21. package/lib/compiler/definer.js +25 -4
  22. package/lib/compiler/index.js +1 -1
  23. package/lib/compiler/propagator.js +3 -2
  24. package/lib/compiler/resolver.js +97 -6
  25. package/lib/compiler/shared.js +12 -1
  26. package/lib/compiler/utils.js +7 -0
  27. package/lib/edm/annotations/genericTranslation.js +34 -17
  28. package/lib/edm/annotations/preprocessAnnotations.js +1 -1
  29. package/lib/edm/csn2edm.js +1 -1
  30. package/lib/edm/edm.js +8 -8
  31. package/lib/edm/edmPreprocessor.js +30 -23
  32. package/lib/edm/edmUtils.js +11 -12
  33. package/lib/gen/Dictionary.json +82 -40
  34. package/lib/gen/language.checksum +1 -1
  35. package/lib/gen/language.interp +3 -1
  36. package/lib/gen/language.tokens +15 -14
  37. package/lib/gen/languageLexer.interp +9 -1
  38. package/lib/gen/languageLexer.js +830 -779
  39. package/lib/gen/languageLexer.tokens +7 -6
  40. package/lib/gen/languageParser.js +2401 -2282
  41. package/lib/json/from-csn.js +47 -16
  42. package/lib/json/to-csn.js +17 -5
  43. package/lib/language/antlrParser.js +3 -3
  44. package/lib/language/docCommentParser.js +1 -1
  45. package/lib/language/genericAntlrParser.js +68 -51
  46. package/lib/language/language.g4 +128 -74
  47. package/lib/language/multiLineStringParser.js +536 -0
  48. package/lib/main.d.ts +5 -3
  49. package/lib/main.js +3 -2
  50. package/lib/model/csnRefs.js +116 -68
  51. package/lib/model/csnUtils.js +40 -48
  52. package/lib/model/enrichCsn.js +30 -14
  53. package/lib/optionProcessor.js +3 -3
  54. package/lib/render/DuplicateChecker.js +1 -1
  55. package/lib/render/manageConstraints.js +1 -1
  56. package/lib/render/toCdl.js +193 -79
  57. package/lib/render/toHdbcds.js +179 -95
  58. package/lib/render/toRename.js +7 -10
  59. package/lib/render/toSql.js +57 -40
  60. package/lib/render/utils/common.js +24 -5
  61. package/lib/render/utils/sql.js +6 -4
  62. package/lib/transform/braceExpression.js +4 -2
  63. package/lib/transform/db/associations.js +389 -0
  64. package/lib/transform/db/cdsPersistence.js +150 -0
  65. package/lib/transform/db/constraints.js +6 -4
  66. package/lib/transform/db/draft.js +3 -2
  67. package/lib/transform/db/expansion.js +4 -5
  68. package/lib/transform/db/flattening.js +5 -6
  69. package/lib/transform/db/temporal.js +236 -0
  70. package/lib/transform/db/transformExists.js +36 -23
  71. package/lib/transform/forHanaNew.js +35 -626
  72. package/lib/transform/forOdataNew.js +5 -4
  73. package/lib/transform/localized.js +3 -14
  74. package/lib/transform/odata/generateForeignKeyElements.js +2 -2
  75. package/lib/transform/transformUtilsNew.js +13 -13
  76. package/lib/transform/translateAssocsToJoins.js +8 -8
  77. package/lib/transform/universalCsnEnricher.js +217 -47
  78. package/lib/utils/file.js +2 -1
  79. package/lib/utils/timetrace.js +8 -2
  80. package/package.json +1 -1
@@ -8,6 +8,7 @@ const {
8
8
  } = require('../model/csnUtils');
9
9
  const {
10
10
  renderFunc, beautifyExprArray, cdsToSqlTypes, getHanaComment, hasHanaComment,
11
+ getSqlSnippets,
11
12
  } = require('./utils/common');
12
13
  const {
13
14
  renderReferentialConstraint, getIdentifierUtils,
@@ -186,10 +187,10 @@ function toSqlDdl(csn, options) {
186
187
  checkCSNVersion(csn, options);
187
188
 
188
189
  // The final result in hdb-kind-specific form, without leading CREATE, without trailing newlines
189
- // (note that the order here is relevant for transmission into 'resultObj.sql' below and that
190
+ // (note that the order here is relevant for transmission into 'mainResultObj.sql' below and that
190
191
  // the attribute names must be the HDI plugin names for --src hdi)
191
192
  // The result object may have a `sql` dictionary for `toSql`.
192
- const resultObj = {
193
+ const mainResultObj = {
193
194
  hdbtabletype: Object.create(null),
194
195
  hdbtable: Object.create(null),
195
196
  hdbindex: Object.create(null),
@@ -215,12 +216,12 @@ function toSqlDdl(csn, options) {
215
216
  // Current indentation string
216
217
  indent: '',
217
218
  };
218
- renderArtifactInto(artifactName, artifact, resultObj, env);
219
+ renderArtifactInto(artifactName, artifact, mainResultObj, env);
219
220
  });
220
221
 
221
222
  // Render each deleted artifact
222
223
  for (const artifactName in csn.deletions)
223
- renderArtifactDeletionInto(artifactName, csn.deletions[artifactName], resultObj);
224
+ renderArtifactDeletionInto(artifactName, csn.deletions[artifactName], mainResultObj);
224
225
 
225
226
  // Render each artifact extension
226
227
  // Only HANA SQL is currently supported.
@@ -231,7 +232,7 @@ function toSqlDdl(csn, options) {
231
232
  const artifactName = extension.extend;
232
233
  const _artifact = csn.definitions[artifactName];
233
234
  const env = { indent: '', _artifact };
234
- renderArtifactExtensionInto(artifactName, _artifact, extension, resultObj, env);
235
+ renderArtifactExtensionInto(artifactName, _artifact, extension, mainResultObj, env);
235
236
  }
236
237
  }
237
238
  }
@@ -244,7 +245,7 @@ function toSqlDdl(csn, options) {
244
245
  const artifactName = migration.migrate;
245
246
  const _artifact = csn.definitions[artifactName];
246
247
  const env = { indent: '', _artifact };
247
- renderArtifactMigrationInto(artifactName, migration, resultObj, env);
248
+ renderArtifactMigrationInto(artifactName, migration, mainResultObj, env);
248
249
  }
249
250
  }
250
251
  }
@@ -265,22 +266,22 @@ function toSqlDdl(csn, options) {
265
266
 
266
267
  // Handle hdbKinds separately from alterTable case
267
268
  // eslint-disable-next-line no-unused-vars
268
- const { deletions, migrations, ...hdbKinds } = resultObj;
269
+ const { deletions, migrations: _, ...hdbKinds } = mainResultObj;
269
270
  for (const hdbKind of Object.keys(hdbKinds)) {
270
- for (const name in resultObj[hdbKind]) {
271
+ for (const name in mainResultObj[hdbKind]) {
271
272
  if (options.toSql.src === 'sql') {
272
- let sourceString = resultObj[hdbKind][name];
273
+ let sourceString = mainResultObj[hdbKind][name];
273
274
  // Hack: Other than in 'hdbtable' files, in HANA SQL COLUMN is not mandatory but default.
274
275
  if (options.toSql.dialect === 'hana' && hdbKind === 'hdbtable' && sourceString.startsWith('COLUMN '))
275
276
  sourceString = sourceString.slice('COLUMN '.length);
276
277
  sql[name] = `${options.testMode ? '' : sqlVersionLine}CREATE ${sourceString};`;
277
278
  }
278
279
  else if (!options.testMode) {
279
- resultObj[hdbKind][name] = sqlVersionLine + resultObj[hdbKind][name];
280
+ mainResultObj[hdbKind][name] = sqlVersionLine + mainResultObj[hdbKind][name];
280
281
  }
281
282
  }
282
283
  if (options.toSql.src === 'sql')
283
- delete resultObj[hdbKind];
284
+ delete mainResultObj[hdbKind];
284
285
  }
285
286
 
286
287
  // add `ALTER TABLE ADD CONSTRAINT` statements if requested
@@ -289,18 +290,18 @@ function toSqlDdl(csn, options) {
289
290
 
290
291
  for ( const constraintName of Object.keys(alterStmts))
291
292
  sql[constraintName] = `${options.testMode ? '' : sqlVersionLine}${alterStmts[constraintName]}`;
292
- resultObj.sql = sql;
293
+ mainResultObj.sql = sql;
293
294
  }
294
295
 
295
296
  if (options.toSql.src === 'sql')
296
- resultObj.sql = sql;
297
+ mainResultObj.sql = sql;
297
298
 
298
299
  for (const name in deletions)
299
300
  deletions[name] = `${options.testMode ? '' : sqlVersionLine}${deletions[name]}`;
300
301
 
301
302
 
302
303
  timetrace.stop();
303
- return resultObj;
304
+ return mainResultObj;
304
305
 
305
306
  /**
306
307
  * Render an artifact into the appropriate dictionary of 'resultObj'.
@@ -491,7 +492,9 @@ function toSqlDdl(csn, options) {
491
492
  env._artifact = art;
492
493
  const childEnv = increaseIndent(env);
493
494
  const hanaTc = art.technicalConfig && art.technicalConfig.hana;
494
- let result = '';
495
+ // tables can have @sql.prepend and @sql.append
496
+ const { front, back } = getSqlSnippets(options, art);
497
+ let result = front;
495
498
  // Only HANA has row/column tables
496
499
  if (options.toSql.dialect === 'hana') {
497
500
  if (hanaTc && hanaTc.storeType) {
@@ -575,6 +578,9 @@ function toSqlDdl(csn, options) {
575
578
  if (options.toSql.dialect === 'hana' && hasHanaComment(art, options))
576
579
  result += ` COMMENT '${getHanaComment(art)}'`;
577
580
 
581
+ if (back)
582
+ result += back;
583
+
578
584
  resultObj.hdbtable[artifactName] = result;
579
585
  }
580
586
 
@@ -610,12 +616,12 @@ function toSqlDdl(csn, options) {
610
616
  if (!(artifactName in resultObj.migrations))
611
617
  resultObj.migrations[artifactName] = [];
612
618
 
613
- const migrations = sqlArray.map(sql => ({ drop, sql }));
619
+ const migrations = sqlArray.map(migrationSql => ({ drop, sql: migrationSql }));
614
620
  resultObj.migrations[artifactName].push(...migrations);
615
621
  }
616
622
 
617
- function addDeletion(resultObj, artifactName, sql) {
618
- resultObj.deletions[artifactName] = sql;
623
+ function addDeletion(resultObj, artifactName, deletionSql) {
624
+ resultObj.deletions[artifactName] = deletionSql;
619
625
  }
620
626
 
621
627
  /**
@@ -672,9 +678,15 @@ function toSqlDdl(csn, options) {
672
678
  if (fzindex && options.toSql.dialect === 'hana')
673
679
  result += ` ${renderExpr(fzindex, env)}`;
674
680
 
675
- if (options.toSql.dialect === 'hana' && hasHanaComment(elm, options, env._artifact))
681
+ if (options.toSql.dialect === 'hana' && hasHanaComment(elm, options))
676
682
  result += ` COMMENT '${getHanaComment(elm)}'`;
677
683
 
684
+ // (table) elements can only have a @sql.append
685
+ const { back } = getSqlSnippets(options, elm);
686
+
687
+ if (back !== '')
688
+ result += back;
689
+
678
690
  return result;
679
691
  }
680
692
 
@@ -984,7 +996,7 @@ function toSqlDdl(csn, options) {
984
996
  * @param {object} node with `args` to render
985
997
  * @param {string} sep Separator between args
986
998
  * @param {object} env Render environment
987
- * @param {string} syntax Some magic A2J paramter - for calcview parameter rendering
999
+ * @param {string|null} syntax Some magic A2J parameter - for calcview parameter rendering
988
1000
  * @returns {string} Rendered arguments
989
1001
  * @throws Throws if args is not an array or object.
990
1002
  */
@@ -1007,14 +1019,13 @@ function toSqlDdl(csn, options) {
1007
1019
  * Render the given argument/parameter correctly.
1008
1020
  *
1009
1021
  * @param {string} arg Argument to render
1010
- * @param {string} syntax Some magic A2J paramter - for calcview parameter rendering
1022
+ * @param {string|null} parameterSyntax Some magic A2J parameter - for calcview parameter rendering
1011
1023
  * @returns {string} Rendered argument
1012
1024
  */
1013
- function decorateParameter(arg, syntax) {
1014
- if (syntax === 'calcview')
1025
+ function decorateParameter(arg, parameterSyntax) {
1026
+ if (parameterSyntax === 'calcview')
1015
1027
  return `PLACEHOLDER."$$${arg}$$"`;
1016
1028
 
1017
-
1018
1029
  return quoteSqlId(arg);
1019
1030
  }
1020
1031
  }
@@ -1075,6 +1086,11 @@ function toSqlDdl(csn, options) {
1075
1086
  result += `${env.indent})`;
1076
1087
  }
1077
1088
 
1089
+ // views can only have a @sql.append
1090
+ const { back } = getSqlSnippets(options, art);
1091
+ if (back)
1092
+ result += back;
1093
+
1078
1094
  return result;
1079
1095
  }
1080
1096
 
@@ -1082,7 +1098,7 @@ function toSqlDdl(csn, options) {
1082
1098
  * Render the parameter definition of a view if any. Return the parameters in parentheses, or an empty string
1083
1099
  *
1084
1100
  * @param {string} artifactName Name of the view
1085
- * @param {Array} params Array of parameters
1101
+ * @param {Object} params Dictionary of parameters
1086
1102
  * @returns {string} Rendered parameters
1087
1103
  */
1088
1104
  function renderParameterDefinitions(artifactName, params) {
@@ -1338,27 +1354,27 @@ function toSqlDdl(csn, options) {
1338
1354
  * (no trailing LF, don't indent if inline)
1339
1355
  *
1340
1356
  * @todo Reuse this with toCdl
1341
- * @param {Array|object|string} x Expression to render
1357
+ * @param {Array|object|string} expr Expression to render
1342
1358
  * @param {object} env Render environment
1343
1359
  * @param {boolean} inline Wether to render the expression inline
1344
1360
  * @param {boolean} nestedExpr Wether to treat the expression as nested
1345
1361
  * @returns {string} Rendered expression
1346
1362
  */
1347
- function renderExpr(x, env, inline = true, nestedExpr = false) {
1363
+ function renderExpr(expr, env, inline = true, nestedExpr = false) {
1348
1364
  // Compound expression
1349
- if (Array.isArray(x)) {
1350
- const tokens = x.map(item => renderExpr(item, env, inline, nestedExpr));
1365
+ if (Array.isArray(expr)) {
1366
+ const tokens = expr.map(item => renderExpr(item, env, inline, nestedExpr));
1351
1367
  return beautifyExprArray(tokens);
1352
1368
  }
1353
- else if (typeof x === 'object' && x !== null) {
1354
- if (nestedExpr && x.cast && x.cast.type)
1355
- return renderExplicitTypeCast(renderExprObject());
1356
- return renderExprObject();
1369
+ else if (typeof expr === 'object' && expr !== null) {
1370
+ if (nestedExpr && expr.cast && expr.cast.type)
1371
+ return renderExplicitTypeCast(expr, renderExprObject(expr));
1372
+ return renderExprObject(expr);
1357
1373
  }
1358
1374
  // Not a literal value but part of an operator, function etc - just leave as it is
1359
1375
  // FIXME: For the sake of simplicity, we should get away from all this uppercasing in toSql
1360
1376
 
1361
- return String(x).toUpperCase();
1377
+ return String(expr).toUpperCase();
1362
1378
 
1363
1379
 
1364
1380
  /**
@@ -1366,7 +1382,7 @@ function toSqlDdl(csn, options) {
1366
1382
  *
1367
1383
  * @returns {string} String representation of the expression
1368
1384
  */
1369
- function renderExprObject() {
1385
+ function renderExprObject(x) {
1370
1386
  if (x.list) {
1371
1387
  return `(${x.list.map(item => renderExpr(item)).join(', ')})`;
1372
1388
  }
@@ -1412,10 +1428,10 @@ function toSqlDdl(csn, options) {
1412
1428
  throw new Error(`Unknown expression: ${JSON.stringify(x)}`);
1413
1429
  }
1414
1430
 
1415
- function renderWindowFunction(funcName, node, env) {
1416
- const suffix = node.xpr.shift(); // OVER
1417
- let r = `${funcName}(${renderArgs(node, '=>', env, null)})`;
1418
- r += ` ${suffix} (${renderExpr(node.xpr, env)})`;
1431
+ function renderWindowFunction(funcName, node, fctEnv) {
1432
+ const suffix = node.xpr[0]; // OVER
1433
+ let r = `${funcName}(${renderArgs(node, '=>', fctEnv, null)})`;
1434
+ r += ` ${suffix} (${renderExpr(node.xpr.slice(1), fctEnv)})`; // do not pass suffix in renderExpr
1419
1435
  return r;
1420
1436
  }
1421
1437
 
@@ -1570,10 +1586,11 @@ function toSqlDdl(csn, options) {
1570
1586
  /**
1571
1587
  * Renders an explicit `cast()` inside an 'xpr'.
1572
1588
  *
1589
+ * @param {object} x Expression with cast
1573
1590
  * @param {string} value Value to cast
1574
1591
  * @returns {string} CAST statement
1575
1592
  */
1576
- function renderExplicitTypeCast(value) {
1593
+ function renderExplicitTypeCast(x, value) {
1577
1594
  const typeRef = renderBuiltinType(x.cast.type) + renderTypeParameters(x.cast);
1578
1595
  return `CAST(${value} AS ${typeRef})`;
1579
1596
  }
@@ -19,6 +19,7 @@ const {
19
19
  } = require('../../model/csnUtils');
20
20
 
21
21
  const { implicitAs } = require('../../model/csnRefs');
22
+ const { isBetaEnabled } = require('../../base/model');
22
23
 
23
24
 
24
25
  /**
@@ -205,12 +206,11 @@ function addMissingChildContexts(csn, artifactName, killList) {
205
206
  addPossibleGaps(name.slice(artifactName.length + 1).split('.'), artifactName);
206
207
  }
207
208
 
208
- function addPossibleGaps(possibleGaps, artifactName) {
209
- let possibleGap = artifactName;
209
+ function addPossibleGaps(possibleGaps, gapArtifactName) {
210
210
  for (const gap of possibleGaps) {
211
- possibleGap += `.${gap}`;
212
- if (!csn.definitions[possibleGap]) {
213
- const contextName = possibleGap;
211
+ gapArtifactName += `.${gap}`;
212
+ if (!csn.definitions[gapArtifactName]) {
213
+ const contextName = gapArtifactName;
214
214
  csn.definitions[contextName] = {
215
215
  kind: 'context',
216
216
  };
@@ -355,6 +355,24 @@ function getHanaComment(obj) {
355
355
  return obj.doc.split('\n\n')[0].trim().replace(/'/g, "''");
356
356
  }
357
357
 
358
+ /**
359
+ * Get the @sql.prepend/append if set - already add a space after/before.
360
+ * If no value is set, use '';
361
+ *
362
+ * @param {CSN.Options} options
363
+ * @param {object} obj
364
+ * @returns {object} object with .front and .back
365
+ */
366
+ function getSqlSnippets(options, obj) {
367
+ if (isBetaEnabled(options, 'sqlSnippets')) {
368
+ const front = obj['@sql.prepend'] ? `${obj['@sql.prepend']} ` : '';
369
+ const back = obj['@sql.append'] ? ` ${obj['@sql.append']}` : '';
370
+
371
+ return { front, back };
372
+ }
373
+ return { front: '', back: '' };
374
+ }
375
+
358
376
  /**
359
377
  * @typedef CdlRenderEnvironment Rendering environment used throughout the render process.
360
378
  *
@@ -383,4 +401,5 @@ module.exports = {
383
401
  getHanaComment,
384
402
  findElement,
385
403
  funcWithoutParen,
404
+ getSqlSnippets,
386
405
  };
@@ -13,11 +13,11 @@ const { smartId, delimitedId } = require('../../sql-identifier');
13
13
  * @param {boolean} toUpperCase Wether to uppercase the identifier
14
14
  * @param {CSN.Model} csn CSN
15
15
  * @param {CSN.Options} options is needed for the naming mode and the sql dialect
16
- * @param {boolean} alterConstraint whether the constraint should be rendered as part of an ALTER TABLE statement
16
+ * @param {boolean} [alterConstraint=false] whether the constraint should be rendered as part of an ALTER TABLE statement
17
17
  *
18
18
  * @returns {string} SQL statement which can be used to create the referential constraint on the db.
19
19
  */
20
- function renderReferentialConstraint(constraint, indent, toUpperCase, csn, options, alterConstraint) {
20
+ function renderReferentialConstraint(constraint, indent, toUpperCase, csn, options, alterConstraint = false) {
21
21
  let quoteId;
22
22
  // for to.hana we can't utilize the sql identifier utils
23
23
  if (options.transformation === 'hdbcds') {
@@ -51,14 +51,16 @@ function renderReferentialConstraint(constraint, indent, toUpperCase, csn, optio
51
51
  if (!alterConstraint) {
52
52
  result += `${indent}FOREIGN KEY(${constraint.foreignKey.map(quoteId).join(', ')})\n`;
53
53
  result += `${indent}REFERENCES ${quoteId(getResultingName(csn, names, constraint.parentTable))}(${constraint.parentKey.map(quoteId).join(', ')})\n`;
54
+ const onDeleteRemark = constraint.onDeleteRemark ? ` -- ${constraint.onDeleteRemark}` : '';
55
+
54
56
  // omit 'RESTRICT' action for ON UPDATE / ON DELETE, because it interferes with deferred constraint check
55
57
  if (forSqlite) {
56
58
  if (constraint.onDelete === 'CASCADE' )
57
- result += `${indent}ON DELETE ${constraint.onDelete}${constraint.onDeleteRemark ? ` -- ${constraint.onDeleteRemark}` : ''}\n`;
59
+ result += `${indent}ON DELETE ${constraint.onDelete}${onDeleteRemark}\n`;
58
60
  }
59
61
  else {
60
62
  result += `${indent}ON UPDATE RESTRICT\n`;
61
- result += `${indent}ON DELETE ${constraint.onDelete}${constraint.onDeleteRemark ? ` -- ${constraint.onDeleteRemark}` : ''}\n`;
63
+ result += `${indent}ON DELETE ${constraint.onDelete}${onDeleteRemark}\n`;
62
64
  }
63
65
  }
64
66
  // constraint enforcement / validation must be switched off using sqlite pragma statement
@@ -1,8 +1,10 @@
1
1
  'use strict';
2
2
 
3
3
  function isAlreadyBraced(expression, start, end){
4
- const isBraced = start - 1 > -1 && end + 1 < expression.length && expression[start-1] === '(' && expression[end+1] === ')';
5
- return isBraced;
4
+ return start - 1 > -1 &&
5
+ end + 1 < expression.length &&
6
+ expression[start-1] === '(' &&
7
+ expression[end+1] === ')';
6
8
  }
7
9
 
8
10
  function binarycomparison(expression, token, index){