[jdbc-v2] Connection.setSchema is ignored by the RowBinary writer INSERT path (rows go to the connection's configured database)
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
- 72/100
Hướng nghiên cứu
Bắt đầu từ jdbc-v2/src/main/java/com/clickhouse/jdbc/WriterStatementImpl.java tại executeLargeUpdate, nơi InsertSettings được tạo mà không có database (khoảng dòng 117-124), và so sánh với cách ConnectionImpl.setSchema thiết lập các cài đặt truy vấn mặc định. Chạy bài kiểm tra tái hiện trong issue với cờ beta của writer RowBinary bật và tắt. Hoàn thành khi dòng dữ liệu được ghi vào database đã chọn bằng setSchema, và trường hợp bảng chỉ tồn tại ở đó không còn ném lỗi UNKNOWN_TABLE nữa.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Description
In jdbc-v2, Connection.setSchema(db) updates the connection's defaultQuerySettings (ConnectionImpl.setSchema, jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java:671-675), and StatementImpl/PreparedStatementImpl pick that up because they merge connection.getDefaultQuerySettings() into their local QuerySettings.
The beta RowBinary writer path does not. When beta.row_binary_for_simple_insert=true and the INSERT qualifies for the writer, ConnectionImpl.prepareStatement creates a WriterStatementImpl, and WriterStatementImpl.executeLargeUpdate builds a fresh InsertSettings with no database:
InsertSettings settings = new InsertSettings();
settings.setQueryId(setLastQueryID(settings.getQueryId()));
... connection.getClient().insert(tableSchema.getTableName(), in, writer.getFormat(), settings)
(jdbc-v2/src/main/java/com/clickhouse/jdbc/WriterStatementImpl.java:117-124)
So the INSERT is sent with the database from the client configuration (the one from the JDBC URL / database property), not the one selected with setSchema. Note that the schema probe does use the right database — client.getTableSchema(table, schema) at ConnectionImpl.java:486 — so when a table with the same name exists in both databases there is no error at all: the rows silently land in the wrong database.
Consequences:
- Table exists in both databases → rows are written to the connection's configured database, silently. Data loss / data in the wrong place.
- Table exists only in the database selected with
setSchema→Code: 60 ... Table default.<t> does not exist. (UNKNOWN_TABLE), even though the schema lookup for the same table succeeded a moment earlier.
The non-writer path (PreparedStatementImpl, i.e. with the beta flag off) behaves correctly.
ClickHouse server version
26.9.18.11 (official build), local HTTP server, verified against a running server.
Reproduction
TestNG test (jdbc-v2), run against a local server on localhost:8123:
package com.clickhouse.jdbc;
import org.testng.Assert;
import org.testng.annotations.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;
public class SetSchemaWriterTest {
private static final String URL = "jdbc:ch:http://localhost:8123/default";
private Connection conn(Properties extra) throws Exception {
Properties info = new Properties();
info.setProperty("user", "default");
info.setProperty("password", "");
if (extra != null) {
info.putAll(extra);
}
return new ConnectionImpl(URL, info);
}
private void exec(String sql) throws Exception {
try (Connection c = conn(null); Statement s = c.createStatement()) {
s.execute(sql);
}
}
private int count(String fqTable) throws Exception {
try (Connection c = conn(null);
Statement s = c.createStatement();
ResultSet rs = s.executeQuery("SELECT count() FROM " + fqTable)) {
rs.next();
return rs.getInt(1);
}
}
@Test
public void testSetSchemaRespectedByWriterStatement() throws Exception {
String otherDb = "scratch_other_db";
String table = "scratch_t";
exec("DROP DATABASE IF EXISTS " + otherDb);
exec("CREATE DATABASE " + otherDb);
exec("CREATE TABLE " + otherDb + "." + table + " (x UInt32) ENGINE = Memory");
exec("DROP TABLE IF EXISTS default." + table);
exec("CREATE TABLE default." + table + " (x UInt32) ENGINE = Memory");
Properties props = new Properties();
props.setProperty(DriverProperties.BETA_ROW_BINARY_WRITER.getKey(), "true");
try (Connection c = conn(props)) {
c.setSchema(otherDb);
try (Statement s = c.createStatement();
ResultSet rs = s.executeQuery("SELECT currentDatabase()")) {
rs.next();
System.out.println("currentDatabase() via Statement = " + rs.getString(1)); // scratch_other_db
}
try (PreparedStatement ps = c.prepareStatement("INSERT INTO " + table + " VALUES (?)")) {
System.out.println("stmt class = " + ps.getClass().getName()); // WriterStatementImpl
ps.setInt(1, 1);
ps.executeUpdate();
}
}
Assert.assertEquals(count(otherDb + "." + table), 1, "row should land in the schema set via setSchema");
Assert.assertEquals(count("default." + table), 0);
}
}
Observed output and result:
currentDatabase() via Statement = scratch_other_db
stmt class = com.clickhouse.jdbc.WriterStatementImpl
rows in scratch_other_db.scratch_t = 0, rows in default.scratch_t = 1
FAILED: row should land in the schema set via setSchema expected [1] but found [0]
Expected: 1 row in scratch_other_db.scratch_t, 0 in default.scratch_t.
Actual: the row is written to default.scratch_t.
Same test with the beta flag off (statement is PreparedStatementImpl) passes: the row lands in scratch_other_db.
Variant where the table exists only in the database set by setSchema (no default.scratch_only_t), same writer path:
java.sql.SQLException: Code: 60. DB::Exception: Table default.scratch_only_t does not exist.
Maybe you meant scratch_other_db3.scratch_only_t?. (UNKNOWN_TABLE) (version 26.9.18.11 (official build))
Suggested fix
In WriterStatementImpl.executeLargeUpdate (jdbc-v2/src/main/java/com/clickhouse/jdbc/WriterStatementImpl.java:119), set the database on the InsertSettings from the connection at write time, e.g. settings.setDatabase(connection.getSchema()) (or the database the TableSchema was resolved against, tableSchema.getDatabaseName()), so the INSERT targets the same database that the schema probe used. Reading it at execution time also makes a setSchema call after prepareStatement behave sensibly.
InsertSettings.setDatabase already exists (client-v2/src/main/java/com/clickhouse/client/api/insert/InsertSettings.java:197) and drives the request database.
Link
Analogous issue reported for the .NET client: https://github.com/ClickHouse/clickhouse-cs/issues/683 (ClickHouseConnection.PostStreamAsync and ClickHouseBulkCopy ignore ChangeDatabase). Found by automated cross-client analysis (Polyglot AI) and verified against a live server.
- Ngôn ngữ chính
- Java
- Star
- 1.6k
- Fork
- 640
- Merge trung bình
- 1 ngày 12 giờ
- Pull request đã merge (30 ngày)
- 39
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- 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.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của ClickHouse/clickhouse-java
-
bug jdbc-v2
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
ClickHouse/clickhouse-java#3214 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
bug client-api-v2 test
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 92/100
ClickHouse/clickhouse-java#3076 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
area:sql-parser bug client-v1
Độ khó 1/5 1-3 giờ Mức phù hợp với người mới 92/100
ClickHouse/clickhouse-java#3066 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
bug client-api-v2 jdbc-v2
Độ khó 1/5 1-3 giờ Mức phù hợp với người mới 78/100
ClickHouse/clickhouse-java#2957 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
bug client-v1 wontfix
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
ClickHouse/clickhouse-java#2895 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của ClickHouse/clickhouse-java
Issue tương tự
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
apache/rocketmq-dashboard#7250 ·
Maintainer thường phản hồi trong vòng 3 ngày
-
HttpMaxFrameSizeTest.sendOversizedMessageTest() passes silently when maxFrameSize is not enforcedĐang mở
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 90/100
apache/activemq#2706 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
federicoiosue/Omni-Notes#1032 ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
unitycatalog/unitycatalog#2025 ·
Maintainer thường phản hồi trong vòng 4 ngày
-
Độ khó 1/5 1-3 giờ Mức phù hợp với người mới 78/100
krizaka/krizaka-notifications#22 ·
Maintainer thường phản hồi trong vòng 1 ngày