[client-v2] Float NaN and Infinity are silently written into integer columns (NaN -> 0, Inf -> MAX_VALUE), also into Nullable(Int*)
維護者通常 1 天內回覆
還沒有人認領這個 Issue。
評估
研究方向
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 小時
- 30 天內合併 PR
- 39
環境準備
- 沒有 Dockerfile 或 Docker Compose 檔案
- 有 Pull Request 範本
- 閱讀貢獻指南
從這裡開始
- 先讀完整個 Issue,再讀專案的貢獻指南。
- 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
- Fork 儲存庫,在一個分支上完成修改。
- 送出 Pull Request,並在描述裡引用這個 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
dekobon/big-code-analysis#1913 ·
維護者通常 1 天內回覆
-
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 小時 新手友好度 70/100
維護者通常 1 天內回覆