Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,9 @@ export class DatabricksQuery extends BaseQuery {
}

public escapeColumnName(name: string) {
return `\`${name}\``;
// Use ` to escape ` itself.
// https://docs.databricks.com/en/sql/language-manual/sql-ref-identifiers.html
return `\`${name.replaceAll('`', '``')}\``;
}

public override getFieldIndex(id: string): string | number | null {
Expand Down
2 changes: 1 addition & 1 deletion packages/cubejs-dremio-driver/driver/DremioQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ class DremioQuery extends BaseQuery {
}

escapeColumnName(name) {
return `"${name}"`;
return `"${name.replaceAll('"', '""')}"`;
}

seriesSql(timeDimension) {
Expand Down
4 changes: 3 additions & 1 deletion packages/cubejs-ksql-driver/src/KsqlQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ export class KsqlQuery extends BaseQuery {
}

public escapeColumnName(name: string) {
return `\`${name}\``;
// https://docs.confluent.io/platform/current/ksqldb/reference/sql/lexical-structure.html#ksqldb-lexical-structure-identifiers
// https://github.com/confluentinc/ksql/blob/84afdf1c2504844a15e02643f796288b8b069073/ksqldb-parser/src/main/antlr4/io/confluent/ksql/parser/SqlBase.g4#L378
return `\`${name.replaceAll('`', '``')}\``;
}

public castToString(sql: string) {
Expand Down
8 changes: 6 additions & 2 deletions packages/cubejs-schema-compiler/src/adapter/BaseQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -684,12 +684,16 @@ export class BaseQuery {
}

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

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,12 @@ export class BigqueryQuery extends BaseQuery {
}

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

public timeGroupedColumn(granularity, dimension) {
Expand Down
11 changes: 9 additions & 2 deletions packages/cubejs-schema-compiler/src/adapter/ClickHouseQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,13 @@ export class ClickHouseQuery extends BaseQuery {
}

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

public convertTz(field) {
Expand Down Expand Up @@ -260,7 +266,8 @@ export class ClickHouseQuery extends BaseQuery {
delete templates.functions.PERCENTILECONT;
delete templates.expressions.like_escape;
templates.quotes.identifiers = '`';
templates.quotes.escape = '\\`';
// See escapeColumnName comment
templates.quotes.escape = '\\x60';
templates.types.boolean = 'BOOL';
templates.types.timestamp = 'DATETIME';
delete templates.types.time;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ export class CubeStoreQuery extends BaseQuery {
}

public escapeColumnName(name) {
return `\`${name}\``;
return `\`${name.replaceAll('`', '``')}\``;
}

public seriesSql(timeDimension) {
Expand Down
4 changes: 3 additions & 1 deletion packages/cubejs-schema-compiler/src/adapter/HiveQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ export class HiveQuery extends BaseQuery {
}

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

public simpleQuery() {
Expand Down
4 changes: 3 additions & 1 deletion packages/cubejs-schema-compiler/src/adapter/MysqlQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,9 @@ export class MysqlQuery extends BaseQuery {
}

public escapeColumnName(name) {
return `\`${name}\``;
// 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
// https://dev.mysql.com/doc/refman/8.0/en/identifiers.html
return `\`${name.replaceAll('`', '``')}\``;
}

public seriesSql(timeDimension) {
Expand Down
21 changes: 21 additions & 0 deletions packages/cubejs-schema-compiler/test/unit/base-query.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2375,6 +2375,16 @@ describe('Class unit tests', () => {
expect(baseQuery.cubeAlias('CamelCaseCube.id')).toEqual('"camel_case_cube__id"');
expect(baseQuery.cubeAlias('CamelCaseCube.description')).toEqual('"camel_case_cube__description"');
expect(baseQuery.cubeAlias('CamelCaseCube.grant_total')).toEqual('"camel_case_cube__grant_total"');

// aliasName + memberToAlias
const memberAliasQuery = new BaseQuery(set, {
memberToAlias: {
'CamelCaseCube.id': 'alias("id")'
}
});
expect(memberAliasQuery.aliasName('CamelCaseCube.id', false)).toEqual('alias("id")');
// Should escape quotes
expect(memberAliasQuery.newDimension('CamelCaseCube.id').aliasName()).toEqual('"alias(""id"")"');
});

it('Test BaseQuery with aliased cube', async () => {
Expand Down Expand Up @@ -2424,6 +2434,17 @@ describe('Class unit tests', () => {
expect(baseQuery.cubeAlias('CamelCaseCube.id')).toEqual('"t1__id"');
expect(baseQuery.cubeAlias('CamelCaseCube.description')).toEqual('"t1__description"');
expect(baseQuery.cubeAlias('CamelCaseCube.grant_total')).toEqual('"t1__grant_total"');

// aliasName + memberToAlias
// Should ignore cube alias
const memberAliasQuery = new BaseQuery(set, {
memberToAlias: {
'CamelCaseCube.id': 'alias("id")'
}
});
expect(memberAliasQuery.aliasName('CamelCaseCube.id', false)).toEqual('alias("id")');
// Should escape quotes
expect(memberAliasQuery.newDimension('CamelCaseCube.id').aliasName()).toEqual('"alias(""id"")"');
});

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