@sap/cds-compiler 4.6.2 → 4.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -0
- package/bin/cds_update_identifiers.js +6 -2
- package/bin/cdsc.js +1 -1
- package/doc/CHANGELOG_ARCHIVE.md +9 -9
- package/doc/CHANGELOG_BETA.md +6 -0
- package/lib/api/main.js +56 -9
- package/lib/api/options.js +6 -3
- package/lib/api/validate.js +20 -29
- package/lib/base/message-registry.js +27 -3
- package/lib/base/messages.js +8 -3
- package/lib/base/model.js +2 -0
- package/lib/checks/dbFeatureFlags.js +28 -0
- package/lib/checks/elements.js +81 -13
- package/lib/checks/enricher.js +3 -2
- package/lib/checks/validator.js +38 -4
- package/lib/compiler/assert-consistency.js +4 -4
- package/lib/compiler/checks.js +5 -4
- package/lib/compiler/define.js +2 -2
- package/lib/compiler/generate.js +2 -1
- package/lib/compiler/propagator.js +3 -11
- package/lib/compiler/shared.js +2 -1
- package/lib/compiler/tweak-assocs.js +43 -24
- package/lib/edm/annotations/edmJson.js +3 -0
- package/lib/edm/annotations/genericTranslation.js +156 -106
- package/lib/edm/annotations/preprocessAnnotations.js +11 -14
- package/lib/edm/csn2edm.js +27 -24
- package/lib/edm/edm.js +8 -8
- package/lib/edm/edmPreprocessor.js +135 -37
- package/lib/edm/edmUtils.js +20 -7
- package/lib/gen/Dictionary.json +1 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +9 -11
- package/lib/gen/languageParser.js +5942 -5446
- package/lib/json/to-csn.js +7 -114
- package/lib/language/genericAntlrParser.js +106 -48
- package/lib/model/cloneCsn.js +203 -0
- package/lib/model/csnRefs.js +11 -3
- package/lib/model/csnUtils.js +42 -85
- package/lib/optionProcessor.js +2 -2
- package/lib/render/manageConstraints.js +1 -1
- package/lib/render/toCdl.js +133 -88
- package/lib/render/toHdbcds.js +1 -5
- package/lib/render/toSql.js +7 -9
- package/lib/render/utils/common.js +9 -16
- package/lib/transform/addTenantFields.js +277 -102
- package/lib/transform/db/applyTransformations.js +14 -9
- package/lib/transform/db/backlinks.js +2 -1
- package/lib/transform/db/constraints.js +60 -82
- package/lib/transform/db/expansion.js +6 -6
- package/lib/transform/db/featureFlags.js +5 -0
- package/lib/transform/db/flattening.js +4 -4
- package/lib/transform/db/killAnnotations.js +1 -0
- package/lib/transform/db/rewriteCalculatedElements.js +2 -2
- package/lib/transform/db/transformExists.js +12 -0
- package/lib/transform/db/views.js +5 -2
- package/lib/transform/draft/odata.js +7 -6
- package/lib/transform/effective/associations.js +2 -1
- package/lib/transform/effective/main.js +3 -2
- package/lib/transform/effective/types.js +6 -3
- package/lib/transform/forOdata.js +39 -24
- package/lib/transform/forRelationalDB.js +34 -27
- package/lib/transform/localized.js +29 -9
- package/lib/transform/odata/flattening.js +419 -0
- package/lib/transform/odata/toFinalBaseType.js +95 -15
- package/lib/transform/odata/typesExposure.js +9 -7
- package/lib/transform/transformUtils.js +7 -6
- package/lib/transform/translateAssocsToJoins.js +3 -3
- package/lib/utils/objectUtils.js +14 -0
- package/package.json +1 -1
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
|
-
|
|
4
|
+
applyTransformationsOnNonDictionary, isAssociationOperand, isDollarSelfOrProjectionOperand,
|
|
5
5
|
} = require('../../model/csnUtils');
|
|
6
6
|
|
|
7
7
|
const { setProp } = require('../../base/model');
|
|
8
8
|
const { ModelError } = require('../../base/error');
|
|
9
9
|
const { forEach } = require('../../utils/objectUtils');
|
|
10
|
+
const { cloneCsnNonDict } = require('../../model/cloneCsn');
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Get a function that transforms $self backlinks
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
const { forEachDefinition } = require('../../base/model');
|
|
4
4
|
const { applyTransformations, hasAnnotationValue, getResultingName } = require('../../model/csnUtils');
|
|
5
|
-
const { csnRefs } = require('../../model/csnRefs');
|
|
6
5
|
const { forEach, forEachKey } = require('../../utils/objectUtils');
|
|
7
6
|
const { CompilerAssertion } = require('../../base/error');
|
|
8
7
|
|
|
@@ -16,6 +15,7 @@ const ASSOCIATION = 'cds.Association';
|
|
|
16
15
|
* @param {CSN.Options} options are used to modify the validate / enforced flag on the constraints
|
|
17
16
|
*/
|
|
18
17
|
function createReferentialConstraints( csn, options ) {
|
|
18
|
+
const isTenant = options.tenantDiscriminator === true;
|
|
19
19
|
let validated = true;
|
|
20
20
|
let enforced = true;
|
|
21
21
|
if (options.integrityNotValidated)
|
|
@@ -24,14 +24,13 @@ function createReferentialConstraints( csn, options ) {
|
|
|
24
24
|
if (options.integrityNotEnforced)
|
|
25
25
|
enforced = false;
|
|
26
26
|
|
|
27
|
-
const { inspectRef } = csnRefs(csn);
|
|
28
27
|
// prepare the functions with the compositions and associations across all entities first
|
|
29
28
|
// and execute it afterwards.
|
|
30
29
|
// compositions must be processed first, as the <up_> links for them must result in `ON DELETE CASCADE`
|
|
31
30
|
const compositions = [];
|
|
32
31
|
const associations = [];
|
|
33
32
|
applyTransformations(csn, {
|
|
34
|
-
elements: (
|
|
33
|
+
elements: (art, prop, elements, path) => {
|
|
35
34
|
// Step I: iterate compositions, enrich dependent keys for <up_> association in target entity of composition
|
|
36
35
|
for (const elementName in elements) {
|
|
37
36
|
const element = elements[elementName];
|
|
@@ -39,7 +38,7 @@ function createReferentialConstraints( csn, options ) {
|
|
|
39
38
|
if (element.type === COMPOSITION && element.$selfOnCondition) {
|
|
40
39
|
compositions.push({
|
|
41
40
|
fn: () => {
|
|
42
|
-
foreignKeyConstraintForUpLinkOfComposition(element,
|
|
41
|
+
foreignKeyConstraintForUpLinkOfComposition(element, art, ePath);
|
|
43
42
|
},
|
|
44
43
|
});
|
|
45
44
|
}
|
|
@@ -52,32 +51,39 @@ function createReferentialConstraints( csn, options ) {
|
|
|
52
51
|
if (element.keys && isToOne(element) && element.type === ASSOCIATION || element.type === COMPOSITION && treatCompositionLikeAssociation(element)) {
|
|
53
52
|
associations.push({
|
|
54
53
|
fn: () => {
|
|
55
|
-
|
|
54
|
+
// parent entity of assoc becomes dependent table
|
|
55
|
+
foreignKeyConstraintForAssociation( element, art, ePath );
|
|
56
56
|
},
|
|
57
57
|
});
|
|
58
58
|
}
|
|
59
59
|
// for `texts` compositions, we may generate foreign key constraints even w/o `up_`
|
|
60
60
|
else if (elementName === 'texts' && element.target === `${path[path.length - 1]}.texts`) {
|
|
61
61
|
const { on } = element;
|
|
62
|
-
const
|
|
62
|
+
const textsEntity = csn.definitions[element.target];
|
|
63
63
|
// `texts` entities have a key named "locale"
|
|
64
|
-
const targetSideHasLocaleKey =
|
|
65
|
-
if (targetSideHasLocaleKey && !skipConstraintGeneration(
|
|
66
|
-
|
|
67
|
-
const
|
|
64
|
+
const targetSideHasLocaleKey = textsEntity.elements.locale?.key;
|
|
65
|
+
if (targetSideHasLocaleKey && !skipConstraintGeneration(art, textsEntity, { /* there is no assoc */ })) {
|
|
66
|
+
// Note: a `texts` composition in `Foo` will co-modify `Foo.texts` and create the constraints over there
|
|
67
|
+
const { dependentKeys, parentKeys } = extractKeys(on, textsEntity.elements, elements);
|
|
68
|
+
|
|
69
|
+
const keysInParent = Object.values(elements).filter(e => e.key);
|
|
70
|
+
if (keysInParent.length !== dependentKeys.length)
|
|
71
|
+
continue;
|
|
72
|
+
|
|
68
73
|
// `texts` entities have all the keys the original entity has
|
|
69
|
-
const allElementsAreKeysAndHaveTheSameName =
|
|
70
|
-
|
|
71
|
-
(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
+
const allElementsAreKeysAndHaveTheSameName = parentKeys.length &&
|
|
75
|
+
parentKeys
|
|
76
|
+
.every(
|
|
77
|
+
([ targetKey, e ]) => e.key &&
|
|
78
|
+
dependentKeys.some(([ sourceKey, sourceElement ]) => sourceElement.key && targetKey === sourceKey )
|
|
79
|
+
);
|
|
74
80
|
if (allElementsAreKeysAndHaveTheSameName)
|
|
75
|
-
attachConstraintsToDependentKeys(
|
|
81
|
+
attachConstraintsToDependentKeys(dependentKeys, parentKeys, path[path.length - 1], 'texts', { texts: true });
|
|
76
82
|
}
|
|
77
83
|
}
|
|
78
84
|
}
|
|
79
85
|
},
|
|
80
|
-
}, [], { skipIgnore: false, skipArtifact: a => a.query || a.kind !== 'entity' });
|
|
86
|
+
}, [], { skipIgnore: false, skipArtifact: a => !!(a.query || a.kind !== 'entity' ) });
|
|
81
87
|
|
|
82
88
|
// create constraints on foreign keys
|
|
83
89
|
// always process unmanaged first, up_ links must be flagged
|
|
@@ -98,7 +104,7 @@ function createReferentialConstraints( csn, options ) {
|
|
|
98
104
|
* @param {CSN.Path} path
|
|
99
105
|
*/
|
|
100
106
|
function foreignKeyConstraintForUpLinkOfComposition( composition, parent, path ) {
|
|
101
|
-
const dependent = csn.definitions[
|
|
107
|
+
const dependent = csn.definitions[composition.target];
|
|
102
108
|
if (skipConstraintGeneration(parent, dependent, composition))
|
|
103
109
|
return;
|
|
104
110
|
|
|
@@ -107,7 +113,7 @@ function createReferentialConstraints( csn, options ) {
|
|
|
107
113
|
const upLinkName = composition.$selfOnCondition.up_[0];
|
|
108
114
|
const up_ = csn.definitions[composition.target].elements[upLinkName];
|
|
109
115
|
if (up_.keys && isToOne(up_)) // no constraint for unmanaged / to-many up_ links
|
|
110
|
-
foreignKeyConstraintForAssociation(up_, [ 'definitions', composition.target, 'elements', upLinkName ], path[path.length - 1]);
|
|
116
|
+
foreignKeyConstraintForAssociation(up_, dependent, [ 'definitions', composition.target, 'elements', upLinkName ], path[path.length - 1] );
|
|
111
117
|
}
|
|
112
118
|
else if (!onCondition && composition.keys.length > 0) {
|
|
113
119
|
throw new CompilerAssertion('Please debug me, an on-condition was expected here, but only found keys');
|
|
@@ -120,28 +126,53 @@ function createReferentialConstraints( csn, options ) {
|
|
|
120
126
|
* If the association is used as an <up_> link in a compositions on-condition, the ON DELETE rule will be `CASCADE`
|
|
121
127
|
*
|
|
122
128
|
* @param {CSN.Association} association for that a constraint should be generated
|
|
129
|
+
* @param {CSN.Entity} dependent the entity for which a constraint will be generated
|
|
123
130
|
* @param {CSN.Path} path
|
|
124
131
|
* @param {CSN.PathSegment} upLinkFor the name of the composition which used this association in a `$self = <comp>.<up_>` comparison
|
|
125
132
|
*/
|
|
126
|
-
function foreignKeyConstraintForAssociation( association, path, upLinkFor = null ) {
|
|
133
|
+
function foreignKeyConstraintForAssociation( association, dependent, path, upLinkFor = null ) {
|
|
127
134
|
const parent = csn.definitions[association.target];
|
|
128
|
-
const dependent = csn.definitions[path[1]];
|
|
129
135
|
if (skipConstraintGeneration(parent, dependent, association))
|
|
130
136
|
return;
|
|
131
|
-
const { elements } =
|
|
132
|
-
|
|
133
|
-
if (onCondition && hasConstraintCompliantOnCondition(association, elements, path)) {
|
|
137
|
+
const { elements } = dependent;
|
|
138
|
+
if (association.keys) {
|
|
134
139
|
// 1. cds.Association has constraint compliant on-condition
|
|
135
140
|
// mark each dependent key - in the entity containing the association - referenced in the on-condition
|
|
136
|
-
const
|
|
137
|
-
const
|
|
141
|
+
const parentKeys = [];
|
|
142
|
+
const dependentKeys = [];
|
|
143
|
+
association.keys.forEach( (k) => {
|
|
144
|
+
dependentKeys.push( [ k.$generatedFieldName, elements[k.$generatedFieldName] ] );
|
|
145
|
+
const parentKey = parent.elements[k.ref[0]];
|
|
146
|
+
if (parentKey.key) // only keys are valid references in foreign key constraints
|
|
147
|
+
parentKeys.push( [ k.ref[0], parent.elements[k.ref[0]] ] );
|
|
148
|
+
});
|
|
149
|
+
if (isTenant && elements.tenant && parent.elements.tenant) { // `tenant` is not part of on
|
|
150
|
+
dependentKeys.push([ 'tenant', elements.tenant ]);
|
|
151
|
+
parentKeys.push([ 'tenant', parent.elements.tenant ]);
|
|
152
|
+
}
|
|
153
|
+
const allKeysCovered = parentKeys.length === Object.values(parent.elements).filter(e => e.key).length;
|
|
138
154
|
// sanity check; do not generate constraints for on-conditions like "dependent.idOne = id AND dependent.idTwo = id"
|
|
139
|
-
if (dependentKeys.length === parentKeys.length)
|
|
155
|
+
if (allKeysCovered && dependentKeys.length === parentKeys.length)
|
|
140
156
|
attachConstraintsToDependentKeys(dependentKeys, parentKeys, association.target, path[path.length - 1], upLinkFor);
|
|
141
157
|
}
|
|
142
|
-
|
|
143
|
-
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Extracts dependent keys and their parent keys based on an 'on' condition.
|
|
162
|
+
*
|
|
163
|
+
* @param {CSN.OnCondition} on - on condition from which dependent keys and their parent keys are extracted.
|
|
164
|
+
* @param {CSN.Elements} elements - The elements of the dependent entity, containing the foreign keys.
|
|
165
|
+
* @param {CSN.Elements} parentElements - The elements of the parent entity, containing the referenced parent keys.
|
|
166
|
+
* @returns {object} An object containing dependent keys and the parent keys which they reference.
|
|
167
|
+
*/
|
|
168
|
+
function extractKeys( on, elements, parentElements ) {
|
|
169
|
+
const dependentKeys = Array.from(elementsOfSourceSide(on, elements));
|
|
170
|
+
const parentKeys = Array.from(elementsOfTargetSide(on, parentElements));
|
|
171
|
+
if (isTenant && elements.tenant && parentElements.tenant) { // `tenant` is not part of on
|
|
172
|
+
dependentKeys.push([ 'tenant', elements.tenant ]);
|
|
173
|
+
parentKeys.push([ 'tenant', parentElements.tenant ]);
|
|
144
174
|
}
|
|
175
|
+
return { dependentKeys, parentKeys };
|
|
145
176
|
}
|
|
146
177
|
|
|
147
178
|
/**
|
|
@@ -181,59 +212,6 @@ function createReferentialConstraints( csn, options ) {
|
|
|
181
212
|
}
|
|
182
213
|
}
|
|
183
214
|
|
|
184
|
-
/**
|
|
185
|
-
* Constraints can only be generated if the full primary key of the target is referenced by the foreign key in an on-condition.
|
|
186
|
-
* 1. on-condition only contains AND as logical operator
|
|
187
|
-
* 2. each part of the on-condition must either:
|
|
188
|
-
* - reference a valid field in the dependent entity:
|
|
189
|
-
* a) for cds.Composition this is in the target entity
|
|
190
|
-
* b) for cds.Association this is the entity, where the association is defined
|
|
191
|
-
* - reference a key element in the parent entity:
|
|
192
|
-
* a) for cds.Composition this is the entity, where the composition itself is defined
|
|
193
|
-
* b) for cds.Association this is the target entity
|
|
194
|
-
* 3. parent keys must be the full primary key tuple
|
|
195
|
-
*
|
|
196
|
-
* @param {CSN.Association} element
|
|
197
|
-
* @param {CSN.Elements} siblingElements
|
|
198
|
-
* @param {CSN.Path} path the path to the element
|
|
199
|
-
* @returns {boolean} indicating whether the association / composition is a constraint candidate
|
|
200
|
-
*/
|
|
201
|
-
function hasConstraintCompliantOnCondition( element, siblingElements, path ) {
|
|
202
|
-
const onCondition = element.on;
|
|
203
|
-
const allowedTokens = [ '=', 'and', '(', ')' ];
|
|
204
|
-
// on condition must only contain logical operator 'AND'
|
|
205
|
-
if (onCondition.some(step => typeof step === 'string' && !allowedTokens.includes(step)))
|
|
206
|
-
return false;
|
|
207
|
-
|
|
208
|
-
// on-condition like ... TemplateAuthGroupAssignments.isTemplate = true; is not allowed
|
|
209
|
-
if (onCondition.some(step => typeof step === 'object' && Object.prototype.hasOwnProperty.call(step, 'val')))
|
|
210
|
-
return false;
|
|
211
|
-
|
|
212
|
-
// no magic vars in on-condition
|
|
213
|
-
// e.g. for localized: ... and localized.locale = $user.locale; -> not a valid on-condition
|
|
214
|
-
if (onCondition.some((step, index) => typeof step === 'object' && inspectRef(path.concat([ 'on', index ])).scope === '$magic'))
|
|
215
|
-
return false;
|
|
216
|
-
|
|
217
|
-
// for cds.Associations the parent keys are in the associations target entity
|
|
218
|
-
// for cds.Composition the parent keys are in the entity, where the composition is defined
|
|
219
|
-
const parentElements = csn.definitions[element.target].elements;
|
|
220
|
-
const parentKeys = elementsOfTargetSide(onCondition, parentElements);
|
|
221
|
-
|
|
222
|
-
const referencesNonPrimaryKeyField = Array.from(parentKeys.values()).some(parentKey => !parentKey.key);
|
|
223
|
-
if (referencesNonPrimaryKeyField)
|
|
224
|
-
return false;
|
|
225
|
-
|
|
226
|
-
// returns true if the parentKeys found in the on-condition are covering the full primary key tuple in the parent entity
|
|
227
|
-
return Array.from(parentKeys.entries())
|
|
228
|
-
// check if primary key found in on-condition is present in association target / composition source
|
|
229
|
-
.filter(([ keyName, pk ]) => pk.key && parentElements[keyName].key).length ===
|
|
230
|
-
Object.keys(parentElements)
|
|
231
|
-
// compare that with the length of the primary key tuple found in association target / composition source
|
|
232
|
-
.filter(key => parentElements[key].key &&
|
|
233
|
-
parentElements[key].type !== ASSOCIATION &&
|
|
234
|
-
parentElements[key].type !== COMPOSITION)
|
|
235
|
-
.length;
|
|
236
|
-
}
|
|
237
215
|
/**
|
|
238
216
|
* Skip referential constraint if the parent table (association target, or artifact where composition is defined)
|
|
239
217
|
* of the relation is:
|
|
@@ -11,6 +11,7 @@ const { implicitAs, columnAlias, pathId } = require('../../model/csnRefs');
|
|
|
11
11
|
const { setProp } = require('../../base/model');
|
|
12
12
|
const { forEach } = require('../../utils/objectUtils');
|
|
13
13
|
const { killNonrequiredAnno } = require('./killAnnotations');
|
|
14
|
+
const { featureFlags } = require('./featureFlags');
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* For keys, columns, groupBy and orderBy, expand structured things.
|
|
@@ -26,7 +27,10 @@ const { killNonrequiredAnno } = require('./killAnnotations');
|
|
|
26
27
|
function expandStructureReferences( csn, options, pathDelimiter, messageFunctions, csnUtils, iterateOptions = {} ) {
|
|
27
28
|
const { error, info, throwWithAnyError } = messageFunctions;
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
if (options.transformation === 'odata' || options.transformation === 'effective' || csn.meta?.[featureFlags]?.$expandInline)
|
|
31
|
+
rewriteExpandInline();
|
|
32
|
+
|
|
33
|
+
throwWithAnyError();
|
|
30
34
|
|
|
31
35
|
const transformers = {
|
|
32
36
|
keys: (parent, name, keys, path) => {
|
|
@@ -189,10 +193,6 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
189
193
|
parent[name].splice(kill[i]);
|
|
190
194
|
}
|
|
191
195
|
|
|
192
|
-
// We would be broken if we continue with assoc usage to now skipped
|
|
193
|
-
throwWithAnyError();
|
|
194
|
-
|
|
195
|
-
|
|
196
196
|
for (const {
|
|
197
197
|
parent, target, path,
|
|
198
198
|
} of publishing) {
|
|
@@ -615,7 +615,7 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
615
615
|
const obj = { ...root, ref: currentRef };
|
|
616
616
|
if (withAlias) {
|
|
617
617
|
// TODO: Remove this line in case foreign key annotations should
|
|
618
|
-
// be
|
|
618
|
+
// be addressed via full path into target instead of using alias
|
|
619
619
|
// names. See flattening.js::flattenAllStructStepsInRefs()
|
|
620
620
|
// apply transformations on `ref` counterpart comment.
|
|
621
621
|
setProp(obj, '$structRef', currentAlias);
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
4
|
applyTransformations, applyTransformationsOnNonDictionary,
|
|
5
|
-
isBuiltinType,
|
|
6
|
-
copyAnnotations, implicitAs, isDeepEqual,
|
|
5
|
+
isBuiltinType, cardinality2str,
|
|
6
|
+
copyAnnotations, implicitAs, isDeepEqual, findAnnotationExpression,
|
|
7
7
|
} = require('../../model/csnUtils');
|
|
8
8
|
const transformUtils = require('../transformUtils');
|
|
9
9
|
const { csnRefs } = require('../../model/csnRefs');
|
|
10
10
|
const { setProp, isBetaEnabled } = require('../../base/model');
|
|
11
11
|
const { forEach } = require('../../utils/objectUtils');
|
|
12
|
-
const { cardinality2str, isAnnotationExpression } = require('../../model/csnUtils');
|
|
13
12
|
const { transformExpression } = require('./applyTransformations');
|
|
13
|
+
const { cloneCsnNonDict } = require('../../model/cloneCsn');
|
|
14
14
|
/**
|
|
15
15
|
* Strip off leading $self from refs where applicable
|
|
16
16
|
*
|
|
@@ -819,7 +819,7 @@ function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathD
|
|
|
819
819
|
if (options.transformation !== 'effective')
|
|
820
820
|
fk[1]['@odata.foreignKey4'] = prefix;
|
|
821
821
|
if (options.transformation === 'odata' || options.transformation === 'effective') {
|
|
822
|
-
const validAnnoNames = Object.keys(element).filter(pn => pn[0] === '@' && !
|
|
822
|
+
const validAnnoNames = Object.keys(element).filter(pn => pn[0] === '@' && !findAnnotationExpression(element, pn));
|
|
823
823
|
copyAnnotations(element, fk[1], true, {}, validAnnoNames);
|
|
824
824
|
}
|
|
825
825
|
// propagate not null to final foreign key
|
|
@@ -5,6 +5,7 @@ const requiredAnnos = {
|
|
|
5
5
|
'@cds.persistence.exists': true,
|
|
6
6
|
'@cds.persistence.table': true,
|
|
7
7
|
'@cds.persistence.journal': true, // Build checks on it
|
|
8
|
+
'@cds.tenant.independent': true,
|
|
8
9
|
'@sql.append': true,
|
|
9
10
|
'@sql.prepend': true,
|
|
10
11
|
'@sql.replace': true, // We do a check on this, no real function
|
|
@@ -7,10 +7,10 @@ const {
|
|
|
7
7
|
applyTransformationsOnNonDictionary,
|
|
8
8
|
applyTransformationsOnDictionary,
|
|
9
9
|
implicitAs,
|
|
10
|
-
cloneCsnNonDict,
|
|
11
10
|
} = require('../../model/csnUtils');
|
|
12
11
|
const { getBranches } = require('./flattening');
|
|
13
12
|
const { getColumnMap } = require('./views');
|
|
13
|
+
const { cloneCsnNonDict } = require('../../model/cloneCsn');
|
|
14
14
|
|
|
15
15
|
const cloneCsnOptions = { hiddenPropertiesToClone: [ '_art', '_links', '$env', '$scope' ] };
|
|
16
16
|
|
|
@@ -305,7 +305,7 @@ function rewriteCalculatedElementsInViews( csn, options, csnUtils, pathDelimiter
|
|
|
305
305
|
* @returns {object|Array}
|
|
306
306
|
*/
|
|
307
307
|
function replaceInRef( oldValue, newValue, isInXpr, refBase, linksBase ) {
|
|
308
|
-
const clone = { value: cloneCsnNonDict(
|
|
308
|
+
const clone = { value: cloneCsnNonDict(newValue, cloneCsnOptions) };
|
|
309
309
|
if (oldValue.stored)
|
|
310
310
|
clone.value.stored = oldValue.stored;
|
|
311
311
|
|
|
@@ -308,9 +308,21 @@ function handleExists( csn, options, error, inspectRef, initDefinition, dropDefi
|
|
|
308
308
|
|
|
309
309
|
const target = subselect.SELECT.from.as; // use subquery alias as target - prevent shadowing
|
|
310
310
|
const extension = root.keys ? translateManagedAssocToWhere(root, target, isPrefixedWithTableAlias, base, current) : translateUnmanagedAssocToWhere(root, target, isPrefixedWithTableAlias, base, current);
|
|
311
|
+
|
|
312
|
+
if (options.tenantDiscriminator) {
|
|
313
|
+
const targetEntity = csn.definitions[root.target];
|
|
314
|
+
if (!targetEntity['@cds.tenant.independent']) {
|
|
315
|
+
subselect.SELECT.where.push(
|
|
316
|
+
{ ref: [ target, 'tenant' ] }, '=', { ref: [ base, 'tenant' ] }, 'AND'
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// TODO: parentheses around sub expressions are to be represented by inner `xpr`
|
|
311
322
|
if (extension.length > 3)
|
|
312
323
|
subselect.SELECT.where.push('('); // add braces around the on-condition part to ensure precedence is kept
|
|
313
324
|
|
|
325
|
+
// TODO: add tenant comparison here ?
|
|
314
326
|
subselect.SELECT.where.push(...extension);
|
|
315
327
|
|
|
316
328
|
if (extension.length > 3)
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
|
-
getUtils,
|
|
4
|
+
getUtils, applyTransformationsOnNonDictionary, forEachDefinition,
|
|
5
5
|
} = require('../../model/csnUtils');
|
|
6
6
|
const { implicitAs, columnAlias, pathId } = require('../../model/csnRefs');
|
|
7
7
|
const { ModelError } = require('../../base/error');
|
|
8
8
|
const { setProp } = require('../../base/model');
|
|
9
|
+
const { cloneCsnNonDict } = require('../../model/cloneCsn');
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* If a mixin association is published, return the mixin association.
|
|
@@ -328,6 +329,7 @@ function getViewTransformer( csn, options, messageFunctions, transformCommon ) {
|
|
|
328
329
|
*/
|
|
329
330
|
// eslint-disable-next-line complexity
|
|
330
331
|
function transformViewOrEntity( query, artifact, artName, path ) {
|
|
332
|
+
const ignoreAssociations = options.sqlDialect === 'hana' && options.withHanaAssociations === false;
|
|
331
333
|
csnUtils.initDefinition(artifact);
|
|
332
334
|
const { elements } = queryOrMain(query, artifact);
|
|
333
335
|
// We use the elements from the leading query/main artifact - adapt the path
|
|
@@ -352,6 +354,7 @@ function getViewTransformer( csn, options, messageFunctions, transformCommon ) {
|
|
|
352
354
|
|
|
353
355
|
for (const elemName in elements) {
|
|
354
356
|
const elem = elements[elemName];
|
|
357
|
+
|
|
355
358
|
if (isSelect) {
|
|
356
359
|
if (!columnMap[elemName])
|
|
357
360
|
addProjectionOrStarElement(query, isProjection, isSelectStar, $combined, columnMap, elemName);
|
|
@@ -380,7 +383,7 @@ function getViewTransformer( csn, options, messageFunctions, transformCommon ) {
|
|
|
380
383
|
|
|
381
384
|
if (isSelect) {
|
|
382
385
|
// Build new columns from the column map - bring elements and columns back in sync basically
|
|
383
|
-
query.SELECT.columns = Object.keys(elements).filter(elem => !elements[elem].$ignore).map(key => stripLeadingSelf(columnMap[key]));
|
|
386
|
+
query.SELECT.columns = Object.keys(elements).filter(elem => !elements[elem].$ignore && !(elements[elem].target && ignoreAssociations)).map(key => stripLeadingSelf(columnMap[key]));
|
|
384
387
|
// If following an association, explicitly set the implicit alias
|
|
385
388
|
// due to an issue with HANA - this seems to only have an effect on ref files with hdbcds-hdbcds, so only run then
|
|
386
389
|
const columnProcessors = [];
|
|
@@ -21,13 +21,16 @@ const { makeMessageFunction } = require('../../base/messages');
|
|
|
21
21
|
*
|
|
22
22
|
* @param {CSN.Model} csn
|
|
23
23
|
* @param {CSN.Options} options
|
|
24
|
-
* @param {
|
|
24
|
+
* @param {string[]|undefined} services Will be calculated JIT if not provided
|
|
25
|
+
* @param {object} [messageFunctions]
|
|
25
26
|
* @returns {CSN.Model} Returns the transformed input model
|
|
26
27
|
* @todo should be done by the compiler - Check associations for valid foreign keys
|
|
27
28
|
* @todo check if needed at all: Remove '$projection' from paths in the element's ON-condition
|
|
28
29
|
*/
|
|
29
|
-
function generateDrafts( csn, options, services ) {
|
|
30
|
-
|
|
30
|
+
function generateDrafts( csn, options, services, messageFunctions ) {
|
|
31
|
+
// TEMP(2024-02-26): Temporary! Umbrella uses this file directly in cds/lib/compile/for/drafts.js#L1
|
|
32
|
+
messageFunctions ??= makeMessageFunction(csn, options, 'odata-drafts');
|
|
33
|
+
|
|
31
34
|
const { error, info } = messageFunctions;
|
|
32
35
|
const {
|
|
33
36
|
createAndAddDraftAdminDataProjection, createScalarElement,
|
|
@@ -77,8 +80,7 @@ function generateDrafts( csn, options, services ) {
|
|
|
77
80
|
*/
|
|
78
81
|
function generateDraftForOdata( artifact, artifactName, rootArtifact ) {
|
|
79
82
|
// Nothing to do if already draft-enabled (composition traversal may have circles)
|
|
80
|
-
if (
|
|
81
|
-
artifact.actions && artifact.actions.draftPrepare)
|
|
83
|
+
if (filterDict[artifactName])
|
|
82
84
|
return;
|
|
83
85
|
|
|
84
86
|
|
|
@@ -194,7 +196,6 @@ function generateDrafts( csn, options, services ) {
|
|
|
194
196
|
}
|
|
195
197
|
}
|
|
196
198
|
|
|
197
|
-
|
|
198
199
|
// Generate the actions into the draft-enabled artifact (only draft roots can be activated/edited)
|
|
199
200
|
|
|
200
201
|
// action draftPrepare (SideEffectsQualifier: String) return <artifact>;
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
const { setProp } = require('../../base/model');
|
|
4
4
|
const flattening = require('../db/flattening');
|
|
5
5
|
const {
|
|
6
|
-
applyTransformations, forEachDefinition, forEachMemberRecursively, implicitAs,
|
|
6
|
+
applyTransformations, forEachDefinition, forEachMemberRecursively, implicitAs, forEachMember, applyTransformationsOnNonDictionary,
|
|
7
7
|
} = require('../../model/csnUtils');
|
|
8
8
|
const associations = require('../db/associations');
|
|
9
9
|
const backlinks = require('../db/backlinks');
|
|
10
|
+
const { cloneCsnNonDict } = require('../../model/cloneCsn');
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { isBetaEnabled } = require('../../base/model');
|
|
4
4
|
const { CompilerAssertion } = require('../../base/error');
|
|
5
|
-
const {
|
|
5
|
+
const { getUtils, isAspect } = require('../../model/csnUtils');
|
|
6
6
|
const transformUtils = require('../transformUtils');
|
|
7
7
|
const flattening = require('../db/flattening');
|
|
8
8
|
const types = require('./types');
|
|
@@ -15,6 +15,7 @@ const generateDrafts = require('../draft/db');
|
|
|
15
15
|
const handleExists = require('../db/transformExists');
|
|
16
16
|
const misc = require('./misc');
|
|
17
17
|
const { rewriteCalculatedElementsInViews, processCalculatedElementsInEntities } = require('../db/rewriteCalculatedElements');
|
|
18
|
+
const { cloneFullCsn } = require('../../model/cloneCsn');
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* This is just a PoC for now!
|
|
@@ -32,7 +33,7 @@ function effectiveCsn( model, options, messageFunctions ) {
|
|
|
32
33
|
if (!isBetaEnabled(options, 'effectiveCsn'))
|
|
33
34
|
throw new CompilerAssertion('effective CSN is only supported with beta flag `effectiveCsn`!');
|
|
34
35
|
|
|
35
|
-
const csn =
|
|
36
|
+
const csn = cloneFullCsn(model, options);
|
|
36
37
|
delete csn.vocabularies; // must not be set for effective CSN
|
|
37
38
|
messageFunctions.setModel(csn);
|
|
38
39
|
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
|
-
|
|
4
|
+
applyTransformations,
|
|
5
|
+
applyTransformationsOnNonDictionary,
|
|
6
|
+
applyTransformationsOnDictionary,
|
|
5
7
|
} = require('../../model/csnUtils');
|
|
6
8
|
const { forEachKey } = require('../../utils/objectUtils');
|
|
9
|
+
const { cloneCsnDict, cloneCsnNonDict } = require('../../model/cloneCsn');
|
|
7
10
|
|
|
8
11
|
/**
|
|
9
12
|
* Resolve all references to structured types in entities to the underlying elements.
|
|
@@ -56,7 +59,7 @@ function resolveTypes( csn, csnUtils ) {
|
|
|
56
59
|
if (final?.elements) {
|
|
57
60
|
// We do full clones so users don't get unexpected linkage later
|
|
58
61
|
if (!parent.elements)
|
|
59
|
-
parent.elements =
|
|
62
|
+
parent.elements = cloneCsnDict(final.elements);
|
|
60
63
|
delete parent.type;
|
|
61
64
|
}
|
|
62
65
|
else if (final && final.items) {
|
|
@@ -102,7 +105,7 @@ function resolveTypes( csn, csnUtils ) {
|
|
|
102
105
|
if (finalSub?.elements) {
|
|
103
106
|
// We do full clones so users don't get unexpected linkage later
|
|
104
107
|
if (!_parent.elements)
|
|
105
|
-
_parent.elements =
|
|
108
|
+
_parent.elements = cloneCsnDict(finalSub.elements);
|
|
106
109
|
delete _parent.type;
|
|
107
110
|
stack.push( _parent );
|
|
108
111
|
}
|