@sap/cds-compiler 3.3.2 → 3.4.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 (74) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/bin/cdsc.js +3 -1
  3. package/doc/CHANGELOG_BETA.md +17 -0
  4. package/lib/api/main.js +147 -18
  5. package/lib/api/validate.js +8 -3
  6. package/lib/base/dictionaries.js +6 -6
  7. package/lib/base/keywords.js +104 -0
  8. package/lib/base/message-registry.js +136 -67
  9. package/lib/base/messages.js +59 -48
  10. package/lib/base/model.js +1 -0
  11. package/lib/checks/actionsFunctions.js +1 -1
  12. package/lib/checks/cdsPersistence.js +1 -1
  13. package/lib/checks/checkForTypes.js +13 -8
  14. package/lib/checks/defaultValues.js +3 -1
  15. package/lib/checks/elements.js +1 -1
  16. package/lib/checks/parameters.js +4 -2
  17. package/lib/checks/queryNoDbArtifacts.js +1 -1
  18. package/lib/checks/sql-snippets.js +12 -10
  19. package/lib/checks/validator.js +14 -4
  20. package/lib/compiler/assert-consistency.js +8 -7
  21. package/lib/compiler/checks.js +30 -20
  22. package/lib/compiler/define.js +89 -25
  23. package/lib/compiler/extend.js +21 -18
  24. package/lib/compiler/finalize-parse-cdl.js +14 -9
  25. package/lib/compiler/populate.js +30 -8
  26. package/lib/compiler/propagator.js +4 -2
  27. package/lib/compiler/resolve.js +11 -5
  28. package/lib/compiler/shared.js +66 -48
  29. package/lib/compiler/tweak-assocs.js +2 -3
  30. package/lib/compiler/utils.js +11 -0
  31. package/lib/edm/annotations/genericTranslation.js +7 -4
  32. package/lib/edm/csn2edm.js +1 -1
  33. package/lib/gen/language.checksum +1 -1
  34. package/lib/gen/language.interp +1 -1
  35. package/lib/gen/languageParser.js +3565 -3544
  36. package/lib/json/csnVersion.js +13 -13
  37. package/lib/json/from-csn.js +140 -158
  38. package/lib/json/to-csn.js +23 -5
  39. package/lib/language/.eslintrc.json +4 -0
  40. package/lib/language/antlrParser.js +7 -10
  41. package/lib/language/docCommentParser.js +1 -2
  42. package/lib/language/errorStrategy.js +54 -27
  43. package/lib/language/genericAntlrParser.js +115 -84
  44. package/lib/language/language.g4 +29 -25
  45. package/lib/language/multiLineStringParser.js +75 -63
  46. package/lib/main.js +1 -0
  47. package/lib/model/csnRefs.js +4 -3
  48. package/lib/model/csnUtils.js +39 -7
  49. package/lib/model/sortViews.js +7 -3
  50. package/lib/modelCompare/compare.js +49 -15
  51. package/lib/modelCompare/filter.js +83 -0
  52. package/lib/optionProcessor.js +5 -1
  53. package/lib/render/manageConstraints.js +9 -5
  54. package/lib/render/toCdl.js +120 -62
  55. package/lib/render/toHdbcds.js +1 -1
  56. package/lib/render/toSql.js +6 -2
  57. package/lib/render/utils/common.js +7 -0
  58. package/lib/sql-identifier.js +7 -0
  59. package/lib/transform/db/assertUnique.js +27 -38
  60. package/lib/transform/db/expansion.js +11 -4
  61. package/lib/transform/db/temporal.js +3 -1
  62. package/lib/transform/db/transformExists.js +7 -1
  63. package/lib/transform/db/views.js +42 -13
  64. package/lib/transform/draft/db.js +2 -2
  65. package/lib/transform/forRelationalDB.js +12 -6
  66. package/lib/transform/localized.js +1 -1
  67. package/lib/transform/odata/typesExposure.js +2 -1
  68. package/lib/transform/parseExpr.js +245 -0
  69. package/lib/transform/transformUtilsNew.js +23 -14
  70. package/lib/transform/translateAssocsToJoins.js +12 -12
  71. package/lib/utils/term.js +5 -5
  72. package/package.json +2 -2
  73. package/share/messages/message-explanations.json +1 -1
  74. package/share/messages/{syntax-expected-integer.md → syntax-expecting-integer.md} +1 -1
@@ -0,0 +1,83 @@
1
+ // Each db has some changes that it can and cannot represent, or that cause problems only on that specific db
2
+ // In this file, we define rules for each db-dialect to detect and act on these cases.
3
+
4
+ const { forEach } = require("../utils/objectUtils");
5
+ const { isPersistedAsTable } = require('../model/csnUtils');
6
+
7
+ function isKey(element) {
8
+ return element.key;
9
+ }
10
+
11
+ module.exports = {
12
+ sqlite: getFilterObject(
13
+ 'sqlite',
14
+ (extend, name, element, error) => {
15
+ if(isKey(element)) { // Key must not be extended
16
+ error(null, ['definitions', extend, 'elements', name], {id: name, name: 'sqlite'}, "Added element $(ID) is a primary key change and will not work with $(NAME)")
17
+ }
18
+ },
19
+ (migrate, name, migration, change, error) => {
20
+ const newIsKey = isKey(migration.new);
21
+ const oldIsKey = isKey(migration.old);
22
+ if((newIsKey || oldIsKey) && oldIsKey !== newIsKey) { // Turned into key or key was removed
23
+ error(null, ['definitions', migrate, 'elements', name], {id: name, name: 'sqlite'}, "Changed element $(ID) is a primary key change and will not work with $(NAME)")
24
+ } else { // Ignore simple migrations
25
+ delete change[name];
26
+ }
27
+ })
28
+ }
29
+
30
+ function getFilterObject(dialect, extensionCallback, migrationCallback) {
31
+ return {
32
+ // will be called with a simple Array.forEach
33
+ extension: ({ elements, extend }, error) => {
34
+ forEach(elements, (name, element) => {
35
+ extensionCallback(extend, name, element, error);
36
+ });
37
+ },
38
+ // will be called with a Array.map, as we need to filter "change" for SQLite
39
+ migration: ({ change, migrate, remove }, error) => {
40
+ forEach(remove, (name) => {
41
+ error(null, ['definitions', migrate, 'elements', name], {}, "Dropping elements is not supported")
42
+ });
43
+
44
+ forEach(change, (name, migration) => {
45
+ if(migration.new.type !== migration.old.type && typeChangeIsNotCompatible(dialect, migration.old.type, migration.new.type)) {
46
+ error(null, ['definitions', migrate, 'elements', name], { id: name, name: migration.old.type, type: migration.new.type }, "Changed element $(ID) is a lossy type change from $(NAME) to $(TYPE) and is not supported")
47
+ } else if(migration.new.length < migration.old.length) {
48
+ error(null, ['definitions', migrate, 'elements', name], { id: name }, "Changed element $(ID) is a length reduction and is not supported")
49
+ } else {
50
+ migrationCallback(migrate, name, migration, change, error);
51
+ }
52
+
53
+ // TODO: precision/scale growth
54
+ });
55
+ },
56
+ deletion: ([artifactName, artifact ], error) => {
57
+ if(isPersistedAsTable(artifact))
58
+ error(null, ['definitions', artifactName], "Dropping tables is not supported");
59
+ }
60
+ }
61
+ }
62
+
63
+ const baseMatrix = {
64
+ // Integer types
65
+ 'cds.hana.tinyint':['cds.UInt8', 'cds.Int16', 'cds.Int32', 'cds.Integer', 'cds.Int64', 'cds.Integer64'],
66
+ 'cds.UInt8': ['cds.hana.tinyint', 'cds.Int16', 'cds.Int32', 'cds.Integer', 'cds.Int64', 'cds.Integer64'],
67
+ 'cds.Int16': ['cds.hana.smallint', 'cds.Int32', 'cds.Integer', 'cds.Int64', 'cds.Integer64'],
68
+ 'cds.hana.smallint':['cds.Int16', 'cds.Int32', 'cds.Integer', 'cds.Int64', 'cds.Integer64'],
69
+ 'cds.Int32': ['cds.Integer', 'cds.Int64', 'cds.Integer64'],
70
+ 'cds.Integer': ['cds.Int32', 'cds.Int64', 'cds.Integer64'],
71
+ 'cds.Integer64': ['cds.Int64'],
72
+ 'cds.Int64': ['cds.Integer64']
73
+ }
74
+
75
+ const allowedTypeChanges = {
76
+ 'sqlite': baseMatrix
77
+ };
78
+
79
+ function typeChangeIsNotCompatible(dialect, before, after) {
80
+ if(allowedTypeChanges[dialect])
81
+ return allowedTypeChanges[dialect][before]?.indexOf(after) === -1;
82
+ return true;
83
+ }
@@ -102,6 +102,9 @@ optionProcessor
102
102
  hanaAssocRealCardinality
103
103
  mapAssocToJoinCardinality
104
104
  ignoreAssocPublishingInUnion
105
+ enableUniversalCsn
106
+ postgres
107
+ aspectWithoutElements
105
108
  odataOpenType
106
109
  optionalActionFunctionParameters
107
110
  --deprecated <list> Comma separated list of deprecated options.
@@ -262,7 +265,7 @@ optionProcessor.command('C, toCdl')
262
265
  optionProcessor.command('Q, toSql')
263
266
  .option('-h, --help')
264
267
  .option('-n, --sql-mapping <style>', ['plain', 'quoted', 'hdbcds'], { aliases: [ '--names' ] })
265
- .option('-d, --sql-dialect <dialect>', ['hana', 'sqlite', 'plain', 'postgres'], { aliases: [ '--dialect' ] })
268
+ .option('-d, --sql-dialect <dialect>', ['hana', 'sqlite', 'plain', 'postgres', 'h2'], { aliases: [ '--dialect' ] })
266
269
  .option(' --render-virtual')
267
270
  .option(' --joinfk')
268
271
  .option('-u, --user <user>')
@@ -300,6 +303,7 @@ optionProcessor.command('Q, toSql')
300
303
  hana : SQL with HANA specific language features
301
304
  sqlite : Common SQL for sqlite
302
305
  postgres : Common SQL for postgres - beta-feature
306
+ h2 : Common SQL for h2
303
307
  -u, --user <user> Value for the "$user" variable
304
308
  -l, --locale <locale> Value for the "$user.locale" variable in "sqlite"/"plain" dialect
305
309
  -s, --src <style> Generate SQL source files as <artifact>.<suffix>
@@ -40,8 +40,10 @@ function alterConstraintsWithCsn(csn, options) {
40
40
  const transformedOptions = _transformSqlOptions(csn, options);
41
41
  const forSqlCsn = transformForRelationalDBWithCsn(csn, transformedOptions, 'to.sql');
42
42
 
43
- if (violations && src && src !== 'sql')
44
- error(null, null, `Option “--violations“ can't be combined with source style “${src}“`);
43
+ if (violations && src && src !== 'sql') {
44
+ error(null, null, { value: '--violations', othervalue: src },
45
+ 'Option $(VALUE) can\'t be combined with source style $(OTHERVALUE)');
46
+ }
45
47
 
46
48
  let intermediateResult;
47
49
  if (violations)
@@ -67,12 +69,14 @@ function _transformSqlOptions(model, options) {
67
69
 
68
70
  if (options.sqlDialect !== 'hana') {
69
71
  // CDXCORE-465, 'quoted' and 'hdbcds' are to be used in combination with dialect 'hana' only
70
- if (options.sqlMapping === 'quoted' || options.sqlMapping === 'hdbcds')
71
- error(null, null, `Option "{ sqlDialect: '${options.sqlDialect}' }" can't be combined with "{ sqlMapping: '${options.sqlMapping}' }"`);
72
+ if (options.sqlMapping === 'quoted' || options.sqlMapping === 'hdbcds') {
73
+ error(null, null, { value: options.sqlDialect, othervalue: options.sqlMapping },
74
+ 'Option sqlDialect: $(VALUE) can\'t be combined with sqlMapping: $(OTHERVALUE)');
75
+ }
72
76
 
73
77
  // No non-HANA SQL for HDI
74
78
  if (options.src === 'hdi')
75
- error(null, null, `Option "{ sqlDialect: '${options.sqlDialect}' }" can't be used for HDI"`);
79
+ error(null, null, { value: options.sqlDialect }, 'Option sqlDialect: $(VALUE) can\'t be used for SAP HANA HDI');
76
80
  }
77
81
 
78
82
  return options;
@@ -351,13 +351,14 @@ function csnToCdl(csn, options) {
351
351
  if (art.query || art.projection)
352
352
  return renderView(artifactName, art, env);
353
353
  return renderEntity(artifactName, art, env);
354
+ case 'aspect':
355
+ return renderAspect(artifactName, art, env);
354
356
 
355
357
  case 'context':
356
358
  case 'service':
357
359
  return renderContextOrService(artifactName, art, env);
358
360
 
359
361
  case 'type':
360
- case 'aspect':
361
362
  case 'annotation': // annotation in 'csn.definitions' for compiler v1 compatibility
362
363
  return renderTypeOrAnnotation(artifactName, art, env);
363
364
 
@@ -380,7 +381,6 @@ function csnToCdl(csn, options) {
380
381
  */
381
382
  function renderEvent(artifactName, art, env) {
382
383
  let result = renderAnnotationAssignmentsAndDocComment(art, env);
383
- const childEnv = increaseIndent(env);
384
384
  const normalizedArtifactName = renderArtifactName(artifactName);
385
385
  result += `${env.indent}event ${normalizedArtifactName}`;
386
386
  if (art.includes)
@@ -396,11 +396,7 @@ function csnToCdl(csn, options) {
396
396
  result += ` : ${renderTypeReferenceAndProps(art, env)};\n`;
397
397
  }
398
398
  else if (art.elements) {
399
- result += ' {\n';
400
- for (const name in art.elements)
401
- result += renderElement(name, art.elements[name], childEnv);
402
-
403
- result += `${env.indent}}`;
399
+ result += ` ${renderElements(art, env)};\n`;
404
400
  }
405
401
  return result;
406
402
  }
@@ -432,21 +428,57 @@ function csnToCdl(csn, options) {
432
428
 
433
429
  if (art.params)
434
430
  result += renderParameters(art, env);
431
+ if (art.includes)
432
+ result += renderIncludes(art.includes);
433
+ result += ` ${renderElements(art, env)}`;
434
+ result += `${renderActionsAndFunctions(art, env)};\n`;
435
+ return result;
436
+ }
435
437
 
438
+ /**
439
+ * Render an aspect. Return the resulting source string.
440
+ * Behaves very similar to renderEntity, _except_ that aspects are
441
+ * allowed to _not_ have elements, e.g. `aspect A;`.
442
+ *
443
+ * @param {string} artifactName
444
+ * @param {CSN.Artifact} art
445
+ * @param {CdlRenderEnvironment} env
446
+ * @return {string}
447
+ */
448
+ function renderAspect(artifactName, art, env) {
449
+ let result = renderAnnotationAssignmentsAndDocComment(art, env);
450
+ result += `${env.indent}aspect ${renderArtifactName(artifactName)}`;
436
451
  if (art.includes)
437
452
  result += renderIncludes(art.includes);
438
- result += ' {\n';
439
- const childEnv = increaseIndent(env);
440
- for (const name in art.elements) {
441
- const element = art.elements[name];
442
- result += renderElement(name, element, childEnv);
443
- }
444
453
 
445
- result += `${env.indent}}`;
454
+ if (art.elements)
455
+ result += ` ${renderElements(art, env)}`;
456
+ else if (art.actions)
457
+ // if there are no elements, but actions, CDL syntax requires braces.
458
+ result += ' { }';
459
+
446
460
  result += `${renderActionsAndFunctions(art, env)};\n`;
447
461
  return result;
448
462
  }
449
463
 
464
+ /**
465
+ * Render a list of elements enclosed in braces. If the list is empty, returns `{ }`.
466
+ *
467
+ * @param {object} artifact Artifact with `elements` property.
468
+ * @param {CdlRenderEnvironment} env
469
+ * @return {string}
470
+ */
471
+ function renderElements(artifact, env) {
472
+ let elements = '';
473
+ const childEnv = increaseIndent(env);
474
+ for (const name in artifact.elements)
475
+ elements += renderElement(name, artifact.elements[name], childEnv, null);
476
+
477
+ if (elements === '')
478
+ return '{ }';
479
+ return `{\n${elements}${env.indent}}`;
480
+ }
481
+
450
482
  /**
451
483
  * Render an element (of an entity, type or annotation, not a projection or view).
452
484
  * Return the resulting source string.
@@ -651,7 +683,7 @@ function csnToCdl(csn, options) {
651
683
 
652
684
  // Even the first step might have parameters and/or a filter
653
685
  if (path.ref[0].args)
654
- result += `(${renderArgs(path.ref[0], ':', env)})`;
686
+ result += `(${renderArguments(path.ref[0], ':', env)})`;
655
687
 
656
688
  if (path.ref[0].where) {
657
689
  const cardinality = path.ref[0].cardinality ? (`${path.ref[0].cardinality.max}: `) : '';
@@ -841,6 +873,8 @@ function csnToCdl(csn, options) {
841
873
  result += renderActionsAndFunctions(art, env);
842
874
  result += ';\n';
843
875
  result += renderQueryElementAndEnumAnnotations(artifactName, art, env);
876
+ if (art.includes)
877
+ result += renderExtension({ extend: artifactName, includes: art.includes }, env);
844
878
  return result;
845
879
  }
846
880
 
@@ -1093,8 +1127,7 @@ function csnToCdl(csn, options) {
1093
1127
  result += ` ${renderTypeReferenceAndProps(art, env)}`;
1094
1128
  else
1095
1129
  result += ` : ${renderTypeReferenceAndProps(art, env)}`;
1096
- // for aspects, but since types don't have `actions` this does not hurt
1097
- result += `${renderActionsAndFunctions(art, env)};\n`;
1130
+ result += ';\n';
1098
1131
  return result;
1099
1132
  }
1100
1133
 
@@ -1130,12 +1163,7 @@ function csnToCdl(csn, options) {
1130
1163
  }
1131
1164
 
1132
1165
  if (!artifact.type && artifact.elements) {
1133
- result += '{\n';
1134
- const childEnv = envAddPath(increaseIndent(env), 'items');
1135
- for (const name in artifact.elements)
1136
- result += renderElement(name, artifact.elements[name], childEnv, null);
1137
-
1138
- result += `${env.indent}}`;
1166
+ result += renderElements(artifact, env);
1139
1167
  if (!isTypeDef)
1140
1168
  result += renderNullability(artifact);
1141
1169
  // structured default not possible at the moment
@@ -1159,12 +1187,7 @@ function csnToCdl(csn, options) {
1159
1187
  }
1160
1188
  else if (elements) {
1161
1189
  // anonymous aspect, either parseCdl or client CSN.
1162
- const childEnv = increaseIndent(env);
1163
- result += '{\n';
1164
- for (const name in elements)
1165
- result += renderElement(name, elements[name], childEnv);
1166
-
1167
- result += `${env.indent}}`;
1190
+ result += renderElements({ elements }, env);
1168
1191
  }
1169
1192
  else {
1170
1193
  throw new ModelError('Association/Composition is missing its target! Throwing exception to trigger recompilation.');
@@ -1350,6 +1373,8 @@ function csnToCdl(csn, options) {
1350
1373
  *
1351
1374
  * @param {string|object} s
1352
1375
  * @param {number} idx
1376
+ * @param {boolean} inline
1377
+ * @param {object} env
1353
1378
  * @returns {string}
1354
1379
  */
1355
1380
  function renderPathStep(s, idx, inline, env) {
@@ -1357,12 +1382,9 @@ function csnToCdl(csn, options) {
1357
1382
  if (typeof s === 'string') {
1358
1383
  // In first path position, do not quote $projection and magic $-variables like CURRENT_DATE, $now etc.
1359
1384
  // FIXME: We should rather explicitly recognize quoting somehow
1360
-
1361
- if (idx === 0 &&
1362
- s.startsWith('$'))
1385
+ if (idx === 0 && s.startsWith('$'))
1363
1386
  return s;
1364
-
1365
- return quoteIdIfRequired(s);
1387
+ return quoteIdIfRequired(s, env.additionalKeywords);
1366
1388
  }
1367
1389
  // ID with filters or parameters
1368
1390
  else if (typeof s === 'object') {
@@ -1372,13 +1394,13 @@ function csnToCdl(csn, options) {
1372
1394
 
1373
1395
  // Not really a path step but an object-like function call
1374
1396
  if (s.func)
1375
- return `${s.func}(${renderArgs(s, '=>', env)})`;
1397
+ return `${s.func}(${renderArguments(s, '=>', env)})`;
1376
1398
 
1377
1399
  // Path step, possibly with view parameters and/or filters
1378
- let result = `${quoteIdIfRequired(s.id)}`;
1400
+ let result = `${quoteIdIfRequired(s.id, env.additionalKeywords)}`;
1379
1401
  if (s.args) {
1380
1402
  // View parameters
1381
- result += `(${renderArgs(s, ':', env)})`;
1403
+ result += `(${renderArguments(s, ':', env)})`;
1382
1404
  }
1383
1405
  if (s.where) {
1384
1406
  // Filter, possibly with cardinality
@@ -1402,24 +1424,48 @@ function csnToCdl(csn, options) {
1402
1424
  * @param {CdlRenderEnvironment} env
1403
1425
  * @returns {string}
1404
1426
  */
1405
- function renderArgs(node, sep, env) {
1406
- const args = node.args || [];
1407
-
1408
- // Positional arguments
1409
- if (Array.isArray(args)) {
1410
- const func = node.func?.toUpperCase();
1411
- if (func)
1412
- return args.map((arg, i) => renderArgument(arg, env, getKeywordsForSpecialFunctionArgument(func, i))).join(', ');
1427
+ function renderArguments(node, sep, env) {
1428
+ if (!node.args)
1429
+ return '';
1430
+ else if (Array.isArray(node.args))
1431
+ return renderPositionalArguments(node, env);
1432
+ else if (typeof node.args === 'object')
1433
+ return renderNamedArguments(node, sep, env);
1434
+ throw new ModelError(`Unknown args: ${JSON.stringify(node.args)}; expected array/object`);
1435
+ }
1413
1436
 
1414
- return args.map(arg => renderArgument(arg, env)).join(', ');
1415
- }
1437
+ /**
1438
+ * Render named function arguments or view parameters,
1439
+ * using 'sep' as separator.
1440
+ *
1441
+ * @param {object} node with `args` to render
1442
+ * @param {string} separator
1443
+ * @param {CdlRenderEnvironment} env
1444
+ * @returns {string}
1445
+ */
1446
+ function renderNamedArguments(node, separator, env) {
1447
+ return Object.keys(node.args).map(function renderNamedArgument(key) {
1448
+ return `${quoteIdIfRequired(key, env.additionalKeywords)} ${separator} ${renderArgument(node.args[key], env)}`;
1449
+ }).join(', ');
1450
+ }
1416
1451
 
1417
- // Named arguments (object/dict)
1418
- else if (typeof args === 'object') {
1419
- return Object.keys(args).map(key => `${quoteIdIfRequired(key)} ${sep} ${renderArgument(args[key], env)}`).join(', ');
1452
+ /**
1453
+ * Render a comma separated list of positional function arguments.
1454
+ *
1455
+ * @param {object} node with `args` to render
1456
+ * @param {CdlRenderEnvironment} env
1457
+ * @returns {string}
1458
+ */
1459
+ function renderPositionalArguments(node, env) {
1460
+ if (!node.args)
1461
+ return '';
1462
+ const func = node.func?.toUpperCase();
1463
+ if (func) {
1464
+ return node.args.map(function renderFunctionArg(arg, i) {
1465
+ return renderArgument(arg, env, getKeywordsForSpecialFunctionArgument(func, i));
1466
+ }).join(', ');
1420
1467
  }
1421
-
1422
- throw new ModelError(`Unknown args: ${JSON.stringify(args)}`);
1468
+ return node.args.map(arg => renderArgument(arg, env)).join(', ');
1423
1469
  }
1424
1470
 
1425
1471
  /**
@@ -1428,13 +1474,14 @@ function csnToCdl(csn, options) {
1428
1474
  *
1429
1475
  * @param {any} arg
1430
1476
  * @param {CdlRenderEnvironment} env
1431
- * @param {string[]} additionalAllowedKeywords
1477
+ * @param {string[]} additionalKeywords
1432
1478
  * @return {string}
1433
1479
  */
1434
- function renderArgument(arg, env, additionalAllowedKeywords = []) {
1480
+ function renderArgument(arg, env, additionalKeywords = []) {
1435
1481
  // If the argument is a xpr with e.g. `=`, it may require parentheses.
1436
1482
  // For nested xpr, `renderExpr()` will already add parentheses.
1437
- return renderExpr(arg, env, true, !isSimpleFunctionExpression(arg && arg.xpr, additionalAllowedKeywords), true);
1483
+ env = { ...env, additionalKeywords };
1484
+ return renderExpr(arg, env, true, !isSimpleFunctionExpression(arg && arg.xpr, additionalKeywords), true);
1438
1485
  }
1439
1486
 
1440
1487
  /**
@@ -1639,7 +1686,7 @@ function csnToCdl(csn, options) {
1639
1686
  if (keywords.cdl_functions.includes(obj.func.toUpperCase()))
1640
1687
  return obj.func;
1641
1688
  const name = identifierRegex.test(obj.func) ? obj.func : quote(obj.func);
1642
- return `${name}(${renderArgs( obj, '=>', env )})`;
1689
+ return `${name}(${renderArguments( obj, '=>', env )})`;
1643
1690
  }
1644
1691
 
1645
1692
  /**
@@ -1762,27 +1809,33 @@ function increaseIndent(env) {
1762
1809
  * Quote the path steps with `![]` if necessary. For simple ids such as
1763
1810
  * `elem` use `quoteIdIfRequired` instead.
1764
1811
  *
1812
+ * In contrast to quoteIdIfRequired, does not handle additional keywords,
1813
+ * because it was not required, yet.
1814
+ *
1765
1815
  * @param {string} path
1766
1816
  * @returns {string}
1767
1817
  *
1768
1818
  * @todo For paths such as `E.key`, `key` does not have to be in quotes.
1769
1819
  */
1770
1820
  function quotePathIfRequired(path) {
1771
- return path.split('.').map(quoteIdIfRequired).join('.');
1821
+ return path.split('.').map(step => quoteIdIfRequired(step)).join('.');
1772
1822
  }
1773
1823
 
1774
1824
  /**
1775
1825
  * Quote the id with `![]` if necessary. For paths such as `E.key` use
1776
1826
  * `quotePathIfRequired` instead.
1777
1827
  *
1828
+ * Set additionalKeywords to an array of UPPERCASE keywords
1829
+ * that also need quoting, e.g. in special functions.
1830
+ *
1778
1831
  * @param {string} id
1832
+ * @param {string[]} [additionalKeywords]
1779
1833
  * @return {string}
1780
1834
  */
1781
- function quoteIdIfRequired(id) {
1835
+ function quoteIdIfRequired(id, additionalKeywords) {
1782
1836
  // Quote if required for CDL
1783
- if (requiresQuotingForCdl(id))
1837
+ if (requiresQuotingForCdl(id, additionalKeywords || []))
1784
1838
  return quote(id);
1785
-
1786
1839
  return id;
1787
1840
  }
1788
1841
 
@@ -1818,13 +1871,18 @@ function quote(id) {
1818
1871
  * does not match the first part of the `Identifier` rule of `language.g4`
1819
1872
  * or if 'id' is a reserved keyword.
1820
1873
  *
1874
+ * Set additionalKeywords to an array of UPPERCASE keywords
1875
+ * that also need quoting, e.g. in special functions.
1876
+ *
1821
1877
  * @param {string} id
1878
+ * @param {string[]} [additionalKeywords]
1822
1879
  * @return {boolean}
1823
1880
  */
1824
- function requiresQuotingForCdl(id) {
1881
+ function requiresQuotingForCdl(id, additionalKeywords) {
1825
1882
  return !identifierRegex.test(id) ||
1826
1883
  keywords.cdl.includes(id.toUpperCase()) ||
1827
- keywords.cdl_functions.includes(id.toUpperCase());
1884
+ keywords.cdl_functions.includes(id.toUpperCase()) ||
1885
+ additionalKeywords.includes(id.toUpperCase());
1828
1886
  }
1829
1887
 
1830
1888
  const functionExpressionOperatorsRequireParentheses = [
@@ -1258,7 +1258,7 @@ function toHdbcdsSource(csn, options) {
1258
1258
  const funcName = regex.test(x.func) ? x.func : quoteId(x.func);
1259
1259
  // we can't quote functions with parens, issue warning if it is a reserved keyword
1260
1260
  if (!funcWithoutParen(x, 'hana') && keywords.hdbcds.includes(uppercaseAndUnderscore(funcName)))
1261
- warning(null, x.$location, `The identifier “${uppercaseAndUnderscore(funcName)} is a SAP HANA keyword`);
1261
+ warning(null, x.$location, { id: uppercaseAndUnderscore(funcName) }, 'The identifier $(ID) is a SAP HANA keyword');
1262
1262
  return renderFunc(funcName, x, 'hana', a => renderArgs(a, '=>', env));
1263
1263
  }
1264
1264
 
@@ -157,7 +157,7 @@ function toSqlDdl(csn, options) {
157
157
  Render column removals as HANA SQL.
158
158
  */
159
159
  dropColumns(tableName, sqlIds) {
160
- return [ `ALTER TABLE ${tableName} DROP (${sqlIds.join(', ')});` ];
160
+ return [ `ALTER TABLE ${tableName} DROP ${options.sqlDialect === 'hana' ? '(' : ''}${sqlIds.join(', ')}${options.sqlDialect === 'hana' ? ')' : ''};` ];
161
161
  },
162
162
  /*
163
163
  Render association removals as HANA SQL.
@@ -221,7 +221,7 @@ function toSqlDdl(csn, options) {
221
221
  };
222
222
 
223
223
  // FIXME: Currently requires 'options.forHana', because it can only render HANA-ish SQL dialect
224
- if (!options.forHana)
224
+ if (!options.forHana && !isBetaEnabled(options, 'sqlExtensions'))
225
225
  throw new Error('toSql can currently only be used with HANA preprocessing');
226
226
 
227
227
  checkCSNVersion(csn, options);
@@ -1506,6 +1506,8 @@ function toSqlDdl(csn, options) {
1506
1506
  case 'sqlite':
1507
1507
  case 'hana':
1508
1508
  return 'CURRENT_TIMESTAMP';
1509
+ case 'h2':
1510
+ return 'current_timestamp';
1509
1511
  case 'postgres':
1510
1512
  return '(current_timestamp at time zone \'UTC\')';
1511
1513
  default:
@@ -1572,6 +1574,7 @@ function toSqlDdl(csn, options) {
1572
1574
  return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-FROM\'))';
1573
1575
  case 'postgres':
1574
1576
  return '(to_timestamp(current_setting(\'CAP.VALID_FROM\'), \'YYYY-MM-DD HH24:MI:SS.FF6\') at time zone \'UTC\')';
1577
+ case 'h2':
1575
1578
  case 'plain':
1576
1579
  return 'current_timestamp';
1577
1580
  default:
@@ -1590,6 +1593,7 @@ function toSqlDdl(csn, options) {
1590
1593
  return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-TO\'))';
1591
1594
  case 'postgres':
1592
1595
  return '(to_timestamp(current_setting(\'CAP.VALID_TO\'), \'YYYY-MM-DD HH24:MI:SS.FF6\') at time zone \'UTC\')';
1596
+ case 'h2':
1593
1597
  case 'plain':
1594
1598
  return 'current_timestamp';
1595
1599
  default:
@@ -283,6 +283,13 @@ const cdsToSqlTypes = {
283
283
  'cds.hana.BINARY': 'BINARY',
284
284
  'cds.hana.SMALLDECIMAL': 'DECIMAL',
285
285
  },
286
+ h2: {
287
+ 'cds.Binary': 'VARBINARY', // same as for plain
288
+ 'cds.LargeBinary': 'BINARY LARGE OBJECT', // BLOB would require a length!
289
+ 'cds.DecimalFloat': 'DECFLOAT', // Decimal and Decimal(p) is mapped to cds.DecimalFloat
290
+ 'cds.DateTime': 'TIMESTAMP(0)',
291
+ 'cds.Timestamp': 'TIMESTAMP(7)',
292
+ },
286
293
  postgres: {
287
294
  // See <https://www.postgresql.org/docs/current/datatype.html>
288
295
  'cds.String': 'VARCHAR',
@@ -39,6 +39,13 @@ const keywords = require( './base/keywords' );
39
39
 
40
40
  const sqlDialects = {
41
41
  plain: {},
42
+ h2: {
43
+ // See http://www.h2database.com/html/grammar.html#name
44
+ regularRegex: /^[A-Za-z_][A-Za-z_0-9]*$/,
45
+ reservedWords: keywords.h2,
46
+ effectiveName: name => name.toUpperCase(),
47
+ asDelimitedId: name => `"${ name.replace(/"/g, '""')}"`,
48
+ },
42
49
  sqlite: {
43
50
  regularRegex: /^[A-Za-z_][A-Za-z_$0-9]*$/,
44
51
  reservedWords: keywords.sqlite,