@sap/cds-compiler 4.4.4 → 4.6.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 (82) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/bin/cdsc.js +18 -11
  3. package/bin/cdsv2m.js +7 -5
  4. package/doc/CHANGELOG_BETA.md +22 -0
  5. package/lib/api/main.js +306 -144
  6. package/lib/api/options.js +18 -6
  7. package/lib/api/validate.js +1 -1
  8. package/lib/base/message-registry.js +45 -10
  9. package/lib/base/messages.js +33 -16
  10. package/lib/base/model.js +4 -0
  11. package/lib/base/optionProcessorHelper.js +45 -176
  12. package/lib/checks/annotationsOData.js +49 -0
  13. package/lib/checks/elements.js +32 -34
  14. package/lib/checks/enricher.js +39 -3
  15. package/lib/checks/validator.js +8 -7
  16. package/lib/compiler/assert-consistency.js +40 -17
  17. package/lib/compiler/builtins.js +30 -53
  18. package/lib/compiler/checks.js +46 -14
  19. package/lib/compiler/cycle-detector.js +1 -4
  20. package/lib/compiler/define.js +35 -10
  21. package/lib/compiler/extend.js +21 -7
  22. package/lib/compiler/generate.js +3 -0
  23. package/lib/compiler/populate.js +5 -1
  24. package/lib/compiler/propagator.js +46 -9
  25. package/lib/compiler/resolve.js +94 -35
  26. package/lib/compiler/shared.js +60 -33
  27. package/lib/compiler/tweak-assocs.js +188 -92
  28. package/lib/compiler/utils.js +11 -1
  29. package/lib/edm/annotations/edmJson.js +41 -66
  30. package/lib/edm/annotations/genericTranslation.js +27 -9
  31. package/lib/edm/annotations/preprocessAnnotations.js +2 -3
  32. package/lib/edm/csn2edm.js +28 -11
  33. package/lib/edm/edmInboundChecks.js +58 -15
  34. package/lib/edm/edmPreprocessor.js +12 -16
  35. package/lib/edm/edmUtils.js +5 -2
  36. package/lib/gen/Dictionary.json +10 -0
  37. package/lib/gen/language.checksum +1 -1
  38. package/lib/gen/language.interp +15 -2
  39. package/lib/gen/language.tokens +1 -0
  40. package/lib/gen/languageParser.js +6557 -5618
  41. package/lib/json/from-csn.js +4 -5
  42. package/lib/json/to-csn.js +29 -4
  43. package/lib/language/antlrParser.js +19 -1
  44. package/lib/language/errorStrategy.js +28 -7
  45. package/lib/language/genericAntlrParser.js +118 -24
  46. package/lib/language/textUtils.js +16 -0
  47. package/lib/main.d.ts +28 -3
  48. package/lib/main.js +3 -0
  49. package/lib/model/csnRefs.js +4 -1
  50. package/lib/model/csnUtils.js +20 -14
  51. package/lib/model/revealInternalProperties.js +5 -2
  52. package/lib/optionProcessor.js +23 -22
  53. package/lib/render/manageConstraints.js +13 -29
  54. package/lib/render/toCdl.js +47 -26
  55. package/lib/render/toHdbcds.js +63 -42
  56. package/lib/render/toRename.js +6 -10
  57. package/lib/render/toSql.js +71 -117
  58. package/lib/render/utils/common.js +41 -6
  59. package/lib/transform/.eslintrc.json +9 -1
  60. package/lib/transform/addTenantFields.js +228 -0
  61. package/lib/transform/db/applyTransformations.js +57 -4
  62. package/lib/transform/db/assertUnique.js +4 -4
  63. package/lib/transform/db/backlinks.js +13 -1
  64. package/lib/transform/db/cdsPersistence.js +1 -1
  65. package/lib/transform/db/expansion.js +24 -3
  66. package/lib/transform/db/flattening.js +70 -71
  67. package/lib/transform/db/killAnnotations.js +37 -0
  68. package/lib/transform/db/rewriteCalculatedElements.js +46 -6
  69. package/lib/transform/db/temporal.js +1 -1
  70. package/lib/transform/draft/db.js +2 -16
  71. package/lib/transform/draft/odata.js +3 -3
  72. package/lib/transform/effective/associations.js +3 -5
  73. package/lib/transform/effective/main.js +6 -9
  74. package/lib/transform/forOdata.js +26 -55
  75. package/lib/transform/forRelationalDB.js +38 -18
  76. package/lib/transform/odata/toFinalBaseType.js +3 -3
  77. package/lib/transform/odata/typesExposure.js +14 -5
  78. package/lib/transform/transformUtils.js +47 -34
  79. package/lib/transform/translateAssocsToJoins.js +45 -11
  80. package/lib/transform/universalCsn/coreComputed.js +1 -1
  81. package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
  82. package/package.json +7 -6
@@ -4,13 +4,14 @@
4
4
  const {
5
5
  getLastPartOf, getLastPartOfRef,
6
6
  hasValidSkipOrExists, isBuiltinType, generatedByCompilerVersion, getNormalizedQuery,
7
- forEachDefinition, getResultingName, getVariableReplacement,
7
+ forEachDefinition, getResultingName,
8
+ getVariableReplacement, isMagicVariable, pathName,
8
9
  } = require('../model/csnUtils');
9
10
  const { forEach, forEachValue, forEachKey } = require('../utils/objectUtils');
10
11
  const {
11
12
  renderFunc, cdsToSqlTypes, getHanaComment, hasHanaComment,
12
13
  getSqlSnippets, createExpressionRenderer, withoutCast,
13
- variableForDialect,
14
+ variableForDialect, isVariableReplacementRequired,
14
15
  } = require('./utils/common');
15
16
  const {
16
17
  getDeltaRenderer,
@@ -20,7 +21,6 @@ const {
20
21
  } = require('./utils/sql');
21
22
  const DuplicateChecker = require('./DuplicateChecker');
22
23
  const { checkCSNVersion } = require('../json/csnVersion');
23
- const { makeMessageFunction } = require('../base/messages');
24
24
  const { timetrace } = require('../utils/timetrace');
25
25
  const { isBetaEnabled, isDeprecatedEnabled } = require('../base/model');
26
26
  const { smartFuncId } = require('../sql-identifier');
@@ -28,6 +28,7 @@ const { sortCsn } = require('../json/to-csn');
28
28
  const { manageConstraints, manageConstraint } = require('./manageConstraints');
29
29
  const { renderUniqueConstraintString, renderUniqueConstraintDrop, renderUniqueConstraintAdd } = require('./utils/unique');
30
30
  const { ModelError, CompilerAssertion } = require('../base/error');
31
+ const { pathId } = require('../model/csnRefs');
31
32
 
32
33
  class SqlRenderEnvironment {
33
34
  indent = '';
@@ -99,13 +100,14 @@ class SqlRenderEnvironment {
99
100
  *
100
101
  * @param {CSN.Model} csn HANA transformed CSN
101
102
  * @param {CSN.Options} options Transformation options
103
+ * @param {object} messageFunctions Message functions such as `error()`, `info()`, …
102
104
  * @returns {object} Dictionary of artifact-type:artifacts, where artifacts is a dictionary of name:content
103
105
  */
104
- function toSqlDdl( csn, options ) {
106
+ function toSqlDdl( csn, options, messageFunctions ) {
105
107
  timetrace.start('SQL rendering');
106
108
  const {
107
109
  error, warning, info, throwWithAnyError,
108
- } = makeMessageFunction(csn, options, 'to.sql');
110
+ } = messageFunctions;
109
111
  const { quoteSqlId, prepareIdentifier, renderArtifactName } = getIdentifierUtils(csn, options);
110
112
  const reportedMissingReplacements = Object.create(null);
111
113
 
@@ -608,7 +610,7 @@ function toSqlDdl( csn, options ) {
608
610
  result += renderTechnicalConfiguration(art.technicalConfig, childEnv);
609
611
 
610
612
 
611
- if (options.sqlDialect === 'hana') {
613
+ if (options.sqlDialect === 'hana' && options.withHanaAssociations) {
612
614
  const associations = Object.keys(art.elements)
613
615
  .map(name => renderAssociationElement(name, art.elements[name], childEnv))
614
616
  .filter(s => s !== '')
@@ -761,7 +763,6 @@ function toSqlDdl( csn, options ) {
761
763
  * is not an association.
762
764
  * Any change to the cardinality rendering must be reflected in A2J mapAssocToJoinCardinality() as well.
763
765
  *
764
- * @todo Duplicate check
765
766
  * @param {string} elementName Name of the element to render
766
767
  * @param {CSN.Element} elm CSN element
767
768
  * @param {SqlRenderEnvironment} env Render environment
@@ -772,6 +773,10 @@ function toSqlDdl( csn, options ) {
772
773
  let result = '';
773
774
  if (elm.target) {
774
775
  result += env.indent;
776
+ const on = (!options.tenantAsColumn || elm.target['@cds.tenant.independent'])
777
+ ? elm.on
778
+ : [ { ref: [ 'tenant' ] }, '=', { ref: [ elementName, 'tenant' ] },
779
+ 'and', { xpr: elm.on } ];
775
780
  if (elm.cardinality) {
776
781
  if (isBetaEnabled(options, 'hanaAssocRealCardinality') && elm.cardinality.src && elm.cardinality.src === 1)
777
782
  result += 'ONE TO ';
@@ -788,7 +793,7 @@ function toSqlDdl( csn, options ) {
788
793
  }
789
794
  result += ' JOIN ';
790
795
  result += `${renderArtifactName(elm.target)} AS ${quoteSqlId(elementName)} ON (`;
791
- result += `${renderExpr(elm.on, env.withSubPath([ 'on' ]))})`;
796
+ result += `${renderExpr(on, env.withSubPath([ 'on' ]))})`;
792
797
  }
793
798
  return result;
794
799
  }
@@ -905,12 +910,11 @@ function toSqlDdl( csn, options ) {
905
910
  *
906
911
  * Returns the source as a string.
907
912
  *
908
- * @todo Misleading name, should be something like 'renderQueryFrom'. All the query parts should probably also be rearranged.
909
913
  * @param {object} source Query source
910
914
  * @param {SqlRenderEnvironment} env Render environment
911
915
  * @returns {string} Rendered view source
912
916
  */
913
- function renderViewSource( source, env ) {
917
+ function renderQuerySource( source, env ) {
914
918
  // Sub-SELECT
915
919
  if (source.SELECT || source.SET) {
916
920
  let result = `(${renderQuery(source, env.withIncreasedIndent())})`;
@@ -922,12 +926,12 @@ function toSqlDdl( csn, options ) {
922
926
  // JOIN
923
927
  else if (source.join) {
924
928
  // One join operation, possibly with ON-condition
925
- let result = `${renderViewSource(source.args[0], env.withSubPath([ 'args', 0 ]))}`;
929
+ let result = `${renderQuerySource(source.args[0], env.withSubPath([ 'args', 0 ]))}`;
926
930
  for (let i = 1; i < source.args.length; i++) {
927
931
  result = `(${result} ${source.join.toUpperCase()} `;
928
932
  if (options.sqlDialect === 'hana')
929
933
  result += renderJoinCardinality(source.cardinality);
930
- result += `JOIN ${renderViewSource(source.args[i], env.withSubPath([ 'args', i ]))}`;
934
+ result += `JOIN ${renderQuerySource(source.args[i], env.withSubPath([ 'args', i ]))}`;
931
935
  if (source.on)
932
936
  result += ` ON ${renderExpr(source.on, env.withSubPath([ 'on' ]))}`;
933
937
 
@@ -1048,7 +1052,7 @@ function toSqlDdl( csn, options ) {
1048
1052
 
1049
1053
  // Add any path steps (possibly with parameters and filters) that may follow after that
1050
1054
  if (path.ref.length > 1)
1051
- result += `${sep}${renderExpr({ ref: path.ref.slice(1) }, env)}`;
1055
+ result += `${sep}${renderTypeRef({ ref: path.ref.slice(1) }, env)}`;
1052
1056
 
1053
1057
  return result;
1054
1058
  }
@@ -1150,7 +1154,7 @@ function toSqlDdl( csn, options ) {
1150
1154
  .map(name => renderAssociationElement(name, art.elements[name], childEnv))
1151
1155
  .filter(s => s !== '')
1152
1156
  .join(',\n');
1153
- if (associations !== '' && options.sqlDialect === 'hana') {
1157
+ if (associations !== '' && options.sqlDialect === 'hana' && options.withHanaAssociations) {
1154
1158
  result += `${env.indent}\nWITH ASSOCIATIONS (\n${associations}\n`;
1155
1159
  result += `${env.indent})`;
1156
1160
  }
@@ -1257,7 +1261,7 @@ function toSqlDdl( csn, options ) {
1257
1261
  })
1258
1262
  .filter(s => s !== '')
1259
1263
  .join(',\n')}\n`;
1260
- result += `${env.indent}FROM ${renderViewSource( select.from, env.withSubPath([ 'from' ]))}`;
1264
+ result += `${env.indent}FROM ${renderQuerySource( select.from, env.withSubPath([ 'from' ]))}`;
1261
1265
  if (select.where)
1262
1266
  result += `\n${env.indent}WHERE ${renderExpr(select.where, env.withSubPath([ 'where' ]))}`;
1263
1267
 
@@ -1335,23 +1339,15 @@ function toSqlDdl( csn, options ) {
1335
1339
  function renderTypeReference( elm, env ) {
1336
1340
  let result = '';
1337
1341
 
1338
- // Anonymous structured type: Not supported with SQL (but shouldn't happen anyway after forHana flattened them)
1339
1342
  if (!elm.type && !elm.value) {
1340
- if (!elm.elements)
1343
+ // Anonymous structured type: Not supported with SQL, but doesn't happen anyway after flattening.
1344
+ if (options.testMode)
1341
1345
  throw new ModelError(`to.sql(): Missing type of: ${JSON.stringify(env.path)}`);
1342
-
1343
- // TODO: Signal is not covered by tests
1344
- error(null, env.path,
1345
- 'Anonymous structured types are not supported for conversion to SQL');
1346
1346
  return result;
1347
1347
  }
1348
-
1349
- // Association type
1350
- if (elm.target) {
1351
- // TODO: Signal is not covered by tests
1352
- // We can't do associations yet
1353
- error(null, env.path,
1354
- 'Association and composition types are not yet supported for conversion to SQL');
1348
+ else if (elm.target) {
1349
+ if (options.testMode)
1350
+ throw new ModelError(`to.sql(): Unexpected association in: ${JSON.stringify(env.path)}`);
1355
1351
  return result;
1356
1352
  }
1357
1353
 
@@ -1370,7 +1366,6 @@ function toSqlDdl( csn, options ) {
1370
1366
  if (elm.value) {
1371
1367
  if (!elm.value.stored)
1372
1368
  throw new CompilerAssertion('Found calculated element on-read in rendering; should have been replaced!');
1373
- // TODO: Properly implement calculated elements on-write:
1374
1369
  // The SQL standard 2016 describes the syntax in section 11.3 - 11.4
1375
1370
  // of the SQL Foundation spec (for 2003 in 5WD-02-Foundation-2003-09.pdf). Summarized:
1376
1371
  // <generation clause> ::= GENERATED ALWAYS AS '(' <value expression> ')'
@@ -1383,7 +1378,6 @@ function toSqlDdl( csn, options ) {
1383
1378
  return result;
1384
1379
  }
1385
1380
 
1386
-
1387
1381
  /**
1388
1382
  * Render the name of a builtin CDS type
1389
1383
  *
@@ -1481,49 +1475,54 @@ function toSqlDdl( csn, options ) {
1481
1475
  }
1482
1476
  }
1483
1477
 
1478
+
1484
1479
  /**
1485
- * @param {object} x
1480
+ * Render a magic variable. Values are determined in following order:
1481
+ * 1. User defined replacement in options.variableReplacements
1482
+ * 2. Predefined fallback values
1483
+ * 3. Rendering of the variable as a string (i.e. its name) + warning
1484
+ *
1485
+ * @param {CSN.Path} ref
1486
1486
  * @param {object} env
1487
1487
  * @return {string}
1488
1488
  */
1489
- function renderExpressionRef( x, env ) {
1490
- if (!x.param && !x.global) {
1491
- const magicReplacement = getVariableReplacement(x.ref, options);
1489
+ function renderMagicVariable( ref, env ) {
1490
+ const magicReplacement = getVariableReplacement(ref, options);
1491
+ if (magicReplacement !== null)
1492
+ return renderStringForSql(magicReplacement, options.sqlDialect);
1492
1493
 
1493
- if (x.ref[0] === '$user') {
1494
- if (magicReplacement !== null)
1495
- return renderStringForSql(magicReplacement, options.sqlDialect);
1494
+ const name = pathName(ref);
1495
+ const result = variableForDialect(options, name);
1496
+ if (result)
1497
+ return result;
1496
1498
 
1497
- const result = render$user(x);
1498
- // Invalid second path step doesn't cause a return
1499
- if (result)
1500
- return result;
1501
- }
1502
- else if (x.ref[0] === '$at' || x.ref[0] === '$valid') {
1503
- const result = render$at(x);
1504
- // Invalid second path step doesn't cause a return
1505
- if (result)
1506
- return result;
1507
- }
1508
- else if (x.ref[0] === '$session' && magicReplacement !== null) {
1509
- return renderStringForSql(magicReplacement, options.sqlDialect);
1510
- }
1511
- else if (x.ref[0] === '$now') { // TODO: Can there be cases where $now is followed by something?
1512
- switch (options.sqlDialect) {
1513
- case 'plain':
1514
- case 'sqlite':
1515
- case 'hana':
1516
- return 'CURRENT_TIMESTAMP';
1517
- case 'h2':
1518
- case 'postgres':
1519
- return 'current_timestamp';
1520
- default:
1521
- return quoteSqlId(x.ref[0]);
1522
- }
1523
- }
1499
+ if (isVariableReplacementRequired(name)) {
1500
+ reportedMissingReplacements[name] = true;
1501
+ error('ref-undefined-var', env.path, { '#': 'value', id: name, option: 'variableReplacements' });
1502
+ }
1503
+ else if (!reportedMissingReplacements[name]) {
1504
+ reportedMissingReplacements[name] = true;
1505
+ warning('ref-unsupported-variable', env.path, { name, option: 'variableReplacements' },
1506
+ 'Variable $(NAME) is not supported. Use option $(OPTION) to specify a value for $(NAME)');
1524
1507
  }
1508
+
1509
+ return renderStringForSql(name, options.sqlDialect);
1510
+ }
1511
+
1512
+ /**
1513
+ * Must not be used for type refs, as something like `$user` will be interpreted as a magic
1514
+ * variable and not definition name.
1515
+ *
1516
+ * @param {object} x
1517
+ * @param {object} env
1518
+ * @return {string}
1519
+ */
1520
+ function renderExpressionRef( x, env ) {
1521
+ if (!x.param && isMagicVariable(pathId(x.ref[0])))
1522
+ return renderMagicVariable(x.ref, env);
1523
+
1525
1524
  // FIXME: We currently cannot distinguish whether '$parameters' was quoted or not - we
1526
- // assume that it was not if the path has length 2 (
1525
+ // assume that it was not if the path has length 2
1527
1526
  if (firstPathStepId(x.ref) === '$parameters' && x.ref.length === 2) {
1528
1527
  // Parameters must be uppercased and unquoted in SQL
1529
1528
  return `:${x.ref[1].toUpperCase()}`;
@@ -1536,43 +1535,10 @@ function toSqlDdl( csn, options ) {
1536
1535
  .join('.');
1537
1536
  }
1538
1537
 
1539
- /**
1540
- * @param {object} x
1541
- * @returns {string|null} Null in case of an invalid second path step
1542
- */
1543
- function render$user( x ) {
1544
- if (x.ref.length > 2 || typeof x.ref[1] !== 'string')
1545
- return null; // `$user` can only have two path steps
1546
-
1547
- const name = `$user.${x.ref[1]}`;
1548
- const result = variableForDialect(options, name);
1549
- if (result)
1550
- return result;
1551
-
1552
- if (!reportedMissingReplacements[name]) {
1553
- reportedMissingReplacements[name] = true;
1554
- warning('ref-unsupported-variable', null, { name, option: 'variableReplacements' },
1555
- 'Variable $(NAME) is not supported. Use option $(OPTION) to specify a value for $(NAME)');
1556
- }
1557
- return `'${name}'`;
1558
- }
1559
-
1560
- /**
1561
- * @param {object} x
1562
- * @returns {string|null} Null in case of an invalid second path step
1563
- */
1564
- function render$at( x ) {
1565
- if (x.ref.length > 2 || typeof x.ref[1] !== 'string')
1566
- return null; // `$at` can only have two path steps
1567
-
1568
- const name = `$at.${x.ref[1]}`;
1569
- const config = variableForDialect(options, name);
1570
- if (config)
1571
- return config;
1572
-
1573
- if (options.testMode)
1574
- throw new CompilerAssertion(`render$at: unhandled sqlDialect '${options.sqlDialect}' when rendering ${x.ref.join('.')}`);
1575
- return null;
1538
+ function renderTypeRef( x, env ) {
1539
+ const prefix = x.param ? ':' : '';
1540
+ const ref = x.ref.map((step, index) => renderPathStep(step, index, env.withSubPath([ 'ref', index ]))).join('.');
1541
+ return `${prefix}${ref}`;
1576
1542
  }
1577
1543
 
1578
1544
  /**
@@ -1586,22 +1552,10 @@ function toSqlDdl( csn, options ) {
1586
1552
  function renderPathStep( s, idx, env ) {
1587
1553
  // Simple id or absolute name
1588
1554
  if (typeof (s) === 'string') {
1589
- // TODO: When is this actually executed and not handled already in renderExpr?
1590
- const magicForHana = {
1591
- '$user.id': 'SESSION_CONTEXT(\'APPLICATIONUSER\')',
1592
- '$user.locale': 'SESSION_CONTEXT(\'LOCALE\')',
1593
- };
1594
1555
  // Some magic for first path steps
1595
- if (idx === 0) {
1596
- // HANA-specific translation of '$now' and '$user'
1597
- // FIXME: this is all not enough: we might need an explicit select item alias
1598
- if (options.sqlDialect === 'hana' && magicForHana[s])
1599
- return magicForHana[s];
1600
-
1601
- // Ignore initial $projection and initial $self
1602
- if (s === '$projection' || s === '$self')
1603
- return '';
1604
- }
1556
+ // Ignore initial $projection and initial $self
1557
+ if (idx === 0 && (s === '$projection' || s === '$self'))
1558
+ return '';
1605
1559
  return quoteSqlId(s);
1606
1560
  }
1607
1561
  // ID with filters or parameters
@@ -262,12 +262,14 @@ const cdsToSqlTypes = {
262
262
  'cds.UUID': 'NVARCHAR', // changed to cds.String earlier
263
263
  'cds.hana.ST_POINT': 'CHAR', // CHAR is implicit fallback used in toSql - make it explicit here
264
264
  'cds.hana.ST_GEOMETRY': 'CHAR', // CHAR is implicit fallback used in toSql - make it explicit here
265
+ 'cds.Vector': 'NVARCHAR', // Not supported; see #11725
265
266
  },
266
267
  hana: {
267
268
  'cds.hana.SMALLDECIMAL': 'SMALLDECIMAL',
268
269
  'cds.DateTime': 'SECONDDATE',
269
270
  'cds.hana.ST_POINT': 'ST_POINT',
270
271
  'cds.hana.ST_GEOMETRY': 'ST_GEOMETRY',
272
+ 'cds.Vector': 'REAL_VECTOR', // FIXME: test me
271
273
  },
272
274
  sqlite: {
273
275
  'cds.Date': 'DATE_TEXT',
@@ -277,6 +279,7 @@ const cdsToSqlTypes = {
277
279
  'cds.Binary': 'BINARY_BLOB',
278
280
  'cds.hana.BINARY': 'BINARY_BLOB',
279
281
  'cds.hana.SMALLDECIMAL': 'SMALLDECIMAL',
282
+ 'cds.Vector': 'BINARY_BLOB', // Not supported; see #11725
280
283
  },
281
284
  plain: {
282
285
  'cds.Binary': 'VARBINARY',
@@ -298,6 +301,7 @@ const cdsToSqlTypes = {
298
301
  'cds.Binary': 'BYTEA',
299
302
  'cds.Double': 'FLOAT8',
300
303
  'cds.UInt8': 'INTEGER', // Not equivalent
304
+ 'cds.Vector': 'VARCHAR', // Not supported; see #11725
301
305
  },
302
306
  };
303
307
 
@@ -344,30 +348,39 @@ function getDefaultTypeLengths( sqlDialect ) {
344
348
  */
345
349
  const variablesToSql = {
346
350
  fallback: {
347
- // no fallback for $user.id and $user.tenant -> warning in call-site
351
+ // no fallback for $user.id and $tenant -> warning in call-site
348
352
  '$user.locale': '\'en\'',
349
- // $at.* are handled in all dialects -> there is no need for a fallback
353
+ // $at.*/$now are handled in all dialects -> there is no need for a fallback
350
354
  },
351
355
  hana: {
352
356
  '$user.id': "SESSION_CONTEXT('APPLICATIONUSER')",
353
357
  '$user.locale': "SESSION_CONTEXT('LOCALE')",
354
- '$user.tenant': "SESSION_CONTEXT('TENANT')",
358
+ $tenant: "SESSION_CONTEXT('APPLICATIONTENANT')",
355
359
  '$at.from': "TO_TIMESTAMP(SESSION_CONTEXT('VALID-FROM'))",
356
360
  '$at.to': "TO_TIMESTAMP(SESSION_CONTEXT('VALID-TO'))",
361
+ '$valid.from': "TO_TIMESTAMP(SESSION_CONTEXT('VALID-FROM'))",
362
+ '$valid.to': "TO_TIMESTAMP(SESSION_CONTEXT('VALID-TO'))",
363
+ $now: 'CURRENT_TIMESTAMP',
357
364
  },
358
365
  postgres: {
359
366
  '$user.id': "current_setting('cap.applicationuser')",
360
367
  '$user.locale': "current_setting('cap.locale')",
361
- '$user.tenant': "current_setting('cap.tenant')",
368
+ $tenant: "current_setting('cap.tenant')",
362
369
  '$at.from': "current_setting('cap.valid_from')::timestamp",
363
370
  '$at.to': "current_setting('cap.valid_to')::timestamp",
371
+ '$valid.from': "current_setting('cap.valid_from')::timestamp",
372
+ '$valid.to': "current_setting('cap.valid_to')::timestamp",
373
+ $now: 'current_timestamp',
364
374
  },
365
375
  'better-sqlite': {
366
376
  '$user.id': "session_context( '$user.id' )",
367
377
  '$user.locale': "session_context( '$user.locale' )",
368
- '$user.tenant': "session_context( '$user.tenant' )",
378
+ $tenant: "session_context( '$tenant' )",
369
379
  '$at.from': "session_context( '$valid.from' )",
370
380
  '$at.to': "session_context( '$valid.to' )",
381
+ '$valid.from': "session_context( '$valid.from' )",
382
+ '$valid.to': "session_context( '$valid.to' )",
383
+ $now: 'CURRENT_TIMESTAMP',
371
384
  },
372
385
  sqlite: {
373
386
  // For sqlite, we render the string-format-time (strftime) function.
@@ -377,17 +390,26 @@ const variablesToSql = {
377
390
  '$at.from': "strftime('%Y-%m-%dT%H:%M:%S.000Z', 'now')",
378
391
  // + 1ms compared to $at.from
379
392
  '$at.to': "strftime('%Y-%m-%dT%H:%M:%S.001Z', 'now')",
393
+ '$valid.from': "strftime('%Y-%m-%dT%H:%M:%S.000Z', 'now')",
394
+ '$valid.to': "strftime('%Y-%m-%dT%H:%M:%S.001Z', 'now')",
395
+ $now: 'CURRENT_TIMESTAMP',
380
396
  },
381
397
  plain: {
382
398
  '$at.from': 'current_timestamp',
383
399
  '$at.to': 'current_timestamp',
400
+ '$valid.from': 'current_timestamp',
401
+ '$valid.to': 'current_timestamp',
402
+ $now: 'CURRENT_TIMESTAMP',
384
403
  },
385
404
  h2: {
386
405
  '$user.id': '@applicationuser',
387
406
  '$user.locale': '@locale',
388
- '$user.tenant': '@tenant',
407
+ $tenant: '@tenant',
389
408
  '$at.from': '@valid_from',
390
409
  '$at.to': '@valid_to',
410
+ '$valid.from': '@valid_from',
411
+ '$valid.to': '@valid_to',
412
+ $now: 'current_timestamp',
391
413
  },
392
414
  };
393
415
 
@@ -407,6 +429,18 @@ function variableForDialect( options, variable ) {
407
429
  return variablesToSql[dialect]?.[variable] || variablesToSql.fallback[variable] || null;
408
430
  }
409
431
 
432
+ /**
433
+ * Whether a replacement is required for the given variable (e.g. '$user.id').
434
+ * Some variables such as `$user.id` are not required to have replacement values, even if
435
+ * there is no proper fallback via `variableForDialect(…)` (for example in sqlDialect 'plain').
436
+ *
437
+ * @param {string} name
438
+ * @return {boolean}
439
+ */
440
+ function isVariableReplacementRequired( name ) {
441
+ const notRequired = [ '$user.id', '$user.locale', '$tenant' ];
442
+ return !notRequired.includes(name);
443
+ }
410
444
 
411
445
  /**
412
446
  * Get the element matching the column
@@ -690,6 +724,7 @@ module.exports = {
690
724
  cdsToSqlTypes,
691
725
  cdsToHdbcdsTypes,
692
726
  variableForDialect,
727
+ isVariableReplacementRequired,
693
728
  hasHanaComment,
694
729
  getHanaComment,
695
730
  findElement,
@@ -1,5 +1,13 @@
1
1
  {
2
2
  "rules": {
3
3
  "no-shadow": "off"
4
- }
4
+ },
5
+ "overrides": [
6
+ {
7
+ "files": [
8
+ "addTenantFields.js"
9
+ ],
10
+ "extends": "../../.eslintrc-ydkjsi.json"
11
+ }
12
+ ]
5
13
  }