Skip to content

Commit 34a10d6

Browse files
committed
Fixed ResultSet.getObject() for StringValue
1 parent db53728 commit 34a10d6

4 files changed

Lines changed: 64 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,11 @@ of `NULL` was not set and read. (https://github.com/ClickHouse/clickhouse-java/i
157157

158158
- **[jdbc-v2, client-v2]** Fixed writing nullable marker for nested `Tuple` and `Map values. (https://github.com/ClickHouse/clickhouse-java/issues/2721)
159159

160+
- **[jdbc-v2]** Fixed `ResultSet.getObject` leaking the internal `StringValue` holder for `String`/`FixedString`
161+
columns when `binary_string_support` is enabled. `getObject(column, byte[].class)` now returns the exact raw bytes,
162+
and `getObject(column, Object.class)` and the no-type `getObject(column)` overloads now return a decoded `String`
163+
instead of the internal holder.
164+
160165
## 0.9.8
161166

162167
### Improvements

docs/features.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ Compatibility-sensitive traits:
6868
- SQL parsing and classification: Classifies SQL to distinguish queries, updates, inserts, `USE`, and role-changing statements, with selectable parser backends.
6969
- JDBC escape processing: Translates supported JDBC escape syntax for dates, timestamps, and functions before execution.
7070
- Result set streaming: Streams result sets from ClickHouse binary formats and `FORMAT JSONEachRow`, enforces max-row limits, and manages result-set lifecycle correctly.
71-
- Binary string reads: `ResultSet#getBytes(int|String)` and `ResultSet#getBinaryStream(int|String)` return the raw bytes of a `String`/`FixedString` column. Combined with the `binary_string_support` connection property, non-UTF-8/binary content stored in `String` columns round-trips byte-for-byte; `NULL` values report `null` with `wasNull()` set.
71+
- Binary string reads: `ResultSet#getBytes(int|String)` and `ResultSet#getBinaryStream(int|String)` return the raw bytes of a `String`/`FixedString` column. Combined with the `binary_string_support` connection property, non-UTF-8/binary content stored in `String` columns round-trips byte-for-byte; `NULL` values report `null` with `wasNull()` set. `ResultSet#getObject(...)` never exposes the internal `StringValue` holder for these columns: `getObject(column, byte[].class)` returns the raw bytes, while `getObject(column, Object.class)` and the no-type `getObject(column)` overloads return a decoded `String`.
7272
- Result-set metadata: Exposes JDBC `ResultSetMetaData` backed by ClickHouse column schema.
7373
- Database metadata: Implements JDBC `DatabaseMetaData` for ClickHouse catalogs, schemas, tables, columns, and related capability reporting.
7474
- Parameter metadata: Reports prepared-statement parameter counts.

jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcUtils.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.clickhouse.client.api.DataTypeUtils;
44
import com.clickhouse.client.api.data_formats.internal.BinaryStreamReader;
55
import com.clickhouse.client.api.data_formats.internal.InetAddressConverter;
6+
import com.clickhouse.client.api.data_formats.internal.StringValue;
67
import com.clickhouse.data.ClickHouseColumn;
78
import com.clickhouse.data.ClickHouseDataType;
89
import com.clickhouse.data.Tuple;
@@ -12,6 +13,7 @@
1213
import java.net.Inet4Address;
1314
import java.net.Inet6Address;
1415
import java.net.InetAddress;
16+
import java.nio.charset.Charset;
1517
import java.nio.charset.StandardCharsets;
1618
import java.sql.Date;
1719
import java.sql.JDBCType;
@@ -291,6 +293,11 @@ public static Object convert(Object value, Class<?> type, ClickHouseColumn colum
291293
return value;
292294
}
293295

296+
// Special case
297+
if (value instanceof StringValue && type == Object.class) {
298+
return ((StringValue)value).asString();
299+
}
300+
294301
type = unwrapPrimitiveType(type);
295302
if (type.isInstance(value)) {
296303
return value;
@@ -344,6 +351,12 @@ static Object convertObject(Object value, Class<?> type, ClickHouseColumn column
344351
try {
345352
if (type == String.class) {
346353
return value.toString();
354+
} else if (type == byte[].class && value instanceof StringValue) {
355+
// Raw bytes of a String/FixedString column read as a StringValue (binary_string_support).
356+
// String and numeric targets already work through the value.toString() branches below.
357+
return ((StringValue) value).toByteArray();
358+
} else if (type == byte[].class && value instanceof String) {
359+
return ((String)value).getBytes(StandardCharsets.UTF_8);
347360
} else if (type == Boolean.class) {
348361
String str = value.toString();
349362
return !("false".equalsIgnoreCase(str) || "0".equalsIgnoreCase(str));

jdbc-v2/src/test/java/com/clickhouse/jdbc/JdbcDataTypeTests.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1539,6 +1539,7 @@ public void testStringsUsedAsBytes() throws Exception {
15391539
for (String[] expected : testData) {
15401540
assertTrue(rs.next());
15411541
assertEquals(new String(rs.getBytes("str"), "UTF-8"), expected[0]);
1542+
assertEquals(new String(rs.getObject("str", byte[].class), "UTF-8"), expected[0]);
15421543
assertEquals(new String(rs.getBytes("fixed"), "UTF-8").replace("\0", ""), expected[1]);
15431544
}
15441545
assertFalse(rs.next());
@@ -1634,6 +1635,50 @@ public void testBinaryStringSupportGetBinaryStream() throws Exception {
16341635
}
16351636
}
16361637

1638+
@Test(groups = { "integration" })
1639+
public void testBinaryStringSupportGetObject() throws Exception {
1640+
// With binary_string_support enabled the read path returns an internal StringValue holder for
1641+
// String/FixedString columns. getObject must never leak that holder: it should return a decoded
1642+
// String for Object.class and the no-type overload, and exact raw bytes for byte[].class.
1643+
runQuery("CREATE TABLE test_binary_string_get_object (id Int8, str String, txt String) ENGINE = MergeTree ORDER BY ()");
1644+
1645+
String text = "Hello, ClickHouse!";
1646+
try (Connection conn = getJdbcConnection();
1647+
PreparedStatement insert = conn.prepareStatement("INSERT INTO test_binary_string_get_object VALUES (?, ?, ?)")) {
1648+
insert.setInt(1, 1);
1649+
insert.setBytes(2, CH_LOGO_PNG);
1650+
insert.setString(3, text);
1651+
insert.executeUpdate();
1652+
}
1653+
1654+
Properties props = new Properties();
1655+
props.put(ClientConfigProperties.BINARY_STRING_SUPPORT.getKey(), "true");
1656+
1657+
try (Connection conn = getJdbcConnection(props);
1658+
Statement stmt = conn.createStatement();
1659+
ResultSet rs = stmt.executeQuery("SELECT * FROM test_binary_string_get_object ORDER BY id")) {
1660+
assertTrue(rs.next());
1661+
1662+
// byte[].class must return the exact bytes without lossy decoding
1663+
Object bytesObj = rs.getObject("str", byte[].class);
1664+
assertTrue(bytesObj instanceof byte[], "getObject(byte[].class) should return byte[], got: " + bytesObj.getClass().getName());
1665+
assertEqualsToClickHouseLogo((byte[]) bytesObj);
1666+
assertFalse(rs.wasNull());
1667+
1668+
// Object.class must return a decoded String, not the internal StringValue holder
1669+
Object textObj = rs.getObject("txt", Object.class);
1670+
assertTrue(textObj instanceof String, "getObject(Object.class) should return String, got: " + textObj.getClass().getName());
1671+
assertEquals(textObj, text);
1672+
1673+
// The no-type overload must also return a String
1674+
Object defaultObj = rs.getObject("txt");
1675+
assertTrue(defaultObj instanceof String, "getObject() should return String, got: " + defaultObj.getClass().getName());
1676+
assertEquals(defaultObj, text);
1677+
1678+
assertFalse(rs.next());
1679+
}
1680+
}
1681+
16371682
@Test(groups = { "integration" })
16381683
public void testNestedArrays() throws Exception {
16391684
try (Connection conn = getJdbcConnection()) {

0 commit comments

Comments
 (0)