@sap/cds-compiler 2.7.0 → 2.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +103 -0
- package/lib/api/main.js +8 -10
- package/lib/api/options.js +13 -9
- package/lib/api/validate.js +11 -8
- package/lib/base/keywords.js +32 -2
- package/lib/base/message-registry.js +16 -0
- package/lib/base/messages.js +2 -0
- package/lib/base/model.js +1 -0
- package/lib/checks/onConditions.js +5 -0
- package/lib/checks/types.js +26 -2
- package/lib/checks/unknownMagic.js +38 -0
- package/lib/checks/validator.js +7 -2
- package/lib/compiler/assert-consistency.js +11 -5
- package/lib/compiler/builtins.js +2 -0
- package/lib/compiler/checks.js +3 -1
- package/lib/compiler/definer.js +87 -29
- package/lib/compiler/resolver.js +75 -16
- package/lib/compiler/shared.js +29 -9
- package/lib/edm/annotations/genericTranslation.js +182 -186
- package/lib/edm/csn2edm.js +93 -98
- package/lib/edm/edm.js +16 -20
- package/lib/edm/edmPreprocessor.js +274 -83
- package/lib/edm/edmUtils.js +29 -10
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +12 -1
- package/lib/gen/language.tokens +57 -53
- package/lib/gen/languageLexer.interp +10 -1
- package/lib/gen/languageLexer.js +770 -744
- package/lib/gen/languageLexer.tokens +49 -46
- package/lib/gen/languageParser.js +4727 -4323
- package/lib/json/from-csn.js +52 -23
- package/lib/json/to-csn.js +185 -71
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +9 -0
- package/lib/language/language.g4 +90 -31
- package/lib/main.js +4 -0
- package/lib/model/api.js +78 -0
- package/lib/model/csnRefs.js +7 -1
- package/lib/model/csnUtils.js +5 -4
- package/lib/optionProcessor.js +7 -1
- package/lib/render/.eslintrc.json +3 -1
- package/lib/render/toCdl.js +45 -9
- package/lib/render/toHdbcds.js +100 -34
- package/lib/render/toSql.js +12 -4
- package/lib/render/utils/common.js +5 -9
- package/lib/sql-identifier.js +6 -1
- package/lib/transform/db/draft.js +6 -4
- package/lib/transform/db/expansion.js +14 -4
- package/lib/transform/db/flattening.js +13 -5
- package/lib/transform/db/transformExists.js +252 -58
- package/lib/transform/forHanaNew.js +7 -1
- package/lib/transform/forOdataNew.js +12 -8
- package/lib/transform/odata/attachPath.js +19 -4
- package/lib/transform/odata/generateForeignKeyElements.js +11 -10
- package/lib/transform/odata/referenceFlattener.js +44 -38
- package/lib/transform/odata/sortByAssociationDependency.js +2 -2
- package/lib/transform/odata/structuralPath.js +76 -0
- package/lib/transform/odata/structureFlattener.js +13 -10
- package/lib/transform/odata/typesExposure.js +22 -12
- package/lib/transform/transformUtilsNew.js +33 -1
- package/lib/transform/translateAssocsToJoins.js +6 -4
- package/lib/transform/universalCsnEnricher.js +67 -0
- package/package.json +1 -1
|
@@ -35,7 +35,7 @@ module.exports = function (csn, options, referenceFlattener, csnUtils, error, is
|
|
|
35
35
|
let generatedForeignKeyNamesForPath = Object.create(null); // map<path,[key-name]>
|
|
36
36
|
|
|
37
37
|
sortedAssociations.forEach(item => {
|
|
38
|
-
const { definitionName, elementName, element, parent, path } = item;
|
|
38
|
+
const { definitionName, structuralNodeName, elementName, element, parent, path } = item;
|
|
39
39
|
|
|
40
40
|
if (csnUtils.isManagedAssociationElement(element) && element.keys) {
|
|
41
41
|
if (flatKeys) // tackling the ref value in assoc.keys
|
|
@@ -44,7 +44,7 @@ module.exports = function (csn, options, referenceFlattener, csnUtils, error, is
|
|
|
44
44
|
fixCardinality(element);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
let arrayOfGeneratedForeignKeyNames = generateForeignKeys(definitionName, elementName, element, parent, path);
|
|
47
|
+
let arrayOfGeneratedForeignKeyNames = generateForeignKeys(definitionName, structuralNodeName, elementName, element, parent, path);
|
|
48
48
|
generatedForeignKeyNamesForPath[item.path.join('/')] = arrayOfGeneratedForeignKeyNames;
|
|
49
49
|
})
|
|
50
50
|
|
|
@@ -111,7 +111,7 @@ module.exports = function (csn, options, referenceFlattener, csnUtils, error, is
|
|
|
111
111
|
/**
|
|
112
112
|
* Generates foreign keys and returns their names as an array
|
|
113
113
|
*/
|
|
114
|
-
function generateForeignKeys(definitionName, assocName, assoc, parent, path) {
|
|
114
|
+
function generateForeignKeys(definitionName, structuralNodeName, assocName, assoc, parent, path) {
|
|
115
115
|
let foreignKeyElements = Object.create(null);
|
|
116
116
|
|
|
117
117
|
// First, loop over the keys array of the association and generate the FKs.
|
|
@@ -134,13 +134,14 @@ module.exports = function (csn, options, referenceFlattener, csnUtils, error, is
|
|
|
134
134
|
if (parent.returns)
|
|
135
135
|
parent = parent.returns.items || parent.returns;
|
|
136
136
|
|
|
137
|
-
|
|
137
|
+
const dictionary = parent[structuralNodeName];
|
|
138
|
+
let currElementsNames = Object.keys(parent[structuralNodeName]);
|
|
138
139
|
for (const [foreignKeyName, foreignKey] of Object.entries(foreignKeyElements)) {
|
|
139
140
|
copyAnnotations(assoc, foreignKey, true);
|
|
140
141
|
// Insert artificial element into artifact, with all cross-links
|
|
141
|
-
if (
|
|
142
|
-
if (!(
|
|
143
|
-
const path =
|
|
142
|
+
if (dictionary[foreignKeyName]) {
|
|
143
|
+
if (!(dictionary[foreignKeyName]['@odata.foreignKey4'] || isDeepEqual(dictionary[foreignKeyName], foreignKey))) {
|
|
144
|
+
const path = dictionary[foreignKeyName].$path;
|
|
144
145
|
error(null, path, { name: foreignKeyName, art: assocName }, 'Generated foreign key element $(NAME) for association $(ART) conflicts with existing element');
|
|
145
146
|
}
|
|
146
147
|
}
|
|
@@ -151,8 +152,8 @@ module.exports = function (csn, options, referenceFlattener, csnUtils, error, is
|
|
|
151
152
|
// if (flatKeys)
|
|
152
153
|
currElementsNames.splice(assocIndex + 1, 0, ...Object.keys(foreignKeyElements));
|
|
153
154
|
|
|
154
|
-
parent
|
|
155
|
-
previous[name] =
|
|
155
|
+
parent[structuralNodeName] = currElementsNames.reduce((previous, name) => {
|
|
156
|
+
previous[name] = dictionary[name] || foreignKeyElements[name];
|
|
156
157
|
return previous;
|
|
157
158
|
}, Object.create(null));
|
|
158
159
|
|
|
@@ -180,7 +181,7 @@ module.exports = function (csn, options, referenceFlattener, csnUtils, error, is
|
|
|
180
181
|
|
|
181
182
|
function processStucturedKey(fkArtifact, assocName, foreignKeyRef) {
|
|
182
183
|
const subStruct = fkArtifact.elements ? fkArtifact : csnUtils.getFinalBaseType(fkArtifact.type);
|
|
183
|
-
const flatElements = flattenStructure(subStruct, subStruct.$path, csnUtils, options, error, undefined, fkArtifact.$path.slice(-1) || []).newFlatElements;
|
|
184
|
+
const flatElements = flattenStructure(subStruct.elements, subStruct.$path, csnUtils, options, error, undefined, fkArtifact.$path.slice(-1) || []).newFlatElements;
|
|
184
185
|
for (const [flatElemName, flatElem] of Object.entries(flatElements)) {
|
|
185
186
|
const foreignKeyElementName =
|
|
186
187
|
`${assocName.replace(/\./g, '_')}_${foreignKeyRef.as ? flatElemName.replace(implicitAs(foreignKeyRef.ref), foreignKeyRef.as) : flatElemName}`;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { forEachRef } = require('../../model/csnUtils');
|
|
2
2
|
const { setProp } = require('../../base/model');
|
|
3
3
|
const { implicitAs } = require('../../model/csnRefs');
|
|
4
|
+
const { structuralPath } = require('./structuralPath');
|
|
4
5
|
|
|
5
6
|
/** This class is used for generic reference flattening.
|
|
6
7
|
* It provides the following functionality:
|
|
@@ -73,8 +74,7 @@ class ReferenceFlattener {
|
|
|
73
74
|
});
|
|
74
75
|
if (paths)
|
|
75
76
|
setProp(node, '$paths', paths);
|
|
76
|
-
|
|
77
|
-
// cache also if structured or not
|
|
77
|
+
// cache if structured or not
|
|
78
78
|
let structured = resolved.links.map(link => link.art ? (isStructured(link.art)) : undefined);
|
|
79
79
|
this.structuredReference[path.join('/')] = structured;
|
|
80
80
|
});
|
|
@@ -176,10 +176,10 @@ class ReferenceFlattener {
|
|
|
176
176
|
if (node.$paths) {
|
|
177
177
|
let newRef = []; // flattened reference
|
|
178
178
|
let flattenWithPrevious = false;
|
|
179
|
-
let
|
|
179
|
+
let lastFlattenedID = null; // The variable will be set to the index of the last flattened path
|
|
180
180
|
node.$paths.forEach((path, i) => {
|
|
181
181
|
if (path === undefined || !ref[i]) return;
|
|
182
|
-
let spath = path.join('/')
|
|
182
|
+
let spath = path.join('/');
|
|
183
183
|
let movedTo = this.elementTransitions[spath]; // detect element transition
|
|
184
184
|
let flattened = this.flattenedElementPaths[spath];
|
|
185
185
|
if (flattenWithPrevious) {
|
|
@@ -190,27 +190,26 @@ class ReferenceFlattener {
|
|
|
190
190
|
ref[i].id = newRef[newRef.length - 1];
|
|
191
191
|
newRef[newRef.length - 1] = ref[i];
|
|
192
192
|
}
|
|
193
|
-
|
|
193
|
+
lastFlattenedID = i;
|
|
194
194
|
} else if(movedTo && i===0) { // handle local scope reference which is transitioned - replace first item in reference
|
|
195
195
|
let movedToElementName = movedTo[movedTo.length-1];
|
|
196
196
|
newRef.push(movedToElementName);
|
|
197
|
-
|
|
197
|
+
lastFlattenedID = i;
|
|
198
198
|
} else {
|
|
199
199
|
newRef.push(ref[i]);
|
|
200
200
|
}
|
|
201
201
|
flattenWithPrevious = flattened;
|
|
202
202
|
});
|
|
203
|
-
if (newRef !== undefined &&
|
|
203
|
+
if (newRef !== undefined && lastFlattenedID !== null) { // make sure the reference changed and only then replace it with the new one
|
|
204
204
|
// check if this is a column and add alias if missing
|
|
205
|
-
|
|
206
|
-
|
|
205
|
+
let structural = structuralPath(csn, path);
|
|
206
|
+
if (isColumnInSelectOrProjection(structural)) {
|
|
207
|
+
if (!node.as && lastFlattenedID === ref.length-1) // attach alias only if there is none and it is the last item in the reference that was flattened
|
|
207
208
|
node.as = node.ref[node.ref.length - 1];
|
|
208
|
-
}
|
|
209
209
|
}
|
|
210
210
|
setProp(newRef, '$path', path.concat('ref'));
|
|
211
211
|
if (!node.as) {
|
|
212
|
-
|
|
213
|
-
if (ifKeysScope(path))
|
|
212
|
+
if (isPartOfKeysStructure(structural))
|
|
214
213
|
node.as = implicitAs(node.ref);
|
|
215
214
|
else
|
|
216
215
|
setProp(node, 'as', implicitAs(node.ref))
|
|
@@ -223,13 +222,11 @@ class ReferenceFlattener {
|
|
|
223
222
|
|
|
224
223
|
applyAliasesInOnCond(csn, inspectRef) {
|
|
225
224
|
forEachRef(csn, (ref, node, path) => {
|
|
226
|
-
//
|
|
227
|
-
|
|
228
|
-
if
|
|
229
|
-
// the above condition means: only handle references starting with
|
|
230
|
-
// $self or element references outside queries
|
|
231
|
-
|
|
225
|
+
// Process only on-conditions of associations
|
|
226
|
+
let structural = structuralPath(csn, path);
|
|
227
|
+
if(!isOnCondition(structural)) return;
|
|
232
228
|
let { links } = inspectRef(path);
|
|
229
|
+
if(!links) return; // $user not resolvable
|
|
233
230
|
let keysOfPreviousStepWhenManagedAssoc = undefined;
|
|
234
231
|
|
|
235
232
|
let aliasedRef = [...ref];
|
|
@@ -253,32 +250,41 @@ class ReferenceFlattener {
|
|
|
253
250
|
}
|
|
254
251
|
}
|
|
255
252
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
253
|
+
/**
|
|
254
|
+
* Checks if the provided path is a column inside a key node
|
|
255
|
+
* by exploring the possibility of the structural path to ends with 'elements' and 'keys'.
|
|
256
|
+
*
|
|
257
|
+
* @param structural structural path to explore
|
|
258
|
+
* @returns {boolean} True if the provided path matched the requirements to be part of a key node.
|
|
259
|
+
*/
|
|
260
|
+
function isPartOfKeysStructure(structural) {
|
|
261
|
+
return structural[structural.length-2] === 'elements'
|
|
262
|
+
&& structural[structural.length-1] === 'keys';
|
|
262
263
|
}
|
|
263
264
|
|
|
264
265
|
/**
|
|
265
|
-
* Checks if the provided path is a column inside a select node
|
|
266
|
-
* by exploring the possibility of the path to
|
|
266
|
+
* Checks if the provided path is a column inside a select or a projection node
|
|
267
|
+
* by exploring the possibility of the structural path to contain 'SELECT' or 'projection'
|
|
268
|
+
* and ends with 'columns' or 'columns' and 'expand'.
|
|
267
269
|
*
|
|
268
|
-
* @param
|
|
270
|
+
* @param structural structural path to explore
|
|
269
271
|
* @returns {boolean} True if the provided path matched the requirements to be a select node.
|
|
270
272
|
*/
|
|
271
|
-
function
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
273
|
+
function isColumnInSelectOrProjection(structural) {
|
|
274
|
+
return (structural.includes('SELECT') || structural.includes('projection'))
|
|
275
|
+
&& (structural[structural.length-1] === 'columns' || (structural[structural.length-2] === 'columns' && structural[structural.length-1] === 'expand'));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Checks if the provided path is a column inside an on-condition of an element
|
|
280
|
+
* by exploring the possibility of the structural path to ends with 'elements' and 'on'.
|
|
281
|
+
*
|
|
282
|
+
* @param structural structural path to explore
|
|
283
|
+
* @returns {boolean} True if the provided path matched the requirements to be part of an element's on-condition.
|
|
284
|
+
*/
|
|
285
|
+
function isOnCondition(structural) {
|
|
286
|
+
return structural[structural.length-2] === 'elements'
|
|
287
|
+
&& structural[structural.length-1] === 'on';
|
|
282
288
|
}
|
|
283
289
|
|
|
284
290
|
module.exports = ReferenceFlattener;
|
|
@@ -24,7 +24,7 @@ function buildDependenciesFor(csn, referenceFlattener, isExternalServiceMember)
|
|
|
24
24
|
forEachDefinition(csn, (def, definitionName) => {
|
|
25
25
|
/** @type {CSN.Path} */
|
|
26
26
|
let root = ['definitions', definitionName];
|
|
27
|
-
forEachMemberRecursively(def, (element, elementName,
|
|
27
|
+
forEachMemberRecursively(def, (element, elementName, structuralNodeName, subpath, parent) => {
|
|
28
28
|
let path = root.concat(subpath);
|
|
29
29
|
// go only through managed associations and compositions
|
|
30
30
|
if (isAssociationOrComposition(element) && element.keys && !element.on) { // check association FKs
|
|
@@ -44,7 +44,7 @@ function buildDependenciesFor(csn, referenceFlattener, isExternalServiceMember)
|
|
|
44
44
|
elementDependencies.push(targetElementPath);
|
|
45
45
|
}
|
|
46
46
|
})
|
|
47
|
-
dependencies[path.join("/")] = { definitionName, elementName, element, path, parent, dependencies: elementDependencies };
|
|
47
|
+
dependencies[path.join("/")] = { structuralNodeName, definitionName, elementName, element, path, parent, dependencies: elementDependencies };
|
|
48
48
|
}
|
|
49
49
|
}) // forEachMemberRecursively
|
|
50
50
|
}, { skipArtifact: isExternalServiceMember }) // forEachDefinition
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// The module traverses a given CSN using a specific path, collects structural node names and returns them.
|
|
2
|
+
|
|
3
|
+
const structuralNodeHandlers = {
|
|
4
|
+
definitions: traverseDict,
|
|
5
|
+
elements: traverseDict,
|
|
6
|
+
actions: traverseDict,
|
|
7
|
+
params: traverseDict,
|
|
8
|
+
items: traverseTyped,
|
|
9
|
+
enum: traverseDict,
|
|
10
|
+
returns: traverseTyped,
|
|
11
|
+
on: traverseArray,
|
|
12
|
+
keys: traverseArray,
|
|
13
|
+
ref: traverseRef,
|
|
14
|
+
query: traverseTyped,
|
|
15
|
+
SELECT: traverseTyped,
|
|
16
|
+
SET: traverseTyped,
|
|
17
|
+
args: traverseArray,
|
|
18
|
+
columns: traverseArray,
|
|
19
|
+
projection: traverseTyped,
|
|
20
|
+
from: traverseTyped,
|
|
21
|
+
mixin: traverseDict,
|
|
22
|
+
where: traverseArray,
|
|
23
|
+
orderBy: traverseArray,
|
|
24
|
+
groupBy: traverseArray,
|
|
25
|
+
having: traverseArray,
|
|
26
|
+
xpr: traverseArray,
|
|
27
|
+
expand: traverseArray,
|
|
28
|
+
inline: traverseArray,
|
|
29
|
+
cast: traverseTyped,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function structuralPath(csn, path) {
|
|
33
|
+
return traverseDict(csn.definitions, path, 1, ['definitions']);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function traverseRef(obj, path, index, typeStack) {
|
|
37
|
+
return traverseArray(obj, path, index, typeStack);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function traverseArray(obj, path, index, typeStack) {
|
|
41
|
+
if(!Array.isArray(obj)) return typeStack;
|
|
42
|
+
const name = path[index];
|
|
43
|
+
const element = obj[name];
|
|
44
|
+
return traverseTyped(element, path, index+1, typeStack);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function traverseDict(obj, path, index, typeStack) {
|
|
48
|
+
if(typeof obj !== 'object') return typeStack;
|
|
49
|
+
const name = path[index];
|
|
50
|
+
if(name === undefined) return typeStack;
|
|
51
|
+
return traverseTyped(obj[name], path, index+1, typeStack);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function traverseDictArray(obj, path, index, typeStack) {
|
|
55
|
+
if(typeof obj !== 'object') return typeStack;
|
|
56
|
+
const name = path[index];
|
|
57
|
+
if(name === undefined) return typeStack;
|
|
58
|
+
return traverseArray(obj[name], path, index+1, typeStack);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function traverseTyped(obj, path, index, typeStack) {
|
|
62
|
+
if(!obj) return typeStack;
|
|
63
|
+
const name = path[index];
|
|
64
|
+
if(name === undefined) return typeStack;
|
|
65
|
+
if(name[0] === '@') return typeStack; // skip annotations
|
|
66
|
+
const func = structuralNodeHandlers[name];
|
|
67
|
+
if(func) return func(obj[name], path, index+1, typeStack.concat(name));
|
|
68
|
+
// not typed -> columns?
|
|
69
|
+
if(typeStack[typeStack.length-1] === 'columns')
|
|
70
|
+
return traverseDictArray(obj, path, index, typeStack);
|
|
71
|
+
return typeStack;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = {
|
|
75
|
+
structuralPath
|
|
76
|
+
}
|
|
@@ -74,17 +74,22 @@ function flattenDefinition(definition, definitionPath, csnUtils, options, refere
|
|
|
74
74
|
if (definition.kind !== 'entity' && definition.kind !== 'view')
|
|
75
75
|
return;
|
|
76
76
|
|
|
77
|
-
let { newFlatElements } = flattenStructure(definition, definitionPath, csnUtils, options, error, referenceFlattener);
|
|
77
|
+
let { newFlatElements } = flattenStructure(definition.elements, definitionPath, csnUtils, options, error, referenceFlattener);
|
|
78
78
|
|
|
79
79
|
attachPathOnPartialCSN(newFlatElements, definitionPath.concat('elements'));
|
|
80
|
-
|
|
81
80
|
definition.elements = newFlatElements;
|
|
81
|
+
|
|
82
|
+
if (definition.params) {
|
|
83
|
+
let { newFlatElements } = flattenStructure(definition.params, definitionPath, csnUtils, options, error, referenceFlattener);
|
|
84
|
+
attachPathOnPartialCSN(newFlatElements, definitionPath.concat('params'));
|
|
85
|
+
definition.params = newFlatElements;
|
|
86
|
+
}
|
|
82
87
|
} // flattenDefinition
|
|
83
88
|
|
|
84
89
|
/**
|
|
85
90
|
* Flattens structured element by calling element flattener for each structured child.
|
|
86
91
|
* Returns a dictionary containing all the new elements for the given structure.
|
|
87
|
-
* @param {*}
|
|
92
|
+
* @param {*} dictionary to flatten
|
|
88
93
|
* @param {CSN.Path} path the path of the structure in the CSN tree
|
|
89
94
|
* @param {*} csnUtils
|
|
90
95
|
* @param {Function} error Error message function
|
|
@@ -93,14 +98,14 @@ function flattenDefinition(definition, definitionPath, csnUtils, options, refere
|
|
|
93
98
|
* @param {*} [newFlatElements]
|
|
94
99
|
* @param {boolean} [isTopLevelElement] states if this is a top level element
|
|
95
100
|
*/
|
|
96
|
-
function flattenStructure(
|
|
101
|
+
function flattenStructure(dictionary, path, csnUtils, options, error, referenceFlattener = undefined, elementPathInStructure = [],
|
|
97
102
|
newFlatElements = Object.create(null), isTopLevelElement = true, isParentNotNull = false) {
|
|
98
103
|
|
|
99
|
-
if (!isTopLevelElement) addPropsForPropagationFromElement(
|
|
104
|
+
if (!isTopLevelElement) addPropsForPropagationFromElement(dictionary);
|
|
100
105
|
|
|
101
106
|
let generatedNewFlatElementsNames = []; // holds the names of all new child elements of the structure
|
|
102
107
|
|
|
103
|
-
|
|
108
|
+
dictionary && Object.entries(dictionary).forEach(([elementName, element]) => {
|
|
104
109
|
let currPath = path.concat('elements', elementName);
|
|
105
110
|
|
|
106
111
|
if (isTopLevelElement) {
|
|
@@ -118,8 +123,8 @@ function flattenStructure(struct, path, csnUtils, options, error, referenceFlatt
|
|
|
118
123
|
addPropsForPropagationFromElement(element);
|
|
119
124
|
|
|
120
125
|
// if the child element is structured itself -> needs to be flattened
|
|
121
|
-
const
|
|
122
|
-
let result = flattenStructure(
|
|
126
|
+
const elements = element.elements || csnUtils.getFinalBaseType(element.type).elements;
|
|
127
|
+
let result = flattenStructure(elements, currPath, csnUtils, options, error, referenceFlattener, elementPathInStructure.concat(elementName), newFlatElements, false, isNotNull());
|
|
123
128
|
generatedNewFlatElementsNames.push(...result.generatedNewFlatElementsNames); // accomulate names of produced elements
|
|
124
129
|
|
|
125
130
|
} else { // when we do not need to flat, this is scalar or empty (cds-compiler#4337) -> needs to be registered in referenceFlattener
|
|
@@ -127,7 +132,6 @@ function flattenStructure(struct, path, csnUtils, options, error, referenceFlatt
|
|
|
127
132
|
let elementNameWithDots = elementPathInStructure.concat(elementName).join('.');
|
|
128
133
|
addNewElementToResult(element, newElementName, elementNameWithDots, currPath);
|
|
129
134
|
}
|
|
130
|
-
|
|
131
135
|
});
|
|
132
136
|
|
|
133
137
|
if (referenceFlattener) {
|
|
@@ -135,7 +139,6 @@ function flattenStructure(struct, path, csnUtils, options, error, referenceFlatt
|
|
|
135
139
|
}
|
|
136
140
|
return { newFlatElements, generatedNewFlatElementsNames };
|
|
137
141
|
|
|
138
|
-
|
|
139
142
|
// adds newly created element into the final dictionary of elements
|
|
140
143
|
function addNewElementToResult(element, elementName, elementNameWithDots, path) {
|
|
141
144
|
if (newFlatElements[elementName]) {
|
|
@@ -25,7 +25,6 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
25
25
|
// collect in this variable all the newly exposed types
|
|
26
26
|
const exposedStructTypes = [];
|
|
27
27
|
const schemas = Object.create(null);
|
|
28
|
-
|
|
29
28
|
// walk through the definitions of the given CSN and expose types where needed
|
|
30
29
|
forEachDefinition(csn, (def, defName, propertyName, path) => {
|
|
31
30
|
// we do expose types only for definition from inside services
|
|
@@ -33,11 +32,11 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
33
32
|
if (serviceName) {
|
|
34
33
|
if (['type', 'entity', 'view'].includes(def.kind)) {
|
|
35
34
|
forEachMember(def, (element, elementName, propertyName, path) => {
|
|
36
|
-
if (
|
|
35
|
+
if (['elements', 'params'].includes(propertyName)) {
|
|
37
36
|
const artificialtName = `${isMultiSchema ?
|
|
38
37
|
defNameWithoutServiceOrContextName(defName, serviceName)
|
|
39
38
|
: defNameWithoutServiceOrContextName(defName, serviceName).replace(/\./g, '_')}_${elementName}`;
|
|
40
|
-
exposeTypeOf(element, elementName, defName, serviceName, artificialtName, path);
|
|
39
|
+
exposeTypeOf(element, element.key || propertyName === 'params', elementName, defName, serviceName, artificialtName, path);
|
|
41
40
|
}
|
|
42
41
|
}, path);
|
|
43
42
|
}
|
|
@@ -64,11 +63,11 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
64
63
|
* @param {string} artificialName
|
|
65
64
|
* @param {CSN.Path} path
|
|
66
65
|
*/
|
|
67
|
-
function exposeTypeOf(node, memberName, defName, service, artificialName, path) {
|
|
66
|
+
function exposeTypeOf(node, isKey, memberName, defName, service, artificialName, path) {
|
|
68
67
|
if (isArrayed(node))
|
|
69
|
-
exposeArrayOfTypeOf(node, memberName, defName, service, artificialName, path);
|
|
68
|
+
exposeArrayOfTypeOf(node, isKey, memberName, defName, service, artificialName, path);
|
|
70
69
|
else if (csnUtils.isStructured(node))
|
|
71
|
-
exposeStructTypeOf(node, memberName, defName, service, artificialName, path);
|
|
70
|
+
exposeStructTypeOf(node, isKey, memberName, defName, service, artificialName, path);
|
|
72
71
|
}
|
|
73
72
|
|
|
74
73
|
/**
|
|
@@ -90,12 +89,12 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
90
89
|
function exposeTypesOfAction(action, actionName, defName, service, path) {
|
|
91
90
|
if (action.returns) {
|
|
92
91
|
const artificialName = `return_${actionName.replace(/\./g, '_')}`;
|
|
93
|
-
exposeTypeOf(action.returns, actionName, defName, service, artificialName, path.concat(['returns']));
|
|
92
|
+
exposeTypeOf(action.returns, false, actionName, defName, service, artificialName, path.concat(['returns']));
|
|
94
93
|
}
|
|
95
94
|
|
|
96
95
|
action.params && Object.entries(action.params).forEach(([paramName, param]) => {
|
|
97
96
|
const artificialName = `param_${actionName.replace(/\./g, '_')}_${paramName}`;
|
|
98
|
-
exposeTypeOf(param, actionName, defName, service, artificialName, path.concat(['params', paramName]));
|
|
97
|
+
exposeTypeOf(param, false, actionName, defName, service, artificialName, path.concat(['params', paramName]));
|
|
99
98
|
});
|
|
100
99
|
}
|
|
101
100
|
|
|
@@ -108,9 +107,11 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
108
107
|
* @param {String} service
|
|
109
108
|
* @param {String} artificialName
|
|
110
109
|
*/
|
|
111
|
-
function exposeStructTypeOf(node, memberName, defName, service, artificialName, path, parentName) {
|
|
110
|
+
function exposeStructTypeOf(node, isKey, memberName, defName, service, artificialName, path, parentName) {
|
|
111
|
+
if (node.items) exposeStructTypeOf(node.items, isKey, memberName, defName, service, artificialName, path);
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
// start conservative, assume we're in a named type
|
|
114
|
+
let isAnonymous = false;
|
|
114
115
|
|
|
115
116
|
if (isExposableStructure(node)) {
|
|
116
117
|
let typeDef = node.type ? csnUtils.getCsnDef(node.type) : /* structure|anonymous type */ node;
|
|
@@ -124,6 +125,11 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
124
125
|
// and the new target. Consequently, we now have both type and elements properties in this case, and the elements should be taken as a priority
|
|
125
126
|
// as the correct target is there and no longer in the type definition
|
|
126
127
|
let newTypeElements = (node.type && node.elements) ? node.elements : typeDef.elements;
|
|
128
|
+
// if node and typeDef are identical, we're anonymous
|
|
129
|
+
isAnonymous = node === typeDef;
|
|
130
|
+
// if we've left the anonymous world, we're no longer in a key def
|
|
131
|
+
if (!isAnonymous)
|
|
132
|
+
isKey = false;
|
|
127
133
|
|
|
128
134
|
let newType = exposeStructType(newTypeFullName, newTypeElements, memberName, path);
|
|
129
135
|
if (!newType) {
|
|
@@ -138,6 +144,7 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
138
144
|
newType.elements && Object.entries(newType.elements).forEach(([elemName, newElem]) => {
|
|
139
145
|
if (node.elements && node.elements[elemName].$location) setProp(newElem, '$location', node.elements[elemName].$location);
|
|
140
146
|
exposeStructTypeOf(newElem,
|
|
147
|
+
isKey,
|
|
141
148
|
memberName,
|
|
142
149
|
typeDef.kind === 'type' ? node.type : defName,
|
|
143
150
|
service,
|
|
@@ -251,6 +258,9 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
251
258
|
error(null, path, `"${elemName}": Element name conflicts with existing element`);
|
|
252
259
|
}
|
|
253
260
|
let cloned = cloneCsn(element, options);
|
|
261
|
+
// if this was an anonymous sub element of a key, mark it as not nullable
|
|
262
|
+
if(isAnonymous && isKey && !cloned.key && cloned.notNull === undefined)
|
|
263
|
+
cloned.notNull = true;
|
|
254
264
|
type.elements[elemName] = cloned;
|
|
255
265
|
});
|
|
256
266
|
|
|
@@ -265,12 +275,12 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
265
275
|
|
|
266
276
|
// If a member is of type "array of <named type|anonymous type>", we expose the arrayed type,
|
|
267
277
|
// like we expose structures in structured mode
|
|
268
|
-
function exposeArrayOfTypeOf(node, memberName, defName, service, artificialName, path) {
|
|
278
|
+
function exposeArrayOfTypeOf(node, isKey, memberName, defName, service, artificialName, path) {
|
|
269
279
|
// if anonymously defined in place -> we always expose the type
|
|
270
280
|
// this would be definition like 'elem: array of { ... }'
|
|
271
281
|
// and we use the artificial name for the new type name
|
|
272
282
|
if (node.items && !node.type) {
|
|
273
|
-
exposeStructTypeOf(node.items, memberName, defName, service, artificialName, path.concat('items'));
|
|
283
|
+
exposeStructTypeOf(node.items, isKey, memberName, defName, service, artificialName, path.concat('items'));
|
|
274
284
|
}
|
|
275
285
|
// we can have both of the 'type' and 'items' in the cases:
|
|
276
286
|
// 1. 'elem: Foo' and 'type Foo: array of Baz' and 'type Baz: { ... }'
|
|
@@ -61,6 +61,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
61
61
|
recurseElements,
|
|
62
62
|
renameAnnotation,
|
|
63
63
|
setAnnotation,
|
|
64
|
+
resetAnnotation,
|
|
64
65
|
expandStructsInExpression,
|
|
65
66
|
};
|
|
66
67
|
|
|
@@ -378,9 +379,10 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
378
379
|
*
|
|
379
380
|
* @param {CSN.Artifact} node
|
|
380
381
|
* @param {WeakMap} [resolved] WeakMap containing already resolved refs
|
|
382
|
+
* @param {boolean} [keepLocalized=false] Wether to clone .localized from a type def
|
|
381
383
|
* @returns {void}
|
|
382
384
|
*/
|
|
383
|
-
function toFinalBaseType(node, resolved) {
|
|
385
|
+
function toFinalBaseType(node, resolved, keepLocalized=false) {
|
|
384
386
|
// Nothing to do if no type (or if array/struct type)
|
|
385
387
|
if (!node || !node.type) return;
|
|
386
388
|
// In case of a ref -> Follow the ref
|
|
@@ -440,6 +442,8 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
440
442
|
Object.assign(node, { scale: typeDef.scale });
|
|
441
443
|
if (node.srid === undefined && typeDef.srid !== undefined)
|
|
442
444
|
Object.assign(node, { srid: typeDef.srid });
|
|
445
|
+
if (keepLocalized && node.localized === undefined && typeDef.localized !== undefined)
|
|
446
|
+
Object.assign(node, { localized: typeDef.localized });
|
|
443
447
|
node.type = typeDef.type;
|
|
444
448
|
toFinalBaseType(node);
|
|
445
449
|
}
|
|
@@ -920,6 +924,34 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
920
924
|
node[name] = value;
|
|
921
925
|
}
|
|
922
926
|
|
|
927
|
+
/**
|
|
928
|
+
* Assigns unconditionally annotation to a node, which means it overwrites already existing annotation assignment.
|
|
929
|
+
* Overwritting is when the assignment differs from undefined and null, also when differs from the already set value.
|
|
930
|
+
* Setting new assignment results false as return value and overwriting - true.
|
|
931
|
+
*
|
|
932
|
+
* @param {object} node Assignee
|
|
933
|
+
* @param {string} name Annotation name
|
|
934
|
+
* @param {any} value Annotation value
|
|
935
|
+
* @param {function} info function that reports info messages
|
|
936
|
+
* @param {CSN.Path} path location of the warning
|
|
937
|
+
* @returns {boolean} wasOverwritten true when the annotation was overwritten
|
|
938
|
+
*/
|
|
939
|
+
function resetAnnotation(node, name, value, info, path) {
|
|
940
|
+
if (!name.startsWith('@')) {
|
|
941
|
+
throw Error('Annotation name should start with "@": ' + name);
|
|
942
|
+
}
|
|
943
|
+
if (value === undefined) {
|
|
944
|
+
throw Error('Annotation value must not be undefined');
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
const wasOverwritten = node[name] !== undefined && node[name] !== null && node[name] !== value;
|
|
948
|
+
const oldValue = node[name];
|
|
949
|
+
node[name] = value;
|
|
950
|
+
if(wasOverwritten)
|
|
951
|
+
info(null, path, { anno: name, prop: value, otherprop: oldValue },
|
|
952
|
+
`Value $(OTHERPROP) of annotation $(ANNO) is overwritten with new value $(PROP)`);
|
|
953
|
+
return wasOverwritten;
|
|
954
|
+
}
|
|
923
955
|
|
|
924
956
|
/*
|
|
925
957
|
Resolve the type of an artifact
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { setProp, forEachGeneric, forEachDefinition, isBetaEnabled } = require('../base/model');
|
|
4
|
-
var {
|
|
4
|
+
var { makeMessageFunction } = require('../base/messages');
|
|
5
5
|
const { recompileX } = require('../compiler/index');
|
|
6
6
|
var { linkToOrigin } = require('../compiler/shared');
|
|
7
7
|
const {compactModel, compactExpr} = require('../json/to-csn');
|
|
@@ -14,7 +14,9 @@ const internalArtifactKinds = ['builtin'/*, '$parameters'*/, 'param'];
|
|
|
14
14
|
function translateAssocsToJoinsCSN(csn, options){
|
|
15
15
|
timetrace.start('Recompiling model');
|
|
16
16
|
// Do not re-complain about localized
|
|
17
|
-
const
|
|
17
|
+
const compileOptions = { ...options, $skipNameCheck: true };
|
|
18
|
+
delete compileOptions.csnFlavor;
|
|
19
|
+
const model = recompileX(csn, compileOptions);
|
|
18
20
|
timetrace.stop();
|
|
19
21
|
timetrace.start('Translating associations to joins');
|
|
20
22
|
translateAssocsToJoins(model, options);
|
|
@@ -42,9 +44,9 @@ function translateAssocsToJoinsCSN(csn, options){
|
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
// If A2J reports error - end! Continuing with a broken CSN makes no sense
|
|
45
|
-
|
|
47
|
+
makeMessageFunction(model, options).throwWithError();
|
|
46
48
|
// FIXME: Move this somewhere more appropriate
|
|
47
|
-
const compact = compactModel(model,
|
|
49
|
+
const compact = compactModel(model, compileOptions);
|
|
48
50
|
return compact;
|
|
49
51
|
}
|
|
50
52
|
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { forEachDefinition } = require('../base/model');
|
|
4
|
+
const {
|
|
5
|
+
applyTransformations,
|
|
6
|
+
cloneCsn,
|
|
7
|
+
getUtils,
|
|
8
|
+
isBuiltinType,
|
|
9
|
+
} = require('../model/csnUtils');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Loop through a universal CSN and enrich it with the properties
|
|
13
|
+
* from the source definition - modifies the input model in-place
|
|
14
|
+
*
|
|
15
|
+
* @param {CSN.Model} csn
|
|
16
|
+
* @param {CSN.Options} options
|
|
17
|
+
*/
|
|
18
|
+
module.exports = function(csn, options) {
|
|
19
|
+
let { getOrigin, getFinalType, getFinalTypeDef } = getUtils(csn);
|
|
20
|
+
// User-defined structured types do not have the elements propagated any longer
|
|
21
|
+
// if there is no association among the elements. For that reason,
|
|
22
|
+
// as a first step propagate the elements of these
|
|
23
|
+
forEachDefinition(csn, (def) => {
|
|
24
|
+
if (def.kind === 'type' && def.type && !def.elements) {
|
|
25
|
+
const finalType = getFinalType(def.type);
|
|
26
|
+
if (isBuiltinType(finalType)) return;
|
|
27
|
+
const finalTypeDef = getFinalTypeDef(def.type);
|
|
28
|
+
if (finalTypeDef.elements)
|
|
29
|
+
def.elements = cloneCsn(finalTypeDef.elements, options);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// as a second step, loop through all the $origin properties in the model
|
|
34
|
+
// and propagate the properties from the origin definition
|
|
35
|
+
applyTransformations(csn, {
|
|
36
|
+
'$origin': (node, _$orign, $originValue, _path, parent, propName) => {
|
|
37
|
+
if (!node.kind) { // we do not want to replace whole definitions
|
|
38
|
+
if (Array.isArray($originValue))
|
|
39
|
+
propagatePropsFromOrigin(node, propName, parent);
|
|
40
|
+
else if ($originValue.$origin && Array.isArray($originValue.$origin)) {
|
|
41
|
+
// cover the case of query entity elements where we have own and ihnerited attributes/annotations
|
|
42
|
+
propagatePropsFromOrigin($originValue, propName, parent);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}, undefined, undefined, options);
|
|
48
|
+
|
|
49
|
+
function propagatePropsFromOrigin(member, memberName, construct) {
|
|
50
|
+
// TODO: shall the $origin be kept as part of the element?
|
|
51
|
+
const origin = getOrigin(member);
|
|
52
|
+
if (origin.kind) return;
|
|
53
|
+
if (member.elements && origin.type) {
|
|
54
|
+
delete member.$origin;
|
|
55
|
+
member.type = origin.type;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
let newMember = cloneCsn(origin, options);
|
|
59
|
+
// keep targets and keys of assoc, if it was redirected
|
|
60
|
+
if (origin.type === 'cds.Association') {
|
|
61
|
+
newMember.target = member.target || newMember.target;
|
|
62
|
+
newMember.keys = member.keys || newMember.keys;
|
|
63
|
+
}
|
|
64
|
+
// TODO: check if this works fine for items/returns/actions
|
|
65
|
+
construct[memberName] = newMember;
|
|
66
|
+
}
|
|
67
|
+
}
|