diff --git a/.github/workflows/analysis.yml b/.github/workflows/analysis.yml index 014c55e18..f945c368d 100644 --- a/.github/workflows/analysis.yml +++ b/.github/workflows/analysis.yml @@ -92,14 +92,14 @@ jobs: - name: Build and install run: | find . -type f -name "simplelogger.*" -exec rm -fv '{}' \; - mvn -q --batch-mode -DclickhouseVersion=$PREFERRED_LTS_VERSION \ + mvn -q --no-transfer-progress --batch-mode -DclickhouseVersion=$PREFERRED_LTS_VERSION \ -DskipTests install - name: Analyze env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} run: | - mvn --batch-mode -DclickhouseVersion=$PREFERRED_LTS_VERSION \ + mvn -fn --no-transfer-progress --batch-mode -DclickhouseVersion=$PREFERRED_LTS_VERSION \ -Panalysis verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=ClickHouse_clickhouse-java continue-on-error: true - name: Generate and post coverage report diff --git a/.gitignore b/.gitignore index 2cb9e0dfb..bb8e8575f 100644 --- a/.gitignore +++ b/.gitignore @@ -48,6 +48,8 @@ java.prof jmh-result.* profile.html jdbc-v2/gen +jdbc-v2/src/main/antlr4/com/clickhouse/jdbc/internal/gen +jdbc-v2/src/main/antlr4/com/clickhouse/jdbc/internal/ClickHouseLexer.tokens # Shell scripts *.sh diff --git a/client-v2/src/main/java/com/clickhouse/client/api/ClientConfigProperties.java b/client-v2/src/main/java/com/clickhouse/client/api/ClientConfigProperties.java index 04bcaf737..752ca729b 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/ClientConfigProperties.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/ClientConfigProperties.java @@ -311,7 +311,9 @@ public static Map parseConfigMap(Map configMap) } } - LOG.warn("Unknown and unmapped config properties: {}", tmpMap); + if (!tmpMap.isEmpty()) { + LOG.warn("Unknown and unmapped config properties: {}", tmpMap); + } return parsedConfig; } diff --git a/jdbc-v2/src/main/antlr4/com/clickhouse/jdbc/internal/ClickHouseLexer.g4 b/jdbc-v2/src/main/antlr4/com/clickhouse/jdbc/internal/ClickHouseLexer.g4 index f6a2b4462..743e73c1a 100644 --- a/jdbc-v2/src/main/antlr4/com/clickhouse/jdbc/internal/ClickHouseLexer.g4 +++ b/jdbc-v2/src/main/antlr4/com/clickhouse/jdbc/internal/ClickHouseLexer.g4 @@ -135,7 +135,7 @@ MONTH : M O N T H; MOVE : M O V E; MUTATION : M U T A T I O N; NAN_SQL : N A N; // conflicts with macro NAN -NAME : N A M E; +NAME : N A M E; NO : N O; NO_PASSWORD : N O '_' P A S S W O R D; NONE : N O N E; @@ -228,6 +228,37 @@ WHERE : W H E R E; WINDOW : W I N D O W; WITH : W I T H; YEAR : Y E A R | Y Y Y Y; +QUOTA : Q U O T A; +ACCESS : A C C E S S; +GRANT : G R A N T; +WAIT : W A I T; +CLEANUP : C L E A N U P; +DEFINER : D E F I N E R; +RESTART : R E S T A R T; +SOURCES : S O U R C E S; +AZURE : A Z U R E; +FILE : F I L E; +HDFS : H D F S; +HIVE : H I V E; +JDBC : J D B C; +KAFKA : K A F K A; +MONGO : M O N G O; +MYSQL : M Y S Q L; +NATS : N A T S; +ODBC : O D B C; +POSTGRES : P O S T G R E S; +RABBITMQ : R A B B I T M Q; +REDIS : R E D I S; +REMOTE : R E M O T E; +S3 : S '3'; +SQLITE : S Q L I T E; +URL : U R L; +LOADING : L O A D I N G; +VIRTUAL : V I R T U A L; +VIEWS : V I E W S; +POLICY : P O L I C Y; +PERMISSIVE : P E R M I S S I V E; +RESTRICTIVE : R E S T R I C T I V E; JSON_FALSE : 'false'; JSON_TRUE : 'true'; 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 c6cbc6fe6..5c7796542 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 @@ -36,6 +36,7 @@ query | useStmt | watchStmt | ctes? selectStmt + | grantStmt ; // CTE statement @@ -148,8 +149,12 @@ createStmt validUntilClause? (DEFAULT ROLE identifier (COMMA identifier)*)? (DEFAULT DATABASE identifier | NONE)? - settingsClause? #CreateUserStmt + | CREATE ROLE (IF NOT EXISTS | OR REPLACE)? identifier (COMMA identifier)* clusterClause? + (IN identifier)? settingsClause? #CreateRoleStmt + | CREATE (ROW)? POLICY (IF NOT EXISTS | OR REPLACE)? identifier clusterClause? ON tableIdentifier + (IN identifier)? (AS (PERMISSIVE | RESTRICTIVE))? (FOR SELECT)? USING columnExpr + (TO identifier | ALL | ALL EXCEPT identifier)? # CreatePolicyStmt ; userIdentifier @@ -328,7 +333,7 @@ describeStmt dropStmt : (DETACH | DROP) DATABASE (IF EXISTS)? databaseIdentifier clusterClause? # DropDatabaseStmt - | (DETACH | DROP) (DICTIONARY | TEMPORARY? TABLE | VIEW) (IF EXISTS)? tableIdentifier clusterClause? ( + | (DETACH | DROP) (DICTIONARY | TEMPORARY? TABLE | VIEW | ROLE | USER) (IF EXISTS)? tableIdentifier clusterClause? ( NO DELAY )? # DropTableStmt ; @@ -372,7 +377,7 @@ assignmentValue : literal # InsertRawValue | QUERY # InsertParameter | identifier (LPAREN columnExprList? RPAREN)? # InsertParameterFuncExpr - + | LPAREN columnExpr RPAREN # InserParameterExpr ; // KILL statement @@ -571,13 +576,245 @@ setStmt // SET ROLE statement setRoleStmt - : SET (DEFAULT)? ROLE (setRolesList | NONE | ALL (EXCEPT setRolesList)) TO identifier | CURRENT_USER (COMMA identifier | CURRENT_USER)* + : SET (DEFAULT)? ROLE (setRolesList | NONE | ALL (EXCEPT setRolesList)) (TO identifier | CURRENT_USER (COMMA identifier | CURRENT_USER)*)? ; setRolesList : identifier (COMMA identifier)* ; +grantStmt + : GRANT clusterClause? ((privilege ON grantTableIdentifier) | (identifier (COMMA identifier)*)) + TO (CURRENT_USER | identifier) (COMMA identifier)* + (WITH GRANT OPTION)? (WITH REPLACE OPTION)? + ; + +grantTableIdentifier + : (identifier DOT)? identifier + | (identifier DOT)? ASTERISK + | (ASTERISK DOT)? identifier + | (ASTERISK DOT)? ASTERISK + ; + +privilege + : + | ACCESS MANAGEMENT + | ALLOW SQL SECURITY NONE + | ROLE ADMIN + | TABLE ENGINE + | TRUNCATE + | UNDROP TABLE + | NONE + | BACKUP + | CLUSTER + | INSERT + | INTROSPECTION + | KILL QUERY + | KILL TRANSACTION + | MOVE PARTITION BETWEEN SHARDS + | NAMED COLLECTION ADMIN + | ALTER NAMED COLLECTION + | CREATE NAMED COLLECTION + | NAMED COLLECTION + | OPTIMIZE + | SELECT + | SET DEFINER + | alterPrivilege + | createPrivilege + | dropPrivilege + | showPrivilege + | sourcePrivilege + | systemPrivilege + ; + +alterPrivilege + : + | ALTER QUOTA + | ALTER ROLE + | ALTER ROW POLICY + | ALTER SETTINGS PROFILE + | ALTER USER + | ALTER + | ALTER DATABASE + | ALTER DATABASE SETTINGS + | ALTER TABLE + | ALTER COLUMN + | ALTER ADD COLUMN + | ALTER CLEAR COLUMN + | ALTER COMMENT COLUMN + | ALTER DROP COLUMN + | ALTER MATERIALIZE COLUMN + | ALTER MODIFY COLUMN + | ALTER RENAME COLUMN + | ALTER CONSTRAINT + | ALTER ADD CONSTRAINT + | ALTER DROP CONSTRAINT + | ALTER DELETE + | ALTER FETCH PARTITION + | ALTER FREEZE PARTITION + | ALTER INDEX + | ALTER ADD INDEX + | ALTER CLEAR INDEX + | ALTER DROP INDEX + | ALTER MATERIALIZE INDEX + | ALTER ORDER BY + | ALTER SAMPLE BY + | ALTER MATERIALIZE TTL + | ALTER MODIFY COMMENT + | ALTER MOVE PARTITION + | ALTER PROJECTION + | ALTER SETTINGS + | ALTER STATISTICS + | ALTER ADD STATISTICS + | ALTER DROP STATISTICS + | ALTER MATERIALIZE STATISTICS + | ALTER MODIFY STATISTICS + | ALTER TTL + | ALTER UPDATE + | ALTER VIEW + | ALTER VIEW MODIFY QUERY + | ALTER VIEW REFRESH + | ALTER VIEW MODIFY SQL SECURITY + ; + +createPrivilege + :CREATE QUOTA + | CREATE ROLE + | CREATE ROW POLICY + | CREATE SETTINGS PROFILE + | CREATE USER + | CREATE + | CREATE ARBITRARY TEMPORARY TABLE + | CREATE TEMPORARY TABLE + | CREATE DATABASE + | CREATE DICTIONARY + | CREATE FUNCTION + | CREATE RESOURCE + | CREATE TABLE + | CREATE VIEW + | CREATE WORKLOAD + ; + +dropPrivilege + : DROP QUOTA + | DROP ROLE + | DROP ROW POLICY + | DROP SETTINGS PROFILE + | DROP USER + | DROP + | DROP DATABASE + | DROP DICTIONARY + | DROP FUNCTION + | DROP RESOURCE + | DROP TABLE + | DROP VIEW + | DROP WORKLOAD + | DROP NAMED COLLECTION + ; + +showPrivilege + : SHOW ACCESS + | SHOW QUOTAS + | SHOW ROLES + | SHOW ROW POLICIES + | SHOW SETTINGS PROFILES + | SHOW USERS + | SHOW + | SHOW COLUMNS + | SHOW DATABASES + | SHOW DICTIONARIES + | SHOW TABLES + | SHOW FILESYSTEM CACHES + | SHOW NAMED COLLECTIONS + | SHOW NAMED COLLECTIONS SECRETS + ; + +sourcePrivilege + : SOURCES + | AZURE + | FILE + | HDFS + | HIVE + | JDBC + | KAFKA + | MONGO + | MYSQL + | NATS + | ODBC + | POSTGRES + | RABBITMQ + | REDIS + | REMOTE + | S3 + | SQLITE + | URL + ; + +systemPrivilege + : SYSTEM + | SYSTEM CLEANUP + | SYSTEM DROP CACHE + | SYSTEM DROP COMPILED EXPRESSION CACHE + | SYSTEM DROP CONNECTIONS CACHE + | SYSTEM DROP DISTRIBUTED CACHE + | SYSTEM DROP DNS CACHE + | SYSTEM DROP FILESYSTEM CACHE + | SYSTEM DROP FORMAT SCHEMA CACHE + | SYSTEM DROP MARK CACHE + | SYSTEM DROP MMAP CACHE + | SYSTEM DROP PAGE CACHE + | SYSTEM DROP PRIMARY INDEX CACHE + | SYSTEM DROP QUERY CACHE + | SYSTEM DROP S3 CLIENT CACHE + | SYSTEM DROP SCHEMA CACHE + | SYSTEM DROP UNCOMPRESSED CACHE + | SYSTEM DROP PRIMARY INDEX CACHE + | SYSTEM DROP REPLICA + | SYSTEM FAILPOINT + | SYSTEM FETCHES + | SYSTEM FLUSH + | SYSTEM FLUSH ASYNC INSERT QUEUE + | SYSTEM FLUSH LOGS + | SYSTEM JEMALLOC + | SYSTEM KILL QUERY + | SYSTEM KILL TRANSACTION + | SYSTEM LISTEN + | SYSTEM LOAD PRIMARY KEY + | SYSTEM MERGES + | SYSTEM MOVES + | SYSTEM PULLING REPLICATION LOG + | SYSTEM REDUCE BLOCKING PARTS + | SYSTEM REPLICATION QUEUES + | SYSTEM REPLICA READINESS + | SYSTEM RESTART DISK + | SYSTEM RESTART REPLICA + | SYSTEM RESTORE REPLICA + | SYSTEM RELOAD + | SYSTEM RELOAD ASYNCHRONOUS METRICS + | SYSTEM RELOAD CONFIG + | SYSTEM RELOAD DICTIONARY + | SYSTEM RELOAD EMBEDDED DICTIONARIES + | SYSTEM RELOAD FUNCTION + | SYSTEM RELOAD MODEL + | SYSTEM RELOAD USERS + | SYSTEM SENDS + | SYSTEM DISTRIBUTED SENDS + | SYSTEM REPLICATED SENDS + | SYSTEM SHUTDOWN + | SYSTEM SYNC DATABASE REPLICA + | SYSTEM SYNC FILE CACHE + | SYSTEM SYNC FILESYSTEM CACHE + | SYSTEM SYNC REPLICA + | SYSTEM SYNC TRANSACTION LOG + | SYSTEM THREAD FUZZER + | SYSTEM TTL MERGES + | SYSTEM UNFREEZE + | SYSTEM UNLOAD PRIMARY KEY + | SYSTEM VIEWS + | SYSTEM VIRTUAL PARTS UPDATE + | SYSTEM WAIT LOADING PARTS + ; + // SHOW statements showStmt @@ -643,6 +880,7 @@ columnsExpr columnExpr : CASE columnExpr? (WHEN columnExpr THEN columnExpr)+ (ELSE columnExpr)? END # ColumnExprCase | CAST LPAREN columnExpr AS columnTypeExpr RPAREN # ColumnExprCast + | columnExpr CAST_OP columnTypeExpr # ColumnExprCast2 | DATE STRING_LITERAL # ColumnExprDate | EXTRACT LPAREN interval FROM columnExpr RPAREN # ColumnExprExtract | INTERVAL columnExpr interval? # ColumnExprInterval @@ -693,8 +931,7 @@ columnExpr | LPAREN columnExprList RPAREN # ColumnExprTuple | LBRACKET columnExprList? RBRACKET # ColumnExprArray | columnIdentifier # ColumnExprIdentifier - | QUERY # ColumnExprParam - | QUERY CAST_OP identifier # ColumnExprParamWithCast + | QUERY (CAST_OP identifier)? # ColumnExprParam ; columnArgList @@ -861,6 +1098,7 @@ keyword | GLOBAL | GRANULARITY | GROUP + | GRANT | HAVING | HIERARCHICAL | ID @@ -901,6 +1139,8 @@ keyword | NO | NOT | NULLS + | NULL_SQL + | NAME | OFFSET | ON | OPTIMIZE @@ -960,6 +1200,7 @@ keyword | UPDATE | USE | USING + | USER | UUID | VALUES | VIEW @@ -986,6 +1227,7 @@ keywordForAlias | TEST | VIEW | PRIMARY + | GRANT ; alias 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 acd3f19c2..62e0493ec 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 @@ -166,10 +166,9 @@ public void enterColumnExprParam(ClickHouseParser.ColumnExprParamContext ctx) { appendParameter(ctx.start.getStartIndex()); } - @Override - public void enterColumnExprParamWithCast(ClickHouseParser.ColumnExprParamWithCastContext ctx) { - appendParameter(ctx.start.getStartIndex()); + public void enterColumnExprPrecedence3(ClickHouseParser.ColumnExprPrecedence3Context ctx) { + super.enterColumnExprPrecedence3(ctx); } @Override diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/SqlParser.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/SqlParser.java index 47ca141c3..111ae77f3 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/SqlParser.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/SqlParser.java @@ -18,27 +18,28 @@ public class SqlParser { private static final Logger LOG = LoggerFactory.getLogger(SqlParser.class); public ParsedStatement parsedStatement(String sql) { - - CharStream charStream = CharStreams.fromString(sql); - ClickHouseLexer lexer = new ClickHouseLexer(charStream); - ClickHouseParser parser = new ClickHouseParser(new CommonTokenStream(lexer)); - parser.removeErrorListeners(); - parser.addErrorListener(new ParserErrorListener()); - ClickHouseParser.QueryStmtContext parseTree = parser.queryStmt(); ParsedStatement parserListener = new ParsedStatement(); - IterativeParseTreeWalker.DEFAULT.walk(parserListener, parseTree); - + walkSql(sql, parserListener); return parserListener; } public ParsedPreparedStatement parsePreparedStatement(String sql) { + ParsedPreparedStatement parserListener = new ParsedPreparedStatement(); + walkSql(sql, parserListener); + return parserListener; + } + + private ClickHouseParser walkSql(String sql, ClickHouseParserBaseListener listener ) { CharStream charStream = CharStreams.fromString(sql); ClickHouseLexer lexer = new ClickHouseLexer(charStream); ClickHouseParser parser = new ClickHouseParser(new CommonTokenStream(lexer)); + parser.removeErrorListeners(); + parser.addErrorListener(new ParserErrorListener()); + ClickHouseParser.QueryStmtContext parseTree = parser.queryStmt(); - ParsedPreparedStatement parserListener = new ParsedPreparedStatement(); - IterativeParseTreeWalker.DEFAULT.walk(parserListener, parseTree); - return parserListener; + IterativeParseTreeWalker.DEFAULT.walk(listener, parseTree); + + return parser; } private final static Pattern UNQUOTE_INDENTIFIER = Pattern.compile( diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/DataTypeTests.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/DataTypeTests.java index 9693a9e06..2a3d11d4a 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/DataTypeTests.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/DataTypeTests.java @@ -294,6 +294,37 @@ public void testUnsignedIntegerTypes() throws Exception { } } + + @Test(groups = { "integration" }) + public void testUUIDTypes() throws Exception { + Random rand = new Random(); + runQuery("CREATE TABLE test_uuids (order Int8, " + + "uuid Nullable(UUID) " + + ") ENGINE = MergeTree ORDER BY ()"); + + // Insert null values + insertData("INSERT INTO test_uuids VALUES ( 1, NULL)"); + + // Insert random values + UUID uuid = UUID.randomUUID(); + insertData("INSERT INTO test_uuids VALUES ( 2, " + + "'" + uuid + "')"); + + try (Connection conn = getJdbcConnection(); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT uuid FROM test_uuids ORDER BY order")) { + + assertTrue(rs.next()); + assertNull((UUID) rs.getObject("uuid")); + + + assertTrue(rs.next()); + assertEquals((UUID) rs.getObject("uuid"), uuid); + + assertFalse(rs.next()); + } + } + @Test(groups = { "integration" }) public void testDecimalTypes() throws SQLException { runQuery("CREATE TABLE test_decimals (order Int8, " 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 a183a5c38..2f9689b8a 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/PreparedStatementTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/PreparedStatementTest.java @@ -868,6 +868,17 @@ void testWriteUUID() throws Exception { Assert.assertTrue(rs.next()); Assert.assertEquals(rs.getInt(1), 1); } + + final String selectSQL = "SELECT * FROM `test_issue_2327` WHERE " + + "`" + getDatabase() + "`.`test_issue_2327`.`uuid` IN (CAST(? AS UUID))"; + try (PreparedStatement stmt = conn.prepareStatement(selectSQL)) { + stmt.setString(1, uuid.toString()); + try (ResultSet rs = stmt.executeQuery()) { + Assert.assertTrue(rs.next()); + Assert.assertEquals(rs.getString(1), "testId01"); + Assert.assertEquals(rs.getString(2), uuid.toString()); + } + } } } @@ -1080,4 +1091,33 @@ public void testParamWithCast() throws Exception { } } } + + @Test(groups = {"integration"}) + public void testSelectWithTableAliasAsKeyword() throws Exception { + try (Connection conn = getJdbcConnection()) { + String[] keywords = { + "ALL", "AND", "ANY", "AS", "ASC", "BY", "CREATE", "DATABASE", "DELETE", "DESC", "DISTINCT", "DROP", "EXISTS", "FROM", "GRANT", "GROUP", "HAVING", "INSERT", "INTO", "LIMIT", "NOT", "NULL", "ON", "ORDER", "REVOKE", "SELECT", "SET", "TABLE", "TO", "UPDATE", "VALUES", "VIEW", "WHILE", "WITH", "WHERE" + }; + + for (String keyword : keywords) { + final String table = keyword; + try (Statement stmt = conn.createStatement()) { + stmt.execute("DROP TABLE IF EXISTS " + table); + stmt.execute("CREATE TABLE " + table + " (v1 Int32, v2 String) Engine MergeTree ORDER BY ()"); + stmt.execute("INSERT INTO `" + table + "` VALUES (1000, 'test')"); + } + + try (PreparedStatement stmt = conn.prepareStatement("SELECT v1, v2 FROM " + table + " AS " + keyword + " WHERE v1 = ? AND v2 = ?")) { + stmt.setInt(1, 1000); + stmt.setString(2, "test"); + stmt.execute(); + try (ResultSet rs = stmt.getResultSet()) { + assertTrue(rs.next()); + Assert.assertEquals(rs.getInt(1), 1000); + Assert.assertEquals(rs.getString(2), "test"); + } + } + } + } + } } 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 ad45659fe..2c1e747be 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java @@ -2,6 +2,7 @@ import com.clickhouse.client.api.ClientConfigProperties; import com.clickhouse.client.api.query.GenericRecord; +import com.clickhouse.jdbc.internal.ClickHouseParser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.Assert; @@ -729,4 +730,27 @@ public void testDDLStatements() throws Exception { } } } + + @Test(groups = {"integration"}) + public void testSelectWithKeywordsAsTableAlias() throws Exception { + String[] keywords = { + "ALL", "AND", "ANY", "AS", "ASC", "BY", "CREATE", "DATABASE", "DELETE", "DESC", "DISTINCT", "DROP", "EXISTS", "FROM", "GRANT", "GROUP", "HAVING", "INSERT", "INTO", "LIMIT", "NOT", "NULL", "ON", "ORDER", "REVOKE", "SELECT", "SET", "TABLE", "TO", "UPDATE", "VALUES", "VIEW", "WHILE", "WITH", "WHERE" + }; + + try (Connection conn = getJdbcConnection()) { + for (String keyword : keywords) { + String createSql = "CREATE TABLE IF NOT EXISTS " + getDatabase() + "." + keyword + "(id UInt8) ENGINE = MergeTree ORDER BY id"; + String insertSql = "INSERT INTO " + getDatabase() + "." + keyword + " VALUES (1)"; + String selectSql = "SELECT * FROM " + getDatabase() + "." + keyword + " AS " + keyword; + try (Statement stmt = conn.createStatement()) { + Assert.assertFalse(stmt.execute(createSql)); + Assert.assertFalse(stmt.execute(insertSql)); + try (ResultSet rs = stmt.executeQuery(selectSql)) { + Assert.assertTrue(rs.next()); + Assert.assertEquals(rs.getInt(1), 1); + } + } + } + } + } } diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/internal/SqlParserTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/internal/SqlParserTest.java index 96a6e3b8d..fb7d5c1f5 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/internal/SqlParserTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/internal/SqlParserTest.java @@ -1,16 +1,10 @@ package com.clickhouse.jdbc.internal; -import org.antlr.v4.runtime.tree.TerminalNode; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; @@ -204,7 +198,7 @@ public void testEscapeQuotes() { @Test public void testStmtWithCasts() { - String sql = "SELECT ?::integer, ?, '?::integer' FROM table WHERE v = ?::integer"; // CAST(?, INTEGER) + String sql = "SELECT ?::integer, ?, '?:: integer' FROM table WHERE v = ?::integer"; // CAST(?, INTEGER) SqlParser parser = new SqlParser(); ParsedPreparedStatement stmt = parser.parsePreparedStatement(sql); Assert.assertEquals(stmt.getArgCount(), 3); @@ -218,6 +212,15 @@ public void testStmtWithFunction() { Assert.assertEquals(stmt.getArgCount(), 4); } + @Test + public void testStmtWithUUID() { + String sql = "select sum(value) from `uuid_filter_db`.`uuid_filter_table` where uuid = ?"; + SqlParser parser = new SqlParser(); + ParsedPreparedStatement stmt = parser.parsePreparedStatement(sql); + Assert.assertEquals(stmt.getArgCount(), 1); + Assert.assertFalse(stmt.isHasErrors()); + } + @Test(dataProvider = "testCreateStmtDP") public void testCreateStatement(String sql) { SqlParser parser = new SqlParser(); @@ -273,9 +276,8 @@ public static Object[][] testCTEStmtsDP() { public void testMiscStatements(String sql, int args) { SqlParser parser = new SqlParser(); ParsedPreparedStatement stmt = parser.parsePreparedStatement(sql); - Assert.assertFalse(stmt.isHasErrors()); Assert.assertEquals(stmt.getArgCount(), args); - System.out.println(stmt.getTable()); + Assert.assertFalse(stmt.isHasErrors()); } @DataProvider @@ -297,6 +299,38 @@ public Object[][] testMiscStmtDp() { {"SELECT * FROM table primary WHERE ts = ?", 1}, {"insert into events (s) values ('a')", 0}, {"insert into `events` (s) values ('a')", 0}, + {"SELECT COUNT(*) > 0 FROM system.databases WHERE name = ?", 1}, + {"SELECT count(*) > 0 FROM system.databases WHERE c1 = ?", 1}, + {"alter table user delete where reg_time = ?", 1}, + {"SELECT * FROM a,b WHERE id > ?", 1}, + {"DROP USER IF EXISTS default_impersonation_user", 0}, + {"DROP ROLE IF EXISTS `vkonfwxapllzkkgkqdvt`", 0}, + {"CREATE ROLE `kjxrsscptauligukwgmf` ON CLUSTER '{cluster}'", 0}, + {"GRANT SELECT ON `test_data`.`venues` TO `vkonfwxapllzkkgkqdvt`", 0}, + {"GRANT `uqkczgnpmpuktxhwvqqd` TO `default_impersonation_user`", 0}, + {"SET ROLE NONE", 0}, + {"CREATE ROLE IF NOT EXISTS row_a ON CLUSTER '{cluster}'", 0}, + {"CREATE ROW POLICY role_policy_BTABPUVDDLXZPYBCJGGZ ON `test_data`.`products` AS RESTRICTIVE FOR SELECT USING (`id` = 1) TO `annhpwyelooonsmqjldo`", 0}, + {"CREATE ROW POLICY role_policy_BTABPUVDDLXZPYBCJGGZ ON `products` AS RESTRICTIVE FOR SELECT USING (`id` = 1) TO `annhpwyelooonsmqjldo`", 0}, + {"GRANT ON CLUSTER '{cluster}' row_a, row_b, row_c TO metabase_impersonation_test_user", 0}, + {"GRANT ON CLUSTER '{cluster}' SELECT ON metabase_impersonation_test.test_1751397165968 TO metabase_impersonation_test_user", 0}, + {"CREATE ROW POLICY OR REPLACE policy_row_a ON CLUSTER '{cluster}' ON metabase_impersonation_test.test_1751397165968 FOR SELECT USING s = 'a' TO row_a", 0}, + {"CREATE ROW POLICY OR REPLACE policy_row_b ON CLUSTER '{cluster}' ON metabase_impersonation_test.test_1751397165968 FOR SELECT USING s = 'b' TO row_b", 0}, + {"CREATE ROW POLICY OR REPLACE policy_row_c ON CLUSTER '{cluster}' ON metabase_impersonation_test.test_1751397165968 FOR SELECT USING s = 'c' TO row_c", 0}, + {"GRANT SELECT ON `metabase_test_role_db`.`*` TO `metabase_test_role`,`metabase-test-role`", 0}, + {"GRANT SELECT ON `metabase_test_role_db`.* TO `metabase_test_role`,`metabase-test-role`", 0}, + {"GRANT `metabase_test_role`, `metabase-test-role` TO `metabase_test_user`", 0}, + {"GRANT ON CLUSTER '{cluster}' SELECT ON `metabase_test_role_db`.* TO `metabase_test_role`, `metabase-test-role`", 0}, + {"GRANT ON CLUSTER '{cluster}' `metabase_test_role`, `metabase-test-role` TO `metabase_test_user`", 0}, + {"SELECT * FROM `test_data`.`categories` WHERE id = 1::String or id = ?", 1}, + {"SELECT * FROM `test_data`.`categories` WHERE id = cast(1 as String) or id = ?", 1}, + {INSERT_INLINE_DATA, 0}, + {"select sum(value) from `uuid_filter_db`.`uuid_filter_table` WHERE `uuid_filter_db`.`uuid_filter_table`.`uuid` IN (CAST('36f7f85c-d7f4-49e2-af05-f45d5f6636ad' AS UUID))", 0}, }; } + + private static final String INSERT_INLINE_DATA = + "INSERT INTO `interval_15_XUTLZWBLKMNZZPRZSKRF`.`checkins` (`timestamp`, `id`) " + + "VALUES ((`now64`(9) + INTERVAL -225 second), 1)"; + } \ No newline at end of file diff --git a/pom.xml b/pom.xml index 708e4b3f1..579c99840 100644 --- a/pom.xml +++ b/pom.xml @@ -80,7 +80,7 @@ - 0.9.0-SNAPSHOT + 0.9.1-SNAPSHOT 2025 UTF-8 UTF-8