ClickHouse / ClickHouse/clickhouse-java
[client-v2] Compiled POJO setter emits invalid bytecode (VerifyError) when the column type does not match the primitive field type
- Ngôn ngữ chính
- Java
- Star
- 1.6k
- Fork
- 636
- Merge trung bình
- 2 ngày 23 giờ
- Pull request đã merge (30 ngày)
- 29
Mô tả
### 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)`)
Hướng dẫn đóng góp
Hướng nghiên cứu
Tái hiện lỗi bằng truy vấn POJO client-v2 được nêu, sau đó kiểm tra SerializerUtils.compilePOJOSetter và binaryReaderMethodForType, bao gồm các nhánh longToOpcode, floatToOpcode và doubleToOpcode. Được xem là hoàn thành khi các primitive binding không khớp tạo ra các setter có thể được verifier xác minh với các phép chuyển đổi narrowing hoặc widening của Java, và các cột có giá trị Number có thể được đọc vào các field primitive mà không xảy ra VerifyError.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Đánh giá
- Công nghệ
- java
- Lĩnh vực
- api
- Loại issue
- Lỗi
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức độ hoạt động
- Ít trao đổi
- Độ rõ ràng
- Khá rõ ràng
- Mức phù hợp với người mới
- 58/100