@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.
- package/CHANGELOG.md +88 -0
- package/bin/cdsc.js +18 -11
- package/bin/cdsv2m.js +7 -5
- package/doc/CHANGELOG_BETA.md +22 -0
- package/lib/api/main.js +306 -144
- package/lib/api/options.js +18 -6
- package/lib/api/validate.js +1 -1
- package/lib/base/message-registry.js +45 -10
- package/lib/base/messages.js +33 -16
- package/lib/base/model.js +4 -0
- package/lib/base/optionProcessorHelper.js +45 -176
- package/lib/checks/annotationsOData.js +49 -0
- package/lib/checks/elements.js +32 -34
- package/lib/checks/enricher.js +39 -3
- package/lib/checks/validator.js +8 -7
- package/lib/compiler/assert-consistency.js +40 -17
- package/lib/compiler/builtins.js +30 -53
- package/lib/compiler/checks.js +46 -14
- package/lib/compiler/cycle-detector.js +1 -4
- package/lib/compiler/define.js +35 -10
- package/lib/compiler/extend.js +21 -7
- package/lib/compiler/generate.js +3 -0
- package/lib/compiler/populate.js +5 -1
- package/lib/compiler/propagator.js +46 -9
- package/lib/compiler/resolve.js +94 -35
- package/lib/compiler/shared.js +60 -33
- package/lib/compiler/tweak-assocs.js +188 -92
- package/lib/compiler/utils.js +11 -1
- package/lib/edm/annotations/edmJson.js +41 -66
- package/lib/edm/annotations/genericTranslation.js +27 -9
- package/lib/edm/annotations/preprocessAnnotations.js +2 -3
- package/lib/edm/csn2edm.js +28 -11
- package/lib/edm/edmInboundChecks.js +58 -15
- package/lib/edm/edmPreprocessor.js +12 -16
- package/lib/edm/edmUtils.js +5 -2
- package/lib/gen/Dictionary.json +10 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +15 -2
- package/lib/gen/language.tokens +1 -0
- package/lib/gen/languageParser.js +6557 -5618
- package/lib/json/from-csn.js +4 -5
- package/lib/json/to-csn.js +29 -4
- package/lib/language/antlrParser.js +19 -1
- package/lib/language/errorStrategy.js +28 -7
- package/lib/language/genericAntlrParser.js +118 -24
- package/lib/language/textUtils.js +16 -0
- package/lib/main.d.ts +28 -3
- package/lib/main.js +3 -0
- package/lib/model/csnRefs.js +4 -1
- package/lib/model/csnUtils.js +20 -14
- package/lib/model/revealInternalProperties.js +5 -2
- package/lib/optionProcessor.js +23 -22
- package/lib/render/manageConstraints.js +13 -29
- package/lib/render/toCdl.js +47 -26
- package/lib/render/toHdbcds.js +63 -42
- package/lib/render/toRename.js +6 -10
- package/lib/render/toSql.js +71 -117
- package/lib/render/utils/common.js +41 -6
- package/lib/transform/.eslintrc.json +9 -1
- package/lib/transform/addTenantFields.js +228 -0
- package/lib/transform/db/applyTransformations.js +57 -4
- package/lib/transform/db/assertUnique.js +4 -4
- package/lib/transform/db/backlinks.js +13 -1
- package/lib/transform/db/cdsPersistence.js +1 -1
- package/lib/transform/db/expansion.js +24 -3
- package/lib/transform/db/flattening.js +70 -71
- package/lib/transform/db/killAnnotations.js +37 -0
- package/lib/transform/db/rewriteCalculatedElements.js +46 -6
- package/lib/transform/db/temporal.js +1 -1
- package/lib/transform/draft/db.js +2 -16
- package/lib/transform/draft/odata.js +3 -3
- package/lib/transform/effective/associations.js +3 -5
- package/lib/transform/effective/main.js +6 -9
- package/lib/transform/forOdata.js +26 -55
- package/lib/transform/forRelationalDB.js +38 -18
- package/lib/transform/odata/toFinalBaseType.js +3 -3
- package/lib/transform/odata/typesExposure.js +14 -5
- package/lib/transform/transformUtils.js +47 -34
- package/lib/transform/translateAssocsToJoins.js +45 -11
- package/lib/transform/universalCsn/coreComputed.js +1 -1
- package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
- package/package.json +7 -6
package/lib/render/toSql.js
CHANGED
|
@@ -4,13 +4,14 @@
|
|
|
4
4
|
const {
|
|
5
5
|
getLastPartOf, getLastPartOfRef,
|
|
6
6
|
hasValidSkipOrExists, isBuiltinType, generatedByCompilerVersion, getNormalizedQuery,
|
|
7
|
-
forEachDefinition, getResultingName,
|
|
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
|
-
} =
|
|
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(
|
|
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
|
|
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 = `${
|
|
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 ${
|
|
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}${
|
|
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 ${
|
|
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
|
-
|
|
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
|
-
|
|
1350
|
-
|
|
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
|
-
*
|
|
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
|
|
1490
|
-
|
|
1491
|
-
|
|
1489
|
+
function renderMagicVariable( ref, env ) {
|
|
1490
|
+
const magicReplacement = getVariableReplacement(ref, options);
|
|
1491
|
+
if (magicReplacement !== null)
|
|
1492
|
+
return renderStringForSql(magicReplacement, options.sqlDialect);
|
|
1492
1493
|
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1494
|
+
const name = pathName(ref);
|
|
1495
|
+
const result = variableForDialect(options, name);
|
|
1496
|
+
if (result)
|
|
1497
|
+
return result;
|
|
1496
1498
|
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
1541
|
-
|
|
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
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
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 $
|
|
351
|
+
// no fallback for $user.id and $tenant -> warning in call-site
|
|
348
352
|
'$user.locale': '\'en\'',
|
|
349
|
-
// $at
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|