@sap/cds-compiler 4.7.6 → 4.9.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 +63 -3
- package/bin/cds_remove_invalid_whitespace.js +135 -0
- package/bin/cds_update_annotations.js +180 -0
- package/bin/cds_update_identifiers.js +3 -4
- package/bin/cdsc.js +28 -1
- package/bin/cdshi.js +13 -3
- package/doc/CHANGELOG_BETA.md +24 -1
- package/lib/api/main.js +119 -46
- package/lib/api/options.js +51 -0
- package/lib/api/validate.js +1 -5
- package/lib/base/builtins.js +116 -0
- package/lib/base/keywords.js +5 -1
- package/lib/base/location.js +91 -14
- package/lib/base/message-registry.js +76 -46
- package/lib/base/messages.js +121 -35
- package/lib/base/model.js +4 -7
- package/lib/checks/actionsFunctions.js +3 -3
- package/lib/checks/annotationsOData.js +3 -0
- package/lib/checks/defaultValues.js +5 -2
- package/lib/checks/elements.js +2 -1
- package/lib/checks/enricher.js +2 -2
- package/lib/checks/queryNoDbArtifacts.js +5 -3
- package/lib/checks/utils.js +1 -1
- package/lib/checks/validator.js +8 -56
- package/lib/compiler/assert-consistency.js +11 -7
- package/lib/compiler/builtins.js +0 -74
- package/lib/compiler/checks.js +105 -29
- package/lib/compiler/define.js +37 -25
- package/lib/compiler/extend.js +35 -12
- package/lib/compiler/index.js +9 -10
- package/lib/compiler/lsp-api.js +5 -0
- package/lib/compiler/populate.js +13 -5
- package/lib/compiler/propagator.js +24 -18
- package/lib/compiler/resolve.js +47 -45
- package/lib/compiler/shared.js +61 -21
- package/lib/compiler/tweak-assocs.js +15 -90
- package/lib/compiler/utils.js +3 -3
- package/lib/compiler/xpr-rewrite.js +689 -0
- package/lib/compiler/{classes.js → xsn-model.js} +0 -16
- package/lib/edm/annotations/edmJson.js +7 -5
- package/lib/edm/annotations/genericTranslation.js +149 -71
- package/lib/edm/csn2edm.js +25 -9
- package/lib/edm/edm.js +7 -7
- package/lib/edm/edmInboundChecks.js +57 -5
- package/lib/edm/edmPreprocessor.js +54 -25
- package/lib/edm/edmUtils.js +3 -16
- package/lib/gen/Dictionary.json +138 -14
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +2085 -1989
- package/lib/json/csnVersion.js +7 -4
- package/lib/json/from-csn.js +21 -11
- package/lib/json/to-csn.js +8 -4
- package/lib/language/antlrParser.js +1 -1
- package/lib/language/genericAntlrParser.js +23 -16
- package/lib/language/multiLineStringParser.js +2 -2
- package/lib/language/textUtils.js +1 -1
- package/lib/main.d.ts +90 -14
- package/lib/main.js +9 -1
- package/lib/model/cloneCsn.js +21 -9
- package/lib/model/csnRefs.js +153 -42
- package/lib/model/csnUtils.js +14 -11
- package/lib/model/enrichCsn.js +4 -2
- package/lib/model/revealInternalProperties.js +2 -1
- package/lib/model/sortViews.js +14 -6
- package/lib/modelCompare/compare.js +135 -122
- package/lib/optionProcessor.js +49 -2
- package/lib/render/DuplicateChecker.js +6 -6
- package/lib/render/manageConstraints.js +1 -0
- package/lib/render/toCdl.js +6 -3
- package/lib/render/toHdbcds.js +4 -48
- package/lib/render/toSql.js +6 -3
- package/lib/transform/addTenantFields.js +58 -35
- package/lib/transform/db/applyTransformations.js +34 -1
- package/lib/transform/db/constraints.js +1 -1
- package/lib/transform/db/expansion.js +11 -3
- package/lib/transform/db/flattening.js +71 -46
- package/lib/transform/db/groupByOrderBy.js +2 -2
- package/lib/transform/db/temporal.js +6 -3
- package/lib/transform/db/transformExists.js +2 -2
- package/lib/transform/db/views.js +1 -4
- package/lib/transform/effective/annotations.js +194 -0
- package/lib/transform/effective/main.js +11 -10
- package/lib/transform/effective/misc.js +45 -14
- package/lib/transform/effective/types.js +4 -3
- package/lib/transform/forOdata.js +29 -12
- package/lib/transform/forRelationalDB.js +104 -113
- package/lib/transform/localized.js +7 -6
- package/lib/transform/odata/flattening.js +228 -107
- package/lib/transform/odata/toFinalBaseType.js +10 -26
- package/lib/transform/odata/typesExposure.js +41 -25
- package/lib/transform/parseExpr.js +4 -7
- package/lib/transform/transformUtils.js +50 -43
- package/lib/transform/translateAssocsToJoins.js +48 -48
- package/lib/transform/universalCsn/coreComputed.js +2 -1
- package/lib/transform/universalCsn/universalCsnEnricher.js +12 -16
- package/package.json +2 -2
- package/share/messages/README.md +4 -0
- package/share/messages/anno-duplicate-unrelated-layer.md +1 -1
- package/share/messages/anno-missing-rewrite.md +45 -0
- package/share/messages/check-proper-type-of.md +1 -1
- package/share/messages/def-duplicate-autoexposed.md +1 -1
- package/share/messages/extend-repeated-intralayer.md +3 -16
- package/share/messages/extend-unrelated-layer.md +1 -1
- package/share/messages/message-explanations.json +2 -0
- package/share/messages/redirected-to-ambiguous.md +1 -1
- package/share/messages/redirected-to-complex.md +1 -1
- package/share/messages/redirected-to-unrelated.md +1 -1
- package/share/messages/rewrite-not-supported.md +1 -1
- package/share/messages/syntax-expecting-unsigned-int.md +2 -2
- package/share/messages/type-missing-enum-value.md +59 -0
- package/share/messages/wildcard-excluding-one.md +1 -1
- package/bin/.eslintrc.json +0 -17
- package/lib/api/.eslintrc.json +0 -37
- package/lib/checks/.eslintrc.json +0 -31
- package/lib/compiler/.eslintrc.json +0 -8
- package/lib/edm/.eslintrc.json +0 -46
- package/lib/inspect/.eslintrc.json +0 -4
- package/lib/json/.eslintrc.json +0 -4
- package/lib/language/.eslintrc.json +0 -4
- package/lib/model/.eslintrc.json +0 -13
- package/lib/modelCompare/utils/.eslintrc.json +0 -22
- package/lib/render/.eslintrc.json +0 -22
- package/lib/transform/.eslintrc.json +0 -13
- package/lib/transform/db/.eslintrc.json +0 -41
- package/lib/transform/draft/.eslintrc.json +0 -4
- package/lib/transform/effective/.eslintrc.json +0 -4
- package/lib/transform/universalCsn/.eslintrc.json +0 -37
- package/lib/utils/.eslintrc.json +0 -7
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { makeMessageFunction } = require('../base/messages');
|
|
4
|
+
const { setProp } = require('../base/model');
|
|
4
5
|
const {
|
|
5
6
|
forEachDefinition,
|
|
6
7
|
forEachMember,
|
|
7
8
|
isPersistedAsTable,
|
|
8
|
-
isPersistedAsView
|
|
9
|
+
isPersistedAsView,
|
|
9
10
|
} = require('../model/csnUtils');
|
|
10
11
|
const { forEachKey, forEach } = require('../utils/objectUtils');
|
|
11
12
|
|
|
12
13
|
// used to mark a view as changed so we know to drop-create it
|
|
13
14
|
const isChanged = Symbol('Marks a view as changed');
|
|
14
15
|
|
|
16
|
+
const relevantProperties = {
|
|
17
|
+
doc: true,
|
|
18
|
+
'@sql.prepend': true,
|
|
19
|
+
'@sql.append': true,
|
|
20
|
+
};
|
|
21
|
+
|
|
15
22
|
/**
|
|
16
23
|
* Compares two models, in HANA-transformed CSN format, to each other.
|
|
17
24
|
*
|
|
@@ -23,7 +30,7 @@ const isChanged = Symbol('Marks a view as changed');
|
|
|
23
30
|
*/
|
|
24
31
|
function compareModels(beforeModel, afterModel, options) {
|
|
25
32
|
// @ts-ignore
|
|
26
|
-
if(!(options && options.testMode)) // no $version with testMode
|
|
33
|
+
if (!(options && options.testMode)) // no $version with testMode
|
|
27
34
|
validateCsnVersions(beforeModel, afterModel, options);
|
|
28
35
|
|
|
29
36
|
const returnObj = Object.create(null);
|
|
@@ -45,8 +52,8 @@ function compareModels(beforeModel, afterModel, options) {
|
|
|
45
52
|
function validateCsnVersions(beforeModel, afterModel, options) {
|
|
46
53
|
const beforeVersion = beforeModel.$version;
|
|
47
54
|
const afterVersion = afterModel.$version;
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
const beforeVersionParts = beforeVersion && beforeVersion.split('.');
|
|
56
|
+
const afterVersionParts = afterVersion && afterVersion.split('.');
|
|
50
57
|
|
|
51
58
|
if (!beforeVersionParts || beforeVersionParts.length < 2) {
|
|
52
59
|
const { error, throwWithAnyError } = makeMessageFunction(beforeModel, options, 'modelCompare');
|
|
@@ -60,53 +67,85 @@ function validateCsnVersions(beforeModel, afterModel, options) {
|
|
|
60
67
|
}
|
|
61
68
|
if (beforeVersionParts[0] > afterVersionParts[0] && !(options && options.allowCsnDowngrade)) {
|
|
62
69
|
const { error, throwWithAnyError } = makeMessageFunction(afterModel, options, 'modelCompare');
|
|
63
|
-
|
|
64
|
-
|
|
70
|
+
// eslint-disable-next-line global-require
|
|
71
|
+
const { version } = require('../../package.json');
|
|
72
|
+
error(null, null, { value: afterVersion, othervalue: beforeVersion, version },
|
|
73
|
+
'Incompatible CSN versions: $(VALUE) is a major downgrade from $(OTHERVALUE). Is @sap/cds-compiler version $(VERSION) outdated?');
|
|
65
74
|
throwWithAnyError();
|
|
66
75
|
}
|
|
67
76
|
}
|
|
68
77
|
|
|
69
78
|
/**
|
|
70
79
|
* Calculate extensions, migrations and unchangedConstraints
|
|
71
|
-
*
|
|
72
|
-
* @param {CSN.Model} beforeModel
|
|
73
|
-
* @param {CSN.Options} options
|
|
74
|
-
* @param {object} returnObj
|
|
80
|
+
*
|
|
81
|
+
* @param {CSN.Model} beforeModel
|
|
82
|
+
* @param {CSN.Options} options
|
|
83
|
+
* @param {object} returnObj
|
|
75
84
|
* @returns {function}
|
|
76
85
|
*/
|
|
77
|
-
function getExtensionAndMigrations(beforeModel, options, {
|
|
86
|
+
function getExtensionAndMigrations(beforeModel, options, {
|
|
87
|
+
extensions, migrations, unchangedConstraints, changedPrimaryKeys,
|
|
88
|
+
}) {
|
|
78
89
|
return function compareArtifacts(artifact, name) {
|
|
79
90
|
let hasPrimaryKeyChange = false;
|
|
91
|
+
const otherArtifact = beforeModel.definitions[name];
|
|
92
|
+
const isPersisted = isPersistedAsTable(artifact);
|
|
93
|
+
const isPersistedOther = otherArtifact && isPersistedAsTable(otherArtifact);
|
|
94
|
+
|
|
95
|
+
// to make it easier to know which views to drop-create
|
|
96
|
+
if (isPersistedAsView(artifact) && isPersistedAsView(otherArtifact)) {
|
|
97
|
+
// TODO: Check only on artifact.query/projection BUT: Need to manually check for sql-snippets then!
|
|
98
|
+
artifact[isChanged] = JSON.stringify(artifact) !== JSON.stringify(otherArtifact);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Looking for added entities and added/deleted/changed elements.
|
|
102
|
+
// Parameters: `artifact` from afterModel and `otherArtifact` from beforeModel.
|
|
103
|
+
|
|
104
|
+
if (!isPersisted) // Artifact not persisted in afterModel.
|
|
105
|
+
return;
|
|
106
|
+
|
|
107
|
+
if (!isPersistedOther) {
|
|
108
|
+
extensions[name] = artifact;
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Artifact changed?
|
|
113
|
+
|
|
114
|
+
addElements();
|
|
115
|
+
changePropsOrRemoveOrChangeElements();
|
|
116
|
+
if (hasPrimaryKeyChange)
|
|
117
|
+
changedPrimaryKeys.push(name);
|
|
80
118
|
|
|
81
119
|
function addElements() {
|
|
82
120
|
const elements = {};
|
|
83
121
|
const keysNow = [];
|
|
84
122
|
forEachMember(artifact, (element, eName) => {
|
|
85
123
|
getElementComparator(otherArtifact, elements)(element, eName);
|
|
86
|
-
if(element.key)
|
|
124
|
+
if (element.key)
|
|
87
125
|
keysNow.push(eName);
|
|
88
126
|
}, [ 'definitions', name ], true, { elementsOnly: true });
|
|
89
127
|
|
|
90
128
|
// Only do this check for to.hdi.migration - the order only "bites" us when doing .hdbmigrationtable as the end-check against the intended
|
|
91
129
|
// create-table will fail. TODO: Does a mismatched order of the primary key hurt us for postgres and others?
|
|
92
|
-
if(!hasPrimaryKeyChange && options.sqlDialect === 'hana' && options.src === 'hdi') {
|
|
130
|
+
if (!hasPrimaryKeyChange && options.sqlDialect === 'hana' && options.src === 'hdi') {
|
|
93
131
|
const keysOther = [];
|
|
94
132
|
forEachMember(otherArtifact, (element, eName) => {
|
|
95
|
-
if(element.key)
|
|
133
|
+
if (element.key)
|
|
96
134
|
keysOther.push(eName);
|
|
97
135
|
}, [ 'definitions', name ], true, { elementsOnly: true });
|
|
98
136
|
|
|
99
|
-
if(keysNow.join(',') !== keysOther.join(','))
|
|
137
|
+
if (keysNow.join(',') !== keysOther.join(','))
|
|
100
138
|
hasPrimaryKeyChange = true;
|
|
101
139
|
}
|
|
102
140
|
|
|
103
141
|
if (Object.keys(elements).length > 0) {
|
|
104
142
|
const added = addedElements(name, elements);
|
|
105
|
-
if(!hasPrimaryKeyChange)
|
|
143
|
+
if (!hasPrimaryKeyChange) {
|
|
106
144
|
forEach(added.elements, (_name, element) => {
|
|
107
|
-
if(element.key && !element.target)
|
|
145
|
+
if (element.key && !element.target)
|
|
108
146
|
hasPrimaryKeyChange = true;
|
|
109
|
-
});
|
|
147
|
+
});
|
|
148
|
+
}
|
|
110
149
|
extensions.push(added);
|
|
111
150
|
}
|
|
112
151
|
}
|
|
@@ -118,128 +157,101 @@ function getExtensionAndMigrations(beforeModel, options, { extensions, migration
|
|
|
118
157
|
|
|
119
158
|
const migration = { migrate: name };
|
|
120
159
|
|
|
121
|
-
Object.keys(relevantProperties).forEach(prop => {
|
|
122
|
-
if (artifact[prop] !== otherArtifact[prop])
|
|
160
|
+
Object.keys(relevantProperties).forEach((prop) => {
|
|
161
|
+
if (artifact[prop] !== otherArtifact[prop])
|
|
123
162
|
changedProperties[prop] = changedElement(artifact[prop], otherArtifact[prop] || null);
|
|
124
|
-
}
|
|
125
163
|
});
|
|
126
164
|
|
|
127
165
|
const removedConstraints = {};
|
|
128
166
|
|
|
129
167
|
// HDI (src === 'hdi) does handle table constraints via separate files and therefore no delta handling needed
|
|
130
|
-
if(options.src === 'sql' && (artifact.$tableConstraints
|
|
168
|
+
if (options.src === 'sql' && (artifact.$tableConstraints || otherArtifact.$tableConstraints)) {
|
|
131
169
|
const current = artifact.$tableConstraints || {};
|
|
132
170
|
const old = otherArtifact.$tableConstraints || {};
|
|
133
171
|
let changes = false;
|
|
134
|
-
const constraintTypes = ['unique', 'referential'];
|
|
135
|
-
constraintTypes.forEach(constraintType => {
|
|
172
|
+
const constraintTypes = [ 'unique', 'referential' ];
|
|
173
|
+
constraintTypes.forEach((constraintType) => {
|
|
136
174
|
// We only render/handle referential constraints for specific cases
|
|
137
|
-
if(hasReferentialConstraints(options) || constraintType !== 'referential')
|
|
138
|
-
if(current[constraintType] || old[constraintType]) {
|
|
175
|
+
if (hasReferentialConstraints(options) || constraintType !== 'referential') {
|
|
176
|
+
if (current[constraintType] || old[constraintType]) {
|
|
139
177
|
removedConstraints[constraintType] = Object.create(null);
|
|
140
178
|
|
|
141
179
|
const cnew = current[constraintType] || Object.create(null);
|
|
142
180
|
const cold = old[constraintType] || Object.create(null);
|
|
143
181
|
forEachKey(cnew, (constraintName) => {
|
|
144
|
-
if(cold[constraintName]) {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if(JSON.stringify(cold[constraintName]) !== JSON.stringify(cnew[constraintName])) {
|
|
182
|
+
if (cold[constraintName]) {
|
|
183
|
+
// constraint changed - add it to "removedConstraints" to drop-create it.
|
|
184
|
+
// Quick-and-dirty compare with JSON.stringify - false-positive will just be a drop-create
|
|
185
|
+
if (JSON.stringify(cold[constraintName]) !== JSON.stringify(cnew[constraintName])) {
|
|
148
186
|
changes = true;
|
|
149
187
|
removedConstraints[constraintType][constraintName] = cold[constraintName];
|
|
150
|
-
if(options.constraintsInCreateTable || constraintType !== 'referential') // schedule an "ADD"
|
|
151
|
-
extensions.push(addedConstraint(name, cnew[constraintName], constraintName,
|
|
152
|
-
}
|
|
188
|
+
if (options.constraintsInCreateTable || constraintType !== 'referential') // schedule an "ADD"
|
|
189
|
+
extensions.push(addedConstraint(name, cnew[constraintName], constraintName, constraintType));
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
153
192
|
unchangedConstraints.add(constraintName);
|
|
154
193
|
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
|
|
194
|
+
}
|
|
195
|
+
else if (options.constraintsInCreateTable || constraintType !== 'referential') {
|
|
196
|
+
// Sometimes referential constraints are anyway added via ALTER - no need to add them as explicit extensions then
|
|
197
|
+
extensions.push(addedConstraint(name, cnew[constraintName], constraintName, constraintType));
|
|
158
198
|
}
|
|
159
199
|
});
|
|
160
200
|
|
|
161
201
|
forEachKey(cold, (constraintName) => {
|
|
162
|
-
if(!cnew[constraintName]) {
|
|
202
|
+
if (!cnew[constraintName]) {
|
|
163
203
|
changes = true;
|
|
164
204
|
removedConstraints[constraintType][constraintName] = cold[constraintName];
|
|
165
205
|
}
|
|
166
|
-
})
|
|
206
|
+
});
|
|
167
207
|
}
|
|
208
|
+
}
|
|
168
209
|
});
|
|
169
210
|
|
|
170
|
-
if(changes)
|
|
211
|
+
if (changes)
|
|
171
212
|
migration.removeConstraints = removedConstraints;
|
|
172
213
|
}
|
|
173
214
|
|
|
174
|
-
if (Object.keys(changedProperties).length > 0)
|
|
215
|
+
if (Object.keys(changedProperties).length > 0)
|
|
175
216
|
migration.properties = changedProperties;
|
|
176
|
-
|
|
217
|
+
|
|
177
218
|
|
|
178
219
|
forEachMember(otherArtifact, getElementComparator(artifact, removedElements), [ 'definitions', name ], true, { elementsOnly: true });
|
|
179
220
|
if (Object.keys(removedElements).length > 0) {
|
|
180
221
|
migration.remove = removedElements;
|
|
181
|
-
if(!hasPrimaryKeyChange)
|
|
222
|
+
if (!hasPrimaryKeyChange) {
|
|
182
223
|
forEach(removedElements, (_name, change) => {
|
|
183
|
-
if(change.key && !change.target)
|
|
224
|
+
if (change.key && !change.target)
|
|
184
225
|
hasPrimaryKeyChange = true;
|
|
185
226
|
});
|
|
227
|
+
}
|
|
186
228
|
}
|
|
187
229
|
|
|
188
230
|
forEachMember(artifact, getElementComparator(otherArtifact, null, changedElements), [ 'definitions', name ], true, { elementsOnly: true });
|
|
189
231
|
if (Object.keys(changedElements).length > 0) {
|
|
190
232
|
migration.change = changedElements;
|
|
191
|
-
if(!hasPrimaryKeyChange)
|
|
233
|
+
if (!hasPrimaryKeyChange) {
|
|
192
234
|
forEach(changedElements, (_name, change) => {
|
|
193
|
-
if(!change.onlyDoc && (change.old.key || change.new.key) && !change.new.target && !change.old.target) {
|
|
194
|
-
|
|
195
|
-
if(options.sqlDialect === 'hana' && options.src === 'hdi' || (!change.old.key || !change.new.key))
|
|
235
|
+
if (!change.onlyDoc && (change.old.key || change.new.key) && !change.new.target && !change.old.target) {
|
|
236
|
+
// For to.hdi.migration: Just drop-create (commented out), for to.sql.migration: Handle case where we add/remove "key" keyword, no drop-create otherwise
|
|
237
|
+
if (options.sqlDialect === 'hana' && options.src === 'hdi' || (!change.old.key || !change.new.key))
|
|
196
238
|
hasPrimaryKeyChange = true;
|
|
197
|
-
}
|
|
198
239
|
}
|
|
199
240
|
});
|
|
241
|
+
}
|
|
200
242
|
}
|
|
201
243
|
|
|
202
|
-
if (migration.properties || migration.remove || migration.change || migration.removeConstraints)
|
|
244
|
+
if (migration.properties || migration.remove || migration.change || migration.removeConstraints)
|
|
203
245
|
migrations.push(migration);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
const otherArtifact = beforeModel.definitions[name];
|
|
208
|
-
const isPersisted = isPersistedAsTable(artifact);
|
|
209
|
-
const isPersistedOther = otherArtifact && isPersistedAsTable(otherArtifact);
|
|
210
|
-
|
|
211
|
-
// to make it easier to know which views to drop-create
|
|
212
|
-
if(isPersistedAsView(artifact) && isPersistedAsView(otherArtifact)) {
|
|
213
|
-
// TODO: Check only on artifact.query/projection BUT: Need to manually check for sql-snippets then!
|
|
214
|
-
artifact[isChanged] = JSON.stringify(artifact) !== JSON.stringify(otherArtifact);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Looking for added entities and added/deleted/changed elements.
|
|
218
|
-
// Parameters: `artifact` from afterModel and `otherArtifact` from beforeModel.
|
|
219
|
-
|
|
220
|
-
if (!isPersisted) // Artifact not persisted in afterModel.
|
|
221
|
-
return;
|
|
222
|
-
|
|
223
|
-
if (!isPersistedOther) {
|
|
224
|
-
extensions[name] = artifact;
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// Artifact changed?
|
|
229
|
-
|
|
230
|
-
addElements();
|
|
231
|
-
changePropsOrRemoveOrChangeElements();
|
|
232
|
-
if(hasPrimaryKeyChange) {
|
|
233
|
-
changedPrimaryKeys.push(name);
|
|
234
246
|
}
|
|
235
247
|
};
|
|
236
248
|
}
|
|
237
249
|
/**
|
|
238
250
|
* Calculate all deleted entities.
|
|
239
|
-
*
|
|
240
|
-
* @param {CSN.Model} afterModel
|
|
241
|
-
* @param {CSN.Options} options
|
|
242
|
-
* @param {object} returnObj
|
|
251
|
+
*
|
|
252
|
+
* @param {CSN.Model} afterModel
|
|
253
|
+
* @param {CSN.Options} options
|
|
254
|
+
* @param {object} returnObj
|
|
243
255
|
* @returns {function}
|
|
244
256
|
*/
|
|
245
257
|
function getDeletions(afterModel, options, { deletions }) {
|
|
@@ -251,8 +263,9 @@ function getDeletions(afterModel, options, { deletions }) {
|
|
|
251
263
|
// Looking for deleted entities only.
|
|
252
264
|
if (isPersisted && !isPersistedOther) {
|
|
253
265
|
deletions[name] = artifact;
|
|
254
|
-
|
|
255
|
-
|
|
266
|
+
}
|
|
267
|
+
// eslint-disable-next-line sonarjs/no-duplicated-branches
|
|
268
|
+
else if (isPersistedAsView(artifact) && isPersistedOther) { // view turned into table - need to render a drop for the view
|
|
256
269
|
deletions[name] = artifact;
|
|
257
270
|
}
|
|
258
271
|
};
|
|
@@ -260,37 +273,40 @@ function getDeletions(afterModel, options, { deletions }) {
|
|
|
260
273
|
|
|
261
274
|
function getElementComparator(otherArtifact, addedElementsDict = null, changedElementsDict = null) {
|
|
262
275
|
return function compareElements(element, name) {
|
|
263
|
-
if (element.$ignore)
|
|
276
|
+
if (element.$ignore)
|
|
264
277
|
return;
|
|
265
|
-
|
|
278
|
+
|
|
266
279
|
|
|
267
280
|
const otherElement = otherArtifact.elements[name];
|
|
268
281
|
if (otherElement && !otherElement.$ignore) {
|
|
269
282
|
// Element type changed?
|
|
270
|
-
if (!changedElementsDict)
|
|
283
|
+
if (!changedElementsDict)
|
|
271
284
|
return;
|
|
272
|
-
|
|
285
|
+
|
|
273
286
|
if (relevantTypeChange(element.type, otherElement.type) || typeParametersChanged(element, otherElement)) {
|
|
274
287
|
// Type or parameters, e.g. association target, changed.
|
|
275
|
-
if(otherElement.notNull && element.notNull === undefined)
|
|
276
|
-
element
|
|
277
|
-
|
|
288
|
+
if (otherElement.notNull && element.notNull === undefined && !element.key || otherElement.key && !element.key && !element.notNull)
|
|
289
|
+
setProp(element, '$notNull', false); // Explicitly set notNull to the implicit default so we render the correct ALTER
|
|
290
|
+
|
|
291
|
+
if (otherElement.default && element.default === undefined)
|
|
292
|
+
setProp(element, '$default', { val: null }); // Explicitly set default to the implicit default "null" so we render the correct ALTER
|
|
293
|
+
|
|
278
294
|
changedElementsDict[name] = changedElement(element, otherElement);
|
|
279
|
-
}
|
|
295
|
+
}
|
|
296
|
+
else if (docCommentChanged(element, otherElement)) {
|
|
280
297
|
changedElementsDict[name] = { ...changedElement(element, otherElement), onlyDoc: true };
|
|
281
298
|
}
|
|
282
299
|
|
|
283
300
|
return;
|
|
284
301
|
}
|
|
285
302
|
|
|
286
|
-
if (addedElementsDict)
|
|
303
|
+
if (addedElementsDict)
|
|
287
304
|
addedElementsDict[name] = element;
|
|
288
|
-
|
|
289
|
-
}
|
|
305
|
+
};
|
|
290
306
|
}
|
|
291
307
|
|
|
292
308
|
function relevantTypeChange(type, otherType) {
|
|
293
|
-
return otherType !== type && !isSuperflousHanaTypeChange(otherType, type) && ![type, otherType].every(t => ['cds.Association', 'cds.Composition'].includes(t));
|
|
309
|
+
return otherType !== type && !isSuperflousHanaTypeChange(otherType, type) && ![ type, otherType ].every(t => [ 'cds.Association', 'cds.Composition' ].includes(t));
|
|
294
310
|
}
|
|
295
311
|
|
|
296
312
|
const superflousTypeChanges = {
|
|
@@ -301,7 +317,7 @@ const superflousTypeChanges = {
|
|
|
301
317
|
'cds.UTCDateTime': 'cds.DateTime',
|
|
302
318
|
'cds.UTCTimestamp': 'cds.Timestamp',
|
|
303
319
|
'cds.LocalDate': 'cds.Date',
|
|
304
|
-
'cds.LocalTime': 'cds.Time'
|
|
320
|
+
'cds.LocalTime': 'cds.Time',
|
|
305
321
|
};
|
|
306
322
|
|
|
307
323
|
/**
|
|
@@ -319,11 +335,11 @@ function isSuperflousHanaTypeChange( before, after ) {
|
|
|
319
335
|
/**
|
|
320
336
|
* If the element has one of the superflous types, do the change so we don't accidentally
|
|
321
337
|
* pass such an old type into the SQL renderer.
|
|
322
|
-
* @param {CSN.Element} element
|
|
338
|
+
* @param {CSN.Element} element
|
|
323
339
|
* @returns {CSN.Element}
|
|
324
340
|
*/
|
|
325
341
|
function remapType( element ) {
|
|
326
|
-
if(element?.type && superflousTypeChanges[element.type])
|
|
342
|
+
if (element?.type && superflousTypeChanges[element.type])
|
|
327
343
|
element.type = superflousTypeChanges[element.type];
|
|
328
344
|
return element;
|
|
329
345
|
}
|
|
@@ -351,23 +367,18 @@ function deepEqual(a, b, include = () => true, depth = 0) {
|
|
|
351
367
|
return Object.keys(a).reduce((prev, key) => prev && (!include(key, depth) || deepEqual(a[key], b[key], include, depth + 1)), true);
|
|
352
368
|
}
|
|
353
369
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
370
|
+
if (isObject(a)) {
|
|
371
|
+
return isObject(b)
|
|
372
|
+
? samePropertyCount() && allPropertiesEqual()
|
|
373
|
+
: false;
|
|
374
|
+
}
|
|
375
|
+
return a === b;
|
|
359
376
|
}
|
|
360
377
|
|
|
361
378
|
function docCommentChanged(element, otherElement) {
|
|
362
379
|
return element.doc && !otherElement.doc || otherElement.doc && !element.doc || element.doc && element.doc !== otherElement.doc;
|
|
363
380
|
}
|
|
364
381
|
|
|
365
|
-
const relevantProperties = {
|
|
366
|
-
'doc': true,
|
|
367
|
-
'@sql.prepend': true,
|
|
368
|
-
'@sql.append': true
|
|
369
|
-
};
|
|
370
|
-
|
|
371
382
|
/**
|
|
372
383
|
* Returns whether any type parameters differ between two given elements. Ignores whether types themselves differ (`type` property) and ignores
|
|
373
384
|
* diff in doc comments.
|
|
@@ -378,18 +389,20 @@ const relevantProperties = {
|
|
|
378
389
|
function typeParametersChanged(element, otherElement) {
|
|
379
390
|
const checked = new Set();
|
|
380
391
|
for (const key in element) {
|
|
381
|
-
if (Object.prototype.hasOwnProperty.call(element, key))
|
|
382
|
-
if((!key.startsWith('@') || relevantProperties[key]) && key !== 'type' && key !== 'doc') {
|
|
392
|
+
if (Object.prototype.hasOwnProperty.call(element, key)) {
|
|
393
|
+
if ((!key.startsWith('@') || relevantProperties[key]) && key !== 'type' && key !== 'doc') {
|
|
383
394
|
checked.add(key);
|
|
384
|
-
if(!deepEqual(element[key], otherElement[key]))
|
|
395
|
+
if (!deepEqual(element[key], otherElement[key]))
|
|
385
396
|
return true;
|
|
386
|
-
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
387
399
|
}
|
|
388
400
|
|
|
389
401
|
for (const key in otherElement) {
|
|
390
|
-
if (Object.prototype.hasOwnProperty.call(otherElement, key))
|
|
391
|
-
if((!key.startsWith('@') || relevantProperties[key]) && key !== 'type' &&
|
|
402
|
+
if (Object.prototype.hasOwnProperty.call(otherElement, key)) {
|
|
403
|
+
if ((!key.startsWith('@') || relevantProperties[key]) && key !== 'type' && key !== 'doc' && !checked.has(key))
|
|
392
404
|
return true;
|
|
405
|
+
}
|
|
393
406
|
}
|
|
394
407
|
|
|
395
408
|
return false;
|
|
@@ -398,7 +411,7 @@ function typeParametersChanged(element, otherElement) {
|
|
|
398
411
|
function addedElements(entity, elements) {
|
|
399
412
|
return {
|
|
400
413
|
extend: entity,
|
|
401
|
-
elements
|
|
414
|
+
elements,
|
|
402
415
|
};
|
|
403
416
|
}
|
|
404
417
|
|
|
@@ -408,24 +421,24 @@ function addedConstraint(entity, constraint, constraintName, constraintType) {
|
|
|
408
421
|
constraint,
|
|
409
422
|
constraintName,
|
|
410
423
|
constraintType,
|
|
411
|
-
}
|
|
424
|
+
};
|
|
412
425
|
}
|
|
413
426
|
|
|
414
427
|
function changedElement(element, otherElement) {
|
|
415
428
|
return {
|
|
416
429
|
old: remapType(otherElement),
|
|
417
|
-
new: element
|
|
430
|
+
new: element,
|
|
418
431
|
};
|
|
419
432
|
}
|
|
420
433
|
|
|
421
434
|
function hasReferentialConstraints(options) {
|
|
422
|
-
return options.src === 'sql' && (options.sqlDialect === 'postgres' || options.sqlDialect === 'hana' || options.sqlDialect === 'sqlite')
|
|
435
|
+
return options.src === 'sql' && (options.sqlDialect === 'postgres' || options.sqlDialect === 'hana' || options.sqlDialect === 'sqlite');
|
|
423
436
|
}
|
|
424
437
|
|
|
425
438
|
module.exports = {
|
|
426
439
|
compareModels,
|
|
427
440
|
deepEqual,
|
|
428
|
-
isChanged
|
|
441
|
+
isChanged,
|
|
429
442
|
};
|
|
430
443
|
|
|
431
444
|
/**
|
package/lib/optionProcessor.js
CHANGED
|
@@ -145,6 +145,7 @@ optionProcessor
|
|
|
145
145
|
O, toOdata [options] <files...> Generate ODATA metadata and annotations
|
|
146
146
|
C, toCdl <files...> Generate CDS source files
|
|
147
147
|
Q, toSql [options] <files...> Generate SQL DDL statements
|
|
148
|
+
J, forJava [options] <files...> Generate CSN for the Java Runtime
|
|
148
149
|
toCsn [options] <files...> (default) Generate original model as CSN
|
|
149
150
|
parseCdl [options] <file> Generate a CSN that is close to the CDL source.
|
|
150
151
|
explain <message-id> Explain a compiler message.
|
|
@@ -154,7 +155,8 @@ optionProcessor
|
|
|
154
155
|
manageConstraints [options] <files...> (internal) Generate ALTER TABLE statements to
|
|
155
156
|
add / modify referential constraints.
|
|
156
157
|
inspect [options] <files...> (internal) Inspect the given CDS files.
|
|
157
|
-
toEffectiveCsn [options] <files...> (internal) Get an effective CSN
|
|
158
|
+
toEffectiveCsn [options] <files...> (internal) Get an effective CSN; requires beta mode
|
|
159
|
+
forSeal [options] <files...> (internal) Get a SEAL CSN
|
|
158
160
|
|
|
159
161
|
Environment variables
|
|
160
162
|
NO_COLOR If set, compiler messages (/output) will not be colored.
|
|
@@ -224,6 +226,7 @@ optionProcessor.command('O, toOdata')
|
|
|
224
226
|
.option(' --odata-containment')
|
|
225
227
|
.option(' --odata-capabilities-pullup')
|
|
226
228
|
.option(' --odata-openapi-hints')
|
|
229
|
+
.option(' --edm4openapi', { optionName: 'edm4OpenAPI' })
|
|
227
230
|
.option(' --odata-proxies')
|
|
228
231
|
.option(' --odata-x-service-refs')
|
|
229
232
|
.option(' --odata-foreign-keys')
|
|
@@ -254,6 +257,7 @@ optionProcessor.command('O, toOdata')
|
|
|
254
257
|
--odata-containment Generate Containment Navigation Properties for compositions (V4 only)
|
|
255
258
|
--odata-capabilities-pullup Rewrite @Capabilities annotations (V4 containment only).
|
|
256
259
|
--odata-openapi-hints Add various annotations to JSON API as input for OpenAPI generation.
|
|
260
|
+
--edm4openapi Downgrade some errors to warnings for OpenAPI generation.
|
|
257
261
|
--odata-proxies Generate Proxies for out-of-service navigation targets (V4 only).
|
|
258
262
|
--odata-x-service-refs Generate schema references (V4 only).
|
|
259
263
|
--odata-foreign-keys Render foreign keys in structured format (V4 only)
|
|
@@ -274,6 +278,17 @@ optionProcessor.command('O, toOdata')
|
|
|
274
278
|
those views, that only have an association to a localized entity/view.
|
|
275
279
|
`);
|
|
276
280
|
|
|
281
|
+
optionProcessor.command('J, forJava')
|
|
282
|
+
.option('-h, --help')
|
|
283
|
+
.help(`
|
|
284
|
+
Usage: cdsc forJava [options] <files...>
|
|
285
|
+
|
|
286
|
+
Generate CSN (structured) for Java Runtime.
|
|
287
|
+
|
|
288
|
+
Options
|
|
289
|
+
-h, --help Show this help text
|
|
290
|
+
`);
|
|
291
|
+
|
|
277
292
|
optionProcessor.command('C, toCdl')
|
|
278
293
|
.option('-h, --help')
|
|
279
294
|
.help(`
|
|
@@ -530,6 +545,10 @@ optionProcessor.command('inspect')
|
|
|
530
545
|
|
|
531
546
|
optionProcessor.command('toEffectiveCsn')
|
|
532
547
|
.option('-h, --help')
|
|
548
|
+
.option('--resolve-simple-types <val>', { valid: ['true', 'false'] } )
|
|
549
|
+
.option('--resolve-projections <val>', { valid: ['true', 'false'] } )
|
|
550
|
+
.option('--remap-odata-annotations <val>', { valid: ['true', 'false'] } )
|
|
551
|
+
.option('--keep-localized <val>', { valid: ['true', 'false'] } )
|
|
533
552
|
.positionalArgument('<files...>')
|
|
534
553
|
.help(`
|
|
535
554
|
Usage: cdsc toEffectiveCsn [options] <files...>
|
|
@@ -539,7 +558,35 @@ optionProcessor.command('toEffectiveCsn')
|
|
|
539
558
|
Beta mode is required.
|
|
540
559
|
|
|
541
560
|
Options
|
|
542
|
-
-h, --help
|
|
561
|
+
-h, --help Show this help text
|
|
562
|
+
--resolve-simple-types <val> Resolve simple types:
|
|
563
|
+
true: (default) resolve simple type references to their simple base type
|
|
564
|
+
false: do not resolve simple type references
|
|
565
|
+
--resolve-projections <val> Resolve projections:
|
|
566
|
+
true: (default) transform projections into ordinary views with SELECT
|
|
567
|
+
false: leave them as real projections
|
|
568
|
+
--remap-odata-annotations <val> Remap OData annotations to ABAP annotations:
|
|
569
|
+
true: remap annotations
|
|
570
|
+
false:(default) leave them as is
|
|
571
|
+
--keep-localized <val> Keep '.localized' property in the CSN:
|
|
572
|
+
true: property is kept
|
|
573
|
+
false:(default) property is deleted
|
|
574
|
+
`);
|
|
575
|
+
|
|
576
|
+
optionProcessor.command('forSeal')
|
|
577
|
+
.option('-h, --help')
|
|
578
|
+
.option('--remap-odata-annotations <val>', { valid: ['true', 'false'] } )
|
|
579
|
+
.positionalArgument('<files...>')
|
|
580
|
+
.help(`
|
|
581
|
+
Usage: cdsc forSeal [options] <files...>
|
|
582
|
+
|
|
583
|
+
(internal): Get the SEAL CSN model compiled from the provided CDS files.
|
|
584
|
+
|
|
585
|
+
Options
|
|
586
|
+
-h, --help Show this help text
|
|
587
|
+
--remap-odata-annotations <val> Remap OData annotations to ABAP annotations:
|
|
588
|
+
true: (default) remap annotations
|
|
589
|
+
false: leave them as is
|
|
543
590
|
`);
|
|
544
591
|
|
|
545
592
|
module.exports = {
|
|
@@ -97,12 +97,12 @@ class DuplicateChecker {
|
|
|
97
97
|
else
|
|
98
98
|
namingMode = 'plain';
|
|
99
99
|
|
|
100
|
-
error(null, [ 'definitions', artifact.modelName ],
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
100
|
+
error(null, [ 'definitions', artifact.modelName ], {
|
|
101
|
+
name: collidesWith.modelName, prop: namingMode, '#': artifact.modelName.includes('.') ? 'dots' : 'std',
|
|
102
|
+
}, {
|
|
103
|
+
std: 'Artifact name can\'t be mapped to a SQL compliant identifier in naming mode $(PROP) because it conflicts with existing definition $(NAME)',
|
|
104
|
+
dots: 'Artifact name containing dots can\'t be mapped to a SQL compliant identifier in naming mode $(PROP) because it conflicts with existing definition $(NAME)',
|
|
105
|
+
});
|
|
106
106
|
});
|
|
107
107
|
}
|
|
108
108
|
artifacts.forEach((artifact) => {
|
|
@@ -32,6 +32,7 @@ function alterConstraintsWithCsn( csn, options, messageFunctions ) {
|
|
|
32
32
|
warning(null, null, { prop: sqlDialect || 'plain' }, 'Referential Constraints are not available for sql dialect $(PROP)');
|
|
33
33
|
|
|
34
34
|
if (drop && alter)
|
|
35
|
+
// eslint-disable-next-line cds-compiler/message-no-quotes
|
|
35
36
|
error(null, null, 'Option “--drop” can\'t be combined with “--alter”');
|
|
36
37
|
|
|
37
38
|
// Of course, we want the database constraints
|
package/lib/render/toCdl.js
CHANGED
|
@@ -9,13 +9,14 @@ const { forEachDefinition, normalizeTypeRef } = require('../model/csnUtils');
|
|
|
9
9
|
const enrichUniversalCsn = require('../transform/universalCsn/universalCsnEnricher');
|
|
10
10
|
const { isBetaEnabled } = require('../base/model');
|
|
11
11
|
const { ModelError } = require('../base/error');
|
|
12
|
-
const { typeParameters, specialFunctions
|
|
12
|
+
const { typeParameters, specialFunctions } = require('../compiler/builtins');
|
|
13
|
+
const { isAnnotationExpression } = require('../base/builtins');
|
|
13
14
|
const { forEach } = require('../utils/objectUtils');
|
|
14
15
|
const {
|
|
15
|
-
isBuiltinType,
|
|
16
16
|
generatedByCompilerVersion,
|
|
17
17
|
getNormalizedQuery,
|
|
18
18
|
} = require('../model/csnUtils');
|
|
19
|
+
const { isBuiltinType } = require('../base/builtins');
|
|
19
20
|
const { cloneFullCsn } = require('../model/cloneCsn');
|
|
20
21
|
|
|
21
22
|
const identifierRegex = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/;
|
|
@@ -613,10 +614,12 @@ function csnToCdl( csn, options, msg ) {
|
|
|
613
614
|
else if (element['#'] !== undefined) { // enum symbol reference
|
|
614
615
|
result += ` = #${element['#']}`;
|
|
615
616
|
}
|
|
616
|
-
else if (!isCalcElement || !isDirectAssocOrComp(element.type)) {
|
|
617
|
+
else if (!isCalcElement || !isDirectAssocOrComp(element.type) && !element.$filtered) {
|
|
617
618
|
// If the element is a calculated element _and_ a direct association or
|
|
618
619
|
// composition, we'd render `Association to F on (cond) = calcValue;` which
|
|
619
620
|
// would alter the ON-condition.
|
|
621
|
+
// If it is a calculated element _and_ an indirect association (via type chain),
|
|
622
|
+
// we'd get a cast to an association.
|
|
620
623
|
const props = renderTypeReferenceAndProps(element, env);
|
|
621
624
|
if (props !== '')
|
|
622
625
|
result += ` : ${props}`;
|