@sap/cds-compiler 4.9.4 → 4.9.8
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 +16 -0
- package/lib/compiler/define.js +3 -1
- package/lib/edm/annotations/genericTranslation.js +26 -7
- package/lib/edm/edm.js +8 -0
- package/lib/edm/edmPreprocessor.js +7 -3
- package/lib/transform/db/flattening.js +3 -2
- package/lib/transform/effective/main.js +0 -2
- package/lib/transform/translateAssocsToJoins.js +21 -2
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,22 @@
|
|
|
7
7
|
Note: `beta` fixes, changes and features are usually not listed in this ChangeLog but [here](doc/CHANGELOG_BETA.md).
|
|
8
8
|
The compiler behavior concerning `beta` features can change at any time without notice.
|
|
9
9
|
|
|
10
|
+
## Version 4.9.8 - 2024-07-29
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- compiler: Fix extensions with bound actions using an explicit binding parameter in `parseCdl` CSN.
|
|
15
|
+
- to.edm(x): No `Nullable` attribute for `$ReturnType` of `Collection(<entity type>)` [OData V4 CSDL, section 12.8 Return Type](https://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_ReturnType)
|
|
16
|
+
|
|
17
|
+
## Version 4.9.6 - 2024-07-15
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- for.seal: Don't generate DRAFT artifacts.
|
|
22
|
+
- for.odata: Propagate all `@odata { Type, MaxLength, Precision, Scale, SRID }` to generated foreign keys.
|
|
23
|
+
- to.edm(x): Respect `AppliesTo` specification in term definitions for actions and functions.
|
|
24
|
+
- to.sql: Conditions inside filters in combination with foreign key aliases were not properly translated in rare cases.
|
|
25
|
+
|
|
10
26
|
## Version 4.9.4 - 2024-05-21
|
|
11
27
|
|
|
12
28
|
### Fixed
|
package/lib/compiler/define.js
CHANGED
|
@@ -1203,7 +1203,9 @@ function define( model ) {
|
|
|
1203
1203
|
const type = first?.type || first?.items?.type; // this sequence = no derived type
|
|
1204
1204
|
const path = type?.path;
|
|
1205
1205
|
if (path?.length === 1 && path[0]?.id === '$self') { // TODO: no where: ?
|
|
1206
|
-
const
|
|
1206
|
+
const $self = main.$tableAliases?.$self ||
|
|
1207
|
+
main.kind === 'extend' && { name: { id: '$self' } };
|
|
1208
|
+
// remark: an extend has no "table alias" `$self` (relevant for parse-cdl)
|
|
1207
1209
|
setLink( type, '_artifact', $self );
|
|
1208
1210
|
setLink( path[0], '_artifact', $self );
|
|
1209
1211
|
}
|
|
@@ -261,6 +261,7 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
|
|
|
261
261
|
const entityName = nameParts.pop();
|
|
262
262
|
|
|
263
263
|
Object.entries(cObject.actions).forEach(([ n, action ]) => {
|
|
264
|
+
setProp(action, '$isBound', true);
|
|
264
265
|
const actionName = `${serviceName}.${isV2() ? `${entityName}_` : ''}${n}`;
|
|
265
266
|
handleAction(actionName, action, cObjectname, [ ...location, 'actions', n ]);
|
|
266
267
|
});
|
|
@@ -516,13 +517,14 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
|
|
|
516
517
|
if (carrier.kind === 'entity') {
|
|
517
518
|
// If AppliesTo=[EntitySet/Singleton/Collection, EntityType], EntitySet/Singleton/Collection has precedence
|
|
518
519
|
testToAlternativeEdmTargetP = ((x) => {
|
|
519
|
-
if (
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
520
|
+
if (x) {
|
|
521
|
+
if (options.isV2())
|
|
522
|
+
return [ 'Singleton', 'EntitySet', 'Collection' ].some(y => x.includes(y));
|
|
523
|
+
return edmUtils.isSingleton(carrier)
|
|
524
|
+
? x.includes('Singleton')
|
|
525
|
+
: [ 'EntitySet', 'Collection' ].some(y => x.includes(y));
|
|
526
|
+
}
|
|
527
|
+
return true;
|
|
526
528
|
});
|
|
527
529
|
testToStandardEdmTargetP = (x => (x ? x.includes('EntityType') : true));
|
|
528
530
|
// if carrier has an alternate 'entitySetName' use this instead of EdmTargetName
|
|
@@ -540,6 +542,23 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
|
|
|
540
542
|
else if (carrier.kind === 'type') {
|
|
541
543
|
testToStandardEdmTargetP = (x => (x ? x.includes(carrier.elements ? 'ComplexType' : 'TypeDefinition') : true));
|
|
542
544
|
}
|
|
545
|
+
else if (carrier.kind === 'action' || carrier.kind === 'function') {
|
|
546
|
+
const type = carrier.kind === 'action' ? 'Action' : 'Function';
|
|
547
|
+
const container = carrier.kind === 'action' ? 'ActionImport' : 'FunctionImport';
|
|
548
|
+
if (options.isV4()) {
|
|
549
|
+
testToStandardEdmTargetP = (x => (x ? x.includes(type) : true));
|
|
550
|
+
// Unbound actions/functions are Action/FunctionImports and are bound to container target
|
|
551
|
+
testToAlternativeEdmTargetP = (x => (x ? x.includes(container) && !carrier.$isBound : true));
|
|
552
|
+
const lastDotIndex = carrier.name.lastIndexOf('.');
|
|
553
|
+
alternativeEdmTargetNameP = lastDotIndex > -1
|
|
554
|
+
? `${serviceName}.EntityContainer/${carrier.name.substring(lastDotIndex + 1)}`
|
|
555
|
+
: `${serviceName}.EntityContainer/${carrier.name}`;
|
|
556
|
+
hasAlternativeCarrierP = true;
|
|
557
|
+
}
|
|
558
|
+
if (options.isV2())
|
|
559
|
+
// same as in V4 but everything goes to standard target
|
|
560
|
+
testToStandardEdmTargetP = (x => (x ? x.includes(type) || (x.includes(container) && !carrier.$isBound) : true));
|
|
561
|
+
}
|
|
543
562
|
else if (carrier.kind === 'service') {
|
|
544
563
|
// if annotated object is a service, annotation goes to EntityContainer,
|
|
545
564
|
// except if AppliesTo contains Schema but not EntityContainer, then annotation goes to Schema
|
package/lib/edm/edm.js
CHANGED
|
@@ -882,12 +882,20 @@ function getEdm( options, messageFunctions ) {
|
|
|
882
882
|
class ReturnType extends PropertyBase {
|
|
883
883
|
constructor(version, csn) {
|
|
884
884
|
super(version, {}, csn);
|
|
885
|
+
// CSDL 12.8: If the return type is a collection of entity types,
|
|
886
|
+
// the Nullable attribute has no meaning and MUST NOT be specified.
|
|
887
|
+
if (csn.$NoNullableProperty)
|
|
888
|
+
delete this._edmAttributes.Nullable;
|
|
885
889
|
}
|
|
886
890
|
|
|
887
891
|
// we need Name but NO $kind, can't use standard to JSON()
|
|
888
892
|
toJSON() {
|
|
889
893
|
const json = Object.create(null);
|
|
890
894
|
this.toJSONattributes(json);
|
|
895
|
+
// CSDL 12.8: If the return type is a collection of entity types,
|
|
896
|
+
// the Nullable attribute has no meaning and MUST NOT be specified.
|
|
897
|
+
if (this._csn.$NoNullableProperty)
|
|
898
|
+
delete json.$Nullable;
|
|
891
899
|
return json;
|
|
892
900
|
}
|
|
893
901
|
}
|
|
@@ -1892,7 +1892,7 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
|
|
|
1892
1892
|
mapCdsToEdmProp(def);
|
|
1893
1893
|
annotateAllowedValues(def, defLocation);
|
|
1894
1894
|
if (def.returns) {
|
|
1895
|
-
markCollection(def.returns);
|
|
1895
|
+
markCollection(def.returns, true);
|
|
1896
1896
|
mapCdsToEdmProp(def.returns);
|
|
1897
1897
|
annotateAllowedValues(def.returns, [ ...defLocation, 'returns' ]);
|
|
1898
1898
|
}
|
|
@@ -1905,16 +1905,20 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
|
|
|
1905
1905
|
rewriteAnnotationExpressions(member);
|
|
1906
1906
|
if (member.returns) {
|
|
1907
1907
|
edmUtils.assignAnnotation(member.returns, '@Core.Description', member.returns.doc);
|
|
1908
|
-
markCollection(member.returns);
|
|
1908
|
+
markCollection(member.returns, true);
|
|
1909
1909
|
mapCdsToEdmProp(member.returns);
|
|
1910
1910
|
annotateAllowedValues(member.returns, [ ...location, 'returns' ]);
|
|
1911
1911
|
rewriteAnnotationExpressions(member.returns);
|
|
1912
1912
|
}
|
|
1913
1913
|
}, defLocation);
|
|
1914
1914
|
// mark members that need to be rendered as collections
|
|
1915
|
-
function markCollection( obj ) {
|
|
1915
|
+
function markCollection( obj, isReturns ) {
|
|
1916
1916
|
const items = obj.items || csn.definitions[obj.type] && csn.definitions[obj.type].items;
|
|
1917
1917
|
if (items) {
|
|
1918
|
+
edmUtils.assignProp(obj, '$NoNullableProperty',
|
|
1919
|
+
isReturns && items.type &&
|
|
1920
|
+
!isBuiltinType(items.type) &&
|
|
1921
|
+
csn.definitions[items.type]?.kind === 'entity');
|
|
1918
1922
|
edmUtils.assignProp(obj, '_NotNullCollection', items.notNull !== undefined ? items.notNull : true);
|
|
1919
1923
|
edmUtils.assignProp(obj, '$isCollection', true);
|
|
1920
1924
|
}
|
|
@@ -16,6 +16,7 @@ const { setProp, isBetaEnabled } = require('../../base/model');
|
|
|
16
16
|
const { forEach } = require('../../utils/objectUtils');
|
|
17
17
|
const { transformExpression } = require('./applyTransformations');
|
|
18
18
|
const { cloneCsnNonDict } = require('../../model/cloneCsn');
|
|
19
|
+
const { EdmTypeFacetNames } = require('../../edm/EdmPrimitiveTypeDefinitions');
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Strip off leading $self from refs where applicable.
|
|
@@ -828,11 +829,11 @@ function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathD
|
|
|
828
829
|
else if (finalElement.type == null || isBuiltinType(finalElement.type)) {
|
|
829
830
|
const newFk = Object.create(null);
|
|
830
831
|
setProp(newFk, '$extensionPath', extensionPath);
|
|
831
|
-
|
|
832
|
+
[ 'type', 'length', 'scale', 'precision', 'srid', 'default', '@odata.Type', ...EdmTypeFacetNames.map(f => `@odata.${f}`) ].forEach((prop) => {
|
|
832
833
|
// copy props from original element to preserve derived types!
|
|
833
834
|
if (element[prop] !== undefined)
|
|
834
835
|
newFk[prop] = element[prop];
|
|
835
|
-
}
|
|
836
|
+
});
|
|
836
837
|
return [ [ prefix, newFk ] ];
|
|
837
838
|
}
|
|
838
839
|
|
|
@@ -11,7 +11,6 @@ const validate = require('../../checks/validator');
|
|
|
11
11
|
const expansion = require('../db/expansion');
|
|
12
12
|
const queries = require('./queries');
|
|
13
13
|
const associations = require('./associations');
|
|
14
|
-
const generateDrafts = require('../draft/db');
|
|
15
14
|
const handleExists = require('../db/transformExists');
|
|
16
15
|
const misc = require('./misc');
|
|
17
16
|
const annotations = require('./annotations');
|
|
@@ -77,7 +76,6 @@ function effectiveCsn( model, options, messageFunctions ) {
|
|
|
77
76
|
processCalculatedElementsInEntities(csn);
|
|
78
77
|
associations.managedToUnmanaged(csn, options, csnUtils, messageFunctions);
|
|
79
78
|
associations.transformBacklinks(csn, options, csnUtils, messageFunctions);
|
|
80
|
-
generateDrafts(csn, options, '_', messageFunctions);
|
|
81
79
|
const transformers = mergeTransformers([ misc.attachPersistenceName(csn, options, csnUtils), options.remapOdataAnnotations ? annotations.remapODataAnnotations(csn) : {}, misc.removeDefinitionsAndProperties(csn, options) ], null);
|
|
82
80
|
applyTransformations(csn, transformers, [], { skipIgnore: false });
|
|
83
81
|
|
|
@@ -1110,8 +1110,27 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1110
1110
|
}
|
|
1111
1111
|
else {
|
|
1112
1112
|
const [ tableAlias, path ] = getTableAliasAndPathSteps(pathNode);
|
|
1113
|
-
const
|
|
1114
|
-
|
|
1113
|
+
const rewrittenPath = [];
|
|
1114
|
+
const leafArtifact = path.at(-1)._artifact;
|
|
1115
|
+
// Walk from left to right and search for first assoc. If assocs in filters become join relevant in the future,
|
|
1116
|
+
// i.e. not only fk-access, we need to revisit this
|
|
1117
|
+
for(let i = 0; i < path.length; i++) {
|
|
1118
|
+
const pathStep = path[i];
|
|
1119
|
+
if(pathStep._artifact?.foreignKeys) {
|
|
1120
|
+
const possibleNonAliasedFkName = path.slice(i).map(ps => ps.id).join(pathDelimiter);
|
|
1121
|
+
if(!pathStep._artifact.$flatSrcFKs)
|
|
1122
|
+
setProp(pathStep._artifact, '$flatSrcFKs', flattenElement(pathStep._artifact, true, pathStep._artifact.name.id, pathStep._artifact.name.id));
|
|
1123
|
+
const fk = pathStep._artifact.$flatSrcFKs.find(f => f._artifact === leafArtifact && f.acc.startsWith(possibleNonAliasedFkName));
|
|
1124
|
+
if(fk) {
|
|
1125
|
+
rewrittenPath.push(fk);
|
|
1126
|
+
i = path.length;
|
|
1127
|
+
continue;
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
rewrittenPath.push(pathStep);
|
|
1132
|
+
}
|
|
1133
|
+
replaceNodeContent(pathNode, constructPathNode([ tableAlias, { id: rewrittenPath.map(ps => ps.id).join(pathDelimiter), _artifact: pathNode._artifact } ]));
|
|
1115
1134
|
}
|
|
1116
1135
|
}
|
|
1117
1136
|
} ]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sap/cds-compiler",
|
|
3
|
-
"version": "4.9.
|
|
3
|
+
"version": "4.9.8",
|
|
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)",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"gentest3": "cross-env MAKEREFS=${MAKEREFS:-'true'} mocha --reporter-option maxDiffSize=0 test3/testRefFiles.js",
|
|
29
29
|
"coverage": "cross-env nyc mocha --reporter-option maxDiffSize=0 test/ test3/testRefFiles.js && nyc report --reporter=lcov",
|
|
30
30
|
"coverage:piper": "cross-env nyc mocha --reporter test/TestMochaReporter.js --reporter-options mochaFile=./coverage/TEST-results.xml --reporter-option maxDiffSize=0 --timeout 10000 test/ test3/ && nyc report --reporter=cobertura && nyc report --reporter=lcov",
|
|
31
|
-
"lint": "eslint bin/ benchmark/ lib/ test/ test3/ scripts/
|
|
31
|
+
"lint": "eslint bin/ benchmark/ lib/ test/ test3/ scripts/ && node scripts/linter/lintGrammar.js && node scripts/linter/lintTests.js test3/ && node scripts/linter/lintMessages.js && node scripts/linter/lintMessageIdCoverage.js lib/ && markdownlint README.md CHANGELOG.md doc/ internalDoc/ && cd share/messages && markdownlint . && cd ../../ && node scripts/check-changelog.js",
|
|
32
32
|
"tslint": "tsc --pretty -p .",
|
|
33
33
|
"updateVocs": "node scripts/odataAnnotations/generateDictMain.js && npm run generateAllRefs",
|
|
34
34
|
"updateTocs": "node scripts/update-toc.js",
|