@sap/cds-compiler 5.7.2 → 5.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +62 -2
- package/bin/cdsse.js +13 -1
- package/doc/CHANGELOG_BETA.md +7 -0
- package/lib/api/options.js +2 -1
- package/lib/api/validate.js +9 -0
- package/lib/base/location.js +1 -1
- package/lib/base/message-registry.js +55 -20
- package/lib/base/messages.js +5 -2
- package/lib/base/model.js +8 -6
- package/lib/checks/assocOutsideService.js +40 -0
- package/lib/checks/featureFlags.js +4 -1
- package/lib/checks/types.js +7 -4
- package/lib/checks/validator.js +3 -0
- package/lib/compiler/assert-consistency.js +11 -5
- package/lib/compiler/checks.js +79 -17
- package/lib/compiler/define.js +60 -3
- package/lib/compiler/extend.js +1 -2
- package/lib/compiler/generate.js +1 -1
- package/lib/compiler/populate.js +17 -6
- package/lib/compiler/propagator.js +1 -1
- package/lib/compiler/resolve.js +181 -150
- package/lib/compiler/shared.js +276 -22
- package/lib/compiler/tweak-assocs.js +15 -4
- package/lib/compiler/xpr-rewrite.js +76 -50
- package/lib/edm/annotations/edmJson.js +1 -1
- package/lib/edm/annotations/genericTranslation.js +2 -2
- package/lib/edm/csn2edm.js +2 -2
- package/lib/edm/edmPreprocessor.js +15 -9
- package/lib/edm/edmUtils.js +12 -5
- package/lib/gen/CdlGrammar.checksum +1 -0
- package/lib/gen/CdlParser.js +2239 -2229
- package/lib/gen/Dictionary.json +55 -8
- package/lib/json/from-csn.js +37 -17
- package/lib/json/to-csn.js +4 -0
- package/lib/language/genericAntlrParser.js +7 -0
- package/lib/main.d.ts +5 -1
- package/lib/model/cloneCsn.js +1 -0
- package/lib/model/csnRefs.js +1 -0
- package/lib/model/csnUtils.js +0 -5
- package/lib/modelCompare/utils/filter.js +2 -2
- package/lib/optionProcessor.js +2 -0
- package/lib/parsers/AstBuildingParser.js +72 -34
- package/lib/parsers/CdlGrammar.g4 +20 -19
- package/lib/parsers/XprTree.js +206 -0
- package/lib/parsers/index.js +1 -1
- package/lib/render/toCdl.js +61 -89
- package/lib/render/toSql.js +59 -29
- package/lib/render/utils/standardDatabaseFunctions.js +252 -15
- package/lib/transform/addTenantFields.js +9 -3
- package/lib/transform/db/assocsToQueries/transformExists.js +3 -0
- package/lib/transform/db/assocsToQueries/utils.js +10 -3
- package/lib/transform/db/expansion.js +3 -1
- package/lib/transform/db/flattening.js +7 -3
- package/lib/transform/db/killAnnotations.js +1 -0
- package/lib/transform/db/processSqlServices.js +70 -17
- package/lib/transform/draft/db.js +8 -3
- package/lib/transform/draft/odata.js +27 -4
- package/lib/transform/effective/main.js +37 -10
- package/lib/transform/effective/misc.js +4 -9
- package/lib/transform/effective/service.js +34 -0
- package/lib/transform/effective/types.js +28 -17
- package/lib/transform/forOdata.js +36 -10
- package/lib/transform/forRelationalDB.js +30 -18
- package/lib/transform/odata/adaptAnnotationRefs.js +37 -21
- package/lib/transform/odata/createForeignKeys.js +120 -116
- package/lib/transform/odata/flattening.js +10 -8
- package/lib/transform/transformUtils.js +58 -25
- package/lib/transform/translateAssocsToJoins.js +10 -6
- package/lib/transform/universalCsn/coreComputed.js +5 -1
- package/package.json +1 -1
- package/share/messages/message-explanations.json +1 -0
- package/share/messages/rewrite-not-supported.md +5 -0
- package/share/messages/rewrite-undefined-key.md +94 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const { setProp, isBetaEnabled } = require('../base/model');
|
|
4
4
|
const { forEachMemberRecursively, forAllQueries, applyTransformationsOnNonDictionary,
|
|
5
5
|
getArtifactDatabaseNameOf, getElementDatabaseNameOf, applyTransformations,
|
|
6
|
-
|
|
6
|
+
walkCsnPath, isPersistedOnDatabase
|
|
7
7
|
} = require('../model/csnUtils');
|
|
8
8
|
const { isBuiltinType } = require('../base/builtins');
|
|
9
9
|
const transformUtils = require('./transformUtils');
|
|
@@ -34,7 +34,7 @@ const backlinks = require('./db/backlinks');
|
|
|
34
34
|
const { getDefaultTypeLengths } = require('../render/utils/common');
|
|
35
35
|
const { featureFlags } = require('./featureFlags');
|
|
36
36
|
const { cloneCsnNonDict, cloneFullCsn } = require('../model/cloneCsn');
|
|
37
|
-
const { processSqlServices } = require('./db/processSqlServices');
|
|
37
|
+
const { processSqlServices, createServiceDummy } = require('./db/processSqlServices');
|
|
38
38
|
|
|
39
39
|
// By default: Do not process non-entities/views
|
|
40
40
|
function forEachDefinition(csn, cb) {
|
|
@@ -157,7 +157,7 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
157
157
|
timetrace.start('Validate');
|
|
158
158
|
// Run validations on CSN - each validator function has access to the message functions and the inspect ref via this
|
|
159
159
|
validate.forRelationalDB(csn, {
|
|
160
|
-
...messageFunctions, csnUtils, ...csnUtils, csn, options,
|
|
160
|
+
...messageFunctions, csnUtils, ...csnUtils, csn, options,
|
|
161
161
|
});
|
|
162
162
|
timetrace.stop('Validate');
|
|
163
163
|
|
|
@@ -354,12 +354,18 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
354
354
|
// some errors can't be handled in the subsequent processing steps for e.g. HDBCDS
|
|
355
355
|
messageFunctions.throwWithError();
|
|
356
356
|
|
|
357
|
+
// TODO: Might have to do this earlier if we want special rendering for projections?
|
|
358
|
+
const findAndMarkSqlServiceArtifacts = options.sqlDialect === 'hana' && options.src === 'hdi' && (csn.meta?.[featureFlags]?.$sqlService || csn.meta?.[featureFlags]?.$dummyService) ? processSqlServices(csn, options): () => {}
|
|
359
|
+
|
|
357
360
|
// Apply view-specific transformations
|
|
358
361
|
// (160) Projections now finally become views
|
|
359
362
|
// Replace managed association in group/order by with foreign keys
|
|
360
363
|
const transformEntityOrViewPass2 = getViewTransformer(csn, options, messageFunctions);
|
|
361
|
-
forEachDefinition(csn,
|
|
362
|
-
|
|
364
|
+
forEachDefinition(csn, [(artifact, artifactName) => {
|
|
365
|
+
findAndMarkSqlServiceArtifacts(artifact, artifactName);
|
|
366
|
+
if(artifact.$dummyService)
|
|
367
|
+
createServiceDummy(artifact, artifactName, csn, messageFunctions);
|
|
368
|
+
}, transformViews]);
|
|
363
369
|
|
|
364
370
|
if(!doA2J) {
|
|
365
371
|
forEachDefinition(csn, [
|
|
@@ -381,9 +387,6 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
381
387
|
]);
|
|
382
388
|
}
|
|
383
389
|
|
|
384
|
-
// TODO: Might have to do this earlier if we want special rendering for projections?
|
|
385
|
-
const findAndMarkSqlServiceArtifacts = options.sqlDialect === 'hana' && options.src === 'hdi' && csn.meta?.[featureFlags]?.$sqlService ? processSqlServices(csn): () => {}
|
|
386
|
-
|
|
387
390
|
// TODO: Could we maybe merge this with the final applyTransformations?
|
|
388
391
|
applyTransformations(csn, {
|
|
389
392
|
type: (parent, prop, type, path) => {
|
|
@@ -409,8 +412,6 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
409
412
|
// Attach @cds.persistence.name to artifacts
|
|
410
413
|
if (!artifact.$ignore && artifact.kind !== 'service' && artifact.kind !== 'context')
|
|
411
414
|
csnUtils.addStringAnnotationTo('@cds.persistence.name', getArtifactDatabaseNameOf(artifactName, options.sqlMapping, csn, options.sqlDialect), artifact);
|
|
412
|
-
|
|
413
|
-
findAndMarkSqlServiceArtifacts(artifact, artifactName);
|
|
414
415
|
}], { allowArtifact: artifact => artifact.kind === 'entity'});
|
|
415
416
|
|
|
416
417
|
throwWithAnyError();
|
|
@@ -636,9 +637,14 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
636
637
|
function handleAssocToJoins() {
|
|
637
638
|
// the augmentor isn't able to deal with technical configurations and since assoc2join can ignore it we
|
|
638
639
|
// simply make it invisible and copy it over to the result csn
|
|
639
|
-
forEachDefinition(csn,
|
|
640
|
-
|
|
641
|
-
|
|
640
|
+
forEachDefinition(csn, (art) => {
|
|
641
|
+
if (art.technicalConfig)
|
|
642
|
+
setProp(art, 'technicalConfig', art.technicalConfig);
|
|
643
|
+
if (art.kind === 'type' && art.projection) {
|
|
644
|
+
// Missing 'elements' already reported by csnRefs.
|
|
645
|
+
delete art.projection;
|
|
646
|
+
}
|
|
647
|
+
});
|
|
642
648
|
|
|
643
649
|
const newCsn = translateAssocsToJoinsCSN(csn, options);
|
|
644
650
|
|
|
@@ -712,9 +718,15 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
712
718
|
|
|
713
719
|
// If 'elem' has a default that is an enum constant, replace that by its value. Complain
|
|
714
720
|
// if not found or not an enum type,
|
|
721
|
+
// usually done by the Core Compiler, but backend might be called directly
|
|
715
722
|
function replaceEnumSymbolsByValues(elem, path) {
|
|
716
723
|
// (190 a) Replace enum symbols by their value (if found)
|
|
717
|
-
if (elem.default
|
|
724
|
+
if (!elem.default?.['#'])
|
|
725
|
+
return;
|
|
726
|
+
if (elem.default.val !== undefined) {
|
|
727
|
+
delete elem.default['#'];
|
|
728
|
+
}
|
|
729
|
+
else {
|
|
718
730
|
let Enum = elem.enum;
|
|
719
731
|
if (!Enum && !isBuiltinType(elem.type)) {
|
|
720
732
|
const typeDef = csnUtils.getCsnDef(elem.type);
|
|
@@ -755,10 +767,10 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
755
767
|
/**
|
|
756
768
|
* Check that required actual parameters on 'node.type' are set, that their values are in the correct range etc.
|
|
757
769
|
|
|
758
|
-
* @param {*} node
|
|
759
|
-
* @param {*} nodeName
|
|
760
|
-
* @param {*} model
|
|
761
|
-
* @param {*} path
|
|
770
|
+
* @param {*} node
|
|
771
|
+
* @param {*} nodeName
|
|
772
|
+
* @param {*} model
|
|
773
|
+
* @param {*} path
|
|
762
774
|
*/
|
|
763
775
|
function checkTypeParameters(node, artifact, path) {
|
|
764
776
|
if (node.type && !node.virtual) {
|
|
@@ -8,21 +8,31 @@ const { transformAnnotationExpression, implicitAs, } = require('../../model/csnU
|
|
|
8
8
|
* key declared in the scope, we replace it with referencing the foreign key itself. If a reference is a $self reference,
|
|
9
9
|
* we do nothing and if a ref points to a structure/managed association, an error is thrown
|
|
10
10
|
*
|
|
11
|
-
* @param {Array[]} generatedForeignKeys
|
|
11
|
+
* @param {Array[object]||Array[]} generatedForeignKeys
|
|
12
12
|
* @param {object} csnUtils
|
|
13
13
|
* @param {object} messageFunctions
|
|
14
|
-
* @param {CSN.Path} elementPath
|
|
15
14
|
*/
|
|
16
15
|
function adaptAnnotationsRefs(generatedForeignKeys, csnUtils, { error }, elementPath) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
16
|
+
if(Array.isArray(generatedForeignKeys && generatedForeignKeys[0])) {
|
|
17
|
+
// ensure we are always called with an array of objects. TODO: Cleanup fk creation in for.effective to create array of objects
|
|
18
|
+
adaptAnnotationsRefs(remapToArrayOfObjects(generatedForeignKeys), csnUtils, { error }, elementPath);
|
|
19
|
+
} else {
|
|
20
|
+
const reportedErrorsForAnnoPath = {};
|
|
21
|
+
generatedForeignKeys.forEach((gfk, index) => {
|
|
22
|
+
Object.entries(gfk.foreignKey).forEach(([key, value]) => {
|
|
23
|
+
if (key[0] !== '@') return;
|
|
24
|
+
|
|
25
|
+
transformAnnotationExpression(gfk.foreignKey, key, {
|
|
26
|
+
ref: (_parent, _prop, ref, path, _p, _ppn, ctx) => {
|
|
27
|
+
// if the reference is a $self reference, we do nothing,
|
|
28
|
+
// as this is the way to tell that we do not reference the foreign key
|
|
29
|
+
if (ref[0] === '$self') return;
|
|
30
|
+
// if annotation was not propagated from the keys array during foreign keys creation,
|
|
31
|
+
// means that it is not a candidate for foreign key substitution
|
|
32
|
+
if (gfk.keyAnnotations !== null && !gfk.keyAnnotations.includes(key)) return;
|
|
33
|
+
|
|
34
|
+
const art = gfk.originalKey._art ||
|
|
35
|
+
csnUtils.inspectRef(elementPath ? path : getOriginatingKeyPath(gfk, path)).art; // OData uses getOriginatingKeyPath - as it relies on $path
|
|
26
36
|
if (csnUtils.isManagedAssociation(art)) {
|
|
27
37
|
if (!reportedErrorsForAnnoPath[path]) {
|
|
28
38
|
error('odata-anno-xpr-ref', path, { elemref: { ref }, anno: key, '#': 'fk_substitution' });
|
|
@@ -31,15 +41,15 @@ function adaptAnnotationsRefs(generatedForeignKeys, csnUtils, { error }, element
|
|
|
31
41
|
} else {
|
|
32
42
|
const gfkForRef = findGeneratedForeignKeyForKeyRef(generatedForeignKeys, ref);
|
|
33
43
|
if (gfkForRef.length === 1) {
|
|
34
|
-
ref[0] = gfkForRef[0]
|
|
35
|
-
|
|
44
|
+
ref[0] = gfkForRef[0].prefix;
|
|
45
|
+
|
|
36
46
|
if (ctx?.annoExpr?.['=']) {
|
|
37
47
|
ctx.annoExpr['='] = true;
|
|
38
48
|
}
|
|
39
49
|
} else {
|
|
40
50
|
// check if the annotation reference points to a structure that has been expanded,
|
|
41
51
|
// if so -> report an error
|
|
42
|
-
const foundInOriginalRef = findOriginalRef(generatedForeignKeys.filter(gfk => gfk
|
|
52
|
+
const foundInOriginalRef = findOriginalRef(generatedForeignKeys.filter(gfk => gfk.originalKey.$originalKeyRef), ref);
|
|
43
53
|
// references to expanded structures in flat mode will be found in the $originalKeyRef
|
|
44
54
|
// and in structured mode more than one match will be found in the generated foreign keys
|
|
45
55
|
if ((foundInOriginalRef.length || gfkForRef.length > 1) && !reportedErrorsForAnnoPath[path]) {
|
|
@@ -49,15 +59,15 @@ function adaptAnnotationsRefs(generatedForeignKeys, csnUtils, { error }, element
|
|
|
49
59
|
}
|
|
50
60
|
}
|
|
51
61
|
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
62
|
+
}, elementPath ? elementPath.concat(['keys', index]) : value?.$path?.slice(0, value.$path.length - 1)); // OData uses $path
|
|
63
|
+
});
|
|
54
64
|
});
|
|
55
|
-
}
|
|
65
|
+
}
|
|
56
66
|
|
|
57
67
|
// During tuple expansion, the key ref object looses the $path, therefore
|
|
58
68
|
// it needs to be extracted from the anno path
|
|
59
69
|
function getOriginatingKeyPath(gfk, path) {
|
|
60
|
-
return gfk
|
|
70
|
+
return gfk.originalKey.$path || path.slice(0, path.findIndex(ps => ps[0] === '@'));
|
|
61
71
|
}
|
|
62
72
|
|
|
63
73
|
// Loops through the generated foreign keys for this entity
|
|
@@ -65,15 +75,21 @@ function adaptAnnotationsRefs(generatedForeignKeys, csnUtils, { error }, element
|
|
|
65
75
|
// key ref. In case there are more than one foreign keys found,
|
|
66
76
|
// that means the key ref points to a structured element/managed assoc
|
|
67
77
|
function findGeneratedForeignKeyForKeyRef(generatedForeignKeys, ref) {
|
|
68
|
-
return generatedForeignKeys.filter(gfk => (ref.join() === (gfk
|
|
78
|
+
return generatedForeignKeys.filter(gfk => (ref.join() === (gfk.originalKey.as || implicitAs(gfk.originalKey.ref))));
|
|
69
79
|
}
|
|
70
80
|
|
|
71
81
|
// Tuple expansion is performed before the generation of the foreign keys and the original(unexpanded) key ref
|
|
72
82
|
// is stored in the property $originalKeyRef. Here we try to evaluate whether the reference in the annotation
|
|
73
83
|
// points to a structure that has been expanded.
|
|
74
84
|
function findOriginalRef(generatedForeignKeys, ref) {
|
|
75
|
-
return generatedForeignKeys.filter(gfk => (ref.join() === (gfk
|
|
85
|
+
return generatedForeignKeys.filter(gfk => (ref.join() === (gfk.originalKey.$originalKeyRef.as || implicitAs(gfk.originalKey.$originalKeyRef.ref))));
|
|
76
86
|
}
|
|
77
87
|
}
|
|
78
88
|
|
|
79
|
-
|
|
89
|
+
function remapToArrayOfObjects(generatedForeignKeys) {
|
|
90
|
+
return generatedForeignKeys.map(([ prefix, foreignKey, originalKey ]) => {
|
|
91
|
+
return { prefix, foreignKey, originalKey, keyAnnotations: null };
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
module.exports = { adaptAnnotationsRefs };
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
const { isBuiltinType } = require('../../base/builtins');
|
|
4
4
|
const { setProp } = require('../../base/model');
|
|
5
|
-
const { applyTransformations, implicitAs,
|
|
5
|
+
const { applyTransformations, implicitAs, copyAnnotations, isDeepEqual } = require('../../model/csnUtils');
|
|
6
6
|
const { EdmTypeFacetNames } = require('../../edm/EdmPrimitiveTypeDefinitions');
|
|
7
|
-
const adaptAnnotationsRefs = require('./adaptAnnotationRefs');
|
|
7
|
+
const { adaptAnnotationsRefs } = require('./adaptAnnotationRefs');
|
|
8
8
|
|
|
9
9
|
function createForeignKeyElements(csn, options, messageFunctions, csnUtils, iterateOptions = {}) {
|
|
10
10
|
|
|
@@ -28,20 +28,20 @@ function createForeignKeyElements(csn, options, messageFunctions, csnUtils, iter
|
|
|
28
28
|
orderedElements.push([ elementName, element ]);
|
|
29
29
|
if (!csnUtils.isManagedAssociation(element)) return;
|
|
30
30
|
const elementPath = path.concat(prop, elementName);
|
|
31
|
-
const generatedForeignKeys = createForeignKeysForElement(
|
|
31
|
+
const generatedForeignKeys = createForeignKeysForElement(elementPath, element, elementName, csn, options, '_');
|
|
32
32
|
|
|
33
33
|
// Second, finalize the generated FK elements
|
|
34
34
|
const refCount = generatedForeignKeys.reduce((acc, fk) => {
|
|
35
35
|
// count duplicates
|
|
36
|
-
if (acc[fk
|
|
37
|
-
acc[fk
|
|
36
|
+
if (acc[fk.prefix])
|
|
37
|
+
acc[fk.prefix]++;
|
|
38
38
|
else
|
|
39
|
-
acc[fk
|
|
39
|
+
acc[fk.prefix] = 1;
|
|
40
40
|
|
|
41
41
|
// check for name clash with existing elements
|
|
42
|
-
if (parent[prop][fk
|
|
42
|
+
if (parent[prop][fk.prefix] && isDeepEqual(element, parent[prop][fk.prefix], true)) {
|
|
43
43
|
// error location is the colliding element
|
|
44
|
-
error('name-duplicate-element', elementPath, { '#': 'flatten-fkey-exists', name: fk
|
|
44
|
+
error('name-duplicate-element', elementPath, { '#': 'flatten-fkey-exists', name: fk.prefix, art: elementName });
|
|
45
45
|
}
|
|
46
46
|
// attach a proper $path
|
|
47
47
|
setProp(element, '$path', elementPath);
|
|
@@ -50,7 +50,7 @@ function createForeignKeyElements(csn, options, messageFunctions, csnUtils, iter
|
|
|
50
50
|
|
|
51
51
|
// set default for single foreign key from association (if available)
|
|
52
52
|
if (element.default?.val !== undefined && generatedForeignKeys.length === 1)
|
|
53
|
-
generatedForeignKeys[0]
|
|
53
|
+
generatedForeignKeys[0].foreignKey.default = element.default;
|
|
54
54
|
|
|
55
55
|
// check for duplicate foreign keys
|
|
56
56
|
Object.entries(refCount).forEach(([ name, occ ]) => {
|
|
@@ -63,8 +63,8 @@ function createForeignKeyElements(csn, options, messageFunctions, csnUtils, iter
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
adaptAnnotationsRefs(generatedForeignKeys, csnUtils, messageFunctions);
|
|
66
|
-
setProp(element, '$generatedForeignKeys', generatedForeignKeys.map(gfk => gfk
|
|
67
|
-
orderedElements.push(...generatedForeignKeys);
|
|
66
|
+
setProp(element, '$generatedForeignKeys', generatedForeignKeys.map(gfk => gfk.prefix));
|
|
67
|
+
orderedElements.push(...generatedForeignKeys.map(gfk => [ gfk.prefix, gfk.foreignKey ]));
|
|
68
68
|
|
|
69
69
|
});
|
|
70
70
|
|
|
@@ -74,123 +74,127 @@ function createForeignKeyElements(csn, options, messageFunctions, csnUtils, iter
|
|
|
74
74
|
}, Object.create(null));
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
function createForeignKeysForElement(
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
function createForeignKeysForElement(path, element, prefix, csn, options, pathDelimiter, lvl = 0, originalKey = {} ) {
|
|
78
|
+
const special$self = !csn?.definitions?.$self && '$self';
|
|
79
|
+
const isInspectRefResult = !Array.isArray(path);
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
let fks = [];
|
|
82
|
+
if (!element)
|
|
83
|
+
return fks;
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
let finalElement = element;
|
|
86
|
+
let finalTypeName;
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
88
|
+
// resolve derived type
|
|
89
|
+
if (element.type && !isBuiltinType(element.type) && element.type !== special$self) {
|
|
90
|
+
const tmpElt = csnUtils.effectiveType(element);
|
|
91
|
+
// effective type resolves to structs and enums only but not scalars
|
|
92
|
+
if (Object.keys(tmpElt).length) {
|
|
93
|
+
finalElement = tmpElt;
|
|
94
|
+
finalTypeName = finalElement.$path[1];
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
// unwind a derived type chain to a scalar type
|
|
98
|
+
while (finalElement?.type && !isBuiltinType(finalElement?.type)) {
|
|
99
|
+
finalTypeName = finalElement.type;
|
|
100
|
+
finalElement = csn.definitions[finalElement.type];
|
|
101
|
+
}
|
|
101
102
|
}
|
|
102
103
|
}
|
|
103
|
-
}
|
|
104
104
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
105
|
+
if (!finalElement)
|
|
106
|
+
return [];
|
|
107
|
+
|
|
108
|
+
// main if for this function
|
|
109
|
+
// the element is a managed association
|
|
110
|
+
if (csnUtils.isManagedAssociation(finalElement)) {
|
|
111
|
+
finalElement.keys.forEach((key, keyIndex) => {
|
|
112
|
+
const continuePath = getContinuePath([ 'keys', keyIndex ]);
|
|
113
|
+
const alias = key.as || implicitAs(key.ref);
|
|
114
|
+
const result = csnUtils.inspectRef(continuePath);
|
|
115
|
+
let gfks = createForeignKeysForElement(result, result.art, alias, csn, options, pathDelimiter, lvl + 1,
|
|
116
|
+
lvl === 0 ? key : originalKey);
|
|
117
|
+
if (lvl === 0) {
|
|
118
|
+
gfks.forEach(gfk => gfk.keyAnnotations.push( ...copyAnnotations(key, gfk.foreignKey)));
|
|
119
|
+
Object.keys(key).forEach( prop => {
|
|
120
|
+
// once applied -> remove the annotations from the keys array, to keep the OData CSN size as small as possible
|
|
121
|
+
if (prop[0] === '@')
|
|
122
|
+
delete key[prop]
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
fks = fks.concat(gfks);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
// the element is a structure
|
|
129
|
+
else if (finalElement.elements) {
|
|
130
|
+
Object.entries(finalElement.elements).forEach(([ elemName, elem ]) => {
|
|
131
|
+
// Skip already produced foreign keys
|
|
132
|
+
if (!elem['@odata.foreignKey4']) {
|
|
133
|
+
const continuePath = getContinuePath([ 'elements', elemName ]);
|
|
134
|
+
fks = fks.concat(createForeignKeysForElement(continuePath, elem, elemName, csn, options, pathDelimiter, lvl + 1, originalKey));
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
// we have reached a leaf element, create a foreign key
|
|
139
|
+
else if ((finalElement.type == null || isBuiltinType(finalElement.type)) && !finalElement.on) {
|
|
140
|
+
const newFk = Object.create(null);
|
|
141
|
+
[ 'type', 'length', 'scale', 'precision', 'srid', 'default', '@odata.Type', ...EdmTypeFacetNames.map(f => `@odata.${f}`) ].forEach((prop) => {
|
|
142
|
+
// copy props from original element to preserve derived types!
|
|
143
|
+
if (element[prop] !== undefined)
|
|
144
|
+
newFk[prop] = element[prop];
|
|
145
|
+
});
|
|
146
|
+
return [ { prefix, foreignKey: newFk, originalKey, keyAnnotations: [] } ];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
fks.forEach((fk) => {
|
|
150
|
+
// prepend current prefix
|
|
151
|
+
fk.prefix = `${prefix}${pathDelimiter}${fk.prefix}`;
|
|
152
|
+
// if this is the entry association, decorate the final foreign keys with the association props/annos
|
|
117
153
|
if (lvl === 0) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
154
|
+
fk.foreignKey['@odata.foreignKey4'] = prefix;
|
|
155
|
+
|
|
156
|
+
const fkPath = path.slice(0, path.length - 1);
|
|
157
|
+
fkPath.push(fk.prefix);
|
|
158
|
+
setProp(fk.foreignKey, '$path', fkPath);
|
|
159
|
+
|
|
160
|
+
const allowedOverwriteAnnotationNames = ['@odata.Type', ...EdmTypeFacetNames.map(f => `@odata.${f}`)];
|
|
161
|
+
const validAnnoNames = Object.keys(element).filter(pn => pn[0] === '@' && !allowedOverwriteAnnotationNames.includes(pn));
|
|
162
|
+
copyAnnotations(element, fk.foreignKey, false, {}, validAnnoNames);
|
|
163
|
+
const overwriteAnnoNames = Object.keys(element).filter(pn => allowedOverwriteAnnotationNames.includes(pn));
|
|
164
|
+
copyAnnotations(element, fk.foreignKey, true, {}, overwriteAnnoNames);
|
|
165
|
+
|
|
166
|
+
// propagate not null to final foreign key
|
|
167
|
+
for (const prop of [ 'notNull', 'key' ]) {
|
|
168
|
+
if (element[prop] !== undefined)
|
|
169
|
+
fk.foreignKey[prop] = element[prop];
|
|
170
|
+
}
|
|
171
|
+
if (element.$location)
|
|
172
|
+
setProp(fk.foreignKey, '$location', element.$location);
|
|
134
173
|
}
|
|
135
174
|
});
|
|
136
|
-
|
|
137
|
-
// we have reached a leaf element, create a foreign key
|
|
138
|
-
else if ((finalElement.type == null || isBuiltinType(finalElement.type)) && !finalElement.on) {
|
|
139
|
-
const newFk = Object.create(null);
|
|
140
|
-
[ 'type', 'length', 'scale', 'precision', 'srid', 'default', '@odata.Type', ...EdmTypeFacetNames.map(f => `@odata.${f}`) ].forEach((prop) => {
|
|
141
|
-
// copy props from original element to preserve derived types!
|
|
142
|
-
if (element[prop] !== undefined)
|
|
143
|
-
newFk[prop] = element[prop];
|
|
144
|
-
});
|
|
145
|
-
return [ [ prefix, newFk, originalkey ] ];
|
|
146
|
-
}
|
|
175
|
+
return fks;
|
|
147
176
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if (element.$location)
|
|
168
|
-
setProp(fk[1], '$location', element.$location);
|
|
177
|
+
/**
|
|
178
|
+
* Get the path to continue resolving references
|
|
179
|
+
*
|
|
180
|
+
* If we are currently inside of a type, we need to start our path fresh from that given type.
|
|
181
|
+
* Otherwise, we would try to resolve .elements on a thing that does not exist.
|
|
182
|
+
*
|
|
183
|
+
* We also respect if we have a previous inspectRef result as our base.
|
|
184
|
+
*
|
|
185
|
+
* @param {Array} additions
|
|
186
|
+
* @returns {CSN.Path}
|
|
187
|
+
*/
|
|
188
|
+
function getContinuePath( additions ) {
|
|
189
|
+
if (csn.definitions[finalElement.type])
|
|
190
|
+
return [ 'definitions', finalElement.type, ...additions ];
|
|
191
|
+
else if (finalTypeName)
|
|
192
|
+
return [ 'definitions', finalTypeName, ...additions ];
|
|
193
|
+
else if (isInspectRefResult)
|
|
194
|
+
return [ path, ...additions ];
|
|
195
|
+
return [ ...path, ...additions ];
|
|
169
196
|
}
|
|
170
|
-
});
|
|
171
|
-
return fks;
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Get the path to continue resolving references
|
|
175
|
-
*
|
|
176
|
-
* If we are currently inside of a type, we need to start our path fresh from that given type.
|
|
177
|
-
* Otherwise, we would try to resolve .elements on a thing that does not exist.
|
|
178
|
-
*
|
|
179
|
-
* We also respect if we have a previous inspectRef result as our base.
|
|
180
|
-
*
|
|
181
|
-
* @param {Array} additions
|
|
182
|
-
* @returns {CSN.Path}
|
|
183
|
-
*/
|
|
184
|
-
function getContinuePath( additions ) {
|
|
185
|
-
if (csn.definitions[finalElement.type])
|
|
186
|
-
return [ 'definitions', finalElement.type, ...additions ];
|
|
187
|
-
else if (finalTypeName)
|
|
188
|
-
return [ 'definitions', finalTypeName, ...additions ];
|
|
189
|
-
else if (isInspectRefResult)
|
|
190
|
-
return [ path, ...additions ];
|
|
191
|
-
return [ ...path, ...additions ];
|
|
192
197
|
}
|
|
193
198
|
}
|
|
194
|
-
}
|
|
195
199
|
|
|
196
200
|
module.exports = createForeignKeyElements;
|
|
@@ -14,7 +14,7 @@ const { forEach } = require('../../utils/objectUtils');
|
|
|
14
14
|
const { assignAnnotation } = require('../../edm/edmUtils');
|
|
15
15
|
|
|
16
16
|
function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, getFinalTypeInfo, isExternalServiceMember, error, csnUtils, options) {
|
|
17
|
-
const allMgdAssocDefs = [];
|
|
17
|
+
const allMgdAssocDefs = [];
|
|
18
18
|
forEachDefinition(csn, (def, defName) => {
|
|
19
19
|
if (def.kind === 'entity' && !isExternalServiceMember(def, defName)) {
|
|
20
20
|
['elements', 'params'].forEach(dictName => {
|
|
@@ -345,7 +345,7 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, getFinalTy
|
|
|
345
345
|
// try to find rightmost 'items', terminate if association comes first.
|
|
346
346
|
let i = links.length-1;
|
|
347
347
|
const getProp = (propName) => links[i].art?.[propName];
|
|
348
|
-
|
|
348
|
+
|
|
349
349
|
let hasItems = false;
|
|
350
350
|
for(; i >= 0 && !getProp('target') && !hasItems; i--) {
|
|
351
351
|
const art = links[i].art;
|
|
@@ -361,7 +361,7 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, getFinalTy
|
|
|
361
361
|
name: refCheck.eltLocationStr,
|
|
362
362
|
'#': 'flatten_builtin'
|
|
363
363
|
});
|
|
364
|
-
}
|
|
364
|
+
}
|
|
365
365
|
}
|
|
366
366
|
}
|
|
367
367
|
}
|
|
@@ -467,7 +467,7 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, getFinalTy
|
|
|
467
467
|
let comeFromSameDef = flatElt.$defPath.slice(0, flatElt.$defPath.length - 1).join('.') === se[1].$defPath.slice(0, se[1].$defPath.length - 1).join('.');
|
|
468
468
|
return (comeFromSameDef && (se[1]['@odata.foreignKey4'] && se[1]['@odata.foreignKey4'] === structuredAssocName) && se[0].startsWith(flatEltName))
|
|
469
469
|
});
|
|
470
|
-
|
|
470
|
+
|
|
471
471
|
generatedForeignKeysForAssoc.forEach(gfk => gfk[1]['@odata.foreignKey4'] = flatEltName);
|
|
472
472
|
// reassign the generated foreign keys for current assoc in order to assign
|
|
473
473
|
// correct values for $generatedFieldName later on during flattenManagedAssocsAsKeys();
|
|
@@ -565,7 +565,7 @@ function getStructRefFlatteningTransformer(csn, inspectRef, effectiveType, optio
|
|
|
565
565
|
if (insideKeys(path))
|
|
566
566
|
setProp(parent, '_art', art);
|
|
567
567
|
const lastRef = ref[ref.length - 1];
|
|
568
|
-
const fn = (suspend = false, suspendPos = 0,
|
|
568
|
+
const fn = (suspend = false, suspendPos = 0,
|
|
569
569
|
refFilter = (_parent) => true) => {
|
|
570
570
|
if (refFilter(parent)) {
|
|
571
571
|
const scopedPath = [ ...parent.$path ];
|
|
@@ -573,9 +573,11 @@ function getStructRefFlatteningTransformer(csn, inspectRef, effectiveType, optio
|
|
|
573
573
|
// full path into target, uncomment this line and
|
|
574
574
|
// comment/remove setProp in expansion.js
|
|
575
575
|
// setProp(parent, '$structRef', parent.ref);
|
|
576
|
+
const flattenParameters = true; // structured parameters are flattened
|
|
576
577
|
const [ newRef, refChanged ] = flattenStructStepsInRef(ref,
|
|
577
578
|
scopedPath, links, scope, resolvedLinkTypes,
|
|
578
|
-
suspend, suspendPos, parent.$bparam
|
|
579
|
+
suspend, suspendPos, parent.$bparam,
|
|
580
|
+
flattenParameters);
|
|
579
581
|
parent.ref = newRef;
|
|
580
582
|
resolved.set(parent, { links, art, scope });
|
|
581
583
|
// Explicitly set implicit alias for things that are now flattened - but only in columns
|
|
@@ -628,7 +630,7 @@ function getStructRefFlatteningTransformer(csn, inspectRef, effectiveType, optio
|
|
|
628
630
|
annoExpr['='] = true;
|
|
629
631
|
});
|
|
630
632
|
}
|
|
631
|
-
else
|
|
633
|
+
else
|
|
632
634
|
adaptRefs.push(fn);
|
|
633
635
|
},
|
|
634
636
|
}
|
|
@@ -679,7 +681,7 @@ function replaceManagedAssocsAsKeys(allFlatManagedAssocDefinitions, csnUtils) {
|
|
|
679
681
|
return done;
|
|
680
682
|
}
|
|
681
683
|
});
|
|
682
|
-
|
|
684
|
+
|
|
683
685
|
function cloneAndExtendRef(keyAssocKey, key) {
|
|
684
686
|
let newKey = { ref: [`${key.ref.join('_')}_${keyAssocKey.as || keyAssocKey.ref.join('_')}`] };
|
|
685
687
|
setProp(newKey, '_art', keyAssocKey._art);
|