@sap/cds-compiler 4.4.2 → 4.5.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 +58 -0
- package/bin/cdsc.js +5 -0
- package/bin/cdsv2m.js +7 -5
- package/doc/CHANGELOG_BETA.md +16 -0
- package/lib/api/main.js +68 -47
- package/lib/api/options.js +10 -6
- package/lib/api/validate.js +1 -1
- package/lib/base/message-registry.js +28 -6
- package/lib/base/messages.js +18 -13
- package/lib/base/model.js +3 -0
- package/lib/checks/annotationsOData.js +49 -0
- package/lib/checks/validator.js +6 -4
- package/lib/compiler/assert-consistency.js +38 -16
- package/lib/compiler/builtins.js +10 -49
- package/lib/compiler/checks.js +16 -8
- package/lib/compiler/cycle-detector.js +1 -4
- package/lib/compiler/define.js +4 -1
- 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 +68 -14
- package/lib/compiler/shared.js +44 -27
- package/lib/compiler/tweak-assocs.js +158 -37
- package/lib/compiler/utils.js +9 -0
- package/lib/edm/annotations/edmJson.js +35 -61
- package/lib/edm/annotations/genericTranslation.js +13 -5
- package/lib/edm/annotations/preprocessAnnotations.js +2 -3
- package/lib/edm/csn2edm.js +4 -1
- package/lib/edm/edmInboundChecks.js +59 -15
- package/lib/edm/edmPreprocessor.js +1 -7
- package/lib/gen/Dictionary.json +8 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +12 -2
- package/lib/gen/languageParser.js +6095 -5195
- package/lib/json/from-csn.js +4 -5
- package/lib/json/to-csn.js +22 -3
- package/lib/language/errorStrategy.js +7 -3
- package/lib/language/genericAntlrParser.js +120 -24
- package/lib/language/textUtils.js +16 -0
- package/lib/model/csnUtils.js +9 -8
- package/lib/model/revealInternalProperties.js +5 -2
- package/lib/modelCompare/compare.js +10 -4
- package/lib/optionProcessor.js +2 -3
- package/lib/render/toCdl.js +31 -13
- package/lib/render/toHdbcds.js +20 -30
- package/lib/render/toSql.js +33 -54
- package/lib/render/utils/common.js +24 -6
- package/lib/transform/db/applyTransformations.js +59 -2
- package/lib/transform/db/backlinks.js +13 -1
- package/lib/transform/db/expansion.js +24 -3
- package/lib/transform/db/flattening.js +2 -2
- package/lib/transform/db/killAnnotations.js +37 -0
- package/lib/transform/db/rewriteCalculatedElements.js +46 -6
- package/lib/transform/forOdata.js +13 -46
- package/lib/transform/forRelationalDB.js +2 -1
- package/lib/transform/translateAssocsToJoins.js +13 -4
- package/lib/transform/universalCsn/coreComputed.js +1 -1
- package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
- package/package.json +7 -6
package/lib/render/toHdbcds.js
CHANGED
|
@@ -4,12 +4,13 @@ const {
|
|
|
4
4
|
getLastPartOf, getLastPartOfRef,
|
|
5
5
|
hasValidSkipOrExists, isBuiltinType, generatedByCompilerVersion, getNormalizedQuery,
|
|
6
6
|
getRootArtifactName, getResultingName, getNamespace, forEachMember, getVariableReplacement, hasAnnotationValue,
|
|
7
|
+
pathName,
|
|
7
8
|
} = require('../model/csnUtils');
|
|
8
9
|
const keywords = require('../base/keywords');
|
|
9
10
|
const {
|
|
10
11
|
renderFunc, createExpressionRenderer, getRealName, addContextMarkers, addIntermediateContexts,
|
|
11
12
|
hasHanaComment, getHanaComment, funcWithoutParen, getSqlSnippets,
|
|
12
|
-
cdsToSqlTypes, cdsToHdbcdsTypes, withoutCast,
|
|
13
|
+
cdsToSqlTypes, cdsToHdbcdsTypes, withoutCast, variableForDialect,
|
|
13
14
|
} = require('./utils/common');
|
|
14
15
|
const {
|
|
15
16
|
renderReferentialConstraint,
|
|
@@ -1209,11 +1210,10 @@ function toHdbcdsSource( csn, options ) {
|
|
|
1209
1210
|
*
|
|
1210
1211
|
* @param {string|object} s Path step
|
|
1211
1212
|
* @param {number} idx Path position
|
|
1212
|
-
* @param {any[]} ref
|
|
1213
1213
|
* @param {HdbcdsRenderEnvironment} env
|
|
1214
1214
|
* @returns {string} Rendered path step
|
|
1215
1215
|
*/
|
|
1216
|
-
function renderPathStep( s, idx,
|
|
1216
|
+
function renderPathStep( s, idx, env ) {
|
|
1217
1217
|
// Simple id or absolute name
|
|
1218
1218
|
if (typeof s === 'string') {
|
|
1219
1219
|
// HANA-specific extra magic (should actually be in forRelationalDB)
|
|
@@ -1227,12 +1227,6 @@ function toHdbcdsSource( csn, options ) {
|
|
|
1227
1227
|
return plainNames ? renderAbsoluteNamePlain(env.currentArtifactName, env)
|
|
1228
1228
|
: renderAbsoluteNameWithQuotes(env.currentArtifactName, env);
|
|
1229
1229
|
}
|
|
1230
|
-
// HANA-specific translation of '$now' and '$user'
|
|
1231
|
-
if (s === '$now' && ref.length === 1)
|
|
1232
|
-
return 'CURRENT_TIMESTAMP';
|
|
1233
|
-
|
|
1234
|
-
// In first path position, do not quote $projection and magic $-variables like CURRENT_DATE, $now etc.
|
|
1235
|
-
// FIXME: We should rather explicitly recognize quoting somehow
|
|
1236
1230
|
|
|
1237
1231
|
// TODO: quote $parameters if it doesn't reference a parameter, this requires knowledge about the kind
|
|
1238
1232
|
// Example: both views are correct in HANA CDS
|
|
@@ -1319,39 +1313,35 @@ function toHdbcdsSource( csn, options ) {
|
|
|
1319
1313
|
/**
|
|
1320
1314
|
* @param {object} x Expression with a ref property
|
|
1321
1315
|
* @returns {string} Rendered expression
|
|
1322
|
-
* @todo no extra magic with x.param
|
|
1316
|
+
* @todo no extra magic with x.param
|
|
1323
1317
|
*/
|
|
1324
1318
|
function renderExpressionRef( x ) {
|
|
1325
|
-
if (!x.param
|
|
1319
|
+
if (!x.param) {
|
|
1326
1320
|
const magicReplacement = getVariableReplacement(x.ref, options);
|
|
1327
|
-
if (x.ref[0] === '$user') {
|
|
1321
|
+
if (x.ref[0] === '$user' || x.ref[0] === '$tenant') {
|
|
1328
1322
|
if (magicReplacement !== null)
|
|
1329
1323
|
return renderStringForHdbcds(magicReplacement);
|
|
1330
1324
|
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
//
|
|
1334
|
-
if (
|
|
1335
|
-
return
|
|
1336
|
-
|
|
1337
|
-
else if (x.ref[1] === 'locale')
|
|
1338
|
-
return 'SESSION_CONTEXT(\'LOCALE\')';
|
|
1339
|
-
|
|
1340
|
-
else if (x.ref[1] === 'tenant')
|
|
1341
|
-
return 'SESSION_CONTEXT(\'TENANT\')';
|
|
1325
|
+
const name = pathName(x.ref);
|
|
1326
|
+
const result = variableForDialect(options, name);
|
|
1327
|
+
// Invalid second path step doesn't cause a return
|
|
1328
|
+
if (result)
|
|
1329
|
+
return result;
|
|
1342
1330
|
}
|
|
1343
|
-
else if (x.ref[0] === '$at' || x.ref[0] === '$valid') {
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
return
|
|
1331
|
+
else if (x.ref[0] === '$at' || x.ref[0] === '$valid' || x.ref[0] === '$now') {
|
|
1332
|
+
const name = pathName(x.ref);
|
|
1333
|
+
const result = variableForDialect(options, name);
|
|
1334
|
+
// Invalid second path step doesn't cause a return
|
|
1335
|
+
if (result)
|
|
1336
|
+
return result;
|
|
1349
1337
|
}
|
|
1350
1338
|
else if (x.ref[0] === '$session' && magicReplacement !== null) {
|
|
1351
1339
|
return renderStringForHdbcds(magicReplacement);
|
|
1352
1340
|
}
|
|
1353
1341
|
}
|
|
1354
|
-
|
|
1342
|
+
const prefix = x.param ? ':' : '';
|
|
1343
|
+
const ref = x.ref.map((step, index) => renderPathStep(step, index, this.env.withSubPath([ 'ref', index ]))).join('.');
|
|
1344
|
+
return `${prefix}${ref}`;
|
|
1355
1345
|
}
|
|
1356
1346
|
|
|
1357
1347
|
/**
|
package/lib/render/toSql.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
const {
|
|
5
5
|
getLastPartOf, getLastPartOfRef,
|
|
6
6
|
hasValidSkipOrExists, isBuiltinType, generatedByCompilerVersion, getNormalizedQuery,
|
|
7
|
-
forEachDefinition, getResultingName, getVariableReplacement,
|
|
7
|
+
forEachDefinition, getResultingName, getVariableReplacement, pathName,
|
|
8
8
|
} = require('../model/csnUtils');
|
|
9
9
|
const { forEach, forEachValue, forEachKey } = require('../utils/objectUtils');
|
|
10
10
|
const {
|
|
@@ -608,7 +608,7 @@ function toSqlDdl( csn, options ) {
|
|
|
608
608
|
result += renderTechnicalConfiguration(art.technicalConfig, childEnv);
|
|
609
609
|
|
|
610
610
|
|
|
611
|
-
if (options.sqlDialect === 'hana') {
|
|
611
|
+
if (options.sqlDialect === 'hana' && options.withHanaAssociations) {
|
|
612
612
|
const associations = Object.keys(art.elements)
|
|
613
613
|
.map(name => renderAssociationElement(name, art.elements[name], childEnv))
|
|
614
614
|
.filter(s => s !== '')
|
|
@@ -761,7 +761,6 @@ function toSqlDdl( csn, options ) {
|
|
|
761
761
|
* is not an association.
|
|
762
762
|
* Any change to the cardinality rendering must be reflected in A2J mapAssocToJoinCardinality() as well.
|
|
763
763
|
*
|
|
764
|
-
* @todo Duplicate check
|
|
765
764
|
* @param {string} elementName Name of the element to render
|
|
766
765
|
* @param {CSN.Element} elm CSN element
|
|
767
766
|
* @param {SqlRenderEnvironment} env Render environment
|
|
@@ -905,12 +904,11 @@ function toSqlDdl( csn, options ) {
|
|
|
905
904
|
*
|
|
906
905
|
* Returns the source as a string.
|
|
907
906
|
*
|
|
908
|
-
* @todo Misleading name, should be something like 'renderQueryFrom'. All the query parts should probably also be rearranged.
|
|
909
907
|
* @param {object} source Query source
|
|
910
908
|
* @param {SqlRenderEnvironment} env Render environment
|
|
911
909
|
* @returns {string} Rendered view source
|
|
912
910
|
*/
|
|
913
|
-
function
|
|
911
|
+
function renderQuerySource( source, env ) {
|
|
914
912
|
// Sub-SELECT
|
|
915
913
|
if (source.SELECT || source.SET) {
|
|
916
914
|
let result = `(${renderQuery(source, env.withIncreasedIndent())})`;
|
|
@@ -922,12 +920,12 @@ function toSqlDdl( csn, options ) {
|
|
|
922
920
|
// JOIN
|
|
923
921
|
else if (source.join) {
|
|
924
922
|
// One join operation, possibly with ON-condition
|
|
925
|
-
let result = `${
|
|
923
|
+
let result = `${renderQuerySource(source.args[0], env.withSubPath([ 'args', 0 ]))}`;
|
|
926
924
|
for (let i = 1; i < source.args.length; i++) {
|
|
927
925
|
result = `(${result} ${source.join.toUpperCase()} `;
|
|
928
926
|
if (options.sqlDialect === 'hana')
|
|
929
927
|
result += renderJoinCardinality(source.cardinality);
|
|
930
|
-
result += `JOIN ${
|
|
928
|
+
result += `JOIN ${renderQuerySource(source.args[i], env.withSubPath([ 'args', i ]))}`;
|
|
931
929
|
if (source.on)
|
|
932
930
|
result += ` ON ${renderExpr(source.on, env.withSubPath([ 'on' ]))}`;
|
|
933
931
|
|
|
@@ -1150,7 +1148,7 @@ function toSqlDdl( csn, options ) {
|
|
|
1150
1148
|
.map(name => renderAssociationElement(name, art.elements[name], childEnv))
|
|
1151
1149
|
.filter(s => s !== '')
|
|
1152
1150
|
.join(',\n');
|
|
1153
|
-
if (associations !== '' && options.sqlDialect === 'hana') {
|
|
1151
|
+
if (associations !== '' && options.sqlDialect === 'hana' && options.withHanaAssociations) {
|
|
1154
1152
|
result += `${env.indent}\nWITH ASSOCIATIONS (\n${associations}\n`;
|
|
1155
1153
|
result += `${env.indent})`;
|
|
1156
1154
|
}
|
|
@@ -1257,7 +1255,7 @@ function toSqlDdl( csn, options ) {
|
|
|
1257
1255
|
})
|
|
1258
1256
|
.filter(s => s !== '')
|
|
1259
1257
|
.join(',\n')}\n`;
|
|
1260
|
-
result += `${env.indent}FROM ${
|
|
1258
|
+
result += `${env.indent}FROM ${renderQuerySource( select.from, env.withSubPath([ 'from' ]))}`;
|
|
1261
1259
|
if (select.where)
|
|
1262
1260
|
result += `\n${env.indent}WHERE ${renderExpr(select.where, env.withSubPath([ 'where' ]))}`;
|
|
1263
1261
|
|
|
@@ -1335,23 +1333,15 @@ function toSqlDdl( csn, options ) {
|
|
|
1335
1333
|
function renderTypeReference( elm, env ) {
|
|
1336
1334
|
let result = '';
|
|
1337
1335
|
|
|
1338
|
-
// Anonymous structured type: Not supported with SQL (but shouldn't happen anyway after forHana flattened them)
|
|
1339
1336
|
if (!elm.type && !elm.value) {
|
|
1340
|
-
|
|
1337
|
+
// Anonymous structured type: Not supported with SQL, but doesn't happen anyway after flattening.
|
|
1338
|
+
if (options.testMode)
|
|
1341
1339
|
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
1340
|
return result;
|
|
1347
1341
|
}
|
|
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');
|
|
1342
|
+
else if (elm.target) {
|
|
1343
|
+
if (options.testMode)
|
|
1344
|
+
throw new ModelError(`to.sql(): Unexpected association in: ${JSON.stringify(env.path)}`);
|
|
1355
1345
|
return result;
|
|
1356
1346
|
}
|
|
1357
1347
|
|
|
@@ -1370,7 +1360,6 @@ function toSqlDdl( csn, options ) {
|
|
|
1370
1360
|
if (elm.value) {
|
|
1371
1361
|
if (!elm.value.stored)
|
|
1372
1362
|
throw new CompilerAssertion('Found calculated element on-read in rendering; should have been replaced!');
|
|
1373
|
-
// TODO: Properly implement calculated elements on-write:
|
|
1374
1363
|
// The SQL standard 2016 describes the syntax in section 11.3 - 11.4
|
|
1375
1364
|
// of the SQL Foundation spec (for 2003 in 5WD-02-Foundation-2003-09.pdf). Summarized:
|
|
1376
1365
|
// <generation clause> ::= GENERATED ALWAYS AS '(' <value expression> ')'
|
|
@@ -1383,7 +1372,6 @@ function toSqlDdl( csn, options ) {
|
|
|
1383
1372
|
return result;
|
|
1384
1373
|
}
|
|
1385
1374
|
|
|
1386
|
-
|
|
1387
1375
|
/**
|
|
1388
1376
|
* Render the name of a builtin CDS type
|
|
1389
1377
|
*
|
|
@@ -1487,7 +1475,7 @@ function toSqlDdl( csn, options ) {
|
|
|
1487
1475
|
* @return {string}
|
|
1488
1476
|
*/
|
|
1489
1477
|
function renderExpressionRef( x, env ) {
|
|
1490
|
-
if (!x.param
|
|
1478
|
+
if (!x.param) {
|
|
1491
1479
|
const magicReplacement = getVariableReplacement(x.ref, options);
|
|
1492
1480
|
|
|
1493
1481
|
if (x.ref[0] === '$user') {
|
|
@@ -1499,6 +1487,15 @@ function toSqlDdl( csn, options ) {
|
|
|
1499
1487
|
if (result)
|
|
1500
1488
|
return result;
|
|
1501
1489
|
}
|
|
1490
|
+
else if (x.ref[0] === '$tenant') {
|
|
1491
|
+
if (magicReplacement !== null)
|
|
1492
|
+
return renderStringForSql(magicReplacement, options.sqlDialect);
|
|
1493
|
+
|
|
1494
|
+
const name = pathName(x.ref);
|
|
1495
|
+
const result = variableForDialect(options, name);
|
|
1496
|
+
if (result)
|
|
1497
|
+
return result;
|
|
1498
|
+
}
|
|
1502
1499
|
else if (x.ref[0] === '$at' || x.ref[0] === '$valid') {
|
|
1503
1500
|
const result = render$at(x);
|
|
1504
1501
|
// Invalid second path step doesn't cause a return
|
|
@@ -1508,18 +1505,12 @@ function toSqlDdl( csn, options ) {
|
|
|
1508
1505
|
else if (x.ref[0] === '$session' && magicReplacement !== null) {
|
|
1509
1506
|
return renderStringForSql(magicReplacement, options.sqlDialect);
|
|
1510
1507
|
}
|
|
1511
|
-
else if (x.ref[0] === '$now') {
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
case 'h2':
|
|
1518
|
-
case 'postgres':
|
|
1519
|
-
return 'current_timestamp';
|
|
1520
|
-
default:
|
|
1521
|
-
return quoteSqlId(x.ref[0]);
|
|
1522
|
-
}
|
|
1508
|
+
else if (x.ref[0] === '$now') {
|
|
1509
|
+
const name = pathName(x.ref);
|
|
1510
|
+
const result = variableForDialect(options, name);
|
|
1511
|
+
if (result)
|
|
1512
|
+
return result;
|
|
1513
|
+
return quoteSqlId(x.ref[0]);
|
|
1523
1514
|
}
|
|
1524
1515
|
}
|
|
1525
1516
|
// FIXME: We currently cannot distinguish whether '$parameters' was quoted or not - we
|
|
@@ -1544,7 +1535,7 @@ function toSqlDdl( csn, options ) {
|
|
|
1544
1535
|
if (x.ref.length > 2 || typeof x.ref[1] !== 'string')
|
|
1545
1536
|
return null; // `$user` can only have two path steps
|
|
1546
1537
|
|
|
1547
|
-
const name =
|
|
1538
|
+
const name = pathName(x.ref);
|
|
1548
1539
|
const result = variableForDialect(options, name);
|
|
1549
1540
|
if (result)
|
|
1550
1541
|
return result;
|
|
@@ -1565,7 +1556,7 @@ function toSqlDdl( csn, options ) {
|
|
|
1565
1556
|
if (x.ref.length > 2 || typeof x.ref[1] !== 'string')
|
|
1566
1557
|
return null; // `$at` can only have two path steps
|
|
1567
1558
|
|
|
1568
|
-
const name =
|
|
1559
|
+
const name = pathName(x.ref);
|
|
1569
1560
|
const config = variableForDialect(options, name);
|
|
1570
1561
|
if (config)
|
|
1571
1562
|
return config;
|
|
@@ -1586,22 +1577,10 @@ function toSqlDdl( csn, options ) {
|
|
|
1586
1577
|
function renderPathStep( s, idx, env ) {
|
|
1587
1578
|
// Simple id or absolute name
|
|
1588
1579
|
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
1580
|
// 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
|
-
}
|
|
1581
|
+
// Ignore initial $projection and initial $self
|
|
1582
|
+
if (idx === 0 && (s === '$projection' || s === '$self'))
|
|
1583
|
+
return '';
|
|
1605
1584
|
return quoteSqlId(s);
|
|
1606
1585
|
}
|
|
1607
1586
|
// ID with filters or parameters
|
|
@@ -344,30 +344,39 @@ function getDefaultTypeLengths( sqlDialect ) {
|
|
|
344
344
|
*/
|
|
345
345
|
const variablesToSql = {
|
|
346
346
|
fallback: {
|
|
347
|
-
// no fallback for $user.id and $
|
|
347
|
+
// no fallback for $user.id and $tenant -> warning in call-site
|
|
348
348
|
'$user.locale': '\'en\'',
|
|
349
|
-
// $at
|
|
349
|
+
// $at.*/$now are handled in all dialects -> there is no need for a fallback
|
|
350
350
|
},
|
|
351
351
|
hana: {
|
|
352
352
|
'$user.id': "SESSION_CONTEXT('APPLICATIONUSER')",
|
|
353
353
|
'$user.locale': "SESSION_CONTEXT('LOCALE')",
|
|
354
|
-
|
|
354
|
+
$tenant: "SESSION_CONTEXT('APPLICATIONTENANT')",
|
|
355
355
|
'$at.from': "TO_TIMESTAMP(SESSION_CONTEXT('VALID-FROM'))",
|
|
356
356
|
'$at.to': "TO_TIMESTAMP(SESSION_CONTEXT('VALID-TO'))",
|
|
357
|
+
'$valid.from': "TO_TIMESTAMP(SESSION_CONTEXT('VALID-FROM'))",
|
|
358
|
+
'$valid.to': "TO_TIMESTAMP(SESSION_CONTEXT('VALID-TO'))",
|
|
359
|
+
$now: 'CURRENT_TIMESTAMP',
|
|
357
360
|
},
|
|
358
361
|
postgres: {
|
|
359
362
|
'$user.id': "current_setting('cap.applicationuser')",
|
|
360
363
|
'$user.locale': "current_setting('cap.locale')",
|
|
361
|
-
|
|
364
|
+
$tenant: "current_setting('cap.tenant')",
|
|
362
365
|
'$at.from': "current_setting('cap.valid_from')::timestamp",
|
|
363
366
|
'$at.to': "current_setting('cap.valid_to')::timestamp",
|
|
367
|
+
'$valid.from': "current_setting('cap.valid_from')::timestamp",
|
|
368
|
+
'$valid.to': "current_setting('cap.valid_to')::timestamp",
|
|
369
|
+
$now: 'current_timestamp',
|
|
364
370
|
},
|
|
365
371
|
'better-sqlite': {
|
|
366
372
|
'$user.id': "session_context( '$user.id' )",
|
|
367
373
|
'$user.locale': "session_context( '$user.locale' )",
|
|
368
|
-
|
|
374
|
+
$tenant: "session_context( '$tenant' )",
|
|
369
375
|
'$at.from': "session_context( '$valid.from' )",
|
|
370
376
|
'$at.to': "session_context( '$valid.to' )",
|
|
377
|
+
'$valid.from': "session_context( '$valid.from' )",
|
|
378
|
+
'$valid.to': "session_context( '$valid.to' )",
|
|
379
|
+
$now: 'CURRENT_TIMESTAMP',
|
|
371
380
|
},
|
|
372
381
|
sqlite: {
|
|
373
382
|
// For sqlite, we render the string-format-time (strftime) function.
|
|
@@ -377,17 +386,26 @@ const variablesToSql = {
|
|
|
377
386
|
'$at.from': "strftime('%Y-%m-%dT%H:%M:%S.000Z', 'now')",
|
|
378
387
|
// + 1ms compared to $at.from
|
|
379
388
|
'$at.to': "strftime('%Y-%m-%dT%H:%M:%S.001Z', 'now')",
|
|
389
|
+
'$valid.from': "strftime('%Y-%m-%dT%H:%M:%S.000Z', 'now')",
|
|
390
|
+
'$valid.to': "strftime('%Y-%m-%dT%H:%M:%S.001Z', 'now')",
|
|
391
|
+
$now: 'CURRENT_TIMESTAMP',
|
|
380
392
|
},
|
|
381
393
|
plain: {
|
|
382
394
|
'$at.from': 'current_timestamp',
|
|
383
395
|
'$at.to': 'current_timestamp',
|
|
396
|
+
'$valid.from': 'current_timestamp',
|
|
397
|
+
'$valid.to': 'current_timestamp',
|
|
398
|
+
$now: 'CURRENT_TIMESTAMP',
|
|
384
399
|
},
|
|
385
400
|
h2: {
|
|
386
401
|
'$user.id': '@applicationuser',
|
|
387
402
|
'$user.locale': '@locale',
|
|
388
|
-
|
|
403
|
+
$tenant: '@tenant',
|
|
389
404
|
'$at.from': '@valid_from',
|
|
390
405
|
'$at.to': '@valid_to',
|
|
406
|
+
'$valid.from': '@valid_from',
|
|
407
|
+
'$valid.to': '@valid_to',
|
|
408
|
+
$now: 'current_timestamp',
|
|
391
409
|
},
|
|
392
410
|
};
|
|
393
411
|
|
|
@@ -85,6 +85,8 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
|
|
|
85
85
|
const trans = transformers[name] || transformers[name.charAt(0)] || standard;
|
|
86
86
|
if (customTransformers[name])
|
|
87
87
|
customTransformers[name](node, name, node[name], csnPath, _parent, _prop);
|
|
88
|
+
else if (options.processAnnotations && customTransformers['@'] && name.charAt(0) === '@')
|
|
89
|
+
customTransformers['@'](node, name, node[name], csnPath, _parent, _prop);
|
|
88
90
|
trans( node, name, node[name], csnPath );
|
|
89
91
|
}
|
|
90
92
|
}
|
|
@@ -109,6 +111,8 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
|
|
|
109
111
|
const trans = transformers[name] || transformers[name.charAt(0)] || standard;
|
|
110
112
|
if (customTransformers[name])
|
|
111
113
|
customTransformers[name](node, name, node[name], csnPath, dict);
|
|
114
|
+
else if (options.processAnnotations && customTransformers['@'] && name.charAt(0) === '@')
|
|
115
|
+
customTransformers['@'](node, name, node[name], csnPath, dict);
|
|
112
116
|
trans( node, name, node[name], csnPath );
|
|
113
117
|
}
|
|
114
118
|
csnPath.pop();
|
|
@@ -150,8 +154,13 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
|
|
|
150
154
|
else if (node && typeof node === 'object') {
|
|
151
155
|
csnPath.push(_prop);
|
|
152
156
|
|
|
153
|
-
|
|
154
|
-
annotation( node,
|
|
157
|
+
if (Array.isArray(node)) {
|
|
158
|
+
node.forEach( (n, i) => annotation( node, i, n ) );
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
for (const name of Object.getOwnPropertyNames( node ))
|
|
162
|
+
annotation( node, name, node[name] );
|
|
163
|
+
}
|
|
155
164
|
|
|
156
165
|
csnPath.pop();
|
|
157
166
|
}
|
|
@@ -287,7 +296,55 @@ function applyTransformationsOnDictionary( dictionary, customTransformers = {},
|
|
|
287
296
|
return applyTransformationsInternal(dictionary, null, customTransformers, [], { directDict: true, ...options }, path);
|
|
288
297
|
}
|
|
289
298
|
|
|
299
|
+
/**
|
|
300
|
+
* transformExpression is a lightweight version of applyTransformations
|
|
301
|
+
* used primarily to transform annotation expressions.
|
|
302
|
+
* @param {object} parent Start node
|
|
303
|
+
* @param {object} customTransformers Map of callback functions
|
|
304
|
+
* @param {CSN.Path} path Path to parent
|
|
305
|
+
* @returns {object} transformed parent
|
|
306
|
+
*/
|
|
307
|
+
function transformExpression( parent, customTransformers, path = [] ) {
|
|
308
|
+
const csnPath = [ ...path ];
|
|
309
|
+
/**
|
|
310
|
+
*
|
|
311
|
+
* @param {object} _parent
|
|
312
|
+
* @param {string} _prop
|
|
313
|
+
* @param {object} node
|
|
314
|
+
*/
|
|
315
|
+
function standard( _parent, _prop, node ) {
|
|
316
|
+
if (!node || typeof node !== 'object' ||
|
|
317
|
+
!{}.propertyIsEnumerable.call( _parent, _prop ) ||
|
|
318
|
+
(typeof _prop === 'string' && _prop.startsWith('@')))
|
|
319
|
+
return;
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
csnPath.push(_prop);
|
|
323
|
+
if (Array.isArray(node)) {
|
|
324
|
+
node.forEach( (n, i) => standard( node, i, n ) );
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
for (const name of Object.getOwnPropertyNames( node )) {
|
|
328
|
+
const ct = customTransformers[name];
|
|
329
|
+
if (ct) {
|
|
330
|
+
if (Array.isArray(ct))
|
|
331
|
+
ct.forEach(cti => cti(node, name, node[name], csnPath, _parent, _prop));
|
|
332
|
+
else
|
|
333
|
+
ct(node, name, node[name], csnPath, _parent, _prop);
|
|
334
|
+
}
|
|
335
|
+
standard(node, name, node[name]);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
csnPath.pop();
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
for (const name of Object.getOwnPropertyNames( parent ))
|
|
342
|
+
standard( parent, name, parent[name] );
|
|
343
|
+
return parent;
|
|
344
|
+
}
|
|
345
|
+
|
|
290
346
|
module.exports = {
|
|
347
|
+
transformExpression,
|
|
291
348
|
applyTransformations,
|
|
292
349
|
applyTransformationsOnNonDictionary,
|
|
293
350
|
applyTransformationsOnDictionary,
|
|
@@ -18,6 +18,7 @@ const { forEach } = require('../../utils/objectUtils');
|
|
|
18
18
|
* @returns {import('../../model/csnUtils').genericCallback} callback for forEachDefinition
|
|
19
19
|
*/
|
|
20
20
|
function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimiter, doA2J = true ) {
|
|
21
|
+
let prepend$self = false;
|
|
21
22
|
return transformSelfInBacklinks;
|
|
22
23
|
/**
|
|
23
24
|
* @param {CSN.Artifact} artifact
|
|
@@ -26,12 +27,15 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
|
|
|
26
27
|
* @param {CSN.Path} path
|
|
27
28
|
*/
|
|
28
29
|
function transformSelfInBacklinks( artifact, artifactName, dummy, path ) {
|
|
30
|
+
prepend$self = false;
|
|
29
31
|
// Fixme: For toHana mixins must be transformed, for toSql -d hana
|
|
30
32
|
// mixin elements must be transformed, why can't toSql also use mixins?
|
|
31
33
|
if (options.transformation === 'effective' && artifact.elements || artifact.kind === 'entity' || artifact.query || (options.forHana && options.sqlMapping === 'hdbcds' && artifact.kind === 'type'))
|
|
32
34
|
processDict(artifact.elements, path.concat([ 'elements' ]));
|
|
33
|
-
if (artifact.query?.SELECT?.mixin)
|
|
35
|
+
if (artifact.query?.SELECT?.mixin) {
|
|
36
|
+
prepend$self = options.transformation === 'effective';
|
|
34
37
|
processDict(artifact.query.SELECT.mixin, path.concat([ 'query', 'SELECT', 'mixin' ]));
|
|
38
|
+
}
|
|
35
39
|
|
|
36
40
|
/**
|
|
37
41
|
* Loop over the dict and start the processing.
|
|
@@ -237,6 +241,10 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
|
|
|
237
241
|
{ ref: k.ref },
|
|
238
242
|
];
|
|
239
243
|
|
|
244
|
+
if (prepend$self)
|
|
245
|
+
a[1].ref = [ '$self', ...a[1].ref ];
|
|
246
|
+
|
|
247
|
+
|
|
240
248
|
conditions.push([ a[0], '=', a[1] ]);
|
|
241
249
|
});
|
|
242
250
|
|
|
@@ -273,11 +281,15 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
|
|
|
273
281
|
// we are in the "path" from the forwarding assoc => need to remove the first part of the path
|
|
274
282
|
if (ref[0] === assocName) {
|
|
275
283
|
ref.shift();
|
|
284
|
+
if (prepend$self)
|
|
285
|
+
ref.unshift('$self');
|
|
276
286
|
}
|
|
277
287
|
else if (ref.length > 1 && ref[0] === '$self' && ref[1] === assocName) {
|
|
278
288
|
// We could also have a $self in front of the assoc name - so we would need to shift twice
|
|
279
289
|
ref.shift();
|
|
280
290
|
ref.shift();
|
|
291
|
+
if (prepend$self)
|
|
292
|
+
ref.unshift('$self');
|
|
281
293
|
}
|
|
282
294
|
else { // we are in the backlink assoc "path" => need to push at the beginning the association's id
|
|
283
295
|
ref.unshift(elemName);
|
|
@@ -10,6 +10,7 @@ const {
|
|
|
10
10
|
const { implicitAs, columnAlias, pathId } = require('../../model/csnRefs');
|
|
11
11
|
const { setProp } = require('../../base/model');
|
|
12
12
|
const { forEach } = require('../../utils/objectUtils');
|
|
13
|
+
const { killNonrequiredAnno } = require('./killAnnotations');
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* For keys, columns, groupBy and orderBy, expand structured things.
|
|
@@ -27,7 +28,7 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
27
28
|
|
|
28
29
|
rewriteExpandInline();
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
const transformers = {
|
|
31
32
|
keys: (parent, name, keys, path) => {
|
|
32
33
|
parent.keys = expand(keys, path.concat('keys'), true);
|
|
33
34
|
},
|
|
@@ -53,7 +54,13 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
53
54
|
orderBy: (parent, name, orderBy, path) => {
|
|
54
55
|
parent.orderBy = expand(orderBy, path.concat('orderBy'));
|
|
55
56
|
},
|
|
56
|
-
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// To not have a whole model loop for such a "small" thing, we kill all non-sql-backend relevant annotations here
|
|
60
|
+
if (options.transformation === 'sql' || options.transformation === 'hdbcds')
|
|
61
|
+
transformers['@'] = killNonrequiredAnno;
|
|
62
|
+
|
|
63
|
+
applyTransformations(csn, transformers, [], iterateOptions);
|
|
57
64
|
|
|
58
65
|
/**
|
|
59
66
|
* Turn .expand/.inline into normal refs. @cds.persistence.skip .expand with to-many (and all transitive views).
|
|
@@ -529,7 +536,7 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
529
536
|
else
|
|
530
537
|
newThing.push(col);
|
|
531
538
|
}
|
|
532
|
-
else if (col.ref && col.$scope === '$magic' && ( col.ref[0] === '$user' || col.ref[0] === '$session' ) && !col.as) {
|
|
539
|
+
else if (col.ref && col.$scope === '$magic' && ( col.ref[0] === '$user' || col.ref[0] === '$tenant' || col.ref[0] === '$session' ) && !col.as) {
|
|
533
540
|
col.as = implicitAs(col.ref);
|
|
534
541
|
newThing.push(col);
|
|
535
542
|
}
|
|
@@ -618,6 +625,20 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
618
625
|
setProp(obj, '$implicitAlias', true);
|
|
619
626
|
}
|
|
620
627
|
|
|
628
|
+
// If our column/thing was cast to a structured type, we need to keep the "cast" insync with the
|
|
629
|
+
// flattened out leaf elements that we turn the ref into
|
|
630
|
+
if (obj.cast?.type) {
|
|
631
|
+
const addedRef = currentRef.slice(root.ref.length);
|
|
632
|
+
if (addedRef.length > 0) {
|
|
633
|
+
// Decouple from other leafs
|
|
634
|
+
obj.cast = { ...obj.cast };
|
|
635
|
+
if (!obj.cast.type.ref)
|
|
636
|
+
obj.cast.type = { ref: [ obj.cast.type ] };
|
|
637
|
+
|
|
638
|
+
obj.cast.type.ref = [ ...obj.cast.type.ref, ...addedRef ];
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
621
642
|
// The Java runtime, as of 2023-09-13, assumes that for _simple projections_, all references
|
|
622
643
|
// are relative to the query source. To avoid breaking that assumption unless necessary,
|
|
623
644
|
// we only add the table alias if:
|
|
@@ -17,7 +17,7 @@ const { cardinality2str } = require('../../model/csnUtils');
|
|
|
17
17
|
* @param {CSN.Model} csn
|
|
18
18
|
*/
|
|
19
19
|
function removeLeadingSelf( csn ) {
|
|
20
|
-
const magicVars = [ '$now', '$self', '$projection', '$user', '$session', '$at' ];
|
|
20
|
+
const magicVars = [ '$now', '$self', '$projection', '$user', '$tenant', '$session', '$at' ];
|
|
21
21
|
applyTransformations(csn, {
|
|
22
22
|
elements: (parent, prop, elements) => {
|
|
23
23
|
for (const [ elementName, element ] of Object.entries(elements)) {
|
|
@@ -184,7 +184,7 @@ function flattenAllStructStepsInRefs( csn, options, resolved, pathDelimiter, ite
|
|
|
184
184
|
* For each step of the links, check if there is a type reference.
|
|
185
185
|
* If there is, resolve it and store the result in a WeakMap.
|
|
186
186
|
*
|
|
187
|
-
* @param {Array} [links
|
|
187
|
+
* @param {Array} [links]
|
|
188
188
|
* @todo seems too hacky
|
|
189
189
|
* @returns {WeakMap} A WeakMap where a link is the key and the type is the value
|
|
190
190
|
*/
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const requiredAnnos = {
|
|
4
|
+
'@cds.persistence.skip': true,
|
|
5
|
+
'@cds.persistence.exists': true,
|
|
6
|
+
'@cds.persistence.table': true,
|
|
7
|
+
'@cds.persistence.journal': true, // Build checks on it
|
|
8
|
+
'@sql.append': true,
|
|
9
|
+
'@sql.prepend': true,
|
|
10
|
+
'@sql.replace': true, // We do a check on this, no real function
|
|
11
|
+
'@assert.unique': true, // We do a check on this, no real function
|
|
12
|
+
'@assert.integrity': true,
|
|
13
|
+
'@cds.valid.from': true,
|
|
14
|
+
'@cds.valid.to': true,
|
|
15
|
+
'@cds.valid.key': true,
|
|
16
|
+
'@odata.draft.enabled': true,
|
|
17
|
+
'@fiori.draft.enabled': true,
|
|
18
|
+
'@cds.persistence.calcview': true,
|
|
19
|
+
'@cds.persistence.udf': true,
|
|
20
|
+
'@cds.autoexpose': true,
|
|
21
|
+
'@cds.autoexposed': true,
|
|
22
|
+
'@cds.redirection.target': true,
|
|
23
|
+
'@Core.Computed': true,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
*
|
|
28
|
+
* @param {object} carrier
|
|
29
|
+
* @param {string} annoKey
|
|
30
|
+
*/
|
|
31
|
+
function killNonrequiredAnno( carrier, annoKey ) {
|
|
32
|
+
if (!requiredAnnos[annoKey] && !annoKey.startsWith('@assert.unique.'))
|
|
33
|
+
delete carrier[annoKey];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
module.exports = { killNonrequiredAnno };
|