@sap/cds-compiler 2.5.0 → 2.10.4
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 +191 -9
- package/bin/cdsc.js +2 -2
- package/doc/CHANGELOG_BETA.md +33 -3
- package/lib/api/main.js +29 -101
- package/lib/api/options.js +15 -11
- package/lib/api/validate.js +12 -8
- package/lib/backends.js +0 -81
- package/lib/base/keywords.js +32 -2
- package/lib/base/message-registry.js +63 -9
- package/lib/base/messages.js +63 -21
- package/lib/base/model.js +2 -3
- package/lib/checks/defaultValues.js +27 -2
- package/lib/checks/elements.js +1 -6
- 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 +25 -7
- 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 +60 -7
- package/lib/compiler/assert-consistency.js +16 -7
- package/lib/compiler/builtins.js +2 -0
- package/lib/compiler/checks.js +6 -4
- package/lib/compiler/definer.js +99 -42
- package/lib/compiler/index.js +73 -27
- package/lib/compiler/resolver.js +288 -157
- package/lib/compiler/shared.js +31 -11
- package/lib/edm/annotations/genericTranslation.js +182 -186
- package/lib/edm/csn2edm.js +103 -108
- package/lib/edm/edm.js +18 -21
- package/lib/edm/edmPreprocessor.js +361 -114
- package/lib/edm/edmUtils.js +103 -33
- package/lib/gen/Dictionary.json +22 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +12 -1
- package/lib/gen/language.tokens +57 -53
- package/lib/gen/languageLexer.interp +10 -1
- package/lib/gen/languageLexer.js +770 -744
- package/lib/gen/languageLexer.tokens +49 -46
- package/lib/gen/languageParser.js +4713 -4279
- package/lib/json/from-csn.js +103 -45
- package/lib/json/to-csn.js +296 -117
- package/lib/language/antlrParser.js +4 -3
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +21 -12
- package/lib/language/language.g4 +99 -31
- package/lib/main.d.ts +81 -3
- package/lib/main.js +30 -7
- package/lib/model/api.js +78 -0
- package/lib/model/csnRefs.js +329 -142
- package/lib/model/csnUtils.js +235 -58
- package/lib/model/enrichCsn.js +18 -1
- package/lib/model/revealInternalProperties.js +2 -1
- package/lib/modelCompare/compare.js +37 -20
- package/lib/optionProcessor.js +9 -3
- package/lib/render/.eslintrc.json +4 -1
- package/lib/render/DuplicateChecker.js +8 -5
- package/lib/render/toCdl.js +112 -33
- package/lib/render/toHdbcds.js +134 -64
- package/lib/render/toSql.js +91 -38
- package/lib/render/utils/common.js +8 -13
- package/lib/render/utils/sql.js +3 -3
- package/lib/sql-identifier.js +6 -1
- package/lib/transform/db/assertUnique.js +5 -6
- package/lib/transform/db/constraints.js +29 -13
- package/lib/transform/db/draft.js +8 -6
- package/lib/transform/db/expansion.js +582 -0
- package/lib/transform/db/flattening.js +325 -0
- package/lib/transform/db/groupByOrderBy.js +2 -2
- package/lib/transform/db/transformExists.js +284 -63
- package/lib/transform/forHanaNew.js +98 -381
- package/lib/transform/forOdataNew.js +21 -22
- package/lib/transform/localized.js +37 -10
- package/lib/transform/odata/attachPath.js +19 -4
- package/lib/transform/odata/generateForeignKeyElements.js +11 -10
- package/lib/transform/odata/referenceFlattener.js +60 -39
- package/lib/transform/odata/sortByAssociationDependency.js +2 -2
- package/lib/transform/odata/structuralPath.js +72 -0
- package/lib/transform/odata/structureFlattener.js +19 -18
- package/lib/transform/odata/typesExposure.js +22 -12
- package/lib/transform/transformUtilsNew.js +134 -78
- 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/package.json +1 -1
- package/lib/json/walker.js +0 -26
- package/lib/transform/sqlite +0 -0
- 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
|
}
|
|
@@ -365,12 +385,32 @@ function toSqlDdl(csn, options) {
|
|
|
365
385
|
function reducesTypeSize(def) {
|
|
366
386
|
// HANA does not allow decreasing the value of any of those type parameters.
|
|
367
387
|
return def.old.type === def.new.type &&
|
|
368
|
-
|
|
388
|
+
[ 'length', 'precision', 'scale' ].some(param => def.new[param] < def.old[param]);
|
|
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);
|
|
369
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,22 +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);
|
|
398
438
|
|
|
399
|
-
const eltStrOld = def.old
|
|
400
|
-
|
|
401
|
-
: renderElement(artifactName, eltName, def.old, null, null, env);
|
|
402
|
-
const eltStrNew = def.new.target
|
|
403
|
-
? renderAssociationElement(eltName, def.new, env)
|
|
404
|
-
: renderElement(artifactName, eltName, def.new, null, null, env);
|
|
439
|
+
const eltStrOld = getEltStr(def.old, eltName);
|
|
440
|
+
const eltStrNew = getEltStr(def.new, eltName);
|
|
405
441
|
if (eltStrNew === eltStrOld)
|
|
406
442
|
return; // Prevent spurious migrations, where the column DDL does not change.
|
|
407
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
|
+
|
|
408
454
|
if (options.sqlChangeMode === 'drop' || def.old.target || def.new.target || reducesTypeSize(def)) {
|
|
409
455
|
// Lossy change because either an association is removed and/or added, or the type size is reduced.
|
|
410
456
|
// Drop old element and re-add it in its new shape.
|
|
@@ -479,12 +525,12 @@ function toSqlDdl(csn, options) {
|
|
|
479
525
|
referentialConstraints[fileName] = renderReferentialConstraint(referentialConstraint, childEnv.indent, false, csn, options);
|
|
480
526
|
});
|
|
481
527
|
if (renderReferentialConstraintsAsHdbconstraint) {
|
|
482
|
-
Object.entries(referentialConstraints).forEach(
|
|
528
|
+
Object.entries(referentialConstraints).forEach(([ fileName, constraint ]) => {
|
|
483
529
|
resultObj.hdbconstraint[fileName] = constraint;
|
|
484
530
|
});
|
|
485
531
|
}
|
|
486
532
|
else {
|
|
487
|
-
Object.values(referentialConstraints).forEach(
|
|
533
|
+
Object.values(referentialConstraints).forEach((constraint) => {
|
|
488
534
|
result += `,\n${constraint}`;
|
|
489
535
|
});
|
|
490
536
|
}
|
|
@@ -500,8 +546,7 @@ function toSqlDdl(csn, options) {
|
|
|
500
546
|
= `UNIQUE INVERTED INDEX ${renderArtifactName(`${artifactName}_${cn}`)} ON ${tableName} (${c.map(cpath => quoteSqlId(cpath.ref[0])).join(', ')})`;
|
|
501
547
|
}
|
|
502
548
|
else {
|
|
503
|
-
result += `,\n${childEnv.indent}CONSTRAINT ${renderArtifactName(`${artifactName}_${cn}`)} UNIQUE (${
|
|
504
|
-
c.map(cpath => quoteSqlId(cpath.ref[0])).join(', ')})`;
|
|
549
|
+
result += `,\n${childEnv.indent}CONSTRAINT ${renderArtifactName(`${artifactName}_${cn}`)} UNIQUE (${c.map(cpath => quoteSqlId(cpath.ref[0])).join(', ')})`;
|
|
505
550
|
}
|
|
506
551
|
}
|
|
507
552
|
result += `${env.indent}\n)`;
|
|
@@ -613,8 +658,7 @@ function toSqlDdl(csn, options) {
|
|
|
613
658
|
if (duplicateChecker)
|
|
614
659
|
duplicateChecker.addElement(quotedElementName, elm.$location, elementName);
|
|
615
660
|
|
|
616
|
-
let result = `${env.indent + quotedElementName} ${
|
|
617
|
-
renderTypeReference(artifactName, elementName, elm)
|
|
661
|
+
let result = `${env.indent + quotedElementName} ${renderTypeReference(artifactName, elementName, elm)
|
|
618
662
|
}${renderNullability(elm, true)}`;
|
|
619
663
|
if (elm.default)
|
|
620
664
|
result += ` DEFAULT ${renderExpr(elm.default, env)}`;
|
|
@@ -910,7 +954,7 @@ function toSqlDdl(csn, options) {
|
|
|
910
954
|
// An empty actual parameter list is rendered as `()`.
|
|
911
955
|
const ref = csn.definitions[path.ref[0].id] || csn.definitions[path.ref[0]];
|
|
912
956
|
if (ref && ref.params) {
|
|
913
|
-
result += `(${renderArgs(path.ref[0]
|
|
957
|
+
result += `(${renderArgs(path.ref[0] || {}, '=>', env, syntax)})`;
|
|
914
958
|
}
|
|
915
959
|
else if ([ 'udf' ].includes(syntax)) {
|
|
916
960
|
// if syntax is user defined function, render empty argument list
|
|
@@ -932,21 +976,23 @@ function toSqlDdl(csn, options) {
|
|
|
932
976
|
* Render function arguments or view parameters (positional if array, named if object/dict),
|
|
933
977
|
* using 'sep' as separator for positional parameters
|
|
934
978
|
*
|
|
935
|
-
* @param {
|
|
979
|
+
* @param {object} node with `args` to render
|
|
936
980
|
* @param {string} sep Separator between args
|
|
937
981
|
* @param {object} env Render environment
|
|
938
982
|
* @param {string} syntax Some magic A2J paramter - for calcview parameter rendering
|
|
939
983
|
* @returns {string} Rendered arguments
|
|
940
984
|
* @throws Throws if args is not an array or object.
|
|
941
985
|
*/
|
|
942
|
-
function renderArgs(
|
|
986
|
+
function renderArgs(node, sep, env, syntax) {
|
|
987
|
+
const args = node.args ? node.args : {};
|
|
943
988
|
// Positional arguments
|
|
944
989
|
if (Array.isArray(args))
|
|
945
990
|
return args.map(arg => renderExpr(arg, env)).join(', ');
|
|
946
991
|
|
|
947
992
|
// Named arguments (object/dict)
|
|
948
993
|
else if (typeof args === 'object')
|
|
949
|
-
|
|
994
|
+
// if this is a function param which is not a reference to the model, we must not quote it
|
|
995
|
+
return Object.keys(args).map(key => `${node.func ? key : decorateParameter(key, syntax)} ${sep} ${renderExpr(args[key], env)}`).join(', ');
|
|
950
996
|
|
|
951
997
|
|
|
952
998
|
throw new Error(`Unknown args: ${JSON.stringify(args)}`);
|
|
@@ -1042,8 +1088,15 @@ function toSqlDdl(csn, options) {
|
|
|
1042
1088
|
const p = params[pn];
|
|
1043
1089
|
if (p.notNull === true || p.notNull === false)
|
|
1044
1090
|
info(null, [ 'definitions', artifactName, 'params', pn ], 'Not Null constraints on SQL view parameters are not allowed and are ignored');
|
|
1045
|
-
|
|
1046
|
-
|
|
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)}`;
|
|
1047
1100
|
if (p.default)
|
|
1048
1101
|
pstr += ` DEFAULT ${renderExpr(p.default)}`;
|
|
1049
1102
|
|
|
@@ -1098,12 +1151,11 @@ function toSqlDdl(csn, options) {
|
|
|
1098
1151
|
const childEnv = increaseIndent(env);
|
|
1099
1152
|
result += `SELECT${select.distinct ? ' DISTINCT' : ''}`;
|
|
1100
1153
|
// FIXME: We probably also need to consider `excluding` here ?
|
|
1101
|
-
result += `\n${
|
|
1102
|
-
(select.
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
.join(',\n')}\n`;
|
|
1154
|
+
result += `\n${(select.columns || [ '*' ])
|
|
1155
|
+
.filter(col => !(select.mixin || Object.create(null))[firstPathStepId(col.ref)]) // No mixin columns
|
|
1156
|
+
.map(col => renderViewColumn(col, childEnv))
|
|
1157
|
+
.filter(s => s !== '')
|
|
1158
|
+
.join(',\n')}\n`;
|
|
1107
1159
|
result += `${env.indent}FROM ${renderViewSource(artifactName, select.from, env)}`;
|
|
1108
1160
|
if (select.where)
|
|
1109
1161
|
result += `\n${env.indent}WHERE ${renderExpr(select.where, env, true, true)}`;
|
|
@@ -1290,7 +1342,8 @@ function toSqlDdl(csn, options) {
|
|
|
1290
1342
|
function renderExpr(x, env, inline = true, nestedExpr = false) {
|
|
1291
1343
|
// Compound expression
|
|
1292
1344
|
if (Array.isArray(x)) {
|
|
1293
|
-
|
|
1345
|
+
const tokens = x.map(item => renderExpr(item, env, inline, nestedExpr));
|
|
1346
|
+
return beautifyExprArray(tokens);
|
|
1294
1347
|
}
|
|
1295
1348
|
else if (typeof x === 'object' && x !== null) {
|
|
1296
1349
|
if (nestedExpr && x.cast && x.cast.type)
|
|
@@ -1358,23 +1411,23 @@ function toSqlDdl(csn, options) {
|
|
|
1358
1411
|
case 'number':
|
|
1359
1412
|
case 'boolean':
|
|
1360
1413
|
case 'null':
|
|
1361
|
-
|
|
1414
|
+
// 17.42, NULL, TRUE
|
|
1362
1415
|
return String(x.val).toUpperCase();
|
|
1363
1416
|
case 'x':
|
|
1364
|
-
|
|
1417
|
+
// x'f000'
|
|
1365
1418
|
return `${x.literal}'${x.val}'`;
|
|
1366
1419
|
case 'date':
|
|
1367
1420
|
case 'time':
|
|
1368
1421
|
case 'timestamp':
|
|
1369
1422
|
if (options.toSql.dialect === 'sqlite') {
|
|
1370
|
-
|
|
1423
|
+
// simple string literal '2017-11-02'
|
|
1371
1424
|
return `'${x.val}'`;
|
|
1372
1425
|
}
|
|
1373
1426
|
// date'2017-11-02'
|
|
1374
1427
|
return `${x.literal}'${x.val}'`;
|
|
1375
1428
|
|
|
1376
1429
|
case 'string':
|
|
1377
|
-
|
|
1430
|
+
// 'foo', with proper escaping
|
|
1378
1431
|
return `'${x.val.replace(/'/g, '\'\'')}'`;
|
|
1379
1432
|
case 'object':
|
|
1380
1433
|
if (x.val === null)
|
|
@@ -1539,13 +1592,13 @@ function toSqlDdl(csn, options) {
|
|
|
1539
1592
|
|
|
1540
1593
|
// Not really a path step but an object-like function call
|
|
1541
1594
|
if (s.func)
|
|
1542
|
-
return `${s.func}(${renderArgs(s
|
|
1595
|
+
return `${s.func}(${renderArgs(s, '=>', env, null)})`;
|
|
1543
1596
|
|
|
1544
1597
|
// Path step, possibly with view parameters and/or filters
|
|
1545
1598
|
let result = `${quoteSqlId(s.id)}`;
|
|
1546
1599
|
if (s.args) {
|
|
1547
1600
|
// View parameters
|
|
1548
|
-
result += `(${renderArgs(s
|
|
1601
|
+
result += `(${renderArgs(s, '=>', env, null)})`;
|
|
1549
1602
|
}
|
|
1550
1603
|
if (s.where) {
|
|
1551
1604
|
// Filter, possibly with cardinality
|
|
@@ -33,7 +33,7 @@ const { implicitAs } = require('../../model/csnRefs');
|
|
|
33
33
|
function renderFunc( funcName, node, dialect, renderArgs) {
|
|
34
34
|
if (funcWithoutParen( node, dialect ))
|
|
35
35
|
return funcName;
|
|
36
|
-
return `${funcName}(${renderArgs( node
|
|
36
|
+
return `${funcName}(${renderArgs( node )})`;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
@@ -53,19 +53,14 @@ function funcWithoutParen( node, dialect ) {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
|
-
* Process
|
|
56
|
+
* Process already rendered expression parts by joining them nicely
|
|
57
57
|
*
|
|
58
|
-
* @param {Array}
|
|
59
|
-
* @param {Function} renderExpr Function to render expressions
|
|
60
|
-
* @param {CdlRenderEnvironment} env Environment for rendering
|
|
61
|
-
* @param {boolean} inline Wether to render the expression inline
|
|
62
|
-
* @param {boolean} inExpr Wether to render as if a subexpression
|
|
58
|
+
* @param {Array} tokens Array of expression tokens
|
|
63
59
|
*
|
|
64
60
|
* @returns {string} The rendered xpr
|
|
65
61
|
*/
|
|
66
|
-
function
|
|
62
|
+
function beautifyExprArray(tokens) {
|
|
67
63
|
// Simply concatenate array parts with spaces (with a tiny bit of beautification)
|
|
68
|
-
const tokens = xpr.map(item => renderExpr(item, env, inline, inExpr));
|
|
69
64
|
let result = '';
|
|
70
65
|
for (let i = 0; i < tokens.length; i++) {
|
|
71
66
|
result += tokens[i];
|
|
@@ -342,11 +337,10 @@ function addIntermediateContexts(csn, killList) {
|
|
|
342
337
|
*
|
|
343
338
|
* @param {CSN.Artifact} obj
|
|
344
339
|
* @param {CSN.Options} options To check for `disableHanaComments`
|
|
345
|
-
* @param {CSN.Artifact} artifact Artifact containing obj (in case of elem or columns)
|
|
346
340
|
* @returns {boolean}
|
|
347
341
|
*/
|
|
348
|
-
function hasHanaComment(obj, options
|
|
349
|
-
return !
|
|
342
|
+
function hasHanaComment(obj, options) {
|
|
343
|
+
return !options.disableHanaComments && typeof obj.doc === 'string';
|
|
350
344
|
}
|
|
351
345
|
/**
|
|
352
346
|
* Return the comment of the given artifact or element.
|
|
@@ -379,7 +373,7 @@ function getHanaComment(obj) {
|
|
|
379
373
|
|
|
380
374
|
module.exports = {
|
|
381
375
|
renderFunc,
|
|
382
|
-
|
|
376
|
+
beautifyExprArray,
|
|
383
377
|
getNamespace,
|
|
384
378
|
getRealName,
|
|
385
379
|
addIntermediateContexts,
|
|
@@ -388,4 +382,5 @@ module.exports = {
|
|
|
388
382
|
hasHanaComment,
|
|
389
383
|
getHanaComment,
|
|
390
384
|
findElement,
|
|
385
|
+
funcWithoutParen,
|
|
391
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 ) {
|
|
@@ -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) {
|
|
@@ -45,7 +45,7 @@ function createReferentialConstraints(csn, options) {
|
|
|
45
45
|
if (element.type === ASSOCIATION ||
|
|
46
46
|
element.type === COMPOSITION && treatCompositionLikeAssociation(element)) {
|
|
47
47
|
associations.push(() => {
|
|
48
|
-
foreignKeyConstraintForAssociation(element, elements, path.concat([ elementName ]));
|
|
48
|
+
foreignKeyConstraintForAssociation(element, elements, path.concat([ elementName ]), elementName);
|
|
49
49
|
});
|
|
50
50
|
}
|
|
51
51
|
}
|
|
@@ -80,12 +80,14 @@ function createReferentialConstraints(csn, options) {
|
|
|
80
80
|
// mark each dependent key referenced in the on-condition (in target entity)
|
|
81
81
|
const dependentKeys = Array.from(elementsOfTargetSide(onCondition, csn.definitions[composition.target].elements));
|
|
82
82
|
const parentKeys = Array.from(elementsOfSourceSide(onCondition, elements));
|
|
83
|
+
const { backlinkName } = composition.$selfOnCondition || {};
|
|
83
84
|
// sanity check; do not generate constraints for on-conditions like "dependent.idOne = id AND dependent.idTwo = id"
|
|
84
|
-
|
|
85
|
-
|
|
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');
|
|
86
88
|
}
|
|
87
89
|
}
|
|
88
|
-
else if (!onCondition && composition.keys) {
|
|
90
|
+
else if (!onCondition && composition.keys.length > 0) {
|
|
89
91
|
throw new Error('Please debug me, an on-condition was expected here, but only found keys');
|
|
90
92
|
}
|
|
91
93
|
}
|
|
@@ -97,8 +99,9 @@ function createReferentialConstraints(csn, options) {
|
|
|
97
99
|
* @param {CSN.Association} association for that a constraint should be generated
|
|
98
100
|
* @param {CSN.Elements} elements of parent entity.
|
|
99
101
|
* @param {CSN.Path} path
|
|
102
|
+
* @param {string} assocName passed through as proper constraint suffix
|
|
100
103
|
*/
|
|
101
|
-
function foreignKeyConstraintForAssociation(association, elements, path) {
|
|
104
|
+
function foreignKeyConstraintForAssociation(association, elements, path, assocName) {
|
|
102
105
|
const associationTarget = csn.definitions[association.target];
|
|
103
106
|
if (skipConstraintGeneration(associationTarget, association))
|
|
104
107
|
return;
|
|
@@ -111,9 +114,9 @@ function createReferentialConstraints(csn, options) {
|
|
|
111
114
|
const parentKeys = Array.from(elementsOfTargetSide(onCondition, associationTarget.elements));
|
|
112
115
|
// sanity check; do not generate constraints for on-conditions like "dependent.idOne = id AND dependent.idTwo = id"
|
|
113
116
|
if (dependentKeys.length === parentKeys.length)
|
|
114
|
-
attachConstraintsToDependentKeys(dependentKeys, parentKeys, association.target, path);
|
|
117
|
+
attachConstraintsToDependentKeys(dependentKeys, parentKeys, association.target, path, assocName);
|
|
115
118
|
}
|
|
116
|
-
else if (!onCondition && association.keys) {
|
|
119
|
+
else if (!onCondition && association.keys.length > 0) {
|
|
117
120
|
throw new Error('Please debug me, an on-condition was expected here, but only found keys');
|
|
118
121
|
}
|
|
119
122
|
}
|
|
@@ -127,9 +130,10 @@ function createReferentialConstraints(csn, options) {
|
|
|
127
130
|
* @param {Array} parentKeys array holding parent keys in the format [['key1', 'value1'], [...], ...]
|
|
128
131
|
* @param {CSN.PathSegment} parentTable the sql-table where the foreign key constraints will be pointing to
|
|
129
132
|
* @param {CSN.Path} path
|
|
133
|
+
* @param {string | null} constraintIdentifierSuffix name of the association / the backlink association
|
|
130
134
|
* @param {string} onDelete the on delete rule which should be applied. Default for associations is 'RESTRICT'
|
|
131
135
|
*/
|
|
132
|
-
function attachConstraintsToDependentKeys(dependentKeys, parentKeys, parentTable, path, onDelete = 'RESTRICT') {
|
|
136
|
+
function attachConstraintsToDependentKeys(dependentKeys, parentKeys, parentTable, path, constraintIdentifierSuffix, onDelete = 'RESTRICT') {
|
|
133
137
|
while (dependentKeys.length > 0) {
|
|
134
138
|
const dependentKeyValuePair = dependentKeys.pop();
|
|
135
139
|
const dependentKey = dependentKeyValuePair[1];
|
|
@@ -143,7 +147,7 @@ function createReferentialConstraints(csn, options) {
|
|
|
143
147
|
parentKey: parentKeyName,
|
|
144
148
|
parentTable,
|
|
145
149
|
sourceAssociation: path[path.length - 1],
|
|
146
|
-
nameSuffix:
|
|
150
|
+
nameSuffix: constraintIdentifierSuffix || 'up_',
|
|
147
151
|
onDelete,
|
|
148
152
|
validated,
|
|
149
153
|
enforced,
|
|
@@ -229,8 +233,10 @@ function createReferentialConstraints(csn, options) {
|
|
|
229
233
|
return true;
|
|
230
234
|
}
|
|
231
235
|
|
|
232
|
-
if (element.$skipReferentialConstraintForUp_)
|
|
236
|
+
if (element.$skipReferentialConstraintForUp_) {
|
|
237
|
+
delete element.$skipReferentialConstraintForUp_;
|
|
233
238
|
return true;
|
|
239
|
+
}
|
|
234
240
|
|
|
235
241
|
if (hasAnnotationValue(parent, '@cds.persistence.skip', true) ||
|
|
236
242
|
hasAnnotationValue(parent, '@cds.persistence.exists', true) ||
|
|
@@ -351,11 +357,21 @@ function createReferentialConstraints(csn, options) {
|
|
|
351
357
|
* The constraints will thus be generated in the entity containing the composition and not in the target entity.
|
|
352
358
|
*
|
|
353
359
|
* @param {CSN.Composition} composition the composition which might be treated like an association
|
|
354
|
-
* @returns {
|
|
360
|
+
* @returns {boolean} true if the composition should be treated as an association in regards to foreign key constraints
|
|
355
361
|
*/
|
|
356
362
|
function treatCompositionLikeAssociation(composition) {
|
|
357
|
-
|
|
358
|
-
|
|
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;
|
|
359
375
|
}
|
|
360
376
|
}
|
|
361
377
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
|
-
hasAnnotationValue,
|
|
5
|
-
getResultingName,
|
|
4
|
+
hasAnnotationValue, getUtils, getServiceNames, forEachDefinition,
|
|
5
|
+
getResultingName, forEachMemberRecursively,
|
|
6
6
|
} = require('../../model/csnUtils');
|
|
7
7
|
const { setProp, isDeprecatedEnabled } = require('../../base/model');
|
|
8
8
|
const { getTransformers } = require('../transformUtilsNew');
|
|
@@ -117,10 +117,12 @@ function generateDrafts(csn, options, pathDelimiter, messageFunctions) {
|
|
|
117
117
|
|
|
118
118
|
// extract keys for UUID inspection
|
|
119
119
|
const keys = [];
|
|
120
|
-
|
|
120
|
+
forEachMemberRecursively(artifact, (elt, name, prop, path) => {
|
|
121
|
+
if (!elt.elements && !elt.type && !elt.virtual) // only check leafs
|
|
122
|
+
error(null, path, 'Expecting element to have a type when used in a draft-enabled artifact');
|
|
121
123
|
if (elt.key && elt.key === true && !elt.virtual)
|
|
122
124
|
keys.push(elt);
|
|
123
|
-
});
|
|
125
|
+
}, [ 'definitions', artifactName ], true, { elementsOnly: true });
|
|
124
126
|
|
|
125
127
|
// In contrast to EDM, the DB entity may have more than one technical keys but should have idealy exactly one key of type cds.UUID
|
|
126
128
|
if (keys.length !== 1)
|
|
@@ -222,7 +224,7 @@ function generateDrafts(csn, options, pathDelimiter, messageFunctions) {
|
|
|
222
224
|
// Note that we may need to do the HANA transformation steps for managed associations
|
|
223
225
|
// (foreign key field generation, generatedFieldName, creating ON-condition) by hand,
|
|
224
226
|
// because the corresponding transformation steps have already been done on all artifacts
|
|
225
|
-
// when we come here). Only for
|
|
227
|
+
// when we come here). Only for to.hdbcds with hdbcds names this is not required.
|
|
226
228
|
/**
|
|
227
229
|
* The given association has a key named DraftUUID
|
|
228
230
|
*
|
|
@@ -256,7 +258,7 @@ function generateDrafts(csn, options, pathDelimiter, messageFunctions) {
|
|
|
256
258
|
}
|
|
257
259
|
|
|
258
260
|
const draftUUIDKey = getDraftUUIDKey(draftAdministrativeData.DraftAdministrativeData);
|
|
259
|
-
if (!options.
|
|
261
|
+
if (!(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds') && draftUUIDKey) {
|
|
260
262
|
const path = [ 'definitions', draftsArtifactName, 'elements', 'DraftAdministrativeData', 'keys', 0 ];
|
|
261
263
|
createForeignKeyElement(draftAdministrativeData.DraftAdministrativeData, 'DraftAdministrativeData', draftUUIDKey, draftsArtifact, draftsArtifactName, path);
|
|
262
264
|
draftAdministrativeData.DraftAdministrativeData.on = createAssociationPathComparison('DraftAdministrativeData',
|