Skip to content

Commit 41d90d4

Browse files
authored
Fix metadata identifiers containing backticks (#1474)
## Summary - Escape literal backticks before interpolating catalog, schema, and table identifiers into metadata SQL commands. - Apply the same escaping to information_schema catalog prefixes used by procedure metadata. - Add regression coverage for primary keys, foreign keys, other metadata commands, and information_schema catalog prefixes. ## Test plan - mvn test -pl jdbc-core -Dtest=CommandBuilderTest,CommandConstantsTest Fixes #1471 Signed-off-by: Madhavendra Rathore <madhavendra.rathore@databricks.com>
1 parent 3cae948 commit 41d90d4

5 files changed

Lines changed: 88 additions & 7 deletions

File tree

NEXT_CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
### Fixed
1010
- Fixed `setCatalog()` and `setSchema()` producing invalid SQL (e.g. `SET CATALOG ``name``) when the catalog or schema name was passed already wrapped in backticks. Backticks are now stripped before wrapping, and `getCatalog()`/`getSchema()` return the bare identifier name.
11+
- Fixed metadata SQL generation for catalog, schema, and table identifiers containing backticks.
1112

1213
---
1314
*Note: When making changes, please add your change under the appropriate section

src/main/java/com/databricks/jdbc/dbclient/impl/common/CommandConstants.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,11 @@ private static ImmutableSqlParameter buildStringParam(int index, String value) {
132132
.build();
133133
}
134134

135+
public static String escapeSqlIdentifier(String identifier) {
136+
return identifier == null ? null : identifier.replace("`", "``");
137+
}
138+
135139
private static String getCatalogPrefix(String catalog) {
136-
return (catalog == null) ? "system" : "`" + catalog + "`";
140+
return (catalog == null) ? "system" : "`" + escapeSqlIdentifier(catalog) + "`";
137141
}
138142
}

src/main/java/com/databricks/jdbc/dbclient/impl/sqlexec/CommandBuilder.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ private String fetchSchemaSQL() {
8787
// Per JDBC spec, null catalog means "do not narrow the search" — list across all catalogs
8888
showSchemasSQL = SHOW_SCHEMAS_IN_ALL_CATALOGS_SQL;
8989
} else {
90-
showSchemasSQL = String.format(SHOW_SCHEMAS_IN_CATALOG_SQL, catalogName);
90+
showSchemasSQL = String.format(SHOW_SCHEMAS_IN_CATALOG_SQL, escapeSqlIdentifier(catalogName));
9191
}
9292
if (schemaPattern != null) {
9393
showSchemasSQL += String.format(LIKE_SQL, schemaPattern);
@@ -107,7 +107,7 @@ private String fetchTablesSQL() {
107107
// Per JDBC spec, null catalog means "do not narrow the search" — list across all catalogs
108108
showTablesSQL = SHOW_TABLES_IN_ALL_CATALOGS_SQL;
109109
} else {
110-
showTablesSQL = String.format(SHOW_TABLES_SQL, catalogName);
110+
showTablesSQL = String.format(SHOW_TABLES_SQL, escapeSqlIdentifier(catalogName));
111111
}
112112
if (schemaPattern != null) {
113113
showTablesSQL += String.format(SCHEMA_LIKE_SQL, schemaPattern);
@@ -125,7 +125,7 @@ private String fetchColumnsSQL() throws DatabricksSQLException {
125125
catalogName, schemaPattern, tablePattern, columnPattern, sessionContext);
126126
LOGGER.debug(contextString);
127127
throwErrorIfNull(Collections.singletonMap(CATALOG, catalogName), contextString);
128-
String showColumnsSQL = String.format(SHOW_COLUMNS_SQL, catalogName);
128+
String showColumnsSQL = String.format(SHOW_COLUMNS_SQL, escapeSqlIdentifier(catalogName));
129129

130130
if (schemaPattern != null) {
131131
showColumnsSQL += String.format(SCHEMA_LIKE_SQL, schemaPattern);
@@ -149,7 +149,7 @@ private String fetchFunctionsSQL() throws DatabricksSQLException {
149149

150150
LOGGER.debug(contextString);
151151
throwErrorIfNull(Collections.singletonMap(CATALOG, catalogName), contextString);
152-
String showFunctionsSQL = String.format(SHOW_FUNCTIONS_SQL, catalogName);
152+
String showFunctionsSQL = String.format(SHOW_FUNCTIONS_SQL, escapeSqlIdentifier(catalogName));
153153
if (schemaPattern != null) {
154154
showFunctionsSQL += String.format(SCHEMA_LIKE_SQL, schemaPattern);
155155
}
@@ -174,7 +174,11 @@ private String fetchPrimaryKeysSQL() throws DatabricksSQLException {
174174
hashMap.put(SCHEMA, schemaName);
175175
hashMap.put(TABLE, tableName);
176176
throwErrorIfNull(hashMap, contextString);
177-
return String.format(SHOW_PRIMARY_KEYS_SQL, catalogName, schemaName, tableName);
177+
return String.format(
178+
SHOW_PRIMARY_KEYS_SQL,
179+
escapeSqlIdentifier(catalogName),
180+
escapeSqlIdentifier(schemaName),
181+
escapeSqlIdentifier(tableName));
178182
}
179183

180184
private String fetchForeignKeysSQL() throws DatabricksSQLException {
@@ -188,7 +192,11 @@ private String fetchForeignKeysSQL() throws DatabricksSQLException {
188192
hashMap.put(SCHEMA, schemaName);
189193
hashMap.put(TABLE, tableName);
190194
throwErrorIfNull(hashMap, contextString);
191-
return String.format(SHOW_FOREIGN_KEYS_SQL, catalogName, schemaName, tableName);
195+
return String.format(
196+
SHOW_FOREIGN_KEYS_SQL,
197+
escapeSqlIdentifier(catalogName),
198+
escapeSqlIdentifier(schemaName),
199+
escapeSqlIdentifier(tableName));
192200
}
193201

194202
public String getSQLString(CommandName command) throws DatabricksSQLException {
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.databricks.jdbc.dbclient.impl.common;
2+
3+
import static com.databricks.jdbc.dbclient.impl.common.CommandConstants.buildProceduresSQL;
4+
import static org.junit.jupiter.api.Assertions.assertTrue;
5+
6+
import com.databricks.jdbc.api.impl.ImmutableSqlParameter;
7+
import java.util.HashMap;
8+
import java.util.Map;
9+
import org.junit.jupiter.api.DisplayName;
10+
import org.junit.jupiter.api.Test;
11+
12+
class CommandConstantsTest {
13+
14+
@Test
15+
@DisplayName("Should escape backticks in information schema catalog prefix")
16+
void shouldEscapeBackticksInInformationSchemaCatalogPrefix() {
17+
Map<Integer, ImmutableSqlParameter> params = new HashMap<>();
18+
19+
String sql = buildProceduresSQL("cat`alog", null, null, params);
20+
21+
assertTrue(sql.contains(" FROM `cat``alog`.information_schema.routines"));
22+
}
23+
}

src/test/java/com/databricks/jdbc/dbclient/impl/sqlexec/CommandBuilderTest.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,17 @@ void shouldGenerateCorrectSqlForPrimaryKeys() throws SQLException {
4848
assertEquals(expectedSql, sql);
4949
}
5050

51+
@Test
52+
@DisplayName("Should escape backticks in identifiers for primary keys")
53+
void shouldEscapeBackticksInIdentifiersForPrimaryKeys() throws SQLException {
54+
CommandBuilder builder =
55+
new CommandBuilder("cat`alog", mockSession).setSchema("sch`ema").setTable("tab`le");
56+
57+
String sql = builder.getSQLString(CommandName.LIST_PRIMARY_KEYS);
58+
59+
assertEquals("SHOW KEYS IN CATALOG `cat``alog` IN SCHEMA `sch``ema` IN TABLE `tab``le`", sql);
60+
}
61+
5162
@Test
5263
@DisplayName("Should throw SQLException when catalog is null for primary keys")
5364
void shouldThrowExceptionWhenCatalogIsNullForPrimaryKeys() {
@@ -150,6 +161,16 @@ void shouldTreatWildcardCatalogAsLiteral() throws SQLException {
150161
assertEquals(String.format(SHOW_TABLES_SQL, "*"), sql1);
151162
}
152163

164+
@Test
165+
@DisplayName("Should escape backticks in catalog identifier for tables")
166+
void shouldEscapeBackticksInCatalogIdentifierForTables() throws SQLException {
167+
CommandBuilder builder = new CommandBuilder("cat`alog", mockSession);
168+
169+
String sql = builder.getSQLString(CommandName.LIST_TABLES);
170+
171+
assertEquals("SHOW TABLES IN CATALOG `cat``alog`", sql);
172+
}
173+
153174
@Test
154175
@DisplayName("Should generate SCHEMA LIKE clause for empty string schema pattern")
155176
void shouldGenerateSchemaLikeClauseForEmptyStringSchemaPattern() throws SQLException {
@@ -201,6 +222,18 @@ void shouldGenerateCorrectSqlForForeignKeys() throws SQLException {
201222
assertEquals(expectedSql, sql);
202223
}
203224

225+
@Test
226+
@DisplayName("Should escape backticks in identifiers for foreign keys")
227+
void shouldEscapeBackticksInIdentifiersForForeignKeys() throws SQLException {
228+
CommandBuilder builder =
229+
new CommandBuilder("cat`alog", mockSession).setSchema("sch`ema").setTable("tab`le");
230+
231+
String sql = builder.getSQLString(CommandName.LIST_FOREIGN_KEYS);
232+
233+
assertEquals(
234+
"SHOW FOREIGN KEYS IN CATALOG `cat``alog` IN SCHEMA `sch``ema` IN TABLE `tab``le`", sql);
235+
}
236+
204237
@Test
205238
@DisplayName("Should throw SQLException when catalog is null for foreign keys")
206239
void shouldThrowExceptionWhenCatalogIsNullForForeignKeys() {
@@ -227,6 +260,18 @@ void shouldThrowExceptionWhenTableIsNullForForeignKeys() {
227260
}
228261
}
229262

263+
@Test
264+
@DisplayName("Should escape backticks in catalog identifiers for other metadata commands")
265+
void shouldEscapeBackticksInCatalogIdentifiersForOtherMetadataCommands() throws SQLException {
266+
CommandBuilder builder = new CommandBuilder("cat`alog", mockSession);
267+
268+
assertEquals("SHOW SCHEMAS IN `cat``alog`", builder.getSQLString(CommandName.LIST_SCHEMAS));
269+
assertEquals(
270+
"SHOW COLUMNS IN CATALOG `cat``alog`", builder.getSQLString(CommandName.LIST_COLUMNS));
271+
assertEquals(
272+
"SHOW FUNCTIONS IN CATALOG `cat``alog`", builder.getSQLString(CommandName.LIST_FUNCTIONS));
273+
}
274+
230275
@Test
231276
@DisplayName("Should throw exception for unsupported command")
232277
void shouldThrowExceptionForUnsupportedCommand() {

0 commit comments

Comments
 (0)