[client-v2] Enum8/Enum16 insert accepts a non-member integer and uses Enum.ordinal(), silently storing values that are not enum members
Mantenedores costumam responder em até 1 dia
Avaliação
- Dificuldade
- 2/5
- Tempo estimado
- 1-3 horas
- Facilidade para iniciantes
- 78/100
Direção de pesquisa
Comece em client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/SerializerUtils.java, em serializeEnumData(), e depois leia os métodos de validação e busca em ClickHouseEnum. Verifique os testes existentes de serialização de enum e adicione cobertura para um Number que não seja membro e uma constante de enum Java cujo ordinal seja diferente do valor ClickHouse declarado. O trabalho estará concluído quando números inválidos forem rejeitados e os nomes dos enums Java forem associados aos valores declarados; execute os testes relevantes de client-v2.
Escrita pelo modelo de indexação a partir do texto da issue.
Descrição
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
- Linguagem predominante
- Java
- Estrelas
- 1.6k
- Forks
- 649
- Merge médio
- 1d 20h
- PRs com merge (30d)
- 39
Preparar o ambiente
- Sem Dockerfile nem arquivo Docker Compose
- Tem um modelo de pull request
- Ler o guia de contribuição
Primeiros passos
- Leia a issue inteira e depois o guia de contribuição do projeto.
- Comente na issue dizendo que vai assumir — evita que duas pessoas façam o mesmo trabalho.
- Faça um fork do repositório e trabalhe em uma branch.
- Abra um pull request que referencie o número da issue.
Mais de ClickHouse/clickhouse-java
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 76/100
ClickHouse/clickhouse-java#3349 ·
Mantenedores costumam responder em até 1 dia
-
bug jdbc-v2
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 85/100
ClickHouse/clickhouse-java#3347 ·
Mantenedores costumam responder em até 1 dia
-
bug jdbc-v2
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 82/100
ClickHouse/clickhouse-java#3326 ·
Mantenedores costumam responder em até 1 dia
-
bug jdbc-v2
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 70/100
ClickHouse/clickhouse-java#3214 ·
Mantenedores costumam responder em até 1 dia
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 72/100
ClickHouse/clickhouse-java#3212 ·
Mantenedores costumam responder em até 1 dia
Todas as issues de ClickHouse/clickhouse-java
Issues semelhantes
-
bug
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 78/100
Mantenedores costumam responder em até 1 dia
-
bug:unconfirmed
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 68/100
UniversalMediaServer/UniversalMediaServer#6473 ·
Mantenedores costumam responder em até 1 dia
-
Dificuldade 1/5 Menos de uma hora Facilidade para iniciantes 82/100
Mantenedores costumam responder em até 1 dia
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 72/100
StarRocks/starrocks#83246 · 1 comentário ·
Mantenedores costumam responder em até 1 dia
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 76/100
Mantenedores costumam responder em até 2 dias