Skip to content

Commit 6f51076

Browse files
mcheshkovKSDaemon
authored andcommitted
fix(schema-compiler): Escape quotes in column names
1 parent a5f8a2e commit 6f51076

File tree

7 files changed

+49
-8
lines changed

7 files changed

+49
-8
lines changed

packages/cubejs-schema-compiler/src/adapter/BaseQuery.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -684,12 +684,16 @@ export class BaseQuery {
684684
}
685685

686686
/**
687-
* Wrap specified column/table name with the double quote.
687+
* Wrap specified column/table name with the double quote and escape quotes inside.
688688
* @param {string} name
689689
* @returns {string}
690690
*/
691691
escapeColumnName(name) {
692-
return `"${name}"`;
692+
// Identifier is wrapped with double quotes
693+
// https://ronsavage.github.io/SQL/sql-2003-2.bnf.html#delimited%20identifier
694+
// Double quote inside is represented by double double quote
695+
// https://ronsavage.github.io/SQL/sql-2003-2.bnf.html#doublequote%20symbol
696+
return `"${name.replaceAll('"', '""')}"`;
693697
}
694698

695699
/**

packages/cubejs-schema-compiler/src/adapter/BigqueryQuery.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,12 @@ export class BigqueryQuery extends BaseQuery {
5454
}
5555

5656
public escapeColumnName(name) {
57-
return `\`${name}\``;
57+
// Quoted identifiers must be enclosed by `
58+
// Identifiers have the same escape sequences as string literals
59+
// https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#quoted_identifiers
60+
// \` is escape sequence for `
61+
// https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#escape_sequences
62+
return `\`${name.replaceAll('`', '\\`')}\``;
5863
}
5964

6065
public timeGroupedColumn(granularity, dimension) {

packages/cubejs-schema-compiler/src/adapter/ClickHouseQuery.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,13 @@ export class ClickHouseQuery extends BaseQuery {
3636
}
3737

3838
public escapeColumnName(name) {
39-
return `\`${name}\``;
39+
// ClickHouse does not document it's exact rules regarding identifiers
40+
// https://clickhouse.com/docs/en/sql-reference/syntax#identifiers
41+
// But there's a bit about escaping in string
42+
// https://clickhouse.com/docs/en/sql-reference/syntax#string
43+
// Note that documentation does not allow to use \` sequence
44+
// See https://github.com/ClickHouse/ClickHouse/issues/71310
45+
return `\`${name.replaceAll('`', '\\x60')}\``;
4046
}
4147

4248
public convertTz(field) {
@@ -260,7 +266,8 @@ export class ClickHouseQuery extends BaseQuery {
260266
delete templates.functions.PERCENTILECONT;
261267
delete templates.expressions.like_escape;
262268
templates.quotes.identifiers = '`';
263-
templates.quotes.escape = '\\`';
269+
// See escapeColumnName comment
270+
templates.quotes.escape = '\\x60';
264271
templates.types.boolean = 'BOOL';
265272
templates.types.timestamp = 'DATETIME';
266273
delete templates.types.time;

packages/cubejs-schema-compiler/src/adapter/CubeStoreQuery.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ export class CubeStoreQuery extends BaseQuery {
150150
}
151151

152152
public escapeColumnName(name) {
153-
return `\`${name}\``;
153+
return `\`${name.replaceAll('`', '``')}\``;
154154
}
155155

156156
public seriesSql(timeDimension) {

packages/cubejs-schema-compiler/src/adapter/HiveQuery.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@ export class HiveQuery extends BaseQuery {
5555
}
5656

5757
public escapeColumnName(name) {
58-
return `\`${name}\``;
58+
// Within a backtick string, use double backticks (``) to represent a backtick character
59+
// https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=27362043#LanguageManualSelect-SelectSyntax
60+
return `\`${name.replaceAll('`', '``')}\``;
5961
}
6062

6163
public simpleQuery() {

packages/cubejs-schema-compiler/src/adapter/MysqlQuery.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,9 @@ export class MysqlQuery extends BaseQuery {
133133
}
134134

135135
public escapeColumnName(name) {
136-
return `\`${name}\``;
136+
// If the character to be included within the identifier is the same as that used to quote the identifier itself, then you need to double the character
137+
// https://dev.mysql.com/doc/refman/8.0/en/identifiers.html
138+
return `\`${name.replaceAll('`', '``')}\``;
137139
}
138140

139141
public seriesSql(timeDimension) {

packages/cubejs-schema-compiler/test/unit/base-query.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2375,6 +2375,16 @@ describe('Class unit tests', () => {
23752375
expect(baseQuery.cubeAlias('CamelCaseCube.id')).toEqual('"camel_case_cube__id"');
23762376
expect(baseQuery.cubeAlias('CamelCaseCube.description')).toEqual('"camel_case_cube__description"');
23772377
expect(baseQuery.cubeAlias('CamelCaseCube.grant_total')).toEqual('"camel_case_cube__grant_total"');
2378+
2379+
// aliasName + memberToAlias
2380+
const memberAliasQuery = new BaseQuery(set, {
2381+
memberToAlias: {
2382+
'CamelCaseCube.id': 'alias("id")'
2383+
}
2384+
});
2385+
expect(memberAliasQuery.aliasName('CamelCaseCube.id', false)).toEqual('alias("id")');
2386+
// Should escape quotes
2387+
expect(memberAliasQuery.newDimension('CamelCaseCube.id').aliasName()).toEqual('"alias(""id"")"');
23782388
});
23792389

23802390
it('Test BaseQuery with aliased cube', async () => {
@@ -2424,6 +2434,17 @@ describe('Class unit tests', () => {
24242434
expect(baseQuery.cubeAlias('CamelCaseCube.id')).toEqual('"t1__id"');
24252435
expect(baseQuery.cubeAlias('CamelCaseCube.description')).toEqual('"t1__description"');
24262436
expect(baseQuery.cubeAlias('CamelCaseCube.grant_total')).toEqual('"t1__grant_total"');
2437+
2438+
// aliasName + memberToAlias
2439+
// Should ignore cube alias
2440+
const memberAliasQuery = new BaseQuery(set, {
2441+
memberToAlias: {
2442+
'CamelCaseCube.id': 'alias("id")'
2443+
}
2444+
});
2445+
expect(memberAliasQuery.aliasName('CamelCaseCube.id', false)).toEqual('alias("id")');
2446+
// Should escape quotes
2447+
expect(memberAliasQuery.newDimension('CamelCaseCube.id').aliasName()).toEqual('"alias(""id"")"');
24272448
});
24282449

24292450
it('Test BaseQuery columns order for the query with the sub-query', async () => {

0 commit comments

Comments
 (0)