[client-v2] Enum type parsing accepts duplicate ordinals and duplicate labels: Enum8('a' = 1, 'b' = 1) silently stores 'b' as 'a'
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
- 76/100
Hướng nghiên cứu
Bắt đầu từ clickhouse-data/src/main/java/com/clickhouse/data/ClickHouseEnum.java, nơi constructor Collection<String> (khoảng dòng 37-53) và constructor (String[], int[]) (khoảng dòng 55-65) đang chấp nhận ordinal và nhãn bị trùng. Thêm kiểm tra ở cả hai constructor, ném IllegalArgumentException và nêu tên kiểu khi một ordinal hoặc nhãn bị lặp lại. Hoàn thành khi ClickHouseColumn.parse từ chối Enum8('a' = 1, 'b' = 1) và Enum8('a' = 1, 'a' = 2), còn Enum8('a' = 200, 'b' = 1) vẫn được phân tích thành công.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Description
ClickHouseColumn.parse(...) / ClickHouseEnum accept an Enum type definition in which two members share an ordinal (Enum8('a' = 1, 'b' = 1)) or share a label (Enum8('a' = 1, 'a' = 2)). The server rejects both (Code: 62 ... Duplicate values in enum / Duplicate names in enum).
com.clickhouse.data.ClickHouseEnum stores the members in two parallel arrays and does no uniqueness check at all:
ClickHouseEnum(Collection<String> params)(clickhouse-data/src/main/java/com/clickhouse/data/ClickHouseEnum.java:37-53) only validates that each entry contains=.value(String name)/name(int value)(lines 87-115) are first-match linear scans.
Consequence for a duplicate ordinal: value("a") == value("b") == 1, so both labels serialize to the same byte in SerializerUtils.serializeEnumData (client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/SerializerUtils.java:858-880), and name(1) only ever returns "a" — "b" is unreachable.
This is reachable whenever the application supplies the column type itself rather than letting the client probe the server: a TableSchema built from ClickHouseColumn.parse(...) and passed to Client.register(Class, TableSchema), then used for a RowBinary insert. The insert succeeds with no error and the wrong value lands in the table.
Impact is low — the server never emits such a type, so reads and server-probed insert schemas are unaffected. But the failure mode is silent data corruption rather than an error.
Found while checking whether ClickHouse/clickhouse-cs#731 also applies here. Distinct from #3304 (non-member integer / Enum.ordinal()).
ClickHouse server version
26.10.3.7 (verified against a running server; client built from main, version 0.12.0-rc1-SNAPSHOT).
The server rejects the type on every version:
SELECT CAST('a' AS Enum8('a' = 1, 'b' = 1))
Code: 62. DB::Exception: Duplicate values in enum: 'b' = 1 and 'a' ... (SYNTAX_ERROR)
Reproduction
client-v2/src/test/java/com/clickhouse/client/insert/EnumDupOrdinalTest.java:
package com.clickhouse.client.insert;
import com.clickhouse.client.api.Client;
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.ClickHouseColumn;
import org.testng.annotations.Test;
import java.util.ArrayList;
import java.util.List;
public class EnumDupOrdinalTest {
public static class Row {
private short id;
private String e;
public short getId() { return id; }
public void setId(short id) { this.id = id; }
public String getE() { return e; }
public void setE(String e) { this.e = e; }
}
@Test
public void duplicateOrdinalIsAccepted() throws Exception {
ClickHouseColumn c = ClickHouseColumn.parse("e Enum8('a' = 1, 'b' = 1)").get(0);
System.out.println("parsed OK (duplicate ordinal): " + c.getOriginalTypeName());
System.out.println(" value('a') = " + c.getEnumConstants().value("a"));
System.out.println(" value('b') = " + c.getEnumConstants().value("b"));
System.out.println(" name(1) = " + c.getEnumConstants().name(1));
ClickHouseColumn c2 = ClickHouseColumn.parse("e Enum8('a' = 1, 'a' = 2)").get(0);
System.out.println("parsed OK (duplicate label): " + c2.getOriginalTypeName());
System.out.println(" value('a') = " + c2.getEnumConstants().value("a"));
try (Client client = new Client.Builder().addEndpoint("http://localhost:8123")
.setUsername("default").setPassword("").compressServerResponse(false).build()) {
String table = "enum_dup_ordinal_test";
client.execute("DROP TABLE IF EXISTS " + table).get();
client.execute("CREATE TABLE " + table
+ " (id UInt8, e Enum8('a' = 1, 'b' = 2)) ENGINE = Memory").get();
// application-supplied schema, with a type the server would reject
TableSchema schema = new TableSchema(table, null, "default",
ClickHouseColumn.parse("id UInt8, e Enum8('a' = 1, 'b' = 1)"));
client.register(Row.class, schema);
List<Object> data = new ArrayList<>();
Row r1 = new Row(); r1.setId((short) 1); r1.setE("a");
Row r2 = new Row(); r2.setId((short) 2); r2.setE("b");
data.add(r1);
data.add(r2);
try (InsertResponse resp = client.insert(table, data, new InsertSettings()).get()) {
System.out.println("insert succeeded, rows written = " + resp.getWrittenRows());
}
for (GenericRecord rec : client.queryAll(
"SELECT concat(toString(id), '|', toString(e), '|', toString(toInt16(e))) AS r FROM "
+ table + " ORDER BY id")) {
System.out.println(" row: " + rec.getString("r"));
}
client.execute("DROP TABLE IF EXISTS " + table).get();
}
}
}
Run: mvn -pl client-v2 -Dtest=EnumDupOrdinalTest test
Actual output (target/surefire-reports/...-output.txt):
parsed OK (duplicate ordinal): Enum8('a' = 1, 'b' = 1)
value('a') = 1
value('b') = 1
name(1) = a
parsed OK (duplicate label): Enum8('a' = 1, 'a' = 2)
value('a') = 1
server version: 26.10.3.7
server rejected: SELECT CAST('a' AS Enum8('a' = 1, 'b' = 1)) <- server refuses the same type
insert succeeded, rows written = 2
row: 1|a|1
row: 2|a|1 <- 'b' was written as ordinal 1 and reads back as 'a'
Expected: ClickHouseColumn.parse / the ClickHouseEnum constructor reject both type strings with an exception naming the offending type, as the server does. The row written as 'b' should either fail or be stored as 'b'.
Suggested fix
In com.clickhouse.data.ClickHouseEnum (clickhouse-data/src/main/java/com/clickhouse/data/ClickHouseEnum.java), validate in the Collection<String> constructor (lines 37-53) and in the (String[], int[]) constructor (lines 55-65) that no ordinal and no label repeats, throwing IllegalArgumentException that names the type, e.g. Enum declares the ordinal 1 more than once / Enum declares the label 'a' more than once.
Note the contrast case that must keep working: an explicit out-of-range ordinal (Enum8('a' = 200, 'b' = 1)) is accepted and wrapped by older servers, so no range check should be added here.
Link
Relayed from https://github.com/ClickHouse/clickhouse-cs/issues/731
Tracking: https://github.com/ClickHouse/integrations-ai-playground/issues/693
- 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
-
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
-
[client-v2] Enum8/Enum16 insert accepts a non-member integer and uses Enum.ordinal(), silently storing values that are not enum membersCó thể đã có người làm @jaya232 đã nhận 6 ngày trước. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
ClickHouse/clickhouse-java#3304 · 1 bình luận ·
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