Skip to content

Commit

Permalink
Merge pull request #1700 from sylph-eu/implement_get_metadata_for_non…
Browse files Browse the repository at this point in the history
…_executed_query

[clickhouse-jdbc] describe SELECT queries to retrieve metadata
  • Loading branch information
chernser authored Jun 25, 2024
2 parents 1b0a40e + 5399034 commit abe31c6
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 1 deletion.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
## Latest

### New Features
- Describe non-executed SELECT queries in prepared statements to provide metadata (https://github.com/ClickHouse/clickhouse-java/issues/1430)

## 0.6.1

### New Features
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,17 @@ default void setClob(int parameterIndex, Clob x) throws SQLException {
@Override
default ResultSetMetaData getMetaData() throws SQLException {
ResultSet currentResult = getResultSet();
return currentResult != null ? currentResult.getMetaData() : null;
if (currentResult != null) {
return currentResult.getMetaData();
} else if (getLargeUpdateCount() != -1L) {
return null; // Update query
}

return describeQueryResult();
}

default ResultSetMetaData describeQueryResult() throws SQLException {
return null;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
import java.sql.Date;
import java.sql.ParameterMetaData;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.time.LocalDate;
Expand Down Expand Up @@ -35,7 +37,9 @@
import com.clickhouse.data.value.ClickHouseStringValue;
import com.clickhouse.logging.Logger;
import com.clickhouse.logging.LoggerFactory;
import com.clickhouse.jdbc.ClickHouseConnection;
import com.clickhouse.jdbc.ClickHousePreparedStatement;
import com.clickhouse.jdbc.ClickHouseResultSetMetaData;
import com.clickhouse.jdbc.JdbcParameterizedQuery;
import com.clickhouse.jdbc.SqlExceptionUtils;
import com.clickhouse.jdbc.parser.ClickHouseSqlStatement;
Expand Down Expand Up @@ -239,6 +243,42 @@ protected int getMaxParameterIndex() {
return templates.length;
}

@Override
public ResultSetMetaData describeQueryResult() throws SQLException {
// No metadata unless query has been recognized as SELECT
if (!parsedStmt.isRecognized() || !parsedStmt.isQuery()) {
return null;
}

final String[] vals;
if (batch.isEmpty()) {
vals = new String[values.length];
System.arraycopy(this.values, 0, vals, 0, values.length);
} else {
vals = batch.get(0);
}
for (int i = 0; i < values.length; i++) {
if (vals[i] == null) {
vals[i] = ClickHouseValues.NULL_EXPR;
}
}

StringBuilder sb = new StringBuilder("desc (");
preparedQuery.apply(sb, vals);
sb.append(')');

List<ClickHouseColumn> columns = new LinkedList<>();
ClickHouseConnection conn = getConnection();
try (Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sb.toString())) {
while (rs.next()) {
columns.add(ClickHouseColumn.of(rs.getString(1), rs.getString(2)));
}
}

return ClickHouseResultSetMetaData.of(conn.getJdbcConfig(), conn.getCurrentDatabase(), "",
Collections.unmodifiableList(new ArrayList<>(columns)), mapper, conn.getTypeMap());
}

@Override
public ResultSet executeQuery() throws SQLException {
ensureParams();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.MalformedURLException;
Expand All @@ -16,6 +17,7 @@
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
Expand Down Expand Up @@ -2006,6 +2008,80 @@ public void testInsertWithSettings() throws SQLException {
}
}

@Test(groups = "integration")
public void testGetMetadataTypes() throws SQLException {
try (Connection conn = newConnection(new Properties());
PreparedStatement ps = conn.prepareStatement("select ? a, ? b")) {
ResultSetMetaData md = ps.getMetaData();
Assert.assertEquals(md.getColumnCount(), 2);
Assert.assertEquals(md.getColumnName(1), "a");
Assert.assertEquals(md.getColumnTypeName(1), "Nullable(Nothing)");
Assert.assertEquals(md.getColumnName(2), "b");
Assert.assertEquals(md.getColumnTypeName(2), "Nullable(Nothing)");

ps.setString(1, "x");
md = ps.getMetaData();
Assert.assertEquals(md.getColumnCount(), 2);
Assert.assertEquals(md.getColumnName(1), "a");
Assert.assertEquals(md.getColumnTypeName(1), "String");
Assert.assertEquals(md.getColumnName(2), "b");
Assert.assertEquals(md.getColumnTypeName(2), "Nullable(Nothing)");

ps.setObject(2, new BigInteger("12345"));
md = ps.getMetaData();
Assert.assertEquals(md.getColumnCount(), 2);
Assert.assertEquals(md.getColumnName(1), "a");
Assert.assertEquals(md.getColumnTypeName(1), "String");
Assert.assertEquals(md.getColumnName(2), "b");
Assert.assertEquals(md.getColumnTypeName(2), "UInt16");

ps.addBatch();
ps.setInt(1, 2);
md = ps.getMetaData();
Assert.assertEquals(md.getColumnCount(), 2);
Assert.assertEquals(md.getColumnName(1), "a");
Assert.assertEquals(md.getColumnTypeName(1), "String");
Assert.assertEquals(md.getColumnName(2), "b");
Assert.assertEquals(md.getColumnTypeName(2), "UInt16");

ps.clearBatch();
ps.clearParameters();
md = ps.getMetaData();
Assert.assertEquals(md.getColumnCount(), 2);
Assert.assertEquals(md.getColumnName(1), "a");
Assert.assertEquals(md.getColumnTypeName(1), "Nullable(Nothing)");
Assert.assertEquals(md.getColumnName(2), "b");
Assert.assertEquals(md.getColumnTypeName(2), "Nullable(Nothing)");
}
}

@Test(groups = "integration")
public void testGetMetadataStatements() throws SQLException {
try (Connection conn = newConnection(new Properties());
PreparedStatement createPs = conn.prepareStatement("create table test_get_metadata_statements (col String) Engine=Log");
PreparedStatement selectPs = conn.prepareStatement("select 'Hello, World!'");
PreparedStatement insertPs = conn.prepareStatement(
"insert into test_get_metadata_statements select 'Hello, World!'");
PreparedStatement updatePs = conn.prepareStatement(
"update test_get_metadata_statements set col = 'Bye, World!'");
PreparedStatement grantPs = conn.prepareStatement("grant select on * to default");
PreparedStatement commitPS = conn.prepareStatement("commit");) {

// Only select shall have valid metadata
ResultSetMetaData selectMetaData = selectPs.getMetaData();
Assert.assertNotNull(selectMetaData);
Assert.assertEquals(selectMetaData.getColumnCount(), 1);
Assert.assertEquals(selectMetaData.getColumnTypeName(1), "String");

// The rest shall return null
Assert.assertNull(createPs.getMetaData());
Assert.assertNull(insertPs.getMetaData());
Assert.assertNull(updatePs.getMetaData());
Assert.assertNull(grantPs.getMetaData());
Assert.assertNull(commitPS.getMetaData());
}
}

@Test(groups = "integration")
public void testGetParameterMetaData() throws SQLException {
try (Connection conn = newConnection(new Properties());
Expand Down

0 comments on commit abe31c6

Please sign in to comment.