@sap/cds-compiler 2.13.8 → 3.0.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 +155 -1594
- package/bin/cdsc.js +144 -66
- package/doc/CHANGELOG_ARCHIVE.md +1592 -0
- package/doc/CHANGELOG_BETA.md +3 -4
- package/doc/CHANGELOG_DEPRECATED.md +35 -1
- package/doc/{DeprecatedOptions.md → DeprecatedOptions_v2.md} +3 -1
- package/doc/Versioning.md +20 -1
- package/lib/api/.eslintrc.json +2 -2
- package/lib/api/main.js +237 -122
- package/lib/api/options.js +17 -88
- package/lib/api/validate.js +12 -16
- package/lib/base/keywords.js +216 -109
- package/lib/base/message-registry.js +152 -37
- package/lib/base/messages.js +145 -83
- package/lib/base/model.js +44 -2
- package/lib/base/optionProcessorHelper.js +19 -0
- package/lib/checks/actionsFunctions.js +7 -5
- package/lib/checks/annotationsOData.js +11 -32
- package/lib/checks/arrayOfs.js +1 -34
- package/lib/checks/cdsPersistence.js +1 -0
- package/lib/checks/elements.js +6 -6
- package/lib/checks/invalidTarget.js +1 -1
- package/lib/checks/nonexpandableStructured.js +1 -1
- package/lib/checks/queryNoDbArtifacts.js +2 -1
- package/lib/checks/selectItems.js +5 -1
- package/lib/checks/types.js +4 -2
- package/lib/checks/utils.js +2 -2
- package/lib/checks/validator.js +4 -5
- package/lib/compiler/assert-consistency.js +16 -10
- package/lib/compiler/base.js +1 -0
- package/lib/compiler/builtins.js +98 -9
- package/lib/compiler/checks.js +22 -70
- package/lib/compiler/define.js +61 -13
- package/lib/compiler/extend.js +79 -14
- package/lib/compiler/finalize-parse-cdl.js +46 -29
- package/lib/compiler/index.js +100 -37
- package/lib/compiler/moduleLayers.js +7 -0
- package/lib/compiler/populate.js +19 -18
- package/lib/compiler/propagator.js +7 -4
- package/lib/compiler/resolve.js +297 -234
- package/lib/compiler/shared.js +107 -102
- package/lib/compiler/tweak-assocs.js +16 -11
- package/lib/compiler/utils.js +5 -0
- package/lib/edm/annotations/genericTranslation.js +93 -21
- package/lib/edm/csn2edm.js +230 -115
- package/lib/edm/edm.js +305 -226
- package/lib/edm/edmPreprocessor.js +509 -438
- package/lib/edm/edmUtils.js +31 -45
- package/lib/gen/Dictionary.json +98 -22
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +10 -30
- package/lib/gen/language.tokens +105 -114
- package/lib/gen/languageLexer.interp +1 -34
- package/lib/gen/languageLexer.js +889 -1007
- package/lib/gen/languageLexer.tokens +95 -106
- package/lib/gen/languageParser.js +20786 -22199
- package/lib/json/csnVersion.js +10 -11
- package/lib/json/from-csn.js +59 -51
- package/lib/json/to-csn.js +10 -10
- package/lib/language/antlrParser.js +2 -2
- package/lib/language/docCommentParser.js +62 -39
- package/lib/language/errorStrategy.js +52 -40
- package/lib/language/genericAntlrParser.js +348 -229
- package/lib/language/language.g4 +629 -653
- package/lib/language/multiLineStringParser.js +14 -42
- package/lib/language/textUtils.js +44 -0
- package/lib/main.d.ts +46 -43
- package/lib/main.js +108 -79
- package/lib/model/csnRefs.js +34 -7
- package/lib/model/csnUtils.js +337 -332
- package/lib/model/enrichCsn.js +1 -0
- package/lib/model/revealInternalProperties.js +30 -10
- package/lib/model/sortViews.js +32 -31
- package/lib/modelCompare/compare.js +6 -6
- package/lib/optionProcessor.js +73 -46
- package/lib/render/.eslintrc.json +1 -1
- package/lib/render/DuplicateChecker.js +4 -7
- package/lib/render/manageConstraints.js +70 -2
- package/lib/render/toCdl.js +1042 -882
- package/lib/render/toHdbcds.js +195 -245
- package/lib/render/toRename.js +44 -22
- package/lib/render/toSql.js +225 -241
- package/lib/render/utils/common.js +145 -15
- package/lib/render/utils/sql.js +20 -19
- package/lib/sql-identifier.js +6 -0
- package/lib/transform/db/.eslintrc.json +4 -3
- package/lib/transform/db/associations.js +2 -2
- package/lib/transform/db/cdsPersistence.js +5 -15
- package/lib/transform/db/constraints.js +4 -2
- package/lib/transform/db/expansion.js +22 -16
- package/lib/transform/db/flattening.js +109 -80
- package/lib/transform/db/transformExists.js +7 -7
- package/lib/transform/db/views.js +9 -6
- package/lib/transform/draft/.eslintrc.json +2 -2
- package/lib/transform/draft/db.js +6 -6
- package/lib/transform/draft/odata.js +6 -7
- package/lib/transform/forHanaNew.js +62 -48
- package/lib/transform/forOdataNew.js +49 -50
- package/lib/transform/localized.js +31 -20
- package/lib/transform/odata/toFinalBaseType.js +16 -14
- package/lib/transform/odata/typesExposure.js +146 -198
- package/lib/transform/odata/utils.js +1 -38
- package/lib/transform/transformUtilsNew.js +67 -84
- package/lib/transform/translateAssocsToJoins.js +7 -3
- package/lib/transform/universalCsn/.eslintrc.json +2 -2
- package/lib/transform/universalCsn/coreComputed.js +16 -9
- package/lib/transform/universalCsn/universalCsnEnricher.js +60 -10
- package/lib/utils/file.js +3 -3
- package/lib/utils/moduleResolve.js +13 -6
- package/lib/utils/timetrace.js +20 -21
- package/package.json +35 -4
- package/share/messages/message-explanations.json +2 -1
- package/share/messages/syntax-expected-integer.md +37 -0
- package/doc/ApiMigration.md +0 -237
- package/doc/CommandLineMigration.md +0 -58
- package/doc/ErrorMessages.md +0 -175
- package/doc/FioriAnnotations.md +0 -94
- package/doc/ODataTransformation.md +0 -273
- package/lib/backends.js +0 -529
- package/lib/fix_antlr4-8_warning.js +0 -56
- 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.sqlDialect), x, env),
|
|
101
|
+
func: (x, env) => renderFunc(smartFuncId(prepareIdentifier(x.func), options.sqlDialect), x, options.sqlDialect, 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.
|
|
@@ -209,7 +238,7 @@ function toSqlDdl(csn, options) {
|
|
|
209
238
|
};
|
|
210
239
|
|
|
211
240
|
// Registries for artifact and element names per CSN section
|
|
212
|
-
const definitionsDuplicateChecker = new DuplicateChecker(options.
|
|
241
|
+
const definitionsDuplicateChecker = new DuplicateChecker(options.sqlMapping);
|
|
213
242
|
const deletionsDuplicateChecker = new DuplicateChecker();
|
|
214
243
|
const extensionsDuplicateChecker = new DuplicateChecker();
|
|
215
244
|
const removeElementsDuplicateChecker = new DuplicateChecker();
|
|
@@ -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.
|
|
265
|
+
if (csn.extensions && (options.sqlDialect === '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.
|
|
278
|
+
if (csn.migrations && (options.sqlDialect === '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,9 +292,9 @@ 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
|
-
// Transfer results from hdb-specific dictionaries into 'sql' dictionary in proper order if
|
|
297
|
+
// Transfer results from hdb-specific dictionaries into 'sql' dictionary in proper order if src === 'sql'
|
|
269
298
|
// (relying on the order of dictionaries above)
|
|
270
299
|
// FIXME: Should consider inter-view dependencies, too
|
|
271
300
|
const sql = Object.create(null);
|
|
@@ -276,10 +305,10 @@ function toSqlDdl(csn, options) {
|
|
|
276
305
|
const { deletions, migrations: _, ...hdbKinds } = mainResultObj;
|
|
277
306
|
for (const hdbKind of Object.keys(hdbKinds)) {
|
|
278
307
|
for (const name in mainResultObj[hdbKind]) {
|
|
279
|
-
if (options.
|
|
308
|
+
if (options.src === 'sql') {
|
|
280
309
|
let sourceString = mainResultObj[hdbKind][name];
|
|
281
310
|
// Hack: Other than in 'hdbtable' files, in HANA SQL COLUMN is not mandatory but default.
|
|
282
|
-
if (options.
|
|
311
|
+
if (options.sqlDialect === 'hana' && hdbKind === 'hdbtable' && sourceString.startsWith('COLUMN '))
|
|
283
312
|
sourceString = sourceString.slice('COLUMN '.length);
|
|
284
313
|
sql[name] = `${options.testMode ? '' : sqlVersionLine}CREATE ${sourceString};`;
|
|
285
314
|
}
|
|
@@ -287,7 +316,7 @@ function toSqlDdl(csn, options) {
|
|
|
287
316
|
mainResultObj[hdbKind][name] = sqlVersionLine + mainResultObj[hdbKind][name];
|
|
288
317
|
}
|
|
289
318
|
}
|
|
290
|
-
if (options.
|
|
319
|
+
if (options.src === 'sql')
|
|
291
320
|
delete mainResultObj[hdbKind];
|
|
292
321
|
}
|
|
293
322
|
|
|
@@ -300,7 +329,7 @@ function toSqlDdl(csn, options) {
|
|
|
300
329
|
mainResultObj.sql = sql;
|
|
301
330
|
}
|
|
302
331
|
|
|
303
|
-
if (options.
|
|
332
|
+
if (options.src === 'sql')
|
|
304
333
|
mainResultObj.sql = sql;
|
|
305
334
|
|
|
306
335
|
for (const name in deletions)
|
|
@@ -391,7 +420,7 @@ function toSqlDdl(csn, options) {
|
|
|
391
420
|
* @returns {string} Artifact name
|
|
392
421
|
*/
|
|
393
422
|
function renderArtifactName(artifactName) {
|
|
394
|
-
return quoteSqlId(getResultingName(csn, options.
|
|
423
|
+
return quoteSqlId(getResultingName(csn, options.sqlMapping, artifactName));
|
|
395
424
|
}
|
|
396
425
|
|
|
397
426
|
// Render an artifact migration into the appropriate dictionary of 'resultObj'.
|
|
@@ -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' ];
|
|
@@ -544,7 +573,7 @@ function toSqlDdl(csn, options) {
|
|
|
544
573
|
const { front, back } = getSqlSnippets(options, art);
|
|
545
574
|
let result = front;
|
|
546
575
|
// Only HANA has row/column tables
|
|
547
|
-
if (options.
|
|
576
|
+
if (options.sqlDialect === 'hana') {
|
|
548
577
|
if (hanaTc && hanaTc.storeType) {
|
|
549
578
|
// Explicitly specified
|
|
550
579
|
result += `${art.technicalConfig.hana.storeType.toUpperCase()} `;
|
|
@@ -575,7 +604,7 @@ function toSqlDdl(csn, options) {
|
|
|
575
604
|
// for `to.sql` w/ dialect `hana` the constraints will be part of the
|
|
576
605
|
const constraintsAsAlter = !options.constraintsInCreateTable && options.src === 'sql' && options.sqlDialect === 'hana';
|
|
577
606
|
if ( !constraintsAsAlter && art.$tableConstraints && art.$tableConstraints.referential) {
|
|
578
|
-
const renderReferentialConstraintsAsHdbconstraint = options.
|
|
607
|
+
const renderReferentialConstraintsAsHdbconstraint = options.src === 'hdi';
|
|
579
608
|
const referentialConstraints = {};
|
|
580
609
|
Object.entries(art.$tableConstraints.referential)
|
|
581
610
|
.forEach(([ fileName, referentialConstraint ]) => {
|
|
@@ -598,7 +627,7 @@ function toSqlDdl(csn, options) {
|
|
|
598
627
|
const uniqueConstraints = art.$tableConstraints && art.$tableConstraints.unique;
|
|
599
628
|
for (const cn in uniqueConstraints) {
|
|
600
629
|
const c = uniqueConstraints[cn];
|
|
601
|
-
if (options.
|
|
630
|
+
if (options.src === 'hdi') {
|
|
602
631
|
resultObj.hdbindex[`${artifactName}.${cn}`]
|
|
603
632
|
= `UNIQUE INVERTED INDEX ${renderArtifactName(`${artifactName}_${cn}`)} ON ${tableName} (${c.map(cpath => quoteSqlId(cpath.ref[0])).join(', ')})`;
|
|
604
633
|
}
|
|
@@ -608,24 +637,24 @@ function toSqlDdl(csn, options) {
|
|
|
608
637
|
}
|
|
609
638
|
result += `${env.indent}\n)`;
|
|
610
639
|
|
|
611
|
-
if (options.
|
|
640
|
+
if (options.sqlDialect === 'hana')
|
|
612
641
|
result += renderTechnicalConfiguration(art.technicalConfig, childEnv);
|
|
613
642
|
|
|
614
643
|
|
|
615
644
|
const associations = Object.keys(art.elements).map(name => renderAssociationElement(name, art.elements[name], childEnv))
|
|
616
645
|
.filter(s => s !== '')
|
|
617
646
|
.join(',\n');
|
|
618
|
-
if (associations !== '' && options.
|
|
647
|
+
if (associations !== '' && options.sqlDialect === 'hana') {
|
|
619
648
|
result += `${env.indent} WITH ASSOCIATIONS (\n${associations}\n`;
|
|
620
649
|
result += `${env.indent})`;
|
|
621
650
|
}
|
|
622
651
|
// Only HANA has indices
|
|
623
652
|
// FIXME: Really? We should provide a DB-agnostic way to specify that
|
|
624
|
-
if (options.
|
|
653
|
+
if (options.sqlDialect === '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;
|
|
@@ -729,7 +758,7 @@ function toSqlDdl(csn, options) {
|
|
|
729
758
|
result += ` DEFAULT ${renderExpr(elm.default, env)}`;
|
|
730
759
|
|
|
731
760
|
// Only HANA has fuzzy indices
|
|
732
|
-
if (fzindex && options.
|
|
761
|
+
if (fzindex && options.sqlDialect === 'hana')
|
|
733
762
|
result += ` ${renderExpr(fzindex, env)}`;
|
|
734
763
|
|
|
735
764
|
// (table) elements can only have a @sql.append
|
|
@@ -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
|
}
|
|
@@ -877,7 +906,7 @@ function toSqlDdl(csn, options) {
|
|
|
877
906
|
throw new ModelError(`Unexpected form of index: "${index}"`);
|
|
878
907
|
|
|
879
908
|
let indexName = renderArtifactName(`${artifactName}.${index[i + 1].ref}`);
|
|
880
|
-
if (options.
|
|
909
|
+
if (options.sqlMapping === 'plain')
|
|
881
910
|
indexName = indexName.replace(/(\.|::)/g, '_');
|
|
882
911
|
|
|
883
912
|
const result = index.slice(0, i + 1); // CREATE UNIQUE INDEX
|
|
@@ -916,7 +945,7 @@ function toSqlDdl(csn, options) {
|
|
|
916
945
|
let result = `${renderViewSource(artifactName, source.args[0], env)}`;
|
|
917
946
|
for (let i = 1; i < source.args.length; i++) {
|
|
918
947
|
result = `(${result} ${source.join.toUpperCase()} `;
|
|
919
|
-
if (options.
|
|
948
|
+
if (options.sqlDialect === 'hana')
|
|
920
949
|
result += renderJoinCardinality(source.cardinality);
|
|
921
950
|
result += `JOIN ${renderViewSource(artifactName, source.args[i], env)}`;
|
|
922
951
|
if (source.on)
|
|
@@ -979,7 +1008,7 @@ function toSqlDdl(csn, options) {
|
|
|
979
1008
|
let result = renderAbsolutePath(path, ':', env);
|
|
980
1009
|
|
|
981
1010
|
// Take care of aliases
|
|
982
|
-
const implicitAlias = path.ref.length === 0 ? getLastPartOf(getResultingName(csn, options.
|
|
1011
|
+
const implicitAlias = path.ref.length === 0 ? getLastPartOf(getResultingName(csn, options.sqlMapping, path.ref[0])) : getLastPartOfRef(path.ref);
|
|
983
1012
|
if (path.as) {
|
|
984
1013
|
// Source had an alias - render it
|
|
985
1014
|
result += ` AS ${quoteSqlId(path.as)}`;
|
|
@@ -1089,14 +1118,15 @@ 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 &&
|
|
1099
|
-
if (isDeprecatedEnabled(options, '
|
|
1128
|
+
if (leaf && elements[leaf] && elements[leaf].virtual) {
|
|
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)}`;
|
|
1102
1132
|
}
|
|
@@ -1124,18 +1154,18 @@ 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)
|
|
1135
1165
|
.map(name => renderAssociationElement(name, art.elements[name], childEnv))
|
|
1136
1166
|
.filter(s => s !== '')
|
|
1137
1167
|
.join(',\n');
|
|
1138
|
-
if (associations !== '' && options.
|
|
1168
|
+
if (associations !== '' && options.sqlDialect === 'hana') {
|
|
1139
1169
|
result += `${env.indent}\nWITH ASSOCIATIONS (\n${associations}\n`;
|
|
1140
1170
|
result += `${env.indent})`;
|
|
1141
1171
|
}
|
|
@@ -1167,7 +1197,7 @@ function toSqlDdl(csn, options) {
|
|
|
1167
1197
|
// this would be an incompatible change, as non-uppercased, quoted identifiers
|
|
1168
1198
|
// are rejected by the HANA compiler.
|
|
1169
1199
|
let pIdentifier;
|
|
1170
|
-
if (options.
|
|
1200
|
+
if (options.sqlMapping === 'quoted' || options.sqlMapping === 'hdbcds')
|
|
1171
1201
|
pIdentifier = prepareIdentifier(pn);
|
|
1172
1202
|
else
|
|
1173
1203
|
pIdentifier = quoteSqlId(pn);
|
|
@@ -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)}`;
|
|
@@ -1355,7 +1386,7 @@ function toSqlDdl(csn, options) {
|
|
|
1355
1386
|
'cds.LocalTime': 'cds.Time',
|
|
1356
1387
|
};
|
|
1357
1388
|
const tName = forHanaRenamesToEarly[typeName] || typeName;
|
|
1358
|
-
const types = cdsToSqlTypes[options.
|
|
1389
|
+
const types = cdsToSqlTypes[options.sqlDialect];
|
|
1359
1390
|
return types && types[tName] || cdsToSqlTypes.standard[tName] || 'CHAR';
|
|
1360
1391
|
}
|
|
1361
1392
|
|
|
@@ -1395,7 +1426,7 @@ function toSqlDdl(csn, options) {
|
|
|
1395
1426
|
|
|
1396
1427
|
if (elm.srid !== undefined) {
|
|
1397
1428
|
// SAP HANA Geometry types translate into CHAR in plain/sqlite (give them the default length of 2000)
|
|
1398
|
-
if (options.
|
|
1429
|
+
if (options.sqlDialect !== 'hana')
|
|
1399
1430
|
params.push(2000);
|
|
1400
1431
|
else
|
|
1401
1432
|
params.push(elm.srid);
|
|
@@ -1403,218 +1434,128 @@ 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
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
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.sqlDialect === 'sqlite') {
|
|
1452
|
+
// simple string literal '2017-11-02'
|
|
1453
|
+
return `'${x.val}'`;
|
|
1454
|
+
}
|
|
1455
|
+
// date'2017-11-02'
|
|
1456
|
+
return `${x.literal}'${x.val}'`;
|
|
1457
|
+
|
|
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';
|
|
1464
|
+
|
|
1465
|
+
// otherwise fall through to
|
|
1466
|
+
default:
|
|
1467
|
+
throw new ModelError(`Unknown literal or type: ${JSON.stringify(x)}`);
|
|
1429
1468
|
}
|
|
1430
|
-
|
|
1431
|
-
// FIXME: For the sake of simplicity, we should get away from all this uppercasing in toSql
|
|
1469
|
+
}
|
|
1432
1470
|
|
|
1433
|
-
|
|
1471
|
+
function renderExpressionRef(x, env) {
|
|
1472
|
+
if (!x.param && !x.global) {
|
|
1473
|
+
const magicReplacement = getVariableReplacement(x.ref, options);
|
|
1434
1474
|
|
|
1475
|
+
if (x.ref[0] === '$user') {
|
|
1476
|
+
if (magicReplacement !== null)
|
|
1477
|
+
return renderStringForSql(magicReplacement, options.sqlDialect);
|
|
1435
1478
|
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
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 '';
|
|
1479
|
+
const result = render$user();
|
|
1480
|
+
// Invalid second path step doesn't cause a return
|
|
1481
|
+
if (result)
|
|
1482
|
+
return result;
|
|
1456
1483
|
}
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
return
|
|
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;
|
|
1460
1489
|
}
|
|
1461
|
-
|
|
1462
|
-
|
|
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));
|
|
1490
|
+
else if (x.ref[0] === '$session' && magicReplacement !== null) {
|
|
1491
|
+
return renderStringForSql(magicReplacement, options.sqlDialect);
|
|
1467
1492
|
}
|
|
1468
|
-
//
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
return `(${renderQuery('<subselect>', x, increaseIndent(env))})`;
|
|
1479
|
-
}
|
|
1480
|
-
else if (x.SET) {
|
|
1481
|
-
// renderQuery for SET always brings its own parentheses (because it is also used in renderViewSource)
|
|
1482
|
-
return `${renderQuery('<union>', x, increaseIndent(env))}`;
|
|
1493
|
+
else if (x.ref[0] === '$now') { // TODO: Can there be cases where $now is followed by something?
|
|
1494
|
+
switch (options.sqlDialect) {
|
|
1495
|
+
case 'sqlite':
|
|
1496
|
+
case 'hana':
|
|
1497
|
+
return 'CURRENT_TIMESTAMP';
|
|
1498
|
+
case 'postgres':
|
|
1499
|
+
return 'current_timestamp';
|
|
1500
|
+
default:
|
|
1501
|
+
return quoteSqlId(x.ref[0]);
|
|
1502
|
+
}
|
|
1483
1503
|
}
|
|
1484
|
-
|
|
1485
|
-
throw new ModelError(`Unknown expression: ${JSON.stringify(x)}`);
|
|
1486
1504
|
}
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
return r;
|
|
1493
|
-
}
|
|
1494
|
-
|
|
1495
|
-
function renderExpressionLiteral(x) {
|
|
1496
|
-
// Literal value, possibly with explicit 'literal' property
|
|
1497
|
-
switch (x.literal || typeof x.val) {
|
|
1498
|
-
case 'number':
|
|
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
|
-
}
|
|
1505
|
+
// FIXME: We currently cannot distinguish whether '$parameters' was quoted or not - we
|
|
1506
|
+
// assume that it was not if the path has length 2 (
|
|
1507
|
+
if (firstPathStepId(x.ref) === '$parameters' && x.ref.length === 2) {
|
|
1508
|
+
// Parameters must be uppercased and unquoted in SQL
|
|
1509
|
+
return `:${x.ref[1].toUpperCase()}`;
|
|
1527
1510
|
}
|
|
1511
|
+
if (x.param)
|
|
1512
|
+
return `:${x.ref[0].toUpperCase()}`;
|
|
1528
1513
|
|
|
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
|
-
}
|
|
1514
|
+
return x.ref.map(renderPathStep)
|
|
1515
|
+
.filter(s => s !== '')
|
|
1516
|
+
.join('.');
|
|
1565
1517
|
|
|
1566
1518
|
/**
|
|
1567
|
-
* @param {object} x
|
|
1568
1519
|
* @returns {string|null} Null in case of an invalid second path step
|
|
1569
1520
|
*/
|
|
1570
|
-
function render$user(
|
|
1571
|
-
// FIXME: this is all not enough: we might need an explicit select item alias
|
|
1521
|
+
function render$user() {
|
|
1522
|
+
// FIXME: this is all not enough: we might need an explicit select item alias (?)
|
|
1572
1523
|
if (x.ref[1] === 'id') {
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
else if ((options.toSql.user && options.toSql.user.id) && (typeof options.toSql.user.id === 'string' || options.toSql.user.id instanceof String))
|
|
1578
|
-
return `'${options.toSql.user.id}'`;
|
|
1579
|
-
|
|
1580
|
-
if (options.toSql.dialect === 'sqlite' || options.toSql.dialect === 'plain') {
|
|
1581
|
-
warning(null, null, 'The "$user" variable is not supported. Use option "variableReplacements" to specify a value for "$user.id"');
|
|
1582
|
-
return '\'$user.id\'';
|
|
1583
|
-
}
|
|
1584
|
-
return 'SESSION_CONTEXT(\'APPLICATIONUSER\')';
|
|
1524
|
+
if (options.sqlDialect === 'hana')
|
|
1525
|
+
return 'SESSION_CONTEXT(\'APPLICATIONUSER\')';
|
|
1526
|
+
warning(null, null, 'The "$user" variable is not supported. Use option "variableReplacements" to specify a value for "$user.id"');
|
|
1527
|
+
return '\'$user.id\'';
|
|
1585
1528
|
}
|
|
1586
1529
|
else if (x.ref[1] === 'locale') {
|
|
1587
|
-
if (options.
|
|
1588
|
-
return (
|
|
1589
|
-
|
|
1590
|
-
}
|
|
1591
|
-
return 'SESSION_CONTEXT(\'LOCALE\')';
|
|
1530
|
+
if (options.sqlDialect === 'hana')
|
|
1531
|
+
return 'SESSION_CONTEXT(\'LOCALE\')';
|
|
1532
|
+
return '\'en\''; // default language
|
|
1592
1533
|
}
|
|
1593
1534
|
// Basically: Second path step was invalid, do nothing - should not happen, see 'unknownMagic.js'
|
|
1594
1535
|
return null;
|
|
1595
1536
|
}
|
|
1596
1537
|
/**
|
|
1597
1538
|
* 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) {
|
|
1539
|
+
* For the sql-dialect hana, we render the TO_TIMESTAMP(SESSION_CONTEXT(..)) function.
|
|
1540
|
+
*
|
|
1541
|
+
*
|
|
1542
|
+
* For sqlite, we render the string-format-time (strftime) function.
|
|
1543
|
+
* Because the format of `current_timestamp` is like that: '2021-05-14 09:17:19' whereas
|
|
1544
|
+
* the format for TimeStamps (at least in Node.js) is like that: '2021-01-01T00:00:00.000Z'
|
|
1545
|
+
* --> Therefore the comparison in the temporal where clause doesn't work properly.
|
|
1546
|
+
*
|
|
1547
|
+
* @returns {string|null} Null in case of an invalid second path step
|
|
1548
|
+
*/
|
|
1549
|
+
function render$at() {
|
|
1610
1550
|
if (x.ref[1] === 'from') {
|
|
1611
|
-
switch (options.
|
|
1551
|
+
switch (options.sqlDialect) {
|
|
1612
1552
|
case 'sqlite': {
|
|
1613
1553
|
const dateFromFormat = '%Y-%m-%dT%H:%M:%S.000Z';
|
|
1614
1554
|
return `strftime('${dateFromFormat}', 'now')`;
|
|
1615
1555
|
}
|
|
1616
1556
|
case 'hana':
|
|
1617
1557
|
return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-FROM\'))';
|
|
1558
|
+
case 'postgres':
|
|
1618
1559
|
case 'plain':
|
|
1619
1560
|
return 'current_timestamp';
|
|
1620
1561
|
default:
|
|
@@ -1623,7 +1564,7 @@ function toSqlDdl(csn, options) {
|
|
|
1623
1564
|
}
|
|
1624
1565
|
|
|
1625
1566
|
if (x.ref[1] === 'to') {
|
|
1626
|
-
switch (options.
|
|
1567
|
+
switch (options.sqlDialect) {
|
|
1627
1568
|
case 'sqlite': {
|
|
1628
1569
|
// + 1ms compared to $at.from
|
|
1629
1570
|
const dateToFormat = '%Y-%m-%dT%H:%M:%S.001Z';
|
|
@@ -1631,6 +1572,7 @@ function toSqlDdl(csn, options) {
|
|
|
1631
1572
|
}
|
|
1632
1573
|
case 'hana':
|
|
1633
1574
|
return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-TO\'))';
|
|
1575
|
+
case 'postgres':
|
|
1634
1576
|
case 'plain':
|
|
1635
1577
|
return 'current_timestamp';
|
|
1636
1578
|
default:
|
|
@@ -1640,18 +1582,6 @@ function toSqlDdl(csn, options) {
|
|
|
1640
1582
|
return null;
|
|
1641
1583
|
}
|
|
1642
1584
|
|
|
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
1585
|
/**
|
|
1656
1586
|
* Render a single path step 's' at path position 'idx', which can have filters or parameters or be a function
|
|
1657
1587
|
*
|
|
@@ -1664,7 +1594,6 @@ function toSqlDdl(csn, options) {
|
|
|
1664
1594
|
if (typeof (s) === 'string') {
|
|
1665
1595
|
// TODO: When is this actually executed and not handled already in renderExpr?
|
|
1666
1596
|
const magicForHana = {
|
|
1667
|
-
$now: 'CURRENT_TIMESTAMP',
|
|
1668
1597
|
'$user.id': 'SESSION_CONTEXT(\'APPLICATIONUSER\')',
|
|
1669
1598
|
'$user.locale': 'SESSION_CONTEXT(\'LOCALE\')',
|
|
1670
1599
|
};
|
|
@@ -1672,7 +1601,7 @@ function toSqlDdl(csn, options) {
|
|
|
1672
1601
|
if (idx === 0) {
|
|
1673
1602
|
// HANA-specific translation of '$now' and '$user'
|
|
1674
1603
|
// FIXME: this is all not enough: we might need an explicit select item alias
|
|
1675
|
-
if (magicForHana[s])
|
|
1604
|
+
if (options.sqlDialect === 'hana' && magicForHana[s])
|
|
1676
1605
|
return magicForHana[s];
|
|
1677
1606
|
|
|
1678
1607
|
// Ignore initial $projection and initial $self
|
|
@@ -1709,6 +1638,13 @@ function toSqlDdl(csn, options) {
|
|
|
1709
1638
|
}
|
|
1710
1639
|
}
|
|
1711
1640
|
|
|
1641
|
+
function renderWindowFunction(funcName, node, fctEnv) {
|
|
1642
|
+
const suffix = node.xpr[0]; // OVER
|
|
1643
|
+
let r = `${funcName}(${renderArgs(node, '=>', fctEnv, null)})`;
|
|
1644
|
+
r += ` ${suffix} (${renderExpr(node.xpr.slice(1), fctEnv)})`; // do not pass suffix in renderExpr
|
|
1645
|
+
return r;
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1712
1648
|
/**
|
|
1713
1649
|
* Returns a copy of 'env' with increased indentation
|
|
1714
1650
|
*
|
|
@@ -1720,6 +1656,54 @@ function toSqlDdl(csn, options) {
|
|
|
1720
1656
|
}
|
|
1721
1657
|
}
|
|
1722
1658
|
|
|
1659
|
+
/**
|
|
1660
|
+
* Render the given string for SQL databases.
|
|
1661
|
+
*
|
|
1662
|
+
* @param {string} str
|
|
1663
|
+
* @param {string} sqlDialect
|
|
1664
|
+
* @return {string}
|
|
1665
|
+
*/
|
|
1666
|
+
function renderStringForSql(str, sqlDialect) {
|
|
1667
|
+
if (sqlDialect === 'hana' || sqlDialect === 'sqlite') {
|
|
1668
|
+
// SQLite
|
|
1669
|
+
// ======
|
|
1670
|
+
// SQLite's tokenizer available at
|
|
1671
|
+
// <https://www.sqlite.org/src/file?name=src/tokenize.c>.
|
|
1672
|
+
//
|
|
1673
|
+
// Note that NUL may have side effects, as explained on
|
|
1674
|
+
// <https://sqlite.org/nulinstr.html>.
|
|
1675
|
+
//
|
|
1676
|
+
//
|
|
1677
|
+
// HANA
|
|
1678
|
+
// ====
|
|
1679
|
+
// Respects the specification available at
|
|
1680
|
+
// <https://help.sap.com/doc/9b40bf74f8644b898fb07dabdd2a36ad/2.0.04/en-US/SAP_HANA_SQL_Reference_Guide_en.pdf>.
|
|
1681
|
+
//
|
|
1682
|
+
// <string_literal> ::= <single_quote>[<any_character>...]<single_quote>
|
|
1683
|
+
// <single_quote> ::= '
|
|
1684
|
+
//
|
|
1685
|
+
// and
|
|
1686
|
+
// > # Quotation marks
|
|
1687
|
+
// > Single quotation marks are used to delimit string literals.
|
|
1688
|
+
// > A single quotation mark itself can be represented using two single quotation marks.
|
|
1689
|
+
str = str.replace(/'/g, '\'\'')
|
|
1690
|
+
.replace(/\u{0}/ug, '\' || CHAR(0) || \'');
|
|
1691
|
+
}
|
|
1692
|
+
else {
|
|
1693
|
+
// Generic SQL databases
|
|
1694
|
+
// =====================
|
|
1695
|
+
// While escaping NUL may be useful to avoid the SQL file being identified as binary,
|
|
1696
|
+
// we can't escape it using `CHAR(0)`. This function is not available on e.g. PostgreSQL.
|
|
1697
|
+
// On top of this, PostgreSQL also has this limitation:
|
|
1698
|
+
// > chr(int) | text | Character with the given code. For UTF8 the argument is treated as a Unicode code point.
|
|
1699
|
+
// > | | For other multibyte encodings the argument must designate an ASCII character. The NULL (0)
|
|
1700
|
+
// > | | character is not allowed because text data types cannot store such bytes.
|
|
1701
|
+
// - <https://www.postgresql.org/docs/9.1/functions-string.html>
|
|
1702
|
+
str = str.replace(/'/g, '\'\'');
|
|
1703
|
+
}
|
|
1704
|
+
return `'${str}'`;
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1723
1707
|
module.exports = {
|
|
1724
1708
|
toSqlDdl,
|
|
1725
1709
|
};
|