@sap/cds-compiler 4.9.2 → 4.9.6
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 +17 -0
- package/lib/api/main.js +1 -1
- package/lib/api/options.js +2 -0
- package/lib/edm/annotations/genericTranslation.js +26 -7
- package/lib/transform/db/constraints.js +23 -25
- 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 +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,23 @@
|
|
|
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.6 - 2024-07-15
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- for.seal: Don't generate DRAFT artifacts.
|
|
15
|
+
- for.odata: Propagate all `@odata { Type, MaxLength, Precision, Scale, SRID }` to generated foreign keys.
|
|
16
|
+
- to.edm(x): Respect `AppliesTo` specification in term definitions for actions and functions.
|
|
17
|
+
- to.sql: Conditions inside filters in combination with foreign key aliases were not properly translated in rare cases.
|
|
18
|
+
|
|
19
|
+
## Version 4.9.4 - 2024-05-21
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- to.sql:
|
|
24
|
+
+ always include `tenant` column in foreign key references.
|
|
25
|
+
+ reject `tenantDiscriminator` option only if sql dialect is `hana` and if `withHanaAssociations` option is set.
|
|
26
|
+
|
|
10
27
|
## Version 4.9.2 - 2024-05-13
|
|
11
28
|
|
|
12
29
|
### Fixed
|
package/lib/api/main.js
CHANGED
|
@@ -1293,7 +1293,7 @@ function lazyload( moduleName ) {
|
|
|
1293
1293
|
* @param {object} messageFunctions Message functions
|
|
1294
1294
|
*/
|
|
1295
1295
|
function handleTenantDiscriminator( options, internalOptions, messageFunctions ) {
|
|
1296
|
-
if (options.tenantDiscriminator && options.withHanaAssociations) {
|
|
1296
|
+
if (options.tenantDiscriminator && options.withHanaAssociations && internalOptions.sqlDialect === 'hana') {
|
|
1297
1297
|
messageFunctions.error('api-invalid-combination', null, {
|
|
1298
1298
|
option: 'tenantDiscriminator',
|
|
1299
1299
|
prop: 'withHanaAssociations',
|
package/lib/api/options.js
CHANGED
|
@@ -172,6 +172,7 @@ module.exports = {
|
|
|
172
172
|
},
|
|
173
173
|
hdi: (options) => {
|
|
174
174
|
const hardOptions = { src: 'hdi', toSql: true, forHana: true };
|
|
175
|
+
// TODO: sqlDialect should be a hard option!
|
|
175
176
|
const defaultOptions = {
|
|
176
177
|
sqlMapping: 'plain', sqlDialect: 'hana', generatedByComment: false, withHanaAssociations: true,
|
|
177
178
|
};
|
|
@@ -179,6 +180,7 @@ module.exports = {
|
|
|
179
180
|
},
|
|
180
181
|
hdbcds: (options) => {
|
|
181
182
|
const hardOptions = { forHana: true };
|
|
183
|
+
// TODO: sqlDialect should be a hard option!
|
|
182
184
|
const defaultOptions = { sqlMapping: 'plain', sqlDialect: 'hana' };
|
|
183
185
|
return translateOptions(options, defaultOptions, hardOptions, { sqlDialect: generateStringValidator([ 'hana' ]) }, undefined, 'to.hdbcds');
|
|
184
186
|
},
|
|
@@ -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
|
|
@@ -203,28 +203,28 @@ function createReferentialConstraints( csn, options ) {
|
|
|
203
203
|
// in contrast to foreign keys which stem from managed associations,
|
|
204
204
|
// a tenant foreign key column may have multiple parent keys as partners
|
|
205
205
|
const tenantForeignKey = isTenant && dependentKeyValuePair[0] === 'tenant';
|
|
206
|
-
if ($foreignKeyConstraint && (!tenantForeignKey || $foreignKeyConstraint.upLinkFor))
|
|
206
|
+
if ($foreignKeyConstraint && (!tenantForeignKey || $foreignKeyConstraint.upLinkFor))
|
|
207
207
|
return;
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
208
|
+
|
|
209
|
+
const parentKeyValuePair = parentKeys.pop();
|
|
210
|
+
const parentKeyName = parentKeyValuePair[0];
|
|
211
|
+
|
|
212
|
+
const constraint = {
|
|
213
|
+
parentKey: parentKeyName,
|
|
214
|
+
parentTable,
|
|
215
|
+
upLinkFor,
|
|
216
|
+
sourceAssociation,
|
|
217
|
+
onDelete: upLinkFor ? 'CASCADE' : 'RESTRICT',
|
|
218
|
+
validated,
|
|
219
|
+
enforced,
|
|
220
|
+
};
|
|
221
|
+
if (tenantForeignKey) {
|
|
222
|
+
const dontOverwriteUp = dependentKey.$foreignKeyConstraint && dependentKey.$foreignKeyConstraint.some(c => c.sourceAssociation === sourceAssociation && c.parentTable === parentTable);
|
|
223
|
+
const dontOverwriteTexts = dependentKey.$foreignKeyConstraint && dependentKey.$foreignKeyConstraint.some(c => c.sourceAssociation === 'texts' && c.upLinkFor.texts);
|
|
224
|
+
if (!dontOverwriteUp && !dontOverwriteTexts)
|
|
225
|
+
dependentKey.$foreignKeyConstraint = dependentKey.$foreignKeyConstraint ? [ ...dependentKey.$foreignKeyConstraint, constraint ] : [ constraint ];
|
|
212
226
|
}
|
|
213
227
|
else {
|
|
214
|
-
const parentKeyValuePair = parentKeys.pop();
|
|
215
|
-
const parentKeyName = parentKeyValuePair[0];
|
|
216
|
-
|
|
217
|
-
const constraint = {
|
|
218
|
-
parentKey: parentKeyName,
|
|
219
|
-
parentTable,
|
|
220
|
-
upLinkFor,
|
|
221
|
-
sourceAssociation: tenantForeignKey
|
|
222
|
-
? [ sourceAssociation ]
|
|
223
|
-
: sourceAssociation,
|
|
224
|
-
onDelete: upLinkFor ? 'CASCADE' : 'RESTRICT',
|
|
225
|
-
validated,
|
|
226
|
-
enforced,
|
|
227
|
-
};
|
|
228
228
|
dependentKey.$foreignKeyConstraint = constraint;
|
|
229
229
|
}
|
|
230
230
|
}
|
|
@@ -509,13 +509,11 @@ function createReferentialConstraints( csn, options ) {
|
|
|
509
509
|
if (isTenant && artifact.elements?.tenant) {
|
|
510
510
|
const element = artifact.elements.tenant;
|
|
511
511
|
if (element.$foreignKeyConstraint) {
|
|
512
|
-
const
|
|
512
|
+
const tenantConstraints = element.$foreignKeyConstraint;
|
|
513
513
|
delete element.$foreignKeyConstraint;
|
|
514
|
-
// create
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
copy.sourceAssociation = sourceAssociation;
|
|
518
|
-
createReferentialConstraints(copy, 'tenant');
|
|
514
|
+
// create (multiple) foreign key constraint(s) for the tenant column with each association in the dependent entity
|
|
515
|
+
tenantConstraints.forEach((c) => {
|
|
516
|
+
createReferentialConstraints(c, 'tenant');
|
|
519
517
|
});
|
|
520
518
|
}
|
|
521
519
|
}
|
|
@@ -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
|
} ]
|