@sap/cds-compiler 5.6.0 → 5.7.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 +31 -0
- package/bin/cdsse.js +1 -0
- package/bin/cdsv2m.js +2 -1
- package/doc/Versioning.md +4 -4
- package/lib/api/options.js +1 -0
- package/lib/base/builtins.js +2 -2
- package/lib/base/dictionaries.js +1 -2
- package/lib/base/keywords.js +3 -1
- package/lib/base/lazyload.js +1 -1
- package/lib/base/message-registry.js +169 -144
- package/lib/base/messages.js +69 -59
- package/lib/base/model.js +3 -3
- package/lib/base/node-helpers.js +17 -16
- package/lib/base/optionProcessorHelper.js +13 -14
- package/lib/base/shuffle.js +4 -1
- package/lib/checks/structuredAnnoExpressions.js +1 -1
- package/lib/compiler/assert-consistency.js +1 -1
- package/lib/compiler/builtins.js +2 -1
- package/lib/compiler/extend.js +20 -5
- package/lib/compiler/resolve.js +45 -9
- package/lib/compiler/shared.js +1 -0
- package/lib/edm/annotations/edmJson.js +3 -3
- package/lib/edm/annotations/genericTranslation.js +5 -1
- package/lib/edm/annotations/vocabularyDefinitions.js +2 -2
- package/lib/edm/edmUtils.js +2 -1
- package/lib/gen/BaseParser.js +32 -32
- package/lib/gen/CdlParser.js +2237 -2196
- package/lib/json/from-csn.js +2 -0
- package/lib/json/to-csn.js +13 -4
- package/lib/language/docCommentParser.js +11 -5
- package/lib/language/errorStrategy.js +3 -3
- package/lib/language/genericAntlrParser.js +2 -0
- package/lib/model/csnUtils.js +6 -1
- package/lib/optionProcessor.js +5 -1
- package/lib/parsers/AstBuildingParser.js +151 -72
- package/lib/parsers/CdlGrammar.g4 +125 -83
- package/lib/parsers/Lexer.js +5 -3
- package/lib/parsers/index.js +1 -1
- package/lib/render/toCdl.js +6 -5
- package/lib/render/toHdbcds.js +1 -1
- package/lib/render/toSql.js +5 -3
- package/lib/render/utils/common.js +19 -6
- package/lib/render/utils/standardDatabaseFunctions.js +576 -0
- package/lib/transform/addTenantFields.js +2 -1
- package/lib/transform/db/flattening.js +18 -77
- package/lib/transform/db/groupByOrderBy.js +2 -2
- package/lib/transform/db/rewriteCalculatedElements.js +14 -19
- package/lib/transform/db/temporal.js +2 -1
- package/lib/transform/odata/adaptAnnotationRefs.js +79 -0
- package/lib/transform/odata/createForeignKeys.js +4 -71
- package/lib/transform/odata/flattening.js +11 -1
- package/lib/transform/transformUtils.js +20 -85
- package/package.json +2 -1
- package/bin/cds_update_annotations.js +0 -180
|
@@ -3,10 +3,8 @@
|
|
|
3
3
|
const {
|
|
4
4
|
applyTransformations,
|
|
5
5
|
applyTransformationsOnNonDictionary,
|
|
6
|
-
cardinality2str,
|
|
7
6
|
copyAnnotations,
|
|
8
7
|
implicitAs,
|
|
9
|
-
isDeepEqual,
|
|
10
8
|
findAnnotationExpression,
|
|
11
9
|
} = require('../../model/csnUtils');
|
|
12
10
|
const { isBuiltinType, isMagicVariable } = require('../../base/builtins');
|
|
@@ -17,6 +15,7 @@ const { forEach } = require('../../utils/objectUtils');
|
|
|
17
15
|
const { transformExpression } = require('./applyTransformations');
|
|
18
16
|
const { cloneCsnNonDict } = require('../../model/cloneCsn');
|
|
19
17
|
const { EdmTypeFacetNames } = require('../../edm/EdmPrimitiveTypeDefinitions');
|
|
18
|
+
const adaptAnnotationsRefs = require('../odata/adaptAnnotationRefs');
|
|
20
19
|
|
|
21
20
|
/**
|
|
22
21
|
* Strip off leading $self from refs where applicable.
|
|
@@ -74,7 +73,6 @@ function resolveTypeReferences( csn, options, messageFunctions, resolved, pathDe
|
|
|
74
73
|
}
|
|
75
74
|
}
|
|
76
75
|
const { toFinalBaseType, csnUtils } = transformUtils.getTransformers(csn, options, messageFunctions, pathDelimiter);
|
|
77
|
-
const { getServiceName, getFinalTypeInfo } = csnUtils;
|
|
78
76
|
|
|
79
77
|
// We don't want to iterate over actions
|
|
80
78
|
if (iterateOptions.skipDict && !iterateOptions.skipDict.actions)
|
|
@@ -82,14 +80,11 @@ function resolveTypeReferences( csn, options, messageFunctions, resolved, pathDe
|
|
|
82
80
|
else
|
|
83
81
|
iterateOptions.skipDict = { actions: true };
|
|
84
82
|
|
|
85
|
-
const ignoreOdataKinds = { aspect: 1, event: 1, type: 1 };
|
|
86
83
|
const replaceWithDummyKinds = { action: 1, function: 1, event: 1 };
|
|
87
84
|
const stripItems = options.transformation === 'hdbcds' || options.transformation === 'sql';
|
|
88
85
|
const removeItems = new Set();
|
|
89
86
|
applyTransformations(csn, {
|
|
90
87
|
type: (node, prop, type, path, parent, parentProp) => {
|
|
91
|
-
if (options.toOdata && node.kind && node.kind in ignoreOdataKinds)
|
|
92
|
-
return;
|
|
93
88
|
if (parentProp === 'cast') {
|
|
94
89
|
const e = csnUtils.getFinalTypeInfo(type, t => resolved.get(t)?.art || csnUtils.artifactRef(t));
|
|
95
90
|
if (e.items && stripItems)
|
|
@@ -97,7 +92,7 @@ function resolveTypeReferences( csn, options, messageFunctions, resolved, pathDe
|
|
|
97
92
|
if (!e || e.items || e.elements)
|
|
98
93
|
return;
|
|
99
94
|
}
|
|
100
|
-
if (!isBuiltinType(type)
|
|
95
|
+
if (!isBuiltinType(type)) {
|
|
101
96
|
toFinalBaseType(node, resolved, true);
|
|
102
97
|
|
|
103
98
|
|
|
@@ -160,42 +155,6 @@ function resolveTypeReferences( csn, options, messageFunctions, resolved, pathDe
|
|
|
160
155
|
delete node.items;
|
|
161
156
|
}
|
|
162
157
|
removeItems.clear();
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* OData V4 only:
|
|
167
|
-
* Do not replace a type ref if:
|
|
168
|
-
* The type definition is terminating on a scalar type (that can also be a derived type chain)
|
|
169
|
-
* AND the typeName (that is the start of that (derived) type chain is defined within the same
|
|
170
|
-
* service as the artifact from which the type reference has to be resolved.
|
|
171
|
-
*
|
|
172
|
-
* @param {string} typeName
|
|
173
|
-
* @param {CSN.Path} path
|
|
174
|
-
* @returns {boolean}
|
|
175
|
-
*/
|
|
176
|
-
function isODataV4BuiltinFromService( typeName, path ) {
|
|
177
|
-
if (!options.toOdata || (options.odataVersion === 'v2') || typeof typeName !== 'string')
|
|
178
|
-
return false;
|
|
179
|
-
|
|
180
|
-
const typeServiceName = getServiceName(typeName);
|
|
181
|
-
const finalBaseType = getFinalTypeInfo(typeName)?.type;
|
|
182
|
-
// we need the service of the current definition
|
|
183
|
-
const currDefServiceName = getServiceName(path[1]);
|
|
184
|
-
|
|
185
|
-
return typeServiceName === currDefServiceName && isBuiltinType(finalBaseType);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* OData stops replacing types @ 'items', if the type ref is a user defined type
|
|
190
|
-
* AND that type has items, don't do toFinalBaseType
|
|
191
|
-
*
|
|
192
|
-
* @param {string} typeName
|
|
193
|
-
* @returns {boolean}
|
|
194
|
-
*/
|
|
195
|
-
function isODataItems( typeName ) {
|
|
196
|
-
const typeDef = csn.definitions[typeName];
|
|
197
|
-
return !!(options.toOdata && typeDef && typeDef?.items);
|
|
198
|
-
}
|
|
199
158
|
}
|
|
200
159
|
|
|
201
160
|
/**
|
|
@@ -334,9 +293,6 @@ function flattenElements( csn, options, messageFunctions, pathDelimiter, iterate
|
|
|
334
293
|
elements: flatten,
|
|
335
294
|
};
|
|
336
295
|
|
|
337
|
-
if (options.toOdata) // Odata needs to flatten the .params as if it was a .elements
|
|
338
|
-
transformers.params = flatten;
|
|
339
|
-
|
|
340
296
|
applyTransformations(csn, transformers, [], iterateOptions);
|
|
341
297
|
|
|
342
298
|
/**
|
|
@@ -474,7 +430,7 @@ function getBranches( element, elementName, effectiveType, pathDelimiter ) {
|
|
|
474
430
|
* @param {object} iterateOptions
|
|
475
431
|
*/
|
|
476
432
|
function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFunctions, pathDelimiter, flattenKeyRefs, csnUtils, iterateOptions = {} ) {
|
|
477
|
-
const { error
|
|
433
|
+
const { error } = messageFunctions;
|
|
478
434
|
const { inspectRef, isStructured } = csnUtils;
|
|
479
435
|
const { flattenStructStepsInRef, flattenStructuredElement } = transformUtils.getTransformers(csn, options, messageFunctions, pathDelimiter);
|
|
480
436
|
if (flattenKeyRefs) {
|
|
@@ -572,10 +528,7 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFun
|
|
|
572
528
|
}
|
|
573
529
|
else if (assoc.keys[i].ref && !assoc.keys[i].as) {
|
|
574
530
|
setProp(assoc.keys[i], inferredAlias, true);
|
|
575
|
-
|
|
576
|
-
// In OData backend there are no aliases assigned when the same as the ref
|
|
577
|
-
// TODO: remove the if after the new flattening in OData has been completed
|
|
578
|
-
assoc.keys[i].as = assoc.keys[i].ref[assoc.keys[i].ref.length - 1];
|
|
531
|
+
assoc.keys[i].as = assoc.keys[i].ref[assoc.keys[i].ref.length - 1];
|
|
579
532
|
collector.push(assoc.keys[i]);
|
|
580
533
|
}
|
|
581
534
|
else {
|
|
@@ -632,8 +585,6 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFun
|
|
|
632
585
|
const transformers = {
|
|
633
586
|
elements: createFks,
|
|
634
587
|
};
|
|
635
|
-
if (options.toOdata)
|
|
636
|
-
transformers.params = createFks;
|
|
637
588
|
|
|
638
589
|
applyTransformations(csn, transformers, [], Object.assign({ skipIgnore: false }, iterateOptions));
|
|
639
590
|
|
|
@@ -661,9 +612,7 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFun
|
|
|
661
612
|
acc[fk[0]] = 1;
|
|
662
613
|
|
|
663
614
|
// check for name clash with existing elements
|
|
664
|
-
if (
|
|
665
|
-
((options.toOdata && isDeepEqual(element, parent[prop][fk[0]], true)) ||
|
|
666
|
-
!options.toOdata)) {
|
|
615
|
+
if (parent[prop][fk[0]]) {
|
|
667
616
|
// error location is the colliding element
|
|
668
617
|
error('name-duplicate-element', eltPath, { '#': 'flatten-fkey-exists', name: fk[0], art: elementName });
|
|
669
618
|
}
|
|
@@ -690,29 +639,18 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFun
|
|
|
690
639
|
key.$generatedFieldName = fks[i][0];
|
|
691
640
|
key.ref = [ (key.$ref || key.ref).join(pathDelimiter) ];
|
|
692
641
|
delete key.$ref;
|
|
693
|
-
|
|
694
|
-
if (options.
|
|
695
|
-
|
|
642
|
+
const fk = fks[i][1];
|
|
643
|
+
if (options.transformation === 'effective')
|
|
644
|
+
copyAnnotations(key, fk);
|
|
696
645
|
}
|
|
697
646
|
});
|
|
698
|
-
// OData specific:
|
|
699
|
-
// Not Null sets min cardinality to 1
|
|
700
|
-
if (options.toOdata && element.notNull) {
|
|
701
|
-
if (element.cardinality === undefined)
|
|
702
|
-
element.cardinality = {};
|
|
703
|
-
// min=0 is falsy => check for undefined
|
|
704
|
-
if (element.cardinality.min === undefined) {
|
|
705
|
-
element.cardinality.min = 1;
|
|
706
|
-
}
|
|
707
|
-
else if (element.cardinality.min === 0) {
|
|
708
|
-
warning(null, element.$path, { value: cardinality2str(element, false), code: 'not null' },
|
|
709
|
-
'Expected target cardinality $(VALUE) and $(CODE) to match');
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
647
|
|
|
713
648
|
if (options.transformation === 'effective')
|
|
714
649
|
delete element.default;
|
|
715
650
|
}
|
|
651
|
+
|
|
652
|
+
if (options.transformation === 'effective')
|
|
653
|
+
adaptAnnotationsRefs(fks, csnUtils, messageFunctions, eltPath);
|
|
716
654
|
orderedElements.push(...fks);
|
|
717
655
|
});
|
|
718
656
|
|
|
@@ -737,9 +675,10 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFun
|
|
|
737
675
|
* @param {object} options
|
|
738
676
|
* @param {string} pathDelimiter
|
|
739
677
|
* @param {number} lvl
|
|
678
|
+
* @param {object} originalKey
|
|
740
679
|
* @returns {Array[]} First element of every sub-array is the foreign key name, second is the foreign key definition
|
|
741
680
|
*/
|
|
742
|
-
function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathDelimiter, lvl = 0 ) {
|
|
681
|
+
function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathDelimiter, lvl = 0, originalKey = { }) {
|
|
743
682
|
const special$self = !csn?.definitions?.$self && '$self';
|
|
744
683
|
const isInspectRefResult = !Array.isArray(path);
|
|
745
684
|
|
|
@@ -785,7 +724,9 @@ function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathD
|
|
|
785
724
|
const continuePath = getContinuePath([ 'keys', keyIndex ]);
|
|
786
725
|
const alias = key.as || implicitAs(key.ref);
|
|
787
726
|
const result = csnUtils.inspectRef(continuePath);
|
|
788
|
-
fks = fks.concat(createForeignKeys(csnUtils, result, result.art, alias, csn, options, pathDelimiter, lvl + 1
|
|
727
|
+
fks = fks.concat(createForeignKeys(csnUtils, result, result.art, alias, csn, options, pathDelimiter, lvl + 1, lvl === 0 ? {
|
|
728
|
+
ref: key.ref, as: key.as, $path: key.$path, $originalKeyRef: key.$originalKeyRef,
|
|
729
|
+
} : originalKey));
|
|
789
730
|
});
|
|
790
731
|
if (!hasKeys)
|
|
791
732
|
delete finalElement.keys;
|
|
@@ -799,7 +740,7 @@ function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathD
|
|
|
799
740
|
// Skip already produced foreign keys
|
|
800
741
|
if (!elem['@odata.foreignKey4']) {
|
|
801
742
|
const continuePath = getContinuePath([ 'elements', elemName ]);
|
|
802
|
-
fks = fks.concat(createForeignKeys(csnUtils, continuePath, elem, elemName, csn, options, pathDelimiter, lvl + 1));
|
|
743
|
+
fks = fks.concat(createForeignKeys(csnUtils, continuePath, elem, elemName, csn, options, pathDelimiter, lvl + 1, originalKey));
|
|
803
744
|
}
|
|
804
745
|
});
|
|
805
746
|
}
|
|
@@ -811,7 +752,7 @@ function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathD
|
|
|
811
752
|
if (element[prop] !== undefined)
|
|
812
753
|
newFk[prop] = element[prop];
|
|
813
754
|
});
|
|
814
|
-
return [ [ prefix, newFk ] ];
|
|
755
|
+
return [ [ prefix, newFk, originalKey ] ];
|
|
815
756
|
}
|
|
816
757
|
|
|
817
758
|
fks.forEach((fk) => {
|
|
@@ -61,8 +61,8 @@ function replaceAssociationsInGroupByOrderBy( inputQuery, options, inspectRef, e
|
|
|
61
61
|
// (230 d) If we keep associations as they are (hdbcds naming convention), we can't have associations in ORDER BY
|
|
62
62
|
if (options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds') {
|
|
63
63
|
error(null, orderByPath,
|
|
64
|
-
{ $reviewed: true },
|
|
65
|
-
'Unexpected managed association in
|
|
64
|
+
{ $reviewed: true, keyword: 'ORDER BY', value: 'hdbcds' },
|
|
65
|
+
'Unexpected managed association in $(KEYWORD) for naming mode $(VALUE)');
|
|
66
66
|
continue;
|
|
67
67
|
}
|
|
68
68
|
const pathPrefix = query.orderBy[i].ref.slice(0, -1);
|
|
@@ -311,25 +311,20 @@ function rewriteCalculatedElementsInViews( csn, options, csnUtils, pathDelimiter
|
|
|
311
311
|
|
|
312
312
|
const refPrefix = refBase.slice(0, -1);
|
|
313
313
|
const linksPrefix = linksBase.slice(0, -1);
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
}
|
|
329
|
-
else if (clone.value.ref && clone.value.ref[0] !== '$self' && clone.value.ref[0] !== '$projection' ) {
|
|
330
|
-
clone.value.ref = [ ...refPrefix, ...clone.value.ref ];
|
|
331
|
-
clone.value._links = [ ...linksPrefix, ...clone.value._links ]; // TODO: Make non-enum, increment idx
|
|
332
|
-
}
|
|
314
|
+
|
|
315
|
+
// We need to adapt the scope of all refs in the new .xpr, as it might have been at a different "root"
|
|
316
|
+
applyTransformationsOnNonDictionary(clone, 'value', {
|
|
317
|
+
ref: (p, prop, ref) => {
|
|
318
|
+
if (ref[0] !== '$self' && ref[0] !== '$projection') {
|
|
319
|
+
p.ref = [ ...refPrefix, ...ref ];
|
|
320
|
+
if (p._links)
|
|
321
|
+
p._links = [ ...linksPrefix, ...p._links ]; // TODO: Make non-enum, increment idx
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
}, {
|
|
325
|
+
// Do not rewrite refs inside an association-where; avoids endless loop
|
|
326
|
+
skipStandard: { where: true },
|
|
327
|
+
});
|
|
333
328
|
return clone.value;
|
|
334
329
|
}
|
|
335
330
|
|
|
@@ -206,7 +206,8 @@ function getAnnotationHandler( csn, options, pathDelimiter, messageFunctions ) {
|
|
|
206
206
|
if (validKey.length) {
|
|
207
207
|
if (!validFrom.length || !validTo.length) {
|
|
208
208
|
error(null, [ 'definitions', artifactName ],
|
|
209
|
-
'
|
|
209
|
+
{ name: '@cds.valid.from', id: '@cds.valid.to', anno: '@cds.valid.key' },
|
|
210
|
+
'Expecting $(NAME) and $(ID) if $(ANNO) is used');
|
|
210
211
|
}
|
|
211
212
|
|
|
212
213
|
forEachMember(artifact, (member) => {
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { transformAnnotationExpression, implicitAs, } = require('../../model/csnUtils');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Used during annotating of foreign keys.
|
|
7
|
+
* Expression annotations which are assigned to a foreign key are examined. If some ref point to another foreign
|
|
8
|
+
* key declared in the scope, we replace it with referencing the foreign key itself. If a reference is a $self reference,
|
|
9
|
+
* we do nothing and if a ref points to a structure/managed association, an error is thrown
|
|
10
|
+
*
|
|
11
|
+
* @param {Array[]} generatedForeignKeys
|
|
12
|
+
* @param {object} csnUtils
|
|
13
|
+
* @param {object} messageFunctions
|
|
14
|
+
* @param {CSN.Path} elementPath
|
|
15
|
+
*/
|
|
16
|
+
function adaptAnnotationsRefs(generatedForeignKeys, csnUtils, { error }, elementPath) {
|
|
17
|
+
const reportedErrorsForAnnoPath = {};
|
|
18
|
+
generatedForeignKeys.forEach((gfk, index) => {
|
|
19
|
+
Object.entries(gfk[1]).forEach(([key, value]) => {
|
|
20
|
+
if (key[0] !== '@') return;
|
|
21
|
+
|
|
22
|
+
transformAnnotationExpression(gfk[1], key, {
|
|
23
|
+
ref: (_parent, _prop, ref, path, _p, _ppn, ctx) => {
|
|
24
|
+
if (ref[0] !== '$self') {
|
|
25
|
+
const { art } = csnUtils.inspectRef(elementPath ? path : getOriginatingKeyPath(gfk, path)); // OData uses getOriginatingKeyPath - as it relies on $path
|
|
26
|
+
if (csnUtils.isManagedAssociation(art)) {
|
|
27
|
+
if (!reportedErrorsForAnnoPath[path]) {
|
|
28
|
+
error('odata-anno-xpr-ref', path, { elemref: { ref }, anno: key, '#': 'fk_substitution' });
|
|
29
|
+
reportedErrorsForAnnoPath[path] = true;
|
|
30
|
+
}
|
|
31
|
+
} else {
|
|
32
|
+
const gfkForRef = findGeneratedForeignKeyForKeyRef(generatedForeignKeys, ref);
|
|
33
|
+
if (gfkForRef.length === 1) {
|
|
34
|
+
ref[0] = gfkForRef[0][0];
|
|
35
|
+
|
|
36
|
+
if (ctx?.annoExpr?.['=']) {
|
|
37
|
+
ctx.annoExpr['='] = true;
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
// check if the annotation reference points to a structure that has been expanded,
|
|
41
|
+
// if so -> report an error
|
|
42
|
+
const foundInOriginalRef = findOriginalRef(generatedForeignKeys.filter(gfk => gfk[2].$originalKeyRef), ref);
|
|
43
|
+
// references to expanded structures in flat mode will be found in the $originalKeyRef
|
|
44
|
+
// and in structured mode more than one match will be found in the generated foreign keys
|
|
45
|
+
if ((foundInOriginalRef.length || gfkForRef.length > 1) && !reportedErrorsForAnnoPath[path]) {
|
|
46
|
+
error('odata-anno-xpr-ref', path, { elemref: { ref }, anno: key, '#': 'fk_substitution' });
|
|
47
|
+
reportedErrorsForAnnoPath[path] = true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}, elementPath ? elementPath.concat(['keys', index]) : value?.$path?.slice(0, value.$path.length - 1)); // OData uses $path
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// During tuple expansion, the key ref object looses the $path, therefore
|
|
58
|
+
// it needs to be extracted from the anno path
|
|
59
|
+
function getOriginatingKeyPath(gfk, path) {
|
|
60
|
+
return gfk[2].$path || path.slice(0, path.findIndex(ps => ps[0] === '@'));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Loops through the generated foreign keys for this entity
|
|
64
|
+
// and filters the ones, which were created for this specific
|
|
65
|
+
// key ref. In case there are more than one foreign keys found,
|
|
66
|
+
// that means the key ref points to a structured element/managed assoc
|
|
67
|
+
function findGeneratedForeignKeyForKeyRef(generatedForeignKeys, ref) {
|
|
68
|
+
return generatedForeignKeys.filter(gfk => (ref.join() === (gfk[2].as || implicitAs(gfk[2].ref))));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Tuple expansion is performed before the generation of the foreign keys and the original(unexpanded) key ref
|
|
72
|
+
// is stored in the property $originalKeyRef. Here we try to evaluate whether the reference in the annotation
|
|
73
|
+
// points to a structure that has been expanded.
|
|
74
|
+
function findOriginalRef(generatedForeignKeys, ref) {
|
|
75
|
+
return generatedForeignKeys.filter(gfk => (ref.join() === (gfk[2].$originalKeyRef.as || implicitAs(gfk[2].$originalKeyRef.ref))));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = adaptAnnotationsRefs;
|
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
const { isBuiltinType } = require('../../base/builtins');
|
|
4
4
|
const { setProp } = require('../../base/model');
|
|
5
|
-
const {
|
|
5
|
+
const { applyTransformations, implicitAs, findAnnotationExpression, copyAnnotations, isDeepEqual } = require('../../model/csnUtils');
|
|
6
6
|
const { EdmTypeFacetNames } = require('../../edm/EdmPrimitiveTypeDefinitions');
|
|
7
|
+
const adaptAnnotationsRefs = require('./adaptAnnotationRefs');
|
|
7
8
|
|
|
8
9
|
function createForeignKeyElements(csn, options, messageFunctions, csnUtils, iterateOptions = {}) {
|
|
9
10
|
|
|
10
11
|
const { error } = messageFunctions;
|
|
11
12
|
|
|
12
13
|
applyTransformations(csn, { elements: createForeignKeysInCsn, params: createForeignKeysInCsn},
|
|
13
|
-
[], Object.assign(iterateOptions, { skip: ['event']
|
|
14
|
+
[], Object.assign(iterateOptions, { skip: ['event'] }));
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Process a given elements or params dictionary and create foreign key elements.
|
|
@@ -61,7 +62,7 @@ function createForeignKeyElements(csn, options, messageFunctions, csnUtils, iter
|
|
|
61
62
|
delete element.default;
|
|
62
63
|
}
|
|
63
64
|
|
|
64
|
-
adaptAnnotationsRefs(generatedForeignKeys, csnUtils);
|
|
65
|
+
adaptAnnotationsRefs(generatedForeignKeys, csnUtils, messageFunctions);
|
|
65
66
|
setProp(element, '$generatedForeignKeys', generatedForeignKeys.map(gfk => gfk[0]));
|
|
66
67
|
orderedElements.push(...generatedForeignKeys);
|
|
67
68
|
|
|
@@ -190,74 +191,6 @@ function createForeignKeysForElement(csnUtils, path, element, prefix, csn, optio
|
|
|
190
191
|
return [ ...path, ...additions ];
|
|
191
192
|
}
|
|
192
193
|
}
|
|
193
|
-
|
|
194
|
-
function adaptAnnotationsRefs(generatedForeignKeys, csnUtils) {
|
|
195
|
-
let reportedErrorsForAnnoPath = {};
|
|
196
|
-
generatedForeignKeys.forEach(gfk => {
|
|
197
|
-
Object.entries(gfk[1]).forEach(([key, value]) => {
|
|
198
|
-
if (key[0] !== '@') return;
|
|
199
|
-
|
|
200
|
-
transformAnnotationExpression(gfk[1], key, {
|
|
201
|
-
ref: (_parent, _prop, ref, path, _p, _ppn, ctx) => {
|
|
202
|
-
if (ref[0] !== '$self') {
|
|
203
|
-
|
|
204
|
-
const { art } = csnUtils.inspectRef(getOriginatingKeyPath(gfk, path));
|
|
205
|
-
if (csnUtils.isManagedAssociation(art)) {
|
|
206
|
-
if (!reportedErrorsForAnnoPath[path]) {
|
|
207
|
-
error('odata-anno-xpr-ref', path, { elemref: { ref }, anno: key, '#': 'fk_substitution' });
|
|
208
|
-
reportedErrorsForAnnoPath[path] = true;
|
|
209
|
-
}
|
|
210
|
-
} else {
|
|
211
|
-
const gfkForRef = findGeneratedForeignKeyForKeyRef(generatedForeignKeys, ref);
|
|
212
|
-
if (gfkForRef.length === 1) {
|
|
213
|
-
ref[0] = gfkForRef[0][0];
|
|
214
|
-
|
|
215
|
-
if (ctx?.annoExpr?.['=']) {
|
|
216
|
-
ctx.annoExpr['='] = true;
|
|
217
|
-
}
|
|
218
|
-
} else {
|
|
219
|
-
// check if the annotation reference points to a structure that has been expanded,
|
|
220
|
-
// if so -> report an error
|
|
221
|
-
const foundInOriginalRef = findOriginalRef(generatedForeignKeys.filter(gfk => gfk[2].$originalKeyRef), ref);
|
|
222
|
-
// references to expanded structures in flat mode will be found in the $originalKeyRef
|
|
223
|
-
// and in strucred mode more than one match will be found in the generated foreign keys
|
|
224
|
-
if ((foundInOriginalRef.length || gfkForRef.length > 1) && !reportedErrorsForAnnoPath[path]) {
|
|
225
|
-
error('odata-anno-xpr-ref', path, { elemref: { ref }, anno: key, '#': 'fk_substitution' });
|
|
226
|
-
reportedErrorsForAnnoPath[path] = true;
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
}, value?.$path?.slice(0, value.$path.length - 1));
|
|
233
|
-
});
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
// During tuple expansion, the key ref object looses the $path, therefore
|
|
237
|
-
// it needs to be extracted from the anno path
|
|
238
|
-
function getOriginatingKeyPath(gfk, path) {
|
|
239
|
-
return gfk[2].$path || path.slice(0, path.findIndex(ps => ps[0] === '@'));
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Loops through the generated foreign keys for this entity
|
|
243
|
-
// and filters the ones, which were created for this specific
|
|
244
|
-
// key ref. In case there are more than one foreign keys found,
|
|
245
|
-
// that means the key ref points to a structured element/managed assoc
|
|
246
|
-
function findGeneratedForeignKeyForKeyRef(generatedForeignKeys, ref) {
|
|
247
|
-
return generatedForeignKeys.filter(gfk => {
|
|
248
|
-
return (ref.join() === (gfk[2].as || implicitAs(gfk[2].ref)));
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// Tuple expansion is performed before the generation of the foreign keys and the original(unexpanded) key ref
|
|
253
|
-
// is stored in the property $originalKeyRef. Here we try to evaluate whether the reference in the annotation
|
|
254
|
-
// points to a structure that has been expanded.
|
|
255
|
-
function findOriginalRef(generatedForeignKeys, ref) {
|
|
256
|
-
return generatedForeignKeys.filter(gfk => {
|
|
257
|
-
return (ref.join() === (gfk[2].$originalKeyRef.as || implicitAs(gfk[2].$originalKeyRef.ref)));
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
194
|
}
|
|
262
195
|
|
|
263
196
|
module.exports = createForeignKeyElements;
|
|
@@ -64,6 +64,15 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, getFinalTy
|
|
|
64
64
|
allMgdAssocDefs.push(elt);
|
|
65
65
|
}
|
|
66
66
|
}, [ flatEltName ], true, { pathWithoutProp: true } );
|
|
67
|
+
} else if (flatElt.targetAspect) {
|
|
68
|
+
forEachMemberRecursively(flatElt.targetAspect, (elt, _eltName, _prop, _path) => {
|
|
69
|
+
// TODO: check whether that needs to be done for targetAspects as well
|
|
70
|
+
// const exprAnnos = Object.keys(elt).filter(pn => pn[0] === '@');
|
|
71
|
+
// flattenAndPrefixExprPaths(elt, exprAnnos, elt.$path, path, 0, true);
|
|
72
|
+
if (csnUtils.isManagedAssociation(elt)) {
|
|
73
|
+
allMgdAssocDefs.push(elt);
|
|
74
|
+
}
|
|
75
|
+
}, [ flatEltName ], true, { pathWithoutProp: true } );
|
|
67
76
|
}
|
|
68
77
|
setProp(flatElt, '$pathInStructuredModel', flatElt.$path);
|
|
69
78
|
setProp(flatElt, '$path', [ ...csnPath, flatEltName ]);
|
|
@@ -339,7 +348,8 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, getFinalTy
|
|
|
339
348
|
|
|
340
349
|
let hasItems = false;
|
|
341
350
|
for(; i >= 0 && !getProp('target') && !hasItems; i--) {
|
|
342
|
-
|
|
351
|
+
const art = links[i].art;
|
|
352
|
+
hasItems = !!getProp('items') || (art.type && !!csnUtils.getFinalTypeInfo(art.type)?.items)
|
|
343
353
|
}
|
|
344
354
|
if(!hasItems) {
|
|
345
355
|
const ft = csnUtils.getFinalTypeInfo(art.type);
|
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
const { setProp } = require('../base/model');
|
|
8
8
|
|
|
9
|
-
const { copyAnnotations, applyTransformations, isDollarSelfOrProjectionOperand } = require('../model/csnUtils');
|
|
9
|
+
const { copyAnnotations, applyTransformations, isDollarSelfOrProjectionOperand, transformAnnotationExpression } = require('../model/csnUtils');
|
|
10
10
|
const { getUtils } = require('../model/csnUtils');
|
|
11
11
|
const { typeParameters } = require('../compiler/builtins');
|
|
12
|
-
const { isBuiltinType } = require('../base/builtins');
|
|
12
|
+
const { isBuiltinType, isAnnotationExpression } = require('../base/builtins');
|
|
13
13
|
const { ModelError, CompilerAssertion} = require('../base/error');
|
|
14
14
|
const { forEach } = require('../utils/objectUtils');
|
|
15
15
|
const { cloneCsnNonDict, cloneCsnDict } = require('../model/cloneCsn');
|
|
@@ -39,11 +39,9 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
39
39
|
resolvePath,
|
|
40
40
|
flattenPath,
|
|
41
41
|
addDefaultTypeFacets,
|
|
42
|
-
getForeignKeyArtifact,
|
|
43
42
|
flattenStructuredElement,
|
|
44
43
|
flattenStructStepsInRef,
|
|
45
44
|
toFinalBaseType,
|
|
46
|
-
copyTypeProperties,
|
|
47
45
|
createExposingProjection,
|
|
48
46
|
createAndAddDraftAdminDataProjection,
|
|
49
47
|
createScalarElement,
|
|
@@ -105,53 +103,6 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
105
103
|
*/
|
|
106
104
|
}
|
|
107
105
|
|
|
108
|
-
// createRealFK 'really' creates the new foreign key element and is used by the two
|
|
109
|
-
// main FK generators getForeignKeyArtifact & createForeignKeyElement
|
|
110
|
-
// and (not yet) generateForeignKeyElements.js::generateForeignKeysForRef()
|
|
111
|
-
// TODO/FIXME: Can they be combined?
|
|
112
|
-
function createRealFK(fkArtifact, assoc, assocName, foreignKey, path, foreignKeyElementName) {
|
|
113
|
-
const foreignKeyElement = Object.create(null);
|
|
114
|
-
|
|
115
|
-
// Transfer selected type properties from target key element
|
|
116
|
-
// FIXME: There is currently no other way but to treat the annotation '@odata.Type' as a type property.
|
|
117
|
-
for (const prop of ['type', 'length', 'scale', 'precision', 'srid', 'default', '@odata.Type']) {
|
|
118
|
-
if (fkArtifact[prop] !== undefined) {
|
|
119
|
-
foreignKeyElement[prop] = fkArtifact[prop];
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (options.transformation === 'odata' || options.transformation === 'effective')
|
|
124
|
-
copyAnnotations(assoc, foreignKeyElement, true);
|
|
125
|
-
|
|
126
|
-
// If the association is non-fkArtifact resp. key, so should be the foreign key field
|
|
127
|
-
for (const prop of ['notNull', 'key']) {
|
|
128
|
-
if (assoc[prop] !== undefined) {
|
|
129
|
-
foreignKeyElement[prop] = assoc[prop];
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Establish the relationship between generated field and association:
|
|
134
|
-
// - generated field has annotation '@odata.foreignKey4'.
|
|
135
|
-
// - foreign key info has 'generatedFieldName'
|
|
136
|
-
if(options.transformation !== 'effective')
|
|
137
|
-
foreignKeyElement['@odata.foreignKey4'] = assocName;
|
|
138
|
-
foreignKey.$generatedFieldName = foreignKeyElementName;
|
|
139
|
-
// attach $path to the newly created element - used for inspectRef in processAssociationOrComposition
|
|
140
|
-
setProp(foreignKeyElement, '$path', path);
|
|
141
|
-
if (assoc.$location) {
|
|
142
|
-
setProp(foreignKeyElement, '$location', assoc.$location);
|
|
143
|
-
}
|
|
144
|
-
return foreignKeyElement;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function getForeignKeyArtifact(assoc, assocName, foreignKey, path) {
|
|
148
|
-
const fkArtifact = inspectRef(path).art;
|
|
149
|
-
// FIXME: Duplicate code
|
|
150
|
-
// Assemble foreign key element name from assoc name, '_' and foreign key name/alias
|
|
151
|
-
const foreignKeyElementName = `${assocName.replace(/\./g, pathDelimiter)}${pathDelimiter}${foreignKey.as || foreignKey.ref.join(pathDelimiter)}`;
|
|
152
|
-
return [ foreignKeyElementName, createRealFK(fkArtifact, assoc, assocName, foreignKey, path, foreignKeyElementName) ];
|
|
153
|
-
}
|
|
154
|
-
|
|
155
106
|
// For a structured element 'elem', return a dictionary of flattened elements to
|
|
156
107
|
// replace it, flattening names with pathDelimiter's value and propagating all annotations and the
|
|
157
108
|
// type properties 'key', 'notNull', 'virtual', 'masked' to the flattened elements.
|
|
@@ -244,7 +195,24 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
244
195
|
'@odata.Collation': 1,
|
|
245
196
|
'@odata.Unicode': 1,
|
|
246
197
|
} : {};
|
|
247
|
-
copyAnnotations(elem, flatElem, false, excludes);
|
|
198
|
+
const annoNames = copyAnnotations(elem, flatElem, false, excludes);
|
|
199
|
+
if(options.transformation === 'effective') {
|
|
200
|
+
for(const annoName of annoNames) {
|
|
201
|
+
if(flatElem.keys && isAnnotationExpression(elem[annoName])) {
|
|
202
|
+
flatElem.keys.forEach(key => {
|
|
203
|
+
if(!key[annoName]) {
|
|
204
|
+
key[annoName] = flatElem[annoName];
|
|
205
|
+
transformAnnotationExpression(key, annoName, {
|
|
206
|
+
ref: (parent, prop, ref) => {
|
|
207
|
+
if(ref[0] !== '$self')
|
|
208
|
+
parent.ref = ['$self', ...parent.ref];
|
|
209
|
+
}
|
|
210
|
+
})
|
|
211
|
+
}
|
|
212
|
+
})
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
248
216
|
|
|
249
217
|
// Copy selected type properties
|
|
250
218
|
const props = ['key', 'virtual', 'masked', 'viaAll'];
|
|
@@ -335,39 +303,6 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
335
303
|
return [ result, refChanged ];
|
|
336
304
|
}
|
|
337
305
|
|
|
338
|
-
/**
|
|
339
|
-
* Copy properties of the referenced type, but don't resolve to the final base type.
|
|
340
|
-
*
|
|
341
|
-
* Do not copy the length if it was just set via the default-value.
|
|
342
|
-
*
|
|
343
|
-
* @param {any} node Node to copy to
|
|
344
|
-
* @returns {void}
|
|
345
|
-
*/
|
|
346
|
-
function copyTypeProperties(node) {
|
|
347
|
-
// Nothing to do if no type (or if array/struct type)
|
|
348
|
-
if (!node || !node.type) return;
|
|
349
|
-
// .. or if it is a ref
|
|
350
|
-
if (node.type && node.type.ref) return;
|
|
351
|
-
// .. or builtin already
|
|
352
|
-
if (node.type && isBuiltinType(node.type)) return;
|
|
353
|
-
|
|
354
|
-
// The type might already be a full fledged type def (array of)
|
|
355
|
-
const typeDef = typeof node.type === 'string' ? getCsnDef(node.type) : node.type;
|
|
356
|
-
// Nothing to do if type is an array or a struct type
|
|
357
|
-
if (typeDef.items || typeDef.elements) return;
|
|
358
|
-
// if the declared element is an enum, these values are with priority
|
|
359
|
-
if (!node.enum && typeDef.enum)
|
|
360
|
-
Object.assign(node, { enum: typeDef.enum });
|
|
361
|
-
if (!node.length && typeDef.length && !typeDef.$default)
|
|
362
|
-
Object.assign(node, { length: typeDef.length });
|
|
363
|
-
if (!node.precision && typeDef.precision)
|
|
364
|
-
Object.assign(node, { precision: typeDef.precision });
|
|
365
|
-
if (!node.scale && typeDef.scale)
|
|
366
|
-
Object.assign(node, { scale: typeDef.scale });
|
|
367
|
-
if (!node.srid && typeDef.srid)
|
|
368
|
-
Object.assign(node, { srid: typeDef.srid });
|
|
369
|
-
}
|
|
370
|
-
|
|
371
306
|
/**
|
|
372
307
|
* Replace the type of 'nodeWithType' with its final base type, i.e. copy relevant type properties and
|
|
373
308
|
* set the `type` property to the builtin if scalar or delete it if structured/arrayed.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sap/cds-compiler",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.7.0",
|
|
4
4
|
"description": "CDS (Core Data Services) compiler and backends",
|
|
5
5
|
"homepage": "https://cap.cloud.sap/",
|
|
6
6
|
"author": "SAP SE (https://www.sap.com)",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"test:ci": "node scripts/verifyGrammarChecksum.js && mocha --timeout 10000 --reporter-option maxDiffSize=0 scripts/testLazyLoading.js && mocha --parallel --reporter-option maxDiffSize=0 test/ test3/",
|
|
23
23
|
"test:piper": "node scripts/verifyGrammarChecksum.js && npm run coverage:piper",
|
|
24
24
|
"test3": "node scripts/verifyGrammarChecksum.js && mocha --reporter-option maxDiffSize=0 test3/",
|
|
25
|
+
"testStandardDatabaseFunctions": "CDS_COMPILER_STANDARD_DB_FUNCTIONS=1 mocha --reporter-option maxDiffSize=0 test3/test.standard-database-functions.js",
|
|
25
26
|
"deployHanaSql": "CDS_COMPILER_DEPLOY_HANA=1 mocha --reporter-option maxDiffSize=0 test3/test.deploy.hana-sql.js",
|
|
26
27
|
"deployHdiHdbcds": "CDS_COMPILER_DEPLOY_HANA=1 mocha --reporter-option maxDiffSize=0 test3/test.deploy.hdi.hdbcds.js",
|
|
27
28
|
"deployHdi": "CDS_COMPILER_DEPLOY_HANA=1 mocha --reporter-option maxDiffSize=0 --extensions .hdi test3/test.deploy.hdi.hdbcds.js",
|