ClickHouse / ClickHouse/clickhouse-java

[client-v2] Compiled POJO setter emits invalid bytecode (VerifyError) when the column type does not match the primitive field type

Abierto
#2,999 0 comentarios 0 reacciones 0 asignados Ver en GitHub
Lenguaje dominante
Java
Estrellas
1.6k
Forks
636
Merge medio
2 d 23 h
PR fusionados (30 d)
29

Descripción

### Describe the bug

`client-v2` compiles a POJO field setter into bytecode (`SerializerUtils.compilePOJOSetter`). When the column type
and the POJO field's primitive type do not match exactly, the generated class is invalid and reading fails with
`java.lang.VerifyError` (thrown when the compiled setter class is first linked, i.e. on the first row read).

Two distinct groups of combinations are affected.

**1. The value left on the stack is not converted to the setter's primitive type.**
`binaryReaderMethodForType` only computed a conversion opcode for a few reader/field combinations
(`longToOpcode`, `floatToOpcode` and `doubleToOpcode` return `-1` for `byte`/`short`/`char`/`boolean`, and the
`Int8`/`UInt8`/`Int16`/`Enum8`/`Enum16`/`Bool` branches computed no conversion at all). So, for example:

* an `Int64`, `UInt32`, `Float32`, `Float64` or `BFloat16` column bound to a `byte`, `short`, `char` or `boolean` field
* an `Int8`, `UInt8`, `Int16`, `Enum8`, `Enum16` or `Bool` column bound to a `long`, `float` or `double` field

leave a value of the wrong type on the operand stack for the setter descriptor, and the generated class does not verify.

**2. A primitive field bound to a column that is not read into a primitive.**
The generic branch emits `LDC` of a class constant for the target type and `CHECKCAST` with its internal name.
For a primitive target that produces a class constant named `"I"`/`"J"` and a `CHECKCAST int`, and the value read
by `readValue` (an `Object`) is passed to a primitive setter descriptor. So `Int128`, `UInt128`, `Int256`, `UInt256`
and `Decimal*` columns cannot be read into a primitive field at all, even though the value is a `Number`.

### Steps to reproduce

```java
public class Pojo {
private short v;
public short getV() { return v; }
public void setV(short v) { this.v = v; }
}
```

```java
String sql = "SELECT toInt64(300) AS v"; // also: toFloat64(-2.7), toInt8(-5) into a long field, ...
TableSchema schema = client.getTableSchemaFromQuery(sql);
client.register(Pojo.class, schema);
client.queryAll(sql, Pojo.class, schema); // -> java.lang.VerifyError
```

The same happens with `SELECT toInt128(-2) AS v` or `SELECT toDecimal64(123.45, 2) AS v` bound to a `long`/`double`
field (group 2).

### Expected behaviour

The value is converted to the field's primitive type following Java's narrowing/widening rules (as already happens
for an `Int32` column bound to a `byte` field), and a `Number`-valued column can be read into a primitive field.

### Error log

```
java.lang.VerifyError
at java.base/java.lang.ClassLoader.defineClass1(Native Method)
...
at com.clickhouse.client.api.data_formats.internal.SerializerUtils.compilePOJOSetter(SerializerUtils.java:...)
```

### Configuration

* Client version: `main` (0.11.0-rc1)
* Language: Java
* Client: `client-v2` (POJO binding / `queryAll(sql, Pojo.class, schema)`)

Guía de contribución

Abrir la guía de contribución

Línea de trabajo

Reproduce el fallo con la consulta POJO de client-v2 mostrada y, después, inspecciona SerializerUtils.compilePOJOSetter y binaryReaderMethodForType, incluidas las ramas longToOpcode, floatToOpcode y doubleToOpcode. Se considera terminado cuando los bindings primitivos incompatibles generan setters verificables con conversiones de narrowing o widening de Java, y las columnas con valores Number pueden leerse en campos primitivos sin producir un VerifyError.

Escrito por el modelo de indexación a partir del texto del issue.

Evaluación

Stack tecnológico
java
Área
api
Tipo de issue
Error
Dificultad
4/5
Tiempo estimado
3-5 días
Estado de actividad
Tranquilo
Claridad
Bastante claro
Aptitud para principiantes
58/100

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.