@sap/cds-compiler 2.15.8 → 3.1.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 (127) hide show
  1. package/CHANGELOG.md +102 -1590
  2. package/bin/.eslintrc.json +2 -1
  3. package/bin/cdsc.js +61 -46
  4. package/doc/API.md +11 -0
  5. package/doc/CHANGELOG_ARCHIVE.md +1592 -0
  6. package/doc/CHANGELOG_BETA.md +26 -5
  7. package/doc/CHANGELOG_DEPRECATED.md +55 -1
  8. package/doc/{DeprecatedOptions.md → DeprecatedOptions_v2.md} +3 -1
  9. package/doc/Versioning.md +20 -1
  10. package/lib/api/.eslintrc.json +2 -2
  11. package/lib/api/main.js +282 -156
  12. package/lib/api/options.js +17 -88
  13. package/lib/api/validate.js +6 -10
  14. package/lib/base/keywords.js +280 -110
  15. package/lib/base/message-registry.js +85 -25
  16. package/lib/base/messages.js +119 -89
  17. package/lib/base/model.js +46 -2
  18. package/lib/base/optionProcessorHelper.js +53 -21
  19. package/lib/checks/actionsFunctions.js +15 -12
  20. package/lib/checks/annotationsOData.js +1 -1
  21. package/lib/checks/cdsPersistence.js +1 -0
  22. package/lib/checks/elements.js +6 -6
  23. package/lib/checks/invalidTarget.js +1 -1
  24. package/lib/checks/nonexpandableStructured.js +1 -1
  25. package/lib/checks/queryNoDbArtifacts.js +2 -1
  26. package/lib/checks/selectItems.js +101 -15
  27. package/lib/checks/types.js +7 -8
  28. package/lib/checks/utils.js +2 -2
  29. package/lib/checks/validator.js +3 -3
  30. package/lib/compiler/assert-consistency.js +78 -21
  31. package/lib/compiler/base.js +6 -4
  32. package/lib/compiler/builtins.js +177 -10
  33. package/lib/compiler/checks.js +1 -1
  34. package/lib/compiler/define.js +28 -23
  35. package/lib/compiler/extend.js +75 -18
  36. package/lib/compiler/finalize-parse-cdl.js +25 -18
  37. package/lib/compiler/index.js +27 -11
  38. package/lib/compiler/moduleLayers.js +7 -0
  39. package/lib/compiler/populate.js +26 -39
  40. package/lib/compiler/propagator.js +12 -7
  41. package/lib/compiler/resolve.js +207 -236
  42. package/lib/compiler/shared.js +100 -93
  43. package/lib/compiler/tweak-assocs.js +13 -20
  44. package/lib/compiler/utils.js +20 -6
  45. package/lib/edm/annotations/preprocessAnnotations.js +12 -13
  46. package/lib/edm/csn2edm.js +35 -37
  47. package/lib/edm/edm.js +22 -13
  48. package/lib/edm/edmAnnoPreprocessor.js +349 -0
  49. package/lib/edm/edmInboundChecks.js +85 -0
  50. package/lib/edm/edmPreprocessor.js +338 -689
  51. package/lib/edm/edmUtils.js +97 -67
  52. package/lib/gen/Dictionary.json +29 -9
  53. package/lib/gen/language.checksum +1 -1
  54. package/lib/gen/language.interp +8 -31
  55. package/lib/gen/language.tokens +105 -114
  56. package/lib/gen/languageLexer.interp +1 -34
  57. package/lib/gen/languageLexer.js +892 -1007
  58. package/lib/gen/languageLexer.tokens +95 -106
  59. package/lib/gen/languageParser.js +20629 -22474
  60. package/lib/inspect/.eslintrc.json +4 -0
  61. package/lib/inspect/index.js +14 -0
  62. package/lib/inspect/inspectModelStatistics.js +81 -0
  63. package/lib/inspect/inspectPropagation.js +189 -0
  64. package/lib/inspect/inspectUtils.js +44 -0
  65. package/lib/json/from-csn.js +74 -69
  66. package/lib/json/to-csn.js +17 -14
  67. package/lib/language/antlrParser.js +2 -2
  68. package/lib/language/docCommentParser.js +61 -38
  69. package/lib/language/errorStrategy.js +52 -40
  70. package/lib/language/genericAntlrParser.js +424 -292
  71. package/lib/language/language.g4 +604 -687
  72. package/lib/language/multiLineStringParser.js +14 -42
  73. package/lib/language/textUtils.js +44 -0
  74. package/lib/main.d.ts +28 -42
  75. package/lib/main.js +104 -81
  76. package/lib/model/api.js +1 -1
  77. package/lib/model/csnRefs.js +57 -30
  78. package/lib/model/csnUtils.js +189 -287
  79. package/lib/model/revealInternalProperties.js +32 -10
  80. package/lib/model/sortViews.js +32 -31
  81. package/lib/modelCompare/compare.js +3 -0
  82. package/lib/optionProcessor.js +91 -57
  83. package/lib/render/.eslintrc.json +1 -1
  84. package/lib/render/DuplicateChecker.js +4 -7
  85. package/lib/render/manageConstraints.js +70 -2
  86. package/lib/render/toCdl.js +387 -367
  87. package/lib/render/toHdbcds.js +20 -16
  88. package/lib/render/toRename.js +44 -22
  89. package/lib/render/toSql.js +81 -59
  90. package/lib/render/utils/common.js +16 -3
  91. package/lib/render/utils/sql.js +20 -19
  92. package/lib/sql-identifier.js +6 -0
  93. package/lib/transform/db/.eslintrc.json +3 -2
  94. package/lib/transform/db/associations.js +43 -35
  95. package/lib/transform/db/cdsPersistence.js +5 -16
  96. package/lib/transform/db/constraints.js +1 -1
  97. package/lib/transform/db/expansion.js +7 -6
  98. package/lib/transform/db/flattening.js +16 -18
  99. package/lib/transform/db/transformExists.js +7 -5
  100. package/lib/transform/db/views.js +3 -3
  101. package/lib/transform/draft/.eslintrc.json +2 -2
  102. package/lib/transform/draft/db.js +6 -6
  103. package/lib/transform/draft/odata.js +6 -7
  104. package/lib/transform/forHanaNew.js +30 -24
  105. package/lib/transform/forOdataNew.js +14 -16
  106. package/lib/transform/localized.js +35 -25
  107. package/lib/transform/odata/toFinalBaseType.js +10 -10
  108. package/lib/transform/odata/typesExposure.js +17 -8
  109. package/lib/transform/odata/utils.js +1 -38
  110. package/lib/transform/transformUtilsNew.js +63 -77
  111. package/lib/transform/translateAssocsToJoins.js +2 -2
  112. package/lib/transform/universalCsn/.eslintrc.json +2 -2
  113. package/lib/transform/universalCsn/coreComputed.js +11 -6
  114. package/lib/transform/universalCsn/universalCsnEnricher.js +33 -5
  115. package/lib/utils/file.js +31 -21
  116. package/lib/utils/moduleResolve.js +0 -1
  117. package/lib/utils/timetrace.js +20 -21
  118. package/package.json +34 -4
  119. package/share/messages/syntax-expected-integer.md +9 -8
  120. package/doc/ApiMigration.md +0 -237
  121. package/doc/CommandLineMigration.md +0 -58
  122. package/doc/ErrorMessages.md +0 -175
  123. package/doc/FioriAnnotations.md +0 -94
  124. package/doc/ODataTransformation.md +0 -273
  125. package/lib/backends.js +0 -529
  126. package/lib/checks/unknownMagic.js +0 -41
  127. 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,14 +117,18 @@ 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
  },
123
126
  fromElementsObj(artifactName, tableName, elementsObj, env, duplicateChecker) {
124
127
  // Only extend with 'ADD' for elements/associations
125
128
  // TODO: May also include 'RENAME' at a later stage
129
+ const alterEnv = activateAlterMode(env);
126
130
  const elements = Object.entries(elementsObj)
127
- .map(([ name, elt ]) => renderElement(artifactName, name, elt, duplicateChecker, null, env))
131
+ .map(([ name, elt ]) => renderElement(artifactName, name, elt, duplicateChecker, null, alterEnv))
128
132
  .filter(s => s !== '');
129
133
 
130
134
  if (elements.length)
@@ -138,10 +142,10 @@ function toSqlDdl(csn, options) {
138
142
  TODO duplicity check
139
143
  */
140
144
  addAssociations(artifactName, tableName, elementsObj, env) {
141
- return Object.entries(elementsObj)
145
+ return options.sqlDialect === 'hana' ? Object.entries(elementsObj)
142
146
  .map(([ name, elt ]) => renderAssociationElement(name, elt, env))
143
147
  .filter(s => s !== '')
144
- .map(eltStr => `ALTER TABLE ${tableName} ADD ASSOCIATION (${eltStr});`);
148
+ .map(eltStr => `ALTER TABLE ${tableName} ADD ASSOCIATION (${eltStr});`) : [];
145
149
  },
146
150
  /*
147
151
  Render key addition as HANA SQL.
@@ -159,7 +163,7 @@ function toSqlDdl(csn, options) {
159
163
  Render association removals as HANA SQL.
160
164
  */
161
165
  dropAssociation(tableName, sqlId) {
162
- return [ `ALTER TABLE ${tableName} DROP ASSOCIATION ${sqlId};` ];
166
+ return options.sqlDialect === 'hana' ? [ `ALTER TABLE ${tableName} DROP ASSOCIATION ${sqlId};` ] : [];
163
167
  },
164
168
  /*
165
169
  Render primary-key removals as HANA SQL.
@@ -238,7 +242,7 @@ function toSqlDdl(csn, options) {
238
242
  };
239
243
 
240
244
  // Registries for artifact and element names per CSN section
241
- const definitionsDuplicateChecker = new DuplicateChecker(options.toSql.names);
245
+ const definitionsDuplicateChecker = new DuplicateChecker(options.sqlMapping);
242
246
  const deletionsDuplicateChecker = new DuplicateChecker();
243
247
  const extensionsDuplicateChecker = new DuplicateChecker();
244
248
  const removeElementsDuplicateChecker = new DuplicateChecker();
@@ -262,7 +266,7 @@ function toSqlDdl(csn, options) {
262
266
  // Render each artifact extension
263
267
  // Only HANA SQL is currently supported.
264
268
  // 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'))) {
269
+ if (csn.extensions && (options.sqlDialect === 'hana' || isBetaEnabled(options, 'sqlExtensions'))) {
266
270
  for (const extension of options && options.testMode ? sortCsn(csn.extensions) : csn.extensions) {
267
271
  if (extension.extend) {
268
272
  const artifactName = extension.extend;
@@ -275,7 +279,7 @@ function toSqlDdl(csn, options) {
275
279
 
276
280
  // Render each artifact change
277
281
  // Only HANA SQL is currently supported.
278
- if (csn.migrations && (options.toSql.dialect === 'hana' || isBetaEnabled(options, 'sqlExtensions'))) {
282
+ if (csn.migrations && (options.sqlDialect === 'hana' || isBetaEnabled(options, 'sqlExtensions'))) {
279
283
  for (const migration of options && options.testMode ? sortCsn(csn.migrations) : csn.migrations) {
280
284
  if (migration.migrate) {
281
285
  const artifactName = migration.migrate;
@@ -294,7 +298,7 @@ function toSqlDdl(csn, options) {
294
298
  // Throw exception in case of errors
295
299
  throwWithAnyError();
296
300
 
297
- // Transfer results from hdb-specific dictionaries into 'sql' dictionary in proper order if toSql.src === 'sql'
301
+ // Transfer results from hdb-specific dictionaries into 'sql' dictionary in proper order if src === 'sql'
298
302
  // (relying on the order of dictionaries above)
299
303
  // FIXME: Should consider inter-view dependencies, too
300
304
  const sql = Object.create(null);
@@ -305,10 +309,10 @@ function toSqlDdl(csn, options) {
305
309
  const { deletions, migrations: _, ...hdbKinds } = mainResultObj;
306
310
  for (const hdbKind of Object.keys(hdbKinds)) {
307
311
  for (const name in mainResultObj[hdbKind]) {
308
- if (options.toSql.src === 'sql') {
312
+ if (options.src === 'sql') {
309
313
  let sourceString = mainResultObj[hdbKind][name];
310
314
  // 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 '))
315
+ if (options.sqlDialect === 'hana' && hdbKind === 'hdbtable' && sourceString.startsWith('COLUMN '))
312
316
  sourceString = sourceString.slice('COLUMN '.length);
313
317
  sql[name] = `${options.testMode ? '' : sqlVersionLine}CREATE ${sourceString};`;
314
318
  }
@@ -316,7 +320,7 @@ function toSqlDdl(csn, options) {
316
320
  mainResultObj[hdbKind][name] = sqlVersionLine + mainResultObj[hdbKind][name];
317
321
  }
318
322
  }
319
- if (options.toSql.src === 'sql')
323
+ if (options.src === 'sql')
320
324
  delete mainResultObj[hdbKind];
321
325
  }
322
326
 
@@ -329,7 +333,7 @@ function toSqlDdl(csn, options) {
329
333
  mainResultObj.sql = sql;
330
334
  }
331
335
 
332
- if (options.toSql.src === 'sql')
336
+ if (options.src === 'sql')
333
337
  mainResultObj.sql = sql;
334
338
 
335
339
  for (const name in deletions)
@@ -420,7 +424,7 @@ function toSqlDdl(csn, options) {
420
424
  * @returns {string} Artifact name
421
425
  */
422
426
  function renderArtifactName(artifactName) {
423
- return quoteSqlId(getResultingName(csn, options.toSql.names, artifactName));
427
+ return quoteSqlId(getResultingName(csn, options.sqlMapping, artifactName));
424
428
  }
425
429
 
426
430
  // Render an artifact migration into the appropriate dictionary of 'resultObj'.
@@ -434,7 +438,7 @@ function toSqlDdl(csn, options) {
434
438
  function getEltStr(defVariant, eltName) {
435
439
  return defVariant.target
436
440
  ? renderAssociationElement(eltName, defVariant, env)
437
- : renderElement(artifactName, eltName, defVariant, null, null, env);
441
+ : renderElement(artifactName, eltName, defVariant, null, null, activateAlterMode(env));
438
442
  }
439
443
  function getEltStrNoProps(defVariant, eltName, ...props) {
440
444
  const defNoProps = Object.assign({}, defVariant);
@@ -573,7 +577,7 @@ function toSqlDdl(csn, options) {
573
577
  const { front, back } = getSqlSnippets(options, art);
574
578
  let result = front;
575
579
  // Only HANA has row/column tables
576
- if (options.toSql.dialect === 'hana') {
580
+ if (options.sqlDialect === 'hana') {
577
581
  if (hanaTc && hanaTc.storeType) {
578
582
  // Explicitly specified
579
583
  result += `${art.technicalConfig.hana.storeType.toUpperCase()} `;
@@ -604,7 +608,7 @@ function toSqlDdl(csn, options) {
604
608
  // for `to.sql` w/ dialect `hana` the constraints will be part of the
605
609
  const constraintsAsAlter = !options.constraintsInCreateTable && options.src === 'sql' && options.sqlDialect === 'hana';
606
610
  if ( !constraintsAsAlter && art.$tableConstraints && art.$tableConstraints.referential) {
607
- const renderReferentialConstraintsAsHdbconstraint = options.toSql.src === 'hdi';
611
+ const renderReferentialConstraintsAsHdbconstraint = options.src === 'hdi';
608
612
  const referentialConstraints = {};
609
613
  Object.entries(art.$tableConstraints.referential)
610
614
  .forEach(([ fileName, referentialConstraint ]) => {
@@ -627,7 +631,7 @@ function toSqlDdl(csn, options) {
627
631
  const uniqueConstraints = art.$tableConstraints && art.$tableConstraints.unique;
628
632
  for (const cn in uniqueConstraints) {
629
633
  const c = uniqueConstraints[cn];
630
- if (options.toSql.src === 'hdi') {
634
+ if (options.src === 'hdi') {
631
635
  resultObj.hdbindex[`${artifactName}.${cn}`]
632
636
  = `UNIQUE INVERTED INDEX ${renderArtifactName(`${artifactName}_${cn}`)} ON ${tableName} (${c.map(cpath => quoteSqlId(cpath.ref[0])).join(', ')})`;
633
637
  }
@@ -637,20 +641,20 @@ function toSqlDdl(csn, options) {
637
641
  }
638
642
  result += `${env.indent}\n)`;
639
643
 
640
- if (options.toSql.dialect === 'hana')
644
+ if (options.sqlDialect === 'hana')
641
645
  result += renderTechnicalConfiguration(art.technicalConfig, childEnv);
642
646
 
643
647
 
644
648
  const associations = Object.keys(art.elements).map(name => renderAssociationElement(name, art.elements[name], childEnv))
645
649
  .filter(s => s !== '')
646
650
  .join(',\n');
647
- if (associations !== '' && options.toSql.dialect === 'hana') {
651
+ if (associations !== '' && options.sqlDialect === 'hana') {
648
652
  result += `${env.indent} WITH ASSOCIATIONS (\n${associations}\n`;
649
653
  result += `${env.indent})`;
650
654
  }
651
655
  // Only HANA has indices
652
656
  // FIXME: Really? We should provide a DB-agnostic way to specify that
653
- if (options.toSql.dialect === 'hana')
657
+ if (options.sqlDialect === 'hana')
654
658
  renderIndexesInto(art.technicalConfig && art.technicalConfig.hana.indexes, artifactName, resultObj, env);
655
659
 
656
660
  if (options.sqlDialect === 'hana' && hasHanaComment(art, options))
@@ -753,12 +757,12 @@ function toSqlDdl(csn, options) {
753
757
  duplicateChecker.addElement(quotedElementName, elm.$location, elementName);
754
758
 
755
759
  let result = `${env.indent + quotedElementName} ${renderTypeReference(artifactName, elementName, elm)
756
- }${renderNullability(elm, true)}`;
760
+ }${renderNullability(elm, true, env.alterMode)}`;
757
761
  if (elm.default)
758
762
  result += ` DEFAULT ${renderExpr(elm.default, env)}`;
759
763
 
760
764
  // Only HANA has fuzzy indices
761
- if (fzindex && options.toSql.dialect === 'hana')
765
+ if (fzindex && options.sqlDialect === 'hana')
762
766
  result += ` ${renderExpr(fzindex, env)}`;
763
767
 
764
768
  // (table) elements can only have a @sql.append
@@ -906,7 +910,7 @@ function toSqlDdl(csn, options) {
906
910
  throw new ModelError(`Unexpected form of index: "${index}"`);
907
911
 
908
912
  let indexName = renderArtifactName(`${artifactName}.${index[i + 1].ref}`);
909
- if (options.toSql.names === 'plain')
913
+ if (options.sqlMapping === 'plain')
910
914
  indexName = indexName.replace(/(\.|::)/g, '_');
911
915
 
912
916
  const result = index.slice(0, i + 1); // CREATE UNIQUE INDEX
@@ -945,7 +949,7 @@ function toSqlDdl(csn, options) {
945
949
  let result = `${renderViewSource(artifactName, source.args[0], env)}`;
946
950
  for (let i = 1; i < source.args.length; i++) {
947
951
  result = `(${result} ${source.join.toUpperCase()} `;
948
- if (options.toSql.dialect === 'hana')
952
+ if (options.sqlDialect === 'hana')
949
953
  result += renderJoinCardinality(source.cardinality);
950
954
  result += `JOIN ${renderViewSource(artifactName, source.args[i], env)}`;
951
955
  if (source.on)
@@ -1008,7 +1012,7 @@ function toSqlDdl(csn, options) {
1008
1012
  let result = renderAbsolutePath(path, ':', env);
1009
1013
 
1010
1014
  // Take care of aliases
1011
- const implicitAlias = path.ref.length === 0 ? getLastPartOf(getResultingName(csn, options.toSql.names, path.ref[0])) : getLastPartOfRef(path.ref);
1015
+ const implicitAlias = path.ref.length === 0 ? getLastPartOf(getResultingName(csn, options.sqlMapping, path.ref[0])) : getLastPartOfRef(path.ref);
1012
1016
  if (path.as) {
1013
1017
  // Source had an alias - render it
1014
1018
  result += ` AS ${quoteSqlId(path.as)}`;
@@ -1126,7 +1130,7 @@ function toSqlDdl(csn, options) {
1126
1130
  let result = '';
1127
1131
  const leaf = col.as || col.ref && col.ref[col.ref.length - 1] || col.func;
1128
1132
  if (leaf && elements[leaf] && elements[leaf].virtual) {
1129
- if (isDeprecatedEnabled(options, 'renderVirtualElements'))
1133
+ if (isDeprecatedEnabled(options, '_renderVirtualElements'))
1130
1134
  // render a virtual column 'null as <alias>'
1131
1135
  result += `${env.indent}NULL AS ${quoteSqlId(col.as || leaf)}`;
1132
1136
  }
@@ -1165,7 +1169,7 @@ function toSqlDdl(csn, options) {
1165
1169
  .map(name => renderAssociationElement(name, art.elements[name], childEnv))
1166
1170
  .filter(s => s !== '')
1167
1171
  .join(',\n');
1168
- if (associations !== '' && options.toSql.dialect === 'hana') {
1172
+ if (associations !== '' && options.sqlDialect === 'hana') {
1169
1173
  result += `${env.indent}\nWITH ASSOCIATIONS (\n${associations}\n`;
1170
1174
  result += `${env.indent})`;
1171
1175
  }
@@ -1197,7 +1201,7 @@ function toSqlDdl(csn, options) {
1197
1201
  // this would be an incompatible change, as non-uppercased, quoted identifiers
1198
1202
  // are rejected by the HANA compiler.
1199
1203
  let pIdentifier;
1200
- if (options.toSql.names === 'quoted' || options.toSql.names === 'hdbcds')
1204
+ if (options.sqlMapping === 'quoted' || options.sqlMapping === 'hdbcds')
1201
1205
  pIdentifier = prepareIdentifier(pn);
1202
1206
  else
1203
1207
  pIdentifier = quoteSqlId(pn);
@@ -1386,7 +1390,7 @@ function toSqlDdl(csn, options) {
1386
1390
  'cds.LocalTime': 'cds.Time',
1387
1391
  };
1388
1392
  const tName = forHanaRenamesToEarly[typeName] || typeName;
1389
- const types = cdsToSqlTypes[options.toSql.dialect];
1393
+ const types = cdsToSqlTypes[options.sqlDialect];
1390
1394
  return types && types[tName] || cdsToSqlTypes.standard[tName] || 'CHAR';
1391
1395
  }
1392
1396
 
@@ -1395,9 +1399,15 @@ function toSqlDdl(csn, options) {
1395
1399
  *
1396
1400
  * @param {object} obj Object to render for
1397
1401
  * @param {boolean} treatKeyAsNotNull Whether to render KEY as not null
1402
+ * @param {boolean} deltaMode Look for a $notNull and use that with precedence over notNull
1398
1403
  * @returns {string} NULL/NOT NULL or ''
1399
1404
  */
1400
- function renderNullability(obj, treatKeyAsNotNull = false) {
1405
+ function renderNullability(obj, treatKeyAsNotNull = false, deltaMode = false) {
1406
+ if (deltaMode && obj.$notNull !== undefined) { // can be set via compare.js if it goes from "not null" to implicit "null"
1407
+ return obj.$notNull ? ' NOT NULL' : ' NULL';
1408
+ }
1409
+
1410
+
1401
1411
  if (obj.notNull === undefined && !(obj.key && treatKeyAsNotNull)) {
1402
1412
  // Attribute not set at all
1403
1413
  return '';
@@ -1426,7 +1436,7 @@ function toSqlDdl(csn, options) {
1426
1436
 
1427
1437
  if (elm.srid !== undefined) {
1428
1438
  // SAP HANA Geometry types translate into CHAR in plain/sqlite (give them the default length of 2000)
1429
- if (options.toSql.dialect !== 'hana')
1439
+ if (options.sqlDialect !== 'hana')
1430
1440
  params.push(2000);
1431
1441
  else
1432
1442
  params.push(elm.srid);
@@ -1448,7 +1458,7 @@ function toSqlDdl(csn, options) {
1448
1458
  case 'date':
1449
1459
  case 'time':
1450
1460
  case 'timestamp':
1451
- if (options.toSql.dialect === 'sqlite') {
1461
+ if (options.sqlDialect === 'sqlite') {
1452
1462
  // simple string literal '2017-11-02'
1453
1463
  return `'${x.val}'`;
1454
1464
  }
@@ -1474,7 +1484,7 @@ function toSqlDdl(csn, options) {
1474
1484
 
1475
1485
  if (x.ref[0] === '$user') {
1476
1486
  if (magicReplacement !== null)
1477
- return `'${magicReplacement}'`;
1487
+ return renderStringForSql(magicReplacement, options.sqlDialect);
1478
1488
 
1479
1489
  const result = render$user();
1480
1490
  // Invalid second path step doesn't cause a return
@@ -1488,7 +1498,19 @@ function toSqlDdl(csn, options) {
1488
1498
  return result;
1489
1499
  }
1490
1500
  else if (x.ref[0] === '$session' && magicReplacement !== null) {
1491
- return `'${magicReplacement}'`;
1501
+ return renderStringForSql(magicReplacement, options.sqlDialect);
1502
+ }
1503
+ else if (x.ref[0] === '$now') { // TODO: Can there be cases where $now is followed by something?
1504
+ switch (options.sqlDialect) {
1505
+ case 'plain':
1506
+ case 'sqlite':
1507
+ case 'hana':
1508
+ return 'CURRENT_TIMESTAMP';
1509
+ case 'postgres':
1510
+ return 'current_timestamp';
1511
+ default:
1512
+ return quoteSqlId(x.ref[0]);
1513
+ }
1492
1514
  }
1493
1515
  }
1494
1516
  // FIXME: We currently cannot distinguish whether '$parameters' was quoted or not - we
@@ -1508,29 +1530,19 @@ function toSqlDdl(csn, options) {
1508
1530
  * @returns {string|null} Null in case of an invalid second path step
1509
1531
  */
1510
1532
  function render$user() {
1511
- // FIXME: this is all not enough: we might need an explicit select item alias
1533
+ // FIXME: this is all not enough: we might need an explicit select item alias (?)
1512
1534
  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\')';
1535
+ if (options.sqlDialect === 'hana')
1536
+ return 'SESSION_CONTEXT(\'APPLICATIONUSER\')';
1537
+ warning(null, null, 'The "$user" variable is not supported. Use option "variableReplacements" to specify a value for "$user.id"');
1538
+ return '\'$user.id\'';
1525
1539
  }
1526
1540
  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\')';
1541
+ if (options.sqlDialect === 'hana')
1542
+ return 'SESSION_CONTEXT(\'LOCALE\')';
1543
+ return '\'en\''; // default language
1532
1544
  }
1533
- // Basically: Second path step was invalid, do nothing - should not happen, see 'unknownMagic.js'
1545
+ // Basically: Second path step was invalid, do nothing - should not happen.
1534
1546
  return null;
1535
1547
  }
1536
1548
  /**
@@ -1547,13 +1559,14 @@ function toSqlDdl(csn, options) {
1547
1559
  */
1548
1560
  function render$at() {
1549
1561
  if (x.ref[1] === 'from') {
1550
- switch (options.toSql.dialect) {
1562
+ switch (options.sqlDialect) {
1551
1563
  case 'sqlite': {
1552
1564
  const dateFromFormat = '%Y-%m-%dT%H:%M:%S.000Z';
1553
1565
  return `strftime('${dateFromFormat}', 'now')`;
1554
1566
  }
1555
1567
  case 'hana':
1556
1568
  return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-FROM\'))';
1569
+ case 'postgres':
1557
1570
  case 'plain':
1558
1571
  return 'current_timestamp';
1559
1572
  default:
@@ -1562,7 +1575,7 @@ function toSqlDdl(csn, options) {
1562
1575
  }
1563
1576
 
1564
1577
  if (x.ref[1] === 'to') {
1565
- switch (options.toSql.dialect) {
1578
+ switch (options.sqlDialect) {
1566
1579
  case 'sqlite': {
1567
1580
  // + 1ms compared to $at.from
1568
1581
  const dateToFormat = '%Y-%m-%dT%H:%M:%S.001Z';
@@ -1570,6 +1583,7 @@ function toSqlDdl(csn, options) {
1570
1583
  }
1571
1584
  case 'hana':
1572
1585
  return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-TO\'))';
1586
+ case 'postgres':
1573
1587
  case 'plain':
1574
1588
  return 'current_timestamp';
1575
1589
  default:
@@ -1591,7 +1605,6 @@ function toSqlDdl(csn, options) {
1591
1605
  if (typeof (s) === 'string') {
1592
1606
  // TODO: When is this actually executed and not handled already in renderExpr?
1593
1607
  const magicForHana = {
1594
- $now: 'CURRENT_TIMESTAMP',
1595
1608
  '$user.id': 'SESSION_CONTEXT(\'APPLICATIONUSER\')',
1596
1609
  '$user.locale': 'SESSION_CONTEXT(\'LOCALE\')',
1597
1610
  };
@@ -1599,7 +1612,7 @@ function toSqlDdl(csn, options) {
1599
1612
  if (idx === 0) {
1600
1613
  // HANA-specific translation of '$now' and '$user'
1601
1614
  // FIXME: this is all not enough: we might need an explicit select item alias
1602
- if (magicForHana[s])
1615
+ if (options.sqlDialect === 'hana' && magicForHana[s])
1603
1616
  return magicForHana[s];
1604
1617
 
1605
1618
  // Ignore initial $projection and initial $self
@@ -1652,6 +1665,15 @@ function toSqlDdl(csn, options) {
1652
1665
  function increaseIndent(env) {
1653
1666
  return Object.assign({}, env, { indent: `${env.indent} ` });
1654
1667
  }
1668
+ /**
1669
+ * Returns a copy of 'env' with alterMode set to true
1670
+ *
1671
+ * @param {object} env Render environment
1672
+ * @returns {object} Render environment with alterMode
1673
+ */
1674
+ function activateAlterMode(env) {
1675
+ return Object.assign({ alterMode: true }, env);
1676
+ }
1655
1677
  }
1656
1678
 
1657
1679
  /**