@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
package/lib/render/toCdl.js
CHANGED
|
@@ -126,7 +126,7 @@ function csnToCdl( csn, options ) {
|
|
|
126
126
|
return result;
|
|
127
127
|
|
|
128
128
|
function renderVocabulariesEntry( name, anno ) {
|
|
129
|
-
if (!anno
|
|
129
|
+
if (!anno.$ignore) {
|
|
130
130
|
// This environment is passed down the call hierarchy, for dealing with
|
|
131
131
|
// indentation and name resolution issues
|
|
132
132
|
const env = createEnv({ path: [ 'vocabularies', name ] });
|
|
@@ -303,6 +303,19 @@ function csnToCdl( csn, options ) {
|
|
|
303
303
|
* @return {string}
|
|
304
304
|
*/
|
|
305
305
|
function renderAnnotateStatement( ext, env ) {
|
|
306
|
+
// Special case: Super annotate has both "returns" and "elements".
|
|
307
|
+
// Render as separate `annotate`s, but keep the order.
|
|
308
|
+
if (ext.elements && ext.returns) {
|
|
309
|
+
const [ , second ] = Object.keys(ext).filter(key => key === 'elements' || key === 'returns');
|
|
310
|
+
|
|
311
|
+
// The first of 'elements' or 'returns' gets all other properties as well.
|
|
312
|
+
// The second only gets one property (itself).
|
|
313
|
+
let result = renderAnnotateStatement({ ...ext, [second]: undefined }, env);
|
|
314
|
+
result += renderAnnotateStatement({ annotate: ext.annotate, [second]: ext[second] }, env);
|
|
315
|
+
|
|
316
|
+
return result;
|
|
317
|
+
}
|
|
318
|
+
|
|
306
319
|
// Top-level annotations of the artifact
|
|
307
320
|
let result = renderAnnotationAssignmentsAndDocComment(ext, env);
|
|
308
321
|
// Note: Not renderDefinitionReference, because we don't care if there
|
|
@@ -512,8 +525,15 @@ function csnToCdl( csn, options ) {
|
|
|
512
525
|
result += renderParameters(art, env);
|
|
513
526
|
if (art.includes)
|
|
514
527
|
result += renderIncludes(art.includes, env);
|
|
515
|
-
|
|
528
|
+
|
|
529
|
+
if (art.elements)
|
|
530
|
+
result += ` ${renderElements(art, env)}`;
|
|
531
|
+
else if (art.actions)
|
|
532
|
+
// if there are no elements, but actions, CDL syntax requires braces.
|
|
533
|
+
result += ' { }';
|
|
534
|
+
|
|
516
535
|
result += `${renderActionsAndFunctions(art, env)};\n`;
|
|
536
|
+
|
|
517
537
|
return result;
|
|
518
538
|
}
|
|
519
539
|
|
|
@@ -565,7 +585,7 @@ function csnToCdl( csn, options ) {
|
|
|
565
585
|
* Returns the resulting source string.
|
|
566
586
|
*
|
|
567
587
|
* @param {string} elementName
|
|
568
|
-
* @param {CSN.Element
|
|
588
|
+
* @param {CSN.Element} element
|
|
569
589
|
* @param {CdlRenderEnvironment} env
|
|
570
590
|
*/
|
|
571
591
|
function renderElement( elementName, element, env ) {
|
|
@@ -1037,7 +1057,7 @@ function csnToCdl( csn, options ) {
|
|
|
1037
1057
|
result += `${env.indent}}`;
|
|
1038
1058
|
}
|
|
1039
1059
|
|
|
1040
|
-
if (isLeadingQuery)
|
|
1060
|
+
if (isLeadingQuery && query.actions)
|
|
1041
1061
|
result += renderActionsAndFunctions(query, env);
|
|
1042
1062
|
|
|
1043
1063
|
if (select.where)
|
|
@@ -1148,14 +1168,14 @@ function csnToCdl( csn, options ) {
|
|
|
1148
1168
|
*/
|
|
1149
1169
|
function renderActionsAndFunctions( art, env ) {
|
|
1150
1170
|
let result = '';
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1171
|
+
if (art.actions) {
|
|
1172
|
+
const childEnv = env.withIncreasedIndent();
|
|
1173
|
+
for (const name in art.actions)
|
|
1174
|
+
result += renderActionOrFunction(name, art.actions[name], childEnv.withSubPath([ 'actions', name ]));
|
|
1175
|
+
result = (result === '')
|
|
1176
|
+
? ' actions { }'
|
|
1177
|
+
: ` actions {\n${result}${env.indent}}`;
|
|
1178
|
+
}
|
|
1159
1179
|
return result;
|
|
1160
1180
|
}
|
|
1161
1181
|
|
|
@@ -1229,14 +1249,31 @@ function csnToCdl( csn, options ) {
|
|
|
1229
1249
|
function renderTypeOrAnnotation( artifactName, art, env, artType ) {
|
|
1230
1250
|
let result = renderAnnotationAssignmentsAndDocComment(art, env);
|
|
1231
1251
|
result += `${env.indent + (artType || art.$syntax || art.kind )} ${renderArtifactName(artifactName, env)}`;
|
|
1232
|
-
|
|
1252
|
+
|
|
1253
|
+
const type = renderTypeReferenceAndProps(art, env);
|
|
1254
|
+
const isDirectStruct = type?.startsWith('{');
|
|
1255
|
+
if (art.includes?.length && isDirectStruct)
|
|
1256
|
+
// We can only render includes, if the type is directly structured. Otherwise, we would
|
|
1257
|
+
// render e.g. `type T : Include : T2;`, which is invalid. We use `extend` in such cases.
|
|
1233
1258
|
result += renderIncludes(art.includes, env);
|
|
1234
1259
|
|
|
1235
|
-
if (
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1260
|
+
if (type) {
|
|
1261
|
+
// For nicer output, no colon if unnamed structure is used.
|
|
1262
|
+
result += (!art.type && art.elements) ? ` ${type}` : ` : ${type}`;
|
|
1263
|
+
}
|
|
1264
|
+
else {
|
|
1265
|
+
msg.warning('syntax-missing-type', env.path, { name: artifactName },
|
|
1266
|
+
'Missing type for definition $(NAME); can\'t be represented in CDL');
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1239
1269
|
result += ';\n';
|
|
1270
|
+
|
|
1271
|
+
if (art.includes?.length && !isDirectStruct) {
|
|
1272
|
+
// If we're not a directly structured type, render the `includes` as `extend`
|
|
1273
|
+
// statements directly below the type definition.
|
|
1274
|
+
result += renderExtendStatement(artifactName, { includes: art.includes }, env);
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1240
1277
|
return result;
|
|
1241
1278
|
}
|
|
1242
1279
|
|
|
@@ -1247,8 +1284,9 @@ function csnToCdl( csn, options ) {
|
|
|
1247
1284
|
*
|
|
1248
1285
|
* @param {CSN.Artifact} artifact
|
|
1249
1286
|
* @param {CdlRenderEnvironment} env
|
|
1250
|
-
* @param {object} [config={}]
|
|
1251
|
-
*
|
|
1287
|
+
* @param {object} [config={}]
|
|
1288
|
+
* @param {boolean} [config.typeRefOnly] Whether to only render type defs, no arrayed/structured/enum.
|
|
1289
|
+
* @param {boolean} [config.noAnnoCollect] Do not collect annotations of sub-elements.
|
|
1252
1290
|
* @return {string}
|
|
1253
1291
|
*/
|
|
1254
1292
|
function renderTypeReferenceAndProps( artifact, env, config = {} ) {
|
|
@@ -1310,8 +1348,9 @@ function csnToCdl( csn, options ) {
|
|
|
1310
1348
|
|
|
1311
1349
|
if (artifact.notNull !== undefined && !artifact.on) // unmanaged associations can't be followed by "not null"
|
|
1312
1350
|
result += renderNullability(artifact);
|
|
1313
|
-
// DEFAULT not possible here.
|
|
1314
1351
|
|
|
1352
|
+
if (artifact.default && !artifact.on)
|
|
1353
|
+
result += ` default ${exprRenderer.renderExpr(artifact.default, env.withSubPath([ 'default' ]))}`;
|
|
1315
1354
|
return result;
|
|
1316
1355
|
}
|
|
1317
1356
|
|
|
@@ -1814,7 +1853,7 @@ function csnToCdl( csn, options ) {
|
|
|
1814
1853
|
// Take the annotation assignment apart into <nameBeforeVariant>#<variantAndRest>
|
|
1815
1854
|
const parts = name.split('#');
|
|
1816
1855
|
const nameBeforeVariant = parts[0];
|
|
1817
|
-
const variant = parts
|
|
1856
|
+
const variant = parts.length > 1 ? parts.slice(1).join('#') : undefined;
|
|
1818
1857
|
const { parentheses } = config;
|
|
1819
1858
|
|
|
1820
1859
|
let result = `${env.indent}@`;
|
|
@@ -1822,10 +1861,11 @@ function csnToCdl( csn, options ) {
|
|
|
1822
1861
|
result += '(';
|
|
1823
1862
|
|
|
1824
1863
|
result += quoteAnnotationPathIfRequired(nameBeforeVariant, env);
|
|
1825
|
-
if (variant !== undefined)
|
|
1864
|
+
if (variant !== undefined) {
|
|
1826
1865
|
// Unfortunately, the compiler does not allow `.@` after the first variant identifier,
|
|
1827
|
-
// so we're back at simple paths.
|
|
1866
|
+
// nor multiple `#`, so we're back at simple paths that are possibly quoted.
|
|
1828
1867
|
result += `#${quotePathIfRequired(variant, env)}`;
|
|
1868
|
+
}
|
|
1829
1869
|
result += ` : ${renderAnnotationValue(anno, env)}`;
|
|
1830
1870
|
|
|
1831
1871
|
if (parentheses)
|
package/lib/render/toHdbcds.js
CHANGED
|
@@ -647,7 +647,7 @@ function toHdbcdsSource( csn, options ) {
|
|
|
647
647
|
// Because we already emit an error that calc-on-write is not supported, just ignore nullability/default.
|
|
648
648
|
if (!elm.value?.stored) {
|
|
649
649
|
result += renderNullability(elm);
|
|
650
|
-
if (elm.default)
|
|
650
|
+
if (elm.default && !elm.target)
|
|
651
651
|
result += ` default ${renderExpr(elm.default, env.withSubPath([ 'default' ]))}`;
|
|
652
652
|
}
|
|
653
653
|
|
|
@@ -798,7 +798,7 @@ function toHdbcdsSource( csn, options ) {
|
|
|
798
798
|
|
|
799
799
|
const key = (!env.skipKeys && (col.key || element?.key) ? 'key ' : '');
|
|
800
800
|
result += key + renderExpr(withoutCast(col), env);
|
|
801
|
-
let alias = col.as || col.func;
|
|
801
|
+
let alias = col.as || (!col.args && col.func); // func: e.g. CURRENT_TIMESTAMP
|
|
802
802
|
// HANA requires an alias for 'key' columns just for syntactical reasons
|
|
803
803
|
// FIXME: This will not complain for non-refs (but that should be checked in forRelationalDB)
|
|
804
804
|
// Explicit or implicit alias?
|
package/lib/render/toRename.js
CHANGED
|
@@ -5,15 +5,11 @@ const { makeMessageFunction } = require('../base/messages');
|
|
|
5
5
|
const { checkCSNVersion } = require('../json/csnVersion');
|
|
6
6
|
const { forEachDefinition } = require('../model/csnUtils');
|
|
7
7
|
const { optionProcessor } = require('../optionProcessor');
|
|
8
|
-
const { isBetaEnabled } = require('../base/model');
|
|
9
8
|
const { transformForRelationalDBWithCsn } = require('../transform/forRelationalDB');
|
|
10
9
|
const { getIdentifierUtils } = require('./utils/sql');
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
/**
|
|
14
|
-
* FIXME: Not yet supported, only in beta mode
|
|
15
|
-
* FIXME: This is not up-to-date in regards to the changes to hdbcds/sql quoting etc.
|
|
16
|
-
*
|
|
17
13
|
* Generate SQL DDL rename statements for a migration, renaming existing tables and their
|
|
18
14
|
* columns so that they match the result of "toHana" or "toSql" with the 'plain' option for names.
|
|
19
15
|
* Expects the naming convention of the existing tables to be either 'quoted' or 'hdbcds' (default).
|
|
@@ -32,7 +28,7 @@ const { getIdentifierUtils } = require('./utils/sql');
|
|
|
32
28
|
* @returns {object} A dictionary of name: rename statement
|
|
33
29
|
*/
|
|
34
30
|
function toRename( inputCsn, options ) {
|
|
35
|
-
const {
|
|
31
|
+
const { warning, throwWithError } = makeMessageFunction(inputCsn, options, 'to.rename');
|
|
36
32
|
|
|
37
33
|
// Merge options with defaults.
|
|
38
34
|
options = Object.assign({ sqlMapping: 'hdbcds', sqlDialect: 'hana' }, options);
|
|
@@ -41,9 +37,8 @@ function toRename( inputCsn, options ) {
|
|
|
41
37
|
optionProcessor.verifyOptions(options, 'toRename').forEach(complaint => warning(null, null, `${complaint}`));
|
|
42
38
|
checkCSNVersion(inputCsn, options);
|
|
43
39
|
|
|
44
|
-
//
|
|
45
|
-
|
|
46
|
-
error(null, null, 'Generation of SQL rename statements is not supported yet (only in beta mode)');
|
|
40
|
+
// Let users know that this is internal
|
|
41
|
+
warning(null, null, 'Generation of SQL rename statements is a beta feature and might change in the future');
|
|
47
42
|
|
|
48
43
|
// FIXME: Currently, 'toRename' implies transformation for HANA (transferring the options to forRelationalDB)
|
|
49
44
|
const csn = transformForRelationalDBWithCsn(inputCsn, options, 'to.rename');
|
|
@@ -102,7 +97,7 @@ function toRename( inputCsn, options ) {
|
|
|
102
97
|
const beforeColumnName = hdbcdsOrQuotedIdentifiers.quoteSqlId(name);
|
|
103
98
|
const afterColumnName = plainIdentifiers.quoteSqlId(name);
|
|
104
99
|
|
|
105
|
-
if (!e
|
|
100
|
+
if (!e.$ignore) {
|
|
106
101
|
if (e.target)
|
|
107
102
|
str = ` EXEC 'ALTER TABLE ${afterTableName} DROP ASSOCIATION ${beforeColumnName}';\n`;
|
|
108
103
|
else if (beforeColumnName.toUpperCase() === `"${afterColumnName}"` ) // Basically a no-op - render commented out
|
package/lib/render/toSql.js
CHANGED
|
@@ -24,7 +24,8 @@ const { timetrace } = require('../utils/timetrace');
|
|
|
24
24
|
const { isBetaEnabled, isDeprecatedEnabled } = require('../base/model');
|
|
25
25
|
const { smartFuncId } = require('../sql-identifier');
|
|
26
26
|
const { sortCsn } = require('../json/to-csn');
|
|
27
|
-
const { manageConstraints } = require('./manageConstraints');
|
|
27
|
+
const { manageConstraints, manageConstraint } = require('./manageConstraints');
|
|
28
|
+
const { renderUniqueConstraintString, renderUniqueConstraintDrop, renderUniqueConstraintAdd } = require('./utils/unique');
|
|
28
29
|
const { ModelError, CompilerAssertion } = require('../base/error');
|
|
29
30
|
|
|
30
31
|
class SqlRenderEnvironment {
|
|
@@ -105,6 +106,7 @@ function toSqlDdl( csn, options ) {
|
|
|
105
106
|
error, warning, info, throwWithAnyError,
|
|
106
107
|
} = makeMessageFunction(csn, options, 'to.sql');
|
|
107
108
|
const { quoteSqlId, prepareIdentifier, renderArtifactName } = getIdentifierUtils(csn, options);
|
|
109
|
+
let reportedMissingUserReplacement = false;
|
|
108
110
|
|
|
109
111
|
const exprRenderer = createExpressionRenderer({
|
|
110
112
|
// FIXME: For the sake of simplicity, we should get away from all this uppercasing in toSql
|
|
@@ -175,6 +177,7 @@ function toSqlDdl( csn, options ) {
|
|
|
175
177
|
hdbview: Object.create(null),
|
|
176
178
|
hdbconstraint: Object.create(null),
|
|
177
179
|
deletions: Object.create(null),
|
|
180
|
+
constraintDeletions: [],
|
|
178
181
|
migrations: Object.create(null),
|
|
179
182
|
};
|
|
180
183
|
|
|
@@ -240,8 +243,10 @@ function toSqlDdl( csn, options ) {
|
|
|
240
243
|
const sqlVersionLine = `-- ${generatedByCompilerVersion()}\n`;
|
|
241
244
|
|
|
242
245
|
// Handle hdbKinds separately from alterTable case
|
|
243
|
-
|
|
244
|
-
|
|
246
|
+
const {
|
|
247
|
+
// eslint-disable-next-line no-unused-vars
|
|
248
|
+
deletions, constraintDeletions, migrations: _, ...hdbKinds
|
|
249
|
+
} = mainResultObj;
|
|
245
250
|
for (const hdbKind of Object.keys(hdbKinds)) {
|
|
246
251
|
for (const name in mainResultObj[hdbKind]) {
|
|
247
252
|
if (options.src === 'sql') {
|
|
@@ -260,14 +265,15 @@ function toSqlDdl( csn, options ) {
|
|
|
260
265
|
}
|
|
261
266
|
|
|
262
267
|
// add `ALTER TABLE ADD CONSTRAINT` statements per default for `to.sql` w/ dialect `hana` / `postgres`
|
|
263
|
-
// TODO `ALTER TABLE ADD CONSTRAINT` statements also for sqlite constraints
|
|
264
268
|
if (!options.constraintsInCreateTable && options.src === 'sql' && (options.sqlDialect === 'hana' || options.sqlDialect === 'postgres' /* || options.sqlDialect === 'sqlite' */)) {
|
|
269
|
+
const constraints = Object.create(null);
|
|
265
270
|
const alterStmts = manageConstraints(csn, options);
|
|
266
271
|
|
|
267
272
|
forEachKey(alterStmts, (constraintName) => {
|
|
268
|
-
|
|
273
|
+
if (!csn.unchangedConstraints?.has(constraintName))
|
|
274
|
+
constraints[constraintName] = `${options.testMode ? '' : sqlVersionLine}${alterStmts[constraintName]}`;
|
|
269
275
|
});
|
|
270
|
-
mainResultObj.
|
|
276
|
+
mainResultObj.constraints = constraints;
|
|
271
277
|
}
|
|
272
278
|
|
|
273
279
|
if (options.src === 'sql')
|
|
@@ -333,8 +339,12 @@ function toSqlDdl( csn, options ) {
|
|
|
333
339
|
function renderArtifactExtensionInto( artifactName, artifact, ext, resultObj, env ) {
|
|
334
340
|
// Property kind is always omitted for elements and can be omitted for
|
|
335
341
|
// top-level type definitions, it does not exist for extensions.
|
|
336
|
-
if (artifactName && !ext.query)
|
|
337
|
-
|
|
342
|
+
if (artifactName && !ext.query) {
|
|
343
|
+
if (ext.constraint)
|
|
344
|
+
renderConstraintExtendInto(artifactName, ext, resultObj);
|
|
345
|
+
else
|
|
346
|
+
renderExtendInto(artifactName, artifact.elements, ext.elements, resultObj, env, extensionsDuplicateChecker);
|
|
347
|
+
}
|
|
338
348
|
|
|
339
349
|
if (!artifactName)
|
|
340
350
|
throw new ModelError(`Undefined artifact name: ${artifactName}`);
|
|
@@ -421,6 +431,24 @@ function toSqlDdl( csn, options ) {
|
|
|
421
431
|
}
|
|
422
432
|
}
|
|
423
433
|
|
|
434
|
+
if (migration.removeConstraints) {
|
|
435
|
+
const constraintTypes = [ 'unique', 'referential' ];
|
|
436
|
+
constraintTypes.forEach((constraintType) => {
|
|
437
|
+
if (migration.removeConstraints[constraintType]) {
|
|
438
|
+
const entries = Object.entries(migration.removeConstraints[constraintType]);
|
|
439
|
+
const optionsWithDrop = { ...options, drop: true };
|
|
440
|
+
let renderer;
|
|
441
|
+
if (constraintType === 'referential')
|
|
442
|
+
renderer = constraint => manageConstraint(constraint, csn, optionsWithDrop, '', quoteSqlId);
|
|
443
|
+
else
|
|
444
|
+
renderer = (constraint, constraintName) => renderUniqueConstraintDrop(constraint, renderArtifactName(`${artifactName}_${constraintName}`), tableName, quoteSqlId);
|
|
445
|
+
entries.forEach(( [ constraintName, constraint ]) => {
|
|
446
|
+
addConstraintDeletion(resultObj, constraint.parentTable, renderer(constraint, constraintName));
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
|
|
424
452
|
// Change column types (unsupported in sqlite)
|
|
425
453
|
if (migration.change) {
|
|
426
454
|
changeElementsDuplicateChecker.addArtifact(tableName, undefined, artifactName);
|
|
@@ -431,7 +459,7 @@ function toSqlDdl( csn, options ) {
|
|
|
431
459
|
const eltStrOld = getEltStr(def.old, eltName, 'migration');
|
|
432
460
|
const eltStrNew = getEltStr(def.new, eltName, 'migration');
|
|
433
461
|
if (eltStrNew === eltStrOld)
|
|
434
|
-
|
|
462
|
+
continue; // Prevent spurious migrations, where the column DDL does not change.
|
|
435
463
|
|
|
436
464
|
const annosIncompat = [];
|
|
437
465
|
sqlSnippetAnnos
|
|
@@ -550,16 +578,11 @@ function toSqlDdl( csn, options ) {
|
|
|
550
578
|
// OR create a unique index for HDI
|
|
551
579
|
const uniqueConstraints = art.$tableConstraints && art.$tableConstraints.unique;
|
|
552
580
|
for (const cn in uniqueConstraints) {
|
|
553
|
-
const
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
= `UNIQUE INVERTED INDEX ${cnName} ON ${tableName} (${refs})`;
|
|
559
|
-
}
|
|
560
|
-
else {
|
|
561
|
-
result += `,\n${childEnv.indent}CONSTRAINT ${cnName} UNIQUE (${refs})`;
|
|
562
|
-
}
|
|
581
|
+
const constraint = renderUniqueConstraintString(uniqueConstraints[cn], renderArtifactName(`${artifactName}_${cn}`), tableName, quoteSqlId, options);
|
|
582
|
+
if (options.src === 'hdi')
|
|
583
|
+
resultObj.hdbindex[`${artifactName}.${cn}`] = constraint;
|
|
584
|
+
else
|
|
585
|
+
result += `,\n${childEnv.indent}${constraint}`;
|
|
563
586
|
}
|
|
564
587
|
result += `${env.indent}\n)`;
|
|
565
588
|
|
|
@@ -591,6 +614,21 @@ function toSqlDdl( csn, options ) {
|
|
|
591
614
|
resultObj.hdbtable[artifactName] = result;
|
|
592
615
|
}
|
|
593
616
|
|
|
617
|
+
/**
|
|
618
|
+
* Render an extended entity constraint into the appropriate dictionaries of 'resultObj'.
|
|
619
|
+
* Only SAP HANA SQL is currently supported.
|
|
620
|
+
*
|
|
621
|
+
* @param {string} artifactName Name of the artifact to render
|
|
622
|
+
* @param {object} ext Constraint comprising the extension
|
|
623
|
+
* @param {object} resultObj Result collector
|
|
624
|
+
*/
|
|
625
|
+
function renderConstraintExtendInto( artifactName, { constraint, constraintName, constraintType }, resultObj ) {
|
|
626
|
+
const result = constraintType === 'unique' ? renderUniqueConstraintAdd(constraint, renderArtifactName(`${artifactName}_${constraintName}`), renderArtifactName(constraint.parentTable), quoteSqlId, options)
|
|
627
|
+
: manageConstraint(constraint, csn, options, '', quoteSqlId);
|
|
628
|
+
|
|
629
|
+
addMigration(resultObj, artifactName, false, [ result ]);
|
|
630
|
+
}
|
|
631
|
+
|
|
594
632
|
|
|
595
633
|
/**
|
|
596
634
|
* Render an extended entity into the appropriate dictionaries of 'resultObj'.
|
|
@@ -632,6 +670,9 @@ function toSqlDdl( csn, options ) {
|
|
|
632
670
|
resultObj.migrations[artifactName].push(...migrations);
|
|
633
671
|
}
|
|
634
672
|
|
|
673
|
+
function addConstraintDeletion( resultObj, artifactName, deletionSql ) {
|
|
674
|
+
resultObj.constraintDeletions.push(deletionSql);
|
|
675
|
+
}
|
|
635
676
|
function addDeletion( resultObj, artifactName, deletionSql ) {
|
|
636
677
|
resultObj.deletions[artifactName] = deletionSql;
|
|
637
678
|
}
|
|
@@ -1064,7 +1105,7 @@ function toSqlDdl( csn, options ) {
|
|
|
1064
1105
|
result = env.indent + renderExpr(withoutCast(col), env);
|
|
1065
1106
|
if (col.as)
|
|
1066
1107
|
result += ` AS ${quoteSqlId(col.as)}`;
|
|
1067
|
-
else if (col.func)
|
|
1108
|
+
else if (col.func && !col.args) // e.g. CURRENT_TIMESTAMP
|
|
1068
1109
|
result += ` AS ${quoteSqlId(col.func)}`;
|
|
1069
1110
|
}
|
|
1070
1111
|
return result;
|
|
@@ -1338,15 +1379,11 @@ function toSqlDdl( csn, options ) {
|
|
|
1338
1379
|
* @returns {string} Rendered type
|
|
1339
1380
|
*/
|
|
1340
1381
|
function renderBuiltinType( typeName ) {
|
|
1341
|
-
const forHanaRenamesToEarly = {
|
|
1342
|
-
'cds.UTCDateTime': 'cds.DateTime',
|
|
1343
|
-
'cds.UTCTimestamp': 'cds.Timestamp',
|
|
1344
|
-
'cds.LocalDate': 'cds.Date',
|
|
1345
|
-
'cds.LocalTime': 'cds.Time',
|
|
1346
|
-
};
|
|
1347
|
-
const tName = forHanaRenamesToEarly[typeName] || typeName;
|
|
1348
1382
|
const types = cdsToSqlTypes[options.sqlDialect];
|
|
1349
|
-
|
|
1383
|
+
const result = types && types[typeName] || cdsToSqlTypes.standard[typeName];
|
|
1384
|
+
if (!result && options.testMode)
|
|
1385
|
+
throw new CompilerAssertion(`Expected to find a type mapping for ${typeName}`);
|
|
1386
|
+
return result || 'CHAR';
|
|
1350
1387
|
}
|
|
1351
1388
|
|
|
1352
1389
|
/**
|
|
@@ -1466,9 +1503,8 @@ function toSqlDdl( csn, options ) {
|
|
|
1466
1503
|
case 'hana':
|
|
1467
1504
|
return 'CURRENT_TIMESTAMP';
|
|
1468
1505
|
case 'h2':
|
|
1469
|
-
return 'current_timestamp';
|
|
1470
1506
|
case 'postgres':
|
|
1471
|
-
return '
|
|
1507
|
+
return 'current_timestamp';
|
|
1472
1508
|
default:
|
|
1473
1509
|
return quoteSqlId(x.ref[0]);
|
|
1474
1510
|
}
|
|
@@ -1498,19 +1534,28 @@ function toSqlDdl( csn, options ) {
|
|
|
1498
1534
|
if (options.sqlDialect === 'hana')
|
|
1499
1535
|
return 'SESSION_CONTEXT(\'APPLICATIONUSER\')';
|
|
1500
1536
|
else if (options.sqlDialect === 'postgres')
|
|
1501
|
-
return 'current_setting(\'
|
|
1537
|
+
return 'current_setting(\'cap.applicationuser\')';
|
|
1502
1538
|
else if (options.betterSqliteSessionVariables && options.sqlDialect === 'sqlite')
|
|
1503
1539
|
return 'session_context( \'$user.id\' )';
|
|
1504
|
-
|
|
1540
|
+
else if (options.sqlDialect === 'h2')
|
|
1541
|
+
return '@applicationuser';
|
|
1542
|
+
|
|
1543
|
+
if (!reportedMissingUserReplacement) {
|
|
1544
|
+
warning(null, null, 'The "$user" variable is not supported. Use option "variableReplacements" to specify a value for "$user.id"');
|
|
1545
|
+
reportedMissingUserReplacement = true;
|
|
1546
|
+
}
|
|
1505
1547
|
return '\'$user.id\'';
|
|
1506
1548
|
}
|
|
1507
1549
|
else if (x.ref[1] === 'locale') {
|
|
1508
1550
|
if (options.sqlDialect === 'hana')
|
|
1509
1551
|
return 'SESSION_CONTEXT(\'LOCALE\')';
|
|
1510
1552
|
else if (options.sqlDialect === 'postgres')
|
|
1511
|
-
return 'current_setting(\'
|
|
1553
|
+
return 'current_setting(\'cap.locale\')';
|
|
1512
1554
|
else if (options.betterSqliteSessionVariables && options.sqlDialect === 'sqlite')
|
|
1513
1555
|
return 'session_context( \'$user.locale\' )';
|
|
1556
|
+
else if (options.sqlDialect === 'h2')
|
|
1557
|
+
return '@locale';
|
|
1558
|
+
|
|
1514
1559
|
return '\'en\''; // default language
|
|
1515
1560
|
}
|
|
1516
1561
|
// Basically: Second path step was invalid, do nothing - should not happen.
|
|
@@ -1542,8 +1587,9 @@ function toSqlDdl( csn, options ) {
|
|
|
1542
1587
|
case 'hana':
|
|
1543
1588
|
return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-FROM\'))';
|
|
1544
1589
|
case 'postgres':
|
|
1545
|
-
return '
|
|
1590
|
+
return 'current_setting(\'cap.valid_from\')::timestamp';
|
|
1546
1591
|
case 'h2':
|
|
1592
|
+
return '@valid_from';
|
|
1547
1593
|
case 'plain':
|
|
1548
1594
|
return 'current_timestamp';
|
|
1549
1595
|
default:
|
|
@@ -1563,8 +1609,9 @@ function toSqlDdl( csn, options ) {
|
|
|
1563
1609
|
case 'hana':
|
|
1564
1610
|
return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-TO\'))';
|
|
1565
1611
|
case 'postgres':
|
|
1566
|
-
return '
|
|
1612
|
+
return 'current_setting(\'cap.valid_to\')::timestamp';
|
|
1567
1613
|
case 'h2':
|
|
1614
|
+
return '@valid_to';
|
|
1568
1615
|
case 'plain':
|
|
1569
1616
|
return 'current_timestamp';
|
|
1570
1617
|
default:
|
|
@@ -146,7 +146,7 @@ function addContextMarkers( csn, killList ) {
|
|
|
146
146
|
const contextsToCreate = Object.create(null);
|
|
147
147
|
forEachDefinition(csn, (art, artifactName) => {
|
|
148
148
|
const namespace = getNamespace(csn, artifactName);
|
|
149
|
-
if (namespace && !(art
|
|
149
|
+
if (namespace && !(art.$ignore || hasValidSkipOrExists(art))) {
|
|
150
150
|
const parts = namespace.split('.');
|
|
151
151
|
contextsToCreate[parts[0]] = true;
|
|
152
152
|
|
|
@@ -204,7 +204,7 @@ function addMissingChildContexts( csn, artifactName, killList ) {
|
|
|
204
204
|
const possibleNames = Object.keys(csn.definitions).filter(name => name.startsWith(`${artifactName}.`)).sort((a, b) => a.length - b.length);
|
|
205
205
|
for (const name of possibleNames) {
|
|
206
206
|
const artifact = csn.definitions[name];
|
|
207
|
-
if (!artifact
|
|
207
|
+
if (!artifact.$ignore && !hasValidSkipOrExists(artifact))
|
|
208
208
|
addPossibleGaps(name.slice(artifactName.length + 1).split('.'), artifactName);
|
|
209
209
|
}
|
|
210
210
|
|
|
@@ -258,16 +258,14 @@ const cdsToSqlTypes = {
|
|
|
258
258
|
'cds.DateTime': 'TIMESTAMP', // cds-compiler#2758
|
|
259
259
|
'cds.Timestamp': 'TIMESTAMP',
|
|
260
260
|
'cds.Boolean': 'BOOLEAN',
|
|
261
|
-
'cds.UUID': 'NVARCHAR', // changed to cds.String earlier
|
|
262
261
|
// (TODO: do it later; TODO: why not CHAR or at least VARCHAR?)
|
|
262
|
+
'cds.UUID': 'NVARCHAR', // changed to cds.String earlier
|
|
263
|
+
'cds.hana.ST_POINT': 'CHAR', // CHAR is implicit fallback used in toSql - make it explicit here
|
|
264
|
+
'cds.hana.ST_GEOMETRY': 'CHAR', // CHAR is implicit fallback used in toSql - make it explicit here
|
|
263
265
|
},
|
|
264
266
|
hana: {
|
|
265
267
|
'cds.hana.SMALLDECIMAL': 'SMALLDECIMAL',
|
|
266
|
-
'cds.LocalDate': 'DATE',
|
|
267
|
-
'cds.LocalTime': 'TIME',
|
|
268
268
|
'cds.DateTime': 'SECONDDATE',
|
|
269
|
-
'cds.UTCDateTime': 'SECONDDATE',
|
|
270
|
-
'cds.UTCTimestamp': 'TIMESTAMP',
|
|
271
269
|
'cds.hana.ST_POINT': 'ST_POINT',
|
|
272
270
|
'cds.hana.ST_GEOMETRY': 'ST_GEOMETRY',
|
|
273
271
|
},
|
|
@@ -275,7 +273,7 @@ const cdsToSqlTypes = {
|
|
|
275
273
|
'cds.Date': 'DATE_TEXT',
|
|
276
274
|
'cds.Time': 'TIME_TEXT',
|
|
277
275
|
'cds.Timestamp': 'TIMESTAMP_TEXT',
|
|
278
|
-
'cds.DateTime': '
|
|
276
|
+
'cds.DateTime': 'DATETIME_TEXT',
|
|
279
277
|
'cds.Binary': 'BINARY_BLOB',
|
|
280
278
|
'cds.hana.BINARY': 'BINARY_BLOB',
|
|
281
279
|
'cds.hana.SMALLDECIMAL': 'SMALLDECIMAL',
|
|
@@ -310,6 +308,10 @@ const cdsToHdbcdsTypes = {
|
|
|
310
308
|
'cds.Int16': 'cds.hana.SMALLINT',
|
|
311
309
|
'cds.Int32': 'cds.Integer',
|
|
312
310
|
'cds.Int64': 'cds.Integer64',
|
|
311
|
+
'cds.Timestamp': 'cds.UTCTimestamp',
|
|
312
|
+
'cds.DateTime': 'cds.UTCDateTime',
|
|
313
|
+
'cds.Date': 'cds.LocalDate',
|
|
314
|
+
'cds.Time': 'cds.LocalTime',
|
|
313
315
|
};
|
|
314
316
|
|
|
315
317
|
/**
|
|
@@ -360,7 +362,7 @@ function findElement( elements, column ) {
|
|
|
360
362
|
function addIntermediateContexts( csn, killList ) {
|
|
361
363
|
for (const artifactName in csn.definitions) {
|
|
362
364
|
const artifact = csn.definitions[artifactName];
|
|
363
|
-
if ((artifact.kind === 'context') && !artifact
|
|
365
|
+
if ((artifact.kind === 'context') && !artifact.$ignore) {
|
|
364
366
|
// If context A.B.C and entity A exist, we still need generate context A_B.
|
|
365
367
|
// But if no entity A exists, A.B is just a namespace.
|
|
366
368
|
// For case 1 and 2, getParentContextName returns undefined - we then use the namespace as our "off-limits"
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Render the "CONSTRAINT <XY>;" for HDI or SQL.
|
|
5
|
+
*
|
|
6
|
+
* @param {object} constraint The constraint to add
|
|
7
|
+
* @param {string} constraintName Name of the constraint - needs to be escaped on caller side
|
|
8
|
+
* @param {string} tableName Name of the table with the constraint - needs to be escaped on caller side
|
|
9
|
+
* @param {function} quoteSqlId Usual rendering function
|
|
10
|
+
* @param {object} options Options
|
|
11
|
+
* @returns {string}
|
|
12
|
+
*/
|
|
13
|
+
function renderUniqueConstraintString( constraint, constraintName, tableName, quoteSqlId, options ) {
|
|
14
|
+
const c = constraint.paths;
|
|
15
|
+
const refs = c.map(cpath => quoteSqlId(cpath.ref[0])).join(', ');
|
|
16
|
+
if (options.src === 'hdi')
|
|
17
|
+
return `UNIQUE INVERTED INDEX ${constraintName} ON ${tableName} (${refs})`;
|
|
18
|
+
|
|
19
|
+
return `CONSTRAINT ${constraintName} UNIQUE (${refs})`;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Render the "ALTER TABLE XY DROP CONSTRAINT <Z>;"
|
|
23
|
+
*
|
|
24
|
+
* @param {object} constraint The constraint to drop
|
|
25
|
+
* @param {string} constraintName Name of the constraint - needs to be escaped on caller side
|
|
26
|
+
* @param {string} tableName Name of the table with the constraint - needs to be escaped on caller side
|
|
27
|
+
* @param {function} quoteSqlId Usual rendering function
|
|
28
|
+
* @returns {string}
|
|
29
|
+
*/
|
|
30
|
+
function renderUniqueConstraintDrop( constraint, constraintName, tableName, quoteSqlId ) {
|
|
31
|
+
return `ALTER TABLE ${tableName} DROP CONSTRAINT ${quoteSqlId(constraintName)};`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Render the "ALTER TABLE XY ADD CONSTRAINT <Z>;"
|
|
36
|
+
*
|
|
37
|
+
* @param {object} constraint The constraint to add
|
|
38
|
+
* @param {string} constraintName Name of the constraint - needs to be escaped on caller side
|
|
39
|
+
* @param {string} tableName Name of the table with the constraint - needs to be escaped on caller side
|
|
40
|
+
* @param {function} quoteSqlId Usual rendering function
|
|
41
|
+
* @param {object} options Options
|
|
42
|
+
* @returns {string}
|
|
43
|
+
*/
|
|
44
|
+
function renderUniqueConstraintAdd( constraint, constraintName, tableName, quoteSqlId, options ) {
|
|
45
|
+
return `ALTER TABLE ${tableName} ADD ${renderUniqueConstraintString(constraint, constraintName, tableName, quoteSqlId, options)};`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = {
|
|
49
|
+
renderUniqueConstraintString,
|
|
50
|
+
renderUniqueConstraintDrop,
|
|
51
|
+
renderUniqueConstraintAdd,
|
|
52
|
+
};
|