From 957b48e52cb1bbc50308524a4ec66c4f6ce5add5 Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Tue, 27 May 2025 11:29:37 -0700 Subject: [PATCH 1/2] added test for WITH statement with parameters. Removed Java 9 api from usage --- clickhouse-client/pom.xml | 1 + .../com/clickhouse/jdbc/ConnectionImpl.java | 38 +++------------- .../com/clickhouse/jdbc/DataSourceImpl.java | 12 ----- .../com/clickhouse/jdbc/StatementImpl.java | 24 ---------- .../jdbc/PreparedStatementTest.java | 44 ++++++++++++++++++- pom.xml | 2 + 6 files changed, 51 insertions(+), 70 deletions(-) diff --git a/clickhouse-client/pom.xml b/clickhouse-client/pom.xml index 71d5f5bc4..6bd9316cb 100644 --- a/clickhouse-client/pom.xml +++ b/clickhouse-client/pom.xml @@ -91,6 +91,7 @@ META-INF/services/* + com/clickhouse/client/ClickHouseTestClient.class diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java index ac6cf3db8..10fafcae3 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java @@ -33,16 +33,18 @@ import java.sql.SQLWarning; import java.sql.SQLXML; import java.sql.Savepoint; -import java.sql.ShardingKey; import java.sql.Statement; import java.sql.Struct; +import java.util.Arrays; import java.util.Calendar; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.Executor; +import java.util.stream.Collectors; public class ConnectionImpl implements Connection, JdbcV2Wrapper { private static final Logger log = LoggerFactory.getLogger(ConnectionImpl.class); @@ -548,7 +550,9 @@ public Properties getClientInfo() throws SQLException { @Override public Array createArrayOf(String typeName, Object[] elements) throws SQLException { try { - return new com.clickhouse.jdbc.types.Array(List.of(elements), typeName, JdbcUtils.convertToSqlType(ClickHouseDataType.valueOf(typeName)).getVendorTypeNumber()); + List list = + (elements == null || elements.length == 0) ? Collections.emptyList() : Arrays.stream(elements, 0, elements.length).collect(Collectors.toList()); + return new com.clickhouse.jdbc.types.Array(list, typeName, JdbcUtils.convertToSqlType(ClickHouseDataType.valueOf(typeName)).getVendorTypeNumber()); } catch (Exception e) { throw new SQLException("Failed to create array", ExceptionUtils.SQL_STATE_CLIENT_ERROR, e); } @@ -601,36 +605,6 @@ public int getNetworkTimeout() throws SQLException { return -1; } - @Override - public void beginRequest() throws SQLException { - Connection.super.beginRequest(); - } - - @Override - public void endRequest() throws SQLException { - Connection.super.endRequest(); - } - - @Override - public boolean setShardingKeyIfValid(ShardingKey shardingKey, ShardingKey superShardingKey, int timeout) throws SQLException { - return Connection.super.setShardingKeyIfValid(shardingKey, superShardingKey, timeout); - } - - @Override - public boolean setShardingKeyIfValid(ShardingKey shardingKey, int timeout) throws SQLException { - return Connection.super.setShardingKeyIfValid(shardingKey, timeout); - } - - @Override - public void setShardingKey(ShardingKey shardingKey, ShardingKey superShardingKey) throws SQLException { - Connection.super.setShardingKey(shardingKey, superShardingKey); - } - - @Override - public void setShardingKey(ShardingKey shardingKey) throws SQLException { - Connection.super.setShardingKey(shardingKey); - } - /** * Returns instance of the client used to execute queries by this connection. * @return - client instance diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/DataSourceImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/DataSourceImpl.java index 73808ae1c..f42c82982 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/DataSourceImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/DataSourceImpl.java @@ -5,10 +5,8 @@ import javax.sql.DataSource; import java.io.PrintWriter; import java.sql.Connection; -import java.sql.ConnectionBuilder; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; -import java.sql.ShardingKeyBuilder; import java.util.Properties; import java.util.logging.Logger; @@ -78,18 +76,8 @@ public int getLoginTimeout() throws SQLException { throw new SQLFeatureNotSupportedException("Method not supported", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); } - @Override - public ConnectionBuilder createConnectionBuilder() throws SQLException { - return DataSource.super.createConnectionBuilder(); - } - @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { throw new SQLFeatureNotSupportedException("Method not supported", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); } - - @Override - public ShardingKeyBuilder createShardingKeyBuilder() throws SQLException { - return DataSource.super.createShardingKeyBuilder(); - } } 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 330b2421b..cff7530ef 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java @@ -526,30 +526,6 @@ public long executeLargeUpdate(String sql, String[] columnNames) throws SQLExcep return executeUpdate(sql, columnNames); } - @Override - public String enquoteLiteral(String val) throws SQLException { - checkClosed(); - return Statement.super.enquoteLiteral(val); - } - - @Override - public String enquoteIdentifier(String identifier, boolean alwaysQuote) throws SQLException { - checkClosed(); - return Statement.super.enquoteIdentifier(identifier, alwaysQuote); - } - - @Override - public boolean isSimpleIdentifier(String identifier) throws SQLException { - checkClosed(); - return Statement.super.isSimpleIdentifier(identifier); - } - - @Override - public String enquoteNCharLiteral(String val) throws SQLException { - checkClosed(); - return Statement.super.enquoteNCharLiteral(val); - } - /** * Return query ID of last executed statement. It is not guaranteed when statements is used concurrently. * @return query ID 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 2360ea9de..a1546cfcc 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/PreparedStatementTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/PreparedStatementTest.java @@ -20,6 +20,9 @@ import java.sql.Types; import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalUnit; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -292,17 +295,54 @@ public void testTernaryOperator() throws Exception { @Test(groups = "integration") void testWithClause() throws Exception { - int count = 0; try (Connection conn = getJdbcConnection()) { try (PreparedStatement stmt = conn.prepareStatement("with data as (SELECT number FROM numbers(100)) select * from data ")) { stmt.execute(); ResultSet rs = stmt.getResultSet(); + int count = 0; while (rs.next()) { count++; } + assertEquals(count, 100); + } + } + } + + @Test(groups = "integration") + void testWithClauseWithParams() throws Exception { + final String table = "test_with_stmt"; + try (Connection conn = getJdbcConnection()) { + try (Statement stmt = conn.createStatement()) { + stmt.execute("DROP TABLE IF EXISTS " + table); + stmt.execute("CREATE TABLE " + table + " (v1 String) Engine MergeTree ORDER BY ()"); + stmt.execute("INSERT INTO " + table + " VALUES ('A'), ('B')"); + } + final Timestamp target_time = Timestamp.valueOf(LocalDateTime.now()); + try (PreparedStatement stmt = conn.prepareStatement("WITH " + + " toDateTime(?) as target_time, " + + " (SELECT 123) as magic_number" + + " SELECT *, target_time, magic_number FROM " + table)) { + stmt.setTimestamp(1, target_time); + stmt.execute(); + ResultSet rs = stmt.getResultSet(); + int count = 0; + assertEquals(rs.getMetaData().getColumnCount(), 3); + while (rs.next()) { + Assert.assertEquals( + rs.getTimestamp("target_time").toLocalDateTime().truncatedTo(ChronoUnit.SECONDS), + target_time.toInstant().atZone(ZoneId.of("UTC")).toLocalDateTime().truncatedTo(ChronoUnit.SECONDS)); + Assert.assertEquals(rs.getString("magic_number"), "123"); + Assert.assertEquals( + rs.getTimestamp(2).toLocalDateTime().truncatedTo(ChronoUnit.SECONDS), + target_time.toInstant().atZone(ZoneId.of("UTC")).toLocalDateTime().truncatedTo(ChronoUnit.SECONDS)); + Assert.assertEquals(rs.getString(3), "123"); + + count++; + } + assertEquals(count, 2, "Expected 2 rows"); + } } - assertEquals(count, 100); } @Test(groups = { "integration" }) diff --git a/pom.xml b/pom.xml index 7be49994f..95366caa9 100644 --- a/pom.xml +++ b/pom.xml @@ -593,6 +593,8 @@ ${minJdk} ${minJdk} + 17 + 17 true -Xlint:all From fe265b370e419fd91520c17bda5b3ec919fa0689 Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Wed, 28 May 2025 10:28:59 -0700 Subject: [PATCH 2/2] Addded more tests and fixed another variant of WITH statement --- .../jdbc/internal/ClickHouseParser.g4 | 1 + .../internal/ParsedPreparedStatement.java | 7 ++ .../jdbc/PreparedStatementTest.java | 75 +++++++++++++++++++ 3 files changed, 83 insertions(+) diff --git a/jdbc-v2/src/main/antlr4/com/clickhouse/jdbc/internal/ClickHouseParser.g4 b/jdbc-v2/src/main/antlr4/com/clickhouse/jdbc/internal/ClickHouseParser.g4 index 1dc7d6509..896ffb7bf 100644 --- a/jdbc-v2/src/main/antlr4/com/clickhouse/jdbc/internal/ClickHouseParser.g4 +++ b/jdbc-v2/src/main/antlr4/com/clickhouse/jdbc/internal/ClickHouseParser.g4 @@ -382,6 +382,7 @@ topClause fromClause : FROM joinExpr + | FROM identifier LPAREN QUERY RPAREN ; arrayJoinClause diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/ParsedPreparedStatement.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/ParsedPreparedStatement.java index 99ad33f84..144b85426 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/ParsedPreparedStatement.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/ParsedPreparedStatement.java @@ -188,6 +188,13 @@ public void enterInsertParameter(ClickHouseParser.InsertParameterContext ctx) { appendParameter(ctx.start.getStartIndex()); } + @Override + public void enterFromClause(ClickHouseParser.FromClauseContext ctx) { + if (ctx.QUERY() != null) { + appendParameter(ctx.QUERY().getSymbol().getStartIndex()); + } + } + private void appendParameter(int startIndex) { argCount++; if (argCount > paramPositions.length) { 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 a1546cfcc..aa7d81ca2 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/PreparedStatementTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/PreparedStatementTest.java @@ -345,6 +345,81 @@ void testWithClauseWithParams() throws Exception { } } + @Test(groups = { "integration" }) + void testMultipleWithClauses() throws Exception { + try (Connection conn = getJdbcConnection(); + PreparedStatement stmt = conn.prepareStatement( + "WITH data1 AS (SELECT 1 AS a), " + + " data2 AS (SELECT a + 1 AS b FROM data1) " + + "SELECT * FROM data2")) { + ResultSet rs = stmt.executeQuery(); + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + assertFalse(rs.next()); + } + } + + @Test(groups = { "integration" }) + void testRecursiveWithClause() throws Exception { + try (Connection conn = getJdbcConnection(); + PreparedStatement stmt = conn.prepareStatement( + "WITH RECURSIVE numbers AS (" + + " SELECT 1 AS n " + + " UNION ALL " + + " SELECT n + 1 FROM numbers WHERE n < 5" + + ") " + + "SELECT * FROM numbers ORDER BY n")) { + ResultSet rs = stmt.executeQuery(); + for (int i = 1; i <= 5; i++) { + assertTrue(rs.next()); + assertEquals(i, rs.getInt(1)); + } + assertFalse(rs.next()); + } + } + + @Test(groups = { "integration" }) + void testWithClauseWithMultipleParameters() throws Exception { + try (Connection conn = getJdbcConnection(); + PreparedStatement stmt = conn.prepareStatement( + "WITH data AS (" + + " (SELECT number AS n " + + " FROM numbers(?) " + + " WHERE n > ?)" + + ") " + + "SELECT * FROM data WHERE n < ?")) { +//"WITH data AS ( (SELECT number AS n FROM numbers(?) WHERE n > ?)) SELECT * FROM data WHERE n < ?" + stmt.setInt(1, 10); // numbers(10) = 0-9 + stmt.setInt(2, 3); // n > 3 + stmt.setInt(3, 7); // n < 7 + + ResultSet rs = stmt.executeQuery(); + int count = 0; + int expected = 4; // 4,5,6 + while (rs.next()) { + count++; + int n = rs.getInt(1); + assertTrue(n > 3 && n < 7); + } + assertEquals(3, count); + } + } + + @Test(groups = { "integration" }) + void testSelectFromArray() throws Exception { + try (Connection conn = getJdbcConnection(); + PreparedStatement stmt = conn.prepareStatement( + "SELECT * FROM numbers(?)")) { + stmt.setInt(1, 10); // numbers(10) = 0-9 + ResultSet rs = stmt.executeQuery(); + int count = 0; + while (rs.next()) { + count++; + } + assertEquals(10, count); + } + } + @Test(groups = { "integration" }) void testInsert() throws Exception { int ROWS = 1000;