@sap/cds-compiler 3.8.0 → 3.9.2
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 +61 -0
- package/bin/cdsc.js +2 -2
- package/doc/CHANGELOG_BETA.md +26 -5
- package/lib/api/.eslintrc.json +3 -2
- package/lib/api/options.js +3 -1
- package/lib/api/validate.js +1 -1
- package/lib/base/message-registry.js +27 -18
- package/lib/base/messages.js +6 -1
- package/lib/base/model.js +2 -2
- package/lib/checks/.eslintrc.json +1 -0
- package/lib/checks/actionsFunctions.js +6 -6
- package/lib/checks/annotationsOData.js +1 -1
- package/lib/checks/elements.js +28 -17
- package/lib/checks/foreignKeys.js +1 -1
- package/lib/checks/invalidTarget.js +1 -1
- package/lib/checks/onConditions.js +11 -6
- package/lib/checks/queryNoDbArtifacts.js +1 -1
- package/lib/checks/types.js +1 -1
- package/lib/checks/utils.js +1 -1
- package/lib/checks/validator.js +3 -2
- package/lib/compiler/assert-consistency.js +8 -3
- package/lib/compiler/base.js +19 -13
- package/lib/compiler/builtins.js +7 -0
- package/lib/compiler/checks.js +73 -6
- package/lib/compiler/define.js +10 -5
- package/lib/compiler/extend.js +924 -1709
- package/lib/compiler/finalize-parse-cdl.js +1 -1
- package/lib/compiler/generate.js +838 -0
- package/lib/compiler/index.js +2 -0
- package/lib/compiler/populate.js +2 -2
- package/lib/compiler/propagator.js +20 -8
- package/lib/compiler/resolve.js +3 -3
- package/lib/compiler/shared.js +11 -6
- package/lib/edm/annotations/genericTranslation.js +6 -6
- package/lib/edm/csn2edm.js +1 -1
- package/lib/edm/edm.js +25 -11
- package/lib/edm/edmPreprocessor.js +47 -23
- package/lib/edm/edmUtils.js +37 -9
- package/lib/gen/Dictionary.json +5 -7
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -1
- package/lib/gen/language.tokens +24 -23
- package/lib/gen/languageLexer.interp +4 -1
- package/lib/gen/languageLexer.js +792 -784
- package/lib/gen/languageLexer.tokens +12 -11
- package/lib/gen/languageParser.js +3944 -3865
- package/lib/json/from-csn.js +27 -6
- package/lib/json/to-csn.js +10 -6
- package/lib/language/antlrParser.js +11 -3
- package/lib/language/genericAntlrParser.js +4 -2
- package/lib/language/language.g4 +32 -24
- package/lib/model/csnRefs.js +15 -7
- package/lib/model/csnUtils.js +41 -76
- package/lib/modelCompare/utils/.eslintrc.json +1 -1
- package/lib/optionProcessor.js +7 -4
- package/lib/render/.eslintrc.json +1 -1
- package/lib/render/toCdl.js +244 -168
- package/lib/render/toHdbcds.js +18 -10
- package/lib/render/toSql.js +24 -2
- package/lib/transform/db/.eslintrc.json +4 -3
- package/lib/transform/db/cdsPersistence.js +1 -1
- package/lib/transform/db/expansion.js +11 -6
- package/lib/transform/db/flattening.js +22 -15
- package/lib/transform/db/rewriteCalculatedElements.js +50 -29
- package/lib/transform/db/temporal.js +1 -1
- package/lib/transform/db/views.js +1 -1
- package/lib/transform/draft/db.js +1 -1
- package/lib/transform/draft/odata.js +3 -4
- package/lib/transform/forOdataNew.js +5 -6
- package/lib/transform/forRelationalDB.js +7 -7
- package/lib/transform/odata/toFinalBaseType.js +6 -6
- package/lib/transform/odata/typesExposure.js +12 -3
- package/lib/transform/odata/utils.js +3 -0
- package/lib/transform/transformUtilsNew.js +11 -26
- package/lib/transform/translateAssocsToJoins.js +9 -9
- package/lib/transform/universalCsn/.eslintrc.json +3 -2
- package/lib/transform/universalCsn/coreComputed.js +1 -1
- package/lib/transform/universalCsn/universalCsnEnricher.js +6 -4
- package/package.json +1 -1
package/lib/render/toHdbcds.js
CHANGED
|
@@ -21,7 +21,7 @@ const { makeMessageFunction } = require('../base/messages');
|
|
|
21
21
|
const { timetrace } = require('../utils/timetrace');
|
|
22
22
|
|
|
23
23
|
const { smartId, delimitedId } = require('../sql-identifier');
|
|
24
|
-
const { ModelError } = require('../base/error');
|
|
24
|
+
const { ModelError, CompilerAssertion } = require('../base/error');
|
|
25
25
|
|
|
26
26
|
const $PROJECTION = '$projection';
|
|
27
27
|
const $SELF = '$self';
|
|
@@ -60,7 +60,7 @@ function toHdbcdsSource( csn, options ) {
|
|
|
60
60
|
const hdbcdsNames = options.sqlMapping === 'hdbcds';
|
|
61
61
|
|
|
62
62
|
const {
|
|
63
|
-
info, warning, error, throwWithAnyError,
|
|
63
|
+
info, warning, error, throwWithAnyError, message,
|
|
64
64
|
} = makeMessageFunction(csn, options, 'to.hdbcds');
|
|
65
65
|
|
|
66
66
|
const exprRenderer = createExpressionRenderer({
|
|
@@ -1051,7 +1051,7 @@ function toHdbcdsSource( csn, options ) {
|
|
|
1051
1051
|
result += (elm.localized ? 'localized ' : '');
|
|
1052
1052
|
|
|
1053
1053
|
// Anonymous structured type
|
|
1054
|
-
if (!elm.type) {
|
|
1054
|
+
if (!elm.type && !elm.value) {
|
|
1055
1055
|
if (!elm.elements)
|
|
1056
1056
|
throw new ModelError(`Missing type of: ${JSON.stringify(elm)}`);
|
|
1057
1057
|
|
|
@@ -1070,14 +1070,14 @@ function toHdbcdsSource( csn, options ) {
|
|
|
1070
1070
|
if ([ 'cds.Association', 'cds.Composition' ].includes(elm.type))
|
|
1071
1071
|
return result + renderAssociationType(elm, env);
|
|
1072
1072
|
|
|
1073
|
-
|
|
1074
|
-
if (elm.type
|
|
1073
|
+
|
|
1074
|
+
if (elm.type?.ref) {
|
|
1075
|
+
// Reference to another element
|
|
1075
1076
|
// For HANA CDS, we need a 'type of'
|
|
1076
|
-
|
|
1077
|
+
result += `type of ${renderAbsolutePath(elm.type, env)}`;
|
|
1077
1078
|
}
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
if (isBuiltinType(elm.type)) {
|
|
1079
|
+
else if (isBuiltinType(elm.type)) {
|
|
1080
|
+
// If we get here, it must be a named type
|
|
1081
1081
|
result += renderBuiltinType(elm);
|
|
1082
1082
|
}
|
|
1083
1083
|
else {
|
|
@@ -1086,6 +1086,14 @@ function toHdbcdsSource( csn, options ) {
|
|
|
1086
1086
|
result += renderAbsoluteNameWithQuotes(elm.type, env);
|
|
1087
1087
|
}
|
|
1088
1088
|
|
|
1089
|
+
if (elm.value) {
|
|
1090
|
+
if (!elm.value.stored)
|
|
1091
|
+
throw new CompilerAssertion('Found calculated element on-read in rendering; should have been replaced!');
|
|
1092
|
+
message('def-unsupported-calc-elem', env.path, { '#': 'hdbcds' });
|
|
1093
|
+
result += ` GENERATED ALWAYS AS ${renderExpr(elm.value)}`;
|
|
1094
|
+
return result;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1089
1097
|
return result;
|
|
1090
1098
|
}
|
|
1091
1099
|
|
|
@@ -1282,7 +1290,7 @@ function toHdbcdsSource( csn, options ) {
|
|
|
1282
1290
|
else if (x.ref[1] === 'locale')
|
|
1283
1291
|
return 'SESSION_CONTEXT(\'LOCALE\')';
|
|
1284
1292
|
}
|
|
1285
|
-
else if (x.ref[0] === '$at') {
|
|
1293
|
+
else if (x.ref[0] === '$at' || x.ref[0] === '$valid') {
|
|
1286
1294
|
if (x.ref[1] === 'from')
|
|
1287
1295
|
return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-FROM\'))';
|
|
1288
1296
|
|
package/lib/render/toSql.js
CHANGED
|
@@ -1250,7 +1250,7 @@ function toSqlDdl( csn, options ) {
|
|
|
1250
1250
|
let result = '';
|
|
1251
1251
|
|
|
1252
1252
|
// Anonymous structured type: Not supported with SQL (but shouldn't happen anyway after forHana flattened them)
|
|
1253
|
-
if (!elm.type) {
|
|
1253
|
+
if (!elm.type && !elm.value) {
|
|
1254
1254
|
if (!elm.elements)
|
|
1255
1255
|
throw new ModelError(`Missing type of: ${elementName}`);
|
|
1256
1256
|
|
|
@@ -1278,6 +1278,20 @@ function toSqlDdl( csn, options ) {
|
|
|
1278
1278
|
throw new ModelError(`Unexpected non-primitive type of: ${artifactName}.${elementName}`);
|
|
1279
1279
|
}
|
|
1280
1280
|
result += renderTypeParameters(elm);
|
|
1281
|
+
|
|
1282
|
+
if (elm.value) {
|
|
1283
|
+
if (!elm.value.stored)
|
|
1284
|
+
throw new CompilerAssertion('Found calculated element on-read in rendering; should have been replaced!');
|
|
1285
|
+
// TODO: Properly implement calculated elements on-write:
|
|
1286
|
+
// The SQL standard 2016 describes the syntax in section 11.3 - 11.4
|
|
1287
|
+
// of the SQL Foundation spec (for 2003 in 5WD-02-Foundation-2003-09.pdf). Summarized:
|
|
1288
|
+
// <generation clause> ::= GENERATED ALWAYS AS '(' <value expression> ')'
|
|
1289
|
+
result += ` GENERATED ALWAYS AS (${renderExpr(elm.value)})`;
|
|
1290
|
+
// However, it appears many databases require a trailing "STORED".
|
|
1291
|
+
if (options.sqlDialect === 'sqlite' || options.sqlDialect === 'postgres')
|
|
1292
|
+
result += ' STORED';
|
|
1293
|
+
return result;
|
|
1294
|
+
}
|
|
1281
1295
|
return result;
|
|
1282
1296
|
}
|
|
1283
1297
|
|
|
@@ -1397,7 +1411,7 @@ function toSqlDdl( csn, options ) {
|
|
|
1397
1411
|
if (result)
|
|
1398
1412
|
return result;
|
|
1399
1413
|
}
|
|
1400
|
-
else if (x.ref[0] === '$at') {
|
|
1414
|
+
else if (x.ref[0] === '$at' || x.ref[0] === '$valid') {
|
|
1401
1415
|
const result = render$at();
|
|
1402
1416
|
// Invalid second path step doesn't cause a return
|
|
1403
1417
|
if (result)
|
|
@@ -1444,6 +1458,8 @@ function toSqlDdl( csn, options ) {
|
|
|
1444
1458
|
return 'SESSION_CONTEXT(\'APPLICATIONUSER\')';
|
|
1445
1459
|
else if (options.sqlDialect === 'postgres')
|
|
1446
1460
|
return 'current_setting(\'CAP.APPLICATIONUSER\')';
|
|
1461
|
+
else if (options.betterSqliteSessionVariables && options.sqlDialect === 'sqlite')
|
|
1462
|
+
return 'session_context( \'$user.id\' )';
|
|
1447
1463
|
warning(null, null, 'The "$user" variable is not supported. Use option "variableReplacements" to specify a value for "$user.id"');
|
|
1448
1464
|
return '\'$user.id\'';
|
|
1449
1465
|
}
|
|
@@ -1452,6 +1468,8 @@ function toSqlDdl( csn, options ) {
|
|
|
1452
1468
|
return 'SESSION_CONTEXT(\'LOCALE\')';
|
|
1453
1469
|
else if (options.sqlDialect === 'postgres')
|
|
1454
1470
|
return 'current_setting(\'CAP.LOCALE\')';
|
|
1471
|
+
else if (options.betterSqliteSessionVariables && options.sqlDialect === 'sqlite')
|
|
1472
|
+
return 'session_context( \'$user.locale\' )';
|
|
1455
1473
|
return '\'en\''; // default language
|
|
1456
1474
|
}
|
|
1457
1475
|
// Basically: Second path step was invalid, do nothing - should not happen.
|
|
@@ -1473,6 +1491,8 @@ function toSqlDdl( csn, options ) {
|
|
|
1473
1491
|
if (x.ref[1] === 'from') {
|
|
1474
1492
|
switch (options.sqlDialect) {
|
|
1475
1493
|
case 'sqlite': {
|
|
1494
|
+
if (options.betterSqliteSessionVariables)
|
|
1495
|
+
return 'session_context( \'$valid.from\' )';
|
|
1476
1496
|
const dateFromFormat = '%Y-%m-%dT%H:%M:%S.000Z';
|
|
1477
1497
|
return `strftime('${dateFromFormat}', 'now')`;
|
|
1478
1498
|
}
|
|
@@ -1491,6 +1511,8 @@ function toSqlDdl( csn, options ) {
|
|
|
1491
1511
|
if (x.ref[1] === 'to') {
|
|
1492
1512
|
switch (options.sqlDialect) {
|
|
1493
1513
|
case 'sqlite': {
|
|
1514
|
+
if (options.betterSqliteSessionVariables)
|
|
1515
|
+
return 'session_context( \'$valid.to\' )';
|
|
1494
1516
|
// + 1ms compared to $at.from
|
|
1495
1517
|
const dateToFormat = '%Y-%m-%dT%H:%M:%S.001Z';
|
|
1496
1518
|
return `strftime('${dateToFormat}', 'now')`;
|
|
@@ -21,14 +21,15 @@
|
|
|
21
21
|
"sonarjs/cognitive-complexity": "off",
|
|
22
22
|
"sonarjs/no-duplicate-string": "off",
|
|
23
23
|
// Does not recognize TS types
|
|
24
|
-
"jsdoc/no-undefined-types": "off"
|
|
24
|
+
"jsdoc/no-undefined-types": "off",
|
|
25
|
+
"jsdoc/tag-lines": "off"
|
|
25
26
|
},
|
|
26
27
|
"parserOptions": {
|
|
27
|
-
"ecmaVersion":
|
|
28
|
+
"ecmaVersion": 2022,
|
|
28
29
|
"sourceType": "script"
|
|
29
30
|
},
|
|
30
31
|
"env": {
|
|
31
|
-
"
|
|
32
|
+
"es2022": true,
|
|
32
33
|
"node": true
|
|
33
34
|
},
|
|
34
35
|
"settings": {
|
|
@@ -82,7 +82,7 @@ function getAssocToSkippedIgnorer( csn, options, messageFunctions, csnUtils ) {
|
|
|
82
82
|
function ignore( member, memberName, prop, path ) {
|
|
83
83
|
if (options.sqlDialect === 'hana' &&
|
|
84
84
|
!member._ignore && member.target &&
|
|
85
|
-
isAssocOrComposition(member
|
|
85
|
+
isAssocOrComposition(member) &&
|
|
86
86
|
!isPersistedOnDatabase(csn.definitions[member.target])) {
|
|
87
87
|
info(null, path,
|
|
88
88
|
{ target: member.target, anno: '@cds.persistence.skip' },
|
|
@@ -26,7 +26,7 @@ const { forEach } = require('../../utils/objectUtils');
|
|
|
26
26
|
*/
|
|
27
27
|
function expandStructureReferences( csn, options, pathDelimiter, { error, info, throwWithAnyError }, csnUtils, iterateOptions = {} ) {
|
|
28
28
|
const {
|
|
29
|
-
isStructured, get$combined,
|
|
29
|
+
isStructured, get$combined, getFinalTypeInfo,
|
|
30
30
|
} = csnUtils;
|
|
31
31
|
let { effectiveType, inspectRef } = csnUtils;
|
|
32
32
|
|
|
@@ -76,6 +76,10 @@ function expandStructureReferences( csn, options, pathDelimiter, { error, info,
|
|
|
76
76
|
// We can directly use SELECT here, as only projections and SELECT can have .columns
|
|
77
77
|
const root = get$combined({ SELECT: parent });
|
|
78
78
|
if (!hasAnnotationValue(artifact, '@cds.persistence.table')) {
|
|
79
|
+
// Make root look like normal .elements - we never cared about conflict afaik anyway
|
|
80
|
+
Object.keys(root).forEach((key) => {
|
|
81
|
+
root[key] = root[key][0].element;
|
|
82
|
+
});
|
|
79
83
|
const rewritten = rewrite(root, parent.columns, parent.excluding);
|
|
80
84
|
/*
|
|
81
85
|
* Do not remove unexpandable many columns in OData
|
|
@@ -240,7 +244,7 @@ function expandStructureReferences( csn, options, pathDelimiter, { error, info,
|
|
|
240
244
|
*/
|
|
241
245
|
function nextBase( parent, base ) {
|
|
242
246
|
if (parent.ref) {
|
|
243
|
-
const finalBaseType =
|
|
247
|
+
const finalBaseType = getFinalTypeInfo(parent._art.type);
|
|
244
248
|
const art = parent._art;
|
|
245
249
|
|
|
246
250
|
if (finalBaseType && (finalBaseType.type === 'cds.Association' || finalBaseType.type === 'cds.Composition'))
|
|
@@ -306,11 +310,11 @@ function expandStructureReferences( csn, options, pathDelimiter, { error, info,
|
|
|
306
310
|
|
|
307
311
|
/**
|
|
308
312
|
* Rewrite the expand/inline. For expand, keep along the alias - for inline, only leaf-alias has effect.
|
|
309
|
-
* Expand * into the corresponding leaves - correctly handling .
|
|
313
|
+
* Expand * into the corresponding leaves - correctly handling .excluding and shadowing.
|
|
310
314
|
*
|
|
311
315
|
* Iterative, to not run into stack overflow.
|
|
312
316
|
*
|
|
313
|
-
* @param {CSN.Artifact} root All elements visible
|
|
317
|
+
* @param {CSN.Artifact} root All elements visible from the query source ($combined)
|
|
314
318
|
* @param {CSN.Column} col Column to expand
|
|
315
319
|
* @param {Array} ref Ref so far
|
|
316
320
|
* @param {Array} alias Any start-alias
|
|
@@ -361,8 +365,9 @@ function expandStructureReferences( csn, options, pathDelimiter, { error, info,
|
|
|
361
365
|
expanded.push(Object.assign({}, current, { as: currentAlias.join(pathDelimiter) } ));
|
|
362
366
|
}
|
|
363
367
|
else { // preserve stuff like .cast for redirection
|
|
364
|
-
|
|
365
|
-
|
|
368
|
+
const thing = base[currentAlias[currentAlias.length - 1]];
|
|
369
|
+
if (current?._art?.value || thing?.value)
|
|
370
|
+
error('query-unsupported-calc', current.$path || col.$path, { '#': 'inside' });
|
|
366
371
|
expanded.push(Object.assign({}, current, { ref: currentRef, as: currentAlias.join(pathDelimiter) } ));
|
|
367
372
|
}
|
|
368
373
|
}
|
|
@@ -9,6 +9,7 @@ const transformUtils = require('../transformUtilsNew');
|
|
|
9
9
|
const { csnRefs } = require('../../model/csnRefs');
|
|
10
10
|
const { setProp } = require('../../base/model');
|
|
11
11
|
const { forEach } = require('../../utils/objectUtils');
|
|
12
|
+
const { cardinality2str } = require('../../model/csnUtils');
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Strip off leading $self from refs where applicable
|
|
@@ -67,7 +68,7 @@ function resolveTypeReferences( csn, options, resolved, pathDelimiter, iterateOp
|
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
70
|
const { toFinalBaseType, csnUtils } = transformUtils.getTransformers(csn, options, pathDelimiter);
|
|
70
|
-
const { getServiceName,
|
|
71
|
+
const { getServiceName, getFinalTypeInfo } = csnUtils;
|
|
71
72
|
|
|
72
73
|
// We don't want to iterate over actions
|
|
73
74
|
if (iterateOptions.skipDict && !iterateOptions.skipDict.actions)
|
|
@@ -155,7 +156,7 @@ function resolveTypeReferences( csn, options, resolved, pathDelimiter, iterateOp
|
|
|
155
156
|
return false;
|
|
156
157
|
|
|
157
158
|
const typeServiceName = getServiceName(typeName);
|
|
158
|
-
const finalBaseType =
|
|
159
|
+
const finalBaseType = getFinalTypeInfo(typeName)?.type;
|
|
159
160
|
// we need the service of the current definition
|
|
160
161
|
const currDefServiceName = getServiceName(path[1]);
|
|
161
162
|
|
|
@@ -313,7 +314,7 @@ function flattenElements( csn, options, pathDelimiter, error, iterateOptions = {
|
|
|
313
314
|
flatElement.notNull = true;
|
|
314
315
|
|
|
315
316
|
|
|
316
|
-
if (flatElement.type && isAssocOrComposition(flatElement
|
|
317
|
+
if (flatElement.type && isAssocOrComposition(flatElement) && flatElement.on) {
|
|
317
318
|
// Make refs resolvable by fixing the first ref step
|
|
318
319
|
for (const onPart of flatElement.on) {
|
|
319
320
|
if (onPart.ref) {
|
|
@@ -406,12 +407,13 @@ function getBranches( element, elementName, effectiveType, pathDelimiter ) {
|
|
|
406
407
|
* @param {CSN.Model} csn
|
|
407
408
|
* @param {CSN.Options} options
|
|
408
409
|
* @param {Function} error
|
|
410
|
+
* @param {Function} warning
|
|
409
411
|
* @param {string} pathDelimiter
|
|
410
412
|
* @param {boolean} flattenKeyRefs
|
|
411
413
|
* @param {object} csnUtils
|
|
412
414
|
* @param {object} iterateOptions
|
|
413
415
|
*/
|
|
414
|
-
function handleManagedAssociationsAndCreateForeignKeys( csn, options, error, pathDelimiter, flattenKeyRefs, csnUtils, iterateOptions = {} ) {
|
|
416
|
+
function handleManagedAssociationsAndCreateForeignKeys( csn, options, error, warning, pathDelimiter, flattenKeyRefs, csnUtils, iterateOptions = {} ) {
|
|
415
417
|
const { isManagedAssociation, inspectRef, isStructured } = csnUtils;
|
|
416
418
|
const { flattenStructStepsInRef, flattenStructuredElement } = transformUtils.getTransformers(csn, options, pathDelimiter);
|
|
417
419
|
if (flattenKeyRefs) {
|
|
@@ -642,8 +644,13 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, error, pat
|
|
|
642
644
|
if (element.cardinality === undefined)
|
|
643
645
|
element.cardinality = {};
|
|
644
646
|
// min=0 is falsy => check for undefined
|
|
645
|
-
if (element.cardinality.min === undefined)
|
|
647
|
+
if (element.cardinality.min === undefined) {
|
|
646
648
|
element.cardinality.min = 1;
|
|
649
|
+
}
|
|
650
|
+
else if (element.cardinality.min === 0) {
|
|
651
|
+
warning(null, element.$path, { value: cardinality2str(element, false), code: 'not null' },
|
|
652
|
+
'Expected target cardinality $(VALUE) and $(CODE) to match');
|
|
653
|
+
}
|
|
647
654
|
}
|
|
648
655
|
}
|
|
649
656
|
orderedElements.push(...fks);
|
|
@@ -724,16 +731,6 @@ function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathD
|
|
|
724
731
|
else if (lvl === 0) {
|
|
725
732
|
return fks;
|
|
726
733
|
}
|
|
727
|
-
// we have reached a leaf element, create a foreign key
|
|
728
|
-
else if (finalElement && isBuiltinType(finalElement.type)) {
|
|
729
|
-
const newFk = Object.create(null);
|
|
730
|
-
for (const prop of [ 'type', 'length', 'scale', 'precision', 'srid', 'default', '@odata.Type' ]) {
|
|
731
|
-
// copy props from original element to preserve derived types!
|
|
732
|
-
if (element[prop] !== undefined)
|
|
733
|
-
newFk[prop] = element[prop];
|
|
734
|
-
}
|
|
735
|
-
return [ [ prefix, newFk ] ];
|
|
736
|
-
}
|
|
737
734
|
else if (finalElement.elements) {
|
|
738
735
|
Object.entries(finalElement.elements).forEach(([ elemName, elem ]) => {
|
|
739
736
|
// Skip already produced foreign keys
|
|
@@ -743,6 +740,16 @@ function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathD
|
|
|
743
740
|
}
|
|
744
741
|
});
|
|
745
742
|
}
|
|
743
|
+
// we have reached a leaf element, create a foreign key
|
|
744
|
+
else if (finalElement && (finalElement.type == null || isBuiltinType(finalElement.type))) {
|
|
745
|
+
const newFk = Object.create(null);
|
|
746
|
+
for (const prop of [ 'type', 'length', 'scale', 'precision', 'srid', 'default', '@odata.Type' ]) {
|
|
747
|
+
// copy props from original element to preserve derived types!
|
|
748
|
+
if (element[prop] !== undefined)
|
|
749
|
+
newFk[prop] = element[prop];
|
|
750
|
+
}
|
|
751
|
+
return [ [ prefix, newFk ] ];
|
|
752
|
+
}
|
|
746
753
|
|
|
747
754
|
/**
|
|
748
755
|
* Get the path to continue resolving references
|
|
@@ -13,6 +13,8 @@ const cloneCsnOptions = { hiddenPropertiesToClone: [ '_art', '_links', '$env', '
|
|
|
13
13
|
* Rewrite usage of calculated Elements into the expression itself.
|
|
14
14
|
* Delete calculated elements in entities after processing so they don't materialize on the db.
|
|
15
15
|
*
|
|
16
|
+
* TODO: Calculated elements on-write (`stored: true`)
|
|
17
|
+
*
|
|
16
18
|
* @param {CSN.Model} csn
|
|
17
19
|
* @param {CSN.Options} options
|
|
18
20
|
* @param {string} pathDelimiter
|
|
@@ -37,24 +39,21 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
|
|
|
37
39
|
}
|
|
38
40
|
});
|
|
39
41
|
|
|
40
|
-
// Replace calculated elements in filters (if the root-association element is in an entity).
|
|
42
|
+
// Replace calculated elements in filters, functions and other places (if the root-association element is in an entity).
|
|
41
43
|
// Depends on the first pass!
|
|
42
44
|
entities.forEach(({ artifactName }) => {
|
|
43
45
|
applyTransformationsOnNonDictionary(csn.definitions, artifactName, {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
},
|
|
57
|
-
});
|
|
46
|
+
ref: (_parent, _prop, ref, _path, root, index) => {
|
|
47
|
+
if (_parent._art && _parent._art.value) {
|
|
48
|
+
root[index] = _parent._art.value;
|
|
49
|
+
// Note: Depends on A2J rejecting deeply nested filters
|
|
50
|
+
applyTransformationsOnNonDictionary(root, index, {
|
|
51
|
+
ref: (__parent, _, _ref) => {
|
|
52
|
+
if (_ref[0] === '$self' || _ref[0] === '$projection')
|
|
53
|
+
__parent.ref = _ref.slice(-1);
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}
|
|
58
57
|
},
|
|
59
58
|
}, { drillRef: true }, [ 'definitions' ]);
|
|
60
59
|
});
|
|
@@ -100,10 +99,11 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
|
|
|
100
99
|
*/
|
|
101
100
|
function rewriteInView( SELECT, elements, path ) {
|
|
102
101
|
const containsExpandInline = hasExpandInline(SELECT);
|
|
102
|
+
let cleanupCallbacks;
|
|
103
103
|
if (!SELECT.columns) // needs to happen for all subqueries!
|
|
104
|
-
calculateColumns(elements, SELECT);
|
|
104
|
+
cleanupCallbacks = calculateColumns(elements, SELECT);
|
|
105
105
|
else
|
|
106
|
-
makeAllCalculatedElementsExplicitColumns(elements, SELECT, containsExpandInline);
|
|
106
|
+
cleanupCallbacks = makeAllCalculatedElementsExplicitColumns(elements, SELECT, containsExpandInline);
|
|
107
107
|
|
|
108
108
|
const name = SELECT.from.args ? undefined : SELECT.from.as || (SELECT.from.ref && implicitAs(SELECT.from.ref));
|
|
109
109
|
|
|
@@ -114,7 +114,8 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
|
|
|
114
114
|
art, env, links, scope,
|
|
115
115
|
} = getRefInfo(parent, p);
|
|
116
116
|
|
|
117
|
-
|
|
117
|
+
// TODO: Calculated elements on-write
|
|
118
|
+
if (art?.value && !art.value.stored) {
|
|
118
119
|
const alias = parent.as || implicitAs(parent.ref);
|
|
119
120
|
// TODO: What about other scopes? expand/inline?
|
|
120
121
|
const value = (scope !== 'ref-target') ? absolutifyPaths(env, art, ref, links, name).value : keepAssocStepsInRef(ref, links, art).value;
|
|
@@ -129,10 +130,21 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
|
|
|
129
130
|
root[p[p.length - 1]].as = alias;
|
|
130
131
|
else
|
|
131
132
|
delete root[p[p.length - 1]].as;
|
|
133
|
+
|
|
134
|
+
// If the calculated element has a type, use it. But only if the column did not have an explicit type.
|
|
135
|
+
// Note: We should not check `art.type`, because we only need the type for columns, not filters.
|
|
136
|
+
if (parent.cast)
|
|
137
|
+
root[p[p.length - 1]].cast = parent.cast;
|
|
138
|
+
else if (parent._element?.type)
|
|
139
|
+
root[p[p.length - 1]].cast = { type: parent._element.type };
|
|
140
|
+
|
|
141
|
+
// TODO: Copy annotations? May become relevant in the future
|
|
132
142
|
}
|
|
133
143
|
},
|
|
134
144
|
}, {}, path);
|
|
135
145
|
}
|
|
146
|
+
|
|
147
|
+
cleanupCallbacks.forEach(fn => fn());
|
|
136
148
|
}
|
|
137
149
|
|
|
138
150
|
/**
|
|
@@ -198,6 +210,7 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
|
|
|
198
210
|
});
|
|
199
211
|
}
|
|
200
212
|
else if (current.value.ref && current.value._art?.value) {
|
|
213
|
+
// TODO: Check for calculated elements on-write
|
|
201
214
|
const linksBase = current.value._links;
|
|
202
215
|
const refBase = current.value.ref;
|
|
203
216
|
const parentIndex = Array.isArray(current.parent) ? current.parent.indexOf(current.value) : -1;
|
|
@@ -285,9 +298,10 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
|
|
|
285
298
|
*/
|
|
286
299
|
function calculateColumns( elements, carrier ) {
|
|
287
300
|
carrier.columns = [ '*' ];
|
|
288
|
-
makeAllCalculatedElementsExplicitColumns(elements, carrier, false);
|
|
301
|
+
const cleanupCallbacks = makeAllCalculatedElementsExplicitColumns(elements, carrier, false);
|
|
289
302
|
if (carrier.columns.length === 1 && carrier.columns[0] === '*')
|
|
290
303
|
delete carrier.columns;
|
|
304
|
+
return cleanupCallbacks;
|
|
291
305
|
}
|
|
292
306
|
|
|
293
307
|
/**
|
|
@@ -304,7 +318,6 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
|
|
|
304
318
|
return from.SELECT.elements;
|
|
305
319
|
}
|
|
306
320
|
else if (from.SET) {
|
|
307
|
-
// FIXME: Check if this is correct
|
|
308
321
|
// args[0] could be SELECT or UNION
|
|
309
322
|
return getDirectlyAdressableElements({ from: from.SET.args[0] });
|
|
310
323
|
}
|
|
@@ -316,7 +329,6 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
|
|
|
316
329
|
mergedElements[elementName] = arg._art.elements[elementName];
|
|
317
330
|
}
|
|
318
331
|
else if (arg.SET) {
|
|
319
|
-
// FIXME: Check if this is correct
|
|
320
332
|
return getDirectlyAdressableElements({ from: arg.SET.args[0] });
|
|
321
333
|
}
|
|
322
334
|
else if (arg.SELECT) { // TODO: UNION
|
|
@@ -348,6 +360,7 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
|
|
|
348
360
|
* @param {boolean} containsExpandInline
|
|
349
361
|
*/
|
|
350
362
|
function makeAllCalculatedElementsExplicitColumns( elements, SELECT, containsExpandInline ) {
|
|
363
|
+
const cleanupCallbacks = [];
|
|
351
364
|
const root = getDirectlyAdressableElements(SELECT);
|
|
352
365
|
const columnMap = getColumnMap( { SELECT });
|
|
353
366
|
const hasStar = SELECT.columns.includes('*');
|
|
@@ -368,10 +381,16 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
|
|
|
368
381
|
containsCalculated = true;
|
|
369
382
|
const columns = [];
|
|
370
383
|
for (const branchName in branches) {
|
|
371
|
-
if (columnMap[branchName]) // Existing column - don't overwrite, we need $env!
|
|
384
|
+
if (columnMap[branchName]) { // Existing column - don't overwrite, we need $env!
|
|
372
385
|
columns.push(columnMap[branchName]);
|
|
373
|
-
|
|
374
|
-
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
// TODO: Hm, will we have a $env in the leaf of the thing then?
|
|
389
|
+
const column = { ref: [ ...originalRef, ...branches[branchName].ref.slice(1) ], as: branchName };
|
|
390
|
+
setProp(column, '_element', element);
|
|
391
|
+
cleanupCallbacks.push(() => delete column._element);
|
|
392
|
+
columns.push(column);
|
|
393
|
+
}
|
|
375
394
|
}
|
|
376
395
|
if (columnMap[name]) {
|
|
377
396
|
unfoldingMap[name] = [ false, [ ...columns ] ];
|
|
@@ -406,6 +425,7 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
|
|
|
406
425
|
|
|
407
426
|
SELECT.columns = newColumns;
|
|
408
427
|
}
|
|
428
|
+
return cleanupCallbacks;
|
|
409
429
|
}
|
|
410
430
|
|
|
411
431
|
/**
|
|
@@ -553,9 +573,8 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
|
|
|
553
573
|
|
|
554
574
|
/**
|
|
555
575
|
* @param {CSN.Model} csn
|
|
556
|
-
* @param {CSN.Options} _options
|
|
557
576
|
*/
|
|
558
|
-
function processCalculatedElementsInEntities( csn
|
|
577
|
+
function processCalculatedElementsInEntities( csn ) {
|
|
559
578
|
forEachDefinition(csn, (artifact, artifactName) => {
|
|
560
579
|
if (artifact.kind === 'entity' && !(artifact.query || artifact.projection))
|
|
561
580
|
killInEntity(artifact, [ 'definitions', artifactName ]);
|
|
@@ -574,7 +593,8 @@ function processCalculatedElementsInEntities( csn, _options ) {
|
|
|
574
593
|
function killInEntity( artifact, path ) {
|
|
575
594
|
applyTransformationsOnDictionary(artifact.elements, {
|
|
576
595
|
value: (parent, prop, value, p, root) => {
|
|
577
|
-
|
|
596
|
+
if (!value.stored)
|
|
597
|
+
delete root[p[p.length - 1]];
|
|
578
598
|
},
|
|
579
599
|
}, {}, path);
|
|
580
600
|
}
|
|
@@ -588,8 +608,9 @@ function killInEntity( artifact, path ) {
|
|
|
588
608
|
*/
|
|
589
609
|
function dummifyInEntity( artifact, path ) {
|
|
590
610
|
applyTransformationsOnDictionary(artifact.elements, {
|
|
591
|
-
value: (parent) => {
|
|
592
|
-
|
|
611
|
+
value: (parent, _prop, value) => {
|
|
612
|
+
if (!value.stored)
|
|
613
|
+
parent.value = { val: 'DUMMY' };
|
|
593
614
|
},
|
|
594
615
|
}, {}, path);
|
|
595
616
|
}
|
|
@@ -294,7 +294,7 @@ function getViewTransformer( csn, options, messageFunctions, transformCommon ) {
|
|
|
294
294
|
function addImplicitAliasWithAssoc( col, path ) {
|
|
295
295
|
if (!col.as && col.ref && col.ref.length > 1) {
|
|
296
296
|
const { links } = inspectRef(path);
|
|
297
|
-
if (links && links.slice(0, -1).some(({ art }) =>
|
|
297
|
+
if (links && links.slice(0, -1).some(({ art }) => art && isAssocOrComposition(art)))
|
|
298
298
|
col.as = getLastRefStepString(col.ref);
|
|
299
299
|
}
|
|
300
300
|
}
|
|
@@ -75,7 +75,7 @@ function generateDrafts( csn, options, pathDelimiter, messageFunctions ) {
|
|
|
75
75
|
// Follow all composition targets in elements of 'artifact'
|
|
76
76
|
for (const elemName in artifact.elements) {
|
|
77
77
|
const elem = artifact.elements[elemName];
|
|
78
|
-
if (elem.target && isComposition(elem
|
|
78
|
+
if (elem.target && isComposition(elem)) {
|
|
79
79
|
const draftNode = getCsnDef(elem.target);
|
|
80
80
|
const draftNodeName = elem.target;
|
|
81
81
|
// Sanity check
|
|
@@ -33,10 +33,9 @@ function generateDrafts( csn, options, services ) {
|
|
|
33
33
|
csnUtils,
|
|
34
34
|
} = getTransformers(csn, options);
|
|
35
35
|
const {
|
|
36
|
-
getFinalType,
|
|
37
36
|
getServiceName,
|
|
38
37
|
hasAnnotationValue,
|
|
39
|
-
|
|
38
|
+
getFinalTypeInfo,
|
|
40
39
|
} = csnUtils;
|
|
41
40
|
|
|
42
41
|
const { error, info } = makeMessageFunction(csn, options, 'for.odata');
|
|
@@ -173,7 +172,7 @@ function generateDrafts( csn, options, services ) {
|
|
|
173
172
|
|
|
174
173
|
// Draft-enable the targets of composition elements (draft nodes), too
|
|
175
174
|
// TODO rewrite
|
|
176
|
-
if (elem.target && elem.type &&
|
|
175
|
+
if (elem.target && elem.type && getFinalTypeInfo(elem.type)?.type === 'cds.Composition') {
|
|
177
176
|
const draftNode = csn.definitions[elem.target];
|
|
178
177
|
|
|
179
178
|
// Ignore if that is our own draft root
|
|
@@ -196,7 +195,7 @@ function generateDrafts( csn, options, services ) {
|
|
|
196
195
|
stack.push(elem);
|
|
197
196
|
}
|
|
198
197
|
else if (elem.type) { // types - possibly structured
|
|
199
|
-
const typeDef =
|
|
198
|
+
const typeDef = getFinalTypeInfo(elem.type);
|
|
200
199
|
if (typeDef?.elements)
|
|
201
200
|
stack.push(typeDef);
|
|
202
201
|
}
|
|
@@ -98,7 +98,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
98
98
|
inspectRef,
|
|
99
99
|
artifactRef,
|
|
100
100
|
effectiveType,
|
|
101
|
-
|
|
101
|
+
getFinalTypeInfo
|
|
102
102
|
} = csnUtils;
|
|
103
103
|
|
|
104
104
|
// are we working with structured OData or not
|
|
@@ -130,7 +130,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
130
130
|
transformUtils.rewriteBuiltinTypeRef(csn);
|
|
131
131
|
|
|
132
132
|
const cleanup = validate.forOdata(csn, {
|
|
133
|
-
message, error, warning, info, inspectRef, effectiveType,
|
|
133
|
+
message, error, warning, info, inspectRef, effectiveType, getFinalTypeInfo, artifactRef, csn, options, csnUtils, services, isAspect, isExternalServiceMember
|
|
134
134
|
});
|
|
135
135
|
|
|
136
136
|
|
|
@@ -186,7 +186,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
186
186
|
|
|
187
187
|
// TODO: add the generated foreign keys to the columns when we are in a view
|
|
188
188
|
// see db/views.js::addForeignKeysToColumns
|
|
189
|
-
flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, error, '_', !structuredOData, csnUtils,{ skipArtifact: isExternalServiceMember });
|
|
189
|
+
flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, error, warning, '_', !structuredOData, csnUtils,{ skipArtifact: isExternalServiceMember });
|
|
190
190
|
|
|
191
191
|
// Allow using managed associations as steps in on-conditions to access their fks
|
|
192
192
|
// To be done after handleManagedAssociationsAndCreateForeignKeys,
|
|
@@ -398,8 +398,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
398
398
|
// Handles on-conditions in unmanaged associations
|
|
399
399
|
function processOnCond(def) {
|
|
400
400
|
forEachMemberRecursively(def, (member) => {
|
|
401
|
-
|
|
402
|
-
if (member.type && isAssocOrComposition(member.type) && member.on) {
|
|
401
|
+
if (member.on && isAssocOrComposition(member)) {
|
|
403
402
|
removeLeadingDollarSelfInOnCondition(member);
|
|
404
403
|
}
|
|
405
404
|
});
|
|
@@ -432,7 +431,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
432
431
|
// TODO: test???
|
|
433
432
|
function addCommonValueListviaAssociation(member, memberName) {
|
|
434
433
|
let vlAnno = '@Common.ValueList.viaAssociation';
|
|
435
|
-
if (isAssociation(member
|
|
434
|
+
if (isAssociation(member)) {
|
|
436
435
|
let navigable = member['@odata.navigable'] !== false; // navigable disabled only if explicitly set to false
|
|
437
436
|
let targetDef = getCsnDef(member.target);
|
|
438
437
|
if (navigable && targetDef['@cds.odata.valuelist'] && !member[vlAnno]) {
|