From fea9b35d08ba11b9ad0b9fa9d64aa69bfb9ffa9c Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Fri, 11 Apr 2025 17:19:15 -0700 Subject: [PATCH 01/10] Code cleanup. Implemented dummy parameter result set. Drafted prepared stmt metadata --- .../jdbc/PreparedStatementImpl.java | 54 ++++++-- .../com/clickhouse/jdbc/ResultSetImpl.java | 17 ++- .../com/clickhouse/jdbc/StatementImpl.java | 2 +- .../clickhouse/jdbc/internal/JdbcUtils.java | 24 ++++ .../jdbc/metadata/ParameterMetaData.java | 108 --------------- .../jdbc/metadata/ParameterMetaDataImpl.java | 128 ++++++++++++++++++ ...taData.java => ResultSetMetaDataImpl.java} | 76 +++++++---- .../jdbc/PreparedStatementTest.java | 44 ++++-- .../jdbc/metadata/DatabaseMetaDataTest.java | 6 +- .../metadata/ParameterMetaDataImplTest.java | 84 ++++++++++++ .../jdbc/metadata/ParameterMetaDataTest.java | 81 ----------- ...st.java => ResultSetMetaDataImplTest.java} | 2 +- 12 files changed, 376 insertions(+), 250 deletions(-) delete mode 100644 jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/ParameterMetaData.java create mode 100644 jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/ParameterMetaDataImpl.java rename jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/{ResultSetMetaData.java => ResultSetMetaDataImpl.java} (73%) create mode 100644 jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/ParameterMetaDataImplTest.java delete mode 100644 jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/ParameterMetaDataTest.java rename jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/{ResultSetMetaDataTest.java => ResultSetMetaDataImplTest.java} (98%) diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java index 2af908edf..1a5321c3f 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java @@ -1,7 +1,11 @@ package com.clickhouse.jdbc; +import com.clickhouse.client.api.metadata.TableSchema; import com.clickhouse.data.Tuple; import com.clickhouse.jdbc.internal.ExceptionUtils; +import com.clickhouse.jdbc.internal.JdbcUtils; +import com.clickhouse.jdbc.metadata.ParameterMetaDataImpl; +import com.clickhouse.jdbc.metadata.ResultSetMetaDataImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,12 +43,7 @@ import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; import java.time.temporal.ChronoField; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collection; -import java.util.GregorianCalendar; -import java.util.List; -import java.util.Map; +import java.util.*; public class PreparedStatementImpl extends StatementImpl implements PreparedStatement, JdbcV2Wrapper { private static final Logger LOG = LoggerFactory.getLogger(PreparedStatementImpl.class); @@ -64,6 +63,11 @@ public class PreparedStatementImpl extends StatementImpl implements PreparedStat String insertIntoSQL; StatementType statementType; + + private final ParameterMetaData parameterMetaData; + + private ResultSetMetaData resultSetMetaData = null; + public PreparedStatementImpl(ConnectionImpl connection, String sql) throws SQLException { super(connection); this.originalSql = sql.trim(); @@ -83,7 +87,7 @@ public PreparedStatementImpl(ConnectionImpl connection, String sql) throws SQLEx } else { this.parameters = new Object[0]; } - + this.parameterMetaData = new ParameterMetaDataImpl(this.parameters.length); this.defaultCalendar = connection.defaultCalendar; } @@ -305,7 +309,30 @@ public void setArray(int parameterIndex, Array x) throws SQLException { @Override public ResultSetMetaData getMetaData() throws SQLException { checkClosed(); - return null; + + if (resultSetMetaData == null && currentResultSet == null) { + // before execution + if (statementType == StatementType.SELECT) { + try { + String sql = JdbcUtils.replaceQuestionMarks(originalSql, JdbcUtils.NULL); + TableSchema tSchema = connection.getClient().getTableSchemaFromQuery(sql); + resultSetMetaData = new ResultSetMetaDataImpl(tSchema.getColumns(), + tSchema.getDatabaseName(), "", tSchema.getTableName(), Collections.emptyMap()); + } catch (Exception e) { + // fallback to empty until + } + } else if (statementType == StatementType.SHOW) { + // predefined + } else if (statementType == StatementType.DESCRIBE) { + // predefined + } else { + // fallback to empty + } + } else if (resultSetMetaData == null) { + resultSetMetaData = currentResultSet.getMetaData(); + } + + return resultSetMetaData; } @Override @@ -352,10 +379,19 @@ public void setURL(int parameterIndex, URL x) throws SQLException { parameters[parameterIndex - 1] = encodeObject(x); } + /** + * Current JDBC driver implementation implement this functionality. + * But specification doesn't expect throwing an exception what + * for makes us return a "dummy" object. Returned metadata reflects only + * information we can guarantee like parameter count. + * @see ParameterMetaDataImpl + * @return {@link ParameterMetaDataImpl} + * @throws SQLException if the statement is close + */ @Override public ParameterMetaData getParameterMetaData() throws SQLException { checkClosed(); - return null; + return parameterMetaData; } @Override diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java index 41aa8c1fb..5ad4d801a 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java @@ -8,11 +8,9 @@ import java.net.URL; import java.nio.charset.StandardCharsets; import java.sql.*; +import java.sql.Date; import java.time.ZonedDateTime; -import java.util.Calendar; -import java.util.GregorianCalendar; -import java.util.List; -import java.util.Map; +import java.util.*; import com.clickhouse.client.api.data_formats.ClickHouseBinaryFormatReader; import com.clickhouse.client.api.metadata.TableSchema; @@ -21,6 +19,7 @@ import com.clickhouse.data.ClickHouseDataType; import com.clickhouse.jdbc.internal.ExceptionUtils; import com.clickhouse.jdbc.internal.JdbcUtils; +import com.clickhouse.jdbc.metadata.ResultSetMetaDataImpl; import com.clickhouse.jdbc.types.Array; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,7 +38,10 @@ public ResultSetImpl(StatementImpl parentStatement, QueryResponse response, Clic this.parentStatement = parentStatement; this.response = response; this.reader = reader; - this.metaData = new com.clickhouse.jdbc.metadata.ResultSetMetaData(this); + TableSchema tableMetadata = reader.getSchema(); + this.metaData = new ResultSetMetaDataImpl(tableMetadata + .getColumns(), tableMetadata.getDatabaseName(), "", tableMetadata.getTableName(), + Collections.emptyMap()); this.closed = false; this.wasNull = false; this.defaultCalendar = parentStatement.connection.defaultCalendar; @@ -49,7 +51,10 @@ protected ResultSetImpl(ResultSetImpl resultSet) { this.parentStatement = resultSet.parentStatement; this.response = resultSet.response; this.reader = resultSet.reader; - this.metaData = new com.clickhouse.jdbc.metadata.ResultSetMetaData(this); + TableSchema tableMetadata = resultSet.getSchema(); + this.metaData = new ResultSetMetaDataImpl(tableMetadata + .getColumns(), tableMetadata.getDatabaseName(), "", tableMetadata.getTableName(), + Collections.emptyMap()); this.closed = false; this.wasNull = false; this.defaultCalendar = parentStatement.connection.defaultCalendar; diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java index d6b04effc..702bdd6b4 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java @@ -31,7 +31,7 @@ public class StatementImpl implements Statement, JdbcV2Wrapper { ConnectionImpl connection; private int queryTimeout; protected boolean closed; - private ResultSetImpl currentResultSet; + protected ResultSetImpl currentResultSet; private OperationMetrics metrics; protected List batch; private String lastSql; diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcUtils.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcUtils.java index 01662a8fd..99adb696d 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcUtils.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcUtils.java @@ -22,6 +22,8 @@ import java.util.List; import java.util.Map; import java.util.TreeMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class JdbcUtils { //Define a map to store the mapping between ClickHouse data types and SQL data types @@ -271,4 +273,26 @@ public static String escapeQuotes(String str) { .replace("'", "\\'") .replace("\"", "\\\""); } + + public static final String NULL = "NULL"; + + private static final Pattern REPLACE_Q_MARK_PATTERN = Pattern.compile("(\"[^\"]*\"|`[^`]*`)|(\\?)"); + + public static String replaceQuestionMarks(String sql, String replacement) { + Matcher matcher = REPLACE_Q_MARK_PATTERN.matcher(sql); + + StringBuilder result = new StringBuilder(); + + while (matcher.find()) { + if (matcher.group(1) != null) { + // Quoted string — keep as-is + matcher.appendReplacement(result, Matcher.quoteReplacement(matcher.group(1))); + } else if (matcher.group(2) != null) { + // Question mark outside quotes — replace it + matcher.appendReplacement(result, Matcher.quoteReplacement(replacement)); + } + } + matcher.appendTail(result); + return result.toString(); + } } diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/ParameterMetaData.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/ParameterMetaData.java deleted file mode 100644 index 912aaf501..000000000 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/ParameterMetaData.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.clickhouse.jdbc.metadata; - -import com.clickhouse.data.ClickHouseColumn; -import com.clickhouse.jdbc.JdbcV2Wrapper; -import com.clickhouse.jdbc.internal.JdbcUtils; -import com.clickhouse.jdbc.internal.ExceptionUtils; - -import java.sql.SQLException; -import java.util.List; - -public class ParameterMetaData implements java.sql.ParameterMetaData, JdbcV2Wrapper { - private final List params; - - protected ParameterMetaData(List params) throws SQLException { - if (params == null) { - throw ExceptionUtils.toSqlState(new IllegalArgumentException("Parameters array cannot be null.")); - } - - this.params = params; - } - - protected ClickHouseColumn getParam(int param) throws SQLException { - if (param < 1 || param > params.size()) { - throw new SQLException("Parameter index out of range: " + param, ExceptionUtils.SQL_STATE_CLIENT_ERROR); - } - - return params.get(param - 1); - } - - @Override - public int getParameterCount() throws SQLException { - try { - return params.size(); - } catch (Exception e) { - throw ExceptionUtils.toSqlState(e); - } - } - - @Override - public int isNullable(int param) throws SQLException { - try { - return getParam(param).isNullable() ? parameterNullable : parameterNoNulls; - } catch (Exception e) { - throw ExceptionUtils.toSqlState(e); - } - } - - @Override - public boolean isSigned(int param) throws SQLException { - try{ - return getParam(param).getDataType().isSigned(); - } catch (Exception e) { - throw ExceptionUtils.toSqlState(e); - } - } - - @Override - public int getPrecision(int param) throws SQLException { - try { - return getParam(param).getPrecision(); - } catch (Exception e) { - throw ExceptionUtils.toSqlState(e); - } - } - - @Override - public int getScale(int param) throws SQLException { - try { - return getParam(param).getScale(); - } catch (Exception e) { - throw ExceptionUtils.toSqlState(e); - } - } - - @Override - public int getParameterType(int param) throws SQLException { - //TODO: Should we implement .getSQLType()? - try { - return JdbcUtils.convertToSqlType(getParam(param).getDataType()).getVendorTypeNumber(); - } catch (Exception e) { - throw ExceptionUtils.toSqlState(e); - } - } - - @Override - public String getParameterTypeName(int param) throws SQLException { - try { - return getParam(param).getDataType().name(); - } catch (Exception e) { - throw ExceptionUtils.toSqlState(e); - } - } - - @Override - public String getParameterClassName(int param) throws SQLException { - //TODO: Should we implement .getClassName()? - try { - return getParam(param).getDataType().getObjectClass().getName(); - } catch (Exception e) { - throw ExceptionUtils.toSqlState(e); - } - } - - @Override - public int getParameterMode(int param) throws SQLException { - return parameterModeIn; - } -} diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/ParameterMetaDataImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/ParameterMetaDataImpl.java new file mode 100644 index 000000000..d68ee4162 --- /dev/null +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/ParameterMetaDataImpl.java @@ -0,0 +1,128 @@ +package com.clickhouse.jdbc.metadata; + +import com.clickhouse.jdbc.JdbcV2Wrapper; + +import java.sql.ParameterMetaData; +import java.sql.SQLException; +import java.sql.Types; + +/** + * Implement ParameterMetaData interface and provides minimal information about parameters. + * This class will return only actual number of parameters. + * This class cannot be used to determine exact datatype for a parameter. + */ +public class ParameterMetaDataImpl implements ParameterMetaData, JdbcV2Wrapper { + + private final int paramCount; + + public ParameterMetaDataImpl(int paramCount) { + this.paramCount = paramCount; + } + + @Override + public int getParameterCount() { + return paramCount; + } + + private void checkParamIndex(int index) throws SQLException { + if (index > paramCount || index < 1) { + throw new SQLException("Parameter index out of range. " + (paramCount == 0 ? "There are no parameters" : "[1," + paramCount + "]")); + } + } + + /** + * Always returns {@code ParameterMetaData.parameterNullableUnknown}. + * + * @param param parameter index starting from 1 + * @return ParameterMetaData.parameterNullableUnknown + */ + @Override + public int isNullable(int param) throws SQLException { + checkParamIndex(param); + return ParameterMetaData.parameterNullableUnknown; + } + + /** + * Always returns {@code false}. + * + * @param param parameter index starting from 1 + * @return false + */ + @Override + public boolean isSigned(int param) throws SQLException { + checkParamIndex(param); + return false; + } + + /** + * Always returns 0. + * + * @param param parameter index starting from 1 + * @return 0 + */ + @Override + public int getPrecision(int param) throws SQLException { + checkParamIndex(param); + return 0; + } + + /** + * Always returns 0. + * + * @param param parameter index starting from 1 + * @return 0 + */ + @Override + public int getScale(int param) throws SQLException { + checkParamIndex(param); + return 0; + } + + /** + * Always returns {@code Types.OTHER}. + * + * @param param parameter index starting from 1 + * @return {@code Types.OTHER} + */ + @Override + public int getParameterType(int param) throws SQLException { + checkParamIndex(param); + return Types.OTHER; + } + + /** + * Always returns "UNKNOWN". + * + * @param param parameter index starting from 1 + * @return String {@code "UNKNOWN"} + */ + @Override + public String getParameterTypeName(int param) throws SQLException { + checkParamIndex(param); + return "UNKNOWN"; + } + + /** + * Always returns {@code Object.class.getName()}. + * + * @param param the first parameter is 1, the second is 2, ... + * @return String {@code Object.class.getName()} + */ + @Override + public String getParameterClassName(int param) throws SQLException { + checkParamIndex(param); + return Object.class.getName(); + } + + /** + * Always return {@code java.sql.ParameterMetaData#parameterModeIn}. + * + * @param param parameter index starting from 1 + * @return {@code java.sql.ParameterMetaData#parameterModeIn} + */ + @Override + public int getParameterMode(int param) throws SQLException { + checkParamIndex(param); + return parameterModeIn; + } +} diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/ResultSetMetaData.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/ResultSetMetaDataImpl.java similarity index 73% rename from jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/ResultSetMetaData.java rename to jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/ResultSetMetaDataImpl.java index ab759c685..8dee6717a 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/ResultSetMetaData.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/ResultSetMetaDataImpl.java @@ -1,50 +1,66 @@ package com.clickhouse.jdbc.metadata; -import java.sql.SQLException; - -import com.clickhouse.client.api.metadata.TableSchema; import com.clickhouse.data.ClickHouseColumn; +import com.clickhouse.data.ClickHouseDataType; import com.clickhouse.jdbc.JdbcV2Wrapper; -import com.clickhouse.jdbc.ResultSetImpl; -import com.clickhouse.jdbc.internal.JdbcUtils; import com.clickhouse.jdbc.internal.ExceptionUtils; +import com.clickhouse.jdbc.internal.JdbcUtils; + +import java.sql.SQLException; +import java.util.List; +import java.util.Map; + +public class ResultSetMetaDataImpl implements java.sql.ResultSetMetaData, JdbcV2Wrapper { + + private final List columns; + + private final String schema; + + private final String catalog; -public class ResultSetMetaData implements java.sql.ResultSetMetaData, JdbcV2Wrapper { - private final ResultSetImpl resultSet; - public ResultSetMetaData(ResultSetImpl resultSet) { - this.resultSet = resultSet; + private final String tableName; + + private final Map> typeClassMap; + + public ResultSetMetaDataImpl(List columns, String schema, String catalog, String tableName, Map> typeClassMap) { + this.columns = columns; + this.schema = schema; + this.catalog = catalog; + this.tableName = tableName; + this.typeClassMap = typeClassMap; } private ClickHouseColumn getColumn(int column) throws SQLException { - if (column < 1 || column > getColumnCount()) { + try { + return columns.get(column - 1); + } catch (IndexOutOfBoundsException e) { throw new SQLException("Column index out of range: " + column, ExceptionUtils.SQL_STATE_CLIENT_ERROR); } - return resultSet.getSchema().getColumns().get(column - 1); } @Override public int getColumnCount() throws SQLException { - try { - TableSchema schema = resultSet.getSchema(); - return schema.getColumns().size(); - } catch (Exception e) { - throw ExceptionUtils.toSqlState(e); - } + return columns.size(); } @Override public boolean isAutoIncrement(int column) throws SQLException { - return false; + return false; // no auto-incremental types } @Override public boolean isCaseSensitive(int column) throws SQLException { - return true; + try { + // TODO: should be in sync with DatabaseMetadata + return getColumn(column).getDataType().isCaseSensitive(); + } catch (Exception e) { + throw ExceptionUtils.toSqlState(e); + } } @Override public boolean isSearchable(int column) throws SQLException { - return true; + return true; // all columns are considered as searchable } @Override @@ -72,7 +88,11 @@ public boolean isSigned(int column) throws SQLException { @Override public int getColumnDisplaySize(int column) throws SQLException { - return 80; + try { + return getColumn(column).getColumnName().length(); + } catch (Exception e) { + throw ExceptionUtils.toSqlState(e); + } } @Override @@ -118,16 +138,12 @@ public int getScale(int column) throws SQLException { @Override public String getTableName(int column) throws SQLException { - try { - return resultSet.getSchema().getTableName(); - } catch (Exception e) { - throw ExceptionUtils.toSqlState(e); - } + return tableName; } @Override public String getCatalogName(int column) throws SQLException { - return ""; + return catalog; } @Override @@ -165,6 +181,10 @@ public boolean isDefinitelyWritable(int column) throws SQLException { @Override public String getColumnClassName(int column) throws SQLException { - return null; + try { + return typeClassMap.getOrDefault(getColumn(column).getDataType(), Object.class).getName(); + } catch (Exception e) { + throw ExceptionUtils.toSqlState(e); + } } } diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/PreparedStatementTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/PreparedStatementTest.java index 141244045..27535f0c8 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/PreparedStatementTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/PreparedStatementTest.java @@ -1,24 +1,19 @@ package com.clickhouse.jdbc; +import com.clickhouse.data.ClickHouseDataType; +import com.clickhouse.jdbc.internal.JdbcUtils; import org.apache.commons.lang3.RandomStringUtils; +import org.testng.Assert; +import org.testng.annotations.DataProvider; import org.testng.annotations.Ignore; import org.testng.annotations.Test; -import java.sql.Array; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.Statement; -import java.sql.Types; +import java.sql.*; import java.util.Arrays; import java.util.GregorianCalendar; import java.util.TimeZone; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; +import static org.testng.Assert.*; public class PreparedStatementTest extends JdbcIntegrationTest { @@ -319,6 +314,33 @@ void testInsert() throws Exception { } } + @Test(dataProvider = "testGetMetadataDataProvider") + void testGetMetadata(String sql) throws Exception { + String tableName = "test_get_metadata"; + runQuery("CREATE TABLE " + tableName + " ( a1 String, b2 Float, b3 Float ) Engine=MergeTree ORDER BY ()"); + + try (Connection conn = getJdbcConnection(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + ResultSetMetaData metadataRs = stmt.getMetaData(); + assertNotNull(metadataRs); + + Assert.assertEquals(metadataRs.getColumnCount(), 3); + Assert.assertEquals(metadataRs.getColumnName(1), "a1"); + Assert.assertEquals(metadataRs.getColumnType(1), Types.VARCHAR); + Assert.assertEquals(metadataRs.getColumnName(2), "b2"); + Assert.assertEquals(metadataRs.getColumnType(2), Types.FLOAT); + Assert.assertEquals(metadataRs.getColumnName(3), "b3"); + Assert.assertEquals(metadataRs.getColumnType(3), Types.FLOAT); + } + } + + @DataProvider(name = "testGetMetadataDataProvider") + static Object[][] testGetMetadataDataProvider() { + return new Object[][] { + {"INSERT INTO `%s` VALUES (?, ?, ?)"} + }; + } + @Test(groups = { "integration" }) void testMetabaseBug01() throws Exception { try (Connection conn = getJdbcConnection()) { diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/DatabaseMetaDataTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/DatabaseMetaDataTest.java index fcd955c39..066792cd8 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/DatabaseMetaDataTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/DatabaseMetaDataTest.java @@ -1,7 +1,6 @@ package com.clickhouse.jdbc.metadata; import com.clickhouse.client.ClickHouseServerForTest; -import com.clickhouse.client.api.command.CommandResponse; import com.clickhouse.data.ClickHouseDataType; import com.clickhouse.data.ClickHouseVersion; import com.clickhouse.jdbc.JdbcIntegrationTest; @@ -9,7 +8,6 @@ import com.clickhouse.jdbc.internal.DriverProperties; import com.clickhouse.jdbc.internal.JdbcUtils; import org.testng.Assert; -import org.testng.annotations.Ignore; import org.testng.annotations.Test; import java.sql.Connection; @@ -19,10 +17,8 @@ import java.sql.DatabaseMetaData; import java.util.Arrays; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Properties; -import java.util.Set; import static org.testng.Assert.*; @@ -173,7 +169,7 @@ public void testGetCatalogs() throws Exception { DatabaseMetaData dbmd = conn.getMetaData(); ResultSet rs = dbmd.getCatalogs(); assertFalse(rs.next()); - ResultSetMetaDataTest.assertColumnNames(rs, "TABLE_CAT"); + ResultSetMetaDataImplTest.assertColumnNames(rs, "TABLE_CAT"); } } diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/ParameterMetaDataImplTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/ParameterMetaDataImplTest.java new file mode 100644 index 000000000..5ce68b6d8 --- /dev/null +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/ParameterMetaDataImplTest.java @@ -0,0 +1,84 @@ +package com.clickhouse.jdbc.metadata; + +import org.testng.annotations.Test; + +import java.sql.SQLException; +import java.sql.Types; + +import static org.testng.Assert.*; + + +public class ParameterMetaDataImplTest { + @Test(groups = {"integration"}) + public void testGetParameterCount() throws SQLException { + ParameterMetaDataImpl metaData = new ParameterMetaDataImpl(0); + assertEquals(metaData.getParameterCount(), 0); + + metaData = new ParameterMetaDataImpl(1); + assertEquals(metaData.getParameterCount(), 1); + } + + @Test(groups = {"integration"}) + public void testIsNullable() throws SQLException { + ParameterMetaDataImpl metaData = new ParameterMetaDataImpl(1); + assertEquals(metaData.isNullable(1), ParameterMetaDataImpl.parameterNullableUnknown); + + assertThrows(() -> metaData.isNullable(0)); + assertThrows(() -> metaData.isNullable(2)); + } + + @Test(groups = {"integration"}) + public void testIsSigned() throws SQLException { + ParameterMetaDataImpl metaData = new ParameterMetaDataImpl(1); + assertFalse(metaData.isSigned(1)); + + assertThrows(() -> metaData.isSigned(0)); + assertThrows(() -> metaData.isSigned(2)); + } + + @Test(groups = {"integration"}) + public void testGetPrecisionAndScale() throws SQLException { + ParameterMetaDataImpl metaData = new ParameterMetaDataImpl(1); + assertEquals(metaData.getPrecision(1), 0); + assertEquals(metaData.getScale(1), 0); + + assertThrows(() -> metaData.getPrecision(0)); + assertThrows(() -> metaData.getPrecision(2)); + } + + @Test(groups = {"integration"}) + public void testGetParameterType() throws SQLException { + ParameterMetaDataImpl metaData = new ParameterMetaDataImpl(1); + assertEquals(metaData.getParameterType(1), Types.OTHER); + + assertThrows(() -> metaData.getParameterType(0)); + assertThrows(() -> metaData.getParameterType(2)); + } + + @Test(groups = {"integration"}) + public void testGetParameterTypeName() throws SQLException { + ParameterMetaDataImpl metaData = new ParameterMetaDataImpl(1); + assertEquals(metaData.getParameterTypeName(1), "UNKNOWN"); + + assertThrows(() -> metaData.getParameterTypeName(0)); + assertThrows(() -> metaData.getParameterTypeName(2)); + } + + @Test(groups = {"integration"}) + public void testGetParameterClassName() throws SQLException { + ParameterMetaDataImpl metaData = new ParameterMetaDataImpl(1); + assertEquals(metaData.getParameterClassName(1), Object.class.getName()); + + assertThrows(() -> metaData.getParameterClassName(0)); + assertThrows(() -> metaData.getParameterClassName(2)); + } + + @Test(groups = {"integration"}) + public void testGetParameterMode() throws SQLException { + ParameterMetaDataImpl metaData = new ParameterMetaDataImpl(1); + assertEquals(metaData.getParameterMode(1), ParameterMetaDataImpl.parameterModeIn); + + assertThrows(() -> metaData.getParameterMode(0)); + assertThrows(() -> metaData.getParameterMode(2)); + } +} diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/ParameterMetaDataTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/ParameterMetaDataTest.java deleted file mode 100644 index 0420650b6..000000000 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/ParameterMetaDataTest.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.clickhouse.jdbc.metadata; - -import com.clickhouse.data.ClickHouseColumn; -import com.clickhouse.data.ClickHouseDataType; -import com.clickhouse.jdbc.JdbcIntegrationTest; -import org.testng.annotations.Test; - -import java.sql.SQLException; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; - - -public class ParameterMetaDataTest extends JdbcIntegrationTest { - @Test(groups = { "integration" }) - public void testGetParameterCount() throws SQLException { - ParameterMetaData metaData = new ParameterMetaData(Collections.emptyList()); - assertEquals(metaData.getParameterCount(), 0); - - metaData = new ParameterMetaData(List.of(ClickHouseColumn.of("param1", ClickHouseDataType.Int32, false))); - assertEquals(metaData.getParameterCount(), 1); - } - - @Test(groups = { "integration" }) - public void testIsNullable() throws SQLException { - ClickHouseColumn column = ClickHouseColumn.of("param1", ClickHouseDataType.Int32, true); - ParameterMetaData metaData = new ParameterMetaData(Collections.singletonList(column)); - assertEquals(metaData.isNullable(1), ParameterMetaData.parameterNullable); - } - - @Test(groups = { "integration" }) - public void testIsSigned() throws SQLException { - ClickHouseColumn column = ClickHouseColumn.of("param1", ClickHouseDataType.Int32, false); - ParameterMetaData metaData = new ParameterMetaData(Collections.singletonList(column)); - assertTrue(metaData.isSigned(1)); - - column = ClickHouseColumn.of("param2", ClickHouseDataType.UInt32, false); - metaData = new ParameterMetaData(Collections.singletonList(column)); - assertFalse(metaData.isSigned(1)); - } - - @Test(groups = { "integration" }) - public void testGetPrecisionAndScale() throws SQLException { - ClickHouseColumn column = ClickHouseColumn.of("param1", ClickHouseDataType.Int32, false, 10, 5); - ParameterMetaData metaData = new ParameterMetaData(Collections.singletonList(column)); - assertEquals(metaData.getPrecision(1), 10); - assertEquals(metaData.getScale(1), 5); - } - - @Test(groups = { "integration" }) - public void testGetParameterType() throws SQLException { - ClickHouseColumn column = ClickHouseColumn.of("param1", ClickHouseDataType.Int32, false); - ParameterMetaData metaData = new ParameterMetaData(Collections.singletonList(column)); - assertEquals(metaData.getParameterType(1), java.sql.Types.INTEGER); - } - - @Test(groups = { "integration" }) - public void testGetParameterTypeName() throws SQLException { - ClickHouseColumn column = ClickHouseColumn.of("param1", ClickHouseDataType.Int32, false); - ParameterMetaData metaData = new ParameterMetaData(Collections.singletonList(column)); - assertEquals(metaData.getParameterTypeName(1), "Int32"); - } - - @Test(groups = { "integration" }) - public void testGetParameterClassName() throws SQLException { - ClickHouseColumn column = ClickHouseColumn.of("param1", ClickHouseDataType.Int32, false); - ParameterMetaData metaData = new ParameterMetaData(Collections.singletonList(column)); - assertEquals(metaData.getParameterClassName(1), "java.lang.Integer"); - } - - @Test(groups = { "integration" }) - public void testGetParameterMode() throws SQLException { - ClickHouseColumn column = ClickHouseColumn.of("param1", ClickHouseDataType.Int32, false); - ParameterMetaData metaData = new ParameterMetaData(Collections.singletonList(column)); - assertEquals(metaData.getParameterMode(1), ParameterMetaData.parameterModeIn); - } -} diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/ResultSetMetaDataTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/ResultSetMetaDataImplTest.java similarity index 98% rename from jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/ResultSetMetaDataTest.java rename to jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/ResultSetMetaDataImplTest.java index 0b6c989a6..1e5ee3c65 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/ResultSetMetaDataTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/ResultSetMetaDataImplTest.java @@ -13,7 +13,7 @@ import static org.testng.Assert.assertTrue; -public class ResultSetMetaDataTest extends JdbcIntegrationTest { +public class ResultSetMetaDataImplTest extends JdbcIntegrationTest { @Test(groups = { "integration" }) public void testGetColumnCount() throws Exception { try (Connection conn = getJdbcConnection()) { From eb0762f581a64db9f70b9cb4ecaee1ab295e4bd0 Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Mon, 14 Apr 2025 09:36:18 -0700 Subject: [PATCH 02/10] Reworked some schema staff --- .../client/api/metadata/TableSchema.java | 19 ++-- .../jdbc/PreparedStatementImpl.java | 20 ++-- .../com/clickhouse/jdbc/StatementImpl.java | 98 +++++++++++++++---- 3 files changed, 105 insertions(+), 32 deletions(-) diff --git a/client-v2/src/main/java/com/clickhouse/client/api/metadata/TableSchema.java b/client-v2/src/main/java/com/clickhouse/client/api/metadata/TableSchema.java index f902ef521..c46ab8c7a 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/metadata/TableSchema.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/metadata/TableSchema.java @@ -1,15 +1,11 @@ package com.clickhouse.client.api.metadata; import com.clickhouse.data.ClickHouseColumn; +import com.clickhouse.data.ClickHouseDataType; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; public class TableSchema { @@ -129,5 +125,16 @@ public String toString() { ", hasDefaults=" + hasDefaults + '}'; } + + public static final Collection SIMPLE_SHOW_STMT_SCHEMA = + Collections.unmodifiableCollection(Arrays.asList(ClickHouseColumn.of("name", ClickHouseDataType.String.name()))); + + public static final Collection SHOW_TABLES_SCHEMA = SIMPLE_SHOW_STMT_SCHEMA; + public static final Collection SHOW_DATABASES_SCHEMA = SIMPLE_SHOW_STMT_SCHEMA; + public static final Collection SHOW_DICTIONARIES_SCHEMA = SIMPLE_SHOW_STMT_SCHEMA; + public static final Collection SHOW_USERS_SCHEMA = SIMPLE_SHOW_STMT_SCHEMA; + public static final Collection SHOW_ROLES_SCHEMA = SIMPLE_SHOW_STMT_SCHEMA; + public static final Collection SHOW_GRANTS_SCHEMA = Collections.unmodifiableCollection(Arrays.asList(ClickHouseColumn.of("GRANTS", ClickHouseDataType.String.name()))); + } diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java index 1a5321c3f..ef21f4ee8 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java @@ -62,6 +62,8 @@ public class PreparedStatementImpl extends StatementImpl implements PreparedStat Object [] parameters; String insertIntoSQL; + ShowStatementType showStatementType; + StatementType statementType; private final ParameterMetaData parameterMetaData; @@ -73,11 +75,17 @@ public PreparedStatementImpl(ConnectionImpl connection, String sql) throws SQLEx this.originalSql = sql.trim(); //Split the sql string into an array of strings around question mark tokens this.sqlSegments = originalSql.split("\\?"); - this.statementType = parseStatementType(originalSql); - - if (statementType == StatementType.INSERT) { - insertIntoSQL = originalSql.substring(0, originalSql.indexOf("VALUES") + 6); - valueSegments = originalSql.substring(originalSql.indexOf("VALUES") + 6).split("\\?"); + Object[] parseResult = parseStatement(originalSql); + this.statementType = (StatementType) parseResult[0]; + + switch (statementType) { + case INSERT: + insertIntoSQL = originalSql.substring(0, originalSql.indexOf("VALUES") + 6); + valueSegments = originalSql.substring(originalSql.indexOf("VALUES") + 6).split("\\?"); + break; + case SHOW: + showStatementType = (ShowStatementType) parseResult[1]; + break; } //Create an array of objects to store the parameters @@ -322,7 +330,7 @@ public ResultSetMetaData getMetaData() throws SQLException { // fallback to empty until } } else if (statementType == StatementType.SHOW) { - // predefined + // predefined (it can be different for different objects) } else if (statementType == StatementType.DESCRIBE) { // predefined } else { diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java index 702bdd6b4..9083a2d5a 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java @@ -60,14 +60,31 @@ protected enum StatementType { SELECT, INSERT, DELETE, UPDATE, CREATE, DROP, ALTER, TRUNCATE, USE, SHOW, DESCRIBE, EXPLAIN, SET, KILL, OTHER, INSERT_INTO_SELECT } + protected enum ShowStatementType { + TABLES, USERS, PROCESSLIST + } + protected static StatementType parseStatementType(String sql) { + return (StatementType) parseStatement(sql)[0]; + } + + private static final Object[] OTHER_STMT_TYPE_RESULT = new Object[] {StatementType.OTHER}; + + /** + * Returns non-empty array with at list StatementType on position 0. + * If Show statement - returns rest token from the statement. + * + * @param sql - raw SQL statement + * @return Object[] - parse result + */ + public static Object[] parseStatement(String sql) { if (sql == null) { - return StatementType.OTHER; + return OTHER_STMT_TYPE_RESULT; } String trimmedSql = sql.trim(); if (trimmedSql.isEmpty()) { - return StatementType.OTHER; + return OTHER_STMT_TYPE_RESULT; } trimmedSql = trimmedSql.replaceAll("/\\*.*?\\*/", "").trim(); // remove comments @@ -81,34 +98,75 @@ protected static StatementType parseStatementType(String sql) { continue; } + StatementType statementType = null; + Object[] parseResult = null; switch (tokens[0].toUpperCase()) { - case "SELECT": return StatementType.SELECT; - case "WITH": return StatementType.SELECT; + case "SELECT": + statementType = StatementType.SELECT; + break; + case "WITH": + statementType = StatementType.SELECT; + break; case "INSERT": + statementType = StatementType.INSERT; for (String token : tokens) { if (token.equalsIgnoreCase("SELECT")) { - return StatementType.INSERT_INTO_SELECT; + statementType = StatementType.INSERT_INTO_SELECT; + } + } + break; + case "DELETE": + statementType = StatementType.DELETE; + break; + case "UPDATE": + statementType = StatementType.UPDATE; + break; + case "CREATE": + statementType = StatementType.CREATE; + break; + case "DROP": + statementType = StatementType.DROP; + break; + case "ALTER": + statementType = StatementType.ALTER; + break; + case "TRUNCATE": + statementType = StatementType.TRUNCATE; + break; + case "USE": + statementType = StatementType.USE; + break; + case "SHOW": + statementType = StatementType.SHOW; + ShowStatementType showStatementType = null; + for (String token : tokens) { + if (!token.isEmpty()) { + showStatementType = ShowStatementType.valueOf(token.toUpperCase()); + break; } } - return StatementType.INSERT; - case "DELETE": return StatementType.DELETE; - case "UPDATE": return StatementType.UPDATE; - case "CREATE": return StatementType.CREATE; - case "DROP": return StatementType.DROP; - case "ALTER": return StatementType.ALTER; - case "TRUNCATE": return StatementType.TRUNCATE; - case "USE": return StatementType.USE; - case "SHOW": return StatementType.SHOW; - case "DESCRIBE": return StatementType.DESCRIBE; - case "EXPLAIN": return StatementType.EXPLAIN; - case "SET": return StatementType.SET; - case "KILL": return StatementType.KILL; - default: return StatementType.OTHER; + parseResult = new Object[] {statementType, showStatementType}; + break; + case "DESCRIBE": + statementType = StatementType.DESCRIBE; + break; + case "EXPLAIN": + statementType = StatementType.EXPLAIN; + break; + case "SET": + statementType = StatementType.SET; + break; + case "KILL": + statementType = StatementType.KILL; + break; + default: + parseResult = OTHER_STMT_TYPE_RESULT; } + return parseResult == null ? new Object[] {statementType} : parseResult; } } - return StatementType.OTHER; + return OTHER_STMT_TYPE_RESULT; } protected static String parseTableName(String sql) { From 4a93d5df1a61b30eaf608461501ac34227a0546f Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Wed, 16 Apr 2025 11:21:30 -0700 Subject: [PATCH 03/10] Implemented getting prepared statement metadata --- .../jdbc/PreparedStatementImpl.java | 34 ++++++---------- .../com/clickhouse/jdbc/ResultSetImpl.java | 4 +- .../com/clickhouse/jdbc/StatementImpl.java | 39 ++++-------------- .../jdbc/metadata/ResultSetMetaDataImpl.java | 5 ++- .../jdbc/PreparedStatementTest.java | 40 ++++++++++++++----- 5 files changed, 55 insertions(+), 67 deletions(-) diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java index b25110dc2..62eb5e667 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java @@ -1,7 +1,5 @@ package com.clickhouse.jdbc; -import com.clickhouse.client.api.metadata.TableSchema; -import com.clickhouse.data.ClickHouseColumn; import com.clickhouse.client.api.metadata.TableSchema; import com.clickhouse.data.Tuple; import com.clickhouse.jdbc.internal.ExceptionUtils; @@ -32,7 +30,6 @@ import java.sql.SQLFeatureNotSupportedException; import java.sql.SQLType; import java.sql.SQLXML; -import java.sql.Statement; import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; @@ -49,6 +46,7 @@ import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -68,12 +66,8 @@ public class PreparedStatementImpl extends StatementImpl implements PreparedStat String [] valueSegments; Object [] parameters; String insertIntoSQL; - - ShowStatementType showStatementType; - StatementType statementType; - private final ParameterMetaData parameterMetaData; private ResultSetMetaData resultSetMetaData = null; @@ -83,22 +77,19 @@ public PreparedStatementImpl(ConnectionImpl connection, String sql) throws SQLEx this.originalSql = sql.trim(); //Split the sql string into an array of strings around question mark tokens this.sqlSegments = splitStatement(originalSql); - Object[] parseResult = parseStatement(originalSql); - this.statementType = (StatementType) parseResult[0]; + this.statementType = parseStatementType(originalSql); switch (statementType) { case INSERT: insertIntoSQL = originalSql.substring(0, originalSql.indexOf("VALUES") + 6); valueSegments = originalSql.substring(originalSql.indexOf("VALUES") + 6).split("\\?"); break; - case SHOW: - showStatementType = (ShowStatementType) parseResult[1]; - break; } //Create an array of objects to store the parameters this.parameters = new Object[sqlSegments.length - 1]; this.defaultCalendar = connection.defaultCalendar; + this.parameterMetaData = new ParameterMetaDataImpl(this.parameters.length); } private String compileSql(String [] segments) { @@ -328,18 +319,19 @@ public ResultSetMetaData getMetaData() throws SQLException { String sql = JdbcUtils.replaceQuestionMarks(originalSql, JdbcUtils.NULL); TableSchema tSchema = connection.getClient().getTableSchemaFromQuery(sql); resultSetMetaData = new ResultSetMetaDataImpl(tSchema.getColumns(), - tSchema.getDatabaseName(), "", tSchema.getTableName(), Collections.emptyMap()); + connection.getSchema(), connection.getCatalog(), + tSchema.getTableName(), Collections.emptyMap()); } catch (Exception e) { - // fallback to empty until + LOG.warn("Failed to get schema for statement '{}'", originalSql); } - } else if (statementType == StatementType.SHOW) { - // predefined (it can be different for different objects) - } else if (statementType == StatementType.DESCRIBE) { - // predefined - } else { - // fallback to empty } - } else if (resultSetMetaData == null) { + + if (resultSetMetaData == null) { + resultSetMetaData = new ResultSetMetaDataImpl(Collections.emptyList(), + connection.getSchema(), connection.getCatalog(), + "", Collections.emptyMap()); + } + } else if (currentResultSet != null) { resultSetMetaData = currentResultSet.getMetaData(); } diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java index 5ad4d801a..725281627 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java @@ -39,8 +39,10 @@ public ResultSetImpl(StatementImpl parentStatement, QueryResponse response, Clic this.response = response; this.reader = reader; TableSchema tableMetadata = reader.getSchema(); + + // Result set contains columns from one database (there is a special table engine 'Merge' to do cross DB queries) this.metaData = new ResultSetMetaDataImpl(tableMetadata - .getColumns(), tableMetadata.getDatabaseName(), "", tableMetadata.getTableName(), + .getColumns(), response.getSettings().getDatabase(), "", tableMetadata.getTableName(), Collections.emptyMap()); this.closed = false; this.wasNull = false; diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java index 3cdb8d94f..d583cc194 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java @@ -19,7 +19,6 @@ import java.sql.SQLWarning; import java.sql.Statement; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.UUID; @@ -60,31 +59,14 @@ protected enum StatementType { SELECT, INSERT, DELETE, UPDATE, CREATE, DROP, ALTER, TRUNCATE, USE, SHOW, DESCRIBE, EXPLAIN, SET, KILL, OTHER, INSERT_INTO_SELECT } - protected enum ShowStatementType { - TABLES, USERS, PROCESSLIST - } - - protected static StatementType parseStatementType(String sql) { - return (StatementType) parseStatement(sql)[0]; - } - - private static final Object[] OTHER_STMT_TYPE_RESULT = new Object[] {StatementType.OTHER}; - - /** - * Returns non-empty array with at list StatementType on position 0. - * If Show statement - returns rest token from the statement. - * - * @param sql - raw SQL statement - * @return Object[] - parse result - */ - public static Object[] parseStatement(String sql) { + public static StatementType parseStatementType(String sql) { if (sql == null) { - return OTHER_STMT_TYPE_RESULT; + return StatementType.OTHER; } String trimmedSql = sql.trim(); if (trimmedSql.isEmpty()) { - return OTHER_STMT_TYPE_RESULT; + return StatementType.OTHER; } trimmedSql = BLOCK_COMMENT.matcher(trimmedSql).replaceAll("").trim(); // remove comments @@ -138,14 +120,6 @@ public static Object[] parseStatement(String sql) { break; case "SHOW": statementType = StatementType.SHOW; - ShowStatementType showStatementType = null; - for (String token : tokens) { - if (!token.isEmpty()) { - showStatementType = ShowStatementType.valueOf(token.toUpperCase()); - break; - } - } - parseResult = new Object[] {statementType, showStatementType}; break; case "DESCRIBE": statementType = StatementType.DESCRIBE; @@ -160,13 +134,13 @@ public static Object[] parseStatement(String sql) { statementType = StatementType.KILL; break; default: - parseResult = OTHER_STMT_TYPE_RESULT; + statementType = StatementType.OTHER; } - return parseResult == null ? new Object[] {statementType} : parseResult; + return statementType; } } - return OTHER_STMT_TYPE_RESULT; + return StatementType.OTHER; } protected static String parseTableName(String sql) { @@ -242,6 +216,7 @@ public ResultSetImpl executeQuery(String sql, QuerySettings settings) throws SQL mergedSettings.setQueryId(lastQueryId); } LOG.debug("Query ID: {}", lastQueryId); + mergedSettings.setDatabase(connection.getSchema()); try { lastSql = parseJdbcEscapeSyntax(sql); diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/ResultSetMetaDataImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/ResultSetMetaDataImpl.java index 8dee6717a..6490f436e 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/ResultSetMetaDataImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/ResultSetMetaDataImpl.java @@ -22,7 +22,8 @@ public class ResultSetMetaDataImpl implements java.sql.ResultSetMetaData, JdbcV2 private final Map> typeClassMap; - public ResultSetMetaDataImpl(List columns, String schema, String catalog, String tableName, Map> typeClassMap) { + public ResultSetMetaDataImpl(List columns, String schema, String catalog, String tableName, + Map> typeClassMap) { this.columns = columns; this.schema = schema; this.catalog = catalog; @@ -115,7 +116,7 @@ public String getColumnName(int column) throws SQLException { @Override public String getSchemaName(int column) throws SQLException { - return ""; + return schema; } @Override diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/PreparedStatementTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/PreparedStatementTest.java index 0603f3b7c..fbf670138 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/PreparedStatementTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/PreparedStatementTest.java @@ -21,6 +21,7 @@ import java.util.TimeZone; import static org.testng.Assert.*; +import static org.testng.Assert.assertEquals; public class PreparedStatementTest extends JdbcIntegrationTest { @@ -322,29 +323,46 @@ void testInsert() throws Exception { } @Test(dataProvider = "testGetMetadataDataProvider") - void testGetMetadata(String sql) throws Exception { + void testGetMetadata(String sql, int colCountBeforeExecution, Object[] values, + int colCountAfterExecution) throws Exception { String tableName = "test_get_metadata"; - runQuery("CREATE TABLE " + tableName + " ( a1 String, b2 Float, b3 Float ) Engine=MergeTree ORDER BY ()"); + runQuery("CREATE TABLE IF NOT EXISTS " + tableName + " ( a1 String, b2 Float, b3 Float ) Engine=MergeTree ORDER BY ()"); try (Connection conn = getJdbcConnection(); - PreparedStatement stmt = conn.prepareStatement(sql)) { + PreparedStatement stmt = conn.prepareStatement(String.format(sql, tableName))) { ResultSetMetaData metadataRs = stmt.getMetaData(); assertNotNull(metadataRs); + Assert.assertEquals(metadataRs.getColumnCount(), colCountBeforeExecution); - Assert.assertEquals(metadataRs.getColumnCount(), 3); - Assert.assertEquals(metadataRs.getColumnName(1), "a1"); - Assert.assertEquals(metadataRs.getColumnType(1), Types.VARCHAR); - Assert.assertEquals(metadataRs.getColumnName(2), "b2"); - Assert.assertEquals(metadataRs.getColumnType(2), Types.FLOAT); - Assert.assertEquals(metadataRs.getColumnName(3), "b3"); - Assert.assertEquals(metadataRs.getColumnType(3), Types.FLOAT); + for (int i = 1; i <= metadataRs.getColumnCount(); i++) { + System.out.println("label=" + metadataRs.getColumnName(i) + " type=" + metadataRs.getColumnType(i)); + assertEquals(metadataRs.getSchemaName(i), stmt.getConnection().getSchema()); + } + + if (values != null) { + for (int i = 0; i < values.length; i++) { + stmt.setObject(i + 1, values[i]); + } + } + + stmt.execute(); + metadataRs = stmt.getMetaData(); + + assertNotNull(metadataRs); + assertEquals(metadataRs.getColumnCount(), colCountAfterExecution); + for (int i = 1; i <= metadataRs.getColumnCount(); i++) { + System.out.println("label=" + metadataRs.getColumnName(i) + " type=" + metadataRs.getColumnType(i)); + assertEquals(metadataRs.getSchemaName(i), stmt.getConnection().getSchema()); + } } } @DataProvider(name = "testGetMetadataDataProvider") static Object[][] testGetMetadataDataProvider() { return new Object[][] { - {"INSERT INTO `%s` VALUES (?, ?, ?)"} + {"INSERT INTO `%s` VALUES (?, ?, ?)", 0, new Object[]{"test", 0.3, 0.4}, 0}, + {"SELECT * FROM `%s`", 3, null, 3}, + {"SHOW TABLES", 0, null, 1} }; } From 6548efb01e6a11a6a9eaa0f46246785c9cd0f708 Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Wed, 16 Apr 2025 13:52:18 -0700 Subject: [PATCH 04/10] fixed tests and removed unused code --- .../client/api/metadata/TableSchema.java | 16 +---- .../com/clickhouse/jdbc/ResultSetImpl.java | 11 +-- .../com/clickhouse/jdbc/StatementImpl.java | 67 +++++-------------- .../jdbc/internal/MetadataResultSet.java | 4 ++ .../jdbc/metadata/ResultSetMetaDataImpl.java | 6 +- 5 files changed, 31 insertions(+), 73 deletions(-) diff --git a/client-v2/src/main/java/com/clickhouse/client/api/metadata/TableSchema.java b/client-v2/src/main/java/com/clickhouse/client/api/metadata/TableSchema.java index c46ab8c7a..8a1589532 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/metadata/TableSchema.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/metadata/TableSchema.java @@ -1,11 +1,12 @@ package com.clickhouse.client.api.metadata; import com.clickhouse.data.ClickHouseColumn; -import com.clickhouse.data.ClickHouseDataType; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import java.util.*; +import java.util.Collection; +import java.util.List; +import java.util.Map; public class TableSchema { @@ -125,16 +126,5 @@ public String toString() { ", hasDefaults=" + hasDefaults + '}'; } - - public static final Collection SIMPLE_SHOW_STMT_SCHEMA = - Collections.unmodifiableCollection(Arrays.asList(ClickHouseColumn.of("name", ClickHouseDataType.String.name()))); - - public static final Collection SHOW_TABLES_SCHEMA = SIMPLE_SHOW_STMT_SCHEMA; - public static final Collection SHOW_DATABASES_SCHEMA = SIMPLE_SHOW_STMT_SCHEMA; - public static final Collection SHOW_DICTIONARIES_SCHEMA = SIMPLE_SHOW_STMT_SCHEMA; - public static final Collection SHOW_USERS_SCHEMA = SIMPLE_SHOW_STMT_SCHEMA; - public static final Collection SHOW_ROLES_SCHEMA = SIMPLE_SHOW_STMT_SCHEMA; - public static final Collection SHOW_GRANTS_SCHEMA = Collections.unmodifiableCollection(Arrays.asList(ClickHouseColumn.of("GRANTS", ClickHouseDataType.String.name()))); - } diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java index 725281627..0d5023aab 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java @@ -26,7 +26,7 @@ public class ResultSetImpl implements ResultSet, JdbcV2Wrapper { private static final Logger log = LoggerFactory.getLogger(ResultSetImpl.class); - private final ResultSetMetaData metaData; + private ResultSetMetaData metaData; protected ClickHouseBinaryFormatReader reader; private QueryResponse response; private boolean closed; @@ -53,10 +53,7 @@ protected ResultSetImpl(ResultSetImpl resultSet) { this.parentStatement = resultSet.parentStatement; this.response = resultSet.response; this.reader = resultSet.reader; - TableSchema tableMetadata = resultSet.getSchema(); - this.metaData = new ResultSetMetaDataImpl(tableMetadata - .getColumns(), tableMetadata.getDatabaseName(), "", tableMetadata.getTableName(), - Collections.emptyMap()); + this.metaData = resultSet.metaData; this.closed = false; this.wasNull = false; this.defaultCalendar = parentStatement.connection.defaultCalendar; @@ -438,6 +435,10 @@ public ResultSetMetaData getMetaData() throws SQLException { return metaData; } + protected void setMetaData(ResultSetMetaDataImpl metaData) { + this.metaData = metaData; + } + @Override public Object getObject(int columnIndex) throws SQLException { return getObject(columnIndex, JdbcUtils.convertToJavaClass(getSchema().getColumnByIndex(columnIndex).getDataType())); diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java index d583cc194..6fb43b69f 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java @@ -80,63 +80,30 @@ public static StatementType parseStatementType(String sql) { continue; } - StatementType statementType = null; - Object[] parseResult = null; switch (tokens[0].toUpperCase()) { - case "SELECT": - statementType = StatementType.SELECT; - break; - case "WITH": - statementType = StatementType.SELECT; - break; + case "SELECT": return StatementType.SELECT; + case "WITH": return StatementType.SELECT; case "INSERT": - statementType = StatementType.INSERT; for (String token : tokens) { if (token.equalsIgnoreCase("SELECT")) { - statementType = StatementType.INSERT_INTO_SELECT; + return StatementType.INSERT_INTO_SELECT; } } - break; - case "DELETE": - statementType = StatementType.DELETE; - break; - case "UPDATE": - statementType = StatementType.UPDATE; - break; - case "CREATE": - statementType = StatementType.CREATE; - break; - case "DROP": - statementType = StatementType.DROP; - break; - case "ALTER": - statementType = StatementType.ALTER; - break; - case "TRUNCATE": - statementType = StatementType.TRUNCATE; - break; - case "USE": - statementType = StatementType.USE; - break; - case "SHOW": - statementType = StatementType.SHOW; - break; - case "DESCRIBE": - statementType = StatementType.DESCRIBE; - break; - case "EXPLAIN": - statementType = StatementType.EXPLAIN; - break; - case "SET": - statementType = StatementType.SET; - break; - case "KILL": - statementType = StatementType.KILL; - break; - default: - statementType = StatementType.OTHER; + return StatementType.INSERT; + case "DELETE": return StatementType.DELETE; + case "UPDATE": return StatementType.UPDATE; + case "CREATE": return StatementType.CREATE; + case "DROP": return StatementType.DROP; + case "ALTER": return StatementType.ALTER; + case "TRUNCATE": return StatementType.TRUNCATE; + case "USE": return StatementType.USE; + case "SHOW": return StatementType.SHOW; + case "DESCRIBE": return StatementType.DESCRIBE; + case "EXPLAIN": return StatementType.EXPLAIN; + case "SET": return StatementType.SET; + case "KILL": return StatementType.KILL; + default: return StatementType.OTHER; } - return statementType; } } diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/MetadataResultSet.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/MetadataResultSet.java index f3403c6a8..68f13365e 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/MetadataResultSet.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/MetadataResultSet.java @@ -3,6 +3,7 @@ import com.clickhouse.client.api.metadata.TableSchema; import com.clickhouse.data.ClickHouseColumn; import com.clickhouse.jdbc.ResultSetImpl; +import com.clickhouse.jdbc.metadata.ResultSetMetaDataImpl; import java.sql.ResultSetMetaData; import java.sql.SQLException; @@ -22,6 +23,9 @@ public class MetadataResultSet extends ResultSetImpl { public MetadataResultSet(ResultSetImpl resultSet) throws SQLException { super(resultSet); this.overridingSchemaAdaptor = new OverridingSchemaAdaptor(resultSet.getSchema()); + this.setMetaData(new ResultSetMetaDataImpl(overridingSchemaAdaptor.getColumns(), + overridingSchemaAdaptor.getDatabaseName(), "", + overridingSchemaAdaptor.getTableName(), Collections.emptyMap())); ResultSetMetaData metaData = getMetaData(); int count = metaData.getColumnCount(); cachedColumnLabels = new String[count]; diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/ResultSetMetaDataImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/ResultSetMetaDataImpl.java index 6490f436e..0de57b4b8 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/ResultSetMetaDataImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/ResultSetMetaDataImpl.java @@ -89,11 +89,7 @@ public boolean isSigned(int column) throws SQLException { @Override public int getColumnDisplaySize(int column) throws SQLException { - try { - return getColumn(column).getColumnName().length(); - } catch (Exception e) { - throw ExceptionUtils.toSqlState(e); - } + return 80; } @Override From 216b9f13af5e4d4586353a47ccaa6cb640f4abae Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Wed, 16 Apr 2025 14:27:46 -0700 Subject: [PATCH 05/10] fixed minor issues --- .../jdbc/PreparedStatementImpl.java | 8 +- .../com/clickhouse/jdbc/ResultSetImpl.java | 41 +++++-- .../jdbc/internal/ClientInfoProperties.java | 74 ++++++------ .../jdbc/internal/DriverProperties.java | 106 +++++++++--------- .../java/com/clickhouse/jdbc/types/Array.java | 1 - .../com/clickhouse/jdbc/ConnectionTest.java | 9 -- .../clickhouse/jdbc/JdbcIntegrationTest.java | 1 - .../jdbc/PreparedStatementTest.java | 10 +- .../com/clickhouse/jdbc/StatementTest.java | 7 -- .../jdbc/internal/JdbcConfigurationTest.java | 3 - .../metadata/ParameterMetaDataImplTest.java | 2 +- .../metadata/ResultSetMetaDataImplTest.java | 7 +- pom.xml | 10 ++ 13 files changed, 142 insertions(+), 137 deletions(-) diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java index 62eb5e667..ad760aa5a 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java @@ -79,11 +79,9 @@ public PreparedStatementImpl(ConnectionImpl connection, String sql) throws SQLEx this.sqlSegments = splitStatement(originalSql); this.statementType = parseStatementType(originalSql); - switch (statementType) { - case INSERT: - insertIntoSQL = originalSql.substring(0, originalSql.indexOf("VALUES") + 6); - valueSegments = originalSql.substring(originalSql.indexOf("VALUES") + 6).split("\\?"); - break; + if (this.statementType == StatementType.INSERT) { + insertIntoSQL = originalSql.substring(0, originalSql.indexOf("VALUES") + 6); + valueSegments = originalSql.substring(originalSql.indexOf("VALUES") + 6).split("\\?"); } //Create an array of objects to store the parameters diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java index 0d5023aab..ea8e2ccb8 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java @@ -1,17 +1,5 @@ package com.clickhouse.jdbc; -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.io.Reader; -import java.io.StringReader; -import java.math.BigDecimal; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.sql.*; -import java.sql.Date; -import java.time.ZonedDateTime; -import java.util.*; - import com.clickhouse.client.api.data_formats.ClickHouseBinaryFormatReader; import com.clickhouse.client.api.metadata.TableSchema; import com.clickhouse.client.api.query.QueryResponse; @@ -24,6 +12,35 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.math.BigDecimal; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Date; +import java.sql.NClob; +import java.sql.Ref; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.RowId; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.sql.SQLType; +import java.sql.SQLWarning; +import java.sql.SQLXML; +import java.sql.Statement; +import java.sql.Time; +import java.sql.Timestamp; +import java.time.ZonedDateTime; +import java.util.Calendar; +import java.util.Collections; +import java.util.List; +import java.util.Map; + public class ResultSetImpl implements ResultSet, JdbcV2Wrapper { private static final Logger log = LoggerFactory.getLogger(ResultSetImpl.class); private ResultSetMetaData metaData; diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/ClientInfoProperties.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/ClientInfoProperties.java index 3c6913b64..ada5139d6 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/ClientInfoProperties.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/ClientInfoProperties.java @@ -1,37 +1,37 @@ -package com.clickhouse.jdbc.internal; - -public enum ClientInfoProperties { - - APPLICATION_NAME("ApplicationName", 255, "", "Client application name."), - ; - - private String key; - private int maxValue; - - private String defaultValue; - - private String description; - - ClientInfoProperties(String key, int maxValue, String defaultValue, String description) { - this.key = key; - this.maxValue = maxValue; - this.defaultValue = defaultValue; - this.description = description; - } - - public String getKey() { - return key; - } - - public int getMaxValue() { - return maxValue; - } - - public String getDefaultValue() { - return defaultValue; - } - - public String getDescription() { - return description; - } -} +package com.clickhouse.jdbc.internal; + +public enum ClientInfoProperties { + + APPLICATION_NAME("ApplicationName", 255, "", "Client application name."), + ; + + private String key; + private int maxValue; + + private String defaultValue; + + private String description; + + ClientInfoProperties(String key, int maxValue, String defaultValue, String description) { + this.key = key; + this.maxValue = maxValue; + this.defaultValue = defaultValue; + this.description = description; + } + + public String getKey() { + return key; + } + + public int getMaxValue() { + return maxValue; + } + + public String getDefaultValue() { + return defaultValue; + } + + public String getDescription() { + return description; + } +} diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/DriverProperties.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/DriverProperties.java index cf7721eeb..c2d82f246 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/DriverProperties.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/DriverProperties.java @@ -1,53 +1,53 @@ -package com.clickhouse.jdbc.internal; - -import java.util.Collections; -import java.util.List; - -/** - * JDBC driver specific properties. Should not include any of ClientConfigProperties. - * Processing logic should be the follows - * 1. If property is among DriverProperties then Driver handles it specially and will not pass to a client - * 2. If property is not among DriverProperties then it is passed to a client - */ -public enum DriverProperties { - - IGNORE_UNSUPPORTED_VALUES("jdbc_ignore_unsupported_values", ""), - SCHEMA_TERM("jdbc_schema_term", ""), - /** - * Indicates if driver should create a secure connection over SSL/TLS - */ - SECURE_CONNECTION("ssl", "false"), - - /** - * query settings to be passed along with query operation. - * {@see com.clickhouse.client.api.query.QuerySettings} - */ - DEFAULT_QUERY_SETTINGS("default_query_settings", null); - private final String key; - - private final String defaultValue; - - private final List choices; - - DriverProperties(String key, String defaultValue) { - this(key, defaultValue, Collections.emptyList()); - } - - DriverProperties(String key, String defaultValue, List choices) { - this.key = key; - this.defaultValue = defaultValue; - this.choices = choices; - } - - public String getKey() { - return key; - } - - public String getDefaultValue() { - return defaultValue; - } - - public List getChoices() { - return choices; - } -} +package com.clickhouse.jdbc.internal; + +import java.util.Collections; +import java.util.List; + +/** + * JDBC driver specific properties. Should not include any of ClientConfigProperties. + * Processing logic should be the follows + * 1. If property is among DriverProperties then Driver handles it specially and will not pass to a client + * 2. If property is not among DriverProperties then it is passed to a client + */ +public enum DriverProperties { + + IGNORE_UNSUPPORTED_VALUES("jdbc_ignore_unsupported_values", ""), + SCHEMA_TERM("jdbc_schema_term", ""), + /** + * Indicates if driver should create a secure connection over SSL/TLS + */ + SECURE_CONNECTION("ssl", "false"), + + /** + * query settings to be passed along with query operation. + * {@see com.clickhouse.client.api.query.QuerySettings} + */ + DEFAULT_QUERY_SETTINGS("default_query_settings", null); + private final String key; + + private final String defaultValue; + + private final List choices; + + DriverProperties(String key, String defaultValue) { + this(key, defaultValue, Collections.emptyList()); + } + + DriverProperties(String key, String defaultValue, List choices) { + this.key = key; + this.defaultValue = defaultValue; + this.choices = choices; + } + + public String getKey() { + return key; + } + + public String getDefaultValue() { + return defaultValue; + } + + public List getChoices() { + return choices; + } +} diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/types/Array.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/types/Array.java index d2e06b2b6..e463a8fb5 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/types/Array.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/types/Array.java @@ -7,7 +7,6 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; -import java.sql.Types; import java.util.List; import java.util.Map; diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/ConnectionTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/ConnectionTest.java index 9346edcd6..d8901dd22 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/ConnectionTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/ConnectionTest.java @@ -1,15 +1,12 @@ package com.clickhouse.jdbc; -import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.sql.*; import java.util.Arrays; import java.util.Base64; -import java.util.List; import java.util.Properties; import java.util.Properties; -import java.util.concurrent.TimeUnit; import java.util.UUID; import com.clickhouse.client.ClickHouseNode; @@ -18,23 +15,17 @@ import com.clickhouse.client.api.Client; import com.clickhouse.client.api.ClientConfigProperties; import com.clickhouse.client.api.ServerException; -import com.clickhouse.client.api.enums.Protocol; import com.clickhouse.client.api.internal.ServerSettings; -import com.clickhouse.client.api.query.GenericRecord; -import com.clickhouse.client.api.query.QueryResponse; import com.clickhouse.jdbc.internal.ClientInfoProperties; import com.clickhouse.jdbc.internal.DriverProperties; import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.common.ConsoleNotifier; import com.github.tomakehurst.wiremock.core.WireMockConfiguration; -import org.apache.hc.core5.http.HttpStatus; -import com.clickhouse.jdbc.internal.JdbcUtils; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertThrows; import static org.testng.Assert.fail; diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/JdbcIntegrationTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/JdbcIntegrationTest.java index af42aada7..6b80b9d55 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/JdbcIntegrationTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/JdbcIntegrationTest.java @@ -5,7 +5,6 @@ import com.clickhouse.client.BaseIntegrationTest; import com.clickhouse.client.ClickHouseProtocol; import com.clickhouse.client.api.ClientConfigProperties; -import com.clickhouse.client.api.internal.ServerSettings; import com.clickhouse.client.api.query.GenericRecord; import com.clickhouse.logging.Logger; import com.clickhouse.logging.LoggerFactory; diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/PreparedStatementTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/PreparedStatementTest.java index fbf670138..0b0bb2418 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/PreparedStatementTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/PreparedStatementTest.java @@ -1,7 +1,5 @@ package com.clickhouse.jdbc; -import com.clickhouse.data.ClickHouseDataType; -import com.clickhouse.jdbc.internal.JdbcUtils; import org.apache.commons.lang3.RandomStringUtils; import org.testng.Assert; import org.testng.annotations.DataProvider; @@ -10,7 +8,6 @@ import java.sql.Array; import java.sql.Connection; -import java.sql.JDBCType; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; @@ -20,8 +17,13 @@ import java.util.GregorianCalendar; import java.util.TimeZone; -import static org.testng.Assert.*; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; + + public class PreparedStatementTest extends JdbcIntegrationTest { diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java index 8b5cca1f9..7396f037c 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java @@ -1,9 +1,7 @@ package com.clickhouse.jdbc; import com.clickhouse.client.api.ClientConfigProperties; -import com.clickhouse.client.api.ClientException; import com.clickhouse.client.api.query.GenericRecord; -import com.clickhouse.client.api.query.QuerySettings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.Assert; @@ -21,13 +19,9 @@ import java.sql.Statement; import java.time.LocalDate; import java.util.Arrays; -import java.util.GregorianCalendar; import java.util.List; import java.util.Properties; -import java.util.TimeZone; -import java.util.UUID; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicReference; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; @@ -35,7 +29,6 @@ import static org.testng.Assert.assertNull; import static org.testng.Assert.assertThrows; import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; public class StatementTest extends JdbcIntegrationTest { diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/internal/JdbcConfigurationTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/internal/JdbcConfigurationTest.java index 125ce0f85..771fed914 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/internal/JdbcConfigurationTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/internal/JdbcConfigurationTest.java @@ -7,11 +7,8 @@ import java.sql.DriverPropertyInfo; import java.util.Arrays; -import java.util.Collections; import java.util.Map; import java.util.Properties; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import java.util.stream.Collectors; import static org.testng.Assert.assertEquals; diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/ParameterMetaDataImplTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/ParameterMetaDataImplTest.java index 5ce68b6d8..d039c8b76 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/ParameterMetaDataImplTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/ParameterMetaDataImplTest.java @@ -10,7 +10,7 @@ public class ParameterMetaDataImplTest { @Test(groups = {"integration"}) - public void testGetParameterCount() throws SQLException { + public void testGetParameterCount() { ParameterMetaDataImpl metaData = new ParameterMetaDataImpl(0); assertEquals(metaData.getParameterCount(), 0); diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/ResultSetMetaDataImplTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/ResultSetMetaDataImplTest.java index 1e5ee3c65..04bd8477b 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/ResultSetMetaDataImplTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/ResultSetMetaDataImplTest.java @@ -10,7 +10,6 @@ import java.sql.Types; import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; public class ResultSetMetaDataImplTest extends JdbcIntegrationTest { @@ -20,7 +19,7 @@ public void testGetColumnCount() throws Exception { try (Statement stmt = conn.createStatement()) { ResultSet rs = stmt.executeQuery("SELECT 1 AS a, 2 AS b, 3 AS c"); ResultSetMetaData rsmd = rs.getMetaData(); - assertEquals(3, rsmd.getColumnCount()); + assertEquals(rsmd.getColumnCount(), 3); } } } @@ -31,7 +30,7 @@ public void testGetColumnLabel() throws Exception { try (Statement stmt = conn.createStatement()) { ResultSet rs = stmt.executeQuery("SELECT 1 AS a"); ResultSetMetaData rsmd = rs.getMetaData(); - assertEquals("a", rsmd.getColumnLabel(1)); + assertEquals(rsmd.getColumnLabel(1), "a"); } } } @@ -42,7 +41,7 @@ public void testGetColumnName() throws Exception { try (Statement stmt = conn.createStatement()) { ResultSet rs = stmt.executeQuery("SELECT 1 AS a"); ResultSetMetaData rsmd = rs.getMetaData(); - assertEquals("a", rsmd.getColumnName(1)); + assertEquals(rsmd.getColumnName(1), "a"); } } } diff --git a/pom.xml b/pom.xml index 7fae19284..caa32f3bb 100644 --- a/pom.xml +++ b/pom.xml @@ -393,6 +393,16 @@ + + com.diffplug.spotless + spotless-maven-plugin + 2.43.0 + + + + + + kr.motd.maven os-maven-plugin From c51f3674ceb25aea38e30913ee7000bc91355403 Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Wed, 16 Apr 2025 14:38:04 -0700 Subject: [PATCH 06/10] fixed javadoc --- .../java/com/clickhouse/jdbc/PreparedStatementImpl.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java index ad760aa5a..31465301a 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java @@ -381,10 +381,9 @@ public void setURL(int parameterIndex, URL x) throws SQLException { } /** - * Current JDBC driver implementation implement this functionality. - * But specification doesn't expect throwing an exception what - * for makes us return a "dummy" object. Returned metadata reflects only - * information we can guarantee like parameter count. + * Returned metadata has only minimal information about parameters. Currently only their count. + * Current implementation do not parse SQL to detect type of each parameter. + * * @see ParameterMetaDataImpl * @return {@link ParameterMetaDataImpl} * @throws SQLException if the statement is close From cd11f5214763287271d1af01e838450c67071e70 Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Wed, 16 Apr 2025 17:18:13 -0700 Subject: [PATCH 07/10] fixing coverage report --- .github/workflows/analysis.yml | 2 +- clickhouse-jdbc/pom.xml | 1 + client-v2/pom.xml | 1 + jdbc-v2/pom.xml | 2 +- .../jdbc/PreparedStatementImpl.java | 6 +++--- .../com/clickhouse/jdbc/ResultSetImpl.java | 2 +- .../clickhouse/jdbc/internal/JdbcUtils.java | 19 +++++++++++++++---- .../jdbc/internal/MetadataResultSet.java | 2 +- .../jdbc/metadata/ParameterMetaDataImpl.java | 8 ++++++-- .../metadata/ResultSetMetaDataImplTest.java | 17 +++++++++++++++++ pom.xml | 1 - 11 files changed, 47 insertions(+), 14 deletions(-) diff --git a/.github/workflows/analysis.yml b/.github/workflows/analysis.yml index 5a3909c77..cdd11fdf4 100644 --- a/.github/workflows/analysis.yml +++ b/.github/workflows/analysis.yml @@ -96,5 +96,5 @@ jobs: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} run: | mvn --batch-mode -DclickhouseVersion=$PREFERRED_LTS_VERSION \ - -Panalysis verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar + -Panalysis org.sonarsource.scanner.maven:sonar-maven-plugin:sonar verify continue-on-error: true diff --git a/clickhouse-jdbc/pom.xml b/clickhouse-jdbc/pom.xml index a2abcced9..f0fe220de 100644 --- a/clickhouse-jdbc/pom.xml +++ b/clickhouse-jdbc/pom.xml @@ -20,6 +20,7 @@ 4.1.4 JDBC 4.2 + ${project.basedir}/target/site/jacoco-aggregate/jacoco.xml clickhouse-jdbc-bin com.clickhouse.jdbc.Main diff --git a/client-v2/pom.xml b/client-v2/pom.xml index ee94812f9..8f0b5b384 100644 --- a/client-v2/pom.xml +++ b/client-v2/pom.xml @@ -19,6 +19,7 @@ 5.3.1 ${project.groupId}.shaded + ${project.basedir}/target/site/jacoco-aggregate/jacoco.xml diff --git a/jdbc-v2/pom.xml b/jdbc-v2/pom.xml index 2adaded36..12d46a941 100644 --- a/jdbc-v2/pom.xml +++ b/jdbc-v2/pom.xml @@ -20,7 +20,7 @@ 4.1.4 JDBC 4.2 - + ${project.basedir}/target/site/jacoco-aggregate/jacoco.xml ${project.groupId}.shaded diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java index 31465301a..5eaf57293 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java @@ -318,7 +318,7 @@ public ResultSetMetaData getMetaData() throws SQLException { TableSchema tSchema = connection.getClient().getTableSchemaFromQuery(sql); resultSetMetaData = new ResultSetMetaDataImpl(tSchema.getColumns(), connection.getSchema(), connection.getCatalog(), - tSchema.getTableName(), Collections.emptyMap()); + tSchema.getTableName(), JdbcUtils.DATA_TYPE_CLASS_MAP); } catch (Exception e) { LOG.warn("Failed to get schema for statement '{}'", originalSql); } @@ -327,7 +327,7 @@ public ResultSetMetaData getMetaData() throws SQLException { if (resultSetMetaData == null) { resultSetMetaData = new ResultSetMetaDataImpl(Collections.emptyList(), connection.getSchema(), connection.getCatalog(), - "", Collections.emptyMap()); + "", JdbcUtils.DATA_TYPE_CLASS_MAP); } } else if (currentResultSet != null) { resultSetMetaData = currentResultSet.getMetaData(); @@ -383,7 +383,7 @@ public void setURL(int parameterIndex, URL x) throws SQLException { /** * Returned metadata has only minimal information about parameters. Currently only their count. * Current implementation do not parse SQL to detect type of each parameter. - * + * * @see ParameterMetaDataImpl * @return {@link ParameterMetaDataImpl} * @throws SQLException if the statement is close diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java index ea8e2ccb8..535f9a495 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java @@ -60,7 +60,7 @@ public ResultSetImpl(StatementImpl parentStatement, QueryResponse response, Clic // Result set contains columns from one database (there is a special table engine 'Merge' to do cross DB queries) this.metaData = new ResultSetMetaDataImpl(tableMetadata .getColumns(), response.getSettings().getDatabase(), "", tableMetadata.getTableName(), - Collections.emptyMap()); + JdbcUtils.DATA_TYPE_CLASS_MAP); this.closed = false; this.wasNull = false; this.defaultCalendar = parentStatement.connection.defaultCalendar; diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcUtils.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcUtils.java index 99adb696d..ec1dc0c47 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcUtils.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcUtils.java @@ -3,6 +3,7 @@ import com.clickhouse.client.api.data_formats.internal.BinaryStreamReader; import com.clickhouse.data.ClickHouseDataType; import com.clickhouse.jdbc.types.Array; +import com.google.common.collect.ImmutableMap; import java.net.Inet4Address; import java.net.Inet6Address; @@ -27,7 +28,7 @@ public class JdbcUtils { //Define a map to store the mapping between ClickHouse data types and SQL data types - private static final Map CLICKHOUSE_TO_SQL_TYPE_MAP = generateTypeMap(); + public static final Map CLICKHOUSE_TO_SQL_TYPE_MAP = generateTypeMap(); public static final Map CLICKHOUSE_TYPE_NAME_TO_SQL_TYPE_MAP = Collections.unmodifiableMap(generateTypeMap().entrySet() .stream().collect( @@ -72,10 +73,10 @@ private static Map generateTypeMap() { map.put(ClickHouseDataType.LineString, JDBCType.OTHER); map.put(ClickHouseDataType.MultiPolygon, JDBCType.OTHER); map.put(ClickHouseDataType.MultiLineString, JDBCType.OTHER); - return map; + return ImmutableMap.copyOf(map); } - private static final Map> SQL_TYPE_TO_CLASS_MAP = generateClassMap(); + public static final Map> SQL_TYPE_TO_CLASS_MAP = generateClassMap(); private static Map> generateClassMap() { Map> map = new HashMap<>(); map.put(JDBCType.CHAR, String.class); @@ -112,6 +113,16 @@ private static Map> generateClassMap() { map.put(JDBCType.LONGNVARCHAR, String.class); map.put(JDBCType.NCLOB, java.sql.NClob.class); map.put(JDBCType.SQLXML, java.sql.SQLXML.class); + return ImmutableMap.copyOf(map); + } + + public static final Map> DATA_TYPE_CLASS_MAP = getDataTypeClassMap(); + private static Map> getDataTypeClassMap() { + Map> map = new HashMap<>(); + for (Map.Entry e : CLICKHOUSE_TO_SQL_TYPE_MAP.entrySet()) { + map.put(e.getKey(), SQL_TYPE_TO_CLASS_MAP.get(e.getValue())); + } + return map; } @@ -124,7 +135,7 @@ public static SQLType convertToSqlType(ClickHouseDataType clickhouseType) { } public static Class convertToJavaClass(ClickHouseDataType clickhouseType) { - return SQL_TYPE_TO_CLASS_MAP.get(convertToSqlType(clickhouseType)); + return DATA_TYPE_CLASS_MAP.get(clickhouseType); } public static List tokenizeSQL(String sql) { diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/MetadataResultSet.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/MetadataResultSet.java index 68f13365e..77ee23a4e 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/MetadataResultSet.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/MetadataResultSet.java @@ -25,7 +25,7 @@ public MetadataResultSet(ResultSetImpl resultSet) throws SQLException { this.overridingSchemaAdaptor = new OverridingSchemaAdaptor(resultSet.getSchema()); this.setMetaData(new ResultSetMetaDataImpl(overridingSchemaAdaptor.getColumns(), overridingSchemaAdaptor.getDatabaseName(), "", - overridingSchemaAdaptor.getTableName(), Collections.emptyMap())); + overridingSchemaAdaptor.getTableName(), JdbcUtils.DATA_TYPE_CLASS_MAP)); ResultSetMetaData metaData = getMetaData(); int count = metaData.getColumnCount(); cachedColumnLabels = new String[count]; diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/ParameterMetaDataImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/ParameterMetaDataImpl.java index d68ee4162..bca28e3ba 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/ParameterMetaDataImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/ParameterMetaDataImpl.java @@ -13,6 +13,10 @@ */ public class ParameterMetaDataImpl implements ParameterMetaData, JdbcV2Wrapper { + private static final int FAIL_SAFE_PRECISION = 0; + + private static final int FAIL_SAFE_SCALE = 0; + private final int paramCount; public ParameterMetaDataImpl(int paramCount) { @@ -63,7 +67,7 @@ public boolean isSigned(int param) throws SQLException { @Override public int getPrecision(int param) throws SQLException { checkParamIndex(param); - return 0; + return FAIL_SAFE_PRECISION; } /** @@ -75,7 +79,7 @@ public int getPrecision(int param) throws SQLException { @Override public int getScale(int param) throws SQLException { checkParamIndex(param); - return 0; + return FAIL_SAFE_SCALE; } /** diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/ResultSetMetaDataImplTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/ResultSetMetaDataImplTest.java index 04bd8477b..c722e85d3 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/ResultSetMetaDataImplTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/ResultSetMetaDataImplTest.java @@ -3,13 +3,18 @@ import com.clickhouse.jdbc.JdbcIntegrationTest; import org.testng.annotations.Test; +import java.math.BigInteger; import java.sql.Connection; +import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.Statement; import java.sql.Types; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertThrows; +import static org.testng.Assert.assertTrue; public class ResultSetMetaDataImplTest extends JdbcIntegrationTest { @@ -53,9 +58,21 @@ public void testGetColumnTypeIntegers() throws Exception { ResultSet rs = stmt.executeQuery("SELECT toInt8(1), toInt16(1), toInt32(1), toInt64(1) AS a"); ResultSetMetaData rsmd = rs.getMetaData(); assertEquals(rsmd.getColumnType(1), Types.TINYINT); + assertEquals(rsmd.getColumnClassName(1), Integer.class.getName()); assertEquals(rsmd.getColumnType(2), Types.SMALLINT); + assertEquals(rsmd.getColumnClassName(2), Integer.class.getName()); assertEquals(rsmd.getColumnType(3), Types.INTEGER); + assertEquals(rsmd.getColumnClassName(3), Integer.class.getName()); assertEquals(rsmd.getColumnType(4), Types.BIGINT); + assertEquals(rsmd.getColumnClassName(4), Long.class.getName()); + + for (int i = 1; i <= rsmd.getColumnCount(); i++) { + assertTrue(rsmd.isCaseSensitive(i)); + assertFalse(rsmd.isCurrency(i)); + assertEquals(rsmd.isNullable(i), ResultSetMetaData.columnNoNulls); + assertTrue(rsmd.isSearchable(i)); + assertTrue(rsmd.isSigned(i)); + } } } } diff --git a/pom.xml b/pom.xml index caa32f3bb..c2d158276 100644 --- a/pom.xml +++ b/pom.xml @@ -159,7 +159,6 @@ **/*0*.java,**/data/*Value.java,**/data/array/*Value.java,**/stream/*Stream.java jacoco reuseReports - target/site/jacoco-aggregate/jacoco.xml java ${maven.build.timestamp} From d3edaff7130691fedfa1a97671a82440ae22c99c Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Wed, 16 Apr 2025 18:47:22 -0700 Subject: [PATCH 08/10] fixing code coverage --- client-v2/pom.xml | 1 - jdbc-v2/pom.xml | 1 - pom.xml | 1 + 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/client-v2/pom.xml b/client-v2/pom.xml index 8f0b5b384..ee94812f9 100644 --- a/client-v2/pom.xml +++ b/client-v2/pom.xml @@ -19,7 +19,6 @@ 5.3.1 ${project.groupId}.shaded - ${project.basedir}/target/site/jacoco-aggregate/jacoco.xml diff --git a/jdbc-v2/pom.xml b/jdbc-v2/pom.xml index 12d46a941..307612d6a 100644 --- a/jdbc-v2/pom.xml +++ b/jdbc-v2/pom.xml @@ -20,7 +20,6 @@ 4.1.4 JDBC 4.2 - ${project.basedir}/target/site/jacoco-aggregate/jacoco.xml ${project.groupId}.shaded diff --git a/pom.xml b/pom.xml index c2d158276..8bb776012 100644 --- a/pom.xml +++ b/pom.xml @@ -159,6 +159,7 @@ **/*0*.java,**/data/*Value.java,**/data/array/*Value.java,**/stream/*Stream.java jacoco reuseReports + ${project.basedir}/**/target/site/jacoco-aggregate/jacoco.xml java ${maven.build.timestamp} From 65572ef600eecbd9ebbbe567f23cfebc6b5dec24 Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Wed, 16 Apr 2025 20:26:39 -0700 Subject: [PATCH 09/10] push build --- client-v2/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/client-v2/pom.xml b/client-v2/pom.xml index ee94812f9..fe8e10cc9 100644 --- a/client-v2/pom.xml +++ b/client-v2/pom.xml @@ -45,6 +45,7 @@ slf4j-api ${slf4j.version} + org.apache.httpcomponents.client5 httpclient5 From 382477446ac75f9d7b712bccc86d7d24e7d9445a Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Thu, 17 Apr 2025 10:46:20 -0700 Subject: [PATCH 10/10] fixed bug with replacing literal '?' with NULL --- .../jdbc/PreparedStatementImpl.java | 1 + .../clickhouse/jdbc/internal/JdbcUtils.java | 2 +- .../jdbc/PreparedStatementTest.java | 21 ++++++++++++++++--- .../jdbc/internal/JdbcUtilsTest.java | 17 +++++++++++++++ 4 files changed, 37 insertions(+), 4 deletions(-) diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java index 5eaf57293..18ec3006c 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java @@ -314,6 +314,7 @@ public ResultSetMetaData getMetaData() throws SQLException { // before execution if (statementType == StatementType.SELECT) { try { + // Replace '?' with NULL to make SQL valid for DESCRIBE String sql = JdbcUtils.replaceQuestionMarks(originalSql, JdbcUtils.NULL); TableSchema tSchema = connection.getClient().getTableSchemaFromQuery(sql); resultSetMetaData = new ResultSetMetaDataImpl(tSchema.getColumns(), diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcUtils.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcUtils.java index ec1dc0c47..754803fc1 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcUtils.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcUtils.java @@ -287,7 +287,7 @@ public static String escapeQuotes(String str) { public static final String NULL = "NULL"; - private static final Pattern REPLACE_Q_MARK_PATTERN = Pattern.compile("(\"[^\"]*\"|`[^`]*`)|(\\?)"); + private static final Pattern REPLACE_Q_MARK_PATTERN = Pattern.compile("(\"[^\"]*\"|`[^`]*`|'[^']*')|(\\?)"); public static String replaceQuestionMarks(String sql, String replacement) { Matcher matcher = REPLACE_Q_MARK_PATTERN.matcher(sql); diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/PreparedStatementTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/PreparedStatementTest.java index 0b0bb2418..59e6bb084 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/PreparedStatementTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/PreparedStatementTest.java @@ -337,7 +337,6 @@ void testGetMetadata(String sql, int colCountBeforeExecution, Object[] values, Assert.assertEquals(metadataRs.getColumnCount(), colCountBeforeExecution); for (int i = 1; i <= metadataRs.getColumnCount(); i++) { - System.out.println("label=" + metadataRs.getColumnName(i) + " type=" + metadataRs.getColumnType(i)); assertEquals(metadataRs.getSchemaName(i), stmt.getConnection().getSchema()); } @@ -353,7 +352,6 @@ void testGetMetadata(String sql, int colCountBeforeExecution, Object[] values, assertNotNull(metadataRs); assertEquals(metadataRs.getColumnCount(), colCountAfterExecution); for (int i = 1; i <= metadataRs.getColumnCount(); i++) { - System.out.println("label=" + metadataRs.getColumnName(i) + " type=" + metadataRs.getColumnType(i)); assertEquals(metadataRs.getSchemaName(i), stmt.getConnection().getSchema()); } } @@ -503,7 +501,10 @@ void testMetabaseBug01() throws Exception { void testStatementSplit() throws Exception { try (Connection conn = getJdbcConnection()) { try (Statement stmt = conn.createStatement()) { - stmt.execute("CREATE TABLE `with_complex_id` (`v?``1` Int32, \"v?\"\"2\" Int32,`v?\\`3` Int32, \"v?\\\"4\" Int32) ENGINE Memory;"); + stmt.execute("CREATE TABLE IF NOT EXISTS `with_complex_id` (`v?``1` Int32, " + + "\"v?\"\"2\" Int32,`v?\\`3` Int32, \"v?\\\"4\" Int32) ENGINE MergeTree ORDER BY ();"); + stmt.execute("CREATE TABLE IF NOT EXISTS `test_stmt_split2` (v1 Int32, v2 String) ENGINE MergeTree ORDER BY (); "); + stmt.execute("INSERT INTO `test_stmt_split2` VALUES (1, 'abc'), (2, '?'), (3, '?')"); } String insertQuery = "-- line comment1 ?\n" + "# line comment2 ?\n" @@ -536,6 +537,20 @@ void testStatementSplit() throws Exception { assertEquals(rs.getString(7), "test string3 ?\\"); } } + + try (PreparedStatement stmt = conn.prepareStatement("SELECT v1 FROM `test_stmt_split2` WHERE v1 > ? AND v2 = '?'")) { + stmt.setInt(1, 2); + try (ResultSet rs = stmt.executeQuery()) { + int count = 0; + while (rs.next()) { + count++; + assertEquals(rs.getInt(1), 3); + } + + Assert.assertEquals(count, 1); + } + } + } } } diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/internal/JdbcUtilsTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/internal/JdbcUtilsTest.java index e83e3f30c..009bff49d 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/internal/JdbcUtilsTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/internal/JdbcUtilsTest.java @@ -1,5 +1,6 @@ package com.clickhouse.jdbc.internal; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.util.List; @@ -57,4 +58,20 @@ public void testEscapeQuotes() { assertEquals(JdbcUtils.escapeQuotes(inStr[i]), outStr[i]); } } + + @Test(dataProvider = "testReplaceQuestionMark_dataProvider") + public void testReplaceQuestionMark(String sql, String result) { + assertEquals(JdbcUtils.replaceQuestionMarks(sql, "NULL"), result); + } + + @DataProvider(name = "testReplaceQuestionMark_dataProvider") + public static Object[][] testReplaceQuestionMark_dataProvider() { + return new Object[][] { + {"", ""}, + {" ", " "}, + {"SELECT * FROM t WHERE a = '?'", "SELECT * FROM t WHERE a = '?'"}, + {"SELECT `v2?` FROM t WHERE `v1?` = ?", "SELECT `v2?` FROM t WHERE `v1?` = NULL"}, + {"INSERT INTO \"t2?\" VALUES (?, ?, 'some_?', ?)", "INSERT INTO \"t2?\" VALUES (NULL, NULL, 'some_?', NULL)"} + }; + } }