Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

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

Aperta Adatta ai principianti
#3,212 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
2/5
Tempo stimato
1-3 ore
Idoneità per principianti
72/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
java
Ambito
databases

Direzione di ricerca

Inizia da jdbc-v2/src/main/java/com/clickhouse/jdbc/WriterStatementImpl.java in executeLargeUpdate, dove InsertSettings viene costruito senza un database (intorno alle righe 117-124), e confronta con come ConnectionImpl.setSchema imposta le impostazioni di query predefinite. Esegui il test di riproduzione nell'issue con il flag beta del writer RowBinary attivo e disattivato. È completato quando la riga finisce nel database selezionato con setSchema e il caso in cui la tabella esiste solo lì non genera più UNKNOWN_TABLE.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

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.

Lingua principale
Java
Stelle
1.6k
Fork
640
Merge medio
1g 12h
PR unite (30g)
42

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di ClickHouse/clickhouse-java

Tutte le issue di ClickHouse/clickhouse-java

Issue simili

Altre issue su Java

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.