@sap/cds-compiler 2.5.2 → 2.11.0
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 +235 -9
- package/bin/cdsc.js +44 -27
- package/bin/cdsse.js +1 -0
- package/doc/CHANGELOG_BETA.md +37 -3
- package/lib/api/.eslintrc.json +2 -0
- package/lib/api/main.js +37 -123
- package/lib/api/options.js +27 -15
- package/lib/api/validate.js +34 -9
- package/lib/backends.js +9 -89
- package/lib/base/dictionaries.js +2 -1
- package/lib/base/keywords.js +32 -2
- package/lib/base/message-registry.js +73 -11
- package/lib/base/messages.js +86 -30
- package/lib/base/model.js +6 -6
- package/lib/base/optionProcessorHelper.js +56 -22
- 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 +29 -2
- package/lib/checks/types.js +26 -2
- package/lib/checks/unknownMagic.js +41 -0
- package/lib/checks/utils.js +61 -0
- package/lib/checks/validator.js +60 -7
- package/lib/compiler/assert-consistency.js +23 -7
- package/lib/compiler/base.js +65 -0
- package/lib/compiler/builtins.js +30 -1
- package/lib/compiler/checks.js +8 -5
- package/lib/compiler/definer.js +157 -133
- package/lib/compiler/index.js +89 -31
- package/lib/compiler/propagator.js +5 -2
- package/lib/compiler/resolver.js +375 -185
- package/lib/compiler/shared.js +49 -202
- package/lib/compiler/utils.js +173 -0
- package/lib/edm/annotations/genericTranslation.js +183 -187
- package/lib/edm/csn2edm.js +104 -108
- package/lib/edm/edm.js +18 -21
- package/lib/edm/edmPreprocessor.js +388 -146
- package/lib/edm/edmUtils.js +104 -34
- package/lib/gen/Dictionary.json +22 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +28 -1
- package/lib/gen/language.tokens +79 -69
- package/lib/gen/languageLexer.interp +28 -1
- package/lib/gen/languageLexer.js +879 -805
- package/lib/gen/languageLexer.tokens +71 -62
- package/lib/gen/languageParser.js +5330 -4300
- package/lib/json/from-csn.js +110 -52
- package/lib/json/to-csn.js +434 -120
- package/lib/language/antlrParser.js +15 -3
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +93 -26
- package/lib/language/language.g4 +172 -31
- package/lib/main.d.ts +216 -19
- package/lib/main.js +32 -7
- package/lib/model/api.js +78 -0
- package/lib/model/csnRefs.js +413 -149
- package/lib/model/csnUtils.js +286 -75
- package/lib/model/enrichCsn.js +50 -6
- package/lib/model/revealInternalProperties.js +22 -5
- package/lib/modelCompare/compare.js +39 -21
- package/lib/optionProcessor.js +35 -18
- package/lib/render/.eslintrc.json +4 -1
- package/lib/render/DuplicateChecker.js +9 -6
- package/lib/render/toCdl.js +121 -36
- package/lib/render/toHdbcds.js +148 -98
- package/lib/render/toSql.js +114 -43
- 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 +281 -106
- package/lib/transform/db/draft.js +11 -8
- package/lib/transform/db/expansion.js +584 -0
- package/lib/transform/db/flattening.js +341 -0
- package/lib/transform/db/groupByOrderBy.js +2 -2
- package/lib/transform/db/transformExists.js +345 -65
- package/lib/transform/db/views.js +438 -0
- package/lib/transform/forHanaNew.js +131 -793
- package/lib/transform/forOdataNew.js +30 -24
- package/lib/transform/localized.js +39 -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 +144 -78
- package/lib/transform/translateAssocsToJoins.js +22 -27
- package/lib/transform/universalCsnEnricher.js +67 -0
- package/lib/utils/file.js +5 -14
- package/lib/utils/moduleResolve.js +6 -8
- package/lib/utils/term.js +65 -42
- package/lib/utils/timetrace.js +48 -26
- 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
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
const {
|
|
5
5
|
getLastPartOf, getLastPartOfRef,
|
|
6
6
|
hasValidSkipOrExists, isBuiltinType, generatedByCompilerVersion, getNormalizedQuery,
|
|
7
|
-
forEachDefinition, getResultingName,
|
|
7
|
+
forEachDefinition, getResultingName, getVariableReplacement,
|
|
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,
|
|
@@ -15,7 +15,7 @@ const {
|
|
|
15
15
|
const DuplicateChecker = require('./DuplicateChecker');
|
|
16
16
|
const { checkCSNVersion } = require('../json/csnVersion');
|
|
17
17
|
const { makeMessageFunction } = require('../base/messages');
|
|
18
|
-
const timetrace = require('../utils/timetrace');
|
|
18
|
+
const { timetrace } = require('../utils/timetrace');
|
|
19
19
|
const { isBetaEnabled, isDeprecatedEnabled } = require('../base/model');
|
|
20
20
|
const { smartFuncId } = require('../sql-identifier');
|
|
21
21
|
const { sortCsn } = require('../json/to-csn');
|
|
@@ -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)`;
|
|
@@ -522,7 +567,7 @@ function toSqlDdl(csn, options) {
|
|
|
522
567
|
if (options.toSql.dialect === 'hana')
|
|
523
568
|
renderIndexesInto(art.technicalConfig && art.technicalConfig.hana.indexes, artifactName, resultObj, env);
|
|
524
569
|
|
|
525
|
-
if (options.toSql.dialect === 'hana' && hasHanaComment(art, options
|
|
570
|
+
if (options.toSql.dialect === 'hana' && hasHanaComment(art, options))
|
|
526
571
|
result += ` COMMENT '${getHanaComment(art)}'`;
|
|
527
572
|
|
|
528
573
|
resultObj.hdbtable[artifactName] = result;
|
|
@@ -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)}`);
|
|
@@ -1008,7 +1054,7 @@ function toSqlDdl(csn, options) {
|
|
|
1008
1054
|
definitionsDuplicateChecker.addArtifact(art['@cds.persistence.name'], art && art.$location, artifactName);
|
|
1009
1055
|
let result = `VIEW ${viewName}`;
|
|
1010
1056
|
|
|
1011
|
-
if (options.toSql.dialect === 'hana' && hasHanaComment(art, options
|
|
1057
|
+
if (options.toSql.dialect === 'hana' && hasHanaComment(art, options))
|
|
1012
1058
|
result += ` COMMENT '${getHanaComment(art)}'`;
|
|
1013
1059
|
|
|
1014
1060
|
result += renderParameterDefinitions(artifactName, art.params);
|
|
@@ -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)
|
|
@@ -1330,6 +1383,8 @@ function toSqlDdl(csn, options) {
|
|
|
1330
1383
|
// Function call, possibly with args (use '=>' for named args)
|
|
1331
1384
|
else if (x.func) {
|
|
1332
1385
|
const funcName = smartFuncId(prepareIdentifier(x.func), options.toSql.dialect);
|
|
1386
|
+
if (x.xpr)
|
|
1387
|
+
return renderWindowFunction(funcName, x, env);
|
|
1333
1388
|
return renderFunc(funcName, x, options.toSql.dialect, a => renderArgs(a, '=>', env, null));
|
|
1334
1389
|
}
|
|
1335
1390
|
// Nested expression
|
|
@@ -1352,29 +1407,36 @@ function toSqlDdl(csn, options) {
|
|
|
1352
1407
|
throw new Error(`Unknown expression: ${JSON.stringify(x)}`);
|
|
1353
1408
|
}
|
|
1354
1409
|
|
|
1410
|
+
function renderWindowFunction(funcName, node, env) {
|
|
1411
|
+
const suffix = node.xpr.shift(); // OVER
|
|
1412
|
+
let r = `${funcName}(${renderArgs(node, '=>', env, null)})`;
|
|
1413
|
+
r += ` ${suffix} (${renderExpr(node.xpr, env)})`;
|
|
1414
|
+
return r;
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1355
1417
|
function renderExpressionLiteral(x) {
|
|
1356
1418
|
// Literal value, possibly with explicit 'literal' property
|
|
1357
1419
|
switch (x.literal || typeof x.val) {
|
|
1358
1420
|
case 'number':
|
|
1359
1421
|
case 'boolean':
|
|
1360
1422
|
case 'null':
|
|
1361
|
-
|
|
1423
|
+
// 17.42, NULL, TRUE
|
|
1362
1424
|
return String(x.val).toUpperCase();
|
|
1363
1425
|
case 'x':
|
|
1364
|
-
|
|
1426
|
+
// x'f000'
|
|
1365
1427
|
return `${x.literal}'${x.val}'`;
|
|
1366
1428
|
case 'date':
|
|
1367
1429
|
case 'time':
|
|
1368
1430
|
case 'timestamp':
|
|
1369
1431
|
if (options.toSql.dialect === 'sqlite') {
|
|
1370
|
-
|
|
1432
|
+
// simple string literal '2017-11-02'
|
|
1371
1433
|
return `'${x.val}'`;
|
|
1372
1434
|
}
|
|
1373
1435
|
// date'2017-11-02'
|
|
1374
1436
|
return `${x.literal}'${x.val}'`;
|
|
1375
1437
|
|
|
1376
1438
|
case 'string':
|
|
1377
|
-
|
|
1439
|
+
// 'foo', with proper escaping
|
|
1378
1440
|
return `'${x.val.replace(/'/g, '\'\'')}'`;
|
|
1379
1441
|
case 'object':
|
|
1380
1442
|
if (x.val === null)
|
|
@@ -1388,7 +1450,12 @@ function toSqlDdl(csn, options) {
|
|
|
1388
1450
|
|
|
1389
1451
|
function renderExpressionRef(x) {
|
|
1390
1452
|
if (!x.param && !x.global) {
|
|
1453
|
+
const magicReplacement = getVariableReplacement(x.ref, options);
|
|
1454
|
+
|
|
1391
1455
|
if (x.ref[0] === '$user') {
|
|
1456
|
+
if (magicReplacement !== null)
|
|
1457
|
+
return `'${magicReplacement}'`;
|
|
1458
|
+
|
|
1392
1459
|
const result = render$user(x);
|
|
1393
1460
|
// Invalid second path step doesn't cause a return
|
|
1394
1461
|
if (result)
|
|
@@ -1400,6 +1467,9 @@ function toSqlDdl(csn, options) {
|
|
|
1400
1467
|
if (result)
|
|
1401
1468
|
return result;
|
|
1402
1469
|
}
|
|
1470
|
+
else if (x.ref[0] === '$session' && magicReplacement !== null) {
|
|
1471
|
+
return `'${magicReplacement}'`;
|
|
1472
|
+
}
|
|
1403
1473
|
}
|
|
1404
1474
|
// FIXME: We currently cannot distinguish whether '$parameters' was quoted or not - we
|
|
1405
1475
|
// assume that it was not if the path has length 2 (
|
|
@@ -1422,6 +1492,7 @@ function toSqlDdl(csn, options) {
|
|
|
1422
1492
|
function render$user(x) {
|
|
1423
1493
|
// FIXME: this is all not enough: we might need an explicit select item alias
|
|
1424
1494
|
if (x.ref[1] === 'id') {
|
|
1495
|
+
// Keep the old-style for compatibilty with magicVars.id - instead of magicVars.user.id...
|
|
1425
1496
|
if (options.toSql.user && typeof options.toSql.user === 'string' || options.toSql.user instanceof String)
|
|
1426
1497
|
return `'${options.toSql.user}'`;
|
|
1427
1498
|
|
|
@@ -1441,7 +1512,7 @@ function toSqlDdl(csn, options) {
|
|
|
1441
1512
|
}
|
|
1442
1513
|
return 'SESSION_CONTEXT(\'LOCALE\')';
|
|
1443
1514
|
}
|
|
1444
|
-
// Basically: Second path step was invalid, do nothing
|
|
1515
|
+
// Basically: Second path step was invalid, do nothing - should not happen, see 'unknownMagic.js'
|
|
1445
1516
|
return null;
|
|
1446
1517
|
}
|
|
1447
1518
|
/**
|
|
@@ -1539,13 +1610,13 @@ function toSqlDdl(csn, options) {
|
|
|
1539
1610
|
|
|
1540
1611
|
// Not really a path step but an object-like function call
|
|
1541
1612
|
if (s.func)
|
|
1542
|
-
return `${s.func}(${renderArgs(s
|
|
1613
|
+
return `${s.func}(${renderArgs(s, '=>', env, null)})`;
|
|
1543
1614
|
|
|
1544
1615
|
// Path step, possibly with view parameters and/or filters
|
|
1545
1616
|
let result = `${quoteSqlId(s.id)}`;
|
|
1546
1617
|
if (s.args) {
|
|
1547
1618
|
// View parameters
|
|
1548
|
-
result += `(${renderArgs(s
|
|
1619
|
+
result += `(${renderArgs(s, '=>', env, null)})`;
|
|
1549
1620
|
}
|
|
1550
1621
|
if (s.where) {
|
|
1551
1622
|
// 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) {
|