@sap/cds-compiler 6.1.0 → 6.3.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 +78 -0
- package/bin/cdsc.js +17 -6
- package/bin/cdsse.js +1 -1
- package/bin/cdsv2m.js +1 -1
- package/lib/api/main.js +29 -7
- package/lib/api/options.js +1 -1
- package/lib/base/builtins.js +9 -0
- package/lib/base/keywords.js +1 -1
- package/lib/base/message-registry.js +41 -10
- package/lib/base/messages.js +13 -6
- package/lib/base/model.js +1 -1
- package/lib/base/optionProcessorHelper.js +7 -2
- package/lib/checks/assocOutsideService.js +17 -30
- package/lib/checks/checkForTypes.js +0 -18
- package/lib/checks/checkPathsInStoredCalcElement.js +2 -1
- package/lib/checks/featureFlags.js +4 -1
- package/lib/checks/onConditions.js +2 -2
- package/lib/checks/queryNoDbArtifacts.js +16 -15
- package/lib/checks/types.js +1 -1
- package/lib/checks/utils.js +30 -6
- package/lib/checks/validator.js +4 -5
- package/lib/compiler/assert-consistency.js +3 -1
- package/lib/compiler/base.js +1 -1
- package/lib/compiler/builtins.js +1 -1
- package/lib/compiler/checks.js +85 -39
- package/lib/compiler/define.js +24 -5
- package/lib/compiler/extend.js +1 -1
- package/lib/compiler/finalize-parse-cdl.js +9 -1
- package/lib/compiler/generate.js +4 -4
- package/lib/compiler/index.js +88 -6
- package/lib/compiler/lsp-api.js +2 -0
- package/lib/compiler/populate.js +8 -8
- package/lib/compiler/propagator.js +1 -1
- package/lib/compiler/resolve.js +22 -21
- package/lib/compiler/shared.js +6 -6
- package/lib/compiler/tweak-assocs.js +53 -31
- package/lib/compiler/utils.js +9 -16
- package/lib/compiler/xpr-rewrite.js +2 -2
- package/lib/gen/BaseParser.js +35 -29
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +1424 -1430
- package/lib/gen/Dictionary.json +1 -2
- package/lib/gen/cdlKeywords.json +26 -0
- package/lib/inspect/inspectPropagation.js +1 -1
- package/lib/json/from-csn.js +2 -2
- package/lib/json/to-csn.js +1 -1
- package/lib/language/multiLineStringParser.js +1 -1
- package/lib/model/cloneCsn.js +1 -0
- package/lib/model/csnRefs.js +9 -4
- package/lib/model/csnUtils.js +67 -2
- package/lib/optionProcessor.js +9 -9
- package/lib/parsers/AstBuildingParser.js +28 -26
- package/lib/parsers/identifiers.js +2 -30
- package/lib/render/toCdl.js +73 -13
- package/lib/render/toSql.js +127 -108
- package/lib/render/utils/common.js +4 -2
- package/lib/render/utils/sql.js +67 -0
- package/lib/transform/addTenantFields.js +4 -4
- package/lib/transform/db/assertUnique.js +2 -1
- package/lib/transform/db/associations.js +37 -1
- package/lib/transform/db/assocsToQueries/transformExists.js +21 -32
- package/lib/transform/db/assocsToQueries/utils.js +1 -1
- package/lib/transform/db/cdsPersistence.js +1 -1
- package/lib/transform/db/expansion.js +37 -36
- package/lib/transform/db/killAnnotations.js +1 -0
- package/lib/transform/db/processSqlServices.js +20 -2
- package/lib/transform/draft/db.js +20 -20
- package/lib/transform/draft/odata.js +38 -40
- package/lib/transform/effective/associations.js +1 -1
- package/lib/transform/effective/flattening.js +40 -47
- package/lib/transform/effective/main.js +6 -4
- package/lib/transform/forOdata.js +201 -92
- package/lib/transform/forRelationalDB.js +151 -142
- package/lib/transform/localized.js +116 -109
- package/lib/transform/odata/adaptAnnotationRefs.js +21 -16
- package/lib/transform/odata/createForeignKeys.js +73 -70
- package/lib/transform/odata/flattening.js +216 -200
- package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +47 -45
- package/lib/transform/odata/toFinalBaseType.js +40 -39
- package/lib/transform/odata/typesExposure.js +151 -133
- package/lib/transform/odata/utils.js +7 -6
- package/lib/transform/parseExpr.js +165 -162
- package/lib/transform/transformUtils.js +184 -551
- package/lib/transform/translateAssocsToJoins.js +511 -596
- package/lib/transform/tupleExpansion.js +495 -0
- package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
- package/lib/utils/moduleResolve.js +1 -1
- package/package.json +2 -2
- package/lib/base/cleanSymbols.js +0 -17
- package/lib/checks/nonexpandableStructured.js +0 -39
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
|
+
transformAnnotationExpression,
|
|
4
5
|
applyTransformationsOnNonDictionary,
|
|
5
6
|
applyTransformations,
|
|
6
7
|
implicitAs,
|
|
@@ -106,18 +107,40 @@ function attachOnConditions( csn, csnUtils, pathDelimiter, iterateOptions = {},
|
|
|
106
107
|
|
|
107
108
|
/**
|
|
108
109
|
* @param {CSN.Model} csn
|
|
110
|
+
* @param {CSN.Options} options
|
|
109
111
|
* @param {object} csnUtils
|
|
110
112
|
* @param {string} pathDelimiter
|
|
111
113
|
* @param {boolean} [processOnInQueries=false] Wether to process on-conditions in queries (joins and mixins)
|
|
112
114
|
* @returns {(artifact: CSN.Artifact, artifactName: string) => void} Callback for forEachDefinition
|
|
113
115
|
*/
|
|
114
|
-
function getFKAccessFinalizer( csn, csnUtils, pathDelimiter, processOnInQueries = false ) {
|
|
116
|
+
function getFKAccessFinalizer( csn, options, csnUtils, pathDelimiter, processOnInQueries = false ) {
|
|
115
117
|
const {
|
|
116
118
|
inspectRef,
|
|
117
119
|
} = csnUtils;
|
|
118
120
|
|
|
119
121
|
return handleManagedAssocSteps;
|
|
120
122
|
|
|
123
|
+
/**
|
|
124
|
+
*
|
|
125
|
+
* @param {object} obj Object with annotations.
|
|
126
|
+
* @param {object} transformer Annotation expression transformers.
|
|
127
|
+
* @param {CSN.Path} path CSN path for locations.
|
|
128
|
+
*/
|
|
129
|
+
function processRefsInAnnotations(obj, transformer, path) {
|
|
130
|
+
Object.keys(obj)
|
|
131
|
+
.filter(pn => pn.startsWith('@') && obj[pn])
|
|
132
|
+
.forEach((anno) => {
|
|
133
|
+
// TODO: Ensure we only do processing here for annotations that have refs, to save time
|
|
134
|
+
const annoBefore = JSON.stringify(obj[anno]);
|
|
135
|
+
transformAnnotationExpression(obj, anno, transformer, path);
|
|
136
|
+
if (obj[anno].ref)
|
|
137
|
+
transformer.ref(obj[anno], 'ref', obj[anno].ref, path.concat(anno));
|
|
138
|
+
const annoAfter = JSON.stringify(obj[anno]);
|
|
139
|
+
if (annoBefore !== annoAfter)
|
|
140
|
+
obj[anno]['='] = true;
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
121
144
|
/**
|
|
122
145
|
* Loop over all elements and for all unmanaged associations translate
|
|
123
146
|
* <assoc base>.<managed assoc>.<fk> to <assoc base>.<managed assoc>_<fk>
|
|
@@ -133,6 +156,11 @@ function getFKAccessFinalizer( csn, csnUtils, pathDelimiter, processOnInQueries
|
|
|
133
156
|
function handleManagedAssocSteps( artifact, artifactName ) {
|
|
134
157
|
const transformer = getTransformer();
|
|
135
158
|
const inColumnsTransformer = getTransformer(true);
|
|
159
|
+
|
|
160
|
+
if (options.transformation === 'effective')
|
|
161
|
+
processRefsInAnnotations(artifact, transformer, [ 'definitions', artifactName ]);
|
|
162
|
+
|
|
163
|
+
|
|
136
164
|
for (const elemName in artifact.elements) {
|
|
137
165
|
const elem = artifact.elements[elemName];
|
|
138
166
|
// The association is an unmanaged one
|
|
@@ -140,6 +168,10 @@ function getFKAccessFinalizer( csn, csnUtils, pathDelimiter, processOnInQueries
|
|
|
140
168
|
applyTransformationsOnNonDictionary(elem, 'on', transformer, {}, [ 'definitions', artifactName, 'elements', elemName ]);
|
|
141
169
|
else if (elem.value?.stored)
|
|
142
170
|
applyTransformationsOnNonDictionary(elem, 'value', transformer, {}, [ 'definitions', artifactName, 'elements', elemName ]);
|
|
171
|
+
|
|
172
|
+
// TODO: Is this enough? I suppose that these annotations can be in places that are not an element.
|
|
173
|
+
if (options.transformation === 'effective')
|
|
174
|
+
processRefsInAnnotations(elem, transformer, [ 'definitions', artifactName, 'elements', elemName ]);
|
|
143
175
|
}
|
|
144
176
|
|
|
145
177
|
if (artifact.query || artifact.projection) {
|
|
@@ -165,6 +197,10 @@ function getFKAccessFinalizer( csn, csnUtils, pathDelimiter, processOnInQueries
|
|
|
165
197
|
};
|
|
166
198
|
queryTransformers.on = transform;
|
|
167
199
|
}
|
|
200
|
+
|
|
201
|
+
if (options.transformation === 'effective' || options.transformation === 'odata')
|
|
202
|
+
queryTransformers.xpr = transform;
|
|
203
|
+
|
|
168
204
|
applyTransformationsOnNonDictionary(artifact, artifact.query ? 'query' : 'projection', queryTransformers, { drillRef: true }, [ 'definitions', artifactName ]);
|
|
169
205
|
}
|
|
170
206
|
|
|
@@ -26,30 +26,29 @@ const { getHelpers } = require('./utils');
|
|
|
26
26
|
* + Is it part of the source side: <path> is turned into <query source>.<path> - a leading $self is stripped-off
|
|
27
27
|
* + Is it something else: Don't touch it, leave as is
|
|
28
28
|
*
|
|
29
|
-
* Given that `assoc` from above has the on-condition assoc.id = id
|
|
29
|
+
* Given that `assoc` from above has the on-condition `assoc.id = id`, we would generate the following:
|
|
30
30
|
* - F.id = E.id
|
|
31
31
|
*
|
|
32
|
-
* The final subselect looks like (select 1 as dummy from E where F.id = E.id and filter = 100)
|
|
32
|
+
* The final subselect looks like `(select 1 as dummy from E where F.id = E.id and filter = 100)`.
|
|
33
33
|
*
|
|
34
34
|
* For a $self backlink:
|
|
35
35
|
* - For $self = <assoc>.<another-assoc>, we do the following for each foreign key of <another-assoc>
|
|
36
36
|
* + <assoc>.<another-assoc>.<fk> -> <assoc.target>.<another-assoc>.<fk>
|
|
37
|
-
* +
|
|
37
|
+
* + Afterward, we get the corresponding key from the source side: <query-source>.<fk>
|
|
38
38
|
* + And turn this into a comparison: <assoc.target>.<another-assoc>.<fk> = <query-source>.<fk>
|
|
39
39
|
*
|
|
40
40
|
* So for the sample above, given an on-condition like $self = assoc.backToE, we would generate:
|
|
41
41
|
* - F.backToE.id = E.id
|
|
42
42
|
*
|
|
43
|
-
* The final subselect looks like (select 1 as dummy from E where F.backToE.id = E.id and filter = 100)
|
|
43
|
+
* The final subselect looks like `(select 1 as dummy from E where F.backToE.id = E.id and filter = 100)`.
|
|
44
44
|
*
|
|
45
45
|
* @param {CSN.Model} csn
|
|
46
46
|
* @param {CSN.Options} options
|
|
47
|
-
* @param {
|
|
48
|
-
* @param {
|
|
49
|
-
* @param {Function} initDefinition
|
|
50
|
-
* @param {Function} dropDefinitionCache
|
|
47
|
+
* @param {object} messageFunctions
|
|
48
|
+
* @param {object} csnUtils
|
|
51
49
|
*/
|
|
52
|
-
function handleExists( csn, options,
|
|
50
|
+
function handleExists( csn, options, messageFunctions, csnUtils ) {
|
|
51
|
+
const { error } = messageFunctions;
|
|
53
52
|
const {
|
|
54
53
|
getBase,
|
|
55
54
|
firstLinkIsEntityOrQuerySource,
|
|
@@ -57,17 +56,12 @@ function handleExists( csn, options, error, inspectRef, initDefinition, dropDefi
|
|
|
57
56
|
translateManagedAssocToWhere,
|
|
58
57
|
getQuerySources,
|
|
59
58
|
translateUnmanagedAssocToWhere,
|
|
60
|
-
} = getHelpers(csn, inspectRef, error);
|
|
59
|
+
} = getHelpers(csn, csnUtils.inspectRef, error);
|
|
60
|
+
|
|
61
61
|
const generatedExists = new WeakMap();
|
|
62
62
|
forEachDefinition(csn, (artifact, artifactName) => {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
dropDefinitionCache(artifact);
|
|
66
|
-
if (artifact.projection) // do the same hack we do for the other stuff...
|
|
67
|
-
artifact.query = { SELECT: artifact.projection };
|
|
68
|
-
|
|
69
|
-
if (artifact.query) {
|
|
70
|
-
forAllQueries(artifact.query, function handleExistsQuery(query, path) {
|
|
63
|
+
if (artifact.query || artifact.projection) {
|
|
64
|
+
forAllQueries(artifact.query || { SELECT: artifact.projection }, function handleExistsQuery(query, path) {
|
|
71
65
|
if (!generatedExists.has(query)) {
|
|
72
66
|
const toProcess = []; // Collect all expressions we need to process here
|
|
73
67
|
if (query.SELECT?.where?.length > 1)
|
|
@@ -90,8 +84,8 @@ function handleExists( csn, options, error, inspectRef, initDefinition, dropDefi
|
|
|
90
84
|
while (toProcess.length > 0) {
|
|
91
85
|
const [ queryPath, exprPath ] = toProcess.pop();
|
|
92
86
|
// Re-init caches for this artifact
|
|
93
|
-
dropDefinitionCache(artifact);
|
|
94
|
-
initDefinition(artifact);
|
|
87
|
+
csnUtils.dropDefinitionCache(artifact);
|
|
88
|
+
csnUtils.initDefinition(artifact);
|
|
95
89
|
// leftovers can happen with nested exists - we then need to drill down into the created SELECT
|
|
96
90
|
// to check for further exists
|
|
97
91
|
const { result, leftovers } = processExists(queryPath, exprPath);
|
|
@@ -100,16 +94,10 @@ function handleExists( csn, options, error, inspectRef, initDefinition, dropDefi
|
|
|
100
94
|
toProcess.push(...leftovers); // any leftovers - schedule for further processing
|
|
101
95
|
}
|
|
102
96
|
// Make sure we leave csnRefs usable
|
|
103
|
-
dropDefinitionCache(artifact);
|
|
104
|
-
initDefinition(artifact);
|
|
97
|
+
csnUtils.dropDefinitionCache(artifact);
|
|
98
|
+
csnUtils.initDefinition(artifact);
|
|
105
99
|
}
|
|
106
|
-
}, [ 'definitions', artifactName, 'query' ]);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (artifact.projection) { // undo our hack
|
|
110
|
-
artifact.projection = artifact.query.SELECT;
|
|
111
|
-
|
|
112
|
-
delete artifact.query;
|
|
100
|
+
}, [ 'definitions', artifactName, artifact.projection ? 'projection' : 'query' ]);
|
|
113
101
|
}
|
|
114
102
|
});
|
|
115
103
|
|
|
@@ -168,7 +156,7 @@ function handleExists( csn, options, error, inspectRef, initDefinition, dropDefi
|
|
|
168
156
|
}
|
|
169
157
|
}
|
|
170
158
|
const stack = [ [ null, startAssoc, startRest, startIndex ] ];
|
|
171
|
-
const { links } = inspectRef(path);
|
|
159
|
+
const { links } = csnUtils.inspectRef(path);
|
|
172
160
|
while (stack.length > 0) {
|
|
173
161
|
// previous: to nest "up" if the previous assoc did not originally have a filter
|
|
174
162
|
// assoc: the assoc path step
|
|
@@ -258,8 +246,9 @@ function handleExists( csn, options, error, inspectRef, initDefinition, dropDefi
|
|
|
258
246
|
const newExpr = [];
|
|
259
247
|
const query = walkCsnPath(csn, queryPath);
|
|
260
248
|
const expr = walkCsnPath(csn, exprPath);
|
|
261
|
-
const
|
|
262
|
-
const
|
|
249
|
+
const select = query.projection ?? query.SELECT;
|
|
250
|
+
const queryBase = select?.from?.ref ? (select.from.as || select.from.ref) : null;
|
|
251
|
+
const sources = getQuerySources(select);
|
|
263
252
|
|
|
264
253
|
for (let i = 0; i < expr.length; i++) {
|
|
265
254
|
if (i < expr.length - 1 && expr[i] === 'exists' && expr[i + 1].ref) {
|
|
@@ -430,7 +430,7 @@ function getHelpers( csn, inspectRef, error ) {
|
|
|
430
430
|
}
|
|
431
431
|
|
|
432
432
|
/**
|
|
433
|
-
* Use
|
|
433
|
+
* Use cached _links and _art or calculate via inspectRef
|
|
434
434
|
* @param {object} obj
|
|
435
435
|
* @param {CSN.Path} objPath
|
|
436
436
|
* @returns {object}
|
|
@@ -125,7 +125,7 @@ function getPersistenceTableProcessor( csn, options, messageFunctions ) {
|
|
|
125
125
|
|
|
126
126
|
recurseElements(artifact, [ 'definitions', artifactName ], (member, path) => {
|
|
127
127
|
// All elements must have a type for this to work
|
|
128
|
-
if (!member.$ignore && !member.kind && !member.type) {
|
|
128
|
+
if (!member.$ignore && !member.kind && !member.type && !member.elements) { // .items? Probably resolved at this point
|
|
129
129
|
error(null, path, { anno: '@cds.persistence.table' },
|
|
130
130
|
'Expecting element to have a type if view is annotated with $(ANNO)');
|
|
131
131
|
}
|
|
@@ -38,18 +38,16 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
38
38
|
columns: (parent, name, columns, path) => {
|
|
39
39
|
const artifact = csn.definitions[path[1]];
|
|
40
40
|
csnUtils.initDefinition(artifact); // potentially not initialized, yet
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
parent.columns = expand(parent.columns, path.concat('columns'), true, isComplexQuery || path.length > 4);
|
|
52
|
-
}
|
|
41
|
+
const root = csnUtils.get$combined({ SELECT: parent });
|
|
42
|
+
// TODO: replace with the correct options.transformation?
|
|
43
|
+
// Do not expand the * in OData for a moment, not to introduce changes
|
|
44
|
+
// while the OData CSN is still official
|
|
45
|
+
const isComplexQuery = parent.from.join !== undefined;
|
|
46
|
+
if (!options.toOdata)
|
|
47
|
+
parent.columns = replaceStar(root, columns, parent.excluding, isComplexQuery);
|
|
48
|
+
// FIXME(v6): Remove argument "isComplexOrNestedQuery"; we use path.length > 4 to check
|
|
49
|
+
// if we're inside the outermost "columns". If so, always prepend a table alias. See #11662
|
|
50
|
+
parent.columns = expand(parent.columns, path.concat('columns'), true, isComplexQuery || path.length > 4);
|
|
53
51
|
},
|
|
54
52
|
groupBy: (parent, name, groupBy, path) => {
|
|
55
53
|
parent.groupBy = expand(groupBy, path.concat('groupBy'));
|
|
@@ -86,24 +84,22 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
86
84
|
// (which is the thing inside SET/SELECT)
|
|
87
85
|
// We can directly use SELECT here, as only projections and SELECT can have .columns
|
|
88
86
|
const root = csnUtils.get$combined({ SELECT: parent });
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
87
|
+
// Make root look like normal .elements - we never cared about conflict afaik anyway
|
|
88
|
+
Object.keys(root).forEach((key) => {
|
|
89
|
+
root[key] = root[key][0].element;
|
|
90
|
+
});
|
|
91
|
+
const rewritten = rewrite(root, parent.columns, parent.excluding);
|
|
92
|
+
/*
|
|
93
|
+
* Do not remove unexpandable many columns in OData
|
|
94
|
+
*/
|
|
95
|
+
if (rewritten.toMany.length > 0 && !options.toOdata) {
|
|
96
|
+
markAsToDummify(artifact, path[1]);
|
|
97
|
+
rewritten.toMany.forEach(({ art }) => {
|
|
98
|
+
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)');
|
|
93
99
|
});
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
*/
|
|
98
|
-
if (rewritten.toMany.length > 0 && !options.toOdata) {
|
|
99
|
-
markAsToDummify(artifact, path[1]);
|
|
100
|
-
rewritten.toMany.forEach(({ art }) => {
|
|
101
|
-
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)');
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
else {
|
|
105
|
-
parent.columns = rewritten.columns;
|
|
106
|
-
}
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
parent.columns = rewritten.columns;
|
|
107
103
|
}
|
|
108
104
|
},
|
|
109
105
|
});
|
|
@@ -280,11 +276,13 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
280
276
|
const allToMany = [];
|
|
281
277
|
const newThing = [];
|
|
282
278
|
const containsExpandInline = columns.some(col => col.expand || col.inline);
|
|
283
|
-
if (containsExpandInline)
|
|
284
|
-
columns = replaceStar(root, columns, excluding);
|
|
285
|
-
else
|
|
279
|
+
if (!containsExpandInline)
|
|
286
280
|
return { columns, toMany: [] };
|
|
287
281
|
|
|
282
|
+
// Replace stars - needs to happen before resolving .expand/.inline since the
|
|
283
|
+
// .expand/.inline first path step affects the root *
|
|
284
|
+
columns = replaceStar(root, columns, excluding);
|
|
285
|
+
|
|
288
286
|
for (const col of columns) {
|
|
289
287
|
if (col.expand || col.inline) {
|
|
290
288
|
const { expanded, toManys } = expandInline(root, col, col.ref || [], col.expand ? [ dbName(col) ] : []);
|
|
@@ -364,7 +362,10 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
364
362
|
}
|
|
365
363
|
else if (current.on || current.cast?.on) {
|
|
366
364
|
rewriteOn(current, [ currentAlias.slice(0, -1).join(pathDelimiter) ]);
|
|
367
|
-
|
|
365
|
+
const expandedCol = Object.assign({}, current, { as: currentAlias.join(pathDelimiter) } );
|
|
366
|
+
if (currentRef.length)
|
|
367
|
+
expandedCol.ref = currentRef;
|
|
368
|
+
expanded.push(expandedCol);
|
|
368
369
|
}
|
|
369
370
|
else if (current.val !== undefined || current.func !== undefined) {
|
|
370
371
|
expanded.push(Object.assign(current, { as: currentAlias.join(pathDelimiter) }));
|
|
@@ -440,7 +441,7 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
440
441
|
function rewriteOnCondition( on, currentRef, stack ) {
|
|
441
442
|
for (let i = 0; i < on.length; i++) {
|
|
442
443
|
const part = on[i];
|
|
443
|
-
if (part.ref && part
|
|
444
|
+
if (part.ref && part.$scope !== '$magic' && part.$scope !== '$self' && part.$scope !== '$projection') {
|
|
444
445
|
part.ref = currentRef[0] ? [ currentRef[0], ...part.ref ] : part.ref;
|
|
445
446
|
on[i] = part;
|
|
446
447
|
stack.push([ part, part.ref ]);
|
|
@@ -461,7 +462,7 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
461
462
|
function rewriteSingleExpressionArray( expressionArray, currentRef, stack ) {
|
|
462
463
|
for (let i = 0; i < expressionArray.length; i++) {
|
|
463
464
|
const part = expressionArray[i];
|
|
464
|
-
if (part.ref) {
|
|
465
|
+
if (part.ref && part.$scope !== '$magic' && part.$scope !== '$self') {
|
|
465
466
|
part.ref = currentRef.concat(part.ref);
|
|
466
467
|
expressionArray[i] = part;
|
|
467
468
|
stack.push([ part, part.ref ]);
|
|
@@ -657,7 +658,7 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
657
658
|
obj.ref = [ root.$env, ...obj.ref ];
|
|
658
659
|
|
|
659
660
|
if (iterateOptions.keepKeysOrigin) {
|
|
660
|
-
setProp(obj, '$originalKeyRef',
|
|
661
|
+
setProp(obj, '$originalKeyRef', root);
|
|
661
662
|
setProp(obj, '$path', root.$path);
|
|
662
663
|
}
|
|
663
664
|
|
|
@@ -26,6 +26,7 @@ const requiredAnnos = {
|
|
|
26
26
|
'@Core.Computed': true,
|
|
27
27
|
[sqlServiceAnnotation]: true,
|
|
28
28
|
'@cds.external': true, // for external ABAP SQL services and data products for now
|
|
29
|
+
'@DataIntegration.dataproduct.type': true, // for data product production
|
|
29
30
|
};
|
|
30
31
|
|
|
31
32
|
/**
|
|
@@ -16,12 +16,15 @@ const sqlServiceAnnotation = '@protocol';
|
|
|
16
16
|
function processSqlServices(csn, options) {
|
|
17
17
|
setProp(csn, '$sqlServiceEntities', Object.create(null));
|
|
18
18
|
setProp(csn, '$dummyServiceEntities', Object.create(null));
|
|
19
|
+
setProp(csn, '$dataProductEntities', Object.create(null));
|
|
19
20
|
return function findAndMarkSqlServiceArtifacts(artifact, artifactName) {
|
|
20
|
-
const { sqlServiceName, dummyServiceName } = isEntityInSqlService(artifact, artifactName, csn, options);
|
|
21
|
+
const { sqlServiceName, dummyServiceName, dataProductServiceName } = isEntityInSqlService(artifact, artifactName, csn, options);
|
|
21
22
|
if (sqlServiceName?.length > 0)
|
|
22
23
|
setProp(artifact, '$sqlService', sqlServiceName);
|
|
23
24
|
if (dummyServiceName?.length > 0)
|
|
24
25
|
setProp(artifact, '$dummyService', dummyServiceName);
|
|
26
|
+
if (dataProductServiceName?.length > 0)
|
|
27
|
+
setProp(artifact, '$dataProductService', dataProductServiceName);
|
|
25
28
|
};
|
|
26
29
|
}
|
|
27
30
|
|
|
@@ -55,7 +58,7 @@ function isDummyService(artifact, options) {
|
|
|
55
58
|
* @returns {object} An object containing the names of the SQL service and external ABAP SQL service, if found.
|
|
56
59
|
*/
|
|
57
60
|
function isEntityInSqlService(artifact, artifactName, csn, options) {
|
|
58
|
-
const result = { sqlServiceName: undefined, dummyServiceName: undefined };
|
|
61
|
+
const result = { sqlServiceName: undefined, dummyServiceName: undefined, dataProductServiceName: undefined };
|
|
59
62
|
if (artifact.kind !== 'entity' || !artifactName.includes('.') || hasPersistenceSkipAnnotation(artifact))
|
|
60
63
|
return result;
|
|
61
64
|
|
|
@@ -72,6 +75,9 @@ function isEntityInSqlService(artifact, artifactName, csn, options) {
|
|
|
72
75
|
if (isDummyService(definition, options))
|
|
73
76
|
result.dummyServiceName = possibleServiceName;
|
|
74
77
|
|
|
78
|
+
if (isDataProductService(definition, options))
|
|
79
|
+
result.dataProductServiceName = possibleServiceName;
|
|
80
|
+
|
|
75
81
|
// We don't allow nested services/contexts - if we find one, we don't need to keep searching
|
|
76
82
|
if (definition.kind === 'service' || definition.kind === 'context')
|
|
77
83
|
return result;
|
|
@@ -108,10 +114,22 @@ function createServiceDummy(artifact, artifactName, csn, { error }) {
|
|
|
108
114
|
csn.definitions[`dummy.${ artifactName }`] = dummy;
|
|
109
115
|
}
|
|
110
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Determines if the given artifact is a data product service.
|
|
119
|
+
*
|
|
120
|
+
* @param {object} artifact - The artifact to evaluate.
|
|
121
|
+
* @param {object} options - The options object containing feature flags.
|
|
122
|
+
* @returns {boolean} - Returns `true` if the artifact is a data product service, otherwise `false`.
|
|
123
|
+
*/
|
|
124
|
+
function isDataProductService(artifact, options) {
|
|
125
|
+
return isBetaEnabled(options, 'projectionViews') && artifact.kind === 'service' && artifact['@DataIntegration.dataproduct.type'] === 'primary';
|
|
126
|
+
}
|
|
127
|
+
|
|
111
128
|
module.exports = {
|
|
112
129
|
processSqlServices,
|
|
113
130
|
isSqlService,
|
|
114
131
|
isDummyService,
|
|
132
|
+
isDataProductService,
|
|
115
133
|
sqlServiceAnnotation,
|
|
116
134
|
createServiceDummy,
|
|
117
135
|
};
|
|
@@ -4,7 +4,7 @@ const {
|
|
|
4
4
|
getServiceNames, forEachDefinition,
|
|
5
5
|
getResultingName, forEachMemberRecursively, applyAnnotationsFromExtensions,
|
|
6
6
|
} = require('../../model/csnUtils');
|
|
7
|
-
const { setProp
|
|
7
|
+
const { setProp } = require('../../base/model');
|
|
8
8
|
const { getTransformers } = require('../transformUtils');
|
|
9
9
|
const { ModelError } = require('../../base/error');
|
|
10
10
|
const { forEach } = require('../../utils/objectUtils');
|
|
@@ -56,10 +56,10 @@ function generateDrafts( csn, options, pathDelimiter, messageFunctions ) {
|
|
|
56
56
|
|
|
57
57
|
// Redirect associations/compositions between draft shadow nodes
|
|
58
58
|
for (const name in draftNodes) {
|
|
59
|
-
const shadowNode = csn.definitions[`${name}${draftSuffix}`];
|
|
59
|
+
const shadowNode = csn.definitions[`${ name }${ draftSuffix }`];
|
|
60
60
|
// Might not exist because of previous errors
|
|
61
61
|
if (shadowNode)
|
|
62
|
-
redirectDraftTargets(csn.definitions[`${name}${draftSuffix}`], draftNodes);
|
|
62
|
+
redirectDraftTargets(csn.definitions[`${ name }${ draftSuffix }`], draftNodes);
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
}
|
|
@@ -84,7 +84,7 @@ function generateDrafts( csn, options, pathDelimiter, messageFunctions ) {
|
|
|
84
84
|
const draftNodeName = elem.target;
|
|
85
85
|
// Sanity check
|
|
86
86
|
if (!draftNode)
|
|
87
|
-
throw new ModelError(`Expecting target to be resolved: ${JSON.stringify(elem, null, 2)}`);
|
|
87
|
+
throw new ModelError(`Expecting target to be resolved: ${ JSON.stringify(elem, null, 2) }`);
|
|
88
88
|
|
|
89
89
|
// Ignore composition if not part of a service
|
|
90
90
|
if (!isPartOfService(draftNodeName)) {
|
|
@@ -115,11 +115,11 @@ function generateDrafts( csn, options, pathDelimiter, messageFunctions ) {
|
|
|
115
115
|
function generateDraftForHana( artifact, artifactName, draftRootName ) {
|
|
116
116
|
// Sanity check
|
|
117
117
|
if (!isPartOfService(artifactName))
|
|
118
|
-
throw new ModelError(`Expecting artifact to be part of a service: ${JSON.stringify(artifact)}`);
|
|
118
|
+
throw new ModelError(`Expecting artifact to be part of a service: ${ JSON.stringify(artifact) }`);
|
|
119
119
|
|
|
120
120
|
|
|
121
121
|
// The name of the draft shadow entity we should generate
|
|
122
|
-
const draftsArtifactName = `${artifactName}${draftSuffix}`;
|
|
122
|
+
const draftsArtifactName = `${ artifactName }${ draftSuffix }`;
|
|
123
123
|
|
|
124
124
|
generatedArtifacts[draftsArtifactName] = true;
|
|
125
125
|
|
|
@@ -132,7 +132,7 @@ function generateDrafts( csn, options, pathDelimiter, messageFunctions ) {
|
|
|
132
132
|
// Ignore boolean return value. We know that we're inside a service or else we wouldn't have reached this code.
|
|
133
133
|
const matchingService = getMatchingService(artifactName) || '';
|
|
134
134
|
// Generate the DraftAdministrativeData projection into the service, unless there is already one
|
|
135
|
-
const draftAdminDataProjectionName = `${matchingService}.DraftAdministrativeData`;
|
|
135
|
+
const draftAdminDataProjectionName = `${ matchingService }.DraftAdministrativeData`;
|
|
136
136
|
let draftAdminDataProjection = csn.definitions[draftAdminDataProjectionName];
|
|
137
137
|
if (!draftAdminDataProjection) {
|
|
138
138
|
generatedArtifacts[draftAdminDataProjectionName] = true;
|
|
@@ -232,10 +232,9 @@ function generateDrafts( csn, options, pathDelimiter, messageFunctions ) {
|
|
|
232
232
|
draftAdministrativeData.DraftAdministrativeData.notNull = true;
|
|
233
233
|
addElement(draftAdministrativeData, draftsArtifact, artifactName);
|
|
234
234
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
235
|
+
const draftMessages = { DraftMessages: { '@Core.Computed': true, virtual: true, items: { type: 'DRAFT.DraftAdministrativeData_DraftMessage' } } };
|
|
236
|
+
addElement(draftMessages, draftsArtifact, artifactName);
|
|
237
|
+
|
|
239
238
|
// Note that we may need to do the HANA transformation steps for managed associations
|
|
240
239
|
// (foreign key field generation, generatedFieldName, creating ON-condition) by hand,
|
|
241
240
|
// because the corresponding transformation steps have already been done on all artifacts
|
|
@@ -278,18 +277,19 @@ function generateDrafts( csn, options, pathDelimiter, messageFunctions ) {
|
|
|
278
277
|
const sourceElement = source.elements[draftUUIDKey.ref[0]];
|
|
279
278
|
const targetElement = {};
|
|
280
279
|
forEach(sourceElement, (key, value) => {
|
|
281
|
-
if(!key.startsWith('@') && key !== 'key')
|
|
280
|
+
if (!key.startsWith('@') && key !== 'key')
|
|
282
281
|
targetElement[key] = value;
|
|
283
|
-
})
|
|
282
|
+
});
|
|
284
283
|
|
|
285
|
-
if(sourceElement.key)
|
|
284
|
+
if (sourceElement.key)
|
|
285
|
+
targetElement.notNull = true;
|
|
286
286
|
|
|
287
|
-
draftsArtifact.elements[
|
|
287
|
+
draftsArtifact.elements[`DraftAdministrativeData${ options.sqlMapping === 'hdbcds' ? '.' : '_' }${ draftUUIDKey.ref[0] }`] = targetElement;
|
|
288
288
|
|
|
289
289
|
draftAdministrativeData.DraftAdministrativeData.on = createAssociationPathComparison('DraftAdministrativeData',
|
|
290
290
|
getNameForRef(draftUUIDKey),
|
|
291
291
|
'=',
|
|
292
|
-
`DraftAdministrativeData${pathDelimiter}DraftUUID`);
|
|
292
|
+
`DraftAdministrativeData${ pathDelimiter }DraftUUID`);
|
|
293
293
|
// The notNull has been transferred to the foreign key field and must be removed on the association
|
|
294
294
|
delete draftAdministrativeData.DraftAdministrativeData.notNull;
|
|
295
295
|
|
|
@@ -337,9 +337,9 @@ function generateDrafts( csn, options, pathDelimiter, messageFunctions ) {
|
|
|
337
337
|
function getDraftShadowEntityFor( draftNode, draftNodeName ) {
|
|
338
338
|
// Sanity check
|
|
339
339
|
if (!draftNodes[draftNodeName])
|
|
340
|
-
throw new ModelError(`Not a draft node: ${draftNodeName}`);
|
|
340
|
+
throw new ModelError(`Not a draft node: ${ draftNodeName }`);
|
|
341
341
|
|
|
342
|
-
return { shadowTarget: csn.definitions[`${draftNodeName}${draftSuffix}`], shadowTargetName: `${draftNodeName}${draftSuffix}` };
|
|
342
|
+
return { shadowTarget: csn.definitions[`${ draftNodeName }${ draftSuffix }`], shadowTargetName: `${ draftNodeName }${ draftSuffix }` };
|
|
343
343
|
}
|
|
344
344
|
}
|
|
345
345
|
|
|
@@ -351,7 +351,7 @@ function generateDrafts( csn, options, pathDelimiter, messageFunctions ) {
|
|
|
351
351
|
*/
|
|
352
352
|
function isPartOfService( artifactName ) {
|
|
353
353
|
for (const serviceName of allServices) {
|
|
354
|
-
if (artifactName.startsWith(`${serviceName}.`))
|
|
354
|
+
if (artifactName.startsWith(`${ serviceName }.`))
|
|
355
355
|
return true;
|
|
356
356
|
}
|
|
357
357
|
|
|
@@ -369,7 +369,7 @@ function generateDrafts( csn, options, pathDelimiter, messageFunctions ) {
|
|
|
369
369
|
/** @type {false|string} */
|
|
370
370
|
let match = false;
|
|
371
371
|
for (const serviceName of allServices) {
|
|
372
|
-
if (artifactName.startsWith(`${serviceName}.`) && (!match || serviceName.length < match.length))
|
|
372
|
+
if (artifactName.startsWith(`${ serviceName }.`) && (!match || serviceName.length < match.length))
|
|
373
373
|
match = serviceName;
|
|
374
374
|
}
|
|
375
375
|
return match;
|