@sap/cds-compiler 2.15.2 → 3.0.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 (111) hide show
  1. package/CHANGELOG.md +66 -1590
  2. package/bin/cdsc.js +42 -46
  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 +312 -143
  10. package/lib/api/options.js +15 -85
  11. package/lib/api/validate.js +6 -10
  12. package/lib/base/keywords.js +280 -110
  13. package/lib/base/message-registry.js +80 -24
  14. package/lib/base/messages.js +103 -52
  15. package/lib/base/model.js +44 -2
  16. package/lib/base/optionProcessorHelper.js +53 -21
  17. package/lib/checks/actionsFunctions.js +7 -5
  18. package/lib/checks/annotationsOData.js +1 -1
  19. package/lib/checks/cdsPersistence.js +1 -0
  20. package/lib/checks/elements.js +6 -6
  21. package/lib/checks/invalidTarget.js +1 -1
  22. package/lib/checks/nonexpandableStructured.js +1 -1
  23. package/lib/checks/queryNoDbArtifacts.js +2 -1
  24. package/lib/checks/selectItems.js +5 -1
  25. package/lib/checks/types.js +4 -2
  26. package/lib/checks/utils.js +2 -2
  27. package/lib/checks/validator.js +2 -1
  28. package/lib/compiler/assert-consistency.js +15 -10
  29. package/lib/compiler/builtins.js +127 -10
  30. package/lib/compiler/define.js +6 -4
  31. package/lib/compiler/extend.js +63 -12
  32. package/lib/compiler/finalize-parse-cdl.js +20 -9
  33. package/lib/compiler/index.js +25 -11
  34. package/lib/compiler/moduleLayers.js +7 -0
  35. package/lib/compiler/populate.js +16 -14
  36. package/lib/compiler/propagator.js +3 -3
  37. package/lib/compiler/resolve.js +194 -222
  38. package/lib/compiler/shared.js +56 -76
  39. package/lib/compiler/tweak-assocs.js +9 -10
  40. package/lib/compiler/utils.js +7 -2
  41. package/lib/edm/annotations/genericTranslation.js +60 -6
  42. package/lib/edm/annotations/preprocessAnnotations.js +10 -11
  43. package/lib/edm/csn2edm.js +39 -41
  44. package/lib/edm/edm.js +22 -15
  45. package/lib/edm/edmPreprocessor.js +66 -69
  46. package/lib/edm/edmUtils.js +12 -62
  47. package/lib/gen/Dictionary.json +8 -6
  48. package/lib/gen/language.checksum +1 -1
  49. package/lib/gen/language.interp +8 -30
  50. package/lib/gen/language.tokens +105 -114
  51. package/lib/gen/languageLexer.interp +1 -34
  52. package/lib/gen/languageLexer.js +889 -1007
  53. package/lib/gen/languageLexer.tokens +95 -106
  54. package/lib/gen/languageParser.js +20717 -22376
  55. package/lib/json/from-csn.js +73 -68
  56. package/lib/json/to-csn.js +13 -10
  57. package/lib/language/antlrParser.js +2 -2
  58. package/lib/language/docCommentParser.js +61 -38
  59. package/lib/language/errorStrategy.js +52 -40
  60. package/lib/language/genericAntlrParser.js +333 -259
  61. package/lib/language/language.g4 +600 -645
  62. package/lib/language/multiLineStringParser.js +14 -42
  63. package/lib/language/textUtils.js +44 -0
  64. package/lib/main.d.ts +27 -42
  65. package/lib/main.js +104 -81
  66. package/lib/model/csnRefs.js +2 -1
  67. package/lib/model/csnUtils.js +183 -285
  68. package/lib/model/revealInternalProperties.js +32 -9
  69. package/lib/model/sortViews.js +32 -31
  70. package/lib/optionProcessor.js +64 -57
  71. package/lib/render/.eslintrc.json +1 -1
  72. package/lib/render/DuplicateChecker.js +4 -7
  73. package/lib/render/manageConstraints.js +70 -2
  74. package/lib/render/toCdl.js +334 -339
  75. package/lib/render/toHdbcds.js +20 -16
  76. package/lib/render/toRename.js +44 -22
  77. package/lib/render/toSql.js +60 -54
  78. package/lib/render/utils/common.js +15 -1
  79. package/lib/render/utils/sql.js +20 -19
  80. package/lib/sql-identifier.js +6 -0
  81. package/lib/transform/db/.eslintrc.json +3 -2
  82. package/lib/transform/db/cdsPersistence.js +5 -15
  83. package/lib/transform/db/constraints.js +1 -1
  84. package/lib/transform/db/expansion.js +7 -6
  85. package/lib/transform/db/flattening.js +18 -19
  86. package/lib/transform/db/views.js +3 -3
  87. package/lib/transform/draft/.eslintrc.json +2 -2
  88. package/lib/transform/draft/db.js +6 -6
  89. package/lib/transform/draft/odata.js +6 -7
  90. package/lib/transform/forHanaNew.js +19 -22
  91. package/lib/transform/forOdataNew.js +13 -15
  92. package/lib/transform/localized.js +35 -25
  93. package/lib/transform/odata/toFinalBaseType.js +11 -9
  94. package/lib/transform/odata/typesExposure.js +3 -3
  95. package/lib/transform/odata/utils.js +1 -38
  96. package/lib/transform/transformUtilsNew.js +63 -77
  97. package/lib/transform/translateAssocsToJoins.js +6 -2
  98. package/lib/transform/universalCsn/.eslintrc.json +2 -2
  99. package/lib/transform/universalCsn/coreComputed.js +11 -6
  100. package/lib/transform/universalCsn/universalCsnEnricher.js +33 -5
  101. package/lib/utils/file.js +31 -21
  102. package/lib/utils/timetrace.js +20 -21
  103. package/package.json +34 -4
  104. package/share/messages/syntax-expected-integer.md +9 -8
  105. package/doc/ApiMigration.md +0 -237
  106. package/doc/CommandLineMigration.md +0 -58
  107. package/doc/ErrorMessages.md +0 -175
  108. package/doc/FioriAnnotations.md +0 -94
  109. package/doc/ODataTransformation.md +0 -273
  110. package/lib/backends.js +0 -529
  111. 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 {
@@ -829,7 +839,7 @@ function toHdbcdsSource(csn, options) {
829
839
  * @param {object} [elements] For leading query, the elements of the artifact
830
840
  * @returns {string} The rendered query
831
841
  */
832
- function renderQuery(query, isLeadingQuery, env, path = [], elements) {
842
+ function renderQuery(query, isLeadingQuery, env, path = [], elements = null) {
833
843
  let result = '';
834
844
  env.skipKeys = !isLeadingQuery;
835
845
  // Set operator, like UNION, INTERSECT, ...
@@ -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)})`;
@@ -117,6 +117,9 @@ function toSqlDdl(csn, options) {
117
117
  */
118
118
  addColumns: {
119
119
  fromElementStrings(tableName, eltStrings) {
120
+ if (options.sqlDialect === 'sqlite') // SQLite can only alter one column at a time
121
+ return eltStrings.map(eltString => `ALTER TABLE ${tableName} ADD ${eltString};`);
122
+
120
123
  const elts = options.sqlDialect === 'hana' ? `(${eltStrings.join(', ')})` : `${eltStrings.join(', ')}`;
121
124
  return [ `ALTER TABLE ${tableName} ADD ${elts};` ];
122
125
  },
@@ -138,10 +141,10 @@ function toSqlDdl(csn, options) {
138
141
  TODO duplicity check
139
142
  */
140
143
  addAssociations(artifactName, tableName, elementsObj, env) {
141
- return Object.entries(elementsObj)
144
+ return options.sqlDialect === 'hana' ? Object.entries(elementsObj)
142
145
  .map(([ name, elt ]) => renderAssociationElement(name, elt, env))
143
146
  .filter(s => s !== '')
144
- .map(eltStr => `ALTER TABLE ${tableName} ADD ASSOCIATION (${eltStr});`);
147
+ .map(eltStr => `ALTER TABLE ${tableName} ADD ASSOCIATION (${eltStr});`) : [];
145
148
  },
146
149
  /*
147
150
  Render key addition as HANA SQL.
@@ -159,7 +162,7 @@ function toSqlDdl(csn, options) {
159
162
  Render association removals as HANA SQL.
160
163
  */
161
164
  dropAssociation(tableName, sqlId) {
162
- return [ `ALTER TABLE ${tableName} DROP ASSOCIATION ${sqlId};` ];
165
+ return options.sqlDialect === 'hana' ? [ `ALTER TABLE ${tableName} DROP ASSOCIATION ${sqlId};` ] : [];
163
166
  },
164
167
  /*
165
168
  Render primary-key removals as HANA SQL.
@@ -238,7 +241,7 @@ function toSqlDdl(csn, options) {
238
241
  };
239
242
 
240
243
  // Registries for artifact and element names per CSN section
241
- const definitionsDuplicateChecker = new DuplicateChecker(options.toSql.names);
244
+ const definitionsDuplicateChecker = new DuplicateChecker(options.sqlMapping);
242
245
  const deletionsDuplicateChecker = new DuplicateChecker();
243
246
  const extensionsDuplicateChecker = new DuplicateChecker();
244
247
  const removeElementsDuplicateChecker = new DuplicateChecker();
@@ -262,7 +265,7 @@ function toSqlDdl(csn, options) {
262
265
  // Render each artifact extension
263
266
  // Only HANA SQL is currently supported.
264
267
  // 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'))) {
268
+ if (csn.extensions && (options.sqlDialect === 'hana' || isBetaEnabled(options, 'sqlExtensions'))) {
266
269
  for (const extension of options && options.testMode ? sortCsn(csn.extensions) : csn.extensions) {
267
270
  if (extension.extend) {
268
271
  const artifactName = extension.extend;
@@ -275,7 +278,7 @@ function toSqlDdl(csn, options) {
275
278
 
276
279
  // Render each artifact change
277
280
  // Only HANA SQL is currently supported.
278
- if (csn.migrations && (options.toSql.dialect === 'hana' || isBetaEnabled(options, 'sqlExtensions'))) {
281
+ if (csn.migrations && (options.sqlDialect === 'hana' || isBetaEnabled(options, 'sqlExtensions'))) {
279
282
  for (const migration of options && options.testMode ? sortCsn(csn.migrations) : csn.migrations) {
280
283
  if (migration.migrate) {
281
284
  const artifactName = migration.migrate;
@@ -294,7 +297,7 @@ function toSqlDdl(csn, options) {
294
297
  // Throw exception in case of errors
295
298
  throwWithAnyError();
296
299
 
297
- // Transfer results from hdb-specific dictionaries into 'sql' dictionary in proper order if toSql.src === 'sql'
300
+ // Transfer results from hdb-specific dictionaries into 'sql' dictionary in proper order if src === 'sql'
298
301
  // (relying on the order of dictionaries above)
299
302
  // FIXME: Should consider inter-view dependencies, too
300
303
  const sql = Object.create(null);
@@ -305,10 +308,10 @@ function toSqlDdl(csn, options) {
305
308
  const { deletions, migrations: _, ...hdbKinds } = mainResultObj;
306
309
  for (const hdbKind of Object.keys(hdbKinds)) {
307
310
  for (const name in mainResultObj[hdbKind]) {
308
- if (options.toSql.src === 'sql') {
311
+ if (options.src === 'sql') {
309
312
  let sourceString = mainResultObj[hdbKind][name];
310
313
  // 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 '))
314
+ if (options.sqlDialect === 'hana' && hdbKind === 'hdbtable' && sourceString.startsWith('COLUMN '))
312
315
  sourceString = sourceString.slice('COLUMN '.length);
313
316
  sql[name] = `${options.testMode ? '' : sqlVersionLine}CREATE ${sourceString};`;
314
317
  }
@@ -316,7 +319,7 @@ function toSqlDdl(csn, options) {
316
319
  mainResultObj[hdbKind][name] = sqlVersionLine + mainResultObj[hdbKind][name];
317
320
  }
318
321
  }
319
- if (options.toSql.src === 'sql')
322
+ if (options.src === 'sql')
320
323
  delete mainResultObj[hdbKind];
321
324
  }
322
325
 
@@ -329,7 +332,7 @@ function toSqlDdl(csn, options) {
329
332
  mainResultObj.sql = sql;
330
333
  }
331
334
 
332
- if (options.toSql.src === 'sql')
335
+ if (options.src === 'sql')
333
336
  mainResultObj.sql = sql;
334
337
 
335
338
  for (const name in deletions)
@@ -420,7 +423,7 @@ function toSqlDdl(csn, options) {
420
423
  * @returns {string} Artifact name
421
424
  */
422
425
  function renderArtifactName(artifactName) {
423
- return quoteSqlId(getResultingName(csn, options.toSql.names, artifactName));
426
+ return quoteSqlId(getResultingName(csn, options.sqlMapping, artifactName));
424
427
  }
425
428
 
426
429
  // Render an artifact migration into the appropriate dictionary of 'resultObj'.
@@ -573,7 +576,7 @@ function toSqlDdl(csn, options) {
573
576
  const { front, back } = getSqlSnippets(options, art);
574
577
  let result = front;
575
578
  // Only HANA has row/column tables
576
- if (options.toSql.dialect === 'hana') {
579
+ if (options.sqlDialect === 'hana') {
577
580
  if (hanaTc && hanaTc.storeType) {
578
581
  // Explicitly specified
579
582
  result += `${art.technicalConfig.hana.storeType.toUpperCase()} `;
@@ -604,7 +607,7 @@ function toSqlDdl(csn, options) {
604
607
  // for `to.sql` w/ dialect `hana` the constraints will be part of the
605
608
  const constraintsAsAlter = !options.constraintsInCreateTable && options.src === 'sql' && options.sqlDialect === 'hana';
606
609
  if ( !constraintsAsAlter && art.$tableConstraints && art.$tableConstraints.referential) {
607
- const renderReferentialConstraintsAsHdbconstraint = options.toSql.src === 'hdi';
610
+ const renderReferentialConstraintsAsHdbconstraint = options.src === 'hdi';
608
611
  const referentialConstraints = {};
609
612
  Object.entries(art.$tableConstraints.referential)
610
613
  .forEach(([ fileName, referentialConstraint ]) => {
@@ -627,7 +630,7 @@ function toSqlDdl(csn, options) {
627
630
  const uniqueConstraints = art.$tableConstraints && art.$tableConstraints.unique;
628
631
  for (const cn in uniqueConstraints) {
629
632
  const c = uniqueConstraints[cn];
630
- if (options.toSql.src === 'hdi') {
633
+ if (options.src === 'hdi') {
631
634
  resultObj.hdbindex[`${artifactName}.${cn}`]
632
635
  = `UNIQUE INVERTED INDEX ${renderArtifactName(`${artifactName}_${cn}`)} ON ${tableName} (${c.map(cpath => quoteSqlId(cpath.ref[0])).join(', ')})`;
633
636
  }
@@ -637,20 +640,20 @@ function toSqlDdl(csn, options) {
637
640
  }
638
641
  result += `${env.indent}\n)`;
639
642
 
640
- if (options.toSql.dialect === 'hana')
643
+ if (options.sqlDialect === 'hana')
641
644
  result += renderTechnicalConfiguration(art.technicalConfig, childEnv);
642
645
 
643
646
 
644
647
  const associations = Object.keys(art.elements).map(name => renderAssociationElement(name, art.elements[name], childEnv))
645
648
  .filter(s => s !== '')
646
649
  .join(',\n');
647
- if (associations !== '' && options.toSql.dialect === 'hana') {
650
+ if (associations !== '' && options.sqlDialect === 'hana') {
648
651
  result += `${env.indent} WITH ASSOCIATIONS (\n${associations}\n`;
649
652
  result += `${env.indent})`;
650
653
  }
651
654
  // Only HANA has indices
652
655
  // FIXME: Really? We should provide a DB-agnostic way to specify that
653
- if (options.toSql.dialect === 'hana')
656
+ if (options.sqlDialect === 'hana')
654
657
  renderIndexesInto(art.technicalConfig && art.technicalConfig.hana.indexes, artifactName, resultObj, env);
655
658
 
656
659
  if (options.sqlDialect === 'hana' && hasHanaComment(art, options))
@@ -758,7 +761,7 @@ function toSqlDdl(csn, options) {
758
761
  result += ` DEFAULT ${renderExpr(elm.default, env)}`;
759
762
 
760
763
  // Only HANA has fuzzy indices
761
- if (fzindex && options.toSql.dialect === 'hana')
764
+ if (fzindex && options.sqlDialect === 'hana')
762
765
  result += ` ${renderExpr(fzindex, env)}`;
763
766
 
764
767
  // (table) elements can only have a @sql.append
@@ -906,7 +909,7 @@ function toSqlDdl(csn, options) {
906
909
  throw new ModelError(`Unexpected form of index: "${index}"`);
907
910
 
908
911
  let indexName = renderArtifactName(`${artifactName}.${index[i + 1].ref}`);
909
- if (options.toSql.names === 'plain')
912
+ if (options.sqlMapping === 'plain')
910
913
  indexName = indexName.replace(/(\.|::)/g, '_');
911
914
 
912
915
  const result = index.slice(0, i + 1); // CREATE UNIQUE INDEX
@@ -945,7 +948,7 @@ function toSqlDdl(csn, options) {
945
948
  let result = `${renderViewSource(artifactName, source.args[0], env)}`;
946
949
  for (let i = 1; i < source.args.length; i++) {
947
950
  result = `(${result} ${source.join.toUpperCase()} `;
948
- if (options.toSql.dialect === 'hana')
951
+ if (options.sqlDialect === 'hana')
949
952
  result += renderJoinCardinality(source.cardinality);
950
953
  result += `JOIN ${renderViewSource(artifactName, source.args[i], env)}`;
951
954
  if (source.on)
@@ -1008,7 +1011,7 @@ function toSqlDdl(csn, options) {
1008
1011
  let result = renderAbsolutePath(path, ':', env);
1009
1012
 
1010
1013
  // Take care of aliases
1011
- const implicitAlias = path.ref.length === 0 ? getLastPartOf(getResultingName(csn, options.toSql.names, path.ref[0])) : getLastPartOfRef(path.ref);
1014
+ const implicitAlias = path.ref.length === 0 ? getLastPartOf(getResultingName(csn, options.sqlMapping, path.ref[0])) : getLastPartOfRef(path.ref);
1012
1015
  if (path.as) {
1013
1016
  // Source had an alias - render it
1014
1017
  result += ` AS ${quoteSqlId(path.as)}`;
@@ -1126,7 +1129,7 @@ function toSqlDdl(csn, options) {
1126
1129
  let result = '';
1127
1130
  const leaf = col.as || col.ref && col.ref[col.ref.length - 1] || col.func;
1128
1131
  if (leaf && elements[leaf] && elements[leaf].virtual) {
1129
- if (isDeprecatedEnabled(options, 'renderVirtualElements'))
1132
+ if (isDeprecatedEnabled(options, '_renderVirtualElements'))
1130
1133
  // render a virtual column 'null as <alias>'
1131
1134
  result += `${env.indent}NULL AS ${quoteSqlId(col.as || leaf)}`;
1132
1135
  }
@@ -1165,7 +1168,7 @@ function toSqlDdl(csn, options) {
1165
1168
  .map(name => renderAssociationElement(name, art.elements[name], childEnv))
1166
1169
  .filter(s => s !== '')
1167
1170
  .join(',\n');
1168
- if (associations !== '' && options.toSql.dialect === 'hana') {
1171
+ if (associations !== '' && options.sqlDialect === 'hana') {
1169
1172
  result += `${env.indent}\nWITH ASSOCIATIONS (\n${associations}\n`;
1170
1173
  result += `${env.indent})`;
1171
1174
  }
@@ -1197,7 +1200,7 @@ function toSqlDdl(csn, options) {
1197
1200
  // this would be an incompatible change, as non-uppercased, quoted identifiers
1198
1201
  // are rejected by the HANA compiler.
1199
1202
  let pIdentifier;
1200
- if (options.toSql.names === 'quoted' || options.toSql.names === 'hdbcds')
1203
+ if (options.sqlMapping === 'quoted' || options.sqlMapping === 'hdbcds')
1201
1204
  pIdentifier = prepareIdentifier(pn);
1202
1205
  else
1203
1206
  pIdentifier = quoteSqlId(pn);
@@ -1386,7 +1389,7 @@ function toSqlDdl(csn, options) {
1386
1389
  'cds.LocalTime': 'cds.Time',
1387
1390
  };
1388
1391
  const tName = forHanaRenamesToEarly[typeName] || typeName;
1389
- const types = cdsToSqlTypes[options.toSql.dialect];
1392
+ const types = cdsToSqlTypes[options.sqlDialect];
1390
1393
  return types && types[tName] || cdsToSqlTypes.standard[tName] || 'CHAR';
1391
1394
  }
1392
1395
 
@@ -1426,7 +1429,7 @@ function toSqlDdl(csn, options) {
1426
1429
 
1427
1430
  if (elm.srid !== undefined) {
1428
1431
  // SAP HANA Geometry types translate into CHAR in plain/sqlite (give them the default length of 2000)
1429
- if (options.toSql.dialect !== 'hana')
1432
+ if (options.sqlDialect !== 'hana')
1430
1433
  params.push(2000);
1431
1434
  else
1432
1435
  params.push(elm.srid);
@@ -1448,7 +1451,7 @@ function toSqlDdl(csn, options) {
1448
1451
  case 'date':
1449
1452
  case 'time':
1450
1453
  case 'timestamp':
1451
- if (options.toSql.dialect === 'sqlite') {
1454
+ if (options.sqlDialect === 'sqlite') {
1452
1455
  // simple string literal '2017-11-02'
1453
1456
  return `'${x.val}'`;
1454
1457
  }
@@ -1474,7 +1477,7 @@ function toSqlDdl(csn, options) {
1474
1477
 
1475
1478
  if (x.ref[0] === '$user') {
1476
1479
  if (magicReplacement !== null)
1477
- return `'${magicReplacement}'`;
1480
+ return renderStringForSql(magicReplacement, options.sqlDialect);
1478
1481
 
1479
1482
  const result = render$user();
1480
1483
  // Invalid second path step doesn't cause a return
@@ -1488,7 +1491,19 @@ function toSqlDdl(csn, options) {
1488
1491
  return result;
1489
1492
  }
1490
1493
  else if (x.ref[0] === '$session' && magicReplacement !== null) {
1491
- return `'${magicReplacement}'`;
1494
+ return renderStringForSql(magicReplacement, options.sqlDialect);
1495
+ }
1496
+ else if (x.ref[0] === '$now') { // TODO: Can there be cases where $now is followed by something?
1497
+ switch (options.sqlDialect) {
1498
+ case 'plain':
1499
+ case 'sqlite':
1500
+ case 'hana':
1501
+ return 'CURRENT_TIMESTAMP';
1502
+ case 'postgres':
1503
+ return 'current_timestamp';
1504
+ default:
1505
+ return quoteSqlId(x.ref[0]);
1506
+ }
1492
1507
  }
1493
1508
  }
1494
1509
  // FIXME: We currently cannot distinguish whether '$parameters' was quoted or not - we
@@ -1508,27 +1523,17 @@ function toSqlDdl(csn, options) {
1508
1523
  * @returns {string|null} Null in case of an invalid second path step
1509
1524
  */
1510
1525
  function render$user() {
1511
- // FIXME: this is all not enough: we might need an explicit select item alias
1526
+ // FIXME: this is all not enough: we might need an explicit select item alias (?)
1512
1527
  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\')';
1528
+ if (options.sqlDialect === 'hana')
1529
+ return 'SESSION_CONTEXT(\'APPLICATIONUSER\')';
1530
+ warning(null, null, 'The "$user" variable is not supported. Use option "variableReplacements" to specify a value for "$user.id"');
1531
+ return '\'$user.id\'';
1525
1532
  }
1526
1533
  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\')';
1534
+ if (options.sqlDialect === 'hana')
1535
+ return 'SESSION_CONTEXT(\'LOCALE\')';
1536
+ return '\'en\''; // default language
1532
1537
  }
1533
1538
  // Basically: Second path step was invalid, do nothing - should not happen, see 'unknownMagic.js'
1534
1539
  return null;
@@ -1547,13 +1552,14 @@ function toSqlDdl(csn, options) {
1547
1552
  */
1548
1553
  function render$at() {
1549
1554
  if (x.ref[1] === 'from') {
1550
- switch (options.toSql.dialect) {
1555
+ switch (options.sqlDialect) {
1551
1556
  case 'sqlite': {
1552
1557
  const dateFromFormat = '%Y-%m-%dT%H:%M:%S.000Z';
1553
1558
  return `strftime('${dateFromFormat}', 'now')`;
1554
1559
  }
1555
1560
  case 'hana':
1556
1561
  return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-FROM\'))';
1562
+ case 'postgres':
1557
1563
  case 'plain':
1558
1564
  return 'current_timestamp';
1559
1565
  default:
@@ -1562,7 +1568,7 @@ function toSqlDdl(csn, options) {
1562
1568
  }
1563
1569
 
1564
1570
  if (x.ref[1] === 'to') {
1565
- switch (options.toSql.dialect) {
1571
+ switch (options.sqlDialect) {
1566
1572
  case 'sqlite': {
1567
1573
  // + 1ms compared to $at.from
1568
1574
  const dateToFormat = '%Y-%m-%dT%H:%M:%S.001Z';
@@ -1570,6 +1576,7 @@ function toSqlDdl(csn, options) {
1570
1576
  }
1571
1577
  case 'hana':
1572
1578
  return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-TO\'))';
1579
+ case 'postgres':
1573
1580
  case 'plain':
1574
1581
  return 'current_timestamp';
1575
1582
  default:
@@ -1591,7 +1598,6 @@ function toSqlDdl(csn, options) {
1591
1598
  if (typeof (s) === 'string') {
1592
1599
  // TODO: When is this actually executed and not handled already in renderExpr?
1593
1600
  const magicForHana = {
1594
- $now: 'CURRENT_TIMESTAMP',
1595
1601
  '$user.id': 'SESSION_CONTEXT(\'APPLICATIONUSER\')',
1596
1602
  '$user.locale': 'SESSION_CONTEXT(\'LOCALE\')',
1597
1603
  };
@@ -1599,7 +1605,7 @@ function toSqlDdl(csn, options) {
1599
1605
  if (idx === 0) {
1600
1606
  // HANA-specific translation of '$now' and '$user'
1601
1607
  // FIXME: this is all not enough: we might need an explicit select item alias
1602
- if (magicForHana[s])
1608
+ if (options.sqlDialect === 'hana' && magicForHana[s])
1603
1609
  return magicForHana[s];
1604
1610
 
1605
1611
  // 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
  /**