@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
|
@@ -8,6 +8,8 @@ const {
|
|
|
8
8
|
isPersistedAsView
|
|
9
9
|
} = require('../model/csnUtils');
|
|
10
10
|
const { isBetaEnabled } = require('../base/model');
|
|
11
|
+
const { forEachKey } = require('../utils/objectUtils');
|
|
12
|
+
|
|
11
13
|
// used to mark a view as changed so we know to drop-create it
|
|
12
14
|
const isChanged = Symbol('Marks a view as changed');
|
|
13
15
|
|
|
@@ -25,19 +27,18 @@ function compareModels(beforeModel, afterModel, options) {
|
|
|
25
27
|
if(!(options && options.testMode)) // no $version with testMode
|
|
26
28
|
validateCsnVersions(beforeModel, afterModel, options);
|
|
27
29
|
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
const returnObj = Object.create(null);
|
|
31
|
+
returnObj.definitions = afterModel.definitions;
|
|
32
|
+
returnObj.deletions = Object.create(null);
|
|
33
|
+
returnObj.extensions = [];
|
|
34
|
+
returnObj.migrations = []; // element changes/removals or changes of entity properties
|
|
35
|
+
returnObj.unchangedConstraints = new Set();
|
|
31
36
|
|
|
32
37
|
// There is currently no use in knowing the added entities only. If this changes, hand in `addedEntities` to `getArtifactComparator` below.
|
|
33
|
-
forEachDefinition(afterModel,
|
|
34
|
-
forEachDefinition(beforeModel,
|
|
38
|
+
forEachDefinition(afterModel, getExtensionAndMigrations(beforeModel, options, returnObj));
|
|
39
|
+
forEachDefinition(beforeModel, getDeletions(afterModel, options, returnObj));
|
|
40
|
+
|
|
35
41
|
|
|
36
|
-
const returnObj = Object.create(null);
|
|
37
|
-
returnObj.definitions = afterModel.definitions;
|
|
38
|
-
returnObj.deletions = deletedEntities;
|
|
39
|
-
returnObj.extensions = elementAdditions;
|
|
40
|
-
returnObj.migrations = migrations;
|
|
41
42
|
return returnObj;
|
|
42
43
|
}
|
|
43
44
|
|
|
@@ -65,13 +66,21 @@ function validateCsnVersions(beforeModel, afterModel, options) {
|
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Calculate extensions, migrations and unchangedConstraints
|
|
71
|
+
*
|
|
72
|
+
* @param {CSN.Model} beforeModel
|
|
73
|
+
* @param {CSN.Options} options
|
|
74
|
+
* @param {object} returnObj
|
|
75
|
+
* @returns {function}
|
|
76
|
+
*/
|
|
77
|
+
function getExtensionAndMigrations(beforeModel, options, { extensions, migrations, unchangedConstraints }) {
|
|
69
78
|
return function compareArtifacts(artifact, name) {
|
|
70
79
|
function addElements() {
|
|
71
80
|
const elements = {};
|
|
72
81
|
forEachMember(artifact, getElementComparator(otherArtifact, elements), [ 'definitions', name ], true, { elementsOnly: true });
|
|
73
82
|
if (Object.keys(elements).length > 0) {
|
|
74
|
-
|
|
83
|
+
extensions.push(addedElements(name, elements));
|
|
75
84
|
}
|
|
76
85
|
}
|
|
77
86
|
function changePropsOrRemoveOrChangeElements() {
|
|
@@ -92,6 +101,54 @@ function getArtifactComparator(otherModel, options, addedEntities, deletedEntiti
|
|
|
92
101
|
changedProperties[prop.name] = changedElement(artifact[prop.name], otherArtifact[prop.name] || null);
|
|
93
102
|
}
|
|
94
103
|
});
|
|
104
|
+
|
|
105
|
+
const removedConstraints = {};
|
|
106
|
+
|
|
107
|
+
// HDI (src === 'hdi) does handle table constraints via separate files and therefore no delta handling needed
|
|
108
|
+
if(options.src === 'sql' && (artifact.$tableConstraints || otherArtifact.$tableConstraints)) {
|
|
109
|
+
const current = artifact.$tableConstraints || {};
|
|
110
|
+
const old = otherArtifact.$tableConstraints || {};
|
|
111
|
+
let changes = false;
|
|
112
|
+
const constraintTypes = ['unique', 'referential'];
|
|
113
|
+
constraintTypes.forEach(constraintType => {
|
|
114
|
+
// We only render/handle referential constraints for specific cases
|
|
115
|
+
if(hasReferentialConstraints(options) || constraintType !== 'referential')
|
|
116
|
+
if(current[constraintType] || old[constraintType]) {
|
|
117
|
+
removedConstraints[constraintType] = Object.create(null);
|
|
118
|
+
|
|
119
|
+
const cnew = current[constraintType] || Object.create(null);
|
|
120
|
+
const cold = old[constraintType] || Object.create(null);
|
|
121
|
+
forEachKey(cnew, (constraintName) => {
|
|
122
|
+
if(cold[constraintName]) {
|
|
123
|
+
// constraint changed - add it to "removedConstraints" to drop-create it.
|
|
124
|
+
// Quick-and-dirty compare with JSON.stringify - false-positive will just be a drop-create
|
|
125
|
+
if(JSON.stringify(cold[constraintName]) !== JSON.stringify(cnew[constraintName])) {
|
|
126
|
+
changes = true;
|
|
127
|
+
removedConstraints[constraintType][constraintName] = cold[constraintName];
|
|
128
|
+
if(options.constraintsInCreateTable || constraintType !== 'referential') // schedule an "ADD"
|
|
129
|
+
extensions.push(addedConstraint(name, cnew[constraintName], constraintName, constraintType));
|
|
130
|
+
} else {
|
|
131
|
+
unchangedConstraints.add(constraintName);
|
|
132
|
+
}
|
|
133
|
+
} else if(options.constraintsInCreateTable || constraintType !== 'referential'){
|
|
134
|
+
// Sometimes referential constraints are anyway added via ALTER - no need to add them as explicit extensions then
|
|
135
|
+
extensions.push(addedConstraint(name, cnew[constraintName], constraintName, constraintType));
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
forEachKey(cold, (constraintName) => {
|
|
140
|
+
if(!cnew[constraintName]) {
|
|
141
|
+
changes = true;
|
|
142
|
+
removedConstraints[constraintType][constraintName] = cold[constraintName];
|
|
143
|
+
}
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
if(changes)
|
|
149
|
+
migration.removeConstraints = removedConstraints;
|
|
150
|
+
}
|
|
151
|
+
|
|
95
152
|
if (Object.keys(changedProperties).length > 0) {
|
|
96
153
|
migration.properties = changedProperties;
|
|
97
154
|
}
|
|
@@ -106,27 +163,15 @@ function getArtifactComparator(otherModel, options, addedEntities, deletedEntiti
|
|
|
106
163
|
migration.change = changedElements;
|
|
107
164
|
}
|
|
108
165
|
|
|
109
|
-
if (migration.properties || migration.remove || migration.change) {
|
|
166
|
+
if (migration.properties || migration.remove || migration.change || migration.removeConstraints) {
|
|
110
167
|
migrations.push(migration);
|
|
111
168
|
}
|
|
112
169
|
}
|
|
113
170
|
|
|
114
|
-
const otherArtifact =
|
|
171
|
+
const otherArtifact = beforeModel.definitions[name];
|
|
115
172
|
const isPersisted = isPersistedAsTable(artifact);
|
|
116
173
|
const isPersistedOther = otherArtifact && isPersistedAsTable(otherArtifact);
|
|
117
174
|
|
|
118
|
-
if (deletedEntities) {
|
|
119
|
-
// Looking for deleted entities only.
|
|
120
|
-
// Arguments are interchanged in this case: `artifact` from beforeModel and `otherArtifact` from afterModel.
|
|
121
|
-
if (isPersisted && !isPersistedOther) {
|
|
122
|
-
deletedEntities[name] = artifact;
|
|
123
|
-
// eslint-disable-next-line sonarjs/no-duplicated-branches
|
|
124
|
-
} else if(isPersistedAsView(artifact) && isPersistedOther) { // view turned into table - need to render a drop for the view
|
|
125
|
-
deletedEntities[name] = artifact;
|
|
126
|
-
}
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
175
|
// to make it easier to know which views to drop-create
|
|
131
176
|
if(isPersistedAsView(artifact) && isPersistedAsView(otherArtifact)) {
|
|
132
177
|
// TODO: Check only on artifact.query/projection BUT: Need to manually check for sql-snippets then!
|
|
@@ -136,37 +181,52 @@ function getArtifactComparator(otherModel, options, addedEntities, deletedEntiti
|
|
|
136
181
|
// Looking for added entities and added/deleted/changed elements.
|
|
137
182
|
// Parameters: `artifact` from afterModel and `otherArtifact` from beforeModel.
|
|
138
183
|
|
|
139
|
-
if (!isPersisted)
|
|
140
|
-
// Artifact not persisted in afterModel.
|
|
184
|
+
if (!isPersisted) // Artifact not persisted in afterModel.
|
|
141
185
|
return;
|
|
142
|
-
}
|
|
143
186
|
|
|
144
187
|
if (!isPersistedOther) {
|
|
145
|
-
|
|
146
|
-
addedEntities[name] = artifact;
|
|
147
|
-
}
|
|
188
|
+
extensions[name] = artifact;
|
|
148
189
|
return;
|
|
149
190
|
}
|
|
150
191
|
|
|
151
192
|
// Artifact changed?
|
|
152
193
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
194
|
+
addElements();
|
|
195
|
+
changePropsOrRemoveOrChangeElements();
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Calculate all deleted entities.
|
|
200
|
+
*
|
|
201
|
+
* @param {CSN.Model} afterModel
|
|
202
|
+
* @param {CSN.Options} options
|
|
203
|
+
* @param {object} returnObj
|
|
204
|
+
* @returns {function}
|
|
205
|
+
*/
|
|
206
|
+
function getDeletions(afterModel, options, { deletions }) {
|
|
207
|
+
return function compareArtifacts(artifact, name) {
|
|
208
|
+
const otherArtifact = afterModel.definitions[name];
|
|
209
|
+
const isPersisted = isPersistedAsTable(artifact);
|
|
210
|
+
const isPersistedOther = otherArtifact && isPersistedAsTable(otherArtifact);
|
|
211
|
+
|
|
212
|
+
// Looking for deleted entities only.
|
|
213
|
+
if (isPersisted && !isPersistedOther) {
|
|
214
|
+
deletions[name] = artifact;
|
|
215
|
+
// eslint-disable-next-line sonarjs/no-duplicated-branches
|
|
216
|
+
} else if (isPersistedAsView(artifact) && isPersistedOther) { // view turned into table - need to render a drop for the view
|
|
217
|
+
deletions[name] = artifact;
|
|
158
218
|
}
|
|
159
219
|
};
|
|
160
220
|
}
|
|
161
221
|
|
|
162
222
|
function getElementComparator(otherArtifact, addedElementsDict = null, changedElementsDict = null) {
|
|
163
223
|
return function compareElements(element, name) {
|
|
164
|
-
if (element
|
|
224
|
+
if (element.$ignore) {
|
|
165
225
|
return;
|
|
166
226
|
}
|
|
167
227
|
|
|
168
228
|
const otherElement = otherArtifact.elements[name];
|
|
169
|
-
if (otherElement && !otherElement
|
|
229
|
+
if (otherElement && !otherElement.$ignore) {
|
|
170
230
|
// Element type changed?
|
|
171
231
|
if (!changedElementsDict) {
|
|
172
232
|
return;
|
|
@@ -189,7 +249,42 @@ function getElementComparator(otherArtifact, addedElementsDict = null, changedEl
|
|
|
189
249
|
}
|
|
190
250
|
|
|
191
251
|
function relevantTypeChange(type, otherType) {
|
|
192
|
-
return otherType !== type && ![type, otherType].every(t => ['cds.Association', 'cds.Composition'].includes(t));
|
|
252
|
+
return otherType !== type && !isSuperflousHanaTypeChange(otherType, type) && ![type, otherType].every(t => ['cds.Association', 'cds.Composition'].includes(t));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const superflousTypeChanges = {
|
|
256
|
+
// turn it into a real dict
|
|
257
|
+
__proto__: null,
|
|
258
|
+
// We used to put these types into the CSN, although they are just internal
|
|
259
|
+
// so we need to be robust against them now.
|
|
260
|
+
'cds.UTCDateTime': 'cds.DateTime',
|
|
261
|
+
'cds.UTCTimestamp': 'cds.Timestamp',
|
|
262
|
+
'cds.LocalDate': 'cds.Date',
|
|
263
|
+
'cds.LocalTime': 'cds.Time' ,
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* We removed some old SAP HANA types from the CSN and we now need to not
|
|
268
|
+
* detect them as changes.
|
|
269
|
+
*
|
|
270
|
+
* @param {string} before Type before
|
|
271
|
+
* @param {string} after Type after
|
|
272
|
+
* @returns {boolean}
|
|
273
|
+
*/
|
|
274
|
+
function isSuperflousHanaTypeChange( before, after ) {
|
|
275
|
+
return superflousTypeChanges[before] ? superflousTypeChanges[before] === after : false;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* If the element has one of the superflous types, do the change so we don't accidentally
|
|
280
|
+
* pass such an old type into the SQL renderer.
|
|
281
|
+
* @param {CSN.Element} element
|
|
282
|
+
* @returns {CSN.Element}
|
|
283
|
+
*/
|
|
284
|
+
function remapType( element ) {
|
|
285
|
+
if(element?.type && superflousTypeChanges[element.type])
|
|
286
|
+
element.type = superflousTypeChanges[element.type];
|
|
287
|
+
return element;
|
|
193
288
|
}
|
|
194
289
|
|
|
195
290
|
/**
|
|
@@ -239,13 +334,26 @@ function addedElements(entity, elements) {
|
|
|
239
334
|
};
|
|
240
335
|
}
|
|
241
336
|
|
|
337
|
+
function addedConstraint(entity, constraint, constraintName, constraintType) {
|
|
338
|
+
return {
|
|
339
|
+
extend: entity,
|
|
340
|
+
constraint,
|
|
341
|
+
constraintName,
|
|
342
|
+
constraintType,
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
242
346
|
function changedElement(element, otherElement) {
|
|
243
347
|
return {
|
|
244
|
-
old: otherElement,
|
|
348
|
+
old: remapType(otherElement),
|
|
245
349
|
new: element
|
|
246
350
|
};
|
|
247
351
|
}
|
|
248
352
|
|
|
353
|
+
function hasReferentialConstraints(options) {
|
|
354
|
+
return options.src === 'sql' && (options.sqlDialect === 'postgres' || options.sqlDialect === 'hana' || options.sqlDialect === 'sqlite')
|
|
355
|
+
}
|
|
356
|
+
|
|
249
357
|
module.exports = {
|
|
250
358
|
compareModels,
|
|
251
359
|
deepEqual,
|
|
@@ -13,20 +13,31 @@ function isKey( element ) {
|
|
|
13
13
|
module.exports = {
|
|
14
14
|
sqlite: getFilterObject(
|
|
15
15
|
'sqlite',
|
|
16
|
-
function forEachExtension(extend, name,
|
|
17
|
-
if (isKey(
|
|
18
|
-
error('type-unsupported-key-sqlite', [ 'definitions', extend, 'elements', name ], { id: name, name: 'sqlite'
|
|
16
|
+
function forEachExtension(extend, name, elementOrConstraint, { error, warning }) {
|
|
17
|
+
if (isKey(elementOrConstraint)) { // Key must not be extended
|
|
18
|
+
error('type-unsupported-key-sqlite', [ 'definitions', extend, 'elements', name ], { id: name, name: 'sqlite', '#': 'std' } );
|
|
19
|
+
return false;
|
|
19
20
|
}
|
|
21
|
+
else if (elementOrConstraint.parentTable) { // constraints have a .parentTable
|
|
22
|
+
warning('def-unsupported-constraint-add', [ 'definitions', elementOrConstraint.parentTable, 'elements', elementOrConstraint.paths ? name : name.slice(elementOrConstraint.parentTable.length + 1) ], { id: elementOrConstraint.identifier || name, name: 'sqlite' },
|
|
23
|
+
'Ignoring add of constraint $(ID), as this is not supported for dialect $(NAME); you need to manually resolve this via shadow tables and data copy');
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return true;
|
|
20
28
|
},
|
|
21
29
|
function forEachMigration(migrate, name, migration, change, error) {
|
|
22
30
|
const newIsKey = isKey(migration.new);
|
|
23
31
|
const oldIsKey = isKey(migration.old);
|
|
24
|
-
if ((newIsKey || oldIsKey) && oldIsKey !== newIsKey)
|
|
25
|
-
error('type-unsupported-key-sqlite', [ 'definitions', migrate, 'elements', name ], { id: name, name: 'sqlite'
|
|
26
|
-
|
|
27
|
-
else { // Ignore simple migrations
|
|
32
|
+
if ((newIsKey || oldIsKey) && oldIsKey !== newIsKey) // Turned into key or key was removed
|
|
33
|
+
error('type-unsupported-key-sqlite', [ 'definitions', migrate, 'elements', name ], { id: name, name: 'sqlite', '#': 'changed' } );
|
|
34
|
+
else
|
|
28
35
|
delete change[name];
|
|
29
|
-
|
|
36
|
+
},
|
|
37
|
+
function forEachConstraintRemoval(constraintRemovals, name, constraint, warning) {
|
|
38
|
+
warning('def-unsupported-constraint-drop', [ 'definitions', constraint.parentTable, 'elements', constraint.paths ? name : name.slice(constraint.parentTable.length + 1) ], { id: constraint.identifier || name, name: 'sqlite' },
|
|
39
|
+
'Ignoring drop of constraint $(ID), as this is not supported for dialect $(NAME); you need to manually resolve this via shadow tables and data copy');
|
|
40
|
+
delete constraintRemovals[name];
|
|
30
41
|
}
|
|
31
42
|
),
|
|
32
43
|
postgres: getFilterObject('postgres'),
|
|
@@ -35,22 +46,32 @@ module.exports = {
|
|
|
35
46
|
csn: filterCsn,
|
|
36
47
|
};
|
|
37
48
|
|
|
38
|
-
function getFilterObject( dialect, extensionCallback, migrationCallback ) {
|
|
49
|
+
function getFilterObject( dialect, extensionCallback, migrationCallback, removeConstraintsCallback ) {
|
|
39
50
|
return {
|
|
40
|
-
// will be called with a simple Array.
|
|
41
|
-
extension: ({
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
51
|
+
// will be called with a simple Array.filter, as we need to filter constraint `ADD` for SQLite
|
|
52
|
+
extension: ({
|
|
53
|
+
elements, constraint, constraintName, extend,
|
|
54
|
+
}, { error, message, warning }) => {
|
|
55
|
+
let returnValue = true;
|
|
56
|
+
forEach(elements, (name, element) => {
|
|
57
|
+
if (dialect !== 'sqlite' && isKey(element))
|
|
58
|
+
message('type-unsupported-key-change', [ 'definitions', extend, 'elements', name ], { id: name, '#': 'std' } );
|
|
59
|
+
else if (extensionCallback && !extensionCallback(extend, name, element, { error, warning }))
|
|
60
|
+
returnValue = false;
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (constraint && extensionCallback && !extensionCallback(extend, constraintName, constraint, { error, warning }))
|
|
64
|
+
returnValue = false;
|
|
65
|
+
|
|
66
|
+
return returnValue;
|
|
47
67
|
},
|
|
48
|
-
// will be called with a Array.
|
|
49
|
-
migration: ({
|
|
68
|
+
// will be called with a Array.forEach
|
|
69
|
+
migration: ({
|
|
70
|
+
change, migrate, remove, removeConstraints,
|
|
71
|
+
}, { error, warning, message }) => {
|
|
50
72
|
forEach(remove, (name) => {
|
|
51
73
|
error('def-unsupported-element-drop', [ 'definitions', migrate, 'elements', name ], {}, 'Dropping elements is not supported');
|
|
52
74
|
});
|
|
53
|
-
|
|
54
75
|
forEach(change, (name, migration) => {
|
|
55
76
|
const loc = [ 'definitions', migrate, 'elements', name ];
|
|
56
77
|
if (migration.new.type === migration.old.type && migration.new.length < migration.old.length)
|
|
@@ -61,11 +82,22 @@ function getFilterObject( dialect, extensionCallback, migrationCallback ) {
|
|
|
61
82
|
error('type-unsupported-precision-change', loc, { id: name }, 'Changed element $(ID) is a precision change and is not supported');
|
|
62
83
|
else if (migration.new.type !== migration.old.type && typeChangeIsNotCompatible(dialect, migration.old.type, migration.new.type))
|
|
63
84
|
error('type-unsupported-change', loc, { id: name, name: migration.old.type, type: migration.new.type }, 'Changed element $(ID) is a lossy type change from $(NAME) to $(TYPE) and is not supported');
|
|
85
|
+
else if (dialect !== 'sqlite' && isKey(migration.new) && !isKey(migration.old)) // key added/changed - pg, hana and sqlite do not support it, h2 probably also - issues when data is in the table already
|
|
86
|
+
message('type-unsupported-key-change', [ 'definitions', migrate, 'elements', name ], { id: name, '#': 'changed' } );
|
|
64
87
|
else if (migrationCallback)
|
|
65
88
|
migrationCallback(migrate, name, migration, change, error);
|
|
66
89
|
|
|
67
90
|
// TODO: precision/scale growth
|
|
68
91
|
});
|
|
92
|
+
|
|
93
|
+
if (removeConstraintsCallback) {
|
|
94
|
+
const constraintTypes = [ 'unique', 'referential' ];
|
|
95
|
+
constraintTypes.forEach((constraintType) => {
|
|
96
|
+
forEach(removeConstraints?.[constraintType], (name, constraint) => {
|
|
97
|
+
removeConstraintsCallback(removeConstraints[constraintType], name, constraint, warning);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
}
|
|
69
101
|
},
|
|
70
102
|
deletion: ([ artifactName, artifact ], error) => {
|
|
71
103
|
if (isPersistedAsTable(artifact))
|
|
@@ -110,10 +142,8 @@ function typeChangeIsNotCompatible( dialect, before, after ) {
|
|
|
110
142
|
*/
|
|
111
143
|
function filterCsn( csn ) {
|
|
112
144
|
const annosToKeep = {
|
|
113
|
-
|
|
114
|
-
'@
|
|
115
|
-
'@cds.persistence.table': true,
|
|
116
|
-
'@cds.persistence.name': true,
|
|
145
|
+
// + @cds.persistence.*
|
|
146
|
+
'@assert.integrity': true,
|
|
117
147
|
'@sql.append': true,
|
|
118
148
|
'@sql.prepend': true,
|
|
119
149
|
};
|
|
@@ -122,14 +152,14 @@ function filterCsn( csn ) {
|
|
|
122
152
|
elements: (parent, prop, elements) => {
|
|
123
153
|
forEachValue(elements, (element) => {
|
|
124
154
|
forEachKey(element, (key) => {
|
|
125
|
-
if ((key.startsWith('@') && !annosToKeep[key]) || key === 'keys')
|
|
155
|
+
if ((key.startsWith('@') && !key.startsWith('@cds.persistence.') && !annosToKeep[key]) || key === 'keys')
|
|
126
156
|
delete element[key];
|
|
127
157
|
});
|
|
128
158
|
});
|
|
129
159
|
},
|
|
130
160
|
}, [ (artifact) => {
|
|
131
161
|
forEachKey(artifact, (key) => {
|
|
132
|
-
if (key.startsWith('@') && !annosToKeep[key])
|
|
162
|
+
if (key.startsWith('@') && !key.startsWith('@cds.persistence.') && !annosToKeep[key])
|
|
133
163
|
delete artifact[key];
|
|
134
164
|
});
|
|
135
165
|
} ]);
|
package/lib/optionProcessor.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { createOptionProcessor } = require('./base/optionProcessorHelper');
|
|
4
|
+
const { availableBetaFlags } = require('./base/model');
|
|
4
5
|
|
|
5
6
|
// This option processor is used both by the command line parser (to translate cmd line options
|
|
6
7
|
// into an options object) and by the API functions (to verify options)
|
|
@@ -20,7 +21,7 @@ optionProcessor
|
|
|
20
21
|
.option(' --color <mode>', ['auto', 'always', 'never'])
|
|
21
22
|
.option('-o, --out <dir>')
|
|
22
23
|
.option(' --cds-home <dir>')
|
|
23
|
-
.option(' --
|
|
24
|
+
.option(' --module-lookup-directories <list>')
|
|
24
25
|
.option(' --trace-fs')
|
|
25
26
|
.option(' --error <id-list>')
|
|
26
27
|
.option(' --warn <id-list>')
|
|
@@ -77,7 +78,8 @@ optionProcessor
|
|
|
77
78
|
never:
|
|
78
79
|
-o, --out <dir> Place generated files in directory <dir>, default is "-" for <stdout>
|
|
79
80
|
--cds-home <dir> When set, modules starting with '@sap/cds/' are searched in <dir>
|
|
80
|
-
--
|
|
81
|
+
--module-lookup-directories <list> Comma separated list of directories to look
|
|
82
|
+
for CDS modules. Default is 'node_modules/'.
|
|
81
83
|
-- Indicate the end of options (helpful if source names start with "-")
|
|
82
84
|
|
|
83
85
|
Type options
|
|
@@ -102,12 +104,11 @@ optionProcessor
|
|
|
102
104
|
--beta-mode Enable all unsupported, incomplete (beta) features
|
|
103
105
|
--beta <list> Comma separated list of unsupported, incomplete (beta) features to use.
|
|
104
106
|
Valid values are:
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
optionalActionFunctionParameters
|
|
107
|
+
${
|
|
108
|
+
Object.keys(availableBetaFlags).sort()
|
|
109
|
+
.filter(flag => availableBetaFlags[flag])
|
|
110
|
+
.join('\n' + ' '.repeat(30))
|
|
111
|
+
}
|
|
111
112
|
--deprecated <list> Comma separated list of deprecated options.
|
|
112
113
|
Valid values are:
|
|
113
114
|
eagerPersistenceForGeneratedEntities
|
|
@@ -228,6 +229,7 @@ optionProcessor.command('O, toOdata')
|
|
|
228
229
|
.option('-f, --odata-format <format>', ['flat', 'structured'])
|
|
229
230
|
.option('-n, --sql-mapping <style>', ['plain', 'quoted', 'hdbcds'], { aliases: [ '--names' ] })
|
|
230
231
|
.option('-s, --service-names <list>')
|
|
232
|
+
.option(' --fewer-localized-views')
|
|
231
233
|
.help(`
|
|
232
234
|
Usage: cdsc toOdata [options] <files...>
|
|
233
235
|
|
|
@@ -264,6 +266,8 @@ optionProcessor.command('O, toOdata')
|
|
|
264
266
|
source (like "quoted", but using element names with dots)
|
|
265
267
|
-s, --service-names <list> List of comma-separated service names to be rendered
|
|
266
268
|
(default) empty, all services are rendered
|
|
269
|
+
--fewer-localized-views If set, the backends will not create localized convenience views for
|
|
270
|
+
those views, that only have an association to a localized entity/view.
|
|
267
271
|
`);
|
|
268
272
|
|
|
269
273
|
optionProcessor.command('C, toCdl')
|
|
@@ -296,6 +300,7 @@ optionProcessor.command('Q, toSql')
|
|
|
296
300
|
.option(' --disable-hana-comments')
|
|
297
301
|
.option(' --generated-by-comment')
|
|
298
302
|
.option(' --better-sqlite-session-variables')
|
|
303
|
+
.option(' --fewer-localized-views')
|
|
299
304
|
.help(`
|
|
300
305
|
Usage: cdsc toSql [options] <files...>
|
|
301
306
|
|
|
@@ -347,7 +352,10 @@ optionProcessor.command('Q, toSql')
|
|
|
347
352
|
--pre2134ReferentialConstraintNames Do not prefix the constraint identifier with "c__"
|
|
348
353
|
--disable-hana-comments Disable rendering of doc comments as SAP HANA comments.
|
|
349
354
|
--generated-by-comment Enable rendering of the initial SQL comment for HDI-based artifacts
|
|
350
|
-
--better-sqlite-session-variables Enable better-sqlite compatible rendering of $user. Only
|
|
355
|
+
--better-sqlite-session-variables Enable better-sqlite compatible rendering of $user. Only
|
|
356
|
+
active if sqlDialect is \`sqlite\`
|
|
357
|
+
--fewer-localized-views If set, the backends will not create localized convenience views for
|
|
358
|
+
those views, that only have an association to a localized entity/view.
|
|
351
359
|
|
|
352
360
|
`);
|
|
353
361
|
|
|
@@ -425,6 +433,7 @@ optionProcessor.command('toCsn')
|
|
|
425
433
|
.option(' --with-localized')
|
|
426
434
|
.option(' --with-locations')
|
|
427
435
|
.option(' --struct-xpr')
|
|
436
|
+
.option(' --fewer-localized-views')
|
|
428
437
|
.help(`
|
|
429
438
|
Usage: cdsc toCsn [options] <files...>
|
|
430
439
|
|
|
@@ -441,6 +450,9 @@ optionProcessor.command('toCsn')
|
|
|
441
450
|
universal: in development (BETA)
|
|
442
451
|
--with-locations Add $location to CSN artifacts. In contrast to \`--enrich-csn\`,
|
|
443
452
|
$location is an object with 'file', 'line' and 'col' properties.
|
|
453
|
+
--fewer-localized-views If --with-locations and this option are set, the backends
|
|
454
|
+
will not create localized convenience views for those views,
|
|
455
|
+
that only have an association to a localized entity/view.
|
|
444
456
|
|
|
445
457
|
Internal options (for testing only, may be changed/removed at any time)
|
|
446
458
|
--with-localized Add localized convenience views to the CSN output.
|
|
@@ -101,26 +101,28 @@ function manageConstraints( csn, options ) {
|
|
|
101
101
|
forEachDefinition(csn, (artifact) => {
|
|
102
102
|
if (artifact.$tableConstraints?.referential) {
|
|
103
103
|
forEach(artifact.$tableConstraints.referential, (fileName, constraint) => {
|
|
104
|
-
|
|
105
|
-
const renderedConstraint = renderReferentialConstraint(constraint, indent, false, csn, options, renderAlterConstraintStatement);
|
|
106
|
-
if (options.src === 'hdi' && !options.drop) {
|
|
107
|
-
resultArtifacts[fileName] = renderedConstraint;
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
let alterTableStatement = '';
|
|
111
|
-
alterTableStatement += `${indent}ALTER TABLE ${quoteSqlId(getResultingName(csn, options.sqlMapping, constraint.dependentTable))}`;
|
|
112
|
-
if (renderAlterConstraintStatement)
|
|
113
|
-
alterTableStatement += `\n${indent}ALTER ${renderedConstraint};`;
|
|
114
|
-
else if (options.drop)
|
|
115
|
-
alterTableStatement += `${indent} DROP CONSTRAINT ${quoteSqlId(constraint.identifier)};`;
|
|
116
|
-
else
|
|
117
|
-
alterTableStatement += `\n${indent}ADD ${renderedConstraint};`;
|
|
118
|
-
|
|
119
|
-
resultArtifacts[fileName] = alterTableStatement;
|
|
104
|
+
resultArtifacts[fileName] = manageConstraint(constraint, csn, options, indent, quoteSqlId);
|
|
120
105
|
});
|
|
121
106
|
}
|
|
122
107
|
});
|
|
123
|
-
return
|
|
108
|
+
return resultArtifacts;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function manageConstraint( constraint, csn, options, indent, quoteSqlId ) {
|
|
112
|
+
const renderAlterConstraintStatement = options.alter && options.src !== 'hdi';
|
|
113
|
+
const renderedConstraint = renderReferentialConstraint(constraint, indent, false, csn, options, renderAlterConstraintStatement);
|
|
114
|
+
if (options.src === 'hdi' && !options.drop)
|
|
115
|
+
return renderedConstraint;
|
|
116
|
+
let alterTableStatement = '';
|
|
117
|
+
alterTableStatement += `${indent}ALTER TABLE ${quoteSqlId(getResultingName(csn, options.sqlMapping, constraint.dependentTable))}`;
|
|
118
|
+
if (renderAlterConstraintStatement)
|
|
119
|
+
alterTableStatement += `\n${indent}ALTER ${renderedConstraint};`;
|
|
120
|
+
else if (options.drop)
|
|
121
|
+
alterTableStatement += `${indent} DROP CONSTRAINT ${quoteSqlId(constraint.identifier)};`;
|
|
122
|
+
else
|
|
123
|
+
alterTableStatement += `\n${indent}ADD ${renderedConstraint};`;
|
|
124
|
+
|
|
125
|
+
return alterTableStatement;
|
|
124
126
|
}
|
|
125
127
|
|
|
126
128
|
/**
|
|
@@ -264,5 +266,6 @@ function getListOfAllConstraints( csn ) {
|
|
|
264
266
|
module.exports = {
|
|
265
267
|
alterConstraintsWithCsn,
|
|
266
268
|
manageConstraints,
|
|
269
|
+
manageConstraint,
|
|
267
270
|
listReferentialIntegrityViolations,
|
|
268
271
|
};
|