Repository navigation
Description
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 ClickHouse/clickhouse-connect#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 ClickHouse/clickhouse-connect#1156
Central tracking issue: https://github.com/ClickHouse/integrations-ai-playground/issues/649
Activity
jaya232 commentedon Nov 7, 2026
Hi, thanks for the detailed report. I'd like to work on this issue.
[client-v2] Validate Enum8/Enum16 values on insert (ClickHouse#3304)