@sap/cds-compiler 2.10.4 → 2.11.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 +50 -0
- package/bin/cdsc.js +42 -25
- package/bin/cdsse.js +1 -0
- package/doc/CHANGELOG_BETA.md +4 -0
- package/lib/api/.eslintrc.json +2 -0
- package/lib/api/main.js +9 -23
- package/lib/api/options.js +12 -4
- package/lib/api/validate.js +23 -2
- package/lib/backends.js +9 -8
- package/lib/base/dictionaries.js +2 -1
- package/lib/base/message-registry.js +10 -2
- package/lib/base/messages.js +23 -9
- package/lib/base/model.js +5 -4
- package/lib/base/optionProcessorHelper.js +56 -22
- package/lib/checks/selectItems.js +4 -0
- package/lib/checks/unknownMagic.js +6 -3
- package/lib/compiler/assert-consistency.js +7 -0
- package/lib/compiler/base.js +65 -0
- package/lib/compiler/builtins.js +28 -1
- package/lib/compiler/checks.js +2 -1
- package/lib/compiler/definer.js +58 -91
- package/lib/compiler/index.js +16 -4
- package/lib/compiler/propagator.js +5 -2
- package/lib/compiler/resolver.js +93 -34
- package/lib/compiler/shared.js +29 -202
- package/lib/compiler/utils.js +173 -0
- package/lib/edm/annotations/genericTranslation.js +1 -1
- package/lib/edm/csn2edm.js +3 -2
- package/lib/edm/edmPreprocessor.js +31 -36
- package/lib/edm/edmUtils.js +3 -3
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +17 -1
- package/lib/gen/language.tokens +79 -73
- package/lib/gen/languageLexer.interp +19 -1
- package/lib/gen/languageLexer.js +779 -731
- package/lib/gen/languageLexer.tokens +71 -65
- package/lib/gen/languageParser.js +4668 -4072
- package/lib/json/from-csn.js +10 -10
- package/lib/json/to-csn.js +169 -34
- package/lib/language/antlrParser.js +11 -0
- package/lib/language/genericAntlrParser.js +72 -14
- package/lib/language/language.g4 +73 -0
- package/lib/main.d.ts +136 -17
- package/lib/main.js +3 -1
- package/lib/model/api.js +2 -2
- package/lib/model/csnRefs.js +108 -31
- package/lib/model/csnUtils.js +63 -29
- package/lib/model/enrichCsn.js +36 -9
- package/lib/model/revealInternalProperties.js +20 -4
- package/lib/modelCompare/compare.js +2 -1
- package/lib/optionProcessor.js +29 -18
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/toCdl.js +9 -3
- package/lib/render/toHdbcds.js +16 -36
- package/lib/render/toSql.js +23 -5
- package/lib/transform/db/constraints.js +278 -119
- package/lib/transform/db/draft.js +3 -2
- package/lib/transform/db/expansion.js +6 -4
- package/lib/transform/db/flattening.js +17 -1
- package/lib/transform/db/transformExists.js +61 -2
- package/lib/transform/db/views.js +438 -0
- package/lib/transform/forHanaNew.js +56 -435
- package/lib/transform/forOdataNew.js +9 -2
- package/lib/transform/localized.js +2 -0
- package/lib/transform/transformUtilsNew.js +10 -0
- package/lib/transform/translateAssocsToJoins.js +5 -13
- package/lib/utils/file.js +5 -3
- package/lib/utils/term.js +65 -42
- package/lib/utils/timetrace.js +48 -26
- package/package.json +1 -1
|
@@ -16,36 +16,40 @@ const ASSOCIATION = 'cds.Association';
|
|
|
16
16
|
function createReferentialConstraints(csn, options) {
|
|
17
17
|
let validated = true;
|
|
18
18
|
let enforced = true;
|
|
19
|
-
if (options.
|
|
19
|
+
if (options.integrityNotValidated)
|
|
20
20
|
validated = false;
|
|
21
21
|
|
|
22
|
-
if (options.
|
|
22
|
+
if (options.integrityNotEnforced)
|
|
23
23
|
enforced = false;
|
|
24
24
|
|
|
25
25
|
const { inspectRef } = csnRefs(csn);
|
|
26
26
|
// prepare the functions with the compositions and associations across all entities first
|
|
27
|
-
// and execute it afterwards
|
|
27
|
+
// and execute it afterwards.
|
|
28
|
+
// compositions must be processed first, as the <up_> links for them must result in `ON DELETE CASCADE`
|
|
28
29
|
const compositions = [];
|
|
29
30
|
const associations = [];
|
|
30
31
|
forEachDefinition(csn, (artifact, artifactName) => {
|
|
31
|
-
if (!artifact.query && artifact.kind === 'entity'
|
|
32
|
+
if (!artifact.query && artifact.kind === 'entity') {
|
|
32
33
|
forAllElements(artifact, artifactName, (parent, elements, path) => {
|
|
33
|
-
// Step I: iterate compositions, enrich dependent keys
|
|
34
|
+
// Step I: iterate compositions, enrich dependent keys for <up_> association in target entity of composition
|
|
34
35
|
for (const elementName in elements) {
|
|
35
36
|
const element = elements[elementName];
|
|
36
|
-
if (element.type === COMPOSITION &&
|
|
37
|
-
compositions.push(
|
|
38
|
-
|
|
37
|
+
if (element.type === COMPOSITION && element.$selfOnCondition) {
|
|
38
|
+
compositions.push({
|
|
39
|
+
fn: () => {
|
|
40
|
+
foreignKeyConstraintForUpLinkOfComposition(element, parent, path.concat([ elementName ]));
|
|
41
|
+
},
|
|
39
42
|
});
|
|
40
43
|
}
|
|
41
44
|
}
|
|
42
45
|
// Step II: iterate associations, enrich dependent keys (in entity containing the association)
|
|
43
46
|
for (const elementName in elements) {
|
|
44
47
|
const element = elements[elementName];
|
|
45
|
-
if (element.type === ASSOCIATION ||
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
if (element.keys && isToOne(element) && element.type === ASSOCIATION || element.type === COMPOSITION && treatCompositionLikeAssociation(element)) {
|
|
49
|
+
associations.push({
|
|
50
|
+
fn: () => {
|
|
51
|
+
foreignKeyConstraintForAssociation(element, path.concat([ elementName ]));
|
|
52
|
+
},
|
|
49
53
|
});
|
|
50
54
|
}
|
|
51
55
|
}
|
|
@@ -53,39 +57,34 @@ function createReferentialConstraints(csn, options) {
|
|
|
53
57
|
}
|
|
54
58
|
});
|
|
55
59
|
// create constraints on foreign keys
|
|
56
|
-
|
|
57
|
-
|
|
60
|
+
// always process unmanaged first, up_ links must be flagged
|
|
61
|
+
// before they are processed
|
|
62
|
+
compositions.forEach(composition => composition.fn());
|
|
63
|
+
associations.forEach(association => association.fn());
|
|
64
|
+
|
|
58
65
|
// Step III: Create the final referential constraints from all dependent key <-> parent key pairs stemming from the same $sourceAssociation
|
|
59
66
|
forEachDefinition(csn, collectAndAttachReferentialConstraints);
|
|
60
67
|
|
|
61
|
-
|
|
62
68
|
/**
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
69
|
+
* Retrieve the <up_> link of an `cds.Composition` used in an on-condition like `$self = <comp>.<up_>`
|
|
70
|
+
* and calculate a foreign key constraint for this association if it is constraint compliant.
|
|
71
|
+
* The constraint will have an `ON DELETE CASCADE`.
|
|
66
72
|
*
|
|
67
|
-
* @param {CSN.Element} composition
|
|
73
|
+
* @param {CSN.Element} composition which might has the `$self = <comp>.<up_>` on-condition
|
|
68
74
|
* @param {CSN.Artifact} parent artifact containing the composition
|
|
69
75
|
* @param {CSN.Path} path
|
|
70
76
|
*/
|
|
71
|
-
function
|
|
72
|
-
|
|
77
|
+
function foreignKeyConstraintForUpLinkOfComposition(composition, parent, path) {
|
|
78
|
+
const dependent = csn.definitions[path[1]];
|
|
79
|
+
if (skipConstraintGeneration(parent, dependent, composition))
|
|
73
80
|
return;
|
|
74
81
|
|
|
75
|
-
const { elements } = parent;
|
|
76
82
|
const onCondition = composition.on;
|
|
77
|
-
if (
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const parentKeys = Array.from(elementsOfSourceSide(onCondition, elements));
|
|
83
|
-
const { backlinkName } = composition.$selfOnCondition || {};
|
|
84
|
-
// sanity check; do not generate constraints for on-conditions like "dependent.idOne = id AND dependent.idTwo = id"
|
|
85
|
-
// also: no constraints for compositions of many w/o backlink
|
|
86
|
-
if (dependentKeys.length === parentKeys.length && backlinkName)
|
|
87
|
-
attachConstraintsToDependentKeys(dependentKeys, parentKeys, path[path.length - 3], path, backlinkName, 'CASCADE');
|
|
88
|
-
}
|
|
83
|
+
if (composition.$selfOnCondition && composition.$selfOnCondition.up_.length === 1) {
|
|
84
|
+
const upLinkName = composition.$selfOnCondition.up_[0];
|
|
85
|
+
const up_ = csn.definitions[composition.target].elements[upLinkName];
|
|
86
|
+
if (up_.keys && isToOne(up_)) // no constraint for unmanaged / to-many up_ links
|
|
87
|
+
foreignKeyConstraintForAssociation(up_, [ 'definitions', composition.target, 'elements', upLinkName ], path[path.length - 1]);
|
|
89
88
|
}
|
|
90
89
|
else if (!onCondition && composition.keys.length > 0) {
|
|
91
90
|
throw new Error('Please debug me, an on-condition was expected here, but only found keys');
|
|
@@ -95,26 +94,27 @@ function createReferentialConstraints(csn, options) {
|
|
|
95
94
|
/**
|
|
96
95
|
* Calculate referential constraints for dependent keys in the entity where the cds.Associations is defined.
|
|
97
96
|
* The DELETE rule for a referential constraint stemming from a cds.Association will be 'RESTRICT'
|
|
97
|
+
* If the association is used as an <up_> link in a compositions on-condition, the ON DELETE rule will be `CASCADE`
|
|
98
98
|
*
|
|
99
99
|
* @param {CSN.Association} association for that a constraint should be generated
|
|
100
|
-
* @param {CSN.Elements} elements of parent entity.
|
|
101
100
|
* @param {CSN.Path} path
|
|
102
|
-
* @param {
|
|
101
|
+
* @param {CSN.PathSegment} upLinkFor the name of the composition which used this association in a `$self = <comp>.<up_>` comparison
|
|
103
102
|
*/
|
|
104
|
-
function foreignKeyConstraintForAssociation(association,
|
|
105
|
-
const
|
|
106
|
-
|
|
103
|
+
function foreignKeyConstraintForAssociation(association, path, upLinkFor = null) {
|
|
104
|
+
const parent = csn.definitions[association.target];
|
|
105
|
+
const dependent = csn.definitions[path[1]];
|
|
106
|
+
if (skipConstraintGeneration(parent, dependent, association))
|
|
107
107
|
return;
|
|
108
|
-
|
|
108
|
+
const { elements } = csn.definitions[path[1]];
|
|
109
109
|
const onCondition = association.on;
|
|
110
110
|
if (onCondition && hasConstraintCompliantOnCondition(association, elements, path)) {
|
|
111
111
|
// 1. cds.Association has constraint compliant on-condition
|
|
112
112
|
// mark each dependent key - in the entity containing the association - referenced in the on-condition
|
|
113
113
|
const dependentKeys = Array.from(elementsOfSourceSide(onCondition, elements));
|
|
114
|
-
const parentKeys = Array.from(elementsOfTargetSide(onCondition,
|
|
114
|
+
const parentKeys = Array.from(elementsOfTargetSide(onCondition, parent.elements));
|
|
115
115
|
// sanity check; do not generate constraints for on-conditions like "dependent.idOne = id AND dependent.idTwo = id"
|
|
116
116
|
if (dependentKeys.length === parentKeys.length)
|
|
117
|
-
attachConstraintsToDependentKeys(dependentKeys, parentKeys, association.target, path,
|
|
117
|
+
attachConstraintsToDependentKeys(dependentKeys, parentKeys, association.target, path[path.length - 1], upLinkFor);
|
|
118
118
|
}
|
|
119
119
|
else if (!onCondition && association.keys.length > 0) {
|
|
120
120
|
throw new Error('Please debug me, an on-condition was expected here, but only found keys');
|
|
@@ -129,14 +129,16 @@ function createReferentialConstraints(csn, options) {
|
|
|
129
129
|
* @param {Array} dependentKeys array holding dependent keys in the format [['key1', 'value1'], [...], ...]
|
|
130
130
|
* @param {Array} parentKeys array holding parent keys in the format [['key1', 'value1'], [...], ...]
|
|
131
131
|
* @param {CSN.PathSegment} parentTable the sql-table where the foreign key constraints will be pointing to
|
|
132
|
-
* @param {CSN.
|
|
133
|
-
* @param {
|
|
134
|
-
*
|
|
132
|
+
* @param {CSN.PathSegment} sourceAssociation the name of the association from which the constraint originates
|
|
133
|
+
* @param {CSN.PathSegment} upLinkFor the name of the composition which used this association in a `$self = <comp>.<up_>` comparison
|
|
134
|
+
* it is used for a comment in the constraint, which is only printed out in test-mode
|
|
135
135
|
*/
|
|
136
|
-
function attachConstraintsToDependentKeys(dependentKeys, parentKeys, parentTable,
|
|
136
|
+
function attachConstraintsToDependentKeys(dependentKeys, parentKeys, parentTable, sourceAssociation, upLinkFor = null) {
|
|
137
137
|
while (dependentKeys.length > 0) {
|
|
138
138
|
const dependentKeyValuePair = dependentKeys.pop();
|
|
139
139
|
const dependentKey = dependentKeyValuePair[1];
|
|
140
|
+
// if it already has a dependent key assigned, do not overwrite it.
|
|
141
|
+
// this is the case for <up_> associations in on-conditions of compositions
|
|
140
142
|
if (Object.prototype.hasOwnProperty.call(dependentKey, '$foreignKeyConstraint'))
|
|
141
143
|
return;
|
|
142
144
|
|
|
@@ -146,9 +148,9 @@ function createReferentialConstraints(csn, options) {
|
|
|
146
148
|
const constraint = {
|
|
147
149
|
parentKey: parentKeyName,
|
|
148
150
|
parentTable,
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
onDelete,
|
|
151
|
+
upLinkFor,
|
|
152
|
+
sourceAssociation,
|
|
153
|
+
onDelete: upLinkFor ? 'CASCADE' : 'RESTRICT',
|
|
152
154
|
validated,
|
|
153
155
|
enforced,
|
|
154
156
|
};
|
|
@@ -168,7 +170,7 @@ function createReferentialConstraints(csn, options) {
|
|
|
168
170
|
* b) for cds.Association this is the target entity
|
|
169
171
|
* 3. parent keys must be the full primary key tuple
|
|
170
172
|
*
|
|
171
|
-
* @param {CSN.Association
|
|
173
|
+
* @param {CSN.Association} element
|
|
172
174
|
* @param {CSN.Elements} siblingElements
|
|
173
175
|
* @param {CSN.Path} path the path to the element
|
|
174
176
|
* @returns {boolean} indicating whether the association / composition is a constraint candidate
|
|
@@ -189,65 +191,245 @@ function createReferentialConstraints(csn, options) {
|
|
|
189
191
|
if (onCondition.some((step, index) => typeof step === 'object' && inspectRef(path.concat([ 'on', index ])).scope === '$magic'))
|
|
190
192
|
return false;
|
|
191
193
|
|
|
192
|
-
// managed composition with target cardinality of one is treated like an association
|
|
193
|
-
const isComposition = element.type === COMPOSITION && !treatCompositionLikeAssociation(element);
|
|
194
194
|
// for cds.Associations the parent keys are in the associations target entity
|
|
195
195
|
// for cds.Composition the parent keys are in the entity, where the composition is defined
|
|
196
|
-
const parentElements =
|
|
197
|
-
const parentKeys =
|
|
196
|
+
const parentElements = csn.definitions[element.target].elements;
|
|
197
|
+
const parentKeys = elementsOfTargetSide(onCondition, parentElements);
|
|
198
|
+
|
|
199
|
+
const referencesNonPrimaryKeyField = Array.from(parentKeys.values()).some(parentKey => !parentKey.key);
|
|
200
|
+
if (referencesNonPrimaryKeyField)
|
|
201
|
+
return false;
|
|
202
|
+
|
|
198
203
|
// returns true if the parentKeys found in the on-condition are covering the full primary key tuple in the parent entity
|
|
199
204
|
return Array.from(parentKeys.entries())
|
|
200
|
-
|
|
201
|
-
.filter(([ keyName, pk ]) => pk.key && parentElements[keyName].key).length ===
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
205
|
+
// check if primary key found in on-condition is present in association target / composition source
|
|
206
|
+
.filter(([ keyName, pk ]) => pk.key && parentElements[keyName].key).length ===
|
|
207
|
+
Object.keys(parentElements)
|
|
208
|
+
// compare that with the length of the primary key tuple found in association target / composition source
|
|
209
|
+
.filter(key => parentElements[key].key &&
|
|
210
|
+
parentElements[key].type !== ASSOCIATION &&
|
|
211
|
+
parentElements[key].type !== COMPOSITION)
|
|
212
|
+
.length;
|
|
207
213
|
}
|
|
208
|
-
|
|
209
214
|
/**
|
|
210
215
|
* Skip referential constraint if the parent table (association target, or artifact where composition is defined)
|
|
211
216
|
* of the relation is:
|
|
212
217
|
* - a query
|
|
213
|
-
* - annotated with '@cds.persistence.skip:true'
|
|
214
|
-
* - annotated with '@cds.persistence.exists:true'
|
|
218
|
+
* - TODO: Revisit -- annotated with '@cds.persistence.skip:true'
|
|
219
|
+
* - TODO: Revisit -- annotated with '@cds.persistence.exists:true'
|
|
220
|
+
*
|
|
221
|
+
* The following decision table reflects the current implementation:
|
|
215
222
|
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
223
|
+
* +-----------------+--------------------+-------------------+----------+
|
|
224
|
+
* | Global Switch: | Global Check Type: | @assert.integrity | Generate |
|
|
225
|
+
* |"assertIntegrity"| "assertIntegrityType"| | Constraint|
|
|
226
|
+
* +-----------------+--------------------+-------------------+----------+
|
|
227
|
+
* | on | RT | false | no |
|
|
228
|
+
* +-----------------+--------------------+-------------------+----------+
|
|
229
|
+
* | on | RT | true/not set | no |
|
|
230
|
+
* +-----------------+--------------------+-------------------+----------+
|
|
231
|
+
* | on | RT | RT | no |
|
|
232
|
+
* +-----------------+--------------------+-------------------+----------+
|
|
233
|
+
* | on | RT | DB | yes |
|
|
234
|
+
* +-----------------+--------------------+-------------------+----------+
|
|
235
|
+
* | | | | |
|
|
236
|
+
* +-----------------+--------------------+-------------------+----------+
|
|
237
|
+
* | on | DB | false | no |
|
|
238
|
+
* +-----------------+--------------------+-------------------+----------+
|
|
239
|
+
* | on | DB | true/not set | yes |
|
|
240
|
+
* +-----------------+--------------------+-------------------+----------+
|
|
241
|
+
* | on | DB | RT | no |
|
|
242
|
+
* +-----------------+--------------------+-------------------+----------+
|
|
243
|
+
* | on | DB | DB | yes |
|
|
244
|
+
* +-----------------+--------------------+-------------------+----------+
|
|
245
|
+
* | | | | |
|
|
246
|
+
* +-----------------+--------------------+-------------------+----------+
|
|
247
|
+
* | off | don't care | don't care | no |
|
|
248
|
+
* +-----------------+--------------------+-------------------+----------+
|
|
249
|
+
* | | | | |
|
|
250
|
+
* +-----------------+--------------------+-------------------+----------+
|
|
251
|
+
* | individual | RT | true | no |
|
|
252
|
+
* +-----------------+--------------------+-------------------+----------+
|
|
253
|
+
* | individual | DB | true | yes |
|
|
254
|
+
* +-----------------+--------------------+-------------------+----------+
|
|
255
|
+
* | individual | don't care | RT | no |
|
|
256
|
+
* +-----------------+--------------------+-------------------+----------+
|
|
257
|
+
* | individual | don't care | DB | yes |
|
|
258
|
+
* +-----------------+--------------------+-------------------+----------+
|
|
259
|
+
* | individual | don't care | false/not set | no |
|
|
260
|
+
* +-----------------+--------------------+-------------------+----------+
|
|
220
261
|
*
|
|
221
|
-
* @param {CSN.
|
|
222
|
-
* @param
|
|
262
|
+
* @param {CSN.Definition} parent entity where the foreign key reference will point at
|
|
263
|
+
* @param {CSN.Definition} dependent entity where the constraint will be defined on
|
|
264
|
+
* @param {CSN.Association} element the composition or association
|
|
223
265
|
* @returns {boolean}
|
|
224
266
|
*/
|
|
225
|
-
function skipConstraintGeneration(parent, element) {
|
|
226
|
-
if
|
|
227
|
-
|
|
228
|
-
// in case of managed composition, the 'up_' link should not result in a constraint
|
|
229
|
-
const target = csn.definitions[element.target];
|
|
230
|
-
const { up_ } = target.elements;
|
|
231
|
-
if (up_)
|
|
232
|
-
up_.$skipReferentialConstraintForUp_ = true;
|
|
267
|
+
function skipConstraintGeneration(parent, dependent, element) {
|
|
268
|
+
// if set to 'off' don't even bother, just skip all constraints
|
|
269
|
+
if (options.assertIntegrity === false || options.assertIntegrity === 'false')
|
|
233
270
|
return true;
|
|
234
|
-
}
|
|
235
271
|
|
|
236
|
-
if (
|
|
237
|
-
|
|
272
|
+
if (parent.query)
|
|
273
|
+
return true;
|
|
274
|
+
|
|
275
|
+
// no constraint if either dependent or parent is not persisted
|
|
276
|
+
if (
|
|
277
|
+
hasAnnotationValue(parent, '@cds.persistence.skip') ||
|
|
278
|
+
hasAnnotationValue(dependent, '@cds.persistence.skip')
|
|
279
|
+
)
|
|
280
|
+
return true;
|
|
281
|
+
|
|
282
|
+
// some commonly used string literals
|
|
283
|
+
const RT = 'RT';
|
|
284
|
+
const DB = 'DB';
|
|
285
|
+
const CREATE_FOR_UP = '$createReferentialConstraintForUp_';
|
|
286
|
+
const SKIP_FOR_UP = '$skipReferentialConstraintForUp_';
|
|
287
|
+
|
|
288
|
+
// if the element itself is explicitly excluded from being checked
|
|
289
|
+
// skip the constraint for it (and its backlink)
|
|
290
|
+
if (isAssertIntegrityAnnotationSetTo(false) ||
|
|
291
|
+
isAssertIntegrityAnnotationSetTo(RT) ||
|
|
292
|
+
element[SKIP_FOR_UP]
|
|
293
|
+
) {
|
|
294
|
+
// for "auto-generated" associations like for the up_ of a composition of aspects,
|
|
295
|
+
// the annotation on the composition influences the referential constraint for the
|
|
296
|
+
// up_ association
|
|
297
|
+
if (element.$selfOnCondition && element.targetAspect) {
|
|
298
|
+
assignPropOnBacklinkIfPossible(SKIP_FOR_UP, true);
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (element.type === 'cds.Composition')
|
|
303
|
+
return false;
|
|
304
|
+
|
|
238
305
|
return true;
|
|
239
306
|
}
|
|
240
307
|
|
|
241
|
-
if (
|
|
242
|
-
|
|
243
|
-
|
|
308
|
+
if ((!options.assertIntegrity || options.assertIntegrity === true || options.assertIntegrity === 'true') &&
|
|
309
|
+
(!options.assertIntegrityType || options.assertIntegrityType === RT))
|
|
310
|
+
return assertForIntegrityTypeRT();
|
|
311
|
+
|
|
312
|
+
if ((!options.assertIntegrity || options.assertIntegrity === true || options.assertIntegrity === 'true') &&
|
|
313
|
+
options.assertIntegrityType === DB)
|
|
314
|
+
return assertForIntegrityTypeDB();
|
|
315
|
+
|
|
316
|
+
if ((options.assertIntegrity === 'individual'))
|
|
317
|
+
return assertForIndividual();
|
|
318
|
+
|
|
319
|
+
// The default for the assertIntegrityType is 'RT', no constraints in that case
|
|
320
|
+
if ((!options.assertIntegrity || options.assertIntegrity === true) &&
|
|
321
|
+
(!options.assertIntegrityType || options.assertIntegrityType === RT))
|
|
244
322
|
return true;
|
|
245
323
|
|
|
246
|
-
|
|
247
|
-
if (!hasAnnotationValue(element, '@cds.persistency.assert.integrity', true) && options.forHana.skipDbConstraints)
|
|
324
|
+
if (!element.keys || !isToOne(element))
|
|
248
325
|
return true;
|
|
249
326
|
|
|
250
327
|
return false;
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* if global checks are 'individual' we evaluate every association,
|
|
331
|
+
* we create db constraints if it is annotated with @assert.integrity: 'DB' (or true)
|
|
332
|
+
*
|
|
333
|
+
* @returns {boolean}
|
|
334
|
+
*/
|
|
335
|
+
function assertForIndividual() {
|
|
336
|
+
if (isAssertIntegrityAnnotationSetTo(DB) || element[CREATE_FOR_UP]) {
|
|
337
|
+
// if this is has a $self comparison, the up_ link should then result in a constraint
|
|
338
|
+
assignPropOnBacklinkIfPossible(CREATE_FOR_UP, true);
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
if (options.assertIntegrityType === DB && isAssertIntegrityAnnotationSetTo(true)) {
|
|
342
|
+
// if this is has a $self comparison, the up_ link should then result in a constraint
|
|
343
|
+
assignPropOnBacklinkIfPossible(CREATE_FOR_UP, true);
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// individual and no ('DB') annotation on constraint --> skip
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* if global check type is 'RT' (or not provided) only generate DB constraint if element
|
|
353
|
+
* is explicitly annotated "@assert.integrity: 'DB'"
|
|
354
|
+
*
|
|
355
|
+
* @returns {boolean}
|
|
356
|
+
*/
|
|
357
|
+
function assertForIntegrityTypeRT() {
|
|
358
|
+
// for "auto-generated" associations like for the up_ of a composition of aspects,
|
|
359
|
+
// the annotation on the composition influences the referential constraint for the
|
|
360
|
+
// up_ association
|
|
361
|
+
if (isAssertIntegrityAnnotationSetTo(DB)) {
|
|
362
|
+
if (element.targetAspect)
|
|
363
|
+
assignPropOnBacklinkIfPossible(CREATE_FOR_UP, true);
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
if (element[CREATE_FOR_UP])
|
|
367
|
+
return false;
|
|
368
|
+
return true;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* if global checks are on and global integrity check type is 'DB'
|
|
373
|
+
* we create db constraints in any case except if annotated
|
|
374
|
+
* with @assert.integrity: 'RT' (or false, but that is rejected earlier)
|
|
375
|
+
*
|
|
376
|
+
* @returns {boolean}
|
|
377
|
+
*/
|
|
378
|
+
function assertForIntegrityTypeDB() {
|
|
379
|
+
if (isAssertIntegrityAnnotationSetTo(RT))
|
|
380
|
+
return true;
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* convenience to check if value of element's @assert.integrity annotation
|
|
387
|
+
* is the same as a given value
|
|
388
|
+
*
|
|
389
|
+
* @param {string|boolean} value
|
|
390
|
+
* @returns {boolean}
|
|
391
|
+
*/
|
|
392
|
+
function isAssertIntegrityAnnotationSetTo(value) {
|
|
393
|
+
return hasAnnotationValue(element, '@assert.integrity', value);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Assigns a helper key-value pair on the up_ association for a $self comparison
|
|
398
|
+
* for the current 'element', if applicable
|
|
399
|
+
*
|
|
400
|
+
* @param {string} prop
|
|
401
|
+
* @param {object} val
|
|
402
|
+
*/
|
|
403
|
+
function assignPropOnBacklinkIfPossible(prop, val) {
|
|
404
|
+
if (!element.$selfOnCondition)
|
|
405
|
+
return;
|
|
406
|
+
const target = csn.definitions[element.target];
|
|
407
|
+
const backlink = target.elements[element.$selfOnCondition.up_[0]];
|
|
408
|
+
backlink[prop] = val;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* If we have a managed composition with a target cardinality of one, we will treat it like
|
|
414
|
+
* a regular association when it comes to referential constraints.
|
|
415
|
+
* The constraint will thus be generated for the foreign key we create in the source entity.
|
|
416
|
+
*
|
|
417
|
+
* @param {CSN.Composition} composition the composition which might be treated like an association
|
|
418
|
+
* @returns {boolean} true if the composition should be treated as an association in regards to foreign key constraints
|
|
419
|
+
*/
|
|
420
|
+
function treatCompositionLikeAssociation(composition) {
|
|
421
|
+
return Boolean(isToOne(composition) && composition.keys);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* returns true if the association/composition has a max target cardinality of one
|
|
426
|
+
*
|
|
427
|
+
* @param {CSN.Association|CSN.Composition} assocOrComposition
|
|
428
|
+
* @returns {boolean}
|
|
429
|
+
*/
|
|
430
|
+
function isToOne(assocOrComposition) {
|
|
431
|
+
const { min, max } = assocOrComposition.cardinality || {};
|
|
432
|
+
return !min && !max || max === 1;
|
|
251
433
|
}
|
|
252
434
|
|
|
253
435
|
/**
|
|
@@ -260,8 +442,8 @@ function createReferentialConstraints(csn, options) {
|
|
|
260
442
|
function elementsOfTargetSide(on, targetElements) {
|
|
261
443
|
const elements = new Map();
|
|
262
444
|
on.filter(element => typeof element === 'object' &&
|
|
263
|
-
|
|
264
|
-
|
|
445
|
+
element.ref.length > 1 &&
|
|
446
|
+
targetElements[element.ref[element.ref.length - 1]])
|
|
265
447
|
.forEach((element) => {
|
|
266
448
|
elements.set(element.ref[element.ref.length - 1], targetElements[element.ref[element.ref.length - 1]]);
|
|
267
449
|
});
|
|
@@ -272,15 +454,15 @@ function createReferentialConstraints(csn, options) {
|
|
|
272
454
|
/**
|
|
273
455
|
* Finds and return elementNames and elements of source side mentioned in on-condition.
|
|
274
456
|
*
|
|
275
|
-
* @param {CSN.
|
|
457
|
+
* @param {CSN.OnCondition} on the on-condition
|
|
276
458
|
* @param {CSN.Elements} sourceElements elements of source entity where the association/composition is defined.
|
|
277
459
|
* @returns {Map} of source elements with their name as key
|
|
278
460
|
*/
|
|
279
461
|
function elementsOfSourceSide(on, sourceElements) {
|
|
280
462
|
const elements = new Map();
|
|
281
463
|
on.filter(element => typeof element === 'object' &&
|
|
282
|
-
|
|
283
|
-
|
|
464
|
+
element.ref.length === 1 &&
|
|
465
|
+
sourceElements[element.ref[0]])
|
|
284
466
|
.forEach((element) => {
|
|
285
467
|
elements.set(element.ref[0], sourceElements[element.ref[0]]);
|
|
286
468
|
});
|
|
@@ -315,8 +497,8 @@ function createReferentialConstraints(csn, options) {
|
|
|
315
497
|
// find all other $foreignKeyConstraint with same $sourceAssociation and same parentTable
|
|
316
498
|
Object.entries(artifact.elements)
|
|
317
499
|
.filter(([ , e ]) => e.$foreignKeyConstraint &&
|
|
318
|
-
|
|
319
|
-
|
|
500
|
+
e.$foreignKeyConstraint.sourceAssociation === $foreignKeyConstraint.sourceAssociation &&
|
|
501
|
+
e.$foreignKeyConstraint.parentTable === $foreignKeyConstraint.parentTable)
|
|
320
502
|
.forEach(([ foreignKeyName, foreignKey ]) => {
|
|
321
503
|
const $foreignKeyConstraintCopy = Object.assign({}, foreignKey.$foreignKeyConstraint);
|
|
322
504
|
delete foreignKey.$foreignKeyConstraint;
|
|
@@ -329,9 +511,9 @@ function createReferentialConstraints(csn, options) {
|
|
|
329
511
|
let onDeleteRemark = null;
|
|
330
512
|
// comments in sqlite files are causing the JDBC driver to throw an error on deployment
|
|
331
513
|
if (options.testMode && onDelete === 'CASCADE')
|
|
332
|
-
onDeleteRemark = `Composition "${$foreignKeyConstraint.
|
|
333
|
-
referentialConstraints[`${getResultingName(csn, 'quoted', artifactName)}_${$foreignKeyConstraint.
|
|
334
|
-
identifier: `${getResultingName(csn, options.forHana.names, artifactName)}_${$foreignKeyConstraint.
|
|
514
|
+
onDeleteRemark = `Up_ link for Composition "${$foreignKeyConstraint.upLinkFor}" implies existential dependency`;
|
|
515
|
+
referentialConstraints[`${getResultingName(csn, 'quoted', artifactName)}_${$foreignKeyConstraint.sourceAssociation}`] = {
|
|
516
|
+
identifier: `${getResultingName(csn, options.forHana.names, artifactName)}_${$foreignKeyConstraint.sourceAssociation}`,
|
|
335
517
|
foreignKey: dependentKey,
|
|
336
518
|
parentKey,
|
|
337
519
|
dependentTable: artifactName,
|
|
@@ -350,29 +532,6 @@ function createReferentialConstraints(csn, options) {
|
|
|
350
532
|
artifact.$tableConstraints.referential = referentialConstraints;
|
|
351
533
|
}
|
|
352
534
|
}
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* If we have a managed composition with a target cardinality of one, we will treat it like
|
|
356
|
-
* a regular association when it comes to referential constraints.
|
|
357
|
-
* The constraints will thus be generated in the entity containing the composition and not in the target entity.
|
|
358
|
-
*
|
|
359
|
-
* @param {CSN.Composition} composition the composition which might be treated like an association
|
|
360
|
-
* @returns {boolean} true if the composition should be treated as an association in regards to foreign key constraints
|
|
361
|
-
*/
|
|
362
|
-
function treatCompositionLikeAssociation(composition) {
|
|
363
|
-
return Boolean((isToOne(composition) && !composition.$selfOnCondition) || composition.keys);
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
/**
|
|
367
|
-
* returns true if the association/composition has a max target cardinality of one
|
|
368
|
-
*
|
|
369
|
-
* @param {CSN.Element} assocOrComposition
|
|
370
|
-
* @returns {boolean}
|
|
371
|
-
*/
|
|
372
|
-
function isToOne(assocOrComposition) {
|
|
373
|
-
const { min, max } = assocOrComposition.cardinality || {};
|
|
374
|
-
return !min && !max || max === 1;
|
|
375
|
-
}
|
|
376
535
|
}
|
|
377
536
|
|
|
378
537
|
/**
|
|
@@ -21,6 +21,7 @@ function generateDrafts(csn, options, pathDelimiter, messageFunctions) {
|
|
|
21
21
|
const draftSuffix = isDeprecatedEnabled(options, 'generatedEntityNameWithUnderscore') ? '_drafts' : '.drafts';
|
|
22
22
|
// All services of the model - needed for drafts
|
|
23
23
|
const allServices = getServiceNames(csn);
|
|
24
|
+
const draftRoots = new WeakMap();
|
|
24
25
|
const {
|
|
25
26
|
createForeignKeyElement, createAndAddDraftAdminDataProjection, createScalarElement, createAssociationElement,
|
|
26
27
|
addElement, copyAndAddElement, createAssociationPathComparison,
|
|
@@ -155,7 +156,7 @@ function generateDrafts(csn, options, pathDelimiter, messageFunctions) {
|
|
|
155
156
|
const persistenceName = getResultingName(csn, options.forHana.names, draftsArtifactName);
|
|
156
157
|
// Duplicate the artifact as a draft shadow entity
|
|
157
158
|
if (csn.definitions[persistenceName]) {
|
|
158
|
-
const definingDraftRoot = csn.definitions[persistenceName]
|
|
159
|
+
const definingDraftRoot = draftRoots.get(csn.definitions[persistenceName]);
|
|
159
160
|
if (!definingDraftRoot) {
|
|
160
161
|
error(null, [ 'definitions', artifactName ], { name: persistenceName },
|
|
161
162
|
'Generated entity name $(NAME) conflicts with existing entity');
|
|
@@ -176,7 +177,7 @@ function generateDrafts(csn, options, pathDelimiter, messageFunctions) {
|
|
|
176
177
|
// Add draft shadow entity to the csn
|
|
177
178
|
csn.definitions[draftsArtifactName] = draftsArtifact;
|
|
178
179
|
|
|
179
|
-
|
|
180
|
+
draftRoots.set(draftsArtifact, draftRootName);
|
|
180
181
|
if (artifact.$location)
|
|
181
182
|
setProp(draftsArtifact, '$location', artifact.$location);
|
|
182
183
|
|
|
@@ -451,7 +451,7 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
|
|
|
451
451
|
else
|
|
452
452
|
newThing.push(col);
|
|
453
453
|
}
|
|
454
|
-
else if (col.ref && col.$scope === '$magic' && col.ref[0] === '$user' && !col.as) {
|
|
454
|
+
else if (col.ref && col.$scope === '$magic' && ( col.ref[0] === '$user' || col.ref[0] === '$session' ) && !col.as) {
|
|
455
455
|
col.as = implicitAs(col.ref);
|
|
456
456
|
newThing.push(col);
|
|
457
457
|
}
|
|
@@ -552,9 +552,11 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
|
|
|
552
552
|
for (const part of Object.keys(base)) {
|
|
553
553
|
if (excluding.indexOf(part) === -1) {
|
|
554
554
|
// The thing is shadowed - ignore names present because of .inline, as those "disappear"
|
|
555
|
-
if (names[part] !== undefined && !subs[names[part]].inline) {
|
|
556
|
-
|
|
557
|
-
|
|
555
|
+
if (names[part] !== undefined && !subs[names[part]].inline) { // Only works for a single * - but a second is forbidden anyway
|
|
556
|
+
if (names[part] > stars[0]) { // explicit definitions BEFORE the star should stay "infront" of the star
|
|
557
|
+
replaced[part] = true;
|
|
558
|
+
star.push(subs[names[part]]);
|
|
559
|
+
}
|
|
558
560
|
}
|
|
559
561
|
else { // the thing is not shadowed - use the name from the base
|
|
560
562
|
star.push({ ref: [ part ] });
|
|
@@ -79,6 +79,22 @@ function resolveTypeReferences(csn, options, resolved, pathDelimiter) {
|
|
|
79
79
|
if (!isBuiltinType(type)) {
|
|
80
80
|
const directLocalized = parent.localized || false;
|
|
81
81
|
toFinalBaseType(parent, resolved);
|
|
82
|
+
// structured types might not have the child-types replaced.
|
|
83
|
+
// Drill down to ensure this.
|
|
84
|
+
if (parent.elements) {
|
|
85
|
+
const stack = [ parent.elements ];
|
|
86
|
+
while (stack.length > 0) {
|
|
87
|
+
const elements = stack.pop();
|
|
88
|
+
for (const e of Object.values(elements)) {
|
|
89
|
+
if (e.type && !isBuiltinType(e.type))
|
|
90
|
+
toFinalBaseType(e, resolved);
|
|
91
|
+
|
|
92
|
+
if (e.elements)
|
|
93
|
+
stack.push(e.elements);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
82
98
|
if (!directLocalized)
|
|
83
99
|
removeLocalized(parent);
|
|
84
100
|
}
|
|
@@ -271,7 +287,7 @@ function flattenElements(csn, options, pathDelimiter, error) {
|
|
|
271
287
|
previous[name] = element;
|
|
272
288
|
return previous;
|
|
273
289
|
}, Object.create(null));
|
|
274
|
-
});
|
|
290
|
+
}, true);
|
|
275
291
|
}
|
|
276
292
|
|
|
277
293
|
/**
|