[jdbc-v2] prepareStatement() throws unchecked ServerException/ClientException, not SQLException, when the RowBinary writer schema lookup fails

Đang mở Phù hợp với người mới
#3,326 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

Chưa có ai nhận issue này.

Đánh giá

Độ khó
2/5
Thời gian dự kiến
1-3 giờ
Mức phù hợp với người mới
82/100
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
java

Hướng nghiên cứu

Bắt đầu tại jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java, ở overload prepareStatement(String, int, int, int) và lời gọi client.getTableSchema của nó. Đọc cách mã JDBC gần đó sử dụng ExceptionUtils.toSqlState, sau đó chạy hoặc bổ sung kiểm thử cho các lỗi tra cứu schema như bảng không tồn tại, bị từ chối quyền truy cập và hết thời gian chờ. Hoàn tất khi các lỗi này được trả về dưới dạng SQLException với SQL state và mã lỗi mong đợi, đồng thời các lần tra cứu thành công vẫn hoạt động.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

bug jdbc-v2

Description

With the beta RowBinary writer on (beta.row_binary_for_simple_insert=true), Connection.prepareStatement("INSERT INTO t VALUES (?)") sends DESCRIBE TABLE t to the server. When that lookup fails, prepareStatement() throws the unchecked client-v2 exception (com.clickhouse.client.api.ServerException or ClientException, both RuntimeException) and not a java.sql.SQLException.

The JDBC contract of prepareStatement is throws SQLException. Callers, connection pools and frameworks catch SQLException, so this exception goes past their error handling. With the writer off, the same failures come from executeUpdate() as SQLException with the correct SQL state.

All failure types of the lookup are affected: unknown table, access denied, socket timeout, server not reachable.

Found by automated analysis of the client while working on #3268. Verified against a live server (not by inspection only).

Steps to reproduce
  1. Open a jdbc-v2 connection with beta.row_binary_for_simple_insert=true.
  2. Call prepareStatement("INSERT INTO no_such_table VALUES (?)"), or prepare an INSERT into a table that the user cannot SHOW COLUMNS on, or let the DESCRIBE TABLE request time out (socket_timeout).
  3. Look at the exception type.
Error Log or Exception StackTrace

Output of the code example below (main f9586aba0, ClickHouse 26.9.1.1629):

==== writer=false
A unknown table   prepareStatement -> OK (PreparedStatementImpl)
A unknown table   executeUpdate -> THREW java.sql.SQLException (sqlState/code=42S02/60, root=ServerException: Code: 60. DB::Exception: Table default.no_such_table does not exist. (UNKNOWN_TABLE))
B no access       prepareStatement -> OK (PreparedStatementImpl)
B no access       executeUpdate -> THREW java.sql.SQLException (sqlState/code=22000/497, root=ServerException: Code: 497 ...)
C socket timeout  prepareStatement -> OK (PreparedStatementImpl)
C socket timeout  executeUpdate -> THREW java.sql.SQLException (sqlState/code=HY000/0, root=java.net.SocketTimeoutException: Read timed out)
==== writer=true
A unknown table   prepareStatement -> THREW com.clickhouse.client.api.ServerException (instanceof SQLException=false, Code: 60 ... (UNKNOWN_TABLE))
A' existing table prepareStatement -> OK (WriterStatementImpl)
B no access       prepareStatement -> THREW com.clickhouse.client.api.ServerException (instanceof SQLException=false, Code: 497 ...)
C socket timeout  prepareStatement -> THREW com.clickhouse.client.api.ClientException (instanceof SQLException=false, root=java.net.SocketTimeoutException: Read timed out)
Expected Behaviour

prepareStatement() throws java.sql.SQLException for all failures of the schema lookup, with the same mapping as the rest of jdbc-v2 (ExceptionUtils.toSqlState): for example SQL state 42S02 and error code 60 for an unknown table, as the writer-off path does today.

Root cause. jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java:489 (prepareStatement(String, int, int, int)) calls client.getTableSchema(table, schema) without a try/catch. Client.getTableSchemaImpl (client-v2/.../Client.java:2390-2411) re-throws ServerException and wraps all other failures in ClientException. Both extend ClickHouseException extends RuntimeException, so they go out of prepareStatement() unchanged. The other overloads (prepareStatement(String), and the autoGeneratedKeys / columnIndexes / columnNames overloads with ignore_unsupported_values) delegate to this method, so they are affected too.

Suggested fix. Catch the failure of the getTableSchema call and re-throw it with ExceptionUtils.toSqlState(e), which already maps ServerException (incl. TABLE_NOT_FOUND → 42S02) and ClientException. Keep the current behavior when the lookup succeeds, and keep the writer-off path (no server call in prepareStatement) as it is.

Code Example
// user "lead_noacc" has no grants: CREATE USER lead_noacc IDENTIFIED WITH plaintext_password BY 'pw'
// table: CREATE TABLE default.lead_t (id Int32) ENGINE = Memory
Properties p = new Properties();
p.setProperty("user", "default");
p.setProperty("password", "");
p.setProperty("beta.row_binary_for_simple_insert", "true");
try (Connection c = new com.clickhouse.jdbc.Driver().connect("jdbc:clickhouse://localhost:8123/default", p)) {
    try {
        c.prepareStatement("INSERT INTO no_such_table VALUES (?)");
    } catch (SQLException e) {
        System.out.println("SQLException " + e.getSQLState());   // expected
    } catch (RuntimeException e) {
        System.out.println("unchecked " + e.getClass().getName()); // actual: com.clickhouse.client.api.ServerException
    }
}
// Same result for user "lead_noacc" with "INSERT INTO default.lead_t VALUES (?)" (ServerException, code 497),
// and ClientException (root SocketTimeoutException) when socket_timeout expires during DESCRIBE TABLE.
Configuration
Client Configuration
beta.row_binary_for_simple_insert=true
// timeout case only: socket_timeout=1000, with the DESCRIBE TABLE response delayed
Environment
  • Cloud
  • Client version: jdbc-v2 from main (f9586aba0, 0.12.0-rc1-SNAPSHOT)
  • Language version: JDK 17
  • 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: CREATE TABLE default.lead_t (id Int32) ENGINE = Memory
  • Sample data for all these tables: not needed
Ngôn ngữ chính
Java
Star
1.6k
Fork
649
Merge trung bình
1 ngày 20 giờ
Pull request đã merge (30 ngày)
39

Chuẩn bị môi trường

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của ClickHouse/clickhouse-java

Tất cả issue của ClickHouse/clickhouse-java

Issue tương tự

Thêm issue về Java

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.