Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

[jdbc-v2] Connection.setSchema is ignored by the RowBinary writer INSERT path (rows go to the connection's configured database)

Đang mở Phù hợp với người mới
#3,212 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
72/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
Lĩnh vực
databases

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

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.