@sap/cds-compiler 2.4.4 → 2.10.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 +241 -1
- package/bin/.eslintrc.json +17 -0
- package/bin/cds_update_identifiers.js +8 -7
- package/bin/cdsc.js +180 -132
- package/bin/cdshi.js +18 -11
- package/bin/cdsse.js +38 -32
- package/bin/cdsv2m.js +8 -7
- package/doc/CHANGELOG_BETA.md +36 -1
- package/lib/api/main.js +81 -100
- package/lib/api/options.js +17 -11
- package/lib/api/validate.js +12 -8
- package/lib/backends.js +0 -81
- package/lib/base/keywords.js +32 -2
- package/lib/base/location.js +2 -2
- package/lib/base/message-registry.js +66 -4
- package/lib/base/messages.js +84 -27
- package/lib/base/model.js +2 -61
- package/lib/checks/arrayOfs.js +0 -1
- package/lib/checks/defaultValues.js +27 -2
- package/lib/checks/elements.js +1 -6
- package/lib/checks/enricher.js +8 -2
- package/lib/checks/foreignKeys.js +0 -6
- package/lib/checks/managedWithoutKeys.js +17 -0
- package/lib/checks/nonexpandableStructured.js +38 -0
- package/lib/checks/onConditions.js +9 -45
- package/lib/checks/queryNoDbArtifacts.js +27 -9
- package/lib/checks/selectItems.js +25 -2
- package/lib/checks/types.js +26 -2
- package/lib/checks/unknownMagic.js +38 -0
- package/lib/checks/utils.js +61 -0
- package/lib/checks/validator.js +66 -13
- package/lib/compiler/assert-consistency.js +24 -12
- package/lib/compiler/builtins.js +2 -0
- package/lib/compiler/checks.js +6 -4
- package/lib/compiler/definer.js +101 -39
- package/lib/compiler/index.js +88 -59
- package/lib/compiler/resolver.js +455 -209
- package/lib/compiler/shared.js +57 -33
- package/lib/edm/annotations/genericTranslation.js +183 -187
- package/lib/edm/csn2edm.js +128 -99
- package/lib/edm/edm.js +18 -21
- package/lib/edm/edmPreprocessor.js +361 -127
- package/lib/edm/edmUtils.js +103 -33
- package/lib/gen/Dictionary.json +74 -28
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +18 -4
- package/lib/gen/language.tokens +124 -118
- package/lib/gen/languageLexer.interp +13 -1
- package/lib/gen/languageLexer.js +870 -839
- package/lib/gen/languageLexer.tokens +116 -111
- package/lib/gen/languageParser.js +5894 -5614
- package/lib/json/from-csn.js +152 -67
- package/lib/json/to-csn.js +334 -135
- package/lib/language/antlrParser.js +4 -3
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +24 -14
- package/lib/language/language.g4 +188 -128
- package/lib/main.d.ts +435 -0
- package/lib/main.js +31 -7
- package/lib/model/api.js +78 -0
- package/lib/model/csnRefs.js +463 -187
- package/lib/model/csnUtils.js +280 -136
- package/lib/model/enrichCsn.js +75 -4
- package/lib/model/revealInternalProperties.js +2 -1
- package/lib/modelCompare/compare.js +70 -25
- package/lib/optionProcessor.js +13 -10
- package/lib/render/.eslintrc.json +4 -1
- package/lib/render/DuplicateChecker.js +8 -5
- package/lib/render/toCdl.js +123 -40
- package/lib/render/toHdbcds.js +156 -65
- package/lib/render/toSql.js +87 -11
- package/lib/render/utils/common.js +55 -9
- package/lib/render/utils/sql.js +3 -3
- package/lib/sql-identifier.js +6 -1
- package/lib/transform/{sql → db}/.eslintrc.json +0 -0
- package/lib/transform/{sql → db}/assertUnique.js +7 -8
- package/lib/transform/{sql → db}/constraints.js +35 -20
- package/lib/transform/db/draft.js +353 -0
- package/lib/transform/db/expansion.js +582 -0
- package/lib/transform/db/flattening.js +325 -0
- package/lib/transform/{sql → db}/groupByOrderBy.js +8 -16
- package/lib/transform/{sql → db}/helpers.js +0 -0
- package/lib/transform/{sql → db}/transformExists.js +256 -60
- package/lib/transform/forHanaNew.js +216 -765
- package/lib/transform/forOdataNew.js +60 -56
- package/lib/transform/localized.js +48 -26
- package/lib/transform/odata/attachPath.js +19 -4
- package/lib/transform/odata/expandStructKeysInAssociations.js +2 -2
- package/lib/transform/odata/generateForeignKeyElements.js +13 -12
- package/lib/transform/odata/referenceFlattener.js +60 -36
- package/lib/transform/odata/sortByAssociationDependency.js +4 -4
- package/lib/transform/odata/structuralPath.js +76 -0
- package/lib/transform/odata/structureFlattener.js +21 -22
- package/lib/transform/odata/toFinalBaseType.js +5 -5
- package/lib/transform/odata/typesExposure.js +27 -17
- package/lib/transform/odata/utils.js +2 -2
- package/lib/transform/transformUtilsNew.js +141 -77
- package/lib/transform/translateAssocsToJoins.js +17 -14
- package/lib/transform/universalCsnEnricher.js +67 -0
- package/lib/utils/file.js +0 -11
- package/lib/utils/moduleResolve.js +6 -8
- package/lib/utils/timetrace.js +6 -1
- package/package.json +2 -1
- package/lib/base/deepCopy.js +0 -66
- package/lib/json/walker.js +0 -26
- package/lib/utils/string.js +0 -17
package/lib/render/toSql.js
CHANGED
|
@@ -7,7 +7,7 @@ const {
|
|
|
7
7
|
forEachDefinition, getResultingName,
|
|
8
8
|
} = require('../model/csnUtils');
|
|
9
9
|
const {
|
|
10
|
-
renderFunc,
|
|
10
|
+
renderFunc, beautifyExprArray, cdsToSqlTypes, getHanaComment, hasHanaComment,
|
|
11
11
|
} = require('./utils/common');
|
|
12
12
|
const {
|
|
13
13
|
renderReferentialConstraint, getIdentifierUtils,
|
|
@@ -152,6 +152,24 @@ function toSqlDdl(csn, options) {
|
|
|
152
152
|
.join(', ');
|
|
153
153
|
return primaryKeys && `PRIMARY KEY(${primaryKeys})`;
|
|
154
154
|
},
|
|
155
|
+
/*
|
|
156
|
+
Render entity-comment modifications as HANA SQL.
|
|
157
|
+
*/
|
|
158
|
+
alterEntityComment(tableName, comment) {
|
|
159
|
+
return [ `COMMENT ON TABLE ${tableName} IS ${render.comment(comment)};` ];
|
|
160
|
+
},
|
|
161
|
+
/*
|
|
162
|
+
Render column-comment modifications as HANA SQL.
|
|
163
|
+
*/
|
|
164
|
+
alterColumnComment(tableName, columnName, comment) {
|
|
165
|
+
return [ `COMMENT ON COLUMN ${tableName}.${columnName} IS ${render.comment(comment)};` ];
|
|
166
|
+
},
|
|
167
|
+
/*
|
|
168
|
+
Render comment string.
|
|
169
|
+
*/
|
|
170
|
+
comment(comment) {
|
|
171
|
+
return comment && `'${comment.replace(/'/g, '\'\'')}'` || 'NULL';
|
|
172
|
+
},
|
|
155
173
|
/*
|
|
156
174
|
Concatenate multiple statements which are to be treated as one by the API caller.
|
|
157
175
|
*/
|
|
@@ -211,8 +229,9 @@ function toSqlDdl(csn, options) {
|
|
|
211
229
|
for (const extension of options && options.testMode ? sortCsn(csn.extensions) : csn.extensions) {
|
|
212
230
|
if (extension.extend) {
|
|
213
231
|
const artifactName = extension.extend;
|
|
214
|
-
const
|
|
215
|
-
|
|
232
|
+
const _artifact = csn.definitions[artifactName];
|
|
233
|
+
const env = { indent: '', _artifact };
|
|
234
|
+
renderArtifactExtensionInto(artifactName, _artifact, extension, resultObj, env);
|
|
216
235
|
}
|
|
217
236
|
}
|
|
218
237
|
}
|
|
@@ -223,7 +242,8 @@ function toSqlDdl(csn, options) {
|
|
|
223
242
|
for (const migration of options && options.testMode ? sortCsn(csn.migrations) : csn.migrations) {
|
|
224
243
|
if (migration.migrate) {
|
|
225
244
|
const artifactName = migration.migrate;
|
|
226
|
-
const
|
|
245
|
+
const _artifact = csn.definitions[artifactName];
|
|
246
|
+
const env = { indent: '', _artifact };
|
|
227
247
|
renderArtifactMigrationInto(artifactName, migration, resultObj, env);
|
|
228
248
|
}
|
|
229
249
|
}
|
|
@@ -367,10 +387,30 @@ function toSqlDdl(csn, options) {
|
|
|
367
387
|
return def.old.type === def.new.type &&
|
|
368
388
|
[ 'length', 'precision', 'scale' ].some(param => def.new[param] < def.old[param]);
|
|
369
389
|
}
|
|
390
|
+
function getEltStr(defVariant, eltName) {
|
|
391
|
+
return defVariant.target
|
|
392
|
+
? renderAssociationElement(eltName, defVariant, env)
|
|
393
|
+
: renderElement(artifactName, eltName, defVariant, null, null, env);
|
|
394
|
+
}
|
|
395
|
+
function getEltStrNoProp(defVariant, prop, eltName) {
|
|
396
|
+
const defNoProp = Object.assign({}, defVariant);
|
|
397
|
+
delete defNoProp[prop];
|
|
398
|
+
return getEltStr(defNoProp, eltName);
|
|
399
|
+
}
|
|
370
400
|
|
|
371
401
|
const tableName = renderArtifactName(artifactName);
|
|
372
402
|
|
|
373
|
-
//
|
|
403
|
+
// Change entity properties
|
|
404
|
+
if (migration.properties) {
|
|
405
|
+
for (const [ prop, def ] of Object.entries(migration.properties)) {
|
|
406
|
+
if (prop === 'doc') {
|
|
407
|
+
const alterComment = render.alterEntityComment(tableName, def.new);
|
|
408
|
+
addMigration(resultObj, artifactName, false, alterComment);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Drop columns (unsupported in sqlite)
|
|
374
414
|
if (migration.remove) {
|
|
375
415
|
const entries = Object.entries(migration.remove);
|
|
376
416
|
if (entries.length) {
|
|
@@ -389,12 +429,28 @@ function toSqlDdl(csn, options) {
|
|
|
389
429
|
}
|
|
390
430
|
}
|
|
391
431
|
|
|
392
|
-
// Change column
|
|
432
|
+
// Change column types (unsupported in sqlite)
|
|
393
433
|
if (migration.change) {
|
|
394
434
|
changeElementsDuplicateChecker.addArtifact(tableName, undefined, artifactName);
|
|
395
435
|
for (const [ eltName, def ] of Object.entries(migration.change)) {
|
|
396
436
|
const sqlId = quoteSqlId(eltName);
|
|
397
437
|
changeElementsDuplicateChecker.addElement(sqlId, undefined, eltName);
|
|
438
|
+
|
|
439
|
+
const eltStrOld = getEltStr(def.old, eltName);
|
|
440
|
+
const eltStrNew = getEltStr(def.new, eltName);
|
|
441
|
+
if (eltStrNew === eltStrOld)
|
|
442
|
+
return; // Prevent spurious migrations, where the column DDL does not change.
|
|
443
|
+
|
|
444
|
+
if (def.old.doc !== def.new.doc) {
|
|
445
|
+
const eltStrOldNoDoc = getEltStrNoProp(def.old, 'doc', eltName);
|
|
446
|
+
const eltStrNewNoDoc = getEltStrNoProp(def.new, 'doc', eltName);
|
|
447
|
+
if (eltStrOldNoDoc === eltStrNewNoDoc) { // only `doc` changed
|
|
448
|
+
const alterComment = render.alterColumnComment(tableName, sqlId, def.new.doc);
|
|
449
|
+
addMigration(resultObj, artifactName, false, alterComment);
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
398
454
|
if (options.sqlChangeMode === 'drop' || def.old.target || def.new.target || reducesTypeSize(def)) {
|
|
399
455
|
// Lossy change because either an association is removed and/or added, or the type size is reduced.
|
|
400
456
|
// Drop old element and re-add it in its new shape.
|
|
@@ -408,8 +464,7 @@ function toSqlDdl(csn, options) {
|
|
|
408
464
|
}
|
|
409
465
|
else {
|
|
410
466
|
// Lossless change: no associations directly affected, no size reduction.
|
|
411
|
-
|
|
412
|
-
addMigration(resultObj, artifactName, false, render.alterColumns(tableName, eltStr));
|
|
467
|
+
addMigration(resultObj, artifactName, false, render.alterColumns(tableName, eltStrNew));
|
|
413
468
|
}
|
|
414
469
|
}
|
|
415
470
|
}
|
|
@@ -425,6 +480,7 @@ function toSqlDdl(csn, options) {
|
|
|
425
480
|
* @param {object} env Render environment
|
|
426
481
|
*/
|
|
427
482
|
function renderEntityInto(artifactName, art, resultObj, env) {
|
|
483
|
+
env._artifact = art;
|
|
428
484
|
const childEnv = increaseIndent(env);
|
|
429
485
|
const hanaTc = art.technicalConfig && art.technicalConfig.hana;
|
|
430
486
|
let result = '';
|
|
@@ -512,6 +568,9 @@ function toSqlDdl(csn, options) {
|
|
|
512
568
|
if (options.toSql.dialect === 'hana')
|
|
513
569
|
renderIndexesInto(art.technicalConfig && art.technicalConfig.hana.indexes, artifactName, resultObj, env);
|
|
514
570
|
|
|
571
|
+
if (options.toSql.dialect === 'hana' && hasHanaComment(art, options, art))
|
|
572
|
+
result += ` COMMENT '${getHanaComment(art)}'`;
|
|
573
|
+
|
|
515
574
|
resultObj.hdbtable[artifactName] = result;
|
|
516
575
|
}
|
|
517
576
|
|
|
@@ -610,6 +669,9 @@ function toSqlDdl(csn, options) {
|
|
|
610
669
|
if (fzindex && options.toSql.dialect === 'hana')
|
|
611
670
|
result += ` ${renderExpr(fzindex, env)}`;
|
|
612
671
|
|
|
672
|
+
if (options.toSql.dialect === 'hana' && hasHanaComment(elm, options, env._artifact))
|
|
673
|
+
result += ` COMMENT '${getHanaComment(elm)}'`;
|
|
674
|
+
|
|
613
675
|
return result;
|
|
614
676
|
}
|
|
615
677
|
|
|
@@ -991,8 +1053,13 @@ function toSqlDdl(csn, options) {
|
|
|
991
1053
|
const viewName = renderArtifactName(artifactName);
|
|
992
1054
|
definitionsDuplicateChecker.addArtifact(art['@cds.persistence.name'], art && art.$location, artifactName);
|
|
993
1055
|
let result = `VIEW ${viewName}`;
|
|
1056
|
+
|
|
1057
|
+
if (options.toSql.dialect === 'hana' && hasHanaComment(art, options, art))
|
|
1058
|
+
result += ` COMMENT '${getHanaComment(art)}'`;
|
|
1059
|
+
|
|
994
1060
|
result += renderParameterDefinitions(artifactName, art.params);
|
|
995
1061
|
result += ` AS ${renderQuery(artifactName, getNormalizedQuery(art).query, env)}`;
|
|
1062
|
+
|
|
996
1063
|
const childEnv = increaseIndent(env);
|
|
997
1064
|
const associations = Object.keys(art.elements).filter(name => !!art.elements[name].target)
|
|
998
1065
|
.map(name => renderAssociationElement(name, art.elements[name], childEnv))
|
|
@@ -1002,6 +1069,7 @@ function toSqlDdl(csn, options) {
|
|
|
1002
1069
|
result += `${env.indent}\nWITH ASSOCIATIONS (\n${associations}\n`;
|
|
1003
1070
|
result += `${env.indent})`;
|
|
1004
1071
|
}
|
|
1072
|
+
|
|
1005
1073
|
return result;
|
|
1006
1074
|
}
|
|
1007
1075
|
|
|
@@ -1020,8 +1088,15 @@ function toSqlDdl(csn, options) {
|
|
|
1020
1088
|
const p = params[pn];
|
|
1021
1089
|
if (p.notNull === true || p.notNull === false)
|
|
1022
1090
|
info(null, [ 'definitions', artifactName, 'params', pn ], 'Not Null constraints on SQL view parameters are not allowed and are ignored');
|
|
1023
|
-
|
|
1024
|
-
|
|
1091
|
+
// do not quote parameter identifiers for naming mode "quoted" / "hdbcds"
|
|
1092
|
+
// this would be an incompatible change, as non-uppercased, quoted identifiers
|
|
1093
|
+
// are rejected by the HANA compiler.
|
|
1094
|
+
let pIdentifier;
|
|
1095
|
+
if (options.toSql.names === 'quoted' || options.toSql.names === 'hdbcds')
|
|
1096
|
+
pIdentifier = prepareIdentifier(pn);
|
|
1097
|
+
else
|
|
1098
|
+
pIdentifier = quoteSqlId(pn);
|
|
1099
|
+
let pstr = `IN ${pIdentifier} ${renderTypeReference(artifactName, pn, p)}`;
|
|
1025
1100
|
if (p.default)
|
|
1026
1101
|
pstr += ` DEFAULT ${renderExpr(p.default)}`;
|
|
1027
1102
|
|
|
@@ -1268,7 +1343,8 @@ function toSqlDdl(csn, options) {
|
|
|
1268
1343
|
function renderExpr(x, env, inline = true, nestedExpr = false) {
|
|
1269
1344
|
// Compound expression
|
|
1270
1345
|
if (Array.isArray(x)) {
|
|
1271
|
-
|
|
1346
|
+
const tokens = x.map(item => renderExpr(item, env, inline, nestedExpr));
|
|
1347
|
+
return beautifyExprArray(tokens);
|
|
1272
1348
|
}
|
|
1273
1349
|
else if (typeof x === 'object' && x !== null) {
|
|
1274
1350
|
if (nestedExpr && x.cast && x.cast.type)
|
|
@@ -18,6 +18,9 @@ const {
|
|
|
18
18
|
hasValidSkipOrExists, forEachDefinition, getNamespace, getUnderscoredName,
|
|
19
19
|
} = require('../../model/csnUtils');
|
|
20
20
|
|
|
21
|
+
const { implicitAs } = require('../../model/csnRefs');
|
|
22
|
+
|
|
23
|
+
|
|
21
24
|
/**
|
|
22
25
|
* Render the given function
|
|
23
26
|
*
|
|
@@ -50,19 +53,14 @@ function funcWithoutParen( node, dialect ) {
|
|
|
50
53
|
}
|
|
51
54
|
|
|
52
55
|
/**
|
|
53
|
-
* Process
|
|
56
|
+
* Process already rendered expression parts by joining them nicely
|
|
54
57
|
*
|
|
55
|
-
* @param {Array}
|
|
56
|
-
* @param {Function} renderExpr Function to render expressions
|
|
57
|
-
* @param {CdlRenderEnvironment} env Environment for rendering
|
|
58
|
-
* @param {boolean} inline Wether to render the expression inline
|
|
59
|
-
* @param {boolean} inExpr Wether to render as if a subexpression
|
|
58
|
+
* @param {Array} tokens Array of expression tokens
|
|
60
59
|
*
|
|
61
60
|
* @returns {string} The rendered xpr
|
|
62
61
|
*/
|
|
63
|
-
function
|
|
62
|
+
function beautifyExprArray(tokens) {
|
|
64
63
|
// Simply concatenate array parts with spaces (with a tiny bit of beautification)
|
|
65
|
-
const tokens = xpr.map(item => renderExpr(item, env, inline, inExpr));
|
|
66
64
|
let result = '';
|
|
67
65
|
for (let i = 0; i < tokens.length; i++) {
|
|
68
66
|
result += tokens[i];
|
|
@@ -283,6 +281,26 @@ const cdsToSqlTypes = {
|
|
|
283
281
|
},
|
|
284
282
|
};
|
|
285
283
|
|
|
284
|
+
/**
|
|
285
|
+
* Get the element matching the column
|
|
286
|
+
*
|
|
287
|
+
* @param {CSN.Elements} elements Elements of a query
|
|
288
|
+
* @param {CSN.Column} column Column from the same query
|
|
289
|
+
* @returns {CSN.Element}
|
|
290
|
+
*/
|
|
291
|
+
function findElement(elements, column) {
|
|
292
|
+
if (!elements)
|
|
293
|
+
return undefined;
|
|
294
|
+
if (column.as)
|
|
295
|
+
return elements[column.as];
|
|
296
|
+
else if (column.ref)
|
|
297
|
+
return elements[implicitAs(column.ref)];
|
|
298
|
+
else if (column.func)
|
|
299
|
+
return elements[column.func];
|
|
300
|
+
|
|
301
|
+
return undefined;
|
|
302
|
+
}
|
|
303
|
+
|
|
286
304
|
/**
|
|
287
305
|
* If there is a context A and a context A.B.C without a definition A.B, create an
|
|
288
306
|
* intermediate context A.B to keep the context hierarchy intact.
|
|
@@ -313,6 +331,30 @@ function addIntermediateContexts(csn, killList) {
|
|
|
313
331
|
}
|
|
314
332
|
}
|
|
315
333
|
|
|
334
|
+
/**
|
|
335
|
+
* Check wether the given artifact or element has a comment that needs to be rendered.
|
|
336
|
+
* Things annotated with @cds.persistence.journal (for HANA SQL), should not get a comment.
|
|
337
|
+
*
|
|
338
|
+
* @param {CSN.Artifact} obj
|
|
339
|
+
* @param {CSN.Options} options To check for `disableHanaComments`
|
|
340
|
+
* @returns {boolean}
|
|
341
|
+
*/
|
|
342
|
+
function hasHanaComment(obj, options) {
|
|
343
|
+
return !options.disableHanaComments && typeof obj.doc === 'string';
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Return the comment of the given artifact or element.
|
|
347
|
+
* Uses the first block (everything up to the first empty line (double \n)).
|
|
348
|
+
* Remove leading/trailing whitespace.
|
|
349
|
+
*
|
|
350
|
+
* @param {CSN.Artifact|CSN.Element} obj
|
|
351
|
+
* @returns {string}
|
|
352
|
+
* @todo Warning/info to user?
|
|
353
|
+
*/
|
|
354
|
+
function getHanaComment(obj) {
|
|
355
|
+
return obj.doc.split('\n\n')[0].trim().replace(/'/g, "''");
|
|
356
|
+
}
|
|
357
|
+
|
|
316
358
|
/**
|
|
317
359
|
* @typedef CdlRenderEnvironment Rendering environment used throughout the render process.
|
|
318
360
|
*
|
|
@@ -331,10 +373,14 @@ function addIntermediateContexts(csn, killList) {
|
|
|
331
373
|
|
|
332
374
|
module.exports = {
|
|
333
375
|
renderFunc,
|
|
334
|
-
|
|
376
|
+
beautifyExprArray,
|
|
335
377
|
getNamespace,
|
|
336
378
|
getRealName,
|
|
337
379
|
addIntermediateContexts,
|
|
338
380
|
addContextMarkers,
|
|
339
381
|
cdsToSqlTypes,
|
|
382
|
+
hasHanaComment,
|
|
383
|
+
getHanaComment,
|
|
384
|
+
findElement,
|
|
385
|
+
funcWithoutParen,
|
|
340
386
|
};
|
package/lib/render/utils/sql.js
CHANGED
|
@@ -20,9 +20,9 @@ const { smartId, delimitedId } = require('../../sql-identifier');
|
|
|
20
20
|
function renderReferentialConstraint(constraint, indent, toUpperCase, csn, options, alterConstraint) {
|
|
21
21
|
let quoteId;
|
|
22
22
|
// for to.hana we can't utilize the sql identifier utils
|
|
23
|
-
if (options.
|
|
23
|
+
if (options.transformation === 'hdbcds') {
|
|
24
24
|
quoteId = (id) => {
|
|
25
|
-
if (options.
|
|
25
|
+
if (options.sqlMapping === 'plain')
|
|
26
26
|
return id.replace(/\./g, '_');
|
|
27
27
|
return `"${id}"`;
|
|
28
28
|
};
|
|
@@ -38,7 +38,7 @@ function renderReferentialConstraint(constraint, indent, toUpperCase, csn, optio
|
|
|
38
38
|
constraint.parentTable = constraint.parentTable.toUpperCase();
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
const renderAsHdbconstraint = options.
|
|
41
|
+
const renderAsHdbconstraint = options.transformation === 'hdbcds' ||
|
|
42
42
|
(options.toSql && options.toSql.src === 'hdi') ||
|
|
43
43
|
(options.manageConstraints && options.manageConstraints.src === 'hdi');
|
|
44
44
|
|
package/lib/sql-identifier.js
CHANGED
|
@@ -51,7 +51,12 @@ const sqlDialects = {
|
|
|
51
51
|
effectiveName: name => name.toUpperCase(),
|
|
52
52
|
asDelimitedId: name => `"${ name.replace(/"/g, '""')}"`,
|
|
53
53
|
},
|
|
54
|
-
|
|
54
|
+
hdbcds: {
|
|
55
|
+
regularRegex: /^[A-Za-z_][A-Za-z_0-9]*$/,
|
|
56
|
+
reservedWords: keywords.hdbcds,
|
|
57
|
+
effectiveName: name => name,
|
|
58
|
+
asDelimitedId: name => `"${ name.replace(/"/g, '""')}"`,
|
|
59
|
+
},
|
|
55
60
|
};
|
|
56
61
|
|
|
57
62
|
function smartId( name, dialect ) {
|
|
File without changes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { forEachDefinition,
|
|
3
|
+
const { forEachDefinition, hasAnnotationValue } = require('../../model/csnUtils');
|
|
4
4
|
const { getTransformers } = require('../transformUtilsNew');
|
|
5
5
|
const { setProp } = require('../../base/model');
|
|
6
6
|
|
|
@@ -31,7 +31,7 @@ function processAssertUnique(csn, options, error, info) {
|
|
|
31
31
|
*/
|
|
32
32
|
function handleAssertUnique(artifact, artifactName) {
|
|
33
33
|
// operate only on real entities that are not abstract
|
|
34
|
-
if (artifact.abstract || (artifact.kind !== 'entity' || (artifact.query || artifact.projection) && !
|
|
34
|
+
if (artifact.abstract || (artifact.kind !== 'entity' || (artifact.query || artifact.projection) && !hasAnnotationValue(artifact, '@cds.persistence.table')))
|
|
35
35
|
return;
|
|
36
36
|
const constraintXrefs = Object.create(null);
|
|
37
37
|
const constraintDict = Object.create(null);
|
|
@@ -260,7 +260,7 @@ function processAssertUnique(csn, options, error, info) {
|
|
|
260
260
|
* If the output format is SQL, the toSql renderer is responsible
|
|
261
261
|
* to render the table constraints from the constraint dictionary.
|
|
262
262
|
*
|
|
263
|
-
* If options.
|
|
263
|
+
* If options.transformation === 'hdbcds', no path flattening is done and association
|
|
264
264
|
* paths are replaced with the foreign key paths by simply
|
|
265
265
|
* concatenating the foreign key paths (available in element.keys).
|
|
266
266
|
*
|
|
@@ -279,11 +279,10 @@ function rewriteUniqueConstraints(csn, options, pathDelimiter) {
|
|
|
279
279
|
* @param {CSN.Artifact} artifact
|
|
280
280
|
*/
|
|
281
281
|
function rewrite(artifact) {
|
|
282
|
-
const naming = options.forHana.names || options.toSql.names;
|
|
283
282
|
if (artifact.$tableConstraints && artifact.$tableConstraints.unique) {
|
|
284
283
|
const uniqueConstraints = artifact.$tableConstraints.unique;
|
|
285
284
|
// it's safe to add the tc here
|
|
286
|
-
if (options.
|
|
285
|
+
if (options.transformation === 'hdbcds') {
|
|
287
286
|
if (!artifact.technicalConfig)
|
|
288
287
|
artifact.technicalConfig = Object.create(null);
|
|
289
288
|
|
|
@@ -302,12 +301,12 @@ function rewriteUniqueConstraints(csn, options, pathDelimiter) {
|
|
|
302
301
|
c.forEach((cpath) => {
|
|
303
302
|
// If 'toSql' or 'toHana' and naming !== 'hdbcds'
|
|
304
303
|
// concatenate path refs with appropriate delimiter
|
|
305
|
-
if (
|
|
304
|
+
if (options.transformation !== 'hdbcds' || (options.transformation === 'hdbcds' && options.sqlMapping !== 'hdbcds'))
|
|
306
305
|
cpath.ref = [ cpath.ref.map(p => p.id).join( pathDelimiter ) ];
|
|
307
306
|
|
|
308
307
|
// Foreign key substitution
|
|
309
308
|
if (cpath._art.target) {
|
|
310
|
-
if (
|
|
309
|
+
if (options.transformation !== 'hdbcds' || (options.transformation === 'hdbcds' && options.sqlMapping !== 'hdbcds')) {
|
|
311
310
|
// read out new association and use $generatedFieldName
|
|
312
311
|
// cpath._art still refers to the assoc definition
|
|
313
312
|
// before the A2J transformation. This assoc
|
|
@@ -332,7 +331,7 @@ function rewriteUniqueConstraints(csn, options, pathDelimiter) {
|
|
|
332
331
|
uniqueConstraints[uniqueConstraint] = rewrittenPaths;
|
|
333
332
|
|
|
334
333
|
// now add the index for HANA CDS
|
|
335
|
-
if (options.
|
|
334
|
+
if (options.transformation === 'hdbcds') {
|
|
336
335
|
const index = [ 'unique', 'index', { ref: [ uniqueConstraint ] }, 'on', '(' ];
|
|
337
336
|
let i = 0;
|
|
338
337
|
for (const constraint of rewrittenPaths) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { forEachDefinition } = require('../../base/model');
|
|
4
|
-
const { forAllElements,
|
|
4
|
+
const { forAllElements, hasAnnotationValue, getResultingName } = require('../../model/csnUtils');
|
|
5
5
|
const { csnRefs } = require('../../model/csnRefs');
|
|
6
6
|
|
|
7
7
|
const COMPOSITION = 'cds.Composition';
|
|
@@ -12,7 +12,6 @@ const ASSOCIATION = 'cds.Association';
|
|
|
12
12
|
*
|
|
13
13
|
* @param {CSN.Model} csn
|
|
14
14
|
* @param {CSN.Options} options are used to modify the validate / enforced flag on the constraints
|
|
15
|
-
*
|
|
16
15
|
*/
|
|
17
16
|
function createReferentialConstraints(csn, options) {
|
|
18
17
|
let validated = true;
|
|
@@ -46,7 +45,7 @@ function createReferentialConstraints(csn, options) {
|
|
|
46
45
|
if (element.type === ASSOCIATION ||
|
|
47
46
|
element.type === COMPOSITION && treatCompositionLikeAssociation(element)) {
|
|
48
47
|
associations.push(() => {
|
|
49
|
-
foreignKeyConstraintForAssociation(element, elements, path.concat([ elementName ]));
|
|
48
|
+
foreignKeyConstraintForAssociation(element, elements, path.concat([ elementName ]), elementName);
|
|
50
49
|
});
|
|
51
50
|
}
|
|
52
51
|
}
|
|
@@ -81,12 +80,14 @@ function createReferentialConstraints(csn, options) {
|
|
|
81
80
|
// mark each dependent key referenced in the on-condition (in target entity)
|
|
82
81
|
const dependentKeys = Array.from(elementsOfTargetSide(onCondition, csn.definitions[composition.target].elements));
|
|
83
82
|
const parentKeys = Array.from(elementsOfSourceSide(onCondition, elements));
|
|
83
|
+
const { backlinkName } = composition.$selfOnCondition || {};
|
|
84
84
|
// sanity check; do not generate constraints for on-conditions like "dependent.idOne = id AND dependent.idTwo = id"
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
// also: no constraints for compositions of many w/o backlink
|
|
86
|
+
if (dependentKeys.length === parentKeys.length && backlinkName)
|
|
87
|
+
attachConstraintsToDependentKeys(dependentKeys, parentKeys, path[path.length - 3], path, backlinkName, 'CASCADE');
|
|
87
88
|
}
|
|
88
89
|
}
|
|
89
|
-
else if (!onCondition && composition.keys) {
|
|
90
|
+
else if (!onCondition && composition.keys.length > 0) {
|
|
90
91
|
throw new Error('Please debug me, an on-condition was expected here, but only found keys');
|
|
91
92
|
}
|
|
92
93
|
}
|
|
@@ -98,8 +99,9 @@ function createReferentialConstraints(csn, options) {
|
|
|
98
99
|
* @param {CSN.Association} association for that a constraint should be generated
|
|
99
100
|
* @param {CSN.Elements} elements of parent entity.
|
|
100
101
|
* @param {CSN.Path} path
|
|
102
|
+
* @param {string} assocName passed through as proper constraint suffix
|
|
101
103
|
*/
|
|
102
|
-
function foreignKeyConstraintForAssociation(association, elements, path) {
|
|
104
|
+
function foreignKeyConstraintForAssociation(association, elements, path, assocName) {
|
|
103
105
|
const associationTarget = csn.definitions[association.target];
|
|
104
106
|
if (skipConstraintGeneration(associationTarget, association))
|
|
105
107
|
return;
|
|
@@ -112,9 +114,9 @@ function createReferentialConstraints(csn, options) {
|
|
|
112
114
|
const parentKeys = Array.from(elementsOfTargetSide(onCondition, associationTarget.elements));
|
|
113
115
|
// sanity check; do not generate constraints for on-conditions like "dependent.idOne = id AND dependent.idTwo = id"
|
|
114
116
|
if (dependentKeys.length === parentKeys.length)
|
|
115
|
-
attachConstraintsToDependentKeys(dependentKeys, parentKeys, association.target, path);
|
|
117
|
+
attachConstraintsToDependentKeys(dependentKeys, parentKeys, association.target, path, assocName);
|
|
116
118
|
}
|
|
117
|
-
else if (!onCondition && association.keys) {
|
|
119
|
+
else if (!onCondition && association.keys.length > 0) {
|
|
118
120
|
throw new Error('Please debug me, an on-condition was expected here, but only found keys');
|
|
119
121
|
}
|
|
120
122
|
}
|
|
@@ -128,9 +130,10 @@ function createReferentialConstraints(csn, options) {
|
|
|
128
130
|
* @param {Array} parentKeys array holding parent keys in the format [['key1', 'value1'], [...], ...]
|
|
129
131
|
* @param {CSN.PathSegment} parentTable the sql-table where the foreign key constraints will be pointing to
|
|
130
132
|
* @param {CSN.Path} path
|
|
133
|
+
* @param {string | null} constraintIdentifierSuffix name of the association / the backlink association
|
|
131
134
|
* @param {string} onDelete the on delete rule which should be applied. Default for associations is 'RESTRICT'
|
|
132
135
|
*/
|
|
133
|
-
function attachConstraintsToDependentKeys(dependentKeys, parentKeys, parentTable, path, onDelete = 'RESTRICT') {
|
|
136
|
+
function attachConstraintsToDependentKeys(dependentKeys, parentKeys, parentTable, path, constraintIdentifierSuffix, onDelete = 'RESTRICT') {
|
|
134
137
|
while (dependentKeys.length > 0) {
|
|
135
138
|
const dependentKeyValuePair = dependentKeys.pop();
|
|
136
139
|
const dependentKey = dependentKeyValuePair[1];
|
|
@@ -144,7 +147,7 @@ function createReferentialConstraints(csn, options) {
|
|
|
144
147
|
parentKey: parentKeyName,
|
|
145
148
|
parentTable,
|
|
146
149
|
sourceAssociation: path[path.length - 1],
|
|
147
|
-
nameSuffix:
|
|
150
|
+
nameSuffix: constraintIdentifierSuffix || 'up_',
|
|
148
151
|
onDelete,
|
|
149
152
|
validated,
|
|
150
153
|
enforced,
|
|
@@ -220,8 +223,8 @@ function createReferentialConstraints(csn, options) {
|
|
|
220
223
|
* @returns {boolean}
|
|
221
224
|
*/
|
|
222
225
|
function skipConstraintGeneration(parent, element) {
|
|
223
|
-
if (
|
|
224
|
-
|
|
226
|
+
if (hasAnnotationValue(element, '@assert.integrity', false) ||
|
|
227
|
+
hasAnnotationValue(element, '@cds.persistency.assert.integrity', false)) {
|
|
225
228
|
// in case of managed composition, the 'up_' link should not result in a constraint
|
|
226
229
|
const target = csn.definitions[element.target];
|
|
227
230
|
const { up_ } = target.elements;
|
|
@@ -230,16 +233,18 @@ function createReferentialConstraints(csn, options) {
|
|
|
230
233
|
return true;
|
|
231
234
|
}
|
|
232
235
|
|
|
233
|
-
if (element.$skipReferentialConstraintForUp_)
|
|
236
|
+
if (element.$skipReferentialConstraintForUp_) {
|
|
237
|
+
delete element.$skipReferentialConstraintForUp_;
|
|
234
238
|
return true;
|
|
239
|
+
}
|
|
235
240
|
|
|
236
|
-
if (
|
|
237
|
-
|
|
241
|
+
if (hasAnnotationValue(parent, '@cds.persistence.skip', true) ||
|
|
242
|
+
hasAnnotationValue(parent, '@cds.persistence.exists', true) ||
|
|
238
243
|
parent.query)
|
|
239
244
|
return true;
|
|
240
245
|
|
|
241
246
|
// '@cds.persistency.assert.integrity: true' supersedes global switch
|
|
242
|
-
if (!
|
|
247
|
+
if (!hasAnnotationValue(element, '@cds.persistency.assert.integrity', true) && options.forHana.skipDbConstraints)
|
|
243
248
|
return true;
|
|
244
249
|
|
|
245
250
|
return false;
|
|
@@ -352,11 +357,21 @@ function createReferentialConstraints(csn, options) {
|
|
|
352
357
|
* The constraints will thus be generated in the entity containing the composition and not in the target entity.
|
|
353
358
|
*
|
|
354
359
|
* @param {CSN.Composition} composition the composition which might be treated like an association
|
|
355
|
-
* @returns {
|
|
360
|
+
* @returns {boolean} true if the composition should be treated as an association in regards to foreign key constraints
|
|
356
361
|
*/
|
|
357
362
|
function treatCompositionLikeAssociation(composition) {
|
|
358
|
-
|
|
359
|
-
|
|
363
|
+
return Boolean((isToOne(composition) && !composition.$selfOnCondition) || composition.keys);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* returns true if the association/composition has a max target cardinality of one
|
|
368
|
+
*
|
|
369
|
+
* @param {CSN.Element} assocOrComposition
|
|
370
|
+
* @returns {boolean}
|
|
371
|
+
*/
|
|
372
|
+
function isToOne(assocOrComposition) {
|
|
373
|
+
const { min, max } = assocOrComposition.cardinality || {};
|
|
374
|
+
return !min && !max || max === 1;
|
|
360
375
|
}
|
|
361
376
|
}
|
|
362
377
|
|