[client-v2] Enum8/Enum16 insert accepts a non-member integer and uses Enum.ordinal(), silently storing values that are not enum members
Maintainer thường phản hồi trong vòng 1 ngà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
- 78/100
Hướng nghiên cứu
Bắt đầu tại client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/SerializerUtils.java ở serializeEnumData(), sau đó đọc các phương thức xác thực và tra cứu trong ClickHouseEnum. Kiểm tra các bài kiểm thử tuần tự hóa enum hiện có và bổ sung kiểm thử cho một Number không phải thành viên và một hằng enum Java có ordinal khác với giá trị ClickHouse đã khai báo. Công việc hoàn tất khi các số không hợp lệ bị từ chối và tên enum Java được ánh xạ tới các giá trị đã khai báo; chạy các bài kiểm thử client-v2 liên quan.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Description
SerializerUtils.serializeEnumData() validates only the String form of an Enum8/Enum16 value. The other two accepted forms are written to the wire unchecked:
Number—((Number) value).intValue()is written as-is, with no check that the integer is a declared member of the column's enum.Enum<?>—((Enum<?>) value).ordinal()is written. Java'sordinal()is a 0-based declaration index and has nothing to do with the ClickHouse enum's declared numeric values, so a Java enum constant almost always maps to the wrong value (and typically to0, which is usually not a member at all).
The server does not validate enum values on RowBinary input, so the row is accepted and stored. The invalid value then breaks reads: any text/JSON rendering of the column fails server-side with UNKNOWN_ELEMENT_OF_ENUM, and the row is effectively unrecoverable except by CAST(col, 'Int8').
Note the String path is already correct — ClickHouseEnum.value(String) throws IllegalArgumentException: Unknown enum name: .... This report is only about the numeric and Java-enum paths.
This is the clickhouse-java counterpart of https://github.com/ClickHouse/clickhouse-connect/issues/1156, where the same class of silent corruption happens (there via an unknown enum name defaulting to 0).
ClickHouse server version
26.10.3.7 (verified against a running server over HTTP).
Reproduction
TestNG test placed in client-v2/src/test/java/com/clickhouse/client/datatypes/:
package com.clickhouse.client.datatypes;
import com.clickhouse.client.api.Client;
import com.clickhouse.client.api.data_formats.RowBinaryFormatWriter;
import com.clickhouse.client.api.enums.Protocol;
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.UUID;
import java.util.concurrent.TimeUnit;
public class EnumInsertValidationTest {
public enum Status { active, inactive }
@Test
public void enumInsertAcceptsNonMemberValues() throws Exception {
Client client = new Client.Builder()
.addEndpoint(Protocol.HTTP, "localhost", 8123, false)
.setUsername("default").setPassword("")
.setDefaultDatabase("default")
.build();
String table = "enum_validation_" + UUID.randomUUID().toString().replace('-', '_');
client.execute("DROP TABLE IF EXISTS " + table).get(30, TimeUnit.SECONDS);
client.execute("CREATE TABLE " + table
+ " (id UInt8, status Enum8('active' = 1, 'inactive' = 2)) ENGINE = Memory")
.get(30, TimeUnit.SECONDS);
TableSchema schema = client.getTableSchema(table);
ClickHouseFormat format = ClickHouseFormat.RowBinary;
// (a) integer that is not a member of the enum -- expected to be rejected
try (InsertResponse r = client.insert(table, out -> {
RowBinaryFormatWriter w = new RowBinaryFormatWriter(out, schema, format);
w.setValue(schema.nameToColumnIndex("id"), 2);
w.setValue(schema.nameToColumnIndex("status"), 7);
w.commitRow();
}, format, new InsertSettings()).get(30, TimeUnit.SECONDS)) {
System.out.println("non-member int accepted, rows=" + r.getWrittenRows());
}
// (b) Java enum constant -- serialized via ordinal(), not via the declared value
try (InsertResponse r = client.insert(table, out -> {
RowBinaryFormatWriter w = new RowBinaryFormatWriter(out, schema, format);
w.setValue(schema.nameToColumnIndex("id"), 3);
w.setValue(schema.nameToColumnIndex("status"), Status.active);
w.commitRow();
}, format, new InsertSettings()).get(30, TimeUnit.SECONDS)) {
System.out.println("Java enum accepted, rows=" + r.getWrittenRows());
}
for (GenericRecord rec : client.queryAll(
"SELECT id, CAST(status, 'Int8') AS raw FROM " + table + " ORDER BY id")) {
System.out.println("row id=" + rec.getInteger("id") + " raw=" + rec.getInteger("raw"));
}
// Reading the enum as text fails server-side: the stored values are not members.
try {
client.queryAll("SELECT toString(status) FROM " + table);
System.out.println("text read OK");
} catch (Exception e) {
System.out.println("text read FAILED: " + e);
}
client.close();
}
}
Actual output
non-member int accepted, rows=1
Java enum accepted, rows=1
row id=2 raw=7
row id=3 raw=0
text read FAILED: com.clickhouse.client.api.ClientException: Failed to get query response
7 is not a member of Enum8('active' = 1, 'inactive' = 2), and Status.active was written as 0 (its ordinal()) rather than as 1 (its declared value in the column). Both rows are stored, and reading the column as text then fails with UNKNOWN_ELEMENT_OF_ENUM on the server.
For contrast, the equivalent text-format insert is rejected by the server:
INSERT INTO t VALUES (2, 'Active')
-> Unknown element 'Active' for enum, maybe you meant: ['active']
Expected output
Both inserts should fail client-side with an error naming the column and the offending value, in the same style as the existing String path (IllegalArgumentException: Unknown enum name: Active), e.g.:
java.lang.IllegalArgumentException: Unknown enum value: 7
and Status.active should be written as the column's declared value for the name active (i.e. 1), or rejected if the name is not a member.
Suggested fix
client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/SerializerUtils.java, serializeEnumData() (around lines 863-872):
} else if (value instanceof Number) {
enumValue = ((Number) value).intValue(); // not validated
} else if (value instanceof Enum<?>) {
enumValue = ((Enum<?>)value).ordinal(); // ordinal != declared enum value
}
ClickHouseEnum already provides the needed checks and lookups:
validate(int)— throwsIllegalArgumentException("Unknown enum value: ..."); use it for theNumberbranch.value(String)— name to declared value; theEnum<?>branch should usecolumn.getEnumConstants().value(((Enum<?>) value).name())rather thanordinal().
It would also help to include the column name and original type name in the message, as the null check immediately above already does.
Link
Relayed from https://github.com/ClickHouse/clickhouse-connect/issues/1156
Central tracking issue: https://github.com/ClickHouse/integrations-ai-playground/issues/649
- Ngôn ngữ chính
- Java
- Star
- 1.6k
- Fork
- 649
- Merge trung bình
- 1 ngày 20 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
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
ClickHouse/clickhouse-java#3349 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
bug jdbc-v2
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 85/100
ClickHouse/clickhouse-java#3347 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
bug jdbc-v2
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
ClickHouse/clickhouse-java#3326 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
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
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
ClickHouse/clickhouse-java#3212 ·
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ự
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
Maintainer thường phản hồi trong vòng 1 ngày
-
bug:unconfirmed
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
UniversalMediaServer/UniversalMediaServer#6473 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 82/100
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
StarRocks/starrocks#83246 · 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 76/100
Maintainer thường phản hồi trong vòng 2 ngày