Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ Compatibility-sensitive traits:
- Nested columns: Un-flattened `Nested(f1 T1, ..., fN TN)` columns (tables created with `flatten_nested = 0`) are exposed as JDBC `ARRAY` whose element type is `Tuple(f1 T1, ..., fN TN)`. They can be inserted through `Connection#createArrayOf`/`setArray` or `setObject` (a Java array of tuples) and read back through `getArray`/`getObject`; `java.sql.Array#getResultSet()` iterates the nested rows as `(INDEX, VALUE)` pairs where each `VALUE` is the tuple.
- Geometry type mapping: For ClickHouse `25.11+`, where `Geometry` changed from a string alias to `Variant(Point, Ring, LineString, MultiLineString, Polygon, MultiPolygon)`, JDBC exposes `Geometry` as `ARRAY`, returns nested Java arrays from `getObject()`/`getArray()`, and accepts `Struct` or nested `Array` inputs for prepared-statement inserts depending on the geometry shape. For ClickHouse `26.8+` the variant also carries `MultiPoint`.
- MultiPoint type mapping: For ClickHouse `26.8+`, JDBC exposes `MultiPoint` as `ARRAY` with type name `MultiPoint`, returns `double[][]` from `getObject()` and a `java.sql.Array` from `getArray()`, and accepts a nested `Array` (`createArrayOf("Array(Point)", ...)`) for prepared-statement inserts, the same as `Ring` and `LineString`.
- IP address type mapping: JDBC exposes `IPv4` and `IPv6` as `OTHER`. `getObject()` returns a `java.net.Inet4Address` for `IPv4` and a `java.net.InetAddress` for `IPv6` (an `Inet6Address`, or an `Inet4Address` for an IPv4-mapped value such as `::ffff:10.0.0.1`). `ResultSetMetaData#getColumnClassName` reports `java.net.Inet4Address` and `java.net.InetAddress` respectively, also for the `VALUE` column of `java.sql.Array#getResultSet()`, and `java.sql.Array#getArray()` returns an `Inet4Address[]` or an `InetAddress[]`.
- Client info propagation: Supports JDBC client info such as `ApplicationName` and forwards it to the underlying client name.
- Wrapper support: Implements standard JDBC `Wrapper` and `unwrap` behavior on major JDBC objects.
- Packaging and runtime compatibility: Ships as a JDBC 4.2 driver, depends on `client-v2`, and includes native-image metadata for GraalVM users.
Expand Down
9 changes: 9 additions & 0 deletions history/latest/3189.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
- **[jdbc-v2]** Fixed `ResultSetMetaData#getColumnClassName` for `IPv4` and `IPv6` columns, which returned
`java.lang.Object`. Now `IPv4` reports `java.net.Inet4Address` and `IPv6` reports `java.net.InetAddress`, the
classes of the values that `ResultSet#getObject` returns. `IPv6` reports `InetAddress` (not `Inet6Address`) because
an IPv4-mapped value (`::ffff:a.b.c.d`) is returned as an `Inet4Address`. `getColumnType` stays `Types.OTHER`, and
`ResultSet#getObject` returns the same values as before. Array elements use the same classes: the `VALUE` column of
`java.sql.Array#getResultSet()` reports the same class name, and `java.sql.Array#getArray()` now returns an
`Inet4Address[]` / `InetAddress[]` instead of an `Object[]`. `getObject(int, Class)` on the result set of a
`java.sql.Array` now also accepts a superclass or an interface of the element value (for example,
`InetAddress.class`). (https://github.com/ClickHouse/clickhouse-java/issues/3189)
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,12 @@ private static Map<ClickHouseDataType, Class<?>> getDataTypeClassMap() {
map.put(e.getKey(), UUID.class);
break;
case IPv4:
map.put(e.getKey(), Inet4Address.class);
break;
case IPv6:
// IPv4-mapped values (::ffff:a.b.c.d) are read as Inet4Address.
map.put(e.getKey(), InetAddress.class);
break;
case Geometry:
// should be mapped to Object because require conversion.
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ private Map<Class<?>, Function<Object, Object>> initValueConverterMapIfNeeded(Ob
}

private Object convertValue(Object value, Class<?> targetType, Map<Class<?>, Function<Object, Object>> valueConverterMap) throws SQLException {
if (value == null || targetType == value.getClass() || targetType == Object.class) {
if (value == null || (targetType != null && targetType.isInstance(value))) {
return value;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
Expand Down Expand Up @@ -268,6 +271,46 @@ public void testGetPrecisionAndScaleOfWrappedTypes(String expression, int precis
}
}

@DataProvider(name = "ipAddressTypes")
public static Object[][] ipAddressTypesProvider() {
return new Object[][] {
{ "toIPv4('10.0.0.1')", "IPv4", Inet4Address.class, Inet4Address.class },
{ "toNullable(toIPv4('10.0.0.1'))", "Nullable(IPv4)", Inet4Address.class, Inet4Address.class },
{ "toLowCardinality(toIPv4('10.0.0.1'))", "LowCardinality(IPv4)", Inet4Address.class, Inet4Address.class },
{ "toIPv6('2001:db8::1')", "IPv6", InetAddress.class, Inet6Address.class },
{ "toNullable(toIPv6('2001:db8::1'))", "Nullable(IPv6)", InetAddress.class, Inet6Address.class },
{ "toLowCardinality(toIPv6('2001:db8::1'))", "LowCardinality(IPv6)", InetAddress.class, Inet6Address.class },
{ "toIPv6('::ffff:10.0.0.1')", "IPv6", InetAddress.class, Inet4Address.class },
};
}

@Test(groups = { "integration" }, dataProvider = "ipAddressTypes")
public void testGetColumnClassNameOfIpAddressTypes(String expression, String typeName, Class<?> columnClass,
Class<?> valueClass) throws Exception {
try (Connection conn = getJdbcConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT " + expression + " AS ip, [" + expression + "] AS ips")) {
ResultSetMetaData rsmd = rs.getMetaData();
assertEquals(rsmd.getColumnTypeName(1), typeName);
assertEquals(rsmd.getColumnType(1), Types.OTHER);
assertEquals(rsmd.getColumnClassName(1), columnClass.getName());

assertTrue(rs.next());
Object value = rs.getObject(1);
assertEquals(value.getClass(), valueClass);
assertTrue(Class.forName(rsmd.getColumnClassName(1)).isInstance(value));

ResultSet elements = rs.getArray(2).getResultSet();
ResultSetMetaData elementsMetaData = elements.getMetaData();
assertEquals(elementsMetaData.getColumnTypeName(2), typeName);
assertEquals(elementsMetaData.getColumnType(2), Types.OTHER);
assertEquals(elementsMetaData.getColumnClassName(2), columnClass.getName());
assertTrue(elements.next());
assertEquals(elements.getObject(2), value);
assertEquals(elements.getObject(2).getClass(), valueClass);
}
}

@Test(groups = { "integration" })
public void testColumnNamesStrippedFromTablePrefix() throws Exception {
final String t1 = "rsmd_test_prefix_t1";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import java.io.StringReader;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.URL;
import java.sql.Blob;
import java.sql.Clob;
Expand Down Expand Up @@ -297,6 +299,24 @@ void testStringValues() throws SQLException {
}
}

@Test
void testInetAddressValues() throws Exception {
InetAddress[] array = new InetAddress[] {
InetAddress.getByName("2001:db8::1"), InetAddress.getByName("10.0.0.1"), null};
ArrayResultSet rs = new ArrayResultSet(array, ClickHouseColumn.parse("v Array(Nullable(IPv6))").get(0));

assertEquals(rs.getMetaData().getColumnClassName(2), InetAddress.class.getName());
for (InetAddress value : array) {
assertTrue(rs.next());
assertEquals(rs.getObject(2), value);
assertEquals(rs.getObject(2, InetAddress.class), value);
assertEquals(rs.getObject(2, Object.class), value);
}

rs.absolute(1);
expectThrows(SQLException.class, () -> rs.getObject(2, Inet4Address.class));
}

@Test
void testEmptyArray() throws SQLException {
ArrayResultSet rs = new ArrayResultSet(new Object[0], ClickHouseColumn.parse("v Array(Int32)").get(0));
Expand Down
Loading