@sap/cds-compiler 4.0.2 → 4.2.2
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 +200 -5
- package/bin/cdsc.js +18 -15
- package/doc/CHANGELOG_BETA.md +16 -0
- package/doc/CHANGELOG_DEPRECATED.md +15 -0
- package/lib/api/main.js +33 -13
- package/lib/api/options.js +2 -2
- package/lib/api/validate.js +25 -25
- package/lib/base/location.js +6 -7
- package/lib/base/message-registry.js +123 -42
- package/lib/base/messages.js +18 -10
- package/lib/base/model.js +43 -10
- package/lib/checks/defaultValues.js +6 -6
- package/lib/checks/elements.js +11 -10
- package/lib/checks/foreignKeys.js +0 -5
- package/lib/checks/manyNavigations.js +33 -0
- package/lib/checks/onConditions.js +22 -14
- package/lib/checks/queryNoDbArtifacts.js +132 -73
- package/lib/checks/selectItems.js +4 -55
- package/lib/checks/sql-snippets.js +15 -4
- package/lib/checks/types.js +3 -3
- package/lib/checks/utils.js +4 -3
- package/lib/checks/validator.js +3 -1
- package/lib/compiler/.eslintrc.json +2 -1
- package/lib/compiler/assert-consistency.js +71 -40
- package/lib/compiler/base.js +7 -2
- package/lib/compiler/builtins.js +40 -41
- package/lib/compiler/checks.js +415 -367
- package/lib/compiler/classes.js +62 -0
- package/lib/compiler/cycle-detector.js +9 -9
- package/lib/compiler/define.js +124 -90
- package/lib/compiler/extend.js +115 -88
- package/lib/compiler/finalize-parse-cdl.js +26 -25
- package/lib/compiler/generate.js +57 -49
- package/lib/compiler/index.js +56 -56
- package/lib/compiler/kick-start.js +10 -7
- package/lib/compiler/moduleLayers.js +1 -1
- package/lib/compiler/populate.js +180 -144
- package/lib/compiler/propagator.js +10 -9
- package/lib/compiler/resolve.js +321 -246
- package/lib/compiler/shared.js +812 -433
- package/lib/compiler/tweak-assocs.js +114 -50
- package/lib/compiler/utils.js +241 -46
- package/lib/edm/.eslintrc.json +40 -1
- package/lib/edm/annotations/genericTranslation.js +721 -707
- package/lib/edm/annotations/preprocessAnnotations.js +88 -77
- package/lib/edm/csn2edm.js +389 -378
- package/lib/edm/edm.js +679 -770
- package/lib/edm/edmAnnoPreprocessor.js +132 -146
- package/lib/edm/edmInboundChecks.js +29 -27
- package/lib/edm/edmPreprocessor.js +689 -648
- package/lib/edm/edmUtils.js +279 -300
- package/lib/gen/Dictionary.json +34 -10
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +2857 -2856
- package/lib/json/from-csn.js +77 -51
- package/lib/json/to-csn.js +15 -15
- package/lib/language/antlrParser.js +2 -1
- package/lib/language/genericAntlrParser.js +52 -43
- package/lib/language/language.g4 +61 -64
- package/lib/language/multiLineStringParser.js +2 -0
- package/lib/main.d.ts +65 -0
- package/lib/model/csnRefs.js +37 -19
- package/lib/model/csnUtils.js +51 -18
- package/lib/model/revealInternalProperties.js +30 -22
- package/lib/modelCompare/compare.js +149 -41
- package/lib/modelCompare/utils/filter.js +55 -25
- package/lib/optionProcessor.js +21 -9
- package/lib/render/manageConstraints.js +20 -17
- package/lib/render/toCdl.js +63 -23
- package/lib/render/toHdbcds.js +2 -2
- package/lib/render/toRename.js +4 -9
- package/lib/render/toSql.js +82 -35
- package/lib/render/utils/common.js +11 -9
- package/lib/render/utils/unique.js +52 -0
- package/lib/transform/db/applyTransformations.js +62 -21
- package/lib/transform/db/assertUnique.js +7 -8
- package/lib/transform/db/associations.js +2 -2
- package/lib/transform/db/cdsPersistence.js +9 -9
- package/lib/transform/db/constraints.js +47 -17
- package/lib/transform/db/expansion.js +138 -68
- package/lib/transform/db/flattening.js +98 -30
- package/lib/transform/db/rewriteCalculatedElements.js +20 -14
- package/lib/transform/db/temporal.js +1 -1
- package/lib/transform/db/transformExists.js +8 -7
- package/lib/transform/db/views.js +73 -33
- package/lib/transform/draft/db.js +11 -9
- package/lib/transform/draft/odata.js +1 -1
- package/lib/transform/{forOdataNew.js → forOdata.js} +10 -7
- package/lib/transform/forRelationalDB.js +148 -136
- package/lib/transform/localized.js +92 -54
- package/lib/transform/odata/toFinalBaseType.js +3 -3
- package/lib/transform/{transformUtilsNew.js → transformUtils.js} +13 -111
- package/lib/transform/translateAssocsToJoins.js +14 -28
- package/lib/utils/file.js +7 -7
- package/lib/utils/moduleResolve.js +210 -121
- package/lib/utils/objectUtils.js +1 -1
- package/package.json +5 -5
- package/share/messages/check-proper-type-of.md +1 -1
- package/share/messages/{check-proper-type.md → def-missing-type.md} +3 -5
- package/share/messages/message-explanations.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/*
|
|
4
4
|
* Module for general (partial) CSN looper functions, respecting dictionaries and allowing
|
|
5
5
|
* to pass custom callbacks for certain properties like "ref".
|
|
6
6
|
*
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
*
|
|
11
11
|
* @module lib/transform/db/applyTransformations
|
|
12
12
|
*/
|
|
13
|
+
|
|
14
|
+
|
|
13
15
|
const { setProp } = require('../../base/model');
|
|
14
16
|
|
|
15
17
|
|
|
@@ -18,11 +20,17 @@ const { setProp } = require('../../base/model');
|
|
|
18
20
|
* @param {string} prop The property of parent to start at
|
|
19
21
|
* @param {object} customTransformers Map of prop to transform and function to apply
|
|
20
22
|
* @param {Function[]} [artifactTransformers=[]] Transformations to run on the artifacts, like forEachDefinition
|
|
21
|
-
* @param {applyTransformationsOptions} [
|
|
23
|
+
* @param {applyTransformationsOptions} [_options={}]
|
|
22
24
|
* @param {CSN.Path} path Path to parent
|
|
23
25
|
* @returns {object} parent with transformations applied
|
|
24
26
|
*/
|
|
25
|
-
function applyTransformationsInternal( parent, prop, customTransformers, artifactTransformers,
|
|
27
|
+
function applyTransformationsInternal( parent, prop, customTransformers, artifactTransformers, _options, path = [] ) {
|
|
28
|
+
const options = { ..._options };
|
|
29
|
+
if (!options.skipStandard)
|
|
30
|
+
options.skipStandard = { $tableConstraints: true };
|
|
31
|
+
else if (options.skipStandard.$tableConstraints === undefined)
|
|
32
|
+
options.skipStandard = { ...options.skipStandard, ...{ $tableConstraints: true } };
|
|
33
|
+
|
|
26
34
|
const transformers = {
|
|
27
35
|
elements: dictionary,
|
|
28
36
|
definitions: dictionary,
|
|
@@ -43,23 +51,24 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
|
|
|
43
51
|
standard( parent, name, parent[name] );
|
|
44
52
|
}
|
|
45
53
|
else {
|
|
46
|
-
standard(parent, prop, parent[prop]);
|
|
54
|
+
standard( parent, prop, parent[prop] );
|
|
47
55
|
}
|
|
48
56
|
return parent;
|
|
49
57
|
|
|
50
58
|
/**
|
|
51
|
-
* Default transformer for things that are not dictionaries
|
|
59
|
+
* Default transformer for things that are not dictionaries or dictionary entries,
|
|
60
|
+
* such as "type" or "keys".
|
|
52
61
|
* The customTransformers are applied here (and only here).
|
|
53
62
|
*
|
|
54
63
|
* @param {object | Array} _parent the thing that has _prop
|
|
55
|
-
* @param {string|number} _prop the name of the current property
|
|
64
|
+
* @param {string|number} _prop the name of the current property or index
|
|
56
65
|
* @param {object} node The value of node[_prop]
|
|
57
66
|
*/
|
|
58
67
|
function standard( _parent, _prop, node ) {
|
|
59
68
|
if (!node || typeof node !== 'object' ||
|
|
60
69
|
!{}.propertyIsEnumerable.call( _parent, _prop ) ||
|
|
61
70
|
(typeof _prop === 'string' && _prop.startsWith('@')) ||
|
|
62
|
-
(options.skipIgnore && node
|
|
71
|
+
(options.skipIgnore && node.$ignore) ||
|
|
63
72
|
options.skipStandard?.[_prop]
|
|
64
73
|
)
|
|
65
74
|
return;
|
|
@@ -75,13 +84,35 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
|
|
|
75
84
|
const trans = transformers[name] || standard;
|
|
76
85
|
if (customTransformers[name])
|
|
77
86
|
customTransformers[name](node, name, node[name], csnPath, _parent, _prop);
|
|
78
|
-
|
|
79
87
|
trans( node, name, node[name], csnPath );
|
|
80
88
|
}
|
|
81
89
|
}
|
|
82
90
|
csnPath.pop();
|
|
83
91
|
}
|
|
84
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Transformer for dictionary entries. Similar to standard(), but does not filter
|
|
95
|
+
* based on the given name. Otherwise, `options.skipStandards` could accidentally skip
|
|
96
|
+
* dictionary entries (e.g. entity called `@Name`).
|
|
97
|
+
*
|
|
98
|
+
* @param {object | Array} dict the thing that has _prop
|
|
99
|
+
* @param {string|number} entryName the name of the current property
|
|
100
|
+
* @param {object} node The value of node[_prop]
|
|
101
|
+
*/
|
|
102
|
+
function dictEntry( dict, entryName, node ) {
|
|
103
|
+
if (!node || typeof node !== 'object' || (options.skipIgnore && node.$ignore))
|
|
104
|
+
return;
|
|
105
|
+
|
|
106
|
+
csnPath.push( entryName );
|
|
107
|
+
for (const name of Object.getOwnPropertyNames( node )) {
|
|
108
|
+
const trans = transformers[name] || standard;
|
|
109
|
+
if (customTransformers[name])
|
|
110
|
+
customTransformers[name](node, name, node[name], csnPath, dict);
|
|
111
|
+
trans( node, name, node[name], csnPath );
|
|
112
|
+
}
|
|
113
|
+
csnPath.pop();
|
|
114
|
+
}
|
|
115
|
+
|
|
85
116
|
/**
|
|
86
117
|
* Transformer for things that are dictionaries - like "elements".
|
|
87
118
|
*
|
|
@@ -90,12 +121,12 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
|
|
|
90
121
|
* @param {object} dict The value of node[_prop]
|
|
91
122
|
*/
|
|
92
123
|
function dictionary( node, _prop, dict ) {
|
|
93
|
-
// Allow skipping
|
|
94
|
-
if (options.skipDict
|
|
124
|
+
// Allow skipping dictionaries like actions in forRelationalDB
|
|
125
|
+
if (options.skipDict?.[_prop] || dict == null) // with universal CSN, dictionaries might be null
|
|
95
126
|
return;
|
|
96
127
|
csnPath.push( _prop );
|
|
97
128
|
for (const name of Object.getOwnPropertyNames( dict ))
|
|
98
|
-
|
|
129
|
+
dictEntry( dict, name, dict[name] );
|
|
99
130
|
|
|
100
131
|
if (!Object.prototype.propertyIsEnumerable.call( node, _prop ))
|
|
101
132
|
setProp(node, `$${_prop}`, dict);
|
|
@@ -112,13 +143,9 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
|
|
|
112
143
|
function definitions( node, _prop, dict ) {
|
|
113
144
|
csnPath.push( _prop );
|
|
114
145
|
for (const name of Object.getOwnPropertyNames( dict )) {
|
|
115
|
-
|
|
116
|
-
(options && options.skipArtifact && options.skipArtifact(dict[name], name)) ||
|
|
117
|
-
(options && options.skip && options.skip.includes(dict[name].kind)) ||
|
|
118
|
-
false;
|
|
119
|
-
if (!skip) {
|
|
146
|
+
if (!isArtifactSkipped( dict, name )) {
|
|
120
147
|
artifactTransformers.forEach(fn => fn(dict, name, dict[name]));
|
|
121
|
-
|
|
148
|
+
dictEntry( dict, name, dict[name] );
|
|
122
149
|
}
|
|
123
150
|
}
|
|
124
151
|
if (!Object.prototype.propertyIsEnumerable.call( node, _prop ))
|
|
@@ -126,6 +153,20 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
|
|
|
126
153
|
csnPath.pop();
|
|
127
154
|
}
|
|
128
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Whether the given artifact `dict[name]` is skipped via options.
|
|
158
|
+
*
|
|
159
|
+
* @param {object} dict
|
|
160
|
+
* @param {string} name
|
|
161
|
+
* @returns {boolean}
|
|
162
|
+
*/
|
|
163
|
+
function isArtifactSkipped( dict, name ) {
|
|
164
|
+
return options && ((options.allowArtifact && !options.allowArtifact(dict[name], name)) ||
|
|
165
|
+
(options.skipArtifact && options.skipArtifact(dict[name], name)) ||
|
|
166
|
+
(options.skip?.includes(dict[name].kind))) ||
|
|
167
|
+
false;
|
|
168
|
+
}
|
|
169
|
+
|
|
129
170
|
/**
|
|
130
171
|
* Keep looping through the pathRef - because in a .ref we can have .args and .where
|
|
131
172
|
*
|
|
@@ -173,7 +214,7 @@ function applyTransformations( csn, customTransformers = {}, artifactTransformer
|
|
|
173
214
|
if (options.skipIgnore === undefined)
|
|
174
215
|
options.skipIgnore = true;
|
|
175
216
|
|
|
176
|
-
if (csn
|
|
217
|
+
if (csn?.definitions)
|
|
177
218
|
return applyTransformationsInternal(csn, 'definitions', customTransformers, artifactTransformers, options);
|
|
178
219
|
return csn;
|
|
179
220
|
}
|
|
@@ -230,12 +271,12 @@ module.exports = {
|
|
|
230
271
|
|
|
231
272
|
/**
|
|
232
273
|
* @typedef {object} applyTransformationsOptions
|
|
233
|
-
* @property {(artifact, name) => boolean} [allowArtifact] to only allow certain artifacts
|
|
234
|
-
* @property {(artifact, name) => boolean} [skipArtifact] to skip certain artifacts
|
|
274
|
+
* @property {(artifact: CSN.Artifact, name: string) => boolean} [allowArtifact] to only allow certain artifacts
|
|
275
|
+
* @property {(artifact: CSN.Artifact, name: string) => boolean} [skipArtifact] to skip certain artifacts
|
|
235
276
|
* @property {boolean} [drillRef] whether to drill into infix/args
|
|
236
277
|
* @property {string[]} [skip] skip definitions from certain kind
|
|
237
278
|
* @property {object} [skipStandard] stop drill-down on certain "standard" props
|
|
238
279
|
* @property {object} [skipDict] stop drill-down on certain "dictionary" props
|
|
239
|
-
* @property {boolean} [skipIgnore=true] Whether to skip
|
|
280
|
+
* @property {boolean} [skipIgnore=true] Whether to skip $ignore elements or not
|
|
240
281
|
* @property {boolean} [directDict=false] Implicitly set via applyTransformationsOnDictionary
|
|
241
282
|
*/
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { forEachDefinition, hasAnnotationValue } = require('../../model/csnUtils');
|
|
4
|
-
const { getTransformers } = require('../
|
|
5
|
-
const { setProp } = require('../../base/model');
|
|
4
|
+
const { getTransformers } = require('../transformUtils');
|
|
6
5
|
const { pathName } = require('../../compiler/utils');
|
|
7
6
|
|
|
8
7
|
|
|
@@ -97,7 +96,7 @@ function processAssertUnique( csn, options, error, info ) {
|
|
|
97
96
|
}
|
|
98
97
|
// 10) Store remaining paths (if any) in constraint dictionary
|
|
99
98
|
if (flattenedPathObjects.length)
|
|
100
|
-
constraintDict[constraintName.join('.')] = flattenedPathObjects;
|
|
99
|
+
constraintDict[constraintName.join('.')] = { paths: flattenedPathObjects, parentTable: artifactName };
|
|
101
100
|
}
|
|
102
101
|
}
|
|
103
102
|
|
|
@@ -110,9 +109,9 @@ function processAssertUnique( csn, options, error, info ) {
|
|
|
110
109
|
// preserve dictionary in '$tableConstraints' on the artifact for path rewriting and rendering
|
|
111
110
|
if (Object.keys(constraintDict).length) {
|
|
112
111
|
if (!('$tableConstraints' in artifact))
|
|
113
|
-
|
|
112
|
+
artifact.$tableConstraints = Object.create(null);
|
|
114
113
|
|
|
115
|
-
|
|
114
|
+
artifact.$tableConstraints.unique = constraintDict;
|
|
116
115
|
}
|
|
117
116
|
|
|
118
117
|
/**
|
|
@@ -286,7 +285,7 @@ function rewriteUniqueConstraints( csn, options, pathDelimiter ) {
|
|
|
286
285
|
}
|
|
287
286
|
for (const uniqueConstraint in artifact.$tableConstraints.unique) {
|
|
288
287
|
// iterate over each constraint
|
|
289
|
-
const c = uniqueConstraints[uniqueConstraint];
|
|
288
|
+
const c = uniqueConstraints[uniqueConstraint].paths;
|
|
290
289
|
const rewrittenPaths = [];
|
|
291
290
|
// and inspect each path of the constraint
|
|
292
291
|
c.forEach((cpath) => {
|
|
@@ -319,7 +318,7 @@ function rewriteUniqueConstraints( csn, options, pathDelimiter ) {
|
|
|
319
318
|
}
|
|
320
319
|
});
|
|
321
320
|
// preserve the rewritten and filtered paths for toSql
|
|
322
|
-
uniqueConstraints[uniqueConstraint] = rewrittenPaths;
|
|
321
|
+
uniqueConstraints[uniqueConstraint] = { paths: rewrittenPaths, parentTable: uniqueConstraints[uniqueConstraint].parentTable };
|
|
323
322
|
|
|
324
323
|
// now add the index for HANA CDS
|
|
325
324
|
if (options.transformation === 'hdbcds') {
|
|
@@ -335,7 +334,7 @@ function rewriteUniqueConstraints( csn, options, pathDelimiter ) {
|
|
|
335
334
|
artifact.technicalConfig.hana.indexes[uniqueConstraint] = index;
|
|
336
335
|
}
|
|
337
336
|
}
|
|
338
|
-
|
|
337
|
+
artifact.$tableConstraints.unique = uniqueConstraints;
|
|
339
338
|
}
|
|
340
339
|
}
|
|
341
340
|
}
|
|
@@ -48,8 +48,8 @@ function attachOnConditions( csn, csnUtils, pathDelimiter ) {
|
|
|
48
48
|
// Assemble an ON-condition with the foreign keys created in earlier steps
|
|
49
49
|
const onCondParts = [];
|
|
50
50
|
let joinWithAnd = false;
|
|
51
|
-
if (elem.keys.length === 0) { // TODO: really kill instead of
|
|
52
|
-
elem
|
|
51
|
+
if (elem.keys.length === 0) { // TODO: really kill instead of $ignore?
|
|
52
|
+
elem.$ignore = true;
|
|
53
53
|
}
|
|
54
54
|
else {
|
|
55
55
|
for (const foreignKey of elem.keys) {
|
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
const {
|
|
4
4
|
forEachGeneric, forEachMemberRecursively, hasAnnotationValue, isPersistedOnDatabase,
|
|
5
5
|
} = require('../../model/csnUtils');
|
|
6
|
-
const transformUtils = require('../
|
|
6
|
+
const transformUtils = require('../transformUtils');
|
|
7
7
|
|
|
8
8
|
const exists = '@cds.persistence.exists';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Return a callback function for forEachDefinition that marks artifacts that are abstract or @cds.persistence.exists/skip
|
|
12
|
-
* with
|
|
12
|
+
* with $ignore.
|
|
13
13
|
*
|
|
14
14
|
* @returns {(artifact: CSN.Artifact, artifactName) => void} Callback function for forEachDefinition
|
|
15
15
|
*/
|
|
@@ -24,12 +24,12 @@ function getAnnoProcessor() {
|
|
|
24
24
|
hasAnnotationValue(artifact, '@cds.persistence.skip') ||
|
|
25
25
|
hasAnnotationValue(artifact, exists));
|
|
26
26
|
if (ignoreArtifact)
|
|
27
|
-
artifact
|
|
27
|
+
artifact.$ignore = true;
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
* Return a callback function for forEachDefinition that marks associations with
|
|
32
|
+
* Return a callback function for forEachDefinition that marks associations with $ignore
|
|
33
33
|
* if their target does not reach the database, i.e. marked with @cds.persistence.skip or is abstract
|
|
34
34
|
*
|
|
35
35
|
* @param {CSN.Model} csn
|
|
@@ -72,7 +72,7 @@ function getAssocToSkippedIgnorer( csn, options, messageFunctions, csnUtils ) {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
|
-
* Mark the given member with
|
|
75
|
+
* Mark the given member with $ignore if it is an association/composition and its target is unreachable.
|
|
76
76
|
*
|
|
77
77
|
* @param {CSN.Element} member
|
|
78
78
|
* @param {string} memberName
|
|
@@ -81,13 +81,13 @@ function getAssocToSkippedIgnorer( csn, options, messageFunctions, csnUtils ) {
|
|
|
81
81
|
*/
|
|
82
82
|
function ignore( member, memberName, prop, path ) {
|
|
83
83
|
if (options.sqlDialect === 'hana' &&
|
|
84
|
-
!member
|
|
84
|
+
!member.$ignore && member.target &&
|
|
85
85
|
isAssocOrComposition(member) &&
|
|
86
86
|
!isPersistedOnDatabase(csn.definitions[member.target])) {
|
|
87
87
|
info(null, path,
|
|
88
88
|
{ target: member.target, anno: '@cds.persistence.skip' },
|
|
89
|
-
'Association has been removed as
|
|
90
|
-
member
|
|
89
|
+
'Association has been removed, as its target $(TARGET) is annotated with $(ANNO) and can\'t be rendered in SAP HANA SQL');
|
|
90
|
+
member.$ignore = true;
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
}
|
|
@@ -124,7 +124,7 @@ function getPersistenceTableProcessor( csn, options, messageFunctions ) {
|
|
|
124
124
|
|
|
125
125
|
recurseElements(artifact, [ 'definitions', artifactName ], (member, path) => {
|
|
126
126
|
// All elements must have a type for this to work
|
|
127
|
-
if (!member
|
|
127
|
+
if (!member.$ignore && !member.kind && !member.type) {
|
|
128
128
|
error(null, path, { anno: '@cds.persistence.table' },
|
|
129
129
|
'Expecting element to have a type if view is annotated with $(ANNO)');
|
|
130
130
|
}
|
|
@@ -56,6 +56,25 @@ function createReferentialConstraints( csn, options ) {
|
|
|
56
56
|
},
|
|
57
57
|
});
|
|
58
58
|
}
|
|
59
|
+
// for `texts` compositions, we may generate foreign key constraints even w/o `up_`
|
|
60
|
+
else if (elementName === 'texts' && element.target === `${path[path.length - 1]}.texts`) {
|
|
61
|
+
const { on } = element;
|
|
62
|
+
const target = csn.definitions[element.target];
|
|
63
|
+
// `texts` entities have a key named "locale"
|
|
64
|
+
const targetSideHasLocaleKey = target.elements.locale?.key;
|
|
65
|
+
if (targetSideHasLocaleKey && !skipConstraintGeneration(parent, target, { /* there is no assoc */ })) {
|
|
66
|
+
const sourceElements = Array.from(elementsOfSourceSide(on, elements));
|
|
67
|
+
const targetElements = Array.from(elementsOfTargetSide(on, target.elements));
|
|
68
|
+
// `texts` entities have all the keys the original entity has
|
|
69
|
+
const allElementsAreKeysAndHaveTheSameName = targetElements.length &&
|
|
70
|
+
targetElements.every(
|
|
71
|
+
([ targetKey, e ]) => e.key &&
|
|
72
|
+
sourceElements.some(([ sourceKey, sourceElement ]) => sourceElement.key && targetKey === sourceKey )
|
|
73
|
+
);
|
|
74
|
+
if (allElementsAreKeysAndHaveTheSameName)
|
|
75
|
+
attachConstraintsToDependentKeys(targetElements, sourceElements, path[path.length - 1], 'texts', { texts: true });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
59
78
|
}
|
|
60
79
|
},
|
|
61
80
|
}, [], { skipIgnore: false, skipArtifact: a => a.query || a.kind !== 'entity' });
|
|
@@ -134,7 +153,7 @@ function createReferentialConstraints( csn, options ) {
|
|
|
134
153
|
* @param {Array} parentKeys array holding parent keys in the format [['key1', 'value1'], [...], ...]
|
|
135
154
|
* @param {CSN.PathSegment} parentTable the sql-table where the foreign key constraints will be pointing to
|
|
136
155
|
* @param {CSN.PathSegment} sourceAssociation the name of the association from which the constraint originates
|
|
137
|
-
* @param {CSN.PathSegment} upLinkFor the name of the composition which used this association in a `$self = <comp>.<up_>` comparison
|
|
156
|
+
* @param {CSN.PathSegment | object} upLinkFor the name of the composition which used this association in a `$self = <comp>.<up_>` comparison
|
|
138
157
|
* it is used for a comment in the constraint, which is only printed out in test-mode
|
|
139
158
|
*/
|
|
140
159
|
function attachConstraintsToDependentKeys( dependentKeys, parentKeys, parentTable, sourceAssociation, upLinkFor = null ) {
|
|
@@ -440,13 +459,16 @@ function createReferentialConstraints( csn, options ) {
|
|
|
440
459
|
*/
|
|
441
460
|
function elementsOfTargetSide( on, targetElements ) {
|
|
442
461
|
const elements = new Map();
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
462
|
+
const findElements = (tokenStream) => {
|
|
463
|
+
tokenStream
|
|
464
|
+
.forEach((element) => {
|
|
465
|
+
if (typeof element === 'object' && element.ref?.length > 1 && targetElements[element.ref[element.ref.length - 1]])
|
|
466
|
+
elements.set(element.ref[element.ref.length - 1], targetElements[element.ref[element.ref.length - 1]]);
|
|
467
|
+
else if (element.xpr)
|
|
468
|
+
findElements(element.xpr);
|
|
469
|
+
});
|
|
470
|
+
};
|
|
471
|
+
findElements(on);
|
|
450
472
|
return elements;
|
|
451
473
|
}
|
|
452
474
|
|
|
@@ -459,12 +481,16 @@ function createReferentialConstraints( csn, options ) {
|
|
|
459
481
|
*/
|
|
460
482
|
function elementsOfSourceSide( on, sourceElements ) {
|
|
461
483
|
const elements = new Map();
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
484
|
+
const findElements = (tokenStream) => {
|
|
485
|
+
tokenStream
|
|
486
|
+
.forEach((element) => {
|
|
487
|
+
if (typeof element === 'object' && element.ref?.length === 1 && sourceElements[element.ref[0]])
|
|
488
|
+
elements.set(element.ref[0], sourceElements[element.ref[0]]);
|
|
489
|
+
else if (element.xpr)
|
|
490
|
+
findElements(element.xpr);
|
|
491
|
+
});
|
|
492
|
+
};
|
|
493
|
+
findElements(on);
|
|
468
494
|
return elements;
|
|
469
495
|
}
|
|
470
496
|
|
|
@@ -507,12 +533,16 @@ function createReferentialConstraints( csn, options ) {
|
|
|
507
533
|
dependentKey.push(foreignKeyName);
|
|
508
534
|
onDeleteRules.add($foreignKeyConstraintCopy.onDelete);
|
|
509
535
|
});
|
|
510
|
-
// onDelete Rule is the "weakest" rule applicable. Precedence: RESTRICT >
|
|
536
|
+
// onDelete Rule is the "weakest" rule applicable. Precedence: RESTRICT > CASCADE
|
|
511
537
|
const onDelete = onDeleteRules.has('RESTRICT') ? 'RESTRICT' : 'CASCADE';
|
|
512
538
|
let onDeleteRemark = null;
|
|
513
539
|
// comments in sqlite files are causing the JDBC driver to throw an error on deployment
|
|
514
|
-
if (options.testMode && onDelete === 'CASCADE')
|
|
515
|
-
|
|
540
|
+
if (options.testMode && onDelete === 'CASCADE') {
|
|
541
|
+
if ($foreignKeyConstraint.upLinkFor?.texts)
|
|
542
|
+
onDeleteRemark = `Constraint originates from localized composition ”${$foreignKeyConstraint.parentTable}:texts“`;
|
|
543
|
+
else
|
|
544
|
+
onDeleteRemark = `Up_ link for Composition "${$foreignKeyConstraint.upLinkFor}" implies existential dependency`;
|
|
545
|
+
}
|
|
516
546
|
// constraint identifier usually start with `c__` to avoid name clashes
|
|
517
547
|
let identifier = options.pre2134ReferentialConstraintNames ? '' : 'c__';
|
|
518
548
|
identifier += `${getResultingName(csn, options.sqlMapping, artifactName)}_${$foreignKeyConstraint.sourceAssociation}`;
|