@sap/cds-compiler 2.13.6 → 2.15.4
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 +128 -4
- package/bin/cdsc.js +112 -37
- package/lib/api/main.js +20 -22
- package/lib/api/options.js +2 -3
- package/lib/api/validate.js +6 -6
- package/lib/base/message-registry.js +92 -17
- package/lib/base/messages.js +85 -64
- package/lib/base/optionProcessorHelper.js +19 -0
- package/lib/checks/annotationsOData.js +11 -32
- package/lib/checks/arrayOfs.js +1 -34
- package/lib/checks/validator.js +2 -4
- package/lib/compiler/assert-consistency.js +1 -0
- package/lib/compiler/base.js +1 -0
- package/lib/compiler/builtins.js +11 -0
- package/lib/compiler/checks.js +22 -70
- package/lib/compiler/define.js +59 -11
- package/lib/compiler/extend.js +20 -3
- package/lib/compiler/finalize-parse-cdl.js +26 -20
- package/lib/compiler/index.js +75 -26
- package/lib/compiler/populate.js +6 -5
- package/lib/compiler/propagator.js +4 -1
- package/lib/compiler/resolve.js +104 -16
- package/lib/compiler/shared.js +61 -27
- package/lib/compiler/tweak-assocs.js +7 -1
- package/lib/edm/annotations/genericTranslation.js +93 -21
- package/lib/edm/csn2edm.js +216 -98
- package/lib/edm/edm.js +305 -226
- package/lib/edm/edmPreprocessor.js +499 -423
- package/lib/edm/edmUtils.js +22 -22
- package/lib/gen/Dictionary.json +98 -22
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -1
- package/lib/gen/languageParser.js +4636 -4368
- package/lib/json/csnVersion.js +10 -11
- package/lib/json/from-csn.js +3 -2
- package/lib/json/to-csn.js +0 -2
- package/lib/language/docCommentParser.js +2 -2
- package/lib/language/genericAntlrParser.js +47 -2
- package/lib/language/language.g4 +59 -27
- package/lib/main.d.ts +19 -1
- package/lib/main.js +6 -0
- package/lib/model/csnRefs.js +33 -6
- package/lib/model/csnUtils.js +193 -75
- package/lib/model/enrichCsn.js +1 -0
- package/lib/model/revealInternalProperties.js +2 -2
- package/lib/modelCompare/compare.js +6 -6
- package/lib/optionProcessor.js +62 -26
- package/lib/render/toCdl.js +844 -679
- package/lib/render/toHdbcds.js +189 -243
- package/lib/render/toSql.js +180 -198
- package/lib/render/utils/common.js +131 -15
- package/lib/transform/db/.eslintrc.json +1 -1
- package/lib/transform/db/associations.js +2 -2
- package/lib/transform/db/constraints.js +3 -1
- package/lib/transform/db/expansion.js +15 -10
- package/lib/transform/db/flattening.js +95 -68
- package/lib/transform/db/transformExists.js +7 -7
- package/lib/transform/db/views.js +6 -3
- package/lib/transform/forHanaNew.js +43 -26
- package/lib/transform/forOdataNew.js +43 -42
- package/lib/transform/localized.js +12 -7
- package/lib/transform/odata/toFinalBaseType.js +8 -6
- package/lib/transform/odata/typesExposure.js +145 -197
- package/lib/transform/transformUtilsNew.js +9 -12
- package/lib/transform/translateAssocsToJoins.js +5 -1
- package/lib/transform/universalCsn/coreComputed.js +5 -3
- package/lib/transform/universalCsn/universalCsnEnricher.js +27 -5
- package/lib/utils/moduleResolve.js +13 -6
- package/package.json +1 -1
- package/share/messages/message-explanations.json +2 -1
- package/share/messages/syntax-expected-integer.md +37 -0
- package/lib/transform/odata/attachPath.js +0 -96
- package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
- package/lib/transform/odata/generateForeignKeyElements.js +0 -261
- package/lib/transform/odata/referenceFlattener.js +0 -296
- package/lib/transform/odata/sortByAssociationDependency.js +0 -105
- package/lib/transform/odata/structuralPath.js +0 -72
- package/lib/transform/odata/structureFlattener.js +0 -171
|
@@ -28,13 +28,18 @@ const extensions = [ '.cds', '.csn', '.json' ];
|
|
|
28
28
|
* - Why a global? The Umbrella could pass it as an option.
|
|
29
29
|
*
|
|
30
30
|
* @param {string} modulePath
|
|
31
|
+
* @param {CSN.Options} options
|
|
31
32
|
* @returns {string}
|
|
32
33
|
*/
|
|
33
|
-
function adaptCdsModule(modulePath) {
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
function adaptCdsModule(modulePath, options = {}) {
|
|
35
|
+
if (modulePath.startsWith( '@sap/cds/' )) {
|
|
36
|
+
if (options.cdsHome)
|
|
37
|
+
return options.cdsHome + modulePath.slice(8);
|
|
36
38
|
// eslint-disable-next-line
|
|
37
|
-
|
|
39
|
+
if (global['cds'] && global['cds'].home)
|
|
40
|
+
// eslint-disable-next-line
|
|
41
|
+
return global['cds'].home + modulePath.slice(8);
|
|
42
|
+
}
|
|
38
43
|
return modulePath;
|
|
39
44
|
}
|
|
40
45
|
|
|
@@ -42,6 +47,7 @@ function adaptCdsModule(modulePath) {
|
|
|
42
47
|
* @param {object} dep
|
|
43
48
|
* @param {object} fileCache
|
|
44
49
|
* @param {CSN.Options} options
|
|
50
|
+
* @param {object} messageFunctions
|
|
45
51
|
*/
|
|
46
52
|
function resolveModule( dep, fileCache, options, messageFunctions ) {
|
|
47
53
|
const _fs = cdsFs(fileCache, options.traceFs);
|
|
@@ -58,7 +64,7 @@ function resolveModule( dep, fileCache, options, messageFunctions ) {
|
|
|
58
64
|
realpath: _fs.realpath,
|
|
59
65
|
};
|
|
60
66
|
return new Promise( (fulfill, reject) => {
|
|
61
|
-
const lookupPath = adaptCdsModule(dep.module);
|
|
67
|
+
const lookupPath = adaptCdsModule(dep.module, options);
|
|
62
68
|
resolveCDS( lookupPath, opts, (err, res) => {
|
|
63
69
|
// console.log('RESOLVE', dep, res, err)
|
|
64
70
|
if (err) {
|
|
@@ -105,6 +111,7 @@ function resolveModule( dep, fileCache, options, messageFunctions ) {
|
|
|
105
111
|
* @param {object} dep
|
|
106
112
|
* @param {object} fileCache
|
|
107
113
|
* @param {CSN.Options} options
|
|
114
|
+
* @param {object} messageFunctions
|
|
108
115
|
*/
|
|
109
116
|
function resolveModuleSync( dep, fileCache, options, messageFunctions ) {
|
|
110
117
|
const _fs = cdsFs(fileCache, options.traceFs);
|
|
@@ -118,7 +125,7 @@ function resolveModuleSync( dep, fileCache, options, messageFunctions ) {
|
|
|
118
125
|
|
|
119
126
|
let result = null;
|
|
120
127
|
let error = null;
|
|
121
|
-
const lookupPath = adaptCdsModule(dep.module);
|
|
128
|
+
const lookupPath = adaptCdsModule(dep.module, options);
|
|
122
129
|
|
|
123
130
|
resolveCDS( lookupPath, opts, (err, res) => {
|
|
124
131
|
if (err)
|
package/package.json
CHANGED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# syntax-expected-integer
|
|
2
|
+
|
|
3
|
+
The compiler expects a safe integer here.
|
|
4
|
+
The last safe Integer is `2^53 - 1` or `9007199254740991`.
|
|
5
|
+
|
|
6
|
+
A safe integer is an integer that
|
|
7
|
+
|
|
8
|
+
- can be exactly represented as an IEEE-754 double precision number, and
|
|
9
|
+
- whose IEEE-754 representation cannot be the result of rounding any
|
|
10
|
+
other integer to fit the IEEE-754 representation.
|
|
11
|
+
|
|
12
|
+
The message's severity is `Error`.
|
|
13
|
+
|
|
14
|
+
## Example
|
|
15
|
+
|
|
16
|
+
Erroneous code example:
|
|
17
|
+
|
|
18
|
+
```cdl
|
|
19
|
+
type LengthIsUnsafe : String(9007199254740992);
|
|
20
|
+
type NotAnInteger : String(42.1);
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
In the above example, the string length for the type `LengthIsUnsafe` is not a
|
|
24
|
+
safe Integer. It is too large.
|
|
25
|
+
Likewise, the string length for the type `NotAnInteger` is a decimal.
|
|
26
|
+
|
|
27
|
+
## How to Fix
|
|
28
|
+
|
|
29
|
+
To fix the issue, you have to provide a safe integer:
|
|
30
|
+
|
|
31
|
+
```cdl
|
|
32
|
+
type LengthIsSafe : String(9007199254740991);
|
|
33
|
+
type AnInteger : String(42);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
If not feasible, a string representation of the number needs to be used,
|
|
37
|
+
e.g. in annotation values.
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
// The module traverses over a CSN or partial one and sets $path on the non-structural nodes and references.
|
|
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
|
-
}
|
|
30
|
-
|
|
31
|
-
function attachPath(csn) {
|
|
32
|
-
traverseDict(csn.definitions, ['definitions']);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function attachPathOnPartialCSN(csnPart, pathPrefix) {
|
|
36
|
-
if(Array.isArray(csnPart))
|
|
37
|
-
traverseArray(csnPart, pathPrefix);
|
|
38
|
-
else
|
|
39
|
-
traverseDict(csnPart, pathPrefix);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function traverseRef(obj, path) {
|
|
43
|
-
if(!obj) return;
|
|
44
|
-
setPath(obj, path);
|
|
45
|
-
traverseArray(obj, path);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function traverseArray(obj, path) {
|
|
49
|
-
if(!Array.isArray(obj)) return;
|
|
50
|
-
obj.forEach( ( element, index ) => traverseTyped(element, path.concat(index)));
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function traverseDict(obj, path) {
|
|
54
|
-
if(!obj || typeof obj !== 'object') return;
|
|
55
|
-
forAllEnumerableProperties(obj, name => {
|
|
56
|
-
const ipath = path.concat(name);
|
|
57
|
-
setPath(obj[name], ipath);
|
|
58
|
-
traverseTyped(obj[name], ipath);
|
|
59
|
-
})
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function traverseDictArray(obj, path) {
|
|
63
|
-
if(!obj || typeof obj !== 'object') return;
|
|
64
|
-
forAllEnumerableProperties(obj, name => {
|
|
65
|
-
const ipath = path.concat(name);
|
|
66
|
-
setPath(obj[name], ipath);
|
|
67
|
-
traverseArray(obj[name], ipath);
|
|
68
|
-
})
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function traverseTyped(obj, path) {
|
|
72
|
-
if(!obj || typeof obj !== 'object') return;
|
|
73
|
-
forAllEnumerableProperties(obj, name => {
|
|
74
|
-
if(name[0]==='@') return; // skip annotations
|
|
75
|
-
const func = structuralNodeHandlers[name];
|
|
76
|
-
if(func)
|
|
77
|
-
func(obj[name], path.concat(name));
|
|
78
|
-
else if(path[path.length-2] === 'columns')
|
|
79
|
-
traverseDictArray(obj[name], path.concat(name)); // for columns
|
|
80
|
-
})
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function setPath(obj, path) {
|
|
84
|
-
if(!obj || typeof obj !== 'object') return;
|
|
85
|
-
if(path.length>0)
|
|
86
|
-
Object.defineProperty( obj, '$path', { value: path, configurable: true, writable: true, enumerable: false } );
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function forAllEnumerableProperties(obj, callback) {
|
|
90
|
-
Object.keys(obj).forEach(callback);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
module.exports = {
|
|
94
|
-
attachPath,
|
|
95
|
-
attachPathOnPartialCSN,
|
|
96
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { forEachManagedAssociation } = require('./utils');
|
|
4
|
-
const { attachPath, attachPathOnPartialCSN } = require('./attachPath');
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* This module runs through the model and for each managed association in it,
|
|
8
|
-
* in case the foreign keys are structured, it is expanding them. Example:
|
|
9
|
-
* entity A {
|
|
10
|
-
* toB: association to B { stru };
|
|
11
|
-
* } // -> CSN: keys:[ { ref:['stru'] } ]
|
|
12
|
-
*
|
|
13
|
-
* entity B {
|
|
14
|
-
* stru: {
|
|
15
|
-
* subid: Integer;
|
|
16
|
-
* }
|
|
17
|
-
* }
|
|
18
|
-
* after expand -> keys:[ { ref: ['stru_subid'] } ]
|
|
19
|
-
*/
|
|
20
|
-
module.exports = function (csn, referenceFlattener, csnUtils, isExternalServiceMember) {
|
|
21
|
-
|
|
22
|
-
forEachManagedAssociation(csn, (element) => {
|
|
23
|
-
if (element.keys) {
|
|
24
|
-
expandStructuredKeysForAssociation(element, referenceFlattener);
|
|
25
|
-
}
|
|
26
|
-
}, isExternalServiceMember);
|
|
27
|
-
|
|
28
|
-
// update paths and resolve references
|
|
29
|
-
attachPath(csn);
|
|
30
|
-
referenceFlattener.resolveAllReferences(csn, csnUtils.inspectRef, csnUtils.isStructured);
|
|
31
|
-
|
|
32
|
-
function expandStructuredKeysForAssociation(assoc, referenceFlattener) {
|
|
33
|
-
let newKeys = [];
|
|
34
|
-
for (let key of assoc.keys) {
|
|
35
|
-
// when are assigned $paths and when not???
|
|
36
|
-
let paths = key.$paths;
|
|
37
|
-
if (paths) {
|
|
38
|
-
let lastPath = paths[paths.length - 1];
|
|
39
|
-
let generatedElements = referenceFlattener.getGeneratedElementsForPath(lastPath);
|
|
40
|
-
if (generatedElements) {
|
|
41
|
-
generatedElements.forEach(elementName => {
|
|
42
|
-
let newRef = { ref: [elementName] };
|
|
43
|
-
if (key.as) {
|
|
44
|
-
newRef.as = elementName.replace(key.ref[0], key.as);
|
|
45
|
-
}
|
|
46
|
-
newKeys.push(newRef);
|
|
47
|
-
})
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
newKeys.push(key);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (newKeys.length) {
|
|
55
|
-
attachPathOnPartialCSN(newKeys, assoc.$path.concat('keys'));
|
|
56
|
-
assoc.keys = newKeys;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* The module handles the processing of foreign key for managed associations.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const { copyAnnotations } = require('../../model/csnUtils');
|
|
8
|
-
const sortByAssociationDependency = require('./sortByAssociationDependency');
|
|
9
|
-
const { flattenStructure } = require('./structureFlattener');
|
|
10
|
-
const { setProp } = require('../../base/model');
|
|
11
|
-
const { implicitAs } = require('../../model/csnRefs');
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
*
|
|
15
|
-
* @param {CSN.Model} csn
|
|
16
|
-
* @param {*} options
|
|
17
|
-
* @param {*} referenceFlattener
|
|
18
|
-
* @param {*} csnUtils
|
|
19
|
-
* @param {object} error;
|
|
20
|
-
*/
|
|
21
|
-
module.exports = function (csn, options, referenceFlattener, csnUtils, error, isExternalServiceMember) {
|
|
22
|
-
|
|
23
|
-
const structuredOData = options.toOdata.odataFormat === 'structured' && options.toOdata.version === 'v4';
|
|
24
|
-
const flatKeys = !structuredOData || (structuredOData && options.toOdata.odataForeignKeys);
|
|
25
|
-
|
|
26
|
-
// sort all associations by their dependencies
|
|
27
|
-
const sortedAssociations = sortByAssociationDependency(csn, referenceFlattener, isExternalServiceMember);
|
|
28
|
-
|
|
29
|
-
// generate foreign keys
|
|
30
|
-
processSortedAssociations(sortedAssociations, flatKeys);
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
function processSortedAssociations(sortedAssociations, flatKeys,) {
|
|
34
|
-
// The map will collect all generated foreign key names for the specific path
|
|
35
|
-
let generatedForeignKeyNamesForPath = Object.create(null); // map<path,[key-name]>
|
|
36
|
-
|
|
37
|
-
sortedAssociations.forEach(item => {
|
|
38
|
-
const { definitionName, structuralNodeName, elementName, element, parent, path } = item;
|
|
39
|
-
|
|
40
|
-
if (csnUtils.isManagedAssociation(element) && element.keys) {
|
|
41
|
-
if (flatKeys) // tackling the ref value in assoc.keys
|
|
42
|
-
takeoverForeignKeysOfTargetAssociations(element, path, generatedForeignKeyNamesForPath);
|
|
43
|
-
// TODO: move in separate function
|
|
44
|
-
fixCardinality(element);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
let arrayOfGeneratedForeignKeyNames = generateForeignKeys(definitionName, structuralNodeName, elementName, element, parent, path);
|
|
48
|
-
generatedForeignKeyNamesForPath[item.path.join('/')] = arrayOfGeneratedForeignKeyNames;
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* if a key is an association and it poins to another association,
|
|
55
|
-
* the foreign keys of the target association become primary keys
|
|
56
|
-
* in the current association
|
|
57
|
-
*/
|
|
58
|
-
function takeoverForeignKeysOfTargetAssociations(assoc, path, generatedForeignKeyNamesForPath) {
|
|
59
|
-
let newResult = [];
|
|
60
|
-
assoc.keys.forEach( (key, keyIndex) => {
|
|
61
|
-
let keyPath = path.concat('keys', keyIndex);
|
|
62
|
-
let resolved = csnUtils.inspectRef(keyPath)
|
|
63
|
-
let targetElement = resolved.art;
|
|
64
|
-
if (targetElement) {
|
|
65
|
-
if (csnUtils.isAssociation(targetElement.type)) {
|
|
66
|
-
// association key
|
|
67
|
-
expandAssociationKey(key);
|
|
68
|
-
} else {
|
|
69
|
-
newResult.push(key);
|
|
70
|
-
}
|
|
71
|
-
} else {
|
|
72
|
-
// target element does not exist, warning is already reported, pass the key anyway
|
|
73
|
-
newResult.push(key);
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
function expandAssociationKey(key) {
|
|
78
|
-
let paths = key.$paths;
|
|
79
|
-
if (!paths) return;
|
|
80
|
-
let lastPath = paths[paths.length - 1];
|
|
81
|
-
let transitionPath = referenceFlattener.getElementTransition(lastPath)
|
|
82
|
-
if (transitionPath)
|
|
83
|
-
lastPath = transitionPath;
|
|
84
|
-
let generatedKeys = generatedForeignKeyNamesForPath[lastPath.join('/')];
|
|
85
|
-
if (!generatedKeys) return;
|
|
86
|
-
generatedKeys.forEach(fkName => {
|
|
87
|
-
let newFkRef = { ref: [fkName] };
|
|
88
|
-
if (key.as) {
|
|
89
|
-
let alias = fkName.replace(key.ref[0], key.as);
|
|
90
|
-
setProp(newFkRef, 'as', alias);
|
|
91
|
-
}
|
|
92
|
-
newResult.push(newFkRef);
|
|
93
|
-
})
|
|
94
|
-
} // expandAssociationKey
|
|
95
|
-
|
|
96
|
-
assoc.keys = newResult;
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function fixCardinality(assoc) {
|
|
101
|
-
if (assoc.notNull) {
|
|
102
|
-
if (!assoc.cardinality) {
|
|
103
|
-
assoc.cardinality = {};
|
|
104
|
-
}
|
|
105
|
-
if (assoc.cardinality.min === undefined) {
|
|
106
|
-
assoc.cardinality.min = 1;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Generates foreign keys and returns their names as an array
|
|
113
|
-
*/
|
|
114
|
-
function generateForeignKeys(definitionName, structuralNodeName, assocName, assoc, parent, path) {
|
|
115
|
-
let foreignKeyElements = Object.create(null);
|
|
116
|
-
|
|
117
|
-
// First, loop over the keys array of the association and generate the FKs.
|
|
118
|
-
// The result of all the FKs for the given association is accumulated
|
|
119
|
-
// in the 'foreignKeyElements' dictionary
|
|
120
|
-
assoc.keys.forEach( (key, keyIndex) => {
|
|
121
|
-
let keyPath = path.concat('keys', keyIndex);
|
|
122
|
-
|
|
123
|
-
let foreignKeyElementsForKey = generateForeignKeysForRef(assoc, assocName, key, keyPath);
|
|
124
|
-
Object.assign(foreignKeyElements, foreignKeyElementsForKey);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
// After that, add the new elements to the definition.
|
|
128
|
-
// At the same time:
|
|
129
|
-
// -> Check for coliding element's name
|
|
130
|
-
// &
|
|
131
|
-
// -> Propagate annotations from the association
|
|
132
|
-
if (parent.items) // proceed to items of such
|
|
133
|
-
parent = parent.items;
|
|
134
|
-
if (parent.returns)
|
|
135
|
-
parent = parent.returns.items || parent.returns;
|
|
136
|
-
|
|
137
|
-
const dictionary = parent[structuralNodeName];
|
|
138
|
-
let currElementsNames = Object.keys(parent[structuralNodeName]);
|
|
139
|
-
for (const [foreignKeyName, foreignKey] of Object.entries(foreignKeyElements)) {
|
|
140
|
-
copyAnnotations(assoc, foreignKey, true);
|
|
141
|
-
// Insert artificial element into artifact, with all cross-links
|
|
142
|
-
if (dictionary[foreignKeyName]) {
|
|
143
|
-
if (!(dictionary[foreignKeyName]['@odata.foreignKey4'] || isDeepEqual(dictionary[foreignKeyName], foreignKey))) {
|
|
144
|
-
const path = dictionary[foreignKeyName].$path;
|
|
145
|
-
error(null, path, { name: foreignKeyName, art: assocName }, 'Generated foreign key element $(NAME) for association $(ART) conflicts with existing element');
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// make sure the generated foreign key(s) is added right after the association (that it belongs to) in the elements dictionary
|
|
151
|
-
const assocIndex = currElementsNames.findIndex(elemName => elemName === assocName);
|
|
152
|
-
// if (flatKeys)
|
|
153
|
-
currElementsNames.splice(assocIndex + 1, 0, ...Object.keys(foreignKeyElements));
|
|
154
|
-
|
|
155
|
-
parent[structuralNodeName] = currElementsNames.reduce((previous, name) => {
|
|
156
|
-
previous[name] = dictionary[name] || foreignKeyElements[name];
|
|
157
|
-
return previous;
|
|
158
|
-
}, Object.create(null));
|
|
159
|
-
|
|
160
|
-
return Object.keys(foreignKeyElements);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// FIXME: Very similar code to
|
|
164
|
-
// transformUtilsNew::getForeignKeyArtifact & createForeignKeyElement
|
|
165
|
-
// Can this be streamlined?
|
|
166
|
-
function generateForeignKeysForRef(assoc, assocName, foreignKeyRef, pathInKeysArr, foreignKey4 = assocName) {
|
|
167
|
-
// in structured OData, might be more than one generated FKs
|
|
168
|
-
let generatedFks = Object.create(null);
|
|
169
|
-
const fkArtifact = csnUtils.inspectRef(pathInKeysArr).art;
|
|
170
|
-
if(fkArtifact) {
|
|
171
|
-
if (csnUtils.isStructured(fkArtifact)) {
|
|
172
|
-
processStucturedKey(fkArtifact, assocName, foreignKeyRef);
|
|
173
|
-
} else {
|
|
174
|
-
// built-in
|
|
175
|
-
const foreignKeyElementName = `${assocName.replace(/\./g, '_')}_${foreignKeyRef.as || foreignKeyRef.ref.join('_')}`;
|
|
176
|
-
newForeignKey(fkArtifact, foreignKeyElementName);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
return generatedFks;
|
|
181
|
-
|
|
182
|
-
function processStucturedKey(fkArtifact, assocName, foreignKeyRef) {
|
|
183
|
-
const subStruct = fkArtifact.elements ? fkArtifact : csnUtils.getFinalBaseType(fkArtifact.type);
|
|
184
|
-
const flatElements = flattenStructure(subStruct.elements, subStruct.$path, csnUtils, options, error, undefined, fkArtifact.$path.slice(-1) || []).newFlatElements;
|
|
185
|
-
for (const [flatElemName, flatElem] of Object.entries(flatElements)) {
|
|
186
|
-
const foreignKeyElementName =
|
|
187
|
-
`${assocName.replace(/\./g, '_')}_${foreignKeyRef.as ? flatElemName.replace(implicitAs(foreignKeyRef.ref), foreignKeyRef.as) : flatElemName}`;
|
|
188
|
-
newForeignKey(flatElem, foreignKeyElementName);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function newForeignKey(fkArtifact, foreignKeyElementName) {
|
|
193
|
-
if (fkArtifact.type === 'cds.Association' || fkArtifact.type === 'cds.Composition') {
|
|
194
|
-
processAssociationOrComposition(fkArtifact, foreignKeyElementName);
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// FIXME: better use transformUtlsNew::createRealFK(...);
|
|
199
|
-
let foreignKeyElement = Object.create(null);
|
|
200
|
-
|
|
201
|
-
// Transfer selected type properties from target key element
|
|
202
|
-
// FIXME: There is currently no other way but to treat the annotation '@odata.Type' as a type property.
|
|
203
|
-
for (let prop of ['type', 'length', 'scale', 'precision', 'srid', 'default', '@odata.Type']) {
|
|
204
|
-
if (fkArtifact[prop] !== undefined) {
|
|
205
|
-
foreignKeyElement[prop] = fkArtifact[prop];
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
// If the association is non-fkArtifact resp. key, so should be the foreign key field
|
|
209
|
-
for (let prop of ['notNull', 'key']) {
|
|
210
|
-
if (assoc[prop] !== undefined) {
|
|
211
|
-
foreignKeyElement[prop] = assoc[prop];
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
foreignKeyElement['@odata.foreignKey4'] = foreignKey4;
|
|
216
|
-
if (flatKeys) foreignKeyRef.$generatedFieldName = foreignKeyElementName;
|
|
217
|
-
setProp(foreignKeyElement, '$path', pathInKeysArr); // attach $path to the newly created element - used for inspectRef in processAssociationOrComposition
|
|
218
|
-
if (assoc.$location) {
|
|
219
|
-
setProp(foreignKeyElement, '$location', assoc.$location);
|
|
220
|
-
}
|
|
221
|
-
generatedFks[foreignKeyElementName] = foreignKeyElement;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function processAssociationOrComposition(fkArtifact, foreignKeyElementName) {
|
|
225
|
-
fkArtifact.keys.forEach((keyRef,keyId) => {
|
|
226
|
-
const path = fkArtifact.$path.concat('keys').concat(keyId);
|
|
227
|
-
const fksForAssoc = generateForeignKeysForRef(assoc, foreignKeyElementName, keyRef, path, foreignKey4);
|
|
228
|
-
Object.assign(generatedFks, fksForAssoc);
|
|
229
|
-
})
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
*
|
|
236
|
-
* @param {object} obj
|
|
237
|
-
* @param {*} other
|
|
238
|
-
* @returns {boolean} Whether 'obj' and 'other' are deeply equal. We need the
|
|
239
|
-
* deep comparison because of annotations that have structured values and they
|
|
240
|
-
* are propagated to the generated foreign keys.
|
|
241
|
-
*/
|
|
242
|
-
function isDeepEqual(obj, other) {
|
|
243
|
-
const objectKeys = Object.keys(obj);
|
|
244
|
-
const otherKeys = Object.keys(other);
|
|
245
|
-
|
|
246
|
-
if (objectKeys.length !== otherKeys.length)
|
|
247
|
-
return false;
|
|
248
|
-
|
|
249
|
-
for (let key of objectKeys) {
|
|
250
|
-
const areValuesObjects = (obj[key] != null && typeof obj[key] === 'object')
|
|
251
|
-
&& (other[key] !== null && typeof other[key] === 'object');
|
|
252
|
-
|
|
253
|
-
if (areValuesObjects) {
|
|
254
|
-
if (!isDeepEqual(obj[key], other[key]))
|
|
255
|
-
return false;
|
|
256
|
-
} else if (obj[key] !== other[key]) {
|
|
257
|
-
return false;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
return true;
|
|
261
|
-
}
|