@sap/cds-compiler 2.13.6 → 2.15.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 +128 -4
- package/bin/cdsc.js +112 -37
- package/lib/api/main.js +20 -22
- package/lib/api/options.js +2 -3
- package/lib/api/validate.js +6 -6
- package/lib/base/message-registry.js +92 -17
- package/lib/base/messages.js +85 -64
- package/lib/base/optionProcessorHelper.js +19 -0
- package/lib/checks/annotationsOData.js +11 -32
- package/lib/checks/arrayOfs.js +1 -34
- package/lib/checks/validator.js +2 -4
- package/lib/compiler/assert-consistency.js +1 -0
- package/lib/compiler/base.js +1 -0
- package/lib/compiler/builtins.js +11 -0
- package/lib/compiler/checks.js +22 -70
- package/lib/compiler/define.js +59 -11
- package/lib/compiler/extend.js +20 -3
- package/lib/compiler/finalize-parse-cdl.js +26 -20
- package/lib/compiler/index.js +75 -26
- package/lib/compiler/populate.js +6 -5
- package/lib/compiler/propagator.js +4 -1
- package/lib/compiler/resolve.js +104 -16
- package/lib/compiler/shared.js +61 -27
- package/lib/compiler/tweak-assocs.js +7 -1
- package/lib/edm/annotations/genericTranslation.js +93 -21
- package/lib/edm/csn2edm.js +216 -98
- package/lib/edm/edm.js +305 -226
- package/lib/edm/edmPreprocessor.js +499 -423
- package/lib/edm/edmUtils.js +22 -22
- package/lib/gen/Dictionary.json +98 -22
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -1
- package/lib/gen/languageParser.js +4636 -4368
- package/lib/json/csnVersion.js +10 -11
- package/lib/json/from-csn.js +3 -2
- package/lib/json/to-csn.js +0 -2
- package/lib/language/docCommentParser.js +2 -2
- package/lib/language/genericAntlrParser.js +47 -2
- package/lib/language/language.g4 +59 -27
- package/lib/main.d.ts +19 -1
- package/lib/main.js +6 -0
- package/lib/model/csnRefs.js +33 -6
- package/lib/model/csnUtils.js +193 -75
- package/lib/model/enrichCsn.js +1 -0
- package/lib/model/revealInternalProperties.js +2 -2
- package/lib/modelCompare/compare.js +6 -6
- package/lib/optionProcessor.js +62 -26
- package/lib/render/toCdl.js +844 -679
- package/lib/render/toHdbcds.js +189 -243
- package/lib/render/toSql.js +180 -198
- package/lib/render/utils/common.js +131 -15
- package/lib/transform/db/.eslintrc.json +1 -1
- package/lib/transform/db/associations.js +2 -2
- package/lib/transform/db/constraints.js +3 -1
- package/lib/transform/db/expansion.js +15 -10
- package/lib/transform/db/flattening.js +95 -68
- package/lib/transform/db/transformExists.js +7 -7
- package/lib/transform/db/views.js +6 -3
- package/lib/transform/forHanaNew.js +43 -26
- package/lib/transform/forOdataNew.js +43 -42
- package/lib/transform/localized.js +12 -7
- package/lib/transform/odata/toFinalBaseType.js +8 -6
- package/lib/transform/odata/typesExposure.js +145 -197
- package/lib/transform/transformUtilsNew.js +9 -12
- package/lib/transform/translateAssocsToJoins.js +5 -1
- package/lib/transform/universalCsn/coreComputed.js +5 -3
- package/lib/transform/universalCsn/universalCsnEnricher.js +27 -5
- package/lib/utils/moduleResolve.js +13 -6
- package/package.json +1 -1
- package/share/messages/message-explanations.json +2 -1
- package/share/messages/syntax-expected-integer.md +37 -0
- package/lib/transform/odata/attachPath.js +0 -96
- package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
- package/lib/transform/odata/generateForeignKeyElements.js +0 -261
- package/lib/transform/odata/referenceFlattener.js +0 -296
- package/lib/transform/odata/sortByAssociationDependency.js +0 -105
- package/lib/transform/odata/structuralPath.js +0 -72
- package/lib/transform/odata/structureFlattener.js +0 -171
package/lib/render/toSql.js
CHANGED
|
@@ -7,8 +7,8 @@ const {
|
|
|
7
7
|
forEachDefinition, getResultingName, getVariableReplacement,
|
|
8
8
|
} = require('../model/csnUtils');
|
|
9
9
|
const {
|
|
10
|
-
renderFunc,
|
|
11
|
-
getSqlSnippets,
|
|
10
|
+
renderFunc, cdsToSqlTypes, getHanaComment, hasHanaComment,
|
|
11
|
+
getSqlSnippets, getExpressionRenderer,
|
|
12
12
|
} = require('./utils/common');
|
|
13
13
|
const {
|
|
14
14
|
renderReferentialConstraint, getIdentifierUtils,
|
|
@@ -77,9 +77,37 @@ const { ModelError } = require('../base/error');
|
|
|
77
77
|
function toSqlDdl(csn, options) {
|
|
78
78
|
timetrace.start('SQL rendering');
|
|
79
79
|
const {
|
|
80
|
-
error, warning, info,
|
|
80
|
+
error, warning, info, throwWithAnyError,
|
|
81
81
|
} = makeMessageFunction(csn, options, 'to.sql');
|
|
82
82
|
const { quoteSqlId, prepareIdentifier } = getIdentifierUtils(options);
|
|
83
|
+
const renderExpr = getExpressionRenderer({
|
|
84
|
+
finalize: x => String(x).toUpperCase(),
|
|
85
|
+
explicitTypeCast: (x, env) => {
|
|
86
|
+
const typeRef = renderBuiltinType(x.cast.type) + renderTypeParameters(x.cast);
|
|
87
|
+
return `CAST(${renderExpr(x, env)} AS ${typeRef})`;
|
|
88
|
+
},
|
|
89
|
+
val: renderExpressionLiteral,
|
|
90
|
+
enum: (x) => {
|
|
91
|
+
// TODO: Signal is not covered by tests + better location
|
|
92
|
+
// FIXME: We can't do enums yet because they are not resolved (and we don't bother finding their value by hand)
|
|
93
|
+
error(null, x.$location, 'Enum values are not yet supported for conversion to SQL');
|
|
94
|
+
return '';
|
|
95
|
+
},
|
|
96
|
+
ref: renderExpressionRef,
|
|
97
|
+
aliasOnly(x, _env) {
|
|
98
|
+
return x.as;
|
|
99
|
+
},
|
|
100
|
+
windowFunction: (x, env) => renderWindowFunction(smartFuncId(prepareIdentifier(x.func), options.toSql.dialect), x, env),
|
|
101
|
+
func: (x, env) => renderFunc(smartFuncId(prepareIdentifier(x.func), options.toSql.dialect), x, options.toSql.dialect, a => renderArgs(a, '=>', env, null)),
|
|
102
|
+
xpr(x, env) {
|
|
103
|
+
if (this.nestedExpr && !x.cast)
|
|
104
|
+
return `(${renderExpr(x.xpr, env, this.inline, true)})`;
|
|
105
|
+
|
|
106
|
+
return renderExpr(x.xpr, env, this.inline, true);
|
|
107
|
+
},
|
|
108
|
+
SELECT: (x, env) => `(${renderQuery('<subselect>', x, increaseIndent(env))})`,
|
|
109
|
+
SET: (x, env) => `(${renderQuery('<union>', x, increaseIndent(env))})`,
|
|
110
|
+
});
|
|
83
111
|
|
|
84
112
|
// Utils to render SQL statements.
|
|
85
113
|
const render = {
|
|
@@ -89,7 +117,8 @@ function toSqlDdl(csn, options) {
|
|
|
89
117
|
*/
|
|
90
118
|
addColumns: {
|
|
91
119
|
fromElementStrings(tableName, eltStrings) {
|
|
92
|
-
|
|
120
|
+
const elts = options.sqlDialect === 'hana' ? `(${eltStrings.join(', ')})` : `${eltStrings.join(', ')}`;
|
|
121
|
+
return [ `ALTER TABLE ${tableName} ADD ${elts};` ];
|
|
93
122
|
},
|
|
94
123
|
fromElementsObj(artifactName, tableName, elementsObj, env, duplicateChecker) {
|
|
95
124
|
// Only extend with 'ADD' for elements/associations
|
|
@@ -171,7 +200,7 @@ function toSqlDdl(csn, options) {
|
|
|
171
200
|
Render comment string.
|
|
172
201
|
*/
|
|
173
202
|
comment(comment) {
|
|
174
|
-
return comment &&
|
|
203
|
+
return comment && renderStringForSql(comment, options.sqlDialect) || 'NULL';
|
|
175
204
|
},
|
|
176
205
|
/*
|
|
177
206
|
Alter SQL snippet for entity.
|
|
@@ -233,7 +262,7 @@ function toSqlDdl(csn, options) {
|
|
|
233
262
|
// Render each artifact extension
|
|
234
263
|
// Only HANA SQL is currently supported.
|
|
235
264
|
// Note that extensions may contain new elements referenced in migrations, thus should be compiled first.
|
|
236
|
-
if (csn.extensions && options.toSql.dialect === 'hana') {
|
|
265
|
+
if (csn.extensions && (options.toSql.dialect === 'hana' || isBetaEnabled(options, 'sqlExtensions'))) {
|
|
237
266
|
for (const extension of options && options.testMode ? sortCsn(csn.extensions) : csn.extensions) {
|
|
238
267
|
if (extension.extend) {
|
|
239
268
|
const artifactName = extension.extend;
|
|
@@ -246,7 +275,7 @@ function toSqlDdl(csn, options) {
|
|
|
246
275
|
|
|
247
276
|
// Render each artifact change
|
|
248
277
|
// Only HANA SQL is currently supported.
|
|
249
|
-
if (csn.migrations && options.toSql.dialect === 'hana') {
|
|
278
|
+
if (csn.migrations && (options.toSql.dialect === 'hana' || isBetaEnabled(options, 'sqlExtensions'))) {
|
|
250
279
|
for (const migration of options && options.testMode ? sortCsn(csn.migrations) : csn.migrations) {
|
|
251
280
|
if (migration.migrate) {
|
|
252
281
|
const artifactName = migration.migrate;
|
|
@@ -263,7 +292,7 @@ function toSqlDdl(csn, options) {
|
|
|
263
292
|
deletionsDuplicateChecker.check(error);
|
|
264
293
|
|
|
265
294
|
// Throw exception in case of errors
|
|
266
|
-
|
|
295
|
+
throwWithAnyError();
|
|
267
296
|
|
|
268
297
|
// Transfer results from hdb-specific dictionaries into 'sql' dictionary in proper order if toSql.src === 'sql'
|
|
269
298
|
// (relying on the order of dictionaries above)
|
|
@@ -416,13 +445,13 @@ function toSqlDdl(csn, options) {
|
|
|
416
445
|
function oldAnnoChangedIncompatibly(defOld, defNew) {
|
|
417
446
|
return typeof defOld === 'string' && defOld.trim().length && !(typeof defNew === 'string' && defNew.trim().startsWith(`${defOld.trim()} `));
|
|
418
447
|
}
|
|
419
|
-
function getUnknownSqlReason(anno,
|
|
448
|
+
function getUnknownSqlReason(anno, artName, defOld, defNew, eltName) {
|
|
420
449
|
const changeKind = defNew === undefined
|
|
421
450
|
? `removed (previous value: ${JSON.stringify(defOld)})`
|
|
422
451
|
: `changed from ${JSON.stringify(defOld)} to ${JSON.stringify(defNew)}`;
|
|
423
452
|
return eltName
|
|
424
|
-
? `annotation ${anno} of element ${
|
|
425
|
-
: `annotation ${anno} of artifact ${
|
|
453
|
+
? `annotation ${anno} of element ${artName}:${eltName} has been ${changeKind}`
|
|
454
|
+
: `annotation ${anno} of artifact ${artName} has been ${changeKind}`;
|
|
426
455
|
}
|
|
427
456
|
|
|
428
457
|
const sqlSnippetAnnos = [ '@sql.prepend', '@sql.append' ];
|
|
@@ -624,8 +653,8 @@ function toSqlDdl(csn, options) {
|
|
|
624
653
|
if (options.toSql.dialect === 'hana')
|
|
625
654
|
renderIndexesInto(art.technicalConfig && art.technicalConfig.hana.indexes, artifactName, resultObj, env);
|
|
626
655
|
|
|
627
|
-
if (options.
|
|
628
|
-
result += ` COMMENT
|
|
656
|
+
if (options.sqlDialect === 'hana' && hasHanaComment(art, options))
|
|
657
|
+
result += ` COMMENT ${renderStringForSql(getHanaComment(art), options.sqlDialect)}`;
|
|
629
658
|
|
|
630
659
|
if (back)
|
|
631
660
|
result += back;
|
|
@@ -738,8 +767,8 @@ function toSqlDdl(csn, options) {
|
|
|
738
767
|
if (back !== '') // Needs to be rendered before the COMMENT
|
|
739
768
|
result += back;
|
|
740
769
|
|
|
741
|
-
if (options.
|
|
742
|
-
result += ` COMMENT
|
|
770
|
+
if (options.sqlDialect === 'hana' && hasHanaComment(elm, options))
|
|
771
|
+
result += ` COMMENT ${renderStringForSql(getHanaComment(elm), options.sqlDialect)}`;
|
|
743
772
|
|
|
744
773
|
return result;
|
|
745
774
|
}
|
|
@@ -1089,13 +1118,14 @@ function toSqlDdl(csn, options) {
|
|
|
1089
1118
|
* Return the resulting source string (one line per column item, no CR).
|
|
1090
1119
|
*
|
|
1091
1120
|
* @param {object} col Column to render
|
|
1121
|
+
* @param {CSN.Elements} elements of leading or subquery
|
|
1092
1122
|
* @param {object} env Render environment
|
|
1093
1123
|
* @returns {string} Rendered column
|
|
1094
1124
|
*/
|
|
1095
|
-
function renderViewColumn(col, env) {
|
|
1125
|
+
function renderViewColumn(col, elements, env) {
|
|
1096
1126
|
let result = '';
|
|
1097
1127
|
const leaf = col.as || col.ref && col.ref[col.ref.length - 1] || col.func;
|
|
1098
|
-
if (leaf &&
|
|
1128
|
+
if (leaf && elements[leaf] && elements[leaf].virtual) {
|
|
1099
1129
|
if (isDeprecatedEnabled(options, 'renderVirtualElements'))
|
|
1100
1130
|
// render a virtual column 'null as <alias>'
|
|
1101
1131
|
result += `${env.indent}NULL AS ${quoteSqlId(col.as || leaf)}`;
|
|
@@ -1124,11 +1154,11 @@ function toSqlDdl(csn, options) {
|
|
|
1124
1154
|
definitionsDuplicateChecker.addArtifact(art['@cds.persistence.name'], art && art.$location, artifactName);
|
|
1125
1155
|
let result = `VIEW ${viewName}`;
|
|
1126
1156
|
|
|
1127
|
-
if (options.
|
|
1128
|
-
result += ` COMMENT
|
|
1157
|
+
if (options.sqlDialect === 'hana' && hasHanaComment(art, options))
|
|
1158
|
+
result += ` COMMENT ${renderStringForSql(getHanaComment(art), options.sqlDialect)}`;
|
|
1129
1159
|
|
|
1130
1160
|
result += renderParameterDefinitions(artifactName, art.params);
|
|
1131
|
-
result += ` AS ${renderQuery(artifactName, getNormalizedQuery(art).query, env)}`;
|
|
1161
|
+
result += ` AS ${renderQuery(artifactName, getNormalizedQuery(art).query, env, art.elements)}`;
|
|
1132
1162
|
|
|
1133
1163
|
const childEnv = increaseIndent(env);
|
|
1134
1164
|
const associations = Object.keys(art.elements).filter(name => !!art.elements[name].target)
|
|
@@ -1188,9 +1218,10 @@ function toSqlDdl(csn, options) {
|
|
|
1188
1218
|
* @param {string} artifactName Artifact containing the query
|
|
1189
1219
|
* @param {CSN.Query} query CSN query
|
|
1190
1220
|
* @param {object} env Render environment
|
|
1221
|
+
* @param {CSN.Elements} [elements] to override direct query elements - e.g. leading union should win
|
|
1191
1222
|
* @returns {string} Rendered query
|
|
1192
1223
|
*/
|
|
1193
|
-
function renderQuery(artifactName, query, env) {
|
|
1224
|
+
function renderQuery(artifactName, query, env, elements = null) {
|
|
1194
1225
|
let result = '';
|
|
1195
1226
|
// Set operator, like UNION, INTERSECT, ...
|
|
1196
1227
|
if (query.SET) {
|
|
@@ -1199,7 +1230,7 @@ function toSqlDdl(csn, options) {
|
|
|
1199
1230
|
// Wrap each query in the SET in parentheses that
|
|
1200
1231
|
// - is a SET itself (to preserve precedence between the different SET operations),
|
|
1201
1232
|
// - has an ORDER BY/LIMIT (because UNION etc. can't stand directly behind an ORDER BY)
|
|
1202
|
-
const queryString = renderQuery(artifactName, arg, env);
|
|
1233
|
+
const queryString = renderQuery(artifactName, arg, env, elements || query.SET.elements);
|
|
1203
1234
|
return (arg.SET || arg.SELECT && (arg.SELECT.orderBy || arg.SELECT.limit)) ? `(${queryString})` : queryString;
|
|
1204
1235
|
})
|
|
1205
1236
|
.join(`\n${env.indent}${query.SET.op && query.SET.op.toUpperCase()}${query.SET.all ? ' ALL ' : ' '}`);
|
|
@@ -1228,7 +1259,7 @@ function toSqlDdl(csn, options) {
|
|
|
1228
1259
|
// FIXME: We probably also need to consider `excluding` here ?
|
|
1229
1260
|
result += `\n${(select.columns || [ '*' ])
|
|
1230
1261
|
.filter(col => !(select.mixin || Object.create(null))[firstPathStepId(col.ref)]) // No mixin columns
|
|
1231
|
-
.map(col => renderViewColumn(col, childEnv))
|
|
1262
|
+
.map(col => renderViewColumn(col, elements || select.elements, childEnv))
|
|
1232
1263
|
.filter(s => s !== '')
|
|
1233
1264
|
.join(',\n')}\n`;
|
|
1234
1265
|
result += `${env.indent}FROM ${renderViewSource(artifactName, select.from, env)}`;
|
|
@@ -1403,171 +1434,80 @@ function toSqlDdl(csn, options) {
|
|
|
1403
1434
|
return params.length === 0 ? '' : `(${params.join(', ')})`;
|
|
1404
1435
|
}
|
|
1405
1436
|
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
if ((nestedExpr || alwaysRenderCast) && expr.cast && expr.cast.type)
|
|
1427
|
-
return renderExplicitTypeCast(expr, renderExprObject(expr));
|
|
1428
|
-
return renderExprObject(expr);
|
|
1429
|
-
}
|
|
1430
|
-
// Not a literal value but part of an operator, function etc - just leave as it is
|
|
1431
|
-
// FIXME: For the sake of simplicity, we should get away from all this uppercasing in toSql
|
|
1437
|
+
function renderExpressionLiteral(x) {
|
|
1438
|
+
// Literal value, possibly with explicit 'literal' property
|
|
1439
|
+
switch (x.literal || typeof x.val) {
|
|
1440
|
+
case 'number':
|
|
1441
|
+
case 'boolean':
|
|
1442
|
+
case 'null':
|
|
1443
|
+
// 17.42, NULL, TRUE
|
|
1444
|
+
return String(x.val).toUpperCase();
|
|
1445
|
+
case 'x':
|
|
1446
|
+
// x'f000'
|
|
1447
|
+
return `${x.literal}'${x.val}'`;
|
|
1448
|
+
case 'date':
|
|
1449
|
+
case 'time':
|
|
1450
|
+
case 'timestamp':
|
|
1451
|
+
if (options.toSql.dialect === 'sqlite') {
|
|
1452
|
+
// simple string literal '2017-11-02'
|
|
1453
|
+
return `'${x.val}'`;
|
|
1454
|
+
}
|
|
1455
|
+
// date'2017-11-02'
|
|
1456
|
+
return `${x.literal}'${x.val}'`;
|
|
1432
1457
|
|
|
1433
|
-
|
|
1458
|
+
case 'string':
|
|
1459
|
+
// 'foo', with proper escaping
|
|
1460
|
+
return renderStringForSql(x.val, options.sqlDialect);
|
|
1461
|
+
case 'object':
|
|
1462
|
+
if (x.val === null)
|
|
1463
|
+
return 'NULL';
|
|
1434
1464
|
|
|
1465
|
+
// otherwise fall through to
|
|
1466
|
+
default:
|
|
1467
|
+
throw new ModelError(`Unknown literal or type: ${JSON.stringify(x)}`);
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1435
1470
|
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
* @param {object} x Expression
|
|
1440
|
-
* @returns {string} String representation of the expression
|
|
1441
|
-
*/
|
|
1442
|
-
function renderExprObject(x) {
|
|
1443
|
-
if (x.list) {
|
|
1444
|
-
return `(${x.list.map(item => renderExpr(item)).join(', ')})`;
|
|
1445
|
-
}
|
|
1446
|
-
else if (x.val !== undefined) {
|
|
1447
|
-
return renderExpressionLiteral(x);
|
|
1448
|
-
}
|
|
1449
|
-
// Enum symbol
|
|
1450
|
-
else if (x['#']) {
|
|
1451
|
-
// #foo
|
|
1452
|
-
// TODO: Signal is not covered by tests + better location
|
|
1453
|
-
// FIXME: We can't do enums yet because they are not resolved (and we don't bother finding their value by hand)
|
|
1454
|
-
error(null, x.$location, 'Enum values are not yet supported for conversion to SQL');
|
|
1455
|
-
return '';
|
|
1456
|
-
}
|
|
1457
|
-
// Reference: Array of path steps, possibly preceded by ':'
|
|
1458
|
-
else if (x.ref) {
|
|
1459
|
-
return renderExpressionRef(x);
|
|
1460
|
-
}
|
|
1461
|
-
// Function call, possibly with args (use '=>' for named args)
|
|
1462
|
-
else if (x.func) {
|
|
1463
|
-
const funcName = smartFuncId(prepareIdentifier(x.func), options.toSql.dialect);
|
|
1464
|
-
if (x.xpr)
|
|
1465
|
-
return renderWindowFunction(funcName, x, env);
|
|
1466
|
-
return renderFunc(funcName, x, options.toSql.dialect, a => renderArgs(a, '=>', env, null));
|
|
1467
|
-
}
|
|
1468
|
-
// Nested expression
|
|
1469
|
-
else if (x.xpr) {
|
|
1470
|
-
if (nestedExpr && !x.cast)
|
|
1471
|
-
return `(${renderExpr(x.xpr, env, inline, true)})`;
|
|
1471
|
+
function renderExpressionRef(x, env) {
|
|
1472
|
+
if (!x.param && !x.global) {
|
|
1473
|
+
const magicReplacement = getVariableReplacement(x.ref, options);
|
|
1472
1474
|
|
|
1473
|
-
|
|
1475
|
+
if (x.ref[0] === '$user') {
|
|
1476
|
+
if (magicReplacement !== null)
|
|
1477
|
+
return `'${magicReplacement}'`;
|
|
1478
|
+
|
|
1479
|
+
const result = render$user();
|
|
1480
|
+
// Invalid second path step doesn't cause a return
|
|
1481
|
+
if (result)
|
|
1482
|
+
return result;
|
|
1474
1483
|
}
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
//
|
|
1478
|
-
|
|
1484
|
+
else if (x.ref[0] === '$at') {
|
|
1485
|
+
const result = render$at();
|
|
1486
|
+
// Invalid second path step doesn't cause a return
|
|
1487
|
+
if (result)
|
|
1488
|
+
return result;
|
|
1479
1489
|
}
|
|
1480
|
-
else if (x.
|
|
1481
|
-
|
|
1482
|
-
return `${renderQuery('<union>', x, increaseIndent(env))}`;
|
|
1490
|
+
else if (x.ref[0] === '$session' && magicReplacement !== null) {
|
|
1491
|
+
return `'${magicReplacement}'`;
|
|
1483
1492
|
}
|
|
1484
|
-
|
|
1485
|
-
throw new ModelError(`Unknown expression: ${JSON.stringify(x)}`);
|
|
1486
|
-
}
|
|
1487
|
-
|
|
1488
|
-
function renderWindowFunction(funcName, node, fctEnv) {
|
|
1489
|
-
const suffix = node.xpr[0]; // OVER
|
|
1490
|
-
let r = `${funcName}(${renderArgs(node, '=>', fctEnv, null)})`;
|
|
1491
|
-
r += ` ${suffix} (${renderExpr(node.xpr.slice(1), fctEnv)})`; // do not pass suffix in renderExpr
|
|
1492
|
-
return r;
|
|
1493
1493
|
}
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
case 'boolean':
|
|
1500
|
-
case 'null':
|
|
1501
|
-
// 17.42, NULL, TRUE
|
|
1502
|
-
return String(x.val).toUpperCase();
|
|
1503
|
-
case 'x':
|
|
1504
|
-
// x'f000'
|
|
1505
|
-
return `${x.literal}'${x.val}'`;
|
|
1506
|
-
case 'date':
|
|
1507
|
-
case 'time':
|
|
1508
|
-
case 'timestamp':
|
|
1509
|
-
if (options.toSql.dialect === 'sqlite') {
|
|
1510
|
-
// simple string literal '2017-11-02'
|
|
1511
|
-
return `'${x.val}'`;
|
|
1512
|
-
}
|
|
1513
|
-
// date'2017-11-02'
|
|
1514
|
-
return `${x.literal}'${x.val}'`;
|
|
1515
|
-
|
|
1516
|
-
case 'string':
|
|
1517
|
-
// 'foo', with proper escaping
|
|
1518
|
-
return `'${x.val.replace(/'/g, '\'\'')}'`;
|
|
1519
|
-
case 'object':
|
|
1520
|
-
if (x.val === null)
|
|
1521
|
-
return 'NULL';
|
|
1522
|
-
|
|
1523
|
-
// otherwise fall through to
|
|
1524
|
-
default:
|
|
1525
|
-
throw new ModelError(`Unknown literal or type: ${JSON.stringify(x)}`);
|
|
1526
|
-
}
|
|
1494
|
+
// FIXME: We currently cannot distinguish whether '$parameters' was quoted or not - we
|
|
1495
|
+
// assume that it was not if the path has length 2 (
|
|
1496
|
+
if (firstPathStepId(x.ref) === '$parameters' && x.ref.length === 2) {
|
|
1497
|
+
// Parameters must be uppercased and unquoted in SQL
|
|
1498
|
+
return `:${x.ref[1].toUpperCase()}`;
|
|
1527
1499
|
}
|
|
1500
|
+
if (x.param)
|
|
1501
|
+
return `:${x.ref[0].toUpperCase()}`;
|
|
1528
1502
|
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
if (x.ref[0] === '$user') {
|
|
1534
|
-
if (magicReplacement !== null)
|
|
1535
|
-
return `'${magicReplacement}'`;
|
|
1536
|
-
|
|
1537
|
-
const result = render$user(x);
|
|
1538
|
-
// Invalid second path step doesn't cause a return
|
|
1539
|
-
if (result)
|
|
1540
|
-
return result;
|
|
1541
|
-
}
|
|
1542
|
-
else if (x.ref[0] === '$at') {
|
|
1543
|
-
const result = render$at(x);
|
|
1544
|
-
// Invalid second path step doesn't cause a return
|
|
1545
|
-
if (result)
|
|
1546
|
-
return result;
|
|
1547
|
-
}
|
|
1548
|
-
else if (x.ref[0] === '$session' && magicReplacement !== null) {
|
|
1549
|
-
return `'${magicReplacement}'`;
|
|
1550
|
-
}
|
|
1551
|
-
}
|
|
1552
|
-
// FIXME: We currently cannot distinguish whether '$parameters' was quoted or not - we
|
|
1553
|
-
// assume that it was not if the path has length 2 (
|
|
1554
|
-
if (firstPathStepId(x.ref) === '$parameters' && x.ref.length === 2) {
|
|
1555
|
-
// Parameters must be uppercased and unquoted in SQL
|
|
1556
|
-
return `:${x.ref[1].toUpperCase()}`;
|
|
1557
|
-
}
|
|
1558
|
-
if (x.param)
|
|
1559
|
-
return `:${x.ref[0].toUpperCase()}`;
|
|
1560
|
-
|
|
1561
|
-
return x.ref.map(renderPathStep)
|
|
1562
|
-
.filter(s => s !== '')
|
|
1563
|
-
.join('.');
|
|
1564
|
-
}
|
|
1503
|
+
return x.ref.map(renderPathStep)
|
|
1504
|
+
.filter(s => s !== '')
|
|
1505
|
+
.join('.');
|
|
1565
1506
|
|
|
1566
1507
|
/**
|
|
1567
|
-
* @param {object} x
|
|
1568
1508
|
* @returns {string|null} Null in case of an invalid second path step
|
|
1569
1509
|
*/
|
|
1570
|
-
function render$user(
|
|
1510
|
+
function render$user() {
|
|
1571
1511
|
// FIXME: this is all not enough: we might need an explicit select item alias
|
|
1572
1512
|
if (x.ref[1] === 'id') {
|
|
1573
1513
|
// Keep the old-style for compatibilty with magicVars.id - instead of magicVars.user.id...
|
|
@@ -1595,18 +1535,17 @@ function toSqlDdl(csn, options) {
|
|
|
1595
1535
|
}
|
|
1596
1536
|
/**
|
|
1597
1537
|
* For a given reference starting with $at, render a 'current_timestamp' literal for plain.
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
function render$at(x) {
|
|
1538
|
+
* For the sql-dialect hana, we render the TO_TIMESTAMP(SESSION_CONTEXT(..)) function.
|
|
1539
|
+
*
|
|
1540
|
+
*
|
|
1541
|
+
* For sqlite, we render the string-format-time (strftime) function.
|
|
1542
|
+
* Because the format of `current_timestamp` is like that: '2021-05-14 09:17:19' whereas
|
|
1543
|
+
* the format for TimeStamps (at least in Node.js) is like that: '2021-01-01T00:00:00.000Z'
|
|
1544
|
+
* --> Therefore the comparison in the temporal where clause doesn't work properly.
|
|
1545
|
+
*
|
|
1546
|
+
* @returns {string|null} Null in case of an invalid second path step
|
|
1547
|
+
*/
|
|
1548
|
+
function render$at() {
|
|
1610
1549
|
if (x.ref[1] === 'from') {
|
|
1611
1550
|
switch (options.toSql.dialect) {
|
|
1612
1551
|
case 'sqlite': {
|
|
@@ -1640,18 +1579,6 @@ function toSqlDdl(csn, options) {
|
|
|
1640
1579
|
return null;
|
|
1641
1580
|
}
|
|
1642
1581
|
|
|
1643
|
-
/**
|
|
1644
|
-
* Renders an explicit `cast()` inside an 'xpr'.
|
|
1645
|
-
*
|
|
1646
|
-
* @param {object} x Expression with cast
|
|
1647
|
-
* @param {string} value Value to cast
|
|
1648
|
-
* @returns {string} CAST statement
|
|
1649
|
-
*/
|
|
1650
|
-
function renderExplicitTypeCast(x, value) {
|
|
1651
|
-
const typeRef = renderBuiltinType(x.cast.type) + renderTypeParameters(x.cast);
|
|
1652
|
-
return `CAST(${value} AS ${typeRef})`;
|
|
1653
|
-
}
|
|
1654
|
-
|
|
1655
1582
|
/**
|
|
1656
1583
|
* Render a single path step 's' at path position 'idx', which can have filters or parameters or be a function
|
|
1657
1584
|
*
|
|
@@ -1709,6 +1636,13 @@ function toSqlDdl(csn, options) {
|
|
|
1709
1636
|
}
|
|
1710
1637
|
}
|
|
1711
1638
|
|
|
1639
|
+
function renderWindowFunction(funcName, node, fctEnv) {
|
|
1640
|
+
const suffix = node.xpr[0]; // OVER
|
|
1641
|
+
let r = `${funcName}(${renderArgs(node, '=>', fctEnv, null)})`;
|
|
1642
|
+
r += ` ${suffix} (${renderExpr(node.xpr.slice(1), fctEnv)})`; // do not pass suffix in renderExpr
|
|
1643
|
+
return r;
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1712
1646
|
/**
|
|
1713
1647
|
* Returns a copy of 'env' with increased indentation
|
|
1714
1648
|
*
|
|
@@ -1720,6 +1654,54 @@ function toSqlDdl(csn, options) {
|
|
|
1720
1654
|
}
|
|
1721
1655
|
}
|
|
1722
1656
|
|
|
1657
|
+
/**
|
|
1658
|
+
* Render the given string for SQL databases.
|
|
1659
|
+
*
|
|
1660
|
+
* @param {string} str
|
|
1661
|
+
* @param {string} sqlDialect
|
|
1662
|
+
* @return {string}
|
|
1663
|
+
*/
|
|
1664
|
+
function renderStringForSql(str, sqlDialect) {
|
|
1665
|
+
if (sqlDialect === 'hana' || sqlDialect === 'sqlite') {
|
|
1666
|
+
// SQLite
|
|
1667
|
+
// ======
|
|
1668
|
+
// SQLite's tokenizer available at
|
|
1669
|
+
// <https://www.sqlite.org/src/file?name=src/tokenize.c>.
|
|
1670
|
+
//
|
|
1671
|
+
// Note that NUL may have side effects, as explained on
|
|
1672
|
+
// <https://sqlite.org/nulinstr.html>.
|
|
1673
|
+
//
|
|
1674
|
+
//
|
|
1675
|
+
// HANA
|
|
1676
|
+
// ====
|
|
1677
|
+
// Respects the specification available at
|
|
1678
|
+
// <https://help.sap.com/doc/9b40bf74f8644b898fb07dabdd2a36ad/2.0.04/en-US/SAP_HANA_SQL_Reference_Guide_en.pdf>.
|
|
1679
|
+
//
|
|
1680
|
+
// <string_literal> ::= <single_quote>[<any_character>...]<single_quote>
|
|
1681
|
+
// <single_quote> ::= '
|
|
1682
|
+
//
|
|
1683
|
+
// and
|
|
1684
|
+
// > # Quotation marks
|
|
1685
|
+
// > Single quotation marks are used to delimit string literals.
|
|
1686
|
+
// > A single quotation mark itself can be represented using two single quotation marks.
|
|
1687
|
+
str = str.replace(/'/g, '\'\'')
|
|
1688
|
+
.replace(/\u{0}/ug, '\' || CHAR(0) || \'');
|
|
1689
|
+
}
|
|
1690
|
+
else {
|
|
1691
|
+
// Generic SQL databases
|
|
1692
|
+
// =====================
|
|
1693
|
+
// While escaping NUL may be useful to avoid the SQL file being identified as binary,
|
|
1694
|
+
// we can't escape it using `CHAR(0)`. This function is not available on e.g. PostgreSQL.
|
|
1695
|
+
// On top of this, PostgreSQL also has this limitation:
|
|
1696
|
+
// > chr(int) | text | Character with the given code. For UTF8 the argument is treated as a Unicode code point.
|
|
1697
|
+
// > | | For other multibyte encodings the argument must designate an ASCII character. The NULL (0)
|
|
1698
|
+
// > | | character is not allowed because text data types cannot store such bytes.
|
|
1699
|
+
// - <https://www.postgresql.org/docs/9.1/functions-string.html>
|
|
1700
|
+
str = str.replace(/'/g, '\'\'');
|
|
1701
|
+
}
|
|
1702
|
+
return `'${str}'`;
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1723
1705
|
module.exports = {
|
|
1724
1706
|
toSqlDdl,
|
|
1725
1707
|
};
|