[client-v2] Float NaN and Infinity are silently written into integer columns (NaN -> 0, Inf -> MAX_VALUE), also into Nullable(Int*)
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
調査の方向性
Start with client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/SerializerUtils.java, especially convertToInteger, convertToLong, and the NumberConverter.toBigInteger path. Trace how integer and Decimal targets handle non-finite Float and Double inputs, then locate or add coverage for the listed scalar and nested types. Done means those inputs fail with a clear client-side error instead of being written as zero or maximum values.
索引モデルが issue の本文から書いたものです。
説明
Describe the bug
SerializerUtils.serializePrimitiveData converts a user-supplied Number to an integer column value with plain Java narrowing — convertToInteger / convertToLong call ((Number) value).intValue() / .longValue() (client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/SerializerUtils.java:1056-1082, used at lines 656-681 and 746).
Java narrowing of a non-finite double produces a legal-looking number instead of failing:
| Written value | Column | Stored value |
|---|---|---|
Double.NaN |
Nullable(Int32) |
0 (not NULL, no error) |
Double.POSITIVE_INFINITY |
Int32 |
2147483647 |
Double.POSITIVE_INFINITY |
Int64 |
9223372036854775807 |
No exception is raised and nothing is logged, so the row is silently corrupted. The server itself rejects both conversions (SELECT toInt32(nan) and SELECT toInt32(inf) fail with code 70, CANNOT_CONVERT_TYPE), so the client is more permissive than the server it writes to.
This is related to #3176 (silent wrapping of out-of-range integers) but is a distinct failure mode: the intValue() vs longValue() comparison suggested there (NumberConverter.toInt) does not catch NaN — both accessors return 0 for it — and does not catch +Inf for Int64 either. A non-finite check is needed on top of the range check.
ClickHouse server version
26.10.3.7 (local server at http://localhost:8123). The test below was run against it.
Reproduction
TestNG test in client-v2:
package com.clickhouse.client.insert;
import com.clickhouse.client.api.Client;
import com.clickhouse.client.api.data_formats.RowBinaryFormatWriter;
import com.clickhouse.client.api.insert.InsertResponse;
import com.clickhouse.client.api.insert.InsertSettings;
import com.clickhouse.client.api.metadata.TableSchema;
import com.clickhouse.client.api.query.GenericRecord;
import com.clickhouse.data.ClickHouseFormat;
import org.testng.annotations.Test;
import java.util.List;
public class NanIntScratchTest {
@Test
public void nanAndInfIntoIntegerColumns() throws Exception {
try (Client client = new Client.Builder()
.addEndpoint("http://localhost:8123")
.setUsername("default").setPassword("")
.compressServerResponse(false).compressClientRequest(false)
.build()) {
client.execute("DROP TABLE IF EXISTS nan_int_test").get();
client.execute("CREATE TABLE nan_int_test (v Nullable(Int32), w Int32, i64 Int64, f Float64) ENGINE Memory").get();
TableSchema schema = client.getTableSchema("nan_int_test");
Object[][] rows = new Object[][]{
{13.0d, 13.0d, 13.0d, 13.0d},
{Double.NaN, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, 1.0d},
};
ClickHouseFormat format = ClickHouseFormat.RowBinary;
try (InsertResponse r = client.insert("nan_int_test", out -> {
RowBinaryFormatWriter w = new RowBinaryFormatWriter(out, schema, format);
for (Object[] row : rows) {
for (int i = 0; i < row.length; i++) {
w.setValue(i + 1, row[i]);
}
w.commitRow();
}
}, format, new InsertSettings()).get()) {
System.out.println("written rows = " + r.getWrittenRows());
}
for (GenericRecord rec : client.queryAll("SELECT v, w, i64, f FROM nan_int_test ORDER BY f")) {
System.out.println("v=" + rec.getObject(1) + " w=" + rec.getInteger(2)
+ " i64=" + rec.getLong(3) + " f=" + rec.getDouble(4));
}
}
}
}
Actual output:
written rows = 2
v=0 w=2147483647 i64=9223372036854775807 f=1.0
v=13 w=13 i64=13 f=13.0
Expected: the insert fails with a clear client-side error for the non-finite values (as the server does), instead of writing 0 / MAX_VALUE.
Suggested fix
In client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/SerializerUtils.java, reject non-finite values before narrowing — in convertToInteger (line 1056) and convertToLong (line 1070), and in the NumberConverter.toBigInteger path used for UInt64/Int128/Int256, throw when a Float/Double argument is NaN or infinite. The same check is needed for Decimal targets. Nested containers (Array(Nullable(Int*)), Tuple, Map, LowCardinality(Nullable(Int*))) go through the same converters, so one check covers them.
Link
Relayed from the equivalent bug in clickhouse-connect: https://github.com/ClickHouse/clickhouse-connect/issues/1172
- 主要言語
- Java
- スター
- 1.6k
- フォーク
- 649
- 平均マージ
- 1日 20時間
- マージ済み PR(30日)
- 39
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
ClickHouse/clickhouse-java のほかの issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
ClickHouse/clickhouse-java#3349 ·
メンテナーはふだん 1 日以内に返信
-
bug jdbc-v2
難易度 2/5 1〜3時間 初心者へのやさしさ 85/100
ClickHouse/clickhouse-java#3347 ·
メンテナーはふだん 1 日以内に返信
-
bug jdbc-v2
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
ClickHouse/clickhouse-java#3326 ·
メンテナーはふだん 1 日以内に返信
-
[client-v2] Enum8/Enum16 insert accepts a non-member integer and uses Enum.ordinal(), silently storing values that are not enum members対応中かも @jaya232 が 6 日前に担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
ClickHouse/clickhouse-java#3304 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
bug jdbc-v2
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
ClickHouse/clickhouse-java#3214 ·
メンテナーはふだん 1 日以内に返信
ClickHouse/clickhouse-java の issue をすべて見る
似ている issue
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
メンテナーはふだん 1 日以内に返信
-
bug:unconfirmed
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
UniversalMediaServer/UniversalMediaServer#6473 ·
メンテナーはふだん 1 日以内に返信
-
難易度 1/5 1時間未満 初心者へのやさしさ 82/100
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
StarRocks/starrocks#83246 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
メンテナーはふだん 2 日以内に返信