@sap/cds-compiler 4.0.0 → 4.1.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 +115 -5
- package/bin/cdsc.js +12 -12
- package/doc/CHANGELOG_BETA.md +11 -0
- package/lib/api/main.js +60 -12
- package/lib/api/validate.js +1 -1
- package/lib/base/location.js +6 -7
- package/lib/base/message-registry.js +84 -38
- package/lib/base/messages.js +11 -10
- package/lib/base/model.js +6 -2
- package/lib/checks/defaultValues.js +6 -6
- package/lib/checks/foreignKeys.js +0 -5
- package/lib/checks/onConditions.js +17 -12
- package/lib/checks/queryNoDbArtifacts.js +132 -72
- package/lib/checks/sql-snippets.js +15 -4
- package/lib/checks/types.js +3 -3
- package/lib/checks/utils.js +1 -1
- package/lib/compiler/assert-consistency.js +44 -16
- package/lib/compiler/base.js +1 -0
- package/lib/compiler/builtins.js +7 -8
- package/lib/compiler/checks.js +274 -197
- package/lib/compiler/classes.js +62 -0
- package/lib/compiler/cycle-detector.js +3 -3
- package/lib/compiler/define.js +63 -50
- package/lib/compiler/extend.js +38 -20
- package/lib/compiler/finalize-parse-cdl.js +2 -1
- package/lib/compiler/generate.js +0 -8
- package/lib/compiler/index.js +9 -7
- package/lib/compiler/kick-start.js +2 -0
- package/lib/compiler/populate.js +139 -110
- package/lib/compiler/propagator.js +4 -3
- package/lib/compiler/resolve.js +157 -126
- package/lib/compiler/shared.js +706 -404
- package/lib/compiler/tweak-assocs.js +21 -10
- package/lib/compiler/utils.js +228 -36
- package/lib/edm/annotations/genericTranslation.js +30 -2
- package/lib/edm/edm.js +4 -1
- package/lib/edm/edmPreprocessor.js +12 -5
- package/lib/edm/edmUtils.js +2 -4
- 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 +3987 -3963
- package/lib/json/from-csn.js +43 -47
- package/lib/json/to-csn.js +11 -11
- package/lib/language/antlrParser.js +2 -1
- package/lib/language/genericAntlrParser.js +52 -43
- package/lib/language/language.g4 +59 -59
- package/lib/language/multiLineStringParser.js +2 -0
- package/lib/main.d.ts +5 -0
- package/lib/model/csnRefs.js +37 -19
- package/lib/model/csnUtils.js +20 -16
- package/lib/model/revealInternalProperties.js +29 -21
- package/lib/model/sortViews.js +4 -2
- package/lib/modelCompare/compare.js +112 -39
- package/lib/modelCompare/utils/filter.js +54 -24
- package/lib/optionProcessor.js +6 -6
- package/lib/render/manageConstraints.js +20 -17
- package/lib/render/toCdl.js +34 -20
- package/lib/render/toHdbcds.js +2 -2
- package/lib/render/toRename.js +4 -9
- package/lib/render/toSql.js +77 -26
- package/lib/render/utils/common.js +3 -3
- package/lib/render/utils/unique.js +52 -0
- package/lib/transform/db/applyTransformations.js +61 -20
- package/lib/transform/db/assertUnique.js +7 -8
- package/lib/transform/db/associations.js +2 -2
- package/lib/transform/db/cdsPersistence.js +8 -8
- package/lib/transform/db/expansion.js +17 -21
- package/lib/transform/db/flattening.js +23 -23
- 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} +56 -42
- package/lib/transform/forRelationalDB.js +69 -75
- package/lib/transform/localized.js +6 -5
- package/lib/transform/odata/toFinalBaseType.js +3 -3
- package/lib/transform/{transformUtilsNew.js → transformUtils.js} +4 -101
- package/lib/transform/translateAssocsToJoins.js +14 -28
- package/package.json +1 -1
- 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
package/lib/model/csnUtils.js
CHANGED
|
@@ -69,7 +69,8 @@ function getUtils( model, universalReady ) {
|
|
|
69
69
|
};
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
|
-
* Compute and return $combined for the given query
|
|
72
|
+
* Compute and return $combined sources for the given query,
|
|
73
|
+
* that is, a map of elements that combine e.g. UNION sources.
|
|
73
74
|
*
|
|
74
75
|
* @param {CSN.Query} query
|
|
75
76
|
* @returns {object}
|
|
@@ -291,11 +292,8 @@ function getUtils( model, universalReady ) {
|
|
|
291
292
|
if (!absoluteName.startsWith('@'))
|
|
292
293
|
throw new CompilerAssertion(`Annotation name should start with "@": ${ absoluteName }`);
|
|
293
294
|
|
|
294
|
-
//
|
|
295
|
-
|
|
296
|
-
// Assemble the annotation
|
|
297
|
-
node[absoluteName] = theValue;
|
|
298
|
-
}
|
|
295
|
+
// Assemble the annotation
|
|
296
|
+
node[absoluteName] ??= theValue;
|
|
299
297
|
}
|
|
300
298
|
|
|
301
299
|
/**
|
|
@@ -332,12 +330,18 @@ function getUtils( model, universalReady ) {
|
|
|
332
330
|
* - Does _not_ return the underlying type definition! It is an object with all relevant
|
|
333
331
|
* type properties collected while traversing the type chain!
|
|
334
332
|
*
|
|
335
|
-
* @
|
|
333
|
+
* @param {string|object} type
|
|
334
|
+
* Type as string or type ref, i.e. `{ ref: [...] }`
|
|
335
|
+
*
|
|
336
|
+
* @param {(string)=>CSN.Artifact} getArtifactRef
|
|
337
|
+
* Function used to get an artifact for a reference. Useful in case that the caller
|
|
338
|
+
* has a custom cache or a CSN that is in in-between state.
|
|
339
|
+
* TODO: Can we make getUtils() more modular, so that this argument is already
|
|
340
|
+
* passed to it?
|
|
336
341
|
*
|
|
337
|
-
* @param {string|object} type Type as string or type ref, i.e. `{ ref: [...] }`
|
|
338
342
|
* @returns {object|null}
|
|
339
343
|
*/
|
|
340
|
-
function getFinalTypeInfo( type ) {
|
|
344
|
+
function getFinalTypeInfo( type, getArtifactRef = artifactRef ) {
|
|
341
345
|
type = normalizeTypeRef(type);
|
|
342
346
|
if (!type)
|
|
343
347
|
return null;
|
|
@@ -362,7 +366,7 @@ function getUtils( model, universalReady ) {
|
|
|
362
366
|
if (typeof type === 'string' && (isBuiltinType( type ) || type === special$self))
|
|
363
367
|
return _cacheResolved({ type });
|
|
364
368
|
|
|
365
|
-
const typeRef =
|
|
369
|
+
const typeRef = getArtifactRef(type); // default artifactRef() throws if not found
|
|
366
370
|
const isNonScalar = _cacheNonScalar({ ...typeRef, type });
|
|
367
371
|
if (isNonScalar)
|
|
368
372
|
return finalBaseTypeCache[resolvedKey];
|
|
@@ -380,7 +384,7 @@ function getUtils( model, universalReady ) {
|
|
|
380
384
|
finalBaseTypeCache[resolvedKey] = true;
|
|
381
385
|
|
|
382
386
|
// Continue the search
|
|
383
|
-
const finalBase = getFinalTypeInfo(type);
|
|
387
|
+
const finalBase = getFinalTypeInfo(type, getArtifactRef);
|
|
384
388
|
if (!finalBase) // Reference has no proper type, e.g. due to `type of View:calculated`.
|
|
385
389
|
return _cacheResolved(null);
|
|
386
390
|
|
|
@@ -515,8 +519,8 @@ function forEachDefinition( csn, callback, iterateOptions = {} ) {
|
|
|
515
519
|
*/
|
|
516
520
|
function forEachMember( construct, callback, path = [], ignoreIgnore = true, iterateOptions = {},
|
|
517
521
|
constructCallback = (_construct, _prop, _path) => {} ) {
|
|
518
|
-
// Allow processing
|
|
519
|
-
if (ignoreIgnore && construct
|
|
522
|
+
// Allow processing $ignored elements if requested
|
|
523
|
+
if (ignoreIgnore && construct.$ignore)
|
|
520
524
|
return;
|
|
521
525
|
|
|
522
526
|
// `items` itself is a structure that can contain "elements", and more.
|
|
@@ -775,7 +779,7 @@ function getArtifactDatabaseNameOf( artifactName, sqlMapping, csn, sqlDialect =
|
|
|
775
779
|
* @returns {string} The resulting name
|
|
776
780
|
*/
|
|
777
781
|
function getResultingName( csn, namingMode, artifactName ) {
|
|
778
|
-
if (namingMode === 'plain' || artifactName.
|
|
782
|
+
if (namingMode === 'plain' || !artifactName.includes('.'))
|
|
779
783
|
return artifactName;
|
|
780
784
|
|
|
781
785
|
const namespace = getNamespace(csn, artifactName);
|
|
@@ -964,7 +968,7 @@ function isPersistedOnDatabase( art ) {
|
|
|
964
968
|
*/
|
|
965
969
|
function isPersistedAsView( artifact ) {
|
|
966
970
|
return artifact && artifact.kind === 'entity' &&
|
|
967
|
-
!artifact
|
|
971
|
+
!artifact.$ignore &&
|
|
968
972
|
!artifact.abstract &&
|
|
969
973
|
((artifact.query || artifact.projection) && !hasAnnotationValue(artifact, '@cds.persistence.table')) &&
|
|
970
974
|
!hasAnnotationValue(artifact, '@cds.persistence.skip') &&
|
|
@@ -978,7 +982,7 @@ function isPersistedAsView( artifact ) {
|
|
|
978
982
|
*/
|
|
979
983
|
function isPersistedAsTable( artifact ) {
|
|
980
984
|
return artifact.kind === 'entity' &&
|
|
981
|
-
!artifact
|
|
985
|
+
!artifact.$ignore &&
|
|
982
986
|
!artifact.abstract &&
|
|
983
987
|
(!artifact.query && !artifact.projection || hasAnnotationValue(artifact, '@cds.persistence.table')) &&
|
|
984
988
|
!hasAnnotationValue(artifact, '@cds.persistence.skip') &&
|
|
@@ -102,8 +102,10 @@ function revealInternalProperties( model, nameOrPath ) {
|
|
|
102
102
|
_status: primOrString, // is a string anyway
|
|
103
103
|
$annotations: as => as.map( $annotation ),
|
|
104
104
|
$messageFunctions: () => '‹some functions›',
|
|
105
|
+
$functions: () => '‹some functions›',
|
|
106
|
+
$builtins: nameOrPath === '++' ? builtinsDictionary : () => '‹reveal with -R ++›',
|
|
105
107
|
};
|
|
106
|
-
uniqueId = 1;
|
|
108
|
+
uniqueId = -1;
|
|
107
109
|
return revealXsnPath(nameOrPath, model);
|
|
108
110
|
|
|
109
111
|
// Returns the desired artifact/dictionary in the XSN.
|
|
@@ -129,7 +131,7 @@ function revealInternalProperties( model, nameOrPath ) {
|
|
|
129
131
|
// `name.space/S/E/elements/a/kind/`
|
|
130
132
|
// `name.space/S/E/elements/a/type/scope/`
|
|
131
133
|
function revealXsnPath( path, xsn ) {
|
|
132
|
-
if (!path || path === '+')
|
|
134
|
+
if (!path || path === '+' || path === '++')
|
|
133
135
|
return reveal( xsn );
|
|
134
136
|
|
|
135
137
|
path = path.split('/');
|
|
@@ -173,7 +175,9 @@ function revealInternalProperties( model, nameOrPath ) {
|
|
|
173
175
|
if (!Array.isArray(deps))
|
|
174
176
|
return primOrString( deps );
|
|
175
177
|
return deps
|
|
176
|
-
.map( d =>
|
|
178
|
+
.map( d => (d.location
|
|
179
|
+
? `${ artifactIdentifier( d.art ) } @${ locationString( d.location ) }`
|
|
180
|
+
: artifactIdentifier( d.art )) );
|
|
177
181
|
}
|
|
178
182
|
|
|
179
183
|
function layerExtends( dict ) {
|
|
@@ -213,7 +217,13 @@ function revealInternalProperties( model, nameOrPath ) {
|
|
|
213
217
|
}
|
|
214
218
|
|
|
215
219
|
function artifactDictionary( node, parent ) {
|
|
216
|
-
if (
|
|
220
|
+
if (parent === model )
|
|
221
|
+
return dictionary( node ); // no dictionary or no definitions section
|
|
222
|
+
return builtinsDictionary( node );
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function builtinsDictionary( node, parent ) {
|
|
226
|
+
if (!node || typeof node !== 'object' || !model.definitions )
|
|
217
227
|
return dictionary( node ); // no dictionary or no definitions section
|
|
218
228
|
const dict = Object.create( Object.getPrototypeOf(node)
|
|
219
229
|
? NOT_A_DICTIONARY.prototype
|
|
@@ -237,7 +247,8 @@ function revealInternalProperties( model, nameOrPath ) {
|
|
|
237
247
|
? NOT_A_DICTIONARY.prototype
|
|
238
248
|
: Object.prototype );
|
|
239
249
|
for (const prop of Object.getOwnPropertyNames( node )) { // also non-enumerable
|
|
240
|
-
|
|
250
|
+
if (node !== model.definitions || nameOrPath === '++' || !node[prop].builtin)
|
|
251
|
+
r[prop] = reveal( node[prop], node, prop );
|
|
241
252
|
}
|
|
242
253
|
if (node[$inferred] && !node['[$inferred]'])
|
|
243
254
|
r['[$inferred]'] = node[$inferred];
|
|
@@ -275,8 +286,8 @@ function revealInternalProperties( model, nameOrPath ) {
|
|
|
275
286
|
|
|
276
287
|
const r = Object.create( Object.getPrototypeOf( node ) );
|
|
277
288
|
// property to recognize === objects
|
|
278
|
-
if (node.kind && node.__unique_id__ == null)
|
|
279
|
-
Object.defineProperty( node, '__unique_id__', { value:
|
|
289
|
+
if (node.kind && node.__unique_id__ == null && node.$effectiveSeqNo == null && !node.builtin)
|
|
290
|
+
Object.defineProperty( node, '__unique_id__', { value: uniqueId-- } );
|
|
280
291
|
|
|
281
292
|
for (const prop of Object.getOwnPropertyNames( node )) { // also non-enumerable
|
|
282
293
|
const func = transformers[prop] ||
|
|
@@ -287,8 +298,8 @@ function revealInternalProperties( model, nameOrPath ) {
|
|
|
287
298
|
}
|
|
288
299
|
|
|
289
300
|
function targetAspect( node, parent ) {
|
|
290
|
-
if (node.elements &&
|
|
291
|
-
Object.defineProperty( node, '__unique_id__', { value:
|
|
301
|
+
if (node.elements && node.__unique_id__ == null && node.$effectiveSeqNo == null)
|
|
302
|
+
Object.defineProperty( node, '__unique_id__', { value: uniqueId-- } );
|
|
292
303
|
return reveal( node, parent );
|
|
293
304
|
}
|
|
294
305
|
|
|
@@ -305,19 +316,16 @@ function array( node, fn ) {
|
|
|
305
316
|
}
|
|
306
317
|
|
|
307
318
|
function artifactIdentifier( node, parent ) {
|
|
319
|
+
if (!node)
|
|
320
|
+
return `${ node }`;
|
|
308
321
|
if (Array.isArray(node))
|
|
309
322
|
return node.map( a => artifactIdentifier( a, node ) );
|
|
310
|
-
if (uniqueId && node.__unique_id__ == null)
|
|
311
|
-
Object.defineProperty( node, '__unique_id__', { value:
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
outer = (node._outer.
|
|
316
|
-
// eslint-disable-next-line no-nested-ternary
|
|
317
|
-
: (node._outer.returns === node) ? `/returns${ outer }` // TODO returns now normal
|
|
318
|
-
// eslint-disable-next-line no-nested-ternary
|
|
319
|
-
: (node._outer.targetAspect === node) ? `/target${ outer }`
|
|
320
|
-
: `/returns/items${ outer }`;
|
|
323
|
+
if (uniqueId && node.__unique_id__ == null && node.$effectiveSeqNo == null && !node.builtin)
|
|
324
|
+
Object.defineProperty( node, '__unique_id__', { value: uniqueId-- } );
|
|
325
|
+
const outerNum = node.$effectiveSeqNo || node.__unique_id__;
|
|
326
|
+
let outer = outerNum != null ? `##${ outerNum }` : '';
|
|
327
|
+
if (node._outer) { // anon aspect in targetAspect | items
|
|
328
|
+
outer = (node._outer.targetAspect === node) ? `/target${ outer }` : `/items${ outer }`;
|
|
321
329
|
node = node._outer;
|
|
322
330
|
}
|
|
323
331
|
if (node === parent)
|
|
@@ -328,7 +336,7 @@ function artifactIdentifier( node, parent ) {
|
|
|
328
336
|
return '$magicVariables';
|
|
329
337
|
if (!node.name) {
|
|
330
338
|
try {
|
|
331
|
-
return `${ locationString( node.location ) || '' }##${
|
|
339
|
+
return `${ locationString( node.location ) || '' }##${ outerNum }`;
|
|
332
340
|
// return JSON.stringify(node);
|
|
333
341
|
}
|
|
334
342
|
catch (e) {
|
package/lib/model/sortViews.js
CHANGED
|
@@ -93,13 +93,12 @@ function _findWithXPointers( definitionsArray, x, _dependents, _dependencies ) {
|
|
|
93
93
|
module.exports = function sortViews({ sql, csn }) {
|
|
94
94
|
const { cleanup, _dependents, _dependencies } = setDependencies(csn);
|
|
95
95
|
const { layers, leftover } = sortTopologically(csn, _dependents, _dependencies);
|
|
96
|
-
cleanup.forEach(fn => fn());
|
|
97
96
|
if (leftover.length > 0)
|
|
98
97
|
throw new ModelError('Unable to build a correct dependency graph! Are there cycles?');
|
|
99
98
|
|
|
100
99
|
const result = [];
|
|
101
100
|
// keep the "artifact name" - needed for to.hdi sorting
|
|
102
|
-
layers.forEach(layer => layer.forEach(objName => result.push({ name: objName, sql: sql[objName] })));
|
|
101
|
+
layers.forEach(layer => layer.forEach(objName => result.push({ name: objName, sql: sql[objName], dependents: csn.definitions[objName][_dependents] })));
|
|
103
102
|
// attach sql artifacts which are not considered during the view sorting algorithm
|
|
104
103
|
// --> this is the case for "ALTER TABLE ADD CONSTRAINT" statements,
|
|
105
104
|
// because their identifiers are not part of the csn.definitions
|
|
@@ -107,5 +106,8 @@ module.exports = function sortViews({ sql, csn }) {
|
|
|
107
106
|
if (!result.some( o => o.name === name )) // not in result but in incoming sql
|
|
108
107
|
result.push({ name, sql: sqlString });
|
|
109
108
|
});
|
|
109
|
+
|
|
110
|
+
cleanup.forEach(fn => fn());
|
|
111
|
+
|
|
110
112
|
return result;
|
|
111
113
|
};
|
|
@@ -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;
|
|
@@ -239,6 +299,15 @@ function addedElements(entity, elements) {
|
|
|
239
299
|
};
|
|
240
300
|
}
|
|
241
301
|
|
|
302
|
+
function addedConstraint(entity, constraint, constraintName, constraintType) {
|
|
303
|
+
return {
|
|
304
|
+
extend: entity,
|
|
305
|
+
constraint,
|
|
306
|
+
constraintName,
|
|
307
|
+
constraintType,
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
242
311
|
function changedElement(element, otherElement) {
|
|
243
312
|
return {
|
|
244
313
|
old: otherElement,
|
|
@@ -246,6 +315,10 @@ function changedElement(element, otherElement) {
|
|
|
246
315
|
};
|
|
247
316
|
}
|
|
248
317
|
|
|
318
|
+
function hasReferentialConstraints(options) {
|
|
319
|
+
return options.src === 'sql' && (options.sqlDialect === 'postgres' || options.sqlDialect === 'hana' || options.sqlDialect === 'sqlite')
|
|
320
|
+
}
|
|
321
|
+
|
|
249
322
|
module.exports = {
|
|
250
323
|
compareModels,
|
|
251
324
|
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,7 +152,7 @@ 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
|
});
|
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)
|
|
@@ -102,12 +103,11 @@ optionProcessor
|
|
|
102
103
|
--beta-mode Enable all unsupported, incomplete (beta) features
|
|
103
104
|
--beta <list> Comma separated list of unsupported, incomplete (beta) features to use.
|
|
104
105
|
Valid values are:
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
optionalActionFunctionParameters
|
|
106
|
+
${
|
|
107
|
+
Object.keys(availableBetaFlags).sort()
|
|
108
|
+
.filter(flag => availableBetaFlags[flag])
|
|
109
|
+
.join('\n' + ' '.repeat(30))
|
|
110
|
+
}
|
|
111
111
|
--deprecated <list> Comma separated list of deprecated options.
|
|
112
112
|
Valid values are:
|
|
113
113
|
eagerPersistenceForGeneratedEntities
|