@sap/cds-compiler 2.11.2 → 2.13.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +175 -2
- package/bin/.eslintrc.json +1 -2
- package/bin/cds_update_identifiers.js +10 -8
- package/bin/cdsc.js +23 -17
- package/bin/cdsse.js +2 -2
- package/bin/cdsv2m.js +3 -2
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +25 -6
- package/doc/CHANGELOG_DEPRECATED.md +22 -6
- package/doc/NameResolution.md +21 -16
- package/lib/api/main.js +32 -79
- package/lib/api/options.js +3 -2
- package/lib/api/validate.js +2 -1
- package/lib/backends.js +16 -26
- package/lib/base/dictionaries.js +0 -8
- package/lib/base/error.js +26 -0
- package/lib/base/keywords.js +10 -19
- package/lib/base/location.js +9 -4
- package/lib/base/message-registry.js +75 -9
- package/lib/base/messages.js +31 -35
- package/lib/base/model.js +2 -62
- package/lib/base/optionProcessorHelper.js +246 -183
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/checks/actionsFunctions.js +2 -1
- package/lib/checks/annotationsOData.js +1 -1
- package/lib/checks/cdsPersistence.js +2 -1
- package/lib/checks/emptyOrOnlyVirtual.js +2 -2
- package/lib/checks/enricher.js +17 -1
- package/lib/checks/foreignKeys.js +4 -4
- package/lib/checks/invalidTarget.js +3 -1
- package/lib/checks/managedInType.js +4 -4
- package/lib/checks/managedWithoutKeys.js +3 -1
- package/lib/checks/queryNoDbArtifacts.js +1 -3
- package/lib/checks/selectItems.js +4 -4
- package/lib/checks/sql-snippets.js +94 -0
- package/lib/checks/types.js +1 -1
- package/lib/checks/unknownMagic.js +1 -1
- package/lib/checks/validator.js +12 -7
- package/lib/compiler/assert-consistency.js +12 -8
- package/lib/compiler/base.js +0 -1
- package/lib/compiler/builtins.js +42 -21
- package/lib/compiler/checks.js +46 -12
- package/lib/compiler/cycle-detector.js +1 -1
- package/lib/compiler/define.js +1103 -0
- package/lib/compiler/extend.js +983 -0
- package/lib/compiler/finalize-parse-cdl.js +231 -0
- package/lib/compiler/index.js +46 -39
- package/lib/compiler/kick-start.js +190 -0
- package/lib/compiler/moduleLayers.js +4 -4
- package/lib/compiler/populate.js +1226 -0
- package/lib/compiler/propagator.js +113 -47
- package/lib/compiler/resolve.js +1433 -0
- package/lib/compiler/shared.js +100 -65
- package/lib/compiler/tweak-assocs.js +529 -0
- package/lib/compiler/utils.js +215 -33
- package/lib/edm/.eslintrc.json +5 -0
- package/lib/edm/annotations/genericTranslation.js +38 -25
- package/lib/edm/annotations/preprocessAnnotations.js +3 -3
- package/lib/edm/csn2edm.js +10 -9
- package/lib/edm/edm.js +19 -20
- package/lib/edm/edmPreprocessor.js +166 -95
- package/lib/edm/edmUtils.js +127 -34
- package/lib/gen/Dictionary.json +92 -43
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +11 -1
- package/lib/gen/language.tokens +86 -82
- package/lib/gen/languageLexer.interp +18 -1
- package/lib/gen/languageLexer.js +925 -847
- package/lib/gen/languageLexer.tokens +78 -74
- package/lib/gen/languageParser.js +5434 -4298
- package/lib/json/from-csn.js +59 -17
- package/lib/json/to-csn.js +189 -71
- package/lib/language/antlrParser.js +3 -3
- package/lib/language/docCommentParser.js +3 -3
- package/lib/language/errorStrategy.js +26 -8
- package/lib/language/genericAntlrParser.js +144 -53
- package/lib/language/language.g4 +424 -200
- package/lib/language/multiLineStringParser.js +536 -0
- package/lib/main.d.ts +550 -61
- package/lib/main.js +38 -11
- package/lib/model/api.js +3 -1
- package/lib/model/csnRefs.js +322 -198
- package/lib/model/csnUtils.js +226 -370
- package/lib/model/enrichCsn.js +124 -69
- package/lib/model/revealInternalProperties.js +29 -7
- package/lib/model/sortViews.js +10 -2
- package/lib/modelCompare/compare.js +17 -12
- package/lib/optionProcessor.js +8 -3
- package/lib/render/.eslintrc.json +1 -2
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/manageConstraints.js +36 -33
- package/lib/render/toCdl.js +174 -275
- package/lib/render/toHdbcds.js +203 -122
- package/lib/render/toRename.js +7 -10
- package/lib/render/toSql.js +161 -82
- package/lib/render/utils/common.js +22 -8
- package/lib/render/utils/sql.js +10 -7
- package/lib/render/utils/stringEscapes.js +111 -0
- package/lib/sql-identifier.js +1 -1
- package/lib/transform/.eslintrc.json +5 -0
- package/lib/transform/braceExpression.js +4 -2
- package/lib/transform/db/.eslintrc.json +2 -0
- package/lib/transform/db/applyTransformations.js +212 -0
- package/lib/transform/db/assertUnique.js +1 -1
- package/lib/transform/db/associations.js +187 -0
- package/lib/transform/db/cdsPersistence.js +150 -0
- package/lib/transform/db/constraints.js +61 -56
- package/lib/transform/db/expansion.js +50 -29
- package/lib/transform/db/flattening.js +556 -106
- package/lib/transform/db/groupByOrderBy.js +3 -1
- package/lib/transform/db/temporal.js +236 -0
- package/lib/transform/db/transformExists.js +103 -28
- package/lib/transform/db/views.js +92 -44
- package/lib/transform/draft/.eslintrc.json +38 -0
- package/lib/transform/{db/draft.js → draft/db.js} +9 -7
- package/lib/transform/draft/odata.js +227 -0
- package/lib/transform/forHanaNew.js +98 -783
- package/lib/transform/forOdataNew.js +22 -175
- package/lib/transform/localized.js +36 -32
- package/lib/transform/odata/generateForeignKeyElements.js +3 -3
- package/lib/transform/odata/referenceFlattener.js +95 -89
- package/lib/transform/odata/structureFlattener.js +1 -1
- package/lib/transform/odata/toFinalBaseType.js +86 -12
- package/lib/transform/odata/typesExposure.js +5 -5
- package/lib/transform/odata/utils.js +2 -2
- package/lib/transform/transformUtilsNew.js +47 -33
- package/lib/transform/translateAssocsToJoins.js +13 -30
- package/lib/transform/universalCsn/.eslintrc.json +36 -0
- package/lib/transform/universalCsn/coreComputed.js +170 -0
- package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
- package/lib/transform/universalCsn/utils.js +63 -0
- package/lib/utils/file.js +8 -3
- package/lib/utils/objectUtils.js +30 -0
- package/lib/utils/timetrace.js +8 -2
- package/package.json +1 -1
- package/share/messages/README.md +26 -0
- package/lib/compiler/definer.js +0 -2349
- package/lib/compiler/resolver.js +0 -2922
- package/lib/transform/db/helpers.js +0 -58
- package/lib/transform/universalCsnEnricher.js +0 -67
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
applyTransformationsOnNonDictionary,
|
|
5
|
+
applyTransformations,
|
|
6
|
+
getUtils,
|
|
7
|
+
} = require('../../model/csnUtils');
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* In all .elements of entities and views (and their bound actions/functions), create the on-condition for
|
|
12
|
+
* a managed associations. This needs to happen after the .keys are expanded and the corresponding elements are created.
|
|
13
|
+
*
|
|
14
|
+
* @param {CSN.Model} csn
|
|
15
|
+
* @param {string} pathDelimiter
|
|
16
|
+
* @returns {CSN.Model} Return the input csn, with the transformations applied
|
|
17
|
+
*/
|
|
18
|
+
function attachOnConditions(csn, pathDelimiter) {
|
|
19
|
+
const {
|
|
20
|
+
isManagedAssociation,
|
|
21
|
+
} = getUtils(csn);
|
|
22
|
+
|
|
23
|
+
const alreadyHandled = new WeakMap();
|
|
24
|
+
applyTransformations(csn, {
|
|
25
|
+
elements: (parent, prop, elements) => {
|
|
26
|
+
for (const elemName in elements) {
|
|
27
|
+
const elem = elements[elemName];
|
|
28
|
+
// (140) Generate the ON-condition for managed associations
|
|
29
|
+
if (isManagedAssociation(elem))
|
|
30
|
+
transformManagedAssociation(elem, elemName);
|
|
31
|
+
}
|
|
32
|
+
}, /* only for views and entities */
|
|
33
|
+
}, [], { skipIgnore: false, allowArtifact: artifact => (artifact.kind === 'entity') });
|
|
34
|
+
|
|
35
|
+
return csn;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Create the foreign key elements for a managed association and build the on-condition
|
|
39
|
+
*
|
|
40
|
+
* @param {Object} elem The association to process
|
|
41
|
+
* @param {string} elemName
|
|
42
|
+
* @returns {void}
|
|
43
|
+
*/
|
|
44
|
+
function transformManagedAssociation(elem, elemName) {
|
|
45
|
+
// No need to run over this - we already did, possibly because it was referenced in the ON-Condition
|
|
46
|
+
// of another association - see a few lines lower
|
|
47
|
+
if (alreadyHandled.has(elem))
|
|
48
|
+
return;
|
|
49
|
+
// Assemble an ON-condition with the foreign keys created in earlier steps
|
|
50
|
+
const onCondParts = [];
|
|
51
|
+
let joinWithAnd = false;
|
|
52
|
+
if (elem.keys.length === 0) { // TODO: really kill instead of _ignore?
|
|
53
|
+
elem._ignore = true;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
for (const foreignKey of elem.keys) {
|
|
57
|
+
// Assemble left hand side of 'assoc.key = fkey'
|
|
58
|
+
const assocKeyArg = {
|
|
59
|
+
ref: [
|
|
60
|
+
elemName,
|
|
61
|
+
].concat(foreignKey.ref),
|
|
62
|
+
};
|
|
63
|
+
const fkName = `${elemName}${pathDelimiter}${foreignKey.as}`;
|
|
64
|
+
const fKeyArg = {
|
|
65
|
+
ref: [
|
|
66
|
+
fkName,
|
|
67
|
+
],
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
if (joinWithAnd) { // more than one FK
|
|
71
|
+
onCondParts.push('and');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
onCondParts.push(
|
|
75
|
+
assocKeyArg
|
|
76
|
+
);
|
|
77
|
+
onCondParts.push('=');
|
|
78
|
+
onCondParts.push(fKeyArg);
|
|
79
|
+
|
|
80
|
+
if (!joinWithAnd)
|
|
81
|
+
joinWithAnd = true;
|
|
82
|
+
}
|
|
83
|
+
elem.on = onCondParts;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// If the managed association has a 'key' property => remove it as unmanaged assocs cannot be keys
|
|
87
|
+
// TODO: Are there other modifiers (like 'key') that are valid for managed, but not valid for unmanaged assocs?
|
|
88
|
+
if (elem.key)
|
|
89
|
+
delete elem.key;
|
|
90
|
+
|
|
91
|
+
// If the managed association has a 'not null' property => remove it
|
|
92
|
+
if (elem.notNull)
|
|
93
|
+
delete elem.notNull;
|
|
94
|
+
|
|
95
|
+
// The association is now unmanaged, i.e. actually it should no longer have foreign keys
|
|
96
|
+
// at all. But the processing of backlink associations below expects to have them, so
|
|
97
|
+
// we don't delete them
|
|
98
|
+
// TODO: maybe make non-enumerable, so we become recompilable in the future?
|
|
99
|
+
|
|
100
|
+
// Remember that we already processed this
|
|
101
|
+
alreadyHandled.set(elem, true);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @param {CSN.Model} csn
|
|
107
|
+
* @param {string} pathDelimiter
|
|
108
|
+
* @returns {(artifact: CSN.Artifact, artifactName: string) => void} Callback for forEachDefinition
|
|
109
|
+
*/
|
|
110
|
+
function getManagedAssocStepsInOnConditionFinalizer(csn, pathDelimiter) {
|
|
111
|
+
const {
|
|
112
|
+
inspectRef,
|
|
113
|
+
} = getUtils(csn);
|
|
114
|
+
|
|
115
|
+
return handleManagedAssocStepsInOnCondition;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Loop over all elements and for all unmanaged associations translate
|
|
119
|
+
* <assoc base>.<managed assoc>.<fk> to <assoc base>.<managed assoc>_<fk>
|
|
120
|
+
*
|
|
121
|
+
* Or in other words: Allow using the foreign keys of managed associations in on-conditions
|
|
122
|
+
*
|
|
123
|
+
* @param {CSN.Artifact} artifact Artifact to check
|
|
124
|
+
* @param {string} artifactName Name of the artifact
|
|
125
|
+
*/
|
|
126
|
+
function handleManagedAssocStepsInOnCondition(artifact, artifactName) {
|
|
127
|
+
for (const elemName in artifact.elements) {
|
|
128
|
+
const elem = artifact.elements[elemName];
|
|
129
|
+
// The association is an unmanaged on
|
|
130
|
+
if (!elem.keys && elem.target && elem.on) {
|
|
131
|
+
applyTransformationsOnNonDictionary(elem, 'on', {
|
|
132
|
+
ref: (refOwner, prop, ref, path) => {
|
|
133
|
+
// [<assoc base>.]<managed assoc>.<field>
|
|
134
|
+
if (ref.length > 1) {
|
|
135
|
+
const { links } = inspectRef(path);
|
|
136
|
+
if (links) {
|
|
137
|
+
// eslint-disable-next-line for-direction
|
|
138
|
+
for (let i = links.length - 1; i >= 0; i--) {
|
|
139
|
+
const link = links[i];
|
|
140
|
+
// We found the latest managed assoc path step
|
|
141
|
+
if (link.art && link.art.target && link.art.keys &&
|
|
142
|
+
// Doesn't work when ref-target (filter condition) or similar is used
|
|
143
|
+
!ref.slice(i).some(refElement => typeof refElement !== 'string')) {
|
|
144
|
+
// We join the managed assoc with everything following it
|
|
145
|
+
const sourceElementName = ref.slice(i).join(pathDelimiter);
|
|
146
|
+
const source = findSource(links, i - 1) || artifact;
|
|
147
|
+
// allow specifying managed assoc on the source side
|
|
148
|
+
const fks = link.art.keys.filter(fk => ref[i] + pathDelimiter + fk.ref[0] === sourceElementName);
|
|
149
|
+
if (fks && fks.length >= 1) {
|
|
150
|
+
const fk = fks[0];
|
|
151
|
+
const managedAssocStepName = refOwner.ref[i];
|
|
152
|
+
const fkName = `${managedAssocStepName}${pathDelimiter}${fk.as}`;
|
|
153
|
+
if (source && source.elements[fkName])
|
|
154
|
+
refOwner.ref = [ ...ref.slice(0, i), fkName ];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
}, {}, [ 'definitions', artifactName, 'elements', elemName ]);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Find out where the managed association is
|
|
167
|
+
*
|
|
168
|
+
* @param {Array} links
|
|
169
|
+
* @param {number} startIndex
|
|
170
|
+
* @returns {Object| undefined} CSN definition of the source of the managed association
|
|
171
|
+
*/
|
|
172
|
+
function findSource(links, startIndex) {
|
|
173
|
+
for (let i = startIndex; i >= 0; i--) {
|
|
174
|
+
const link = links[i];
|
|
175
|
+
// We found the latest assoc step - now check where that points to
|
|
176
|
+
if (link.art && link.art.target)
|
|
177
|
+
return csn.definitions[link.art.target];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
module.exports = {
|
|
185
|
+
attachOnConditions,
|
|
186
|
+
getManagedAssocStepsInOnConditionFinalizer,
|
|
187
|
+
};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
forEachGeneric, forEachMemberRecursively, hasAnnotationValue, isPersistedOnDatabase,
|
|
5
|
+
getUtils,
|
|
6
|
+
} = require('../../model/csnUtils');
|
|
7
|
+
const transformUtils = require('../transformUtilsNew');
|
|
8
|
+
|
|
9
|
+
const exists = '@cds.persistence.exists';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Return a callback function for forEachDefinition that marks artifacts that are abstract or @cds.persistence.exists/skip
|
|
13
|
+
* with _ignore.
|
|
14
|
+
*
|
|
15
|
+
* @returns {(artifact: CSN.Artifact, artifactName) => void} Callback function for forEachDefinition
|
|
16
|
+
*/
|
|
17
|
+
function getAnnoProcessor() {
|
|
18
|
+
return handleCdsPersistence;
|
|
19
|
+
/**
|
|
20
|
+
* @param {CSN.Artifact} artifact
|
|
21
|
+
*/
|
|
22
|
+
function handleCdsPersistence(artifact) {
|
|
23
|
+
const ignoreArtifact = (artifact.kind === 'entity') &&
|
|
24
|
+
(artifact.abstract ||
|
|
25
|
+
hasAnnotationValue(artifact, '@cds.persistence.skip') ||
|
|
26
|
+
hasAnnotationValue(artifact, exists));
|
|
27
|
+
if (ignoreArtifact)
|
|
28
|
+
artifact._ignore = true;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Return a callback function for forEachDefinition that marks associations with _ignore
|
|
34
|
+
* if their target does not reach the database, i.e. marked with @cds.persistence.skip or is abstract
|
|
35
|
+
*
|
|
36
|
+
* @param {CSN.Model} csn
|
|
37
|
+
* @param {CSN.Options} options
|
|
38
|
+
* @param {object} messageFunctions
|
|
39
|
+
* @param {Function} messageFunctions.info
|
|
40
|
+
* @returns {(artifact: CSN.Artifact, artifactName: string, prop: string, path: CSN.Path) => void} Callback function for forEachDefinition
|
|
41
|
+
*/
|
|
42
|
+
function getAssocToSkippedIgnorer(csn, options, messageFunctions) {
|
|
43
|
+
const { info } = messageFunctions;
|
|
44
|
+
const doA2J = !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds');
|
|
45
|
+
|
|
46
|
+
const { isAssocOrComposition } = getUtils(csn);
|
|
47
|
+
|
|
48
|
+
return ignoreAssociationToSkippedTarget;
|
|
49
|
+
/**
|
|
50
|
+
* Associations that target a @cds.persistence.skip artifact must be removed
|
|
51
|
+
* from the persistence model
|
|
52
|
+
*
|
|
53
|
+
* @param {CSN.Artifact} artifact
|
|
54
|
+
* @param {string} artifactName
|
|
55
|
+
* @param {string} prop
|
|
56
|
+
* @param {CSN.Path} path
|
|
57
|
+
*/
|
|
58
|
+
function ignoreAssociationToSkippedTarget(artifact, artifactName, prop, path) {
|
|
59
|
+
if (isPersistedOnDatabase(artifact)) {
|
|
60
|
+
// TODO: structure in CSN is artifact.query.[SELECT/SET].mixin
|
|
61
|
+
if (artifact.query) {
|
|
62
|
+
// If we do A2J, we don't need to check the mixin. Either it is used -> a join
|
|
63
|
+
// or published -> handled via elements/members. Unused mixins are removed anyway.
|
|
64
|
+
if (!doA2J && artifact.query.SELECT && artifact.query.SELECT.mixin)
|
|
65
|
+
forEachGeneric(artifact.query.SELECT, 'mixin', ignore, path.concat([ 'query', 'SELECT' ]));
|
|
66
|
+
|
|
67
|
+
else if (!doA2J && artifact.query.SET && artifact.query.SET.mixin)
|
|
68
|
+
forEachGeneric(artifact.query.SET, 'mixin', ignore, path.concat([ 'query', 'SET' ]));
|
|
69
|
+
}
|
|
70
|
+
forEachMemberRecursively(artifact, ignore, [ 'definitions', artifactName ]);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Mark the given member with _ignore if it is an association/composition and it's target is unreachable.
|
|
76
|
+
*
|
|
77
|
+
* @param {CSN.Element} member
|
|
78
|
+
* @param {string} memberName
|
|
79
|
+
* @param {string} prop
|
|
80
|
+
* @param {CSN.Path} path
|
|
81
|
+
* @todo Why do we check for @cds.persistence.exists here if the parent-function only calls this for skip/abstract?
|
|
82
|
+
*/
|
|
83
|
+
function ignore(member, memberName, prop, path) {
|
|
84
|
+
if (options.sqlDialect === 'hana' && !member._ignore && member.target && isAssocOrComposition(member.type) && isUnreachableAssociationTarget(csn.definitions[member.target])) {
|
|
85
|
+
const targetAnnotation = hasAnnotationValue(csn.definitions[member.target], exists) ? exists : '@cds.persistence.skip';
|
|
86
|
+
info(null, path,
|
|
87
|
+
{ target: member.target, anno: targetAnnotation },
|
|
88
|
+
'Association has been removed as it\'s target $(TARGET) is annotated with $(ANNO)');
|
|
89
|
+
member._ignore = true;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Check whether the given artifact is an unreachable association target because it will not "realy" hit the database:
|
|
95
|
+
* - @cds.persistence.skip/exists
|
|
96
|
+
* - abstract
|
|
97
|
+
*
|
|
98
|
+
* @param {CSN.Artifact} art
|
|
99
|
+
* @returns {boolean}
|
|
100
|
+
*/
|
|
101
|
+
function isUnreachableAssociationTarget(art) {
|
|
102
|
+
return !isPersistedOnDatabase(art) || hasAnnotationValue(art, exists);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Return a callback function for forEachDefinition that handles artifacts marked with @cds.persistence.table.
|
|
108
|
+
* If a .query artifact has this annotation, the .query will be deleted and it will be treated like a table.
|
|
109
|
+
*
|
|
110
|
+
* @param {CSN.Model} csn
|
|
111
|
+
* @param {CSN.Options} options
|
|
112
|
+
* @param {object} messageFunctions
|
|
113
|
+
* @param {Function} messageFunctions.error
|
|
114
|
+
* @returns {(artifact: CSN.Artifact, artifactName) => void} Callback function for forEachDefinition
|
|
115
|
+
*/
|
|
116
|
+
function getPersistenceTableProcessor(csn, options, messageFunctions ) {
|
|
117
|
+
const { error } = messageFunctions;
|
|
118
|
+
const {
|
|
119
|
+
recurseElements,
|
|
120
|
+
} = transformUtils.getTransformers(csn, options, '_');
|
|
121
|
+
|
|
122
|
+
return handleQueryish;
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @param {CSN.Artifact} artifact
|
|
127
|
+
* @param {string} artifactName
|
|
128
|
+
*/
|
|
129
|
+
function handleQueryish(artifact, artifactName) {
|
|
130
|
+
const stripQueryish = artifact.query && hasAnnotationValue(artifact, '@cds.persistence.table');
|
|
131
|
+
|
|
132
|
+
if (stripQueryish) {
|
|
133
|
+
artifact.kind = 'entity';
|
|
134
|
+
delete artifact.query;
|
|
135
|
+
|
|
136
|
+
recurseElements(artifact, [ 'definitions', artifactName ], (member, path) => {
|
|
137
|
+
// All elements must have a type for this to work
|
|
138
|
+
if (!member._ignore && !member.kind && !member.type)
|
|
139
|
+
error(null, path, 'Expecting element to have a type if view is annotated with “@cds.persistence.table“');
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
module.exports = {
|
|
147
|
+
getAnnoProcessor,
|
|
148
|
+
getAssocToSkippedIgnorer,
|
|
149
|
+
getPersistenceTableProcessor,
|
|
150
|
+
};
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { forEachDefinition } = require('../../base/model');
|
|
4
|
-
const {
|
|
4
|
+
const { applyTransformations, hasAnnotationValue, getResultingName } = require('../../model/csnUtils');
|
|
5
5
|
const { csnRefs } = require('../../model/csnRefs');
|
|
6
|
+
const { forEach, forEachKey } = require('../../utils/objectUtils');
|
|
6
7
|
|
|
7
8
|
const COMPOSITION = 'cds.Composition';
|
|
8
9
|
const ASSOCIATION = 'cds.Association';
|
|
@@ -28,34 +29,36 @@ function createReferentialConstraints(csn, options) {
|
|
|
28
29
|
// compositions must be processed first, as the <up_> links for them must result in `ON DELETE CASCADE`
|
|
29
30
|
const compositions = [];
|
|
30
31
|
const associations = [];
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
32
|
+
applyTransformations(csn, {
|
|
33
|
+
elements: (parent, prop, elements, path) => {
|
|
34
|
+
// Step I: iterate compositions, enrich dependent keys for <up_> association in target entity of composition
|
|
35
|
+
for (const elementName in elements) {
|
|
36
|
+
const element = elements[elementName];
|
|
37
|
+
const ePath = path.concat([ 'elements', elementName ]); // Save a copy in this scope for the late callback
|
|
38
|
+
if (element.type === COMPOSITION && element.$selfOnCondition) {
|
|
39
|
+
compositions.push({
|
|
40
|
+
fn: () => {
|
|
41
|
+
foreignKeyConstraintForUpLinkOfComposition(element, parent, ePath);
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
44
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Step II: iterate associations, enrich dependent keys (in entity containing the association)
|
|
48
|
+
for (const elementName in elements) {
|
|
49
|
+
const element = elements[elementName];
|
|
50
|
+
const ePath = path.concat([ 'elements', elementName ]); // Save a copy in this scope for the late callback
|
|
51
|
+
if (element.keys && isToOne(element) && element.type === ASSOCIATION || element.type === COMPOSITION && treatCompositionLikeAssociation(element)) {
|
|
52
|
+
associations.push({
|
|
53
|
+
fn: () => {
|
|
54
|
+
foreignKeyConstraintForAssociation(element, ePath );
|
|
55
|
+
},
|
|
56
|
+
});
|
|
55
57
|
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
});
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
}, [], { skipIgnore: false, skipArtifact: a => a.query || a.kind !== 'entity' });
|
|
61
|
+
|
|
59
62
|
// create constraints on foreign keys
|
|
60
63
|
// always process unmanaged first, up_ links must be flagged
|
|
61
64
|
// before they are processed
|
|
@@ -215,8 +218,7 @@ function createReferentialConstraints(csn, options) {
|
|
|
215
218
|
* Skip referential constraint if the parent table (association target, or artifact where composition is defined)
|
|
216
219
|
* of the relation is:
|
|
217
220
|
* - a query
|
|
218
|
-
* -
|
|
219
|
-
* - TODO: Revisit -- annotated with '@cds.persistence.exists:true'
|
|
221
|
+
* - annotated with '@cds.persistence.skip:true'
|
|
220
222
|
*
|
|
221
223
|
* The following decision table reflects the current implementation:
|
|
222
224
|
*
|
|
@@ -299,13 +301,15 @@ function createReferentialConstraints(csn, options) {
|
|
|
299
301
|
|
|
300
302
|
return true;
|
|
301
303
|
}
|
|
304
|
+
const runtimeChecks = options.assertIntegrityType && options.assertIntegrityType.toUpperCase() === RT;
|
|
305
|
+
const compilerChecks = options.assertIntegrityType && options.assertIntegrityType.toUpperCase() === DB;
|
|
302
306
|
|
|
303
307
|
if ((!options.assertIntegrity || options.assertIntegrity === true || options.assertIntegrity === 'true') &&
|
|
304
|
-
(!options.assertIntegrityType ||
|
|
308
|
+
(!options.assertIntegrityType || runtimeChecks))
|
|
305
309
|
return assertForIntegrityTypeRT();
|
|
306
310
|
|
|
307
311
|
if ((!options.assertIntegrity || options.assertIntegrity === true || options.assertIntegrity === 'true') &&
|
|
308
|
-
|
|
312
|
+
compilerChecks)
|
|
309
313
|
return assertForIntegrityTypeDB();
|
|
310
314
|
|
|
311
315
|
if ((options.assertIntegrity === 'individual'))
|
|
@@ -313,7 +317,7 @@ function createReferentialConstraints(csn, options) {
|
|
|
313
317
|
|
|
314
318
|
// The default for the assertIntegrityType is 'RT', no constraints in that case
|
|
315
319
|
if ((!options.assertIntegrity || options.assertIntegrity === true) &&
|
|
316
|
-
(!options.assertIntegrityType ||
|
|
320
|
+
(!options.assertIntegrityType || runtimeChecks))
|
|
317
321
|
return true;
|
|
318
322
|
|
|
319
323
|
if (!element.keys || !isToOne(element))
|
|
@@ -385,7 +389,7 @@ function createReferentialConstraints(csn, options) {
|
|
|
385
389
|
* @returns {boolean}
|
|
386
390
|
*/
|
|
387
391
|
function isAssertIntegrityAnnotationSetTo(value) {
|
|
388
|
-
return hasAnnotationValue(element, '@assert.integrity', value);
|
|
392
|
+
return hasAnnotationValue(element, '@assert.integrity', value, true);
|
|
389
393
|
}
|
|
390
394
|
|
|
391
395
|
/**
|
|
@@ -489,18 +493,20 @@ function createReferentialConstraints(csn, options) {
|
|
|
489
493
|
const dependentKey = [ elementName ];
|
|
490
494
|
const onDeleteRules = new Set();
|
|
491
495
|
onDeleteRules.add($foreignKeyConstraint.onDelete);
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
496
|
+
forEach(artifact.elements, (foreignKeyName, foreignKey) => {
|
|
497
|
+
// find all other `$foreignKeyConstraint`s with same `$sourceAssociation` and same `parentTable`
|
|
498
|
+
const matchingForeignKeyFound = foreignKey.$foreignKeyConstraint &&
|
|
499
|
+
foreignKey.$foreignKeyConstraint.sourceAssociation === $foreignKeyConstraint.sourceAssociation &&
|
|
500
|
+
foreignKey.$foreignKeyConstraint.parentTable === $foreignKeyConstraint.parentTable;
|
|
501
|
+
if (!matchingForeignKeyFound)
|
|
502
|
+
return;
|
|
503
|
+
|
|
504
|
+
const $foreignKeyConstraintCopy = Object.assign({}, foreignKey.$foreignKeyConstraint);
|
|
505
|
+
delete foreignKey.$foreignKeyConstraint;
|
|
506
|
+
parentKey.push($foreignKeyConstraintCopy.parentKey);
|
|
507
|
+
dependentKey.push(foreignKeyName);
|
|
508
|
+
onDeleteRules.add($foreignKeyConstraintCopy.onDelete);
|
|
509
|
+
});
|
|
504
510
|
// onDelete Rule is the "weakest" rule applicable. Precedence: RESTRICT > SET NULL > CASCADE
|
|
505
511
|
const onDelete = onDeleteRules.has('RESTRICT') ? 'RESTRICT' : 'CASCADE';
|
|
506
512
|
let onDeleteRemark = null;
|
|
@@ -508,14 +514,14 @@ function createReferentialConstraints(csn, options) {
|
|
|
508
514
|
if (options.testMode && onDelete === 'CASCADE')
|
|
509
515
|
onDeleteRemark = `Up_ link for Composition "${$foreignKeyConstraint.upLinkFor}" implies existential dependency`;
|
|
510
516
|
referentialConstraints[`${getResultingName(csn, 'quoted', artifactName)}_${$foreignKeyConstraint.sourceAssociation}`] = {
|
|
511
|
-
identifier
|
|
517
|
+
// constraint identifier start with `c__` to avoid name clashes
|
|
518
|
+
identifier: `c__${getResultingName(csn, options.forHana.names, artifactName)}_${$foreignKeyConstraint.sourceAssociation}`,
|
|
512
519
|
foreignKey: dependentKey,
|
|
513
520
|
parentKey,
|
|
514
521
|
dependentTable: artifactName,
|
|
515
522
|
parentTable,
|
|
516
523
|
onDelete,
|
|
517
524
|
onDeleteRemark, // explain why this particular rule is chosen
|
|
518
|
-
// TODO: do we want to switch off validation / enforcement via annotation on association?
|
|
519
525
|
validated: $foreignKeyConstraint.validated,
|
|
520
526
|
enforced: $foreignKeyConstraint.enforced,
|
|
521
527
|
};
|
|
@@ -543,15 +549,14 @@ function assertConstraintIdentifierUniqueness(artifact, artifactName, path, erro
|
|
|
543
549
|
if (!(artifact.$tableConstraints && artifact.$tableConstraints.referential && artifact.$tableConstraints.unique))
|
|
544
550
|
return;
|
|
545
551
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
.
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
});
|
|
552
|
+
forEachKey(artifact.$tableConstraints.unique, (uniqueConstraintKey) => {
|
|
553
|
+
const uniqueConstraintIdentifier = `${artifactName}_${uniqueConstraintKey}`; // final unique constraint identifier will be generated in renderer likewise
|
|
554
|
+
if (artifact.$tableConstraints.referential[uniqueConstraintIdentifier]) {
|
|
555
|
+
error(null, path,
|
|
556
|
+
{ name: uniqueConstraintIdentifier, art: artifactName },
|
|
557
|
+
'Duplicate constraint name $(NAME) in artifact $(ART)');
|
|
558
|
+
}
|
|
559
|
+
});
|
|
555
560
|
}
|
|
556
561
|
|
|
557
562
|
module.exports = { createReferentialConstraints, assertConstraintIdentifierUniqueness };
|