@sap/cds-compiler 4.3.2 → 4.4.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 +39 -0
- package/lib/api/main.js +14 -24
- package/lib/api/options.js +1 -0
- package/lib/api/trace.js +38 -0
- package/lib/base/location.js +46 -1
- package/lib/base/message-registry.js +68 -16
- package/lib/base/messages.js +8 -3
- package/lib/checks/.eslintrc.json +1 -0
- package/lib/checks/actionsFunctions.js +1 -1
- package/lib/checks/annotationsOData.js +2 -2
- package/lib/checks/selectItems.js +4 -1
- package/lib/compiler/assert-consistency.js +3 -2
- package/lib/compiler/base.js +1 -1
- package/lib/compiler/builtins.js +25 -1
- package/lib/compiler/checks.js +6 -5
- package/lib/compiler/define.js +12 -10
- package/lib/compiler/extend.js +44 -23
- package/lib/compiler/finalize-parse-cdl.js +1 -1
- package/lib/compiler/generate.js +70 -53
- package/lib/compiler/kick-start.js +7 -5
- package/lib/compiler/populate.js +31 -22
- package/lib/compiler/propagator.js +6 -2
- package/lib/compiler/resolve.js +52 -17
- package/lib/compiler/shared.js +85 -39
- package/lib/compiler/tweak-assocs.js +64 -23
- package/lib/compiler/utils.js +40 -23
- package/lib/edm/.eslintrc.json +2 -0
- package/lib/edm/EdmPrimitiveTypeDefinitions.js +260 -0
- package/lib/edm/annotations/edmJson.js +994 -0
- package/lib/edm/annotations/genericTranslation.js +82 -423
- package/lib/edm/annotations/vocabularyDefinitions.js +160 -0
- package/lib/edm/csn2edm.js +12 -5
- package/lib/edm/edm.js +14 -73
- package/lib/edm/edmPreprocessor.js +6 -0
- package/lib/gen/Dictionary.json +187 -16
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageLexer.interp +1 -1
- package/lib/gen/languageLexer.js +1129 -671
- package/lib/gen/languageParser.js +4285 -4283
- package/lib/json/from-csn.js +13 -18
- package/lib/json/to-csn.js +11 -6
- package/lib/language/antlrParser.js +0 -1
- package/lib/language/docCommentParser.js +1 -1
- package/lib/language/errorStrategy.js +95 -30
- package/lib/language/genericAntlrParser.js +21 -1
- package/lib/main.js +13 -3
- package/lib/model/csnRefs.js +42 -8
- package/lib/model/csnUtils.js +14 -2
- package/lib/model/enrichCsn.js +33 -5
- package/lib/model/revealInternalProperties.js +5 -0
- package/lib/modelCompare/compare.js +76 -14
- package/lib/modelCompare/utils/filter.js +19 -12
- package/lib/optionProcessor.js +2 -0
- package/lib/render/.eslintrc.json +1 -1
- package/lib/render/manageConstraints.js +1 -0
- package/lib/render/toHdbcds.js +3 -0
- package/lib/render/toRename.js +3 -1
- package/lib/render/toSql.js +46 -92
- package/lib/render/utils/common.js +76 -0
- package/lib/render/utils/delta.js +17 -3
- package/lib/sql-identifier.js +1 -1
- package/lib/transform/db/.eslintrc.json +1 -0
- package/lib/transform/db/applyTransformations.js +30 -4
- package/lib/transform/db/associations.js +22 -10
- package/lib/transform/db/backlinks.js +6 -2
- package/lib/transform/db/expansion.js +2 -2
- package/lib/transform/db/transformExists.js +13 -39
- package/lib/transform/draft/db.js +14 -3
- package/lib/transform/draft/odata.js +5 -18
- package/lib/transform/effective/associations.js +46 -15
- package/lib/transform/effective/main.js +7 -2
- package/lib/transform/effective/misc.js +43 -24
- package/lib/transform/effective/queries.js +20 -22
- package/lib/transform/effective/types.js +6 -2
- package/lib/transform/forOdata.js +10 -3
- package/lib/transform/localized.js +1 -1
- package/lib/transform/parseExpr.js +73 -21
- package/lib/transform/translateAssocsToJoins.js +22 -15
- package/lib/utils/term.js +2 -2
- package/package.json +2 -1
package/lib/model/enrichCsn.js
CHANGED
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
const { csnRefs, artifactProperties } = require('./csnRefs');
|
|
43
43
|
const { locationString } = require('../base/location');
|
|
44
44
|
const { CompilerAssertion } = require('../base/error');
|
|
45
|
+
const { isAnnotationExpression } = require('../compiler/builtins');
|
|
45
46
|
const shuffleGen = require('../base/shuffle');
|
|
46
47
|
|
|
47
48
|
function enrichCsn( csn, options = {} ) {
|
|
@@ -61,7 +62,8 @@ function enrichCsn( csn, options = {} ) {
|
|
|
61
62
|
includes: simpleRef,
|
|
62
63
|
$origin,
|
|
63
64
|
// TODO: excluding
|
|
64
|
-
'@':
|
|
65
|
+
'@': assignment,
|
|
66
|
+
$: () => { /* ignore properties like $location for performance */ },
|
|
65
67
|
};
|
|
66
68
|
// options.enrichCsn = 'DEBUG';
|
|
67
69
|
let $$cacheObjectNumber = 0; // for debugging
|
|
@@ -131,9 +133,30 @@ function enrichCsn( csn, options = {} ) {
|
|
|
131
133
|
csnPath.pop();
|
|
132
134
|
}
|
|
133
135
|
|
|
136
|
+
function assignment( parent, prop, obj ) {
|
|
137
|
+
if (!obj || typeof obj !== 'object' || !{}.propertyIsEnumerable.call( parent, prop ))
|
|
138
|
+
return;
|
|
139
|
+
|
|
140
|
+
csnPath.push( prop );
|
|
141
|
+
if (Array.isArray(obj)) {
|
|
142
|
+
obj.forEach( (n, i) => assignment( obj, i, n ) );
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
const record = !isAnnotationExpression( obj ) && assignment;
|
|
146
|
+
// is record without `=` and other expression property
|
|
147
|
+
for (const name of Object.getOwnPropertyNames( obj ) ) {
|
|
148
|
+
const trans = record || transformers[name] || transformers[name.charAt(0)] || standard;
|
|
149
|
+
trans( obj, name, obj[name] );
|
|
150
|
+
}
|
|
151
|
+
if (!record && obj.$parens)
|
|
152
|
+
reveal( obj, '$parens', obj.$parens );
|
|
153
|
+
}
|
|
154
|
+
csnPath.pop();
|
|
155
|
+
}
|
|
156
|
+
|
|
134
157
|
function refLocation( art ) {
|
|
135
158
|
if (!art || typeof art !== 'object' || Array.isArray( art )) {
|
|
136
|
-
if (
|
|
159
|
+
if (catchRefError()) {
|
|
137
160
|
return (typeof art === 'string')
|
|
138
161
|
? `<illegal ref = ${ art }>`
|
|
139
162
|
: `<illegal ref: ${ typeof art }>`;
|
|
@@ -144,14 +167,14 @@ function enrichCsn( csn, options = {} ) {
|
|
|
144
167
|
return art.$location;
|
|
145
168
|
}
|
|
146
169
|
|
|
147
|
-
if (
|
|
170
|
+
if (catchRefError())
|
|
148
171
|
return `<${ Object.keys( art ).join('+') }+!$location>`;
|
|
149
172
|
throw new CompilerAssertion( 'Reference to object without $location' );
|
|
150
173
|
}
|
|
151
174
|
|
|
152
175
|
function simpleRef( parent, prop, ref ) {
|
|
153
176
|
// try {
|
|
154
|
-
const notFound = (
|
|
177
|
+
const notFound = (catchRefError()) ? null : undefined;
|
|
155
178
|
if (Array.isArray( ref )) {
|
|
156
179
|
parent[`_${ prop }`] = ref.map( r => refLocation( artifactRef( r, notFound ) ) );
|
|
157
180
|
}
|
|
@@ -220,7 +243,7 @@ function enrichCsn( csn, options = {} ) {
|
|
|
220
243
|
}
|
|
221
244
|
|
|
222
245
|
function handleError( callback ) {
|
|
223
|
-
if (
|
|
246
|
+
if (!catchRefError())
|
|
224
247
|
return callback();
|
|
225
248
|
try {
|
|
226
249
|
return callback();
|
|
@@ -335,6 +358,11 @@ function enrichCsn( csn, options = {} ) {
|
|
|
335
358
|
setLocations( node[name], isMember || name, loc );
|
|
336
359
|
}
|
|
337
360
|
}
|
|
361
|
+
|
|
362
|
+
function catchRefError() {
|
|
363
|
+
return !options.testMode || // false &&
|
|
364
|
+
csnPath.some( p => typeof p === 'string' && p.charAt(0) === '@');
|
|
365
|
+
}
|
|
338
366
|
}
|
|
339
367
|
|
|
340
368
|
|
|
@@ -104,6 +104,8 @@ function revealInternalProperties( model, nameOrPath ) {
|
|
|
104
104
|
$messageFunctions: () => '‹some functions›',
|
|
105
105
|
$functions: () => '‹some functions›',
|
|
106
106
|
$builtins: nameOrPath === '++' ? builtinsDictionary : () => '‹reveal with -R ++›',
|
|
107
|
+
tokenStream: ts => `‹${ ts?.tokens?.length ?? '?' } tokens›`,
|
|
108
|
+
parseListener: _ => '‹parseListener›',
|
|
107
109
|
};
|
|
108
110
|
uniqueId = -1;
|
|
109
111
|
return revealXsnPath(nameOrPath, model);
|
|
@@ -292,6 +294,7 @@ function revealInternalProperties( model, nameOrPath ) {
|
|
|
292
294
|
}
|
|
293
295
|
|
|
294
296
|
function targetAspect( node, parent ) {
|
|
297
|
+
// TODO: avoid repeated display of same target aspect (includes)
|
|
295
298
|
if (node.elements && node.__unique_id__ == null && node.$effectiveSeqNo == null)
|
|
296
299
|
Object.defineProperty( node, '__unique_id__', { value: uniqueId-- } );
|
|
297
300
|
return reveal( node, parent );
|
|
@@ -339,6 +342,8 @@ function artifactIdentifier( node, parent ) {
|
|
|
339
342
|
}
|
|
340
343
|
switch (node.kind) {
|
|
341
344
|
case undefined:
|
|
345
|
+
if (node.name.id === '$self' && node.location.file === '')
|
|
346
|
+
return `type:${ quoted( '$self' ) }##0`;
|
|
342
347
|
return (node._artifact && node._artifact.kind)
|
|
343
348
|
? artifactIdentifier( node._artifact )
|
|
344
349
|
: JSON.stringify(node.name);
|
|
@@ -7,8 +7,7 @@ const {
|
|
|
7
7
|
isPersistedAsTable,
|
|
8
8
|
isPersistedAsView
|
|
9
9
|
} = require('../model/csnUtils');
|
|
10
|
-
const {
|
|
11
|
-
const { forEachKey } = require('../utils/objectUtils');
|
|
10
|
+
const { forEachKey, forEach } = require('../utils/objectUtils');
|
|
12
11
|
|
|
13
12
|
// used to mark a view as changed so we know to drop-create it
|
|
14
13
|
const isChanged = Symbol('Marks a view as changed');
|
|
@@ -33,6 +32,7 @@ function compareModels(beforeModel, afterModel, options) {
|
|
|
33
32
|
returnObj.extensions = [];
|
|
34
33
|
returnObj.migrations = []; // element changes/removals or changes of entity properties
|
|
35
34
|
returnObj.unchangedConstraints = new Set();
|
|
35
|
+
returnObj.changedPrimaryKeys = [];
|
|
36
36
|
|
|
37
37
|
// There is currently no use in knowing the added entities only. If this changes, hand in `addedEntities` to `getArtifactComparator` below.
|
|
38
38
|
forEachDefinition(afterModel, getExtensionAndMigrations(beforeModel, options, returnObj));
|
|
@@ -74,21 +74,43 @@ function validateCsnVersions(beforeModel, afterModel, options) {
|
|
|
74
74
|
* @param {object} returnObj
|
|
75
75
|
* @returns {function}
|
|
76
76
|
*/
|
|
77
|
-
function getExtensionAndMigrations(beforeModel, options, { extensions, migrations, unchangedConstraints }) {
|
|
77
|
+
function getExtensionAndMigrations(beforeModel, options, { extensions, migrations, unchangedConstraints, changedPrimaryKeys }) {
|
|
78
78
|
return function compareArtifacts(artifact, name) {
|
|
79
|
+
let hasPrimaryKeyChange = false;
|
|
80
|
+
|
|
79
81
|
function addElements() {
|
|
80
82
|
const elements = {};
|
|
81
|
-
|
|
83
|
+
const keysNow = [];
|
|
84
|
+
forEachMember(artifact, (element, eName) => {
|
|
85
|
+
getElementComparator(otherArtifact, elements)(element, eName);
|
|
86
|
+
if(element.key)
|
|
87
|
+
keysNow.push(eName);
|
|
88
|
+
}, [ 'definitions', name ], true, { elementsOnly: true });
|
|
89
|
+
|
|
90
|
+
// Only do this check for to.hdi.migration - the order only "bites" us when doing .hdbmigrationtable as the end-check against the intended
|
|
91
|
+
// 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') {
|
|
93
|
+
const keysOther = [];
|
|
94
|
+
forEachMember(otherArtifact, (element, eName) => {
|
|
95
|
+
if(element.key)
|
|
96
|
+
keysOther.push(eName);
|
|
97
|
+
}, [ 'definitions', name ], true, { elementsOnly: true });
|
|
98
|
+
|
|
99
|
+
if(keysNow.join(',') !== keysOther.join(','))
|
|
100
|
+
hasPrimaryKeyChange = true;
|
|
101
|
+
}
|
|
102
|
+
|
|
82
103
|
if (Object.keys(elements).length > 0) {
|
|
83
|
-
|
|
104
|
+
const added = addedElements(name, elements);
|
|
105
|
+
if(!hasPrimaryKeyChange)
|
|
106
|
+
forEach(added.elements, (_name, element) => {
|
|
107
|
+
if(element.key && !element.target)
|
|
108
|
+
hasPrimaryKeyChange = true;
|
|
109
|
+
});
|
|
110
|
+
extensions.push(added);
|
|
84
111
|
}
|
|
85
112
|
}
|
|
86
113
|
function changePropsOrRemoveOrChangeElements() {
|
|
87
|
-
const relevantProperties = [
|
|
88
|
-
{ name: 'doc' },
|
|
89
|
-
{ name: '@sql.prepend' },
|
|
90
|
-
{ name: '@sql.append' },
|
|
91
|
-
];
|
|
92
114
|
const changedProperties = {};
|
|
93
115
|
|
|
94
116
|
const removedElements = {};
|
|
@@ -96,9 +118,9 @@ function getExtensionAndMigrations(beforeModel, options, { extensions, migration
|
|
|
96
118
|
|
|
97
119
|
const migration = { migrate: name };
|
|
98
120
|
|
|
99
|
-
relevantProperties.forEach(prop => {
|
|
100
|
-
if (artifact[prop
|
|
101
|
-
changedProperties[prop
|
|
121
|
+
Object.keys(relevantProperties).forEach(prop => {
|
|
122
|
+
if (artifact[prop] !== otherArtifact[prop]) {
|
|
123
|
+
changedProperties[prop] = changedElement(artifact[prop], otherArtifact[prop] || null);
|
|
102
124
|
}
|
|
103
125
|
});
|
|
104
126
|
|
|
@@ -156,11 +178,25 @@ function getExtensionAndMigrations(beforeModel, options, { extensions, migration
|
|
|
156
178
|
forEachMember(otherArtifact, getElementComparator(artifact, removedElements), [ 'definitions', name ], true, { elementsOnly: true });
|
|
157
179
|
if (Object.keys(removedElements).length > 0) {
|
|
158
180
|
migration.remove = removedElements;
|
|
181
|
+
if(!hasPrimaryKeyChange)
|
|
182
|
+
forEach(removedElements, (_name, change) => {
|
|
183
|
+
if(change.key && !change.target)
|
|
184
|
+
hasPrimaryKeyChange = true;
|
|
185
|
+
});
|
|
159
186
|
}
|
|
160
187
|
|
|
161
188
|
forEachMember(artifact, getElementComparator(otherArtifact, null, changedElements), [ 'definitions', name ], true, { elementsOnly: true });
|
|
162
189
|
if (Object.keys(changedElements).length > 0) {
|
|
163
190
|
migration.change = changedElements;
|
|
191
|
+
if(!hasPrimaryKeyChange)
|
|
192
|
+
forEach(changedElements, (_name, change) => {
|
|
193
|
+
if((change.old.key || change.new.key) && !change.new.target && !change.old.target) {
|
|
194
|
+
// 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
|
|
195
|
+
if(options.sqlDialect === 'hana' && options.src === 'hdi' || (!change.old.key || !change.new.key)) {
|
|
196
|
+
hasPrimaryKeyChange = true;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
});
|
|
164
200
|
}
|
|
165
201
|
|
|
166
202
|
if (migration.properties || migration.remove || migration.change || migration.removeConstraints) {
|
|
@@ -193,6 +229,9 @@ function getExtensionAndMigrations(beforeModel, options, { extensions, migration
|
|
|
193
229
|
|
|
194
230
|
addElements();
|
|
195
231
|
changePropsOrRemoveOrChangeElements();
|
|
232
|
+
if(hasPrimaryKeyChange) {
|
|
233
|
+
changedPrimaryKeys.push(name);
|
|
234
|
+
}
|
|
196
235
|
};
|
|
197
236
|
}
|
|
198
237
|
/**
|
|
@@ -317,6 +356,13 @@ function deepEqual(a, b, include = () => true, depth = 0) {
|
|
|
317
356
|
: a === b;
|
|
318
357
|
}
|
|
319
358
|
|
|
359
|
+
|
|
360
|
+
const relevantProperties = {
|
|
361
|
+
'doc': true,
|
|
362
|
+
'@sql.prepend': true,
|
|
363
|
+
'@sql.append': true
|
|
364
|
+
};
|
|
365
|
+
|
|
320
366
|
/**
|
|
321
367
|
* Returns whether any type parameters differ between two given elements. Ignores whether types themselves differ (`type` property).
|
|
322
368
|
* @param element {object} an element
|
|
@@ -324,7 +370,23 @@ function deepEqual(a, b, include = () => true, depth = 0) {
|
|
|
324
370
|
* @returns {boolean}
|
|
325
371
|
*/
|
|
326
372
|
function typeParametersChanged(element, otherElement) {
|
|
327
|
-
|
|
373
|
+
const checked = new Set();
|
|
374
|
+
for (const key in element) {
|
|
375
|
+
if (Object.prototype.hasOwnProperty.call(element, key))
|
|
376
|
+
if((!key.startsWith('@') || relevantProperties[key]) && key !== 'type') {
|
|
377
|
+
checked.add(key);
|
|
378
|
+
if(!deepEqual(element[key], otherElement[key]))
|
|
379
|
+
return true;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
for (const key in otherElement) {
|
|
384
|
+
if (Object.prototype.hasOwnProperty.call(otherElement, key))
|
|
385
|
+
if((!key.startsWith('@') || relevantProperties[key]) && key !== 'type' && !checked.has(key))
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return false;
|
|
328
390
|
}
|
|
329
391
|
|
|
330
392
|
function addedElements(entity, elements) {
|
|
@@ -38,6 +38,9 @@ module.exports = {
|
|
|
38
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
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
40
|
delete constraintRemovals[name];
|
|
41
|
+
},
|
|
42
|
+
function primaryKey() {
|
|
43
|
+
return false;
|
|
41
44
|
}
|
|
42
45
|
),
|
|
43
46
|
postgres: getFilterObject('postgres'),
|
|
@@ -46,7 +49,7 @@ module.exports = {
|
|
|
46
49
|
csn: filterCsn,
|
|
47
50
|
};
|
|
48
51
|
|
|
49
|
-
function getFilterObject( dialect, extensionCallback, migrationCallback, removeConstraintsCallback ) {
|
|
52
|
+
function getFilterObject( dialect, extensionCallback, migrationCallback, removeConstraintsCallback, primaryKeyCallback ) {
|
|
50
53
|
return {
|
|
51
54
|
// will be called with a simple Array.filter, as we need to filter constraint `ADD` for SQLite
|
|
52
55
|
extension: ({
|
|
@@ -66,14 +69,12 @@ function getFilterObject( dialect, extensionCallback, migrationCallback, removeC
|
|
|
66
69
|
return returnValue;
|
|
67
70
|
},
|
|
68
71
|
// will be called with a Array.forEach
|
|
69
|
-
migration: ({
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
forEach(remove, (name) => {
|
|
73
|
-
error('def-unsupported-element-drop', [ 'definitions', migrate, 'elements', name ], {}, 'Dropping elements is not supported');
|
|
72
|
+
migration: (migrations, { error, warning, message }) => {
|
|
73
|
+
forEach(migrations.remove, (name) => {
|
|
74
|
+
error('def-unsupported-element-drop', [ 'definitions', migrations.migrate, 'elements', name ], {}, 'Dropping elements is not supported');
|
|
74
75
|
});
|
|
75
|
-
forEach(change, (name, migration) => {
|
|
76
|
-
const loc = [ 'definitions', migrate, 'elements', name ];
|
|
76
|
+
forEach(migrations.change, (name, migration) => {
|
|
77
|
+
const loc = [ 'definitions', migrations.migrate, 'elements', name ];
|
|
77
78
|
if (migration.new.type === migration.old.type && migration.new.length < migration.old.length)
|
|
78
79
|
error('type-unsupported-length-change', loc, { id: name }, 'Changed element $(ID) is a length reduction and is not supported');
|
|
79
80
|
else if (migration.new.type === migration.old.type && migration.new.scale !== migration.old.scale)
|
|
@@ -83,9 +84,9 @@ function getFilterObject( dialect, extensionCallback, migrationCallback, removeC
|
|
|
83
84
|
else if (migration.new.type !== migration.old.type && typeChangeIsNotCompatible(dialect, migration.old.type, migration.new.type))
|
|
84
85
|
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
86
|
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' } );
|
|
87
|
+
message('type-unsupported-key-change', [ 'definitions', migrations.migrate, 'elements', name ], { id: name, '#': 'changed' } );
|
|
87
88
|
else if (migrationCallback)
|
|
88
|
-
migrationCallback(migrate, name, migration, change, error);
|
|
89
|
+
migrationCallback(migrations.migrate, name, migration, migrations.change, error);
|
|
89
90
|
|
|
90
91
|
// TODO: precision/scale growth
|
|
91
92
|
});
|
|
@@ -93,8 +94,8 @@ function getFilterObject( dialect, extensionCallback, migrationCallback, removeC
|
|
|
93
94
|
if (removeConstraintsCallback) {
|
|
94
95
|
const constraintTypes = [ 'unique', 'referential' ];
|
|
95
96
|
constraintTypes.forEach((constraintType) => {
|
|
96
|
-
forEach(removeConstraints?.[constraintType], (name, constraint) => {
|
|
97
|
-
removeConstraintsCallback(removeConstraints[constraintType], name, constraint, warning);
|
|
97
|
+
forEach(migrations.removeConstraints?.[constraintType], (name, constraint) => {
|
|
98
|
+
removeConstraintsCallback(migrations.removeConstraints[constraintType], name, constraint, warning);
|
|
98
99
|
});
|
|
99
100
|
});
|
|
100
101
|
}
|
|
@@ -103,6 +104,12 @@ function getFilterObject( dialect, extensionCallback, migrationCallback, removeC
|
|
|
103
104
|
if (isPersistedAsTable(artifact))
|
|
104
105
|
error('def-unsupported-table-drop', [ 'definitions', artifactName ], 'Dropping tables is not supported');
|
|
105
106
|
},
|
|
107
|
+
changedPrimaryKeys: (changedPrimaryKeyArtifactName) => {
|
|
108
|
+
if (primaryKeyCallback)
|
|
109
|
+
return primaryKeyCallback(changedPrimaryKeyArtifactName);
|
|
110
|
+
|
|
111
|
+
return true;
|
|
112
|
+
},
|
|
106
113
|
};
|
|
107
114
|
}
|
|
108
115
|
|
package/lib/optionProcessor.js
CHANGED
|
@@ -227,6 +227,7 @@ optionProcessor.command('O, toOdata')
|
|
|
227
227
|
.option(' --odata-foreign-keys')
|
|
228
228
|
.option(' --odata-v2-partial-constr')
|
|
229
229
|
.option(' --odata-vocabularies <list>')
|
|
230
|
+
.option(' --odata-open-type')
|
|
230
231
|
.option('-c, --csn')
|
|
231
232
|
.option('-f, --odata-format <format>', ['flat', 'structured'])
|
|
232
233
|
.option('-n, --sql-mapping <style>', ['plain', 'quoted', 'hdbcds'], { aliases: [ '--names' ] })
|
|
@@ -259,6 +260,7 @@ optionProcessor.command('O, toOdata')
|
|
|
259
260
|
(Not spec compliant and V2 only)
|
|
260
261
|
--odata-vocabularies <list> JSON array of adhoc vocabulary definitions
|
|
261
262
|
{ prefix: { alias, ns, uri }, ... }
|
|
263
|
+
--odata-open-type Renders all structured types as OpenType=true, if not annotated otherwise.
|
|
262
264
|
-n, --sql-mapping <style> Annotate artifacts and elements with "@cds.persistence.name", which is
|
|
263
265
|
the corresponding database name (see "--sql-mapping" for "toHana or "toSql")
|
|
264
266
|
plain : (default) Names in uppercase and flattened with underscores
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"plugins": ["sonarjs"],
|
|
3
3
|
"extends": ["../../.eslintrc-ydkjsi.json", "plugin:sonarjs/recommended"],
|
|
4
4
|
"rules": {
|
|
5
|
+
"cds-compiler/message-no-quotes": "off",
|
|
5
6
|
"prefer-const": "error",
|
|
6
7
|
"quotes": ["error", "single", "avoid-escape"],
|
|
7
8
|
"prefer-template": "error",
|
|
@@ -11,7 +12,6 @@
|
|
|
11
12
|
"sonarjs/no-nested-template-literals": "off",
|
|
12
13
|
"template-curly-spacing":["error", "never"],
|
|
13
14
|
"class-methods-use-this": "off",
|
|
14
|
-
// Who cares - just very whiny and in the way
|
|
15
15
|
"complexity": "off",
|
|
16
16
|
"max-len": "off",
|
|
17
17
|
"no-shadow": "warn"
|
|
@@ -67,6 +67,7 @@ function _transformSqlOptions( model, options ) {
|
|
|
67
67
|
const { warning, error } = makeMessageFunction(model, options, 'to.sql');
|
|
68
68
|
|
|
69
69
|
optionProcessor.verifyOptions(options, 'toSql', true)
|
|
70
|
+
// eslint-disable-next-line cds-compiler/message-template-string
|
|
70
71
|
.forEach(complaint => warning(null, null, `${complaint}`));
|
|
71
72
|
|
|
72
73
|
if (options.sqlDialect !== 'hana') {
|
package/lib/render/toHdbcds.js
CHANGED
|
@@ -1336,6 +1336,9 @@ function toHdbcdsSource( csn, options ) {
|
|
|
1336
1336
|
|
|
1337
1337
|
else if (x.ref[1] === 'locale')
|
|
1338
1338
|
return 'SESSION_CONTEXT(\'LOCALE\')';
|
|
1339
|
+
|
|
1340
|
+
else if (x.ref[1] === 'tenant')
|
|
1341
|
+
return 'SESSION_CONTEXT(\'TENANT\')';
|
|
1339
1342
|
}
|
|
1340
1343
|
else if (x.ref[0] === '$at' || x.ref[0] === '$valid') {
|
|
1341
1344
|
if (x.ref[1] === 'from')
|
package/lib/render/toRename.js
CHANGED
|
@@ -34,7 +34,9 @@ function toRename( inputCsn, options ) {
|
|
|
34
34
|
options = Object.assign({ sqlMapping: 'hdbcds', sqlDialect: 'hana' }, options);
|
|
35
35
|
|
|
36
36
|
// Verify options
|
|
37
|
-
optionProcessor.verifyOptions(options, 'toRename')
|
|
37
|
+
optionProcessor.verifyOptions(options, 'toRename')
|
|
38
|
+
// eslint-disable-next-line cds-compiler/message-template-string
|
|
39
|
+
.forEach(complaint => warning(null, null, `${complaint}`));
|
|
38
40
|
checkCSNVersion(inputCsn, options);
|
|
39
41
|
|
|
40
42
|
// Let users know that this is internal
|
package/lib/render/toSql.js
CHANGED
|
@@ -10,6 +10,7 @@ const { forEach, forEachValue, forEachKey } = require('../utils/objectUtils');
|
|
|
10
10
|
const {
|
|
11
11
|
renderFunc, cdsToSqlTypes, getHanaComment, hasHanaComment,
|
|
12
12
|
getSqlSnippets, createExpressionRenderer, withoutCast,
|
|
13
|
+
variableForDialect,
|
|
13
14
|
} = require('./utils/common');
|
|
14
15
|
const {
|
|
15
16
|
getDeltaRenderer,
|
|
@@ -106,7 +107,7 @@ function toSqlDdl( csn, options ) {
|
|
|
106
107
|
error, warning, info, throwWithAnyError,
|
|
107
108
|
} = makeMessageFunction(csn, options, 'to.sql');
|
|
108
109
|
const { quoteSqlId, prepareIdentifier, renderArtifactName } = getIdentifierUtils(csn, options);
|
|
109
|
-
|
|
110
|
+
const reportedMissingReplacements = Object.create(null);
|
|
110
111
|
|
|
111
112
|
const exprRenderer = createExpressionRenderer({
|
|
112
113
|
// FIXME: For the sake of simplicity, we should get away from all this uppercasing in toSql
|
|
@@ -157,6 +158,7 @@ function toSqlDdl( csn, options ) {
|
|
|
157
158
|
renderStringForSql,
|
|
158
159
|
activateAlterMode,
|
|
159
160
|
getHanaComment,
|
|
161
|
+
renderExpr,
|
|
160
162
|
});
|
|
161
163
|
|
|
162
164
|
// FIXME: Currently requires 'options.forHana', because it can only render HANA-ish SQL dialect
|
|
@@ -197,6 +199,15 @@ function toSqlDdl( csn, options ) {
|
|
|
197
199
|
for (const artifactName in csn.deletions)
|
|
198
200
|
renderArtifactDeletionInto(artifactName, csn.deletions[artifactName], mainResultObj);
|
|
199
201
|
|
|
202
|
+
|
|
203
|
+
if (csn.changedPrimaryKeys && (options.sqlDialect === 'hana' || isBetaEnabled(options, 'sqlExtensions'))) {
|
|
204
|
+
csn.changedPrimaryKeys = options.testMode ? sortCsn(csn.changedPrimaryKeys) : csn.changedPrimaryKeys;
|
|
205
|
+
csn.changedPrimaryKeys.forEach((artifactName) => {
|
|
206
|
+
const drop = render.dropKey(artifactName);
|
|
207
|
+
addMigration(mainResultObj, artifactName, true, render.concat(...drop));
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
200
211
|
// Render each artifact extension
|
|
201
212
|
// Only SAP HANA SQL is currently supported.
|
|
202
213
|
// Note that extensions may contain new elements referenced in migrations, thus should be compiled first.
|
|
@@ -223,11 +234,19 @@ function toSqlDdl( csn, options ) {
|
|
|
223
234
|
// There is no "migrations" property in client CSN, so for better locations, use
|
|
224
235
|
// a path to the definition.
|
|
225
236
|
const env = new SqlRenderEnvironment({ path: [ 'definitions', artifactName ] });
|
|
226
|
-
renderArtifactMigrationInto(artifactName, migration, mainResultObj, env);
|
|
237
|
+
renderArtifactMigrationInto(artifactName, csn.definitions[artifactName], migration, mainResultObj, env);
|
|
227
238
|
}
|
|
228
239
|
}
|
|
229
240
|
}
|
|
230
241
|
|
|
242
|
+
if (csn.changedPrimaryKeys && (options.sqlDialect === 'hana' || isBetaEnabled(options, 'sqlExtensions'))) {
|
|
243
|
+
csn.changedPrimaryKeys = options.testMode ? sortCsn(csn.changedPrimaryKeys) : csn.changedPrimaryKeys;
|
|
244
|
+
csn.changedPrimaryKeys.forEach((artifactName) => {
|
|
245
|
+
const add = render.addKey(artifactName, csn.definitions[artifactName].elements);
|
|
246
|
+
addMigration(mainResultObj, artifactName, true, render.concat(...add));
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
231
250
|
// trigger artifact and element name checks
|
|
232
251
|
definitionsDuplicateChecker.check(error, options);
|
|
233
252
|
extensionsDuplicateChecker.check(error);
|
|
@@ -360,7 +379,7 @@ function toSqlDdl( csn, options ) {
|
|
|
360
379
|
|
|
361
380
|
// Render an artifact migration into the appropriate dictionary of 'resultObj'.
|
|
362
381
|
// Only SAP HANA SQL is currently supported.
|
|
363
|
-
function renderArtifactMigrationInto( artifactName, migration, resultObj, env ) {
|
|
382
|
+
function renderArtifactMigrationInto( artifactName, artifact, migration, resultObj, env ) {
|
|
364
383
|
function reducesTypeSize( def ) {
|
|
365
384
|
// HANA does not allow decreasing the value of any of those type parameters.
|
|
366
385
|
return def.old.type === def.new.type &&
|
|
@@ -501,9 +520,8 @@ function toSqlDdl( csn, options ) {
|
|
|
501
520
|
: render.addColumnsFromElementsObj(artifactName, { [eltName]: def.new }, env);
|
|
502
521
|
addMigration(resultObj, artifactName, true, render.concat(...drop, ...add));
|
|
503
522
|
}
|
|
504
|
-
else {
|
|
505
|
-
|
|
506
|
-
addMigration(resultObj, artifactName, false, render.alterColumns(artifactName, sqlId, def, eltStrNew));
|
|
523
|
+
else { // Lossless change: no associations directly affected, no size reduction.
|
|
524
|
+
addMigration(resultObj, artifactName, false, render.alterColumns(artifactName, sqlId, def, eltStrNew, eltName, activateAlterMode(env, 'migration')));
|
|
507
525
|
}
|
|
508
526
|
}
|
|
509
527
|
}
|
|
@@ -649,12 +667,6 @@ function toSqlDdl( csn, options ) {
|
|
|
649
667
|
const associations = render.addAssociations(artifactName, extElements, env);
|
|
650
668
|
if (elements.length + associations.length > 0)
|
|
651
669
|
addMigration(resultObj, artifactName, false, [ ...elements, ...associations ]);
|
|
652
|
-
|
|
653
|
-
if (Object.values(extElements).some(elt => elt.key)) {
|
|
654
|
-
const drop = render.dropKey(artifactName);
|
|
655
|
-
const add = render.addKey(artifactName, artifactElements);
|
|
656
|
-
addMigration(resultObj, artifactName, true, render.concat(...drop, ...add));
|
|
657
|
-
}
|
|
658
670
|
}
|
|
659
671
|
|
|
660
672
|
function addMigration( resultObj, artifactName, drop, sqlArray, description ) {
|
|
@@ -1529,95 +1541,37 @@ function toSqlDdl( csn, options ) {
|
|
|
1529
1541
|
* @returns {string|null} Null in case of an invalid second path step
|
|
1530
1542
|
*/
|
|
1531
1543
|
function render$user( x ) {
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
warning(null, null, 'The "$user" variable is not supported. Use option "variableReplacements" to specify a value for "$user.id"');
|
|
1545
|
-
reportedMissingUserReplacement = true;
|
|
1546
|
-
}
|
|
1547
|
-
return '\'$user.id\'';
|
|
1548
|
-
}
|
|
1549
|
-
else if (x.ref[1] === 'locale') {
|
|
1550
|
-
if (options.sqlDialect === 'hana')
|
|
1551
|
-
return 'SESSION_CONTEXT(\'LOCALE\')';
|
|
1552
|
-
else if (options.sqlDialect === 'postgres')
|
|
1553
|
-
return 'current_setting(\'cap.locale\')';
|
|
1554
|
-
else if (options.betterSqliteSessionVariables && options.sqlDialect === 'sqlite')
|
|
1555
|
-
return 'session_context( \'$user.locale\' )';
|
|
1556
|
-
else if (options.sqlDialect === 'h2')
|
|
1557
|
-
return '@locale';
|
|
1558
|
-
|
|
1559
|
-
return '\'en\''; // default language
|
|
1544
|
+
if (x.ref.length > 2 || typeof x.ref[1] !== 'string')
|
|
1545
|
+
return null; // `$user` can only have two path steps
|
|
1546
|
+
|
|
1547
|
+
const name = `$user.${x.ref[1]}`;
|
|
1548
|
+
const result = variableForDialect(options, name);
|
|
1549
|
+
if (result)
|
|
1550
|
+
return result;
|
|
1551
|
+
|
|
1552
|
+
if (!reportedMissingReplacements[name]) {
|
|
1553
|
+
reportedMissingReplacements[name] = true;
|
|
1554
|
+
warning('ref-unsupported-variable', null, { name, option: 'variableReplacements' },
|
|
1555
|
+
'Variable $(NAME) is not supported. Use option $(OPTION) to specify a value for $(NAME)');
|
|
1560
1556
|
}
|
|
1561
|
-
|
|
1562
|
-
return null;
|
|
1557
|
+
return `'${name}'`;
|
|
1563
1558
|
}
|
|
1564
1559
|
|
|
1565
1560
|
/**
|
|
1566
|
-
* For a given reference starting with $at, render a 'current_timestamp' literal for plain.
|
|
1567
|
-
* For the sql-dialect hana, we render the TO_TIMESTAMP(SESSION_CONTEXT(..)) function.
|
|
1568
|
-
*
|
|
1569
|
-
*
|
|
1570
|
-
* For sqlite, we render the string-format-time (strftime) function.
|
|
1571
|
-
* Because the format of `current_timestamp` is like that: '2021-05-14 09:17:19' whereas
|
|
1572
|
-
* the format for TimeStamps (at least in Node.js) is like that: '2021-01-01T00:00:00.000Z'
|
|
1573
|
-
* --> Therefore the comparison in the temporal where clause doesn't work properly.
|
|
1574
|
-
*
|
|
1575
1561
|
* @param {object} x
|
|
1576
1562
|
* @returns {string|null} Null in case of an invalid second path step
|
|
1577
1563
|
*/
|
|
1578
1564
|
function render$at( x ) {
|
|
1579
|
-
if (x.ref[1]
|
|
1580
|
-
|
|
1581
|
-
case 'sqlite': {
|
|
1582
|
-
if (options.betterSqliteSessionVariables)
|
|
1583
|
-
return 'session_context( \'$valid.from\' )';
|
|
1584
|
-
const dateFromFormat = '%Y-%m-%dT%H:%M:%S.000Z';
|
|
1585
|
-
return `strftime('${dateFromFormat}', 'now')`;
|
|
1586
|
-
}
|
|
1587
|
-
case 'hana':
|
|
1588
|
-
return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-FROM\'))';
|
|
1589
|
-
case 'postgres':
|
|
1590
|
-
return 'current_setting(\'cap.valid_from\')::timestamp';
|
|
1591
|
-
case 'h2':
|
|
1592
|
-
return '@valid_from';
|
|
1593
|
-
case 'plain':
|
|
1594
|
-
return 'current_timestamp';
|
|
1595
|
-
default:
|
|
1596
|
-
break;
|
|
1597
|
-
}
|
|
1598
|
-
}
|
|
1565
|
+
if (x.ref.length > 2 || typeof x.ref[1] !== 'string')
|
|
1566
|
+
return null; // `$at` can only have two path steps
|
|
1599
1567
|
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
return `strftime('${dateToFormat}', 'now')`;
|
|
1608
|
-
}
|
|
1609
|
-
case 'hana':
|
|
1610
|
-
return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-TO\'))';
|
|
1611
|
-
case 'postgres':
|
|
1612
|
-
return 'current_setting(\'cap.valid_to\')::timestamp';
|
|
1613
|
-
case 'h2':
|
|
1614
|
-
return '@valid_to';
|
|
1615
|
-
case 'plain':
|
|
1616
|
-
return 'current_timestamp';
|
|
1617
|
-
default:
|
|
1618
|
-
break;
|
|
1619
|
-
}
|
|
1620
|
-
}
|
|
1568
|
+
const name = `$at.${x.ref[1]}`;
|
|
1569
|
+
const config = variableForDialect(options, name);
|
|
1570
|
+
if (config)
|
|
1571
|
+
return config;
|
|
1572
|
+
|
|
1573
|
+
if (options.testMode)
|
|
1574
|
+
throw new CompilerAssertion(`render$at: unhandled sqlDialect '${options.sqlDialect}' when rendering ${x.ref.join('.')}`);
|
|
1621
1575
|
return null;
|
|
1622
1576
|
}
|
|
1623
1577
|
|