@sap/cds-compiler 2.15.4 → 3.0.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 (105) hide show
  1. package/CHANGELOG.md +33 -1590
  2. package/bin/cdsc.js +36 -33
  3. package/doc/CHANGELOG_ARCHIVE.md +1592 -0
  4. package/doc/CHANGELOG_BETA.md +3 -4
  5. package/doc/CHANGELOG_DEPRECATED.md +35 -1
  6. package/doc/{DeprecatedOptions.md → DeprecatedOptions_v2.md} +3 -1
  7. package/doc/Versioning.md +20 -1
  8. package/lib/api/.eslintrc.json +2 -2
  9. package/lib/api/main.js +220 -103
  10. package/lib/api/options.js +15 -85
  11. package/lib/api/validate.js +6 -10
  12. package/lib/base/keywords.js +216 -109
  13. package/lib/base/message-registry.js +60 -20
  14. package/lib/base/messages.js +65 -24
  15. package/lib/base/model.js +44 -2
  16. package/lib/checks/actionsFunctions.js +7 -5
  17. package/lib/checks/annotationsOData.js +1 -1
  18. package/lib/checks/cdsPersistence.js +1 -0
  19. package/lib/checks/elements.js +6 -6
  20. package/lib/checks/invalidTarget.js +1 -1
  21. package/lib/checks/nonexpandableStructured.js +1 -1
  22. package/lib/checks/queryNoDbArtifacts.js +2 -1
  23. package/lib/checks/selectItems.js +5 -1
  24. package/lib/checks/types.js +4 -2
  25. package/lib/checks/utils.js +2 -2
  26. package/lib/checks/validator.js +2 -1
  27. package/lib/compiler/assert-consistency.js +15 -10
  28. package/lib/compiler/builtins.js +87 -9
  29. package/lib/compiler/define.js +2 -2
  30. package/lib/compiler/extend.js +59 -11
  31. package/lib/compiler/finalize-parse-cdl.js +20 -9
  32. package/lib/compiler/index.js +25 -11
  33. package/lib/compiler/moduleLayers.js +7 -0
  34. package/lib/compiler/populate.js +13 -13
  35. package/lib/compiler/propagator.js +3 -3
  36. package/lib/compiler/resolve.js +193 -218
  37. package/lib/compiler/shared.js +47 -76
  38. package/lib/compiler/tweak-assocs.js +9 -10
  39. package/lib/compiler/utils.js +5 -0
  40. package/lib/edm/csn2edm.js +18 -21
  41. package/lib/edm/edmPreprocessor.js +25 -30
  42. package/lib/edm/edmUtils.js +10 -24
  43. package/lib/gen/language.checksum +1 -1
  44. package/lib/gen/language.interp +8 -30
  45. package/lib/gen/language.tokens +105 -114
  46. package/lib/gen/languageLexer.interp +1 -34
  47. package/lib/gen/languageLexer.js +889 -1007
  48. package/lib/gen/languageLexer.tokens +95 -106
  49. package/lib/gen/languageParser.js +20632 -22313
  50. package/lib/json/from-csn.js +56 -49
  51. package/lib/json/to-csn.js +10 -8
  52. package/lib/language/antlrParser.js +2 -2
  53. package/lib/language/docCommentParser.js +61 -38
  54. package/lib/language/errorStrategy.js +52 -40
  55. package/lib/language/genericAntlrParser.js +303 -229
  56. package/lib/language/language.g4 +573 -629
  57. package/lib/language/multiLineStringParser.js +14 -42
  58. package/lib/language/textUtils.js +44 -0
  59. package/lib/main.d.ts +27 -42
  60. package/lib/main.js +104 -81
  61. package/lib/model/csnRefs.js +1 -1
  62. package/lib/model/csnUtils.js +170 -283
  63. package/lib/model/revealInternalProperties.js +28 -8
  64. package/lib/model/sortViews.js +32 -31
  65. package/lib/optionProcessor.js +12 -21
  66. package/lib/render/.eslintrc.json +1 -1
  67. package/lib/render/DuplicateChecker.js +4 -7
  68. package/lib/render/manageConstraints.js +70 -2
  69. package/lib/render/toCdl.js +334 -339
  70. package/lib/render/toHdbcds.js +19 -15
  71. package/lib/render/toRename.js +44 -22
  72. package/lib/render/toSql.js +53 -51
  73. package/lib/render/utils/common.js +15 -1
  74. package/lib/render/utils/sql.js +20 -19
  75. package/lib/sql-identifier.js +6 -0
  76. package/lib/transform/db/.eslintrc.json +3 -2
  77. package/lib/transform/db/cdsPersistence.js +5 -15
  78. package/lib/transform/db/constraints.js +1 -1
  79. package/lib/transform/db/expansion.js +7 -6
  80. package/lib/transform/db/flattening.js +18 -19
  81. package/lib/transform/db/views.js +3 -3
  82. package/lib/transform/draft/.eslintrc.json +2 -2
  83. package/lib/transform/draft/db.js +6 -6
  84. package/lib/transform/draft/odata.js +6 -7
  85. package/lib/transform/forHanaNew.js +19 -22
  86. package/lib/transform/forOdataNew.js +10 -12
  87. package/lib/transform/localized.js +22 -16
  88. package/lib/transform/odata/toFinalBaseType.js +10 -10
  89. package/lib/transform/odata/typesExposure.js +3 -3
  90. package/lib/transform/odata/utils.js +1 -38
  91. package/lib/transform/transformUtilsNew.js +63 -77
  92. package/lib/transform/translateAssocsToJoins.js +2 -2
  93. package/lib/transform/universalCsn/.eslintrc.json +2 -2
  94. package/lib/transform/universalCsn/coreComputed.js +11 -6
  95. package/lib/transform/universalCsn/universalCsnEnricher.js +33 -5
  96. package/lib/utils/file.js +3 -3
  97. package/lib/utils/timetrace.js +20 -21
  98. package/package.json +35 -4
  99. package/doc/ApiMigration.md +0 -237
  100. package/doc/CommandLineMigration.md +0 -58
  101. package/doc/ErrorMessages.md +0 -175
  102. package/doc/FioriAnnotations.md +0 -94
  103. package/doc/ODataTransformation.md +0 -273
  104. package/lib/backends.js +0 -529
  105. package/lib/fix_antlr4-8_warning.js +0 -56
@@ -35,6 +35,16 @@ function getEscapedHanaComment(obj) {
35
35
  return getHanaComment(obj).replace(/\n/g, '\\n').replace(/'/g, "''");
36
36
  }
37
37
 
38
+ /**
39
+ * Render a string for HDBCDS, i.e. put it in quotes and escape single quotes.
40
+ *
41
+ * @param {string} str
42
+ * @returns {string}
43
+ */
44
+ function renderStringForHdbcds(str) {
45
+ return `'${str.replace(/'/g, '\'\'')}'`;
46
+ }
47
+
38
48
  /**
39
49
  * Render the CSN model 'model' to CDS source text. One source is created per
40
50
  * top-level artifact. Return a dictionary of top-level artifacts
@@ -731,7 +741,7 @@ function toHdbcdsSource(csn, options) {
731
741
 
732
742
  // Render 'null as <alias>' only for database and if element is virtual
733
743
  if (element && element.virtual) {
734
- if (isDeprecatedEnabled(options, 'renderVirtualElements'))
744
+ if (isDeprecatedEnabled(options, '_renderVirtualElements'))
735
745
  return `${result}${env.indent}null as ${formatIdentifier(leaf)}`;
736
746
  }
737
747
  else {
@@ -1223,7 +1233,7 @@ function toHdbcdsSource(csn, options) {
1223
1233
  case 'timestamp':
1224
1234
  return `${x.literal}'${x.val}'`;
1225
1235
  case 'string':
1226
- return `'${x.val.replace(/'/g, '\'\'')}'`;
1236
+ return renderStringForHdbcds(x.val);
1227
1237
  case 'object':
1228
1238
  if (x.val === null)
1229
1239
  return 'null';
@@ -1260,22 +1270,16 @@ function toHdbcdsSource(csn, options) {
1260
1270
  const magicReplacement = getVariableReplacement(x.ref, options);
1261
1271
  if (x.ref[0] === '$user') {
1262
1272
  if (magicReplacement !== null)
1263
- return `'${magicReplacement}'`;
1264
-
1265
- // Keep old way of solving this to remain backwards compatible
1266
- // FIXME: this is all not enough: we might need an explicit select item alias
1267
- if (x.ref[1] === 'id') {
1268
- if (options.magicVars && options.magicVars.user && (typeof options.magicVars.user === 'string' || options.magicVars.user instanceof String))
1269
- return `'${options.magicVars.user}'`;
1273
+ return renderStringForHdbcds(magicReplacement);
1270
1274
 
1271
- else if ((options.magicVars && options.magicVars.user && options.magicVars.user.id) && (typeof options.magicVars.user.id === 'string' || options.magicVars.user.id instanceof String))
1272
- return `'${options.magicVars.user.id}'`;
1275
+ // Note: The compiler already transforms $user into $user.id.
1273
1276
 
1277
+ // FIXME: this is all not enough: we might need an explicit select item alias (?)
1278
+ if (x.ref[1] === 'id')
1274
1279
  return 'SESSION_CONTEXT(\'APPLICATIONUSER\')';
1275
- }
1276
- else if (x.ref[1] === 'locale') {
1280
+
1281
+ else if (x.ref[1] === 'locale')
1277
1282
  return 'SESSION_CONTEXT(\'LOCALE\')';
1278
- }
1279
1283
  }
1280
1284
  else if (x.ref[0] === '$at') {
1281
1285
  if (x.ref[1] === 'from')
@@ -1285,7 +1289,7 @@ function toHdbcdsSource(csn, options) {
1285
1289
  return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-TO\'))';
1286
1290
  }
1287
1291
  else if (x.ref[0] === '$session' && magicReplacement !== null) {
1288
- return `'${magicReplacement}'`;
1292
+ return renderStringForHdbcds(magicReplacement);
1289
1293
  }
1290
1294
  }
1291
1295
  return `${(x.param || x.global) ? ':' : ''}${x.ref.map((step, index) => renderPathStep(step, index, x.ref, env, this.inline)).join('.')}`;
@@ -1,20 +1,24 @@
1
1
 
2
2
  'use strict';
3
3
 
4
- const { CompilationError, hasErrors } = require('../base/messages');
4
+ const { makeMessageFunction } = require('../base/messages');
5
5
  const { checkCSNVersion } = require('../json/csnVersion');
6
- const { getUtils } = require('../model/csnUtils');
6
+ const { getUtils, forEachDefinition } = require('../model/csnUtils');
7
+ const { optionProcessor } = require('../optionProcessor');
8
+ const { isBetaEnabled } = require('../base/model');
9
+ const { transformForHanaWithCsn } = require('../transform/forHanaNew');
7
10
 
8
- // FIXME: This is not up-to-date in regards to the changes to hdbcds/sql quoting etc.
9
11
 
10
12
  /**
13
+ * FIXME: Not yet supported, only in beta mode
14
+ * FIXME: This is not up-to-date in regards to the changes to hdbcds/sql quoting etc.
11
15
  *
12
- * Render the augmented CSN 'model' to SQL DDL statements renaming existing tables and their
16
+ * Generate SQL DDL rename statements for a migration, renaming existing tables and their
13
17
  * columns so that they match the result of "toHana" or "toSql" with the 'plain' option for names.
14
18
  * Expects the naming convention of the existing tables to be either 'quoted' or 'hdbcds' (default).
15
- * The following options control what is actually generated:
19
+ * The following options control what is actually generated (see help above):
16
20
  * options : {
17
- * toRename.names : existing names, either 'quoted' or 'hdbcds' (default)
21
+ * sqlMapping : existing names, either 'quoted' or 'hdbcds' (default)
18
22
  * }
19
23
  * Return a dictionary of top-level artifacts by their names, like this:
20
24
  * { "foo" : "RENAME TABLE \"foo\" ...",
@@ -22,34 +26,52 @@ const { getUtils } = require('../model/csnUtils');
22
26
  * }
23
27
  *
24
28
  * @todo clarify input parameters
25
- * @param {CSN.Model} csn Augmented csn?
29
+ * @param {CSN.Model} inputCsn CSN?
26
30
  * @param {CSN.Options} options Transformation options
27
31
  * @returns {object} A dictionary of name: rename statement
28
32
  */
29
- function toRenameDdl(csn, options) {
30
- // Merge options (arguments first, then model options and default)
31
- const result = Object.create(null);
33
+ function toRename(inputCsn, options) {
34
+ const { error, warning, throwWithError } = makeMessageFunction(inputCsn, options, 'to.rename');
35
+
36
+ // Merge options with defaults.
37
+ options = Object.assign({ sqlMapping: 'hdbcds' }, options);
38
+
39
+ // Verify options
40
+ optionProcessor.verifyOptions(options, 'toRename').forEach(complaint => warning(null, null, `${complaint}`));
41
+ checkCSNVersion(inputCsn, options);
42
+
43
+ // Requires beta mode
44
+ if (!isBetaEnabled(options, 'toRename'))
45
+ error(null, null, 'Generation of SQL rename statements is not supported yet (only in beta mode)');
32
46
 
33
- checkCSNVersion(csn, options);
47
+ // FIXME: Currently, 'toRename' implies transformation for HANA (transferring the options to forHana)
48
+ const csn = transformForHanaWithCsn(inputCsn, options, 'to.rename');
49
+ // forHanaCsn looses empty contexts and services, add them again so that toRename can calculate the namespaces
50
+ forEachDefinition(csn, (artifact, artifactName) => {
51
+ if ((artifact.kind === 'context' || artifact.kind === 'service') && csn.definitions[artifactName] === undefined)
52
+ csn.definitions[artifactName] = artifact;
53
+ });
54
+
55
+ const result = Object.create(null);
56
+ const { getNamespaceOfArtifact } = getUtils(csn, false);
34
57
 
35
- const { getNamespaceOfArtifact } = getUtils(csn);
36
58
  // Render each artifact on its own
37
59
  for (const artifactName in csn.definitions) {
38
60
  const sourceStr = renameTableAndColumns(artifactName, csn.definitions[artifactName]);
39
-
40
61
  if (sourceStr !== '')
41
62
  result[artifactName] = sourceStr;
42
63
  }
43
- // Throw exception in case of errors
44
- if (hasErrors(options.messages))
45
- throw new CompilationError(options.messages);
46
64
 
47
- return result;
65
+ throwWithError();
48
66
 
67
+ return {
68
+ rename: result,
69
+ options,
70
+ };
49
71
 
50
72
  /**
51
73
  * If 'art' is a non-view entity, generate SQL statements to rename the corresponding
52
- * table and its columns from the naming conventions given in 'options.toRename.name'
74
+ * table and its columns from the naming conventions given in 'options.sqlMapping'
53
75
  * (either 'quoted' or 'hdbcds') to 'plain'. In addition, drop any existing associations
54
76
  * from the columns (they would likely become invalid anyway).
55
77
  * Do not rename anything if the names are identical.
@@ -97,7 +119,7 @@ function toRenameDdl(csn, options) {
97
119
  * @returns {string} Absolute name
98
120
  */
99
121
  function absoluteCdsName(name) {
100
- if (options.toRename.names !== 'hdbcds')
122
+ if (options.sqlMapping !== 'hdbcds')
101
123
  return name;
102
124
 
103
125
  const namespaceName = getNamespaceOfArtifact(name);
@@ -108,14 +130,14 @@ function toRenameDdl(csn, options) {
108
130
  }
109
131
 
110
132
  /**
111
- * Return 'name' with appropriate "-quotes, also replacing '::' by '.' if 'options.toRename.names'
133
+ * Return 'name' with appropriate "-quotes, also replacing '::' by '.' if 'options.sqlMapping'
112
134
  * is 'quoted'
113
135
  *
114
136
  * @param {string} name Name to quote
115
137
  * @returns {string} Quoted string
116
138
  */
117
139
  function quoteSqlId(name) {
118
- if (options.toRename.names === 'quoted')
140
+ if (options.sqlMapping === 'quoted')
119
141
  name = name.replace(/::/g, '.');
120
142
 
121
143
  return `"${name.replace(/"/g, '""')}"`;
@@ -134,5 +156,5 @@ function toRenameDdl(csn, options) {
134
156
  }
135
157
 
136
158
  module.exports = {
137
- toRenameDdl,
159
+ toRename,
138
160
  };
@@ -97,8 +97,8 @@ function toSqlDdl(csn, options) {
97
97
  aliasOnly(x, _env) {
98
98
  return x.as;
99
99
  },
100
- windowFunction: (x, env) => renderWindowFunction(smartFuncId(prepareIdentifier(x.func), options.toSql.dialect), x, env),
101
- func: (x, env) => renderFunc(smartFuncId(prepareIdentifier(x.func), options.toSql.dialect), x, options.toSql.dialect, a => renderArgs(a, '=>', env, null)),
100
+ windowFunction: (x, env) => renderWindowFunction(smartFuncId(prepareIdentifier(x.func), options.sqlDialect), x, env),
101
+ func: (x, env) => renderFunc(smartFuncId(prepareIdentifier(x.func), options.sqlDialect), x, options.sqlDialect, a => renderArgs(a, '=>', env, null)),
102
102
  xpr(x, env) {
103
103
  if (this.nestedExpr && !x.cast)
104
104
  return `(${renderExpr(x.xpr, env, this.inline, true)})`;
@@ -238,7 +238,7 @@ function toSqlDdl(csn, options) {
238
238
  };
239
239
 
240
240
  // Registries for artifact and element names per CSN section
241
- const definitionsDuplicateChecker = new DuplicateChecker(options.toSql.names);
241
+ const definitionsDuplicateChecker = new DuplicateChecker(options.sqlMapping);
242
242
  const deletionsDuplicateChecker = new DuplicateChecker();
243
243
  const extensionsDuplicateChecker = new DuplicateChecker();
244
244
  const removeElementsDuplicateChecker = new DuplicateChecker();
@@ -262,7 +262,7 @@ function toSqlDdl(csn, options) {
262
262
  // Render each artifact extension
263
263
  // Only HANA SQL is currently supported.
264
264
  // Note that extensions may contain new elements referenced in migrations, thus should be compiled first.
265
- if (csn.extensions && (options.toSql.dialect === 'hana' || isBetaEnabled(options, 'sqlExtensions'))) {
265
+ if (csn.extensions && (options.sqlDialect === 'hana' || isBetaEnabled(options, 'sqlExtensions'))) {
266
266
  for (const extension of options && options.testMode ? sortCsn(csn.extensions) : csn.extensions) {
267
267
  if (extension.extend) {
268
268
  const artifactName = extension.extend;
@@ -275,7 +275,7 @@ function toSqlDdl(csn, options) {
275
275
 
276
276
  // Render each artifact change
277
277
  // Only HANA SQL is currently supported.
278
- if (csn.migrations && (options.toSql.dialect === 'hana' || isBetaEnabled(options, 'sqlExtensions'))) {
278
+ if (csn.migrations && (options.sqlDialect === 'hana' || isBetaEnabled(options, 'sqlExtensions'))) {
279
279
  for (const migration of options && options.testMode ? sortCsn(csn.migrations) : csn.migrations) {
280
280
  if (migration.migrate) {
281
281
  const artifactName = migration.migrate;
@@ -294,7 +294,7 @@ function toSqlDdl(csn, options) {
294
294
  // Throw exception in case of errors
295
295
  throwWithAnyError();
296
296
 
297
- // Transfer results from hdb-specific dictionaries into 'sql' dictionary in proper order if toSql.src === 'sql'
297
+ // Transfer results from hdb-specific dictionaries into 'sql' dictionary in proper order if src === 'sql'
298
298
  // (relying on the order of dictionaries above)
299
299
  // FIXME: Should consider inter-view dependencies, too
300
300
  const sql = Object.create(null);
@@ -305,10 +305,10 @@ function toSqlDdl(csn, options) {
305
305
  const { deletions, migrations: _, ...hdbKinds } = mainResultObj;
306
306
  for (const hdbKind of Object.keys(hdbKinds)) {
307
307
  for (const name in mainResultObj[hdbKind]) {
308
- if (options.toSql.src === 'sql') {
308
+ if (options.src === 'sql') {
309
309
  let sourceString = mainResultObj[hdbKind][name];
310
310
  // Hack: Other than in 'hdbtable' files, in HANA SQL COLUMN is not mandatory but default.
311
- if (options.toSql.dialect === 'hana' && hdbKind === 'hdbtable' && sourceString.startsWith('COLUMN '))
311
+ if (options.sqlDialect === 'hana' && hdbKind === 'hdbtable' && sourceString.startsWith('COLUMN '))
312
312
  sourceString = sourceString.slice('COLUMN '.length);
313
313
  sql[name] = `${options.testMode ? '' : sqlVersionLine}CREATE ${sourceString};`;
314
314
  }
@@ -316,7 +316,7 @@ function toSqlDdl(csn, options) {
316
316
  mainResultObj[hdbKind][name] = sqlVersionLine + mainResultObj[hdbKind][name];
317
317
  }
318
318
  }
319
- if (options.toSql.src === 'sql')
319
+ if (options.src === 'sql')
320
320
  delete mainResultObj[hdbKind];
321
321
  }
322
322
 
@@ -329,7 +329,7 @@ function toSqlDdl(csn, options) {
329
329
  mainResultObj.sql = sql;
330
330
  }
331
331
 
332
- if (options.toSql.src === 'sql')
332
+ if (options.src === 'sql')
333
333
  mainResultObj.sql = sql;
334
334
 
335
335
  for (const name in deletions)
@@ -420,7 +420,7 @@ function toSqlDdl(csn, options) {
420
420
  * @returns {string} Artifact name
421
421
  */
422
422
  function renderArtifactName(artifactName) {
423
- return quoteSqlId(getResultingName(csn, options.toSql.names, artifactName));
423
+ return quoteSqlId(getResultingName(csn, options.sqlMapping, artifactName));
424
424
  }
425
425
 
426
426
  // Render an artifact migration into the appropriate dictionary of 'resultObj'.
@@ -573,7 +573,7 @@ function toSqlDdl(csn, options) {
573
573
  const { front, back } = getSqlSnippets(options, art);
574
574
  let result = front;
575
575
  // Only HANA has row/column tables
576
- if (options.toSql.dialect === 'hana') {
576
+ if (options.sqlDialect === 'hana') {
577
577
  if (hanaTc && hanaTc.storeType) {
578
578
  // Explicitly specified
579
579
  result += `${art.technicalConfig.hana.storeType.toUpperCase()} `;
@@ -604,7 +604,7 @@ function toSqlDdl(csn, options) {
604
604
  // for `to.sql` w/ dialect `hana` the constraints will be part of the
605
605
  const constraintsAsAlter = !options.constraintsInCreateTable && options.src === 'sql' && options.sqlDialect === 'hana';
606
606
  if ( !constraintsAsAlter && art.$tableConstraints && art.$tableConstraints.referential) {
607
- const renderReferentialConstraintsAsHdbconstraint = options.toSql.src === 'hdi';
607
+ const renderReferentialConstraintsAsHdbconstraint = options.src === 'hdi';
608
608
  const referentialConstraints = {};
609
609
  Object.entries(art.$tableConstraints.referential)
610
610
  .forEach(([ fileName, referentialConstraint ]) => {
@@ -627,7 +627,7 @@ function toSqlDdl(csn, options) {
627
627
  const uniqueConstraints = art.$tableConstraints && art.$tableConstraints.unique;
628
628
  for (const cn in uniqueConstraints) {
629
629
  const c = uniqueConstraints[cn];
630
- if (options.toSql.src === 'hdi') {
630
+ if (options.src === 'hdi') {
631
631
  resultObj.hdbindex[`${artifactName}.${cn}`]
632
632
  = `UNIQUE INVERTED INDEX ${renderArtifactName(`${artifactName}_${cn}`)} ON ${tableName} (${c.map(cpath => quoteSqlId(cpath.ref[0])).join(', ')})`;
633
633
  }
@@ -637,20 +637,20 @@ function toSqlDdl(csn, options) {
637
637
  }
638
638
  result += `${env.indent}\n)`;
639
639
 
640
- if (options.toSql.dialect === 'hana')
640
+ if (options.sqlDialect === 'hana')
641
641
  result += renderTechnicalConfiguration(art.technicalConfig, childEnv);
642
642
 
643
643
 
644
644
  const associations = Object.keys(art.elements).map(name => renderAssociationElement(name, art.elements[name], childEnv))
645
645
  .filter(s => s !== '')
646
646
  .join(',\n');
647
- if (associations !== '' && options.toSql.dialect === 'hana') {
647
+ if (associations !== '' && options.sqlDialect === 'hana') {
648
648
  result += `${env.indent} WITH ASSOCIATIONS (\n${associations}\n`;
649
649
  result += `${env.indent})`;
650
650
  }
651
651
  // Only HANA has indices
652
652
  // FIXME: Really? We should provide a DB-agnostic way to specify that
653
- if (options.toSql.dialect === 'hana')
653
+ if (options.sqlDialect === 'hana')
654
654
  renderIndexesInto(art.technicalConfig && art.technicalConfig.hana.indexes, artifactName, resultObj, env);
655
655
 
656
656
  if (options.sqlDialect === 'hana' && hasHanaComment(art, options))
@@ -758,7 +758,7 @@ function toSqlDdl(csn, options) {
758
758
  result += ` DEFAULT ${renderExpr(elm.default, env)}`;
759
759
 
760
760
  // Only HANA has fuzzy indices
761
- if (fzindex && options.toSql.dialect === 'hana')
761
+ if (fzindex && options.sqlDialect === 'hana')
762
762
  result += ` ${renderExpr(fzindex, env)}`;
763
763
 
764
764
  // (table) elements can only have a @sql.append
@@ -906,7 +906,7 @@ function toSqlDdl(csn, options) {
906
906
  throw new ModelError(`Unexpected form of index: "${index}"`);
907
907
 
908
908
  let indexName = renderArtifactName(`${artifactName}.${index[i + 1].ref}`);
909
- if (options.toSql.names === 'plain')
909
+ if (options.sqlMapping === 'plain')
910
910
  indexName = indexName.replace(/(\.|::)/g, '_');
911
911
 
912
912
  const result = index.slice(0, i + 1); // CREATE UNIQUE INDEX
@@ -945,7 +945,7 @@ function toSqlDdl(csn, options) {
945
945
  let result = `${renderViewSource(artifactName, source.args[0], env)}`;
946
946
  for (let i = 1; i < source.args.length; i++) {
947
947
  result = `(${result} ${source.join.toUpperCase()} `;
948
- if (options.toSql.dialect === 'hana')
948
+ if (options.sqlDialect === 'hana')
949
949
  result += renderJoinCardinality(source.cardinality);
950
950
  result += `JOIN ${renderViewSource(artifactName, source.args[i], env)}`;
951
951
  if (source.on)
@@ -1008,7 +1008,7 @@ function toSqlDdl(csn, options) {
1008
1008
  let result = renderAbsolutePath(path, ':', env);
1009
1009
 
1010
1010
  // Take care of aliases
1011
- const implicitAlias = path.ref.length === 0 ? getLastPartOf(getResultingName(csn, options.toSql.names, path.ref[0])) : getLastPartOfRef(path.ref);
1011
+ const implicitAlias = path.ref.length === 0 ? getLastPartOf(getResultingName(csn, options.sqlMapping, path.ref[0])) : getLastPartOfRef(path.ref);
1012
1012
  if (path.as) {
1013
1013
  // Source had an alias - render it
1014
1014
  result += ` AS ${quoteSqlId(path.as)}`;
@@ -1126,7 +1126,7 @@ function toSqlDdl(csn, options) {
1126
1126
  let result = '';
1127
1127
  const leaf = col.as || col.ref && col.ref[col.ref.length - 1] || col.func;
1128
1128
  if (leaf && elements[leaf] && elements[leaf].virtual) {
1129
- if (isDeprecatedEnabled(options, 'renderVirtualElements'))
1129
+ if (isDeprecatedEnabled(options, '_renderVirtualElements'))
1130
1130
  // render a virtual column 'null as <alias>'
1131
1131
  result += `${env.indent}NULL AS ${quoteSqlId(col.as || leaf)}`;
1132
1132
  }
@@ -1165,7 +1165,7 @@ function toSqlDdl(csn, options) {
1165
1165
  .map(name => renderAssociationElement(name, art.elements[name], childEnv))
1166
1166
  .filter(s => s !== '')
1167
1167
  .join(',\n');
1168
- if (associations !== '' && options.toSql.dialect === 'hana') {
1168
+ if (associations !== '' && options.sqlDialect === 'hana') {
1169
1169
  result += `${env.indent}\nWITH ASSOCIATIONS (\n${associations}\n`;
1170
1170
  result += `${env.indent})`;
1171
1171
  }
@@ -1197,7 +1197,7 @@ function toSqlDdl(csn, options) {
1197
1197
  // this would be an incompatible change, as non-uppercased, quoted identifiers
1198
1198
  // are rejected by the HANA compiler.
1199
1199
  let pIdentifier;
1200
- if (options.toSql.names === 'quoted' || options.toSql.names === 'hdbcds')
1200
+ if (options.sqlMapping === 'quoted' || options.sqlMapping === 'hdbcds')
1201
1201
  pIdentifier = prepareIdentifier(pn);
1202
1202
  else
1203
1203
  pIdentifier = quoteSqlId(pn);
@@ -1386,7 +1386,7 @@ function toSqlDdl(csn, options) {
1386
1386
  'cds.LocalTime': 'cds.Time',
1387
1387
  };
1388
1388
  const tName = forHanaRenamesToEarly[typeName] || typeName;
1389
- const types = cdsToSqlTypes[options.toSql.dialect];
1389
+ const types = cdsToSqlTypes[options.sqlDialect];
1390
1390
  return types && types[tName] || cdsToSqlTypes.standard[tName] || 'CHAR';
1391
1391
  }
1392
1392
 
@@ -1426,7 +1426,7 @@ function toSqlDdl(csn, options) {
1426
1426
 
1427
1427
  if (elm.srid !== undefined) {
1428
1428
  // SAP HANA Geometry types translate into CHAR in plain/sqlite (give them the default length of 2000)
1429
- if (options.toSql.dialect !== 'hana')
1429
+ if (options.sqlDialect !== 'hana')
1430
1430
  params.push(2000);
1431
1431
  else
1432
1432
  params.push(elm.srid);
@@ -1448,7 +1448,7 @@ function toSqlDdl(csn, options) {
1448
1448
  case 'date':
1449
1449
  case 'time':
1450
1450
  case 'timestamp':
1451
- if (options.toSql.dialect === 'sqlite') {
1451
+ if (options.sqlDialect === 'sqlite') {
1452
1452
  // simple string literal '2017-11-02'
1453
1453
  return `'${x.val}'`;
1454
1454
  }
@@ -1474,7 +1474,7 @@ function toSqlDdl(csn, options) {
1474
1474
 
1475
1475
  if (x.ref[0] === '$user') {
1476
1476
  if (magicReplacement !== null)
1477
- return `'${magicReplacement}'`;
1477
+ return renderStringForSql(magicReplacement, options.sqlDialect);
1478
1478
 
1479
1479
  const result = render$user();
1480
1480
  // Invalid second path step doesn't cause a return
@@ -1488,7 +1488,18 @@ function toSqlDdl(csn, options) {
1488
1488
  return result;
1489
1489
  }
1490
1490
  else if (x.ref[0] === '$session' && magicReplacement !== null) {
1491
- return `'${magicReplacement}'`;
1491
+ return renderStringForSql(magicReplacement, options.sqlDialect);
1492
+ }
1493
+ else if (x.ref[0] === '$now') { // TODO: Can there be cases where $now is followed by something?
1494
+ switch (options.sqlDialect) {
1495
+ case 'sqlite':
1496
+ case 'hana':
1497
+ return 'CURRENT_TIMESTAMP';
1498
+ case 'postgres':
1499
+ return 'current_timestamp';
1500
+ default:
1501
+ return quoteSqlId(x.ref[0]);
1502
+ }
1492
1503
  }
1493
1504
  }
1494
1505
  // FIXME: We currently cannot distinguish whether '$parameters' was quoted or not - we
@@ -1508,27 +1519,17 @@ function toSqlDdl(csn, options) {
1508
1519
  * @returns {string|null} Null in case of an invalid second path step
1509
1520
  */
1510
1521
  function render$user() {
1511
- // FIXME: this is all not enough: we might need an explicit select item alias
1522
+ // FIXME: this is all not enough: we might need an explicit select item alias (?)
1512
1523
  if (x.ref[1] === 'id') {
1513
- // Keep the old-style for compatibilty with magicVars.id - instead of magicVars.user.id...
1514
- if (options.toSql.user && typeof options.toSql.user === 'string' || options.toSql.user instanceof String)
1515
- return `'${options.toSql.user}'`;
1516
-
1517
- else if ((options.toSql.user && options.toSql.user.id) && (typeof options.toSql.user.id === 'string' || options.toSql.user.id instanceof String))
1518
- return `'${options.toSql.user.id}'`;
1519
-
1520
- if (options.toSql.dialect === 'sqlite' || options.toSql.dialect === 'plain') {
1521
- warning(null, null, 'The "$user" variable is not supported. Use option "variableReplacements" to specify a value for "$user.id"');
1522
- return '\'$user.id\'';
1523
- }
1524
- return 'SESSION_CONTEXT(\'APPLICATIONUSER\')';
1524
+ if (options.sqlDialect === 'hana')
1525
+ return 'SESSION_CONTEXT(\'APPLICATIONUSER\')';
1526
+ warning(null, null, 'The "$user" variable is not supported. Use option "variableReplacements" to specify a value for "$user.id"');
1527
+ return '\'$user.id\'';
1525
1528
  }
1526
1529
  else if (x.ref[1] === 'locale') {
1527
- if (options.toSql.dialect === 'sqlite' || options.toSql.dialect === 'plain') {
1528
- return (options.toSql.user && options.toSql.user.locale)
1529
- ? `'${options.toSql.user && options.toSql.user.locale}'` : '\'en\'';
1530
- }
1531
- return 'SESSION_CONTEXT(\'LOCALE\')';
1530
+ if (options.sqlDialect === 'hana')
1531
+ return 'SESSION_CONTEXT(\'LOCALE\')';
1532
+ return '\'en\''; // default language
1532
1533
  }
1533
1534
  // Basically: Second path step was invalid, do nothing - should not happen, see 'unknownMagic.js'
1534
1535
  return null;
@@ -1547,13 +1548,14 @@ function toSqlDdl(csn, options) {
1547
1548
  */
1548
1549
  function render$at() {
1549
1550
  if (x.ref[1] === 'from') {
1550
- switch (options.toSql.dialect) {
1551
+ switch (options.sqlDialect) {
1551
1552
  case 'sqlite': {
1552
1553
  const dateFromFormat = '%Y-%m-%dT%H:%M:%S.000Z';
1553
1554
  return `strftime('${dateFromFormat}', 'now')`;
1554
1555
  }
1555
1556
  case 'hana':
1556
1557
  return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-FROM\'))';
1558
+ case 'postgres':
1557
1559
  case 'plain':
1558
1560
  return 'current_timestamp';
1559
1561
  default:
@@ -1562,7 +1564,7 @@ function toSqlDdl(csn, options) {
1562
1564
  }
1563
1565
 
1564
1566
  if (x.ref[1] === 'to') {
1565
- switch (options.toSql.dialect) {
1567
+ switch (options.sqlDialect) {
1566
1568
  case 'sqlite': {
1567
1569
  // + 1ms compared to $at.from
1568
1570
  const dateToFormat = '%Y-%m-%dT%H:%M:%S.001Z';
@@ -1570,6 +1572,7 @@ function toSqlDdl(csn, options) {
1570
1572
  }
1571
1573
  case 'hana':
1572
1574
  return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-TO\'))';
1575
+ case 'postgres':
1573
1576
  case 'plain':
1574
1577
  return 'current_timestamp';
1575
1578
  default:
@@ -1591,7 +1594,6 @@ function toSqlDdl(csn, options) {
1591
1594
  if (typeof (s) === 'string') {
1592
1595
  // TODO: When is this actually executed and not handled already in renderExpr?
1593
1596
  const magicForHana = {
1594
- $now: 'CURRENT_TIMESTAMP',
1595
1597
  '$user.id': 'SESSION_CONTEXT(\'APPLICATIONUSER\')',
1596
1598
  '$user.locale': 'SESSION_CONTEXT(\'LOCALE\')',
1597
1599
  };
@@ -1599,7 +1601,7 @@ function toSqlDdl(csn, options) {
1599
1601
  if (idx === 0) {
1600
1602
  // HANA-specific translation of '$now' and '$user'
1601
1603
  // FIXME: this is all not enough: we might need an explicit select item alias
1602
- if (magicForHana[s])
1604
+ if (options.sqlDialect === 'hana' && magicForHana[s])
1603
1605
  return magicForHana[s];
1604
1606
 
1605
1607
  // Ignore initial $projection and initial $self
@@ -279,6 +279,18 @@ const cdsToSqlTypes = {
279
279
  'cds.hana.BINARY': 'BINARY',
280
280
  'cds.hana.SMALLDECIMAL': 'DECIMAL',
281
281
  },
282
+ postgres: {
283
+ // TODO: Type mapping for binary types is not correct, yet.
284
+ // We can't use text types for binary on PostgreSQL due to NUL!
285
+ 'cds.String': 'VARCHAR',
286
+ 'cds.LargeString': 'text',
287
+ 'cds.hana.CLOB': 'text',
288
+ 'cds.LargeBinary': 'bytea',
289
+ 'cds.Binary': 'VARCHAR',
290
+ 'cds.hana.BINARY': 'VARCHAR',
291
+ 'cds.Double': 'double precision',
292
+ 'cds.hana.TINYINT': 'INTEGER',
293
+ },
282
294
  };
283
295
 
284
296
  /**
@@ -375,7 +387,7 @@ function getSqlSnippets(options, obj) {
375
387
  * A function used to render a certain part of an expression object
376
388
  *
377
389
  * @callback renderPart
378
- * @param {object||array} expression
390
+ * @param {object|array} expression
379
391
  * @param {CdlRenderEnvironment} env
380
392
  * @this {{inline: Boolean, nestedExpr: Boolean}}
381
393
  * @returns {string}
@@ -397,6 +409,8 @@ function getSqlSnippets(options, obj) {
397
409
  * @property {renderPart} xpr
398
410
  * @property {renderPart} SELECT
399
411
  * @property {renderPart} SET
412
+ * @property {boolean} [inline]
413
+ * @property {boolean} [nestedExpr]
400
414
  */
401
415
 
402
416
  /**