[jdbc-v2] Connection.createArrayOf / createStruct throw IllegalArgumentException instead of SQLException for an invalid type name

未關閉 適合新手
#3,347 0 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視

維護者通常 1 天內回覆

還沒有人認領這個 Issue。

評估

難度
2/5
預估耗時
1-3 小時
新手友好度
85/100
Issue 類型
缺陷
描述清晰度
描述清楚
活躍度
活躍
技術堆疊
java
領域
database

研究方向

從 jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java 中的 createArrayOf(約第 652 行)和 createStruct(約第 667 行)開始,追蹤 ClickHouseColumn.of 在何處拋出 IllegalArgumentException 或 NumberFormatException。完成的標準是:兩個方法都拋出以解析器例外為原因、SQLState 為 HY000 的 SQLException,同時 null、連線已關閉以及有效型別的行為保持不變。新增一個包含被拒絕型別名稱的 DataProvider,涵蓋這兩個方法,並斷言 SQLException 及其原因。

由索引模型根據 Issue 內容生成。

描述

bug jdbc-v2

Description

In jdbc-v2, Connection.createArrayOf(typeName, ...) and Connection.createStruct(typeName, ...) throw an unchecked IllegalArgumentException (or NumberFormatException) when the type-name parser rejects typeName. Both methods declare throws SQLException, and the JDBC API says that they throw SQLException. A caller that catches SQLException does not catch this error. Frameworks that translate SQLException (for example Spring's SQLExceptionTranslator) also do not see it.

All type names that the parser rejects are affected: an unknown type, a misspelled type, unbalanced parentheses, invalid type parameters, and an empty string. The server also rejects these names (see below), so an exception is correct. Only the exception type is wrong.

The two methods already throw SQLException for their other failures: a closed connection (08000), a non-Tuple type in createStruct (HY000, "Only Tuple datatype is supported for Struct"), and an error while createArrayOf builds the array (HY000, "Failed to create array"). Only the type-name parse step is outside this handling.

This is a different problem from #3344. #3344 is about one parse failure (Enum elements without numbers). After a fix for #3344, createArrayOf("NoSuchType", ...) still throws IllegalArgumentException.

Root cause

jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java:

  • createArrayOf, line 652: ClickHouseColumn.of("array", typeName) runs before the try { ... } catch (Exception e) { throw new SQLException("Failed to create array", ...) } block. A parse exception is not wrapped.
  • createStruct, line 667: ClickHouseColumn.of("v", typeName) has no try/catch.

ClickHouseColumn.of throws IllegalArgumentException for a type name that it cannot parse (for example from ClickHouseDataType.of, ClickHouseDataType.java:497), and NumberFormatException for an invalid numeric parameter.

Suggested fix
  • createArrayOf: move ClickHouseColumn.of(...) into the existing try.
  • createStruct: put ClickHouseColumn.of(...) in a try, and rethrow a parse exception as SQLException with ExceptionUtils.SQL_STATE_CLIENT_ERROR and the original exception as the cause. Put the type name in the message.
  • Keep the current behavior for: typeName == null (SQLFeatureNotSupportedException), a closed connection (SQLException, 08000), a non-Tuple type in createStruct, and all valid type names.
  • Test: one @DataProvider of rejected type names (unknown type, unbalanced parentheses, invalid Decimal parameters, empty string) for both methods. Assert SQLException and its cause.

Compatibility: a caller that catches IllegalArgumentException from these two methods gets SQLException after the fix. SQLException is already in both method signatures, so existing code still compiles.

Steps to reproduce
  1. Build main (f73e372bc). Connect with jdbc-v2 to ClickHouse 26.9.1.1629.
  2. Call conn.createArrayOf("NoSuchType", new Object[] {1}) in try { ... } catch (SQLException e) { ... }.
  3. The catch block does not run. java.lang.IllegalArgumentException: Unknown data type: NoSuchType goes out of the try. createStruct("Tuple(NoSuchType, UInt8)", ...) gives the same result.
Error Log or Exception StackTrace
java.lang.IllegalArgumentException: Unknown data type: NoSuchType
	at com.clickhouse.data.ClickHouseDataType.of(ClickHouseDataType.java:497)
	at com.clickhouse.data.ClickHouseColumn.readColumn(ClickHouseColumn.java:714)
	at com.clickhouse.data.ClickHouseColumn.of(ClickHouseColumn.java:763)
	at com.clickhouse.jdbc.ConnectionImpl.createArrayOf(ConnectionImpl.java:652)

java.lang.IllegalArgumentException: Unknown data type: NoSuchType
	at com.clickhouse.data.ClickHouseDataType.of(ClickHouseDataType.java:497)
	at com.clickhouse.data.ClickHouseColumn.readColumn(ClickHouseColumn.java:714)
	at com.clickhouse.data.ClickHouseColumn.readColumn(ClickHouseColumn.java:544)
	at com.clickhouse.data.ClickHouseColumn.of(ClickHouseColumn.java:763)
	at com.clickhouse.jdbc.ConnectionImpl.createStruct(ConnectionImpl.java:667)

Results of the jdbc-v2 calls (same program, same connection):

createArrayOf("Int32")                    -> OK
createArrayOf("NoSuchType")               -> java.lang.IllegalArgumentException: Unknown data type: NoSuchType
createArrayOf("Array(NoSuchType)")        -> java.lang.IllegalArgumentException: Unknown data type: NoSuchType
createArrayOf("Nullable(Strng)")          -> java.lang.IllegalArgumentException: Unknown data type: Strng
createArrayOf("Array(Int32")              -> java.lang.IllegalArgumentException: Missing ')' for '(' at position 5
createArrayOf("Decimal(abc, 2)")          -> java.lang.NumberFormatException: For input string: "abc"
createArrayOf("")                         -> java.lang.IllegalArgumentException: Non-empty typeName is required
createStruct("Tuple(Int8, String)")       -> OK
createStruct("Int32")                     -> java.sql.SQLException (HY000): Only Tuple datatype is supported for Struct
createStruct("NoSuchType")                -> java.lang.IllegalArgumentException: Unknown data type: NoSuchType
createStruct("Tuple(NoSuchType, UInt8)")  -> java.lang.IllegalArgumentException: Unknown data type: NoSuchType
createStruct("Tuple(Int8, String")        -> java.lang.IllegalArgumentException: Missing ')' for '(' at position 5
createStruct("Tuple(Decimal(abc, 2), UInt8)") -> java.lang.NumberFormatException: For input string: "abc"
createStruct("")                          -> java.lang.IllegalArgumentException: Non-empty typeName is required
createArrayOf(null) / createStruct(null)  -> java.sql.SQLFeatureNotSupportedException: typeName cannot be null
closed connection, both methods           -> java.sql.SQLException (08000): Connection is closed

The server rejects the same type names (SELECT toTypeName(defaultValueOfTypeName('<type>'))):

NoSuchType                Code: 50. DB::Exception: Unknown data type family: NoSuchType ... (UNKNOWN_TYPE)
Nullable(Strng)           Code: 50. DB::Exception: Unknown data type family: Strng. Maybe you meant: ['String'] ... (UNKNOWN_TYPE)
Tuple(NoSuchType, UInt8)  Code: 50. DB::Exception: Unknown data type family: NoSuchType ... (UNKNOWN_TYPE)
Array(Int32               Code: 62. DB::Exception: Syntax error (data type): ... Unmatched parentheses: '(' at position 6 is never closed ...
Decimal(abc, 2)           Code: 43. DB::Exception: Decimal argument precision is invalid ... (ILLEGAL_TYPE_OF_ARGUMENT)
Expected Behaviour

Both methods throw java.sql.SQLException for a type name that they cannot parse, with the parser exception as the cause. Use SQLState HY000, as the other client-side errors in these methods do.

java.sql.Connection Javadoc:

  • createArrayOf: "SQLException - if a database error occurs, the JDBC type is not appropriate for the typeName and the conversion is not supported, the typeName is null or this method is called on a closed connection"
  • createStruct: "SQLException - if a database error occurs, typeName is null or this method is called on a closed connection"
Code Example
Properties props = new Properties();
props.setProperty("user", "default");
props.setProperty("password", "<password>");
try (Connection conn = DriverManager.getConnection("jdbc:clickhouse://<host>:8123/default", props)) {
    try {
        conn.createArrayOf("NoSuchType", new Object[] {1});
        // same for: conn.createStruct("Tuple(NoSuchType, UInt8)", new Object[] {1, 2});
    } catch (SQLException e) {
        System.out.println("caught: " + e); // not reached
    }
}
// Exception in thread "main" java.lang.IllegalArgumentException: Unknown data type: NoSuchType
Configuration
Client Configuration
// default jdbc-v2 configuration, no extra properties
Environment
  • Cloud
  • Client version: jdbc-v2 0.12.0-rc1-SNAPSHOT (main, f73e372bc)
  • Language version: OpenJDK 17.0.21
  • OS: Linux (Docker)
ClickHouse Server
  • ClickHouse Server version: 26.9.1.1629
  • ClickHouse Server non-default settings, if any: none
  • CREATE TABLE statements for tables involved: none
  • Sample data for all these tables: none

Found by automated analysis (Polyglot AI) while investigating #3344. Verified with a program against a live ClickHouse 26.9.1.1629 server, not only by reading the code.

主要語言
Java
星號
1.6k
分支
649
平均合併
1 天 20 小時
30 天內合併 PR
39

環境準備

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

ClickHouse/clickhouse-java 的其他 Issue

查看 ClickHouse/clickhouse-java 的全部 Issue

相似的 Issue

更多 Java Issue

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。