Skip to content
Merged
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 @@ -1262,10 +1262,16 @@ public ResultSet getPrimaryKeys(String catalog, String schema, String table) thr
"FROM system.tables " +
"ARRAY JOIN arrayZip(splitByChar(',', primary_key), arrayEnumerate(splitByChar(',', primary_key))) as c " +
"WHERE system.tables.primary_key <> '' " +
"AND system.tables.database ILIKE '" + (schema == null ? "%" : schema) + "' " +
"AND system.tables.name ILIKE '" + (table == null ? "%" : table) + "' " +
"AND system.tables.database ILIKE ? " +
"AND system.tables.name ILIKE ? " +
"ORDER BY COLUMN_NAME";
return createStatement().executeQuery(sql);
try (PreparedStatement stmt = prepareStatement(sql)) {
stmt.setString(1, schema == null ? "%" : schema);
stmt.setString(2, table == null ? "%" : table);
try (ResultSet rs = stmt.executeQuery()) {
return DetachedResultSet.createFromResultSet(rs, connection.getDefaultCalendar(), Collections.emptyList());
}
}
} catch (Exception e) {
throw ExceptionUtils.toSqlState(e);
}
Expand Down Expand Up @@ -1808,9 +1814,13 @@ public RowIdLifetime getRowIdLifetime() throws SQLException {
@Override
public ResultSet getSchemas(String catalog, String schemaPattern) throws SQLException {
// TODO: handle useCatalogs == true and return schema catalog name
try {
return createStatement().executeQuery("SELECT name AS TABLE_SCHEM, " + catalogPlaceholder + " AS TABLE_CATALOG FROM system.databases " +
"WHERE name LIKE '" + (schemaPattern == null ? "%" : schemaPattern) + "'");
String sql = "SELECT name AS TABLE_SCHEM, " + catalogPlaceholder + " AS TABLE_CATALOG FROM system.databases " +
"WHERE name LIKE ?";
try (PreparedStatement stmt = prepareStatement(sql)) {
stmt.setString(1, schemaPattern == null ? "%" : schemaPattern);
try (ResultSet rs = stmt.executeQuery()) {
return DetachedResultSet.createFromResultSet(rs, connection.getDefaultCalendar(), Collections.emptyList());
}
} catch (Exception e) {
throw ExceptionUtils.toSqlState(e);
}
Expand Down Expand Up @@ -1861,9 +1871,12 @@ public ResultSet getFunctions(String catalog, String schemaPattern, String funct
"CAST(" + java.sql.DatabaseMetaData.functionResultUnknown + " AS Int16) AS FUNCTION_TYPE, " +
"name AS SPECIFIC_NAME " +
"FROM system.functions " +
"WHERE name LIKE '" + (functionNamePattern == null ? "%" : functionNamePattern) + "'";
try {
return createStatement().executeQuery(sql);
"WHERE name LIKE ?";
try (PreparedStatement stmt = prepareStatement(sql)) {
stmt.setString(1, functionNamePattern == null ? "%" : functionNamePattern);
try (ResultSet rs = stmt.executeQuery()) {
return DetachedResultSet.createFromResultSet(rs, connection.getDefaultCalendar(), Collections.emptyList());
}
} catch (Exception e) {
throw ExceptionUtils.toSqlState(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.clickhouse.jdbc.internal.JdbcUtils;
import org.testng.Assert;
import org.testng.SkipException;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import java.io.ByteArrayOutputStream;
Expand Down Expand Up @@ -607,6 +608,89 @@ public void testGetPrimaryKeys() throws Exception {
}
}

/**
* A {@link DatabaseMetaData} lookup that takes a schema pattern and an object-name pattern.
* Used to run the same assertions over the metadata methods that filter on caller-supplied
* patterns.
*/
@FunctionalInterface
private interface MetaDataPatternCall {
ResultSet apply(DatabaseMetaData dbmd, String schemaPattern, String namePattern) throws SQLException;
}

@DataProvider(name = "metadataPatternArguments")
public static Object[][] metadataPatternArguments() {
final String schemaWithQuote = QUOTED_NAME_SCHEMA;
final String tableWithQuote = QUOTED_NAME_TABLE;
final String injection = "nomatch' OR '1'='1";

MetaDataPatternCall getSchemas = (dbmd, schemaPattern, namePattern) -> dbmd.getSchemas(null, schemaPattern);
MetaDataPatternCall getPrimaryKeys = (dbmd, schemaPattern, namePattern) -> dbmd.getPrimaryKeys(null, schemaPattern, namePattern);
MetaDataPatternCall getFunctions = (dbmd, schemaPattern, namePattern) -> dbmd.getFunctions(null, null, namePattern);

return new Object[][] {
// A name that legally contains a single quote must be matched, not rejected as a syntax error.
{ "getSchemas", getSchemas, schemaWithQuote, null, 1 },
{ "getPrimaryKeys", getPrimaryKeys, schemaWithQuote, tableWithQuote, 1 },
// No function name contains a quote, so the only thing to assert is that it does not throw.
{ "getFunctions", getFunctions, null, "to'Int", 0 },
// The pattern must stay a value: it must not close the literal and inject a predicate.
{ "getSchemas", getSchemas, injection, null, 0 },
{ "getPrimaryKeys", getPrimaryKeys, injection, null, 0 },
{ "getFunctions", getFunctions, null, injection, 0 },
};
}

/**
* Regression test: these methods used to concatenate the caller's pattern straight into the SQL
* text, so a pattern containing a single quote either failed with SYNTAX_ERROR or was parsed as
* SQL and changed the result set.
*/
@Test(groups = { "integration" }, dataProvider = "metadataPatternArguments")
public void testMetadataPatternIsUsedAsValueNotSql(String method, MetaDataPatternCall call,
String schemaPattern, String namePattern,
int expectedRows) throws Exception {
if ("getFunctions".equals(method) && ClickHouseVersion.of(getServerVersion()).check("(,23.8]")) {
throw new SkipException("getFunctions is broken before 23.9 - see testGetFunctions");
}

try (Connection conn = getJdbcConnection()) {
createQuotedNameFixture(conn);
try {
DatabaseMetaData dbmd = conn.getMetaData();
int rows = 0;
try (ResultSet rs = call.apply(dbmd, schemaPattern, namePattern)) {
while (rs.next()) {
rows++;
}
}
assertEquals(rows, expectedRows, method + " returned the wrong number of rows for schemaPattern="
+ schemaPattern + ", namePattern=" + namePattern);
} finally {
dropQuotedNameFixture(conn);
}
}
}

/** Deliberately contains a single quote, and no {@code _} so that LIKE matches it exactly. */
private static final String QUOTED_NAME_SCHEMA = "mdquotea'b";

private static final String QUOTED_NAME_TABLE = "t'1";

private static void createQuotedNameFixture(Connection conn) throws SQLException {
try (Statement stmt = conn.createStatement()) {
stmt.executeUpdate("DROP DATABASE IF EXISTS `mdquotea'b`");
stmt.executeUpdate("CREATE DATABASE `mdquotea'b`");
stmt.executeUpdate("CREATE TABLE `mdquotea'b`.`t'1` (id Int32, v String) ENGINE MergeTree ORDER BY id");
}
}

private static void dropQuotedNameFixture(Connection conn) throws SQLException {
try (Statement stmt = conn.createStatement()) {
stmt.executeUpdate("DROP DATABASE IF EXISTS `mdquotea'b`");
}
}

@Test(groups = { "integration" })
public void testGetSchemas() throws Exception {
try (Connection conn = getJdbcConnection()) {
Expand Down
Loading