@sap/cds-compiler 4.8.0 → 4.9.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 +29 -4
- package/bin/cds_remove_invalid_whitespace.js +135 -0
- package/bin/cds_update_annotations.js +180 -0
- package/bin/cds_update_identifiers.js +3 -4
- package/bin/cdsc.js +14 -1
- package/doc/CHANGELOG_BETA.md +19 -0
- package/lib/api/main.js +59 -24
- package/lib/api/options.js +12 -1
- package/lib/api/validate.js +1 -5
- package/lib/base/builtins.js +27 -0
- package/lib/base/message-registry.js +32 -19
- package/lib/base/messages.js +50 -19
- package/lib/base/model.js +4 -5
- package/lib/checks/actionsFunctions.js +2 -2
- package/lib/checks/annotationsOData.js +3 -0
- package/lib/checks/defaultValues.js +5 -2
- package/lib/checks/queryNoDbArtifacts.js +3 -2
- package/lib/checks/validator.js +2 -34
- package/lib/compiler/assert-consistency.js +8 -2
- package/lib/compiler/checks.js +44 -18
- package/lib/compiler/define.js +34 -22
- package/lib/compiler/extend.js +33 -10
- package/lib/compiler/index.js +0 -1
- package/lib/compiler/lsp-api.js +5 -0
- package/lib/compiler/propagator.js +21 -18
- package/lib/compiler/resolve.js +44 -28
- package/lib/compiler/shared.js +60 -20
- package/lib/compiler/tweak-assocs.js +13 -88
- package/lib/compiler/xpr-rewrite.js +689 -0
- package/lib/edm/annotations/genericTranslation.js +80 -60
- package/lib/edm/edm.js +4 -4
- package/lib/edm/edmInboundChecks.js +33 -0
- package/lib/edm/edmPreprocessor.js +9 -6
- package/lib/gen/Dictionary.json +129 -14
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +1523 -1518
- package/lib/json/from-csn.js +13 -4
- package/lib/json/to-csn.js +10 -11
- package/lib/language/genericAntlrParser.js +14 -6
- package/lib/main.d.ts +67 -14
- package/lib/main.js +1 -0
- package/lib/model/cloneCsn.js +6 -3
- package/lib/model/csnRefs.js +12 -7
- package/lib/model/csnUtils.js +13 -7
- package/lib/model/enrichCsn.js +3 -1
- package/lib/model/revealInternalProperties.js +2 -1
- package/lib/model/sortViews.js +14 -6
- package/lib/modelCompare/compare.js +33 -34
- package/lib/optionProcessor.js +27 -2
- package/lib/render/DuplicateChecker.js +6 -6
- package/lib/render/manageConstraints.js +1 -0
- package/lib/render/toCdl.js +3 -1
- package/lib/transform/db/applyTransformations.js +33 -0
- package/lib/transform/db/constraints.js +1 -1
- package/lib/transform/db/expansion.js +8 -3
- package/lib/transform/db/groupByOrderBy.js +2 -2
- package/lib/transform/db/temporal.js +6 -3
- package/lib/transform/db/transformExists.js +2 -2
- package/lib/transform/effective/annotations.js +194 -0
- package/lib/transform/effective/main.js +6 -8
- package/lib/transform/effective/misc.js +31 -10
- package/lib/transform/forOdata.js +23 -7
- package/lib/transform/forRelationalDB.js +1 -1
- package/lib/transform/localized.js +7 -6
- package/lib/transform/odata/flattening.js +189 -106
- package/lib/transform/odata/toFinalBaseType.js +1 -1
- package/lib/transform/odata/typesExposure.js +15 -12
- package/lib/transform/parseExpr.js +4 -4
- package/lib/transform/transformUtils.js +40 -37
- package/lib/transform/translateAssocsToJoins.js +47 -47
- package/lib/transform/universalCsn/universalCsnEnricher.js +12 -16
- package/package.json +1 -1
- package/share/messages/anno-missing-rewrite.md +45 -0
- package/share/messages/message-explanations.json +1 -0
- package/bin/.eslintrc.json +0 -17
- package/lib/api/.eslintrc.json +0 -37
- package/lib/checks/.eslintrc.json +0 -31
- package/lib/compiler/.eslintrc.json +0 -8
- package/lib/edm/.eslintrc.json +0 -46
- package/lib/inspect/.eslintrc.json +0 -4
- package/lib/json/.eslintrc.json +0 -4
- package/lib/language/.eslintrc.json +0 -4
- package/lib/model/.eslintrc.json +0 -13
- package/lib/modelCompare/utils/.eslintrc.json +0 -22
- package/lib/render/.eslintrc.json +0 -22
- package/lib/transform/.eslintrc.json +0 -13
- package/lib/transform/db/.eslintrc.json +0 -41
- package/lib/transform/draft/.eslintrc.json +0 -4
- package/lib/transform/effective/.eslintrc.json +0 -4
- package/lib/transform/universalCsn/.eslintrc.json +0 -37
- package/lib/utils/.eslintrc.json +0 -7
|
@@ -97,12 +97,12 @@ class DuplicateChecker {
|
|
|
97
97
|
else
|
|
98
98
|
namingMode = 'plain';
|
|
99
99
|
|
|
100
|
-
error(null, [ 'definitions', artifact.modelName ],
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
100
|
+
error(null, [ 'definitions', artifact.modelName ], {
|
|
101
|
+
name: collidesWith.modelName, prop: namingMode, '#': artifact.modelName.includes('.') ? 'dots' : 'std',
|
|
102
|
+
}, {
|
|
103
|
+
std: 'Artifact name can\'t be mapped to a SQL compliant identifier in naming mode $(PROP) because it conflicts with existing definition $(NAME)',
|
|
104
|
+
dots: 'Artifact name containing dots can\'t be mapped to a SQL compliant identifier in naming mode $(PROP) because it conflicts with existing definition $(NAME)',
|
|
105
|
+
});
|
|
106
106
|
});
|
|
107
107
|
}
|
|
108
108
|
artifacts.forEach((artifact) => {
|
|
@@ -32,6 +32,7 @@ function alterConstraintsWithCsn( csn, options, messageFunctions ) {
|
|
|
32
32
|
warning(null, null, { prop: sqlDialect || 'plain' }, 'Referential Constraints are not available for sql dialect $(PROP)');
|
|
33
33
|
|
|
34
34
|
if (drop && alter)
|
|
35
|
+
// eslint-disable-next-line cds-compiler/message-no-quotes
|
|
35
36
|
error(null, null, 'Option “--drop” can\'t be combined with “--alter”');
|
|
36
37
|
|
|
37
38
|
// Of course, we want the database constraints
|
package/lib/render/toCdl.js
CHANGED
|
@@ -614,10 +614,12 @@ function csnToCdl( csn, options, msg ) {
|
|
|
614
614
|
else if (element['#'] !== undefined) { // enum symbol reference
|
|
615
615
|
result += ` = #${element['#']}`;
|
|
616
616
|
}
|
|
617
|
-
else if (!isCalcElement || !isDirectAssocOrComp(element.type)) {
|
|
617
|
+
else if (!isCalcElement || !isDirectAssocOrComp(element.type) && !element.$filtered) {
|
|
618
618
|
// If the element is a calculated element _and_ a direct association or
|
|
619
619
|
// composition, we'd render `Association to F on (cond) = calcValue;` which
|
|
620
620
|
// would alter the ON-condition.
|
|
621
|
+
// If it is a calculated element _and_ an indirect association (via type chain),
|
|
622
|
+
// we'd get a cast to an association.
|
|
621
623
|
const props = renderTypeReferenceAndProps(element, env);
|
|
622
624
|
if (props !== '')
|
|
623
625
|
result += ` : ${props}`;
|
|
@@ -344,7 +344,40 @@ function transformExpression( parent, propName, transformers, path = [] ) {
|
|
|
344
344
|
return parent;
|
|
345
345
|
}
|
|
346
346
|
|
|
347
|
+
/**
|
|
348
|
+
* Merge an array of transformer-objects into a single one, set the this-value of every subfunction to "that"
|
|
349
|
+
*
|
|
350
|
+
* @param {object[]} transformers transformers
|
|
351
|
+
* @param {object} that Value for this
|
|
352
|
+
* @returns {object} Remapped transformers.
|
|
353
|
+
*/
|
|
354
|
+
function mergeTransformers( transformers, that ) {
|
|
355
|
+
const remapped = {};
|
|
356
|
+
for (const transformer of transformers) {
|
|
357
|
+
for (const [ n, fns ] of Object.entries(transformer)) {
|
|
358
|
+
if (!remapped[n])
|
|
359
|
+
remapped[n] = [];
|
|
360
|
+
|
|
361
|
+
if (Array.isArray(fns)) {
|
|
362
|
+
remapped[n].push((parent, name, prop, path, parentParent) => fns.forEach(
|
|
363
|
+
fn => fn.bind(that)(parent, name, prop, path, parentParent)
|
|
364
|
+
));
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
remapped[n].push((parent, name, prop, path, parentParent) => fns.bind(that)(parent, name, prop, path, parentParent));
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
for (const [ n, fns ] of Object.entries(remapped))
|
|
373
|
+
remapped[n] = (parent, name, prop, path, parentParent) => fns.forEach(fn => fn.bind(that)(parent, name, prop, path, parentParent));
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
return remapped;
|
|
377
|
+
}
|
|
378
|
+
|
|
347
379
|
module.exports = {
|
|
380
|
+
mergeTransformers,
|
|
348
381
|
transformExpression,
|
|
349
382
|
applyTransformations,
|
|
350
383
|
applyTransformationsOnNonDictionary,
|
|
@@ -116,7 +116,7 @@ function createReferentialConstraints( csn, options ) {
|
|
|
116
116
|
foreignKeyConstraintForAssociation(up_, dependent, [ 'definitions', composition.target, 'elements', upLinkName ], path[path.length - 1] );
|
|
117
117
|
}
|
|
118
118
|
else if (!onCondition && composition.keys.length > 0) {
|
|
119
|
-
throw new CompilerAssertion('
|
|
119
|
+
throw new CompilerAssertion('Debug me, an on-condition was expected here, but only found keys');
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
|
|
@@ -98,7 +98,9 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
98
98
|
*/
|
|
99
99
|
if (rewritten.toMany.length > 0 && !options.toOdata) {
|
|
100
100
|
markAsToDummify(artifact, path[1]);
|
|
101
|
-
|
|
101
|
+
rewritten.toMany.forEach(({ art }) => {
|
|
102
|
+
error( null, art.$path || [ 'definitions', path[1] ], { name: `${art.$env || path[1]}:${art.ref.map(r => r.id || r)}` }, 'Unexpected .expand with to-many association $(NAME)');
|
|
103
|
+
});
|
|
102
104
|
}
|
|
103
105
|
else {
|
|
104
106
|
parent.columns = rewritten.columns;
|
|
@@ -175,8 +177,11 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
175
177
|
const target = art.target ? art.target : pathStep;
|
|
176
178
|
if (toDummify.indexOf(target) !== -1) {
|
|
177
179
|
error( null, obj.$path, {
|
|
178
|
-
id: pathStep,
|
|
179
|
-
|
|
180
|
+
id: pathStep,
|
|
181
|
+
elemref: obj,
|
|
182
|
+
name,
|
|
183
|
+
anno: '@cds.persistence.skip',
|
|
184
|
+
}, 'Unexpected $(ANNO) annotation on Association target $(NAME) of $(ID) in path $(ELEMREF) was skipped because of .expand in conjunction with to-many');
|
|
180
185
|
}
|
|
181
186
|
}
|
|
182
187
|
|
|
@@ -28,8 +28,8 @@ function replaceAssociationsInGroupByOrderBy( inputQuery, options, inspectRef, e
|
|
|
28
28
|
// (230 c) If we keep associations as they are (hdbcds naming convention), we can't have associations in GROUP BY
|
|
29
29
|
if (options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds') {
|
|
30
30
|
error(null, groupByPath,
|
|
31
|
-
{ $reviewed: true },
|
|
32
|
-
'Unexpected managed association in
|
|
31
|
+
{ $reviewed: true, keyword: 'GROUP BY', value: 'hdbcds' },
|
|
32
|
+
'Unexpected managed association in $(KEYWORD) for naming mode $(VALUE)');
|
|
33
33
|
continue;
|
|
34
34
|
}
|
|
35
35
|
const pathPrefix = query.groupBy[i].ref.slice(0, -1);
|
|
@@ -4,7 +4,7 @@ const {
|
|
|
4
4
|
getNormalizedQuery, hasAnnotationValue, forEachMember,
|
|
5
5
|
} = require('../../model/csnUtils');
|
|
6
6
|
const { implicitAs } = require('../../model/csnRefs');
|
|
7
|
-
const { setProp } = require('../../base/model');
|
|
7
|
+
const { setProp, isBetaEnabled } = require('../../base/model');
|
|
8
8
|
const { getTransformers } = require('../transformUtils');
|
|
9
9
|
|
|
10
10
|
const validToString = '@cds.valid.to';
|
|
@@ -21,9 +21,10 @@ const validFromString = '@cds.valid.from';
|
|
|
21
21
|
* @param {object} messageFunctions
|
|
22
22
|
* @param {Function} messageFunctions.info
|
|
23
23
|
* @param {object} csnUtils
|
|
24
|
+
* @param {object} options
|
|
24
25
|
* @returns {(artifact: CSN.Artifact, artifactName: string) => void} Callback for forEachDefinition applying the where-condition to views.
|
|
25
26
|
*/
|
|
26
|
-
function getViewDecorator( csn, messageFunctions, csnUtils ) {
|
|
27
|
+
function getViewDecorator( csn, messageFunctions, csnUtils, options ) {
|
|
27
28
|
const { info } = messageFunctions;
|
|
28
29
|
const { get$combined } = csnUtils;
|
|
29
30
|
return addTemporalWhereConditionToView;
|
|
@@ -52,7 +53,9 @@ function getViewDecorator( csn, messageFunctions, csnUtils ) {
|
|
|
52
53
|
if (from.length === 1 && to.length === 1) {
|
|
53
54
|
// and both are from the same origin
|
|
54
55
|
if (from[0].source === to[0].source && from[0].parent === to[0].parent) {
|
|
55
|
-
|
|
56
|
+
const omitWhereClause = isBetaEnabled(options, 'temporalRawProjection') &&
|
|
57
|
+
hasFalsyTemporalAnnotations(normalizedQuery.query.SELECT, artifact.elements, from[0], to[0]);
|
|
58
|
+
if (!omitWhereClause) {
|
|
56
59
|
const fromPath = {
|
|
57
60
|
ref: [
|
|
58
61
|
from[0].parent,
|
|
@@ -616,10 +616,10 @@ function handleExists( csn, options, error, inspectRef, initDefinition, dropDefi
|
|
|
616
616
|
else if (typeof xpr.$env === 'number') {
|
|
617
617
|
if (xpr.$scope === 'mixin')
|
|
618
618
|
return '';
|
|
619
|
-
return error(null, xpr.$path, '$env with number is not handled yet -
|
|
619
|
+
return error(null, xpr.$path, '$env with number is not handled yet - report this error!');
|
|
620
620
|
}
|
|
621
621
|
|
|
622
|
-
return error(null, xpr.$path, 'Boolean $env is not handled yet -
|
|
622
|
+
return error(null, xpr.$path, 'Boolean $env is not handled yet - report this error!');
|
|
623
623
|
}
|
|
624
624
|
else if (xpr.ref) {
|
|
625
625
|
throw new ModelError('Missing $env and missing leading artifact ref - throwing to trigger recompilation!');
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { CompilerAssertion } = require('../../base/error');
|
|
4
|
+
|
|
5
|
+
const directMappings = {
|
|
6
|
+
'@Common.IsDayOfCalendarMonth': replace('@Semantics.calendar.dayOfMonth'),
|
|
7
|
+
'@Common.IsDayOfCalendarYear': replace('@Semantics.calendar.dayOfYear'),
|
|
8
|
+
'@Common.IsCalendarWeek': replace('@Semantics.calendar.week'),
|
|
9
|
+
'@Common.IsCalendarMonth': replace('@Semantics.calendar.month'),
|
|
10
|
+
'@Common.IsCalendarQuarter': replace('@Semantics.calendar.quarter'),
|
|
11
|
+
'@Common.IsCalendarHalfyear': replace('@Semantics.calendar.halfyear'),
|
|
12
|
+
'@Common.IsCalendarYear': replace('@Semantics.calendar.year'),
|
|
13
|
+
'@Common.IsCalendarYearWeek': replace('@Semantics.calendar.yearWeek'),
|
|
14
|
+
'@Common.IsCalendarYearMonth': replace('@Semantics.calendar.yearMonth'),
|
|
15
|
+
'@Common.IsCalendarYearQuarter': replace('@Semantics.calendar.yearQuarter'),
|
|
16
|
+
'@Common.IsCalendarYearHalfyear': replace('@Semantics.calendar.yearHalfyear'),
|
|
17
|
+
'@Common.IsCalendarDate': replace('@Semantics.date'),
|
|
18
|
+
'@Common.IsFiscalYearVariant': replace('@Semantics.yearVariant'),
|
|
19
|
+
'@Common.IsFiscalPeriod': replace('@Semantics.period'),
|
|
20
|
+
'@Common.IsFiscalYear': replace('@Semantics.year'),
|
|
21
|
+
'@Common.IsFiscalYearPeriod': replace('@Semantics.yearPeriod'),
|
|
22
|
+
'@Common.IsFiscalQuarter': replace('@Semantics.quarter'),
|
|
23
|
+
'@Common.IsFiscalYearQuarter': replace('@Semantics.yearQuarter'),
|
|
24
|
+
'@Common.IsFiscalWeek': replace('@Semantics.week'),
|
|
25
|
+
'@Common.IsFiscalYearWeek': replace('@Semantics.yearWeek'),
|
|
26
|
+
'@Common.IsDayOfFiscalYear': replace('@Semantics.dayOfYear'),
|
|
27
|
+
'@Measures.ISOCurrency': (csn, artifact, element, oldAnno) => {
|
|
28
|
+
const { targetElement } = getAnnoRefTarget(csn, artifact, element[oldAnno]);
|
|
29
|
+
if (refPointsToThisArtifact(csn, artifact, element, oldAnno)) {
|
|
30
|
+
replace('@Semantics.amount.currencyCode')(csn, artifact, element, oldAnno);
|
|
31
|
+
if (targetElement && targetElement['@Semantics.currencyCode'] === undefined)
|
|
32
|
+
targetElement['@Semantics.currencyCode'] = true;
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
'@Measures.Unit': (csn, artifact, element, oldAnno) => {
|
|
36
|
+
const { targetElement } = getAnnoRefTarget(csn, artifact, element[oldAnno]);
|
|
37
|
+
if (refPointsToThisArtifact(csn, artifact, element, oldAnno)) {
|
|
38
|
+
replace('@Semantics.quantity.unitOfMeasure')(csn, artifact, element, oldAnno);
|
|
39
|
+
if (targetElement && targetElement['@Semantics.unitOfMeasure'] === undefined)
|
|
40
|
+
targetElement['@Semantics.unitOfMeasure'] = true;
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
'@UI.IsImageURL': replace('@Semantics.imageUrl'),
|
|
44
|
+
'@Common.ValueList.CollectionPath': (csn, artifact, element) => {
|
|
45
|
+
if (!element.target && element['@Consumption.valueHelpDefinition'] === undefined) {
|
|
46
|
+
if (element['@Common.ValueList.Parameters'] && Array.isArray(element['@Common.ValueList.Parameters'])) {
|
|
47
|
+
const InOutParameters = element['@Common.ValueList.Parameters'].filter(param => param.$Type === 'Common.ValueListParameterInOut');
|
|
48
|
+
|
|
49
|
+
if (InOutParameters.length === 1) {
|
|
50
|
+
element['@Consumption.valueHelpDefinition'] = [ {
|
|
51
|
+
name: element['@Common.ValueList.CollectionPath'],
|
|
52
|
+
} ];
|
|
53
|
+
|
|
54
|
+
delete element['@Common.ValueList.CollectionPath'];
|
|
55
|
+
delete element['@Common.ValueList.Label'];
|
|
56
|
+
|
|
57
|
+
element['@Consumption.valueHelpDefinition'][0].element = element['@Common.ValueList.Parameters'][0].ValueListProperty;
|
|
58
|
+
delete element['@Common.ValueList.Parameters'];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
'@Common.TextFor': replace('@Semantics.text', true),
|
|
64
|
+
'@Common.IsLanguageIdentifier': replaceIf('@Semantics.language', true, (csn, artifact, element, anno) => !!element[anno]),
|
|
65
|
+
// We need to set two different annos here, depending on the value -> need a custom replacer
|
|
66
|
+
'@Common.Text': (csn, artifact, element, oldAnno) => {
|
|
67
|
+
const { targetArtifact, targetElement } = getAnnoRefTarget(csn, artifact, element[oldAnno]);
|
|
68
|
+
if (targetArtifact === artifact && !element['@ObjectModel.text.element'] && !targetElement['@Semantics.text']) {
|
|
69
|
+
element['@ObjectModel.text.element'] = element[oldAnno];
|
|
70
|
+
if (targetElement['@Semantics.text'] === undefined)
|
|
71
|
+
targetElement['@Semantics.text'] = true;
|
|
72
|
+
delete element['@Common.Text'];
|
|
73
|
+
}
|
|
74
|
+
else if (targetArtifact && targetElement && !element['@ObjectModel.text.association'] && !targetElement['@Semantics.text']) {
|
|
75
|
+
element['@ObjectModel.text.association'] = element[oldAnno];
|
|
76
|
+
if (targetElement['@Semantics.text'] === undefined)
|
|
77
|
+
targetElement['@Semantics.text'] = true;
|
|
78
|
+
delete element['@Common.Text'];
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
*
|
|
85
|
+
* @param {CSN.Model} csn
|
|
86
|
+
* @param {CSN.Artifact} artifact
|
|
87
|
+
* @param {CSN.Element} element
|
|
88
|
+
* @param {object} anno
|
|
89
|
+
* @returns {boolean}
|
|
90
|
+
*/
|
|
91
|
+
function refPointsToThisArtifact( csn, artifact, element, anno ) {
|
|
92
|
+
const { targetArtifact } = getAnnoRefTarget(csn, artifact, element[anno]);
|
|
93
|
+
return targetArtifact && targetArtifact === artifact;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Walk the possible annotation ref and return the artifact and element it points to
|
|
98
|
+
*
|
|
99
|
+
* @param {CSN.Model} csn
|
|
100
|
+
* @param {CSN.Artifact} startArtifact
|
|
101
|
+
* @param {object} annoValue
|
|
102
|
+
* @returns {object}
|
|
103
|
+
*/
|
|
104
|
+
function getAnnoRefTarget( csn, startArtifact, annoValue ) {
|
|
105
|
+
if (!annoValue || !annoValue['='])
|
|
106
|
+
return { targetArtifact: undefined, targetElement: undefined };
|
|
107
|
+
|
|
108
|
+
const steps = annoValue['='].split('.');
|
|
109
|
+
let base = startArtifact;
|
|
110
|
+
let element;
|
|
111
|
+
for (const step of steps) {
|
|
112
|
+
if (!base.elements)
|
|
113
|
+
return { targetArtifact: undefined, targetElement: undefined };
|
|
114
|
+
element = base.elements[step];
|
|
115
|
+
if (!element)
|
|
116
|
+
return { targetArtifact: undefined, targetElement: undefined };
|
|
117
|
+
if (element.target)
|
|
118
|
+
base = csn.definitions[element.target];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return { targetArtifact: base, targetElement: element };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get the function to replace oldAnno with newAnno on carrier.
|
|
126
|
+
*
|
|
127
|
+
* - If available, use "replacement" as value.
|
|
128
|
+
* - Only do replacement if "condition" returns true
|
|
129
|
+
* - Possibly set additional annotations via "additional"
|
|
130
|
+
* @param {string} newAnno
|
|
131
|
+
* @param {any} replacement
|
|
132
|
+
* @param {Function} [condition]
|
|
133
|
+
* @param {Function} [additional]
|
|
134
|
+
* @returns {Function}
|
|
135
|
+
*/
|
|
136
|
+
function replace( newAnno, replacement, condition = () => true, additional = () => true ) {
|
|
137
|
+
return function replaceAnnotationPrefix(csn, artifact, carrier, oldAnno) {
|
|
138
|
+
if (carrier[newAnno] === undefined && condition(csn, artifact, carrier, oldAnno, newAnno)) {
|
|
139
|
+
carrier[newAnno] = replacement || carrier[oldAnno];
|
|
140
|
+
additional(carrier, oldAnno, newAnno);
|
|
141
|
+
delete carrier[oldAnno];
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get the function to replace oldAnno with newAnno on carrier.
|
|
148
|
+
*
|
|
149
|
+
* - If available, use "replacement" as value.
|
|
150
|
+
* - Only do replacement if "condition" returns true
|
|
151
|
+
*
|
|
152
|
+
* @param {string} newAnno
|
|
153
|
+
* @param {any} replacement
|
|
154
|
+
* @param {Function} condition
|
|
155
|
+
* @returns {Function}
|
|
156
|
+
*/
|
|
157
|
+
function replaceIf( newAnno, replacement, condition ) {
|
|
158
|
+
return replace( newAnno, replacement, condition );
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
*
|
|
163
|
+
* @param {CSN.Model} csn
|
|
164
|
+
* @returns {object} Transfomer object for applyTransformations
|
|
165
|
+
*/
|
|
166
|
+
function remapODataAnnotations( csn ) {
|
|
167
|
+
/**
|
|
168
|
+
*
|
|
169
|
+
* @param {CSN.Artifact} artifact
|
|
170
|
+
* @param {CSN.Element} element Element to process
|
|
171
|
+
*/
|
|
172
|
+
function remapAnnotationsOnElement( artifact, element ) {
|
|
173
|
+
if (element.elements && !element.$ignore) // We expect to only be called on flattened CSN - error if we encounter .elements!
|
|
174
|
+
throw new CompilerAssertion(`Expected a flat model. Found element with subelements: ${JSON.stringify(element)}`);
|
|
175
|
+
for (const prop in element) {
|
|
176
|
+
if (directMappings[prop])
|
|
177
|
+
directMappings[prop](csn, artifact, element, prop);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
elements: (parent, prop, elements, path) => {
|
|
183
|
+
const artifact = csn.definitions[path[1]];
|
|
184
|
+
if (artifact?.kind === 'entity') {
|
|
185
|
+
for (const elementName in elements)
|
|
186
|
+
remapAnnotationsOnElement(artifact, elements[elementName]);
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
module.exports = {
|
|
193
|
+
remapODataAnnotations,
|
|
194
|
+
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
const {
|
|
4
|
+
getUtils, isAspect, mergeTransformers, applyTransformations,
|
|
5
|
+
} = require('../../model/csnUtils');
|
|
6
6
|
const transformUtils = require('../transformUtils');
|
|
7
7
|
const flattening = require('../db/flattening');
|
|
8
8
|
const types = require('./types');
|
|
@@ -14,6 +14,7 @@ const associations = require('./associations');
|
|
|
14
14
|
const generateDrafts = require('../draft/db');
|
|
15
15
|
const handleExists = require('../db/transformExists');
|
|
16
16
|
const misc = require('./misc');
|
|
17
|
+
const annotations = require('./annotations');
|
|
17
18
|
const { rewriteCalculatedElementsInViews, processCalculatedElementsInEntities } = require('../db/rewriteCalculatedElements');
|
|
18
19
|
const { cloneFullCsn } = require('../../model/cloneCsn');
|
|
19
20
|
|
|
@@ -30,9 +31,6 @@ const { cloneFullCsn } = require('../../model/cloneCsn');
|
|
|
30
31
|
* @returns {CSN.Model}
|
|
31
32
|
*/
|
|
32
33
|
function effectiveCsn( model, options, messageFunctions ) {
|
|
33
|
-
if (!isBetaEnabled(options, 'effectiveCsn'))
|
|
34
|
-
throw new CompilerAssertion('effective CSN is only supported with beta flag `effectiveCsn`!');
|
|
35
|
-
|
|
36
34
|
const csn = cloneFullCsn(model, options);
|
|
37
35
|
delete csn.vocabularies; // must not be set for effective CSN
|
|
38
36
|
messageFunctions.setModel(csn);
|
|
@@ -80,8 +78,8 @@ function effectiveCsn( model, options, messageFunctions ) {
|
|
|
80
78
|
associations.managedToUnmanaged(csn, options, csnUtils, messageFunctions);
|
|
81
79
|
associations.transformBacklinks(csn, options, csnUtils, messageFunctions);
|
|
82
80
|
generateDrafts(csn, options, '_', messageFunctions);
|
|
83
|
-
misc.attachPersistenceName(csn, options, csnUtils);
|
|
84
|
-
|
|
81
|
+
const transformers = mergeTransformers([ misc.attachPersistenceName(csn, options, csnUtils), options.remapOdataAnnotations ? annotations.remapODataAnnotations(csn) : {}, misc.removeDefinitionsAndProperties(csn, options) ], null);
|
|
82
|
+
applyTransformations(csn, transformers, [], { skipIgnore: false });
|
|
85
83
|
|
|
86
84
|
if (!options.resolveProjections)
|
|
87
85
|
redoProjections.forEach(fn => fn());
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
|
-
|
|
4
|
+
getArtifactDatabaseNameOf, getElementDatabaseNameOf,
|
|
5
5
|
} = require('../../model/csnUtils');
|
|
6
6
|
/**
|
|
7
7
|
* Attach @cds.persistence.name to all artifacts and "things".
|
|
@@ -9,17 +9,34 @@ const {
|
|
|
9
9
|
* @param {CSN.Model} csn
|
|
10
10
|
* @param {CSN.Options} options
|
|
11
11
|
* @param {object} csnUtils
|
|
12
|
+
* @returns {object}
|
|
12
13
|
*/
|
|
13
14
|
function attachPersistenceName( csn, options, csnUtils ) {
|
|
14
15
|
const { addStringAnnotationTo } = csnUtils;
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
/**
|
|
18
|
+
*
|
|
19
|
+
* @param {object} parent
|
|
20
|
+
* @param {string} prop
|
|
21
|
+
* @param {object} dict
|
|
22
|
+
* @param {CSN.Path} path
|
|
23
|
+
*/
|
|
24
|
+
function addToEachMember( parent, prop, dict, path ) {
|
|
25
|
+
const artifact = csn.definitions[path[1]];
|
|
26
|
+
if (artifact?.kind === 'entity') {
|
|
27
|
+
for (const memberName in dict)
|
|
28
|
+
addStringAnnotationTo('@cds.persistence.name', getElementDatabaseNameOf(memberName, options.sqlMapping, options.sqlDialect), dict[memberName]);
|
|
21
29
|
}
|
|
22
|
-
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
kind: (parent, prop, kind, path) => {
|
|
34
|
+
if (kind === 'entity')
|
|
35
|
+
addStringAnnotationTo('@cds.persistence.name', getArtifactDatabaseNameOf(path[1], options.sqlMapping, csn, options.sqlDialect), parent);
|
|
36
|
+
},
|
|
37
|
+
elements: addToEachMember,
|
|
38
|
+
params: addToEachMember,
|
|
39
|
+
};
|
|
23
40
|
}
|
|
24
41
|
|
|
25
42
|
/**
|
|
@@ -42,10 +59,11 @@ function killProp( parent, prop ) {
|
|
|
42
59
|
* - localized
|
|
43
60
|
* @param {CSN.Model} csn
|
|
44
61
|
* @param {CSN.Options} options
|
|
62
|
+
* @returns {object}
|
|
45
63
|
* @todo Callback-like architecture and merge with persistence name?
|
|
46
64
|
*/
|
|
47
65
|
function _removeDefinitionsAndProperties( csn, options ) {
|
|
48
|
-
const
|
|
66
|
+
const transformers = {
|
|
49
67
|
$ignore: (a, b, c, path, parentParent) => {
|
|
50
68
|
const tail = path[path.length - 1];
|
|
51
69
|
delete parentParent[tail];
|
|
@@ -74,13 +92,16 @@ function _removeDefinitionsAndProperties( csn, options ) {
|
|
|
74
92
|
// Set when we remove .key from temporal things, used in localized.js
|
|
75
93
|
$key: killProp,
|
|
76
94
|
includes: killProp,
|
|
77
|
-
localized: killProp,
|
|
78
95
|
enum: killProp,
|
|
79
96
|
keys: killProp,
|
|
80
97
|
excluding: killProp, // * is resolved, so has no effect anymore
|
|
98
|
+
targetAspect: killProp,
|
|
81
99
|
};
|
|
82
100
|
|
|
83
|
-
|
|
101
|
+
if (!options.keepLocalized)
|
|
102
|
+
transformers.localized = killProp;
|
|
103
|
+
|
|
104
|
+
return transformers;
|
|
84
105
|
}
|
|
85
106
|
|
|
86
107
|
|
|
@@ -73,7 +73,7 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
73
73
|
timetrace.start('OData transformation');
|
|
74
74
|
|
|
75
75
|
// copy the model as we don't want to change the input model
|
|
76
|
-
|
|
76
|
+
const csn = cloneFullCsn(inputModel, options);
|
|
77
77
|
messageFunctions.setModel(csn);
|
|
78
78
|
|
|
79
79
|
const { message, error, warning, info, throwWithAnyError } = messageFunctions;
|
|
@@ -184,14 +184,19 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
184
184
|
expandStructsInExpression(csn, { skipArtifact: isExternalServiceMember, drillRef: true });
|
|
185
185
|
|
|
186
186
|
if (!structuredOData) {
|
|
187
|
-
expansion.expandStructureReferences(csn, options, '_',
|
|
187
|
+
expansion.expandStructureReferences(csn, options, '_',
|
|
188
|
+
{ error, info, throwWithAnyError }, csnUtils,
|
|
189
|
+
{ skipArtifact: isExternalServiceMember });
|
|
188
190
|
const resolved = new WeakMap();
|
|
189
191
|
|
|
190
192
|
const { inspectRef, effectiveType } = csnRefs(csn);
|
|
191
|
-
const { adaptRefs, transformer: refFlattener } =
|
|
193
|
+
const { adaptRefs, transformer: refFlattener } =
|
|
194
|
+
flattening.getStructRefFlatteningTransformer(csn, inspectRef, effectiveType, options, resolved, '_');
|
|
192
195
|
|
|
193
|
-
flattening.allInOneFlattening(csn, refFlattener, adaptRefs,
|
|
194
|
-
|
|
196
|
+
flattening.allInOneFlattening(csn, refFlattener, adaptRefs,
|
|
197
|
+
inspectRef, isExternalServiceMember, error, csnUtils, options);
|
|
198
|
+
flattening.flattenAllStructStepsInRefs(csn, refFlattener, adaptRefs,
|
|
199
|
+
inspectRef, effectiveType, csnUtils, error, options,
|
|
195
200
|
{ //skip: ['action', 'aspect', 'event', 'function', 'type'],
|
|
196
201
|
skipArtifact: isExternalServiceMember,
|
|
197
202
|
});
|
|
@@ -208,12 +213,22 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
208
213
|
def[an] = av;
|
|
209
214
|
})
|
|
210
215
|
}
|
|
216
|
+
if(def.actions) {
|
|
217
|
+
Object.values(def.actions).forEach((action) => {
|
|
218
|
+
if(action.$flatAnnotations) {
|
|
219
|
+
Object.entries(action.$flatAnnotations).forEach(([an, av]) => {
|
|
220
|
+
action[an] = av;
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
}
|
|
211
225
|
});
|
|
212
226
|
}
|
|
213
227
|
|
|
214
228
|
// TODO: add the generated foreign keys to the columns when we are in a view
|
|
215
229
|
// see db/views.js::addForeignKeysToColumns
|
|
216
|
-
flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, messageFunctions, '_',
|
|
230
|
+
flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, messageFunctions, '_',
|
|
231
|
+
!structuredOData, csnUtils,{ skipArtifact: isExternalServiceMember });
|
|
217
232
|
|
|
218
233
|
// Allow using managed associations as steps in on-conditions to access their fks
|
|
219
234
|
// To be done after handleManagedAssociationsAndCreateForeignKeys,
|
|
@@ -252,7 +267,8 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
252
267
|
// Annotate artifacts with their DB names if requested.
|
|
253
268
|
// Skip artifacts that have no DB equivalent anyway
|
|
254
269
|
if (options.sqlMapping && !(def.kind in skipPersNameKinds))
|
|
255
|
-
|
|
270
|
+
// hana to allow naming mode "hdbcds"
|
|
271
|
+
def['@cds.persistence.name'] = getArtifactDatabaseNameOf(defName, options.sqlMapping, csn, 'hana');
|
|
256
272
|
|
|
257
273
|
forEachMemberRecursively(def, (member, memberName, propertyName) => {
|
|
258
274
|
// Annotate elements, foreign keys, parameters, etc. with their DB names if requested
|
|
@@ -183,7 +183,7 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
183
183
|
forEachDefinition(csn, [
|
|
184
184
|
// (001) Add a temporal where condition to views where applicable before assoc2join
|
|
185
185
|
// assoc2join eventually rewrites the table aliases
|
|
186
|
-
temporal.getViewDecorator(csn, messageFunctions, csnUtils),
|
|
186
|
+
temporal.getViewDecorator(csn, messageFunctions, csnUtils, options),
|
|
187
187
|
// check unique constraints - further processing is done in rewriteUniqueConstraints
|
|
188
188
|
assertUnique.prepare(csn, options, messageFunctions)
|
|
189
189
|
]);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { makeMessageFunction } = require('../base/messages');
|
|
4
|
-
const { setProp, isDeprecatedEnabled} = require('../base/model');
|
|
4
|
+
const { setProp, isDeprecatedEnabled, isBetaEnabled} = require('../base/model');
|
|
5
5
|
const { forEachKey } = require('../utils/objectUtils');
|
|
6
6
|
const { cleanSymbols } = require('../base/cleanSymbols.js');
|
|
7
7
|
const {
|
|
@@ -747,7 +747,7 @@ function copyLocation(target, source) {
|
|
|
747
747
|
}
|
|
748
748
|
|
|
749
749
|
/**
|
|
750
|
-
* Copy @cds.persistence.
|
|
750
|
+
* Copy @cds.persistence.skip annotations from the source to
|
|
751
751
|
* the target. Ignores existing annotations on the _target_.
|
|
752
752
|
*
|
|
753
753
|
* @param {CSN.Artifact} target
|
|
@@ -755,14 +755,15 @@ function copyLocation(target, source) {
|
|
|
755
755
|
* @param {CSN.Options} options
|
|
756
756
|
*/
|
|
757
757
|
function copyPersistenceAnnotations(target, source, options) {
|
|
758
|
-
const
|
|
758
|
+
const copyExists = !isBetaEnabled(options, 'v5preview') &&
|
|
759
|
+
!isDeprecatedEnabled( options, 'eagerPersistenceForGeneratedEntities' );
|
|
759
760
|
forEachKey(source, anno => {
|
|
760
761
|
// Note:
|
|
761
|
-
// Because `.exists` is copied to the convenience view, it could
|
|
762
|
+
// v3/v4: Because `.exists` is copied to the convenience view, it could
|
|
762
763
|
// lead to some localization views referencing non-existing ones.
|
|
763
764
|
// But that is the contract: User says that it already exists!
|
|
764
|
-
//
|
|
765
|
-
if (anno === annoPersistenceSkip || (
|
|
765
|
+
// v2/>=v5, `.exists` is never copied.
|
|
766
|
+
if (anno === annoPersistenceSkip || (copyExists && anno === '@cds.persistence.exists'))
|
|
766
767
|
target[anno] = source[anno];
|
|
767
768
|
});
|
|
768
769
|
}
|