@sap/cds-compiler 5.7.4 → 5.8.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 +54 -2
- package/bin/cdsse.js +13 -1
- package/doc/CHANGELOG_BETA.md +7 -0
- package/lib/api/options.js +2 -1
- package/lib/api/validate.js +9 -0
- package/lib/base/message-registry.js +55 -20
- package/lib/base/messages.js +5 -2
- package/lib/base/model.js +4 -1
- package/lib/checks/assocOutsideService.js +40 -0
- package/lib/checks/featureFlags.js +4 -1
- package/lib/checks/types.js +7 -4
- package/lib/checks/validator.js +3 -0
- package/lib/compiler/assert-consistency.js +11 -5
- package/lib/compiler/checks.js +79 -17
- package/lib/compiler/define.js +57 -3
- package/lib/compiler/extend.js +1 -2
- package/lib/compiler/generate.js +1 -1
- package/lib/compiler/populate.js +17 -6
- package/lib/compiler/propagator.js +1 -1
- package/lib/compiler/resolve.js +181 -150
- package/lib/compiler/shared.js +276 -22
- package/lib/compiler/tweak-assocs.js +15 -4
- package/lib/compiler/xpr-rewrite.js +76 -50
- package/lib/edm/annotations/edmJson.js +1 -1
- package/lib/edm/annotations/genericTranslation.js +2 -2
- package/lib/edm/csn2edm.js +2 -2
- package/lib/edm/edmPreprocessor.js +15 -9
- package/lib/edm/edmUtils.js +12 -5
- package/lib/gen/CdlGrammar.checksum +1 -0
- package/lib/gen/CdlParser.js +2234 -2233
- package/lib/gen/Dictionary.json +55 -8
- package/lib/json/from-csn.js +37 -17
- package/lib/json/to-csn.js +4 -0
- package/lib/language/genericAntlrParser.js +7 -0
- package/lib/main.d.ts +5 -0
- package/lib/model/cloneCsn.js +1 -0
- package/lib/model/csnRefs.js +1 -0
- package/lib/model/csnUtils.js +0 -5
- package/lib/modelCompare/utils/filter.js +2 -2
- package/lib/optionProcessor.js +2 -0
- package/lib/parsers/AstBuildingParser.js +47 -17
- package/lib/parsers/CdlGrammar.g4 +10 -12
- package/lib/parsers/XprTree.js +206 -0
- package/lib/render/toCdl.js +61 -89
- package/lib/render/toSql.js +59 -29
- package/lib/render/utils/standardDatabaseFunctions.js +252 -15
- package/lib/transform/addTenantFields.js +9 -3
- package/lib/transform/db/assocsToQueries/transformExists.js +3 -0
- package/lib/transform/db/assocsToQueries/utils.js +10 -3
- package/lib/transform/db/expansion.js +3 -1
- package/lib/transform/db/flattening.js +7 -3
- package/lib/transform/db/killAnnotations.js +1 -0
- package/lib/transform/db/processSqlServices.js +70 -17
- package/lib/transform/draft/db.js +8 -3
- package/lib/transform/draft/odata.js +27 -4
- package/lib/transform/effective/main.js +37 -10
- package/lib/transform/effective/misc.js +4 -9
- package/lib/transform/effective/service.js +34 -0
- package/lib/transform/effective/types.js +28 -17
- package/lib/transform/forOdata.js +36 -10
- package/lib/transform/forRelationalDB.js +30 -18
- package/lib/transform/odata/adaptAnnotationRefs.js +37 -21
- package/lib/transform/odata/createForeignKeys.js +120 -116
- package/lib/transform/odata/flattening.js +10 -8
- package/lib/transform/transformUtils.js +58 -25
- package/lib/transform/translateAssocsToJoins.js +10 -6
- package/lib/transform/universalCsn/coreComputed.js +5 -1
- package/package.json +1 -1
- package/share/messages/message-explanations.json +1 -0
- package/share/messages/rewrite-not-supported.md +5 -0
- package/share/messages/rewrite-undefined-key.md +94 -0
|
@@ -7,6 +7,7 @@ const { forEach } = require('../../utils/objectUtils');
|
|
|
7
7
|
const { isArtifactInSomeService, getServiceOfArtifact } = require('../odata/utils');
|
|
8
8
|
const { getTransformers } = require('../transformUtils');
|
|
9
9
|
const { makeMessageFunction } = require('../../base/messages');
|
|
10
|
+
const { isBetaEnabled } = require('../../base/model');
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* - Generate artificial draft fields if requested
|
|
@@ -35,10 +36,11 @@ function generateDrafts( csn, options, services, messageFunctions ) {
|
|
|
35
36
|
|
|
36
37
|
const { error, info } = messageFunctions;
|
|
37
38
|
const {
|
|
38
|
-
createAndAddDraftAdminDataProjection,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
createAndAddDraftAdminDataProjection, isValidDraftAdminDataMessagesType,
|
|
40
|
+
createScalarElement, createAssociationElement,
|
|
41
|
+
createAssociationPathComparison, addElement,
|
|
42
|
+
createAction, assignAction,
|
|
43
|
+
resetAnnotation, setAnnotation,
|
|
42
44
|
csnUtils,
|
|
43
45
|
} = getTransformers(csn, options, messageFunctions);
|
|
44
46
|
const {
|
|
@@ -56,6 +58,16 @@ function generateDrafts( csn, options, services, messageFunctions ) {
|
|
|
56
58
|
// @ts-ignore
|
|
57
59
|
const isExternalServiceMember = (_art, name) => externalServices.includes(getServiceName(name));
|
|
58
60
|
const filterDict = Object.create(null);
|
|
61
|
+
|
|
62
|
+
// validate the 'DRAFT.DraftAdministrativeData_DraftMessage' type if already present in the model
|
|
63
|
+
if (isBetaEnabled(options, 'draftMessages')) {
|
|
64
|
+
const draftAdminDataMessagesType = csn.definitions['DRAFT.DraftAdministrativeData_DraftMessage'];
|
|
65
|
+
if (draftAdminDataMessagesType && !isValidDraftAdminDataMessagesType(draftAdminDataMessagesType)) {
|
|
66
|
+
error(null, [ 'definitions', 'DRAFT.DraftAdministrativeData_DraftMessage' ], { name: 'DRAFT.DraftAdministrativeData_DraftMessage' },
|
|
67
|
+
'Generated type $(NAME) conflicts with existing artifact');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
59
71
|
forEachDefinition(csn, (def, defName) => {
|
|
60
72
|
// Generate artificial draft fields for entities/views if requested, ignore if not part of a service
|
|
61
73
|
if (def.kind === 'entity' && def['@odata.draft.enabled'] && isArtifactInSomeService(defName, services))
|
|
@@ -167,6 +179,17 @@ function generateDrafts( csn, options, services, messageFunctions ) {
|
|
|
167
179
|
// ... on SiblingEntity.IsActiveEntity != IsActiveEntity ...
|
|
168
180
|
siblingEntity.SiblingEntity.on = createAssociationPathComparison('SiblingEntity', 'IsActiveEntity', '!=', 'IsActiveEntity');
|
|
169
181
|
|
|
182
|
+
if (isBetaEnabled(options, 'draftMessages')) {
|
|
183
|
+
const draftMessages = { DraftMessages: { '@Core.Computed': true, virtual: true, items: { type: 'DRAFT.DraftAdministrativeData_DraftMessage' } } };
|
|
184
|
+
addElement(draftMessages, artifact, artifactName);
|
|
185
|
+
|
|
186
|
+
if (!artifact['@Common.SideEffects#alwaysFetchMessages'] && artifact['@Common.SideEffects#alwaysFetchMessages'] !== null) {
|
|
187
|
+
setAnnotation(artifact, '@Common.SideEffects#alwaysFetchMessages.SourceEntities', ['']);
|
|
188
|
+
setAnnotation(artifact, '@Common.SideEffects#alwaysFetchMessages.TargetProperties', ['DraftMessages'] );
|
|
189
|
+
}
|
|
190
|
+
setAnnotation(artifact, '@Common.Messages', { '=': 'DraftMessages', ref: ['DraftMessages'] });
|
|
191
|
+
}
|
|
192
|
+
|
|
170
193
|
// Iterate elements
|
|
171
194
|
// TODO: Iterative vs recursive? What is more likely: Super deep nesting or cycles via malicious CSN?
|
|
172
195
|
if (artifact.elements) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
|
-
getUtils,
|
|
4
|
+
getUtils, mergeTransformers, applyTransformations,
|
|
5
5
|
} = require('../../model/csnUtils');
|
|
6
6
|
const transformUtils = require('../transformUtils');
|
|
7
7
|
const effectiveFlattening = require('./flattening');
|
|
@@ -18,6 +18,7 @@ const annotations = require('./annotations');
|
|
|
18
18
|
const { rewriteCalculatedElementsInViews, processCalculatedElementsInEntities } = require('../db/rewriteCalculatedElements');
|
|
19
19
|
const { cloneFullCsn } = require('../../model/cloneCsn');
|
|
20
20
|
const { featureFlags } = require('../featureFlags');
|
|
21
|
+
const getServiceFilterFunction = require('./service');
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* This is just a PoC for now!
|
|
@@ -36,14 +37,15 @@ function effectiveCsn( model, options, messageFunctions ) {
|
|
|
36
37
|
delete csn.vocabularies; // must not be set for effective CSN
|
|
37
38
|
messageFunctions.setModel(csn);
|
|
38
39
|
|
|
39
|
-
const
|
|
40
|
+
const transformerUtils = transformUtils.getTransformers(csn, options, messageFunctions, '_');
|
|
41
|
+
const { expandStructsInExpression } = transformerUtils;
|
|
40
42
|
const redoProjections = queries.projectionToSELECTAndAddColumns(csn);
|
|
41
43
|
|
|
42
44
|
let csnUtils = getUtils(csn, 'init-all');
|
|
43
45
|
|
|
44
46
|
// Run validations on CSN - each validator function has access to the message functions and the inspect ref via this
|
|
45
47
|
const cleanup = validate.forRelationalDB(csn, {
|
|
46
|
-
...messageFunctions, csnUtils, ...csnUtils, csn, options,
|
|
48
|
+
...messageFunctions, csnUtils, ...csnUtils, csn, options,
|
|
47
49
|
});
|
|
48
50
|
|
|
49
51
|
if (csn.meta?.[featureFlags]?.$calculatedElements)
|
|
@@ -63,7 +65,7 @@ function effectiveCsn( model, options, messageFunctions ) {
|
|
|
63
65
|
// Expand a structured thing in: keys, columns, order by, group by
|
|
64
66
|
expansion.expandStructureReferences(csn, options, '_', messageFunctions, csnUtils);
|
|
65
67
|
|
|
66
|
-
const resolveTypesInActionsAfterFlattening = types.resolve(csn, csnUtils, options);
|
|
68
|
+
const resolveTypesInActionsAfterFlattening = types.resolve(csn, csnUtils, transformerUtils, options);
|
|
67
69
|
|
|
68
70
|
// Remove properties attached by validator - they do not "grow" as the model grows.
|
|
69
71
|
cleanup();
|
|
@@ -72,30 +74,55 @@ function effectiveCsn( model, options, messageFunctions ) {
|
|
|
72
74
|
effectiveFlattening.flattenRefs(csn, options, csnUtils, messageFunctions);
|
|
73
75
|
flattening.flattenElements(csn, options, messageFunctions, '_', { skipDict: { actions: true } });
|
|
74
76
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
// ensure getElement works on flattened struct_assoc columns
|
|
77
|
+
// ensure getElement works on flattened struct_assoc columns and getFinalTypeInfo refreshes the cache
|
|
78
78
|
csnUtils = getUtils(csn, 'init-all');
|
|
79
79
|
|
|
80
|
+
resolveTypesInActionsAfterFlattening(csnUtils);
|
|
81
|
+
|
|
80
82
|
processCalculatedElementsInEntities(csn, options);
|
|
81
83
|
associations.managedToUnmanaged(csn, options, csnUtils, messageFunctions);
|
|
82
84
|
associations.transformBacklinks(csn, options, csnUtils, messageFunctions);
|
|
83
85
|
const transformers = mergeTransformers([
|
|
84
|
-
options.addCdsPersistenceName ? misc.attachPersistenceName(csn, options, csnUtils) : {},
|
|
85
86
|
options.remapOdataAnnotations ? annotations.remapODataAnnotations(csn) : {},
|
|
86
87
|
misc.removeDefinitionsAndProperties(csn, options),
|
|
87
88
|
options.deriveAnalyticalAnnotations ? annotations.sealAnnoMagic(csn) : {},
|
|
88
89
|
], null);
|
|
89
90
|
|
|
90
|
-
|
|
91
|
+
if (options.addCdsPersistenceName) {
|
|
92
|
+
applyTransformations(csn, misc.attachPersistenceName(csn, options, csnUtils), [], {
|
|
93
|
+
skipIgnore: false, skipArtifact: artifact => artifact.kind !== 'entity', skipDict: { actions: true }, skipStandard: { items: true },
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const artifactTransformers = [];
|
|
98
|
+
|
|
99
|
+
const collector = {
|
|
100
|
+
service: null,
|
|
101
|
+
containedArtifacts: Object.create(null),
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
if (options.effectiveServiceName)
|
|
105
|
+
artifactTransformers.push(getServiceFilterFunction(options.effectiveServiceName, collector));
|
|
106
|
+
|
|
107
|
+
applyTransformations(csn, transformers, artifactTransformers, { skipIgnore: false, processAnnotations: true });
|
|
91
108
|
|
|
92
109
|
if (!options.resolveProjections)
|
|
93
110
|
redoProjections.forEach(fn => fn());
|
|
94
111
|
|
|
95
|
-
|
|
96
112
|
// Remove unapplied extensions/annotations
|
|
97
113
|
delete csn.extensions;
|
|
98
114
|
|
|
115
|
+
if (options.effectiveServiceName) {
|
|
116
|
+
if (collector.service) {
|
|
117
|
+
csn.definitions = collector.containedArtifacts;
|
|
118
|
+
csn.definitions[options.effectiveServiceName] = collector.service;
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
messageFunctions.warning(null, null, { name: options.effectiveServiceName, option: 'effectiveServiceName' }, 'Could not find a service matching requested effective service $(NAME) (option $(OPTION))');
|
|
122
|
+
csn.definitions = Object.create(null);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
99
126
|
messageFunctions.throwWithError();
|
|
100
127
|
|
|
101
128
|
return csn;
|
|
@@ -19,20 +19,15 @@ function attachPersistenceName( csn, options, csnUtils ) {
|
|
|
19
19
|
* @param {object} parent
|
|
20
20
|
* @param {string} prop
|
|
21
21
|
* @param {object} dict
|
|
22
|
-
* @param {CSN.Path} path
|
|
23
22
|
*/
|
|
24
|
-
function addToEachMember( parent, prop, dict
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
for (const memberName in dict)
|
|
28
|
-
addStringAnnotationTo('@cds.persistence.name', getElementDatabaseNameOf(memberName, options.sqlMapping, options.sqlDialect), dict[memberName]);
|
|
29
|
-
}
|
|
23
|
+
function addToEachMember( parent, prop, dict ) {
|
|
24
|
+
for (const memberName in dict)
|
|
25
|
+
addStringAnnotationTo('@cds.persistence.name', getElementDatabaseNameOf(memberName, options.sqlMapping, options.sqlDialect), dict[memberName]);
|
|
30
26
|
}
|
|
31
27
|
|
|
32
28
|
return {
|
|
33
29
|
kind: (parent, prop, kind, path) => {
|
|
34
|
-
|
|
35
|
-
addStringAnnotationTo('@cds.persistence.name', getArtifactDatabaseNameOf(path[1], options.sqlMapping, csn, options.sqlDialect), parent);
|
|
30
|
+
addStringAnnotationTo('@cds.persistence.name', getArtifactDatabaseNameOf(path[1], options.sqlMapping, csn, options.sqlDialect), parent);
|
|
36
31
|
},
|
|
37
32
|
elements: addToEachMember,
|
|
38
33
|
params: addToEachMember,
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { forEach } = require('../../utils/objectUtils');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Creates a filter function for a specific service that processes and strips artifacts.
|
|
7
|
+
*
|
|
8
|
+
* @param {string} serviceName - The name of the service to filter for.
|
|
9
|
+
* @param {Object} collector - An object to collect the service and its contained artifacts.
|
|
10
|
+
* @param {Object} collector.service - The main service artifact.
|
|
11
|
+
* @param {Object} collector.containedArtifacts - The contained artifacts of the service.
|
|
12
|
+
* @returns {Function} A filter function that processes definitions and artifacts.
|
|
13
|
+
*/
|
|
14
|
+
function getServiceFilterFunction(serviceName, collector) {
|
|
15
|
+
return function filterAndStripForService(definitions, artifactName, artifact) {
|
|
16
|
+
if (artifactName === serviceName && artifact.kind === 'service') {
|
|
17
|
+
collector.service = artifact;
|
|
18
|
+
}
|
|
19
|
+
else if (definitions[serviceName]?.kind === 'service' && artifactName.startsWith(`${serviceName }.`)) {
|
|
20
|
+
collector.containedArtifacts[artifactName] = artifact;
|
|
21
|
+
delete artifact.query;
|
|
22
|
+
delete artifact.projection;
|
|
23
|
+
if (artifact.elements) {
|
|
24
|
+
forEach(artifact.elements, (elementName, element) => {
|
|
25
|
+
if (element.target && !element.target.startsWith(`${serviceName }.`))
|
|
26
|
+
delete artifact.elements[elementName];
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
module.exports = getServiceFilterFunction;
|
|
@@ -17,32 +17,43 @@ const { cloneCsnDict, cloneCsnNonDict } = require('../../model/cloneCsn');
|
|
|
17
17
|
* @todo What about annotations on the type?
|
|
18
18
|
* @param {CSN.Model} csn will be transformed
|
|
19
19
|
* @param {object} csnUtils
|
|
20
|
+
* @param {object} transformers
|
|
20
21
|
* @param {CSN.Options} options
|
|
21
22
|
* @returns {Function} Callback to resolve types in action returns later - as for them, $self would lead to unresolvable constructs at this point
|
|
22
23
|
* so we can call this callback after flattening is done - then we can safely resolve their types.
|
|
23
24
|
*/
|
|
24
|
-
function resolveTypes( csn, csnUtils, options ) {
|
|
25
|
-
const {
|
|
25
|
+
function resolveTypes( csn, csnUtils, transformers, options ) {
|
|
26
|
+
const { flattenStructStepsInRef } = transformers;
|
|
26
27
|
const later = [];
|
|
27
28
|
applyTransformations(csn, {
|
|
28
29
|
type: (parent) => {
|
|
29
|
-
resolveType(parent);
|
|
30
|
+
resolveType(csnUtils, parent);
|
|
30
31
|
},
|
|
31
32
|
}, [ (definitions, artifactName, artifact) => {
|
|
32
33
|
// In a non-flat model, replacing types with some $self inside causes issues for actions (bound or unbound)
|
|
33
34
|
// we remember them and replace them after flattening.
|
|
34
|
-
if (artifact.kind === 'action' || artifact.kind === 'function')
|
|
35
|
-
later.push({ [artifactName]: artifact });
|
|
35
|
+
if (artifact.kind === 'action' || artifact.kind === 'function') // TODO: We still process them, this does not abort that? I am pretty sure at least...
|
|
36
|
+
later.push([ { [artifactName]: artifact }, [ 'definitions' ] ]);
|
|
36
37
|
else if (artifact.actions)
|
|
37
|
-
later.push(artifact.actions);
|
|
38
|
-
} ], { skipStandard: { returns: true }, processAnnotations: true });
|
|
38
|
+
later.push([ artifact.actions, [ 'definitions', artifactName, 'actions' ] ]);
|
|
39
|
+
} ], { skipStandard: { returns: true }, processAnnotations: true, skipDict: { actions: true } });
|
|
40
|
+
|
|
41
|
+
// Type refs like E:struct.sub can not be resolved later, as struct will be flattened. So we rewrite it to E:struct_sub here.
|
|
42
|
+
later.forEach(([ action, actionPath ]) => {
|
|
43
|
+
applyTransformationsOnDictionary(action, {
|
|
44
|
+
type: (parent, prop, type, path) => {
|
|
45
|
+
if (type.ref)
|
|
46
|
+
type.ref = flattenStructStepsInRef(type.ref, path.concat('type'))[0];
|
|
47
|
+
},
|
|
48
|
+
}, {}, actionPath);
|
|
49
|
+
});
|
|
39
50
|
|
|
40
51
|
// TODO: Directly push the .returns into the later so we have a more minimal looping
|
|
41
|
-
return function resolveTypesInActions() {
|
|
42
|
-
later.forEach((
|
|
43
|
-
applyTransformationsOnDictionary(
|
|
52
|
+
return function resolveTypesInActions(refreshedCsnUtils) {
|
|
53
|
+
later.forEach(([ action ]) => {
|
|
54
|
+
applyTransformationsOnDictionary(action, {
|
|
44
55
|
type: (parent) => {
|
|
45
|
-
resolveType(parent);
|
|
56
|
+
resolveType(refreshedCsnUtils, parent);
|
|
46
57
|
},
|
|
47
58
|
});
|
|
48
59
|
});
|
|
@@ -59,9 +70,9 @@ function resolveTypes( csn, csnUtils, options ) {
|
|
|
59
70
|
*
|
|
60
71
|
* @param {object} parent Object with a .type property
|
|
61
72
|
*/
|
|
62
|
-
function resolveType( parent ) {
|
|
73
|
+
function resolveType( csnUtils, parent ) {
|
|
63
74
|
// TODO: I assume there can be cases with a type ref but still having .elements already? Subelement anno?
|
|
64
|
-
const final = getFinalTypeInfo(parent.type);
|
|
75
|
+
const final = csnUtils.getFinalTypeInfo(parent.type);
|
|
65
76
|
if (final?.elements) {
|
|
66
77
|
// We do full clones so users don't get unexpected linkage later
|
|
67
78
|
if (!parent.elements)
|
|
@@ -88,12 +99,12 @@ function resolveTypes( csn, csnUtils, options ) {
|
|
|
88
99
|
const obj = stack.pop();
|
|
89
100
|
if (obj.elements) {
|
|
90
101
|
applyTransformationsOnDictionary(obj.elements, {
|
|
91
|
-
type: getTypeTransformer(stack),
|
|
102
|
+
type: getTypeTransformer(csnUtils, stack),
|
|
92
103
|
}, { skipDict: { actions: true } });
|
|
93
104
|
}
|
|
94
105
|
else if (obj.items) {
|
|
95
106
|
applyTransformationsOnNonDictionary(obj, 'items', {
|
|
96
|
-
type: getTypeTransformer(stack),
|
|
107
|
+
type: getTypeTransformer(csnUtils, stack),
|
|
97
108
|
}, { skipDict: { actions: true } });
|
|
98
109
|
}
|
|
99
110
|
}
|
|
@@ -104,9 +115,9 @@ function resolveTypes( csn, csnUtils, options ) {
|
|
|
104
115
|
* @param {object[]} stack
|
|
105
116
|
* @returns {Function}
|
|
106
117
|
*/
|
|
107
|
-
function getTypeTransformer( stack ) {
|
|
118
|
+
function getTypeTransformer( csnUtils, stack ) {
|
|
108
119
|
return function typeTransformer( _parent, _prop, _type ) {
|
|
109
|
-
const finalSub = getFinalTypeInfo(_type);
|
|
120
|
+
const finalSub = csnUtils.getFinalTypeInfo(_type);
|
|
110
121
|
|
|
111
122
|
if (finalSub?.elements) {
|
|
112
123
|
// We do full clones so users don't get unexpected linkage later
|
|
@@ -7,7 +7,6 @@ const { forEachDefinition,
|
|
|
7
7
|
applyTransformationsOnNonDictionary,
|
|
8
8
|
getArtifactDatabaseNameOf,
|
|
9
9
|
getElementDatabaseNameOf,
|
|
10
|
-
isAspect,
|
|
11
10
|
getServiceNames,
|
|
12
11
|
forEachGeneric,
|
|
13
12
|
cardinality2str,
|
|
@@ -147,7 +146,7 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
147
146
|
options.enrichAnnotations = true;
|
|
148
147
|
const cleanup = validate.forOdata(csn, {
|
|
149
148
|
message, error, warning, info, inspectRef, effectiveType, getFinalTypeInfo, artifactRef,
|
|
150
|
-
options, csnUtils, services,
|
|
149
|
+
options, csnUtils, services, isExternalServiceMember, recurseElements,
|
|
151
150
|
checkMultipleAssignments, csn,
|
|
152
151
|
});
|
|
153
152
|
|
|
@@ -208,7 +207,7 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
208
207
|
skipArtifact: isExternalServiceMember,
|
|
209
208
|
});
|
|
210
209
|
flattening.replaceManagedAssocsAsKeys(allMgdAssocDefs, csnUtils);
|
|
211
|
-
|
|
210
|
+
|
|
212
211
|
// replace structured with flat dictionaries that contain
|
|
213
212
|
// rewritten path expressions
|
|
214
213
|
forEachDefinition(csn, (def) => {
|
|
@@ -218,7 +217,7 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
218
217
|
})
|
|
219
218
|
if(def.$flatAnnotations) {
|
|
220
219
|
Object.entries(def.$flatAnnotations).forEach(([an, av]) => {
|
|
221
|
-
def[an] = av;
|
|
220
|
+
def[an] = av;
|
|
222
221
|
})
|
|
223
222
|
}
|
|
224
223
|
if(def.actions) {
|
|
@@ -425,15 +424,42 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
425
424
|
if (node['@mandatory'] && !Object.entries(node).some(([k,v]) => k === '@Common.FieldControl' || k.startsWith('@Common.FieldControl.') && v != null)) {
|
|
426
425
|
setAnnotation(node, '@Common.FieldControl', { '#': 'Mandatory' });
|
|
427
426
|
}
|
|
428
|
-
if (node['@assert.range'] != null
|
|
429
|
-
|
|
430
|
-
node['@assert.range'].length === 2)) {
|
|
431
|
-
setAnnotation(node, '@Validation.Minimum', node['@assert.range'][0]);
|
|
432
|
-
setAnnotation(node, '@Validation.Maximum', node['@assert.range'][1]);
|
|
433
|
-
}
|
|
427
|
+
if (node['@assert.range'] != null)
|
|
428
|
+
setAssertRangeAnnotation(node);
|
|
434
429
|
}
|
|
435
430
|
}
|
|
436
431
|
|
|
432
|
+
|
|
433
|
+
function setAssertRangeAnnotation(node) {
|
|
434
|
+
const range = node['@assert.range'];
|
|
435
|
+
if (!Array.isArray(range) || range.length !== 2)
|
|
436
|
+
return; // TODO: Warning for wrong format?
|
|
437
|
+
|
|
438
|
+
const min = range[0];
|
|
439
|
+
const max = range[1];
|
|
440
|
+
const minVal = min?.val ?? min;
|
|
441
|
+
const maxVal = max?.val ?? max;
|
|
442
|
+
|
|
443
|
+
// CAP Node 8.5 introduced "exclusive" ranges using the annotation expression
|
|
444
|
+
// syntax. Hence, the compiler uses the same. It also introduced "infinity"
|
|
445
|
+
// via `@assert.range: [ _, _ ]`.
|
|
446
|
+
// For `_`, minVal is an object and this function returns false, which is ok,
|
|
447
|
+
// since we don't render the annotation for "infinite" values.
|
|
448
|
+
const shouldSet = (val) => (typeof val !== 'object' && val !== undefined && val !== null);
|
|
449
|
+
|
|
450
|
+
if (shouldSet(minVal)) {
|
|
451
|
+
setAnnotation(node, '@Validation.Minimum', minVal);
|
|
452
|
+
if (min['='] !== undefined)
|
|
453
|
+
setAnnotation(node, '@Validation.Minimum.@Validation.Exclusive', true);
|
|
454
|
+
}
|
|
455
|
+
if (shouldSet(maxVal)) {
|
|
456
|
+
setAnnotation(node, '@Validation.Maximum', maxVal);
|
|
457
|
+
if (max['='] !== undefined)
|
|
458
|
+
setAnnotation(node, '@Validation.Maximum.@Validation.Exclusive', true);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
}
|
|
462
|
+
|
|
437
463
|
// If an association was modelled as not null, like so:
|
|
438
464
|
// <associationName>: Association to <target> not null;
|
|
439
465
|
// a cardinality property is set to the association member
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const { setProp, isBetaEnabled } = require('../base/model');
|
|
4
4
|
const { forEachMemberRecursively, forAllQueries, applyTransformationsOnNonDictionary,
|
|
5
5
|
getArtifactDatabaseNameOf, getElementDatabaseNameOf, applyTransformations,
|
|
6
|
-
|
|
6
|
+
walkCsnPath, isPersistedOnDatabase
|
|
7
7
|
} = require('../model/csnUtils');
|
|
8
8
|
const { isBuiltinType } = require('../base/builtins');
|
|
9
9
|
const transformUtils = require('./transformUtils');
|
|
@@ -34,7 +34,7 @@ const backlinks = require('./db/backlinks');
|
|
|
34
34
|
const { getDefaultTypeLengths } = require('../render/utils/common');
|
|
35
35
|
const { featureFlags } = require('./featureFlags');
|
|
36
36
|
const { cloneCsnNonDict, cloneFullCsn } = require('../model/cloneCsn');
|
|
37
|
-
const { processSqlServices } = require('./db/processSqlServices');
|
|
37
|
+
const { processSqlServices, createServiceDummy } = require('./db/processSqlServices');
|
|
38
38
|
|
|
39
39
|
// By default: Do not process non-entities/views
|
|
40
40
|
function forEachDefinition(csn, cb) {
|
|
@@ -157,7 +157,7 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
157
157
|
timetrace.start('Validate');
|
|
158
158
|
// Run validations on CSN - each validator function has access to the message functions and the inspect ref via this
|
|
159
159
|
validate.forRelationalDB(csn, {
|
|
160
|
-
...messageFunctions, csnUtils, ...csnUtils, csn, options,
|
|
160
|
+
...messageFunctions, csnUtils, ...csnUtils, csn, options,
|
|
161
161
|
});
|
|
162
162
|
timetrace.stop('Validate');
|
|
163
163
|
|
|
@@ -354,12 +354,18 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
354
354
|
// some errors can't be handled in the subsequent processing steps for e.g. HDBCDS
|
|
355
355
|
messageFunctions.throwWithError();
|
|
356
356
|
|
|
357
|
+
// TODO: Might have to do this earlier if we want special rendering for projections?
|
|
358
|
+
const findAndMarkSqlServiceArtifacts = options.sqlDialect === 'hana' && options.src === 'hdi' && (csn.meta?.[featureFlags]?.$sqlService || csn.meta?.[featureFlags]?.$dummyService) ? processSqlServices(csn, options): () => {}
|
|
359
|
+
|
|
357
360
|
// Apply view-specific transformations
|
|
358
361
|
// (160) Projections now finally become views
|
|
359
362
|
// Replace managed association in group/order by with foreign keys
|
|
360
363
|
const transformEntityOrViewPass2 = getViewTransformer(csn, options, messageFunctions);
|
|
361
|
-
forEachDefinition(csn,
|
|
362
|
-
|
|
364
|
+
forEachDefinition(csn, [(artifact, artifactName) => {
|
|
365
|
+
findAndMarkSqlServiceArtifacts(artifact, artifactName);
|
|
366
|
+
if(artifact.$dummyService)
|
|
367
|
+
createServiceDummy(artifact, artifactName, csn, messageFunctions);
|
|
368
|
+
}, transformViews]);
|
|
363
369
|
|
|
364
370
|
if(!doA2J) {
|
|
365
371
|
forEachDefinition(csn, [
|
|
@@ -381,9 +387,6 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
381
387
|
]);
|
|
382
388
|
}
|
|
383
389
|
|
|
384
|
-
// TODO: Might have to do this earlier if we want special rendering for projections?
|
|
385
|
-
const findAndMarkSqlServiceArtifacts = options.sqlDialect === 'hana' && options.src === 'hdi' && csn.meta?.[featureFlags]?.$sqlService ? processSqlServices(csn): () => {}
|
|
386
|
-
|
|
387
390
|
// TODO: Could we maybe merge this with the final applyTransformations?
|
|
388
391
|
applyTransformations(csn, {
|
|
389
392
|
type: (parent, prop, type, path) => {
|
|
@@ -409,8 +412,6 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
409
412
|
// Attach @cds.persistence.name to artifacts
|
|
410
413
|
if (!artifact.$ignore && artifact.kind !== 'service' && artifact.kind !== 'context')
|
|
411
414
|
csnUtils.addStringAnnotationTo('@cds.persistence.name', getArtifactDatabaseNameOf(artifactName, options.sqlMapping, csn, options.sqlDialect), artifact);
|
|
412
|
-
|
|
413
|
-
findAndMarkSqlServiceArtifacts(artifact, artifactName);
|
|
414
415
|
}], { allowArtifact: artifact => artifact.kind === 'entity'});
|
|
415
416
|
|
|
416
417
|
throwWithAnyError();
|
|
@@ -636,9 +637,14 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
636
637
|
function handleAssocToJoins() {
|
|
637
638
|
// the augmentor isn't able to deal with technical configurations and since assoc2join can ignore it we
|
|
638
639
|
// simply make it invisible and copy it over to the result csn
|
|
639
|
-
forEachDefinition(csn,
|
|
640
|
-
|
|
641
|
-
|
|
640
|
+
forEachDefinition(csn, (art) => {
|
|
641
|
+
if (art.technicalConfig)
|
|
642
|
+
setProp(art, 'technicalConfig', art.technicalConfig);
|
|
643
|
+
if (art.kind === 'type' && art.projection) {
|
|
644
|
+
// Missing 'elements' already reported by csnRefs.
|
|
645
|
+
delete art.projection;
|
|
646
|
+
}
|
|
647
|
+
});
|
|
642
648
|
|
|
643
649
|
const newCsn = translateAssocsToJoinsCSN(csn, options);
|
|
644
650
|
|
|
@@ -712,9 +718,15 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
712
718
|
|
|
713
719
|
// If 'elem' has a default that is an enum constant, replace that by its value. Complain
|
|
714
720
|
// if not found or not an enum type,
|
|
721
|
+
// usually done by the Core Compiler, but backend might be called directly
|
|
715
722
|
function replaceEnumSymbolsByValues(elem, path) {
|
|
716
723
|
// (190 a) Replace enum symbols by their value (if found)
|
|
717
|
-
if (elem.default
|
|
724
|
+
if (!elem.default?.['#'])
|
|
725
|
+
return;
|
|
726
|
+
if (elem.default.val !== undefined) {
|
|
727
|
+
delete elem.default['#'];
|
|
728
|
+
}
|
|
729
|
+
else {
|
|
718
730
|
let Enum = elem.enum;
|
|
719
731
|
if (!Enum && !isBuiltinType(elem.type)) {
|
|
720
732
|
const typeDef = csnUtils.getCsnDef(elem.type);
|
|
@@ -755,10 +767,10 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
755
767
|
/**
|
|
756
768
|
* Check that required actual parameters on 'node.type' are set, that their values are in the correct range etc.
|
|
757
769
|
|
|
758
|
-
* @param {*} node
|
|
759
|
-
* @param {*} nodeName
|
|
760
|
-
* @param {*} model
|
|
761
|
-
* @param {*} path
|
|
770
|
+
* @param {*} node
|
|
771
|
+
* @param {*} nodeName
|
|
772
|
+
* @param {*} model
|
|
773
|
+
* @param {*} path
|
|
762
774
|
*/
|
|
763
775
|
function checkTypeParameters(node, artifact, path) {
|
|
764
776
|
if (node.type && !node.virtual) {
|
|
@@ -8,21 +8,31 @@ const { transformAnnotationExpression, implicitAs, } = require('../../model/csnU
|
|
|
8
8
|
* key declared in the scope, we replace it with referencing the foreign key itself. If a reference is a $self reference,
|
|
9
9
|
* we do nothing and if a ref points to a structure/managed association, an error is thrown
|
|
10
10
|
*
|
|
11
|
-
* @param {Array[]} generatedForeignKeys
|
|
11
|
+
* @param {Array[object]||Array[]} generatedForeignKeys
|
|
12
12
|
* @param {object} csnUtils
|
|
13
13
|
* @param {object} messageFunctions
|
|
14
|
-
* @param {CSN.Path} elementPath
|
|
15
14
|
*/
|
|
16
15
|
function adaptAnnotationsRefs(generatedForeignKeys, csnUtils, { error }, elementPath) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
16
|
+
if(Array.isArray(generatedForeignKeys && generatedForeignKeys[0])) {
|
|
17
|
+
// ensure we are always called with an array of objects. TODO: Cleanup fk creation in for.effective to create array of objects
|
|
18
|
+
adaptAnnotationsRefs(remapToArrayOfObjects(generatedForeignKeys), csnUtils, { error }, elementPath);
|
|
19
|
+
} else {
|
|
20
|
+
const reportedErrorsForAnnoPath = {};
|
|
21
|
+
generatedForeignKeys.forEach((gfk, index) => {
|
|
22
|
+
Object.entries(gfk.foreignKey).forEach(([key, value]) => {
|
|
23
|
+
if (key[0] !== '@') return;
|
|
24
|
+
|
|
25
|
+
transformAnnotationExpression(gfk.foreignKey, key, {
|
|
26
|
+
ref: (_parent, _prop, ref, path, _p, _ppn, ctx) => {
|
|
27
|
+
// if the reference is a $self reference, we do nothing,
|
|
28
|
+
// as this is the way to tell that we do not reference the foreign key
|
|
29
|
+
if (ref[0] === '$self') return;
|
|
30
|
+
// if annotation was not propagated from the keys array during foreign keys creation,
|
|
31
|
+
// means that it is not a candidate for foreign key substitution
|
|
32
|
+
if (gfk.keyAnnotations !== null && !gfk.keyAnnotations.includes(key)) return;
|
|
33
|
+
|
|
34
|
+
const art = gfk.originalKey._art ||
|
|
35
|
+
csnUtils.inspectRef(elementPath ? path : getOriginatingKeyPath(gfk, path)).art; // OData uses getOriginatingKeyPath - as it relies on $path
|
|
26
36
|
if (csnUtils.isManagedAssociation(art)) {
|
|
27
37
|
if (!reportedErrorsForAnnoPath[path]) {
|
|
28
38
|
error('odata-anno-xpr-ref', path, { elemref: { ref }, anno: key, '#': 'fk_substitution' });
|
|
@@ -31,15 +41,15 @@ function adaptAnnotationsRefs(generatedForeignKeys, csnUtils, { error }, element
|
|
|
31
41
|
} else {
|
|
32
42
|
const gfkForRef = findGeneratedForeignKeyForKeyRef(generatedForeignKeys, ref);
|
|
33
43
|
if (gfkForRef.length === 1) {
|
|
34
|
-
ref[0] = gfkForRef[0]
|
|
35
|
-
|
|
44
|
+
ref[0] = gfkForRef[0].prefix;
|
|
45
|
+
|
|
36
46
|
if (ctx?.annoExpr?.['=']) {
|
|
37
47
|
ctx.annoExpr['='] = true;
|
|
38
48
|
}
|
|
39
49
|
} else {
|
|
40
50
|
// check if the annotation reference points to a structure that has been expanded,
|
|
41
51
|
// if so -> report an error
|
|
42
|
-
const foundInOriginalRef = findOriginalRef(generatedForeignKeys.filter(gfk => gfk
|
|
52
|
+
const foundInOriginalRef = findOriginalRef(generatedForeignKeys.filter(gfk => gfk.originalKey.$originalKeyRef), ref);
|
|
43
53
|
// references to expanded structures in flat mode will be found in the $originalKeyRef
|
|
44
54
|
// and in structured mode more than one match will be found in the generated foreign keys
|
|
45
55
|
if ((foundInOriginalRef.length || gfkForRef.length > 1) && !reportedErrorsForAnnoPath[path]) {
|
|
@@ -49,15 +59,15 @@ function adaptAnnotationsRefs(generatedForeignKeys, csnUtils, { error }, element
|
|
|
49
59
|
}
|
|
50
60
|
}
|
|
51
61
|
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
62
|
+
}, elementPath ? elementPath.concat(['keys', index]) : value?.$path?.slice(0, value.$path.length - 1)); // OData uses $path
|
|
63
|
+
});
|
|
54
64
|
});
|
|
55
|
-
}
|
|
65
|
+
}
|
|
56
66
|
|
|
57
67
|
// During tuple expansion, the key ref object looses the $path, therefore
|
|
58
68
|
// it needs to be extracted from the anno path
|
|
59
69
|
function getOriginatingKeyPath(gfk, path) {
|
|
60
|
-
return gfk
|
|
70
|
+
return gfk.originalKey.$path || path.slice(0, path.findIndex(ps => ps[0] === '@'));
|
|
61
71
|
}
|
|
62
72
|
|
|
63
73
|
// Loops through the generated foreign keys for this entity
|
|
@@ -65,15 +75,21 @@ function adaptAnnotationsRefs(generatedForeignKeys, csnUtils, { error }, element
|
|
|
65
75
|
// key ref. In case there are more than one foreign keys found,
|
|
66
76
|
// that means the key ref points to a structured element/managed assoc
|
|
67
77
|
function findGeneratedForeignKeyForKeyRef(generatedForeignKeys, ref) {
|
|
68
|
-
return generatedForeignKeys.filter(gfk => (ref.join() === (gfk
|
|
78
|
+
return generatedForeignKeys.filter(gfk => (ref.join() === (gfk.originalKey.as || implicitAs(gfk.originalKey.ref))));
|
|
69
79
|
}
|
|
70
80
|
|
|
71
81
|
// Tuple expansion is performed before the generation of the foreign keys and the original(unexpanded) key ref
|
|
72
82
|
// is stored in the property $originalKeyRef. Here we try to evaluate whether the reference in the annotation
|
|
73
83
|
// points to a structure that has been expanded.
|
|
74
84
|
function findOriginalRef(generatedForeignKeys, ref) {
|
|
75
|
-
return generatedForeignKeys.filter(gfk => (ref.join() === (gfk
|
|
85
|
+
return generatedForeignKeys.filter(gfk => (ref.join() === (gfk.originalKey.$originalKeyRef.as || implicitAs(gfk.originalKey.$originalKeyRef.ref))));
|
|
76
86
|
}
|
|
77
87
|
}
|
|
78
88
|
|
|
79
|
-
|
|
89
|
+
function remapToArrayOfObjects(generatedForeignKeys) {
|
|
90
|
+
return generatedForeignKeys.map(([ prefix, foreignKey, originalKey ]) => {
|
|
91
|
+
return { prefix, foreignKey, originalKey, keyAnnotations: null };
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
module.exports = { adaptAnnotationsRefs };
|