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
7 changes: 4 additions & 3 deletions docs/clickhouse-docs/jdbc.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -619,14 +619,14 @@ try (Connection conn = ...) {
|-----------------------------|------------------------------|-----------------------------|
| UUID | OTHER | java.util.UUID |
| IPv4 | OTHER | java.net.Inet4Address |
| IPv6 | OTHER | java.net.Inet6Address |
| IPv6 | OTHER | java.net.InetAddress |
| JSON | OTHER | java.lang.String |
| AggregateFunction | OTHER | (binary representation) |
| SimpleAggregateFunction | (wrapped type) | (wrapped class) |

- `UUID` isn't JDBC standard type. However it is part of JDK. By default `java.util.UUID` is returned on `getObject()` method.
- `UUID` can be read/written as `String` by using `getObject(columnIndex, String.class)` method.
- `IPv4` and `IPv6` aren't JDBC standard types. However they're part of JDK. By default `java.net.Inet4Address` and `java.net.Inet6Address` are returned on `getObject()` method.
- `IPv4` and `IPv6` aren't JDBC standard types. However they're part of JDK. By default `java.net.Inet4Address` and `java.net.Inet6Address` are returned on `getObject()` method. An IPv4-mapped `IPv6` value (`::ffff:a.b.c.d`) is returned as a `java.net.Inet4Address`. For this reason `ResultSetMetaData#getColumnClassName` reports `java.net.InetAddress` for `IPv6`.
- `IPv4` and `IPv6` can be read/written as `String` by using `getObject(columnIndex, String.class)` method.

**JSON Type**
Expand Down Expand Up @@ -1023,12 +1023,13 @@ try (Connection conn = DriverManager.getConnection(url, properties)) {
| SimpleAggregateFunction | ✅ | (wrapped type) | (wrapped class) | (wrapped type) | (wrapped class) |
| UUID | ✅ | OTHER | java.util.UUID | VARCHAR | java.util.UUID |
| IPv4 | ✅ | OTHER | java.net.Inet4Address | VARCHAR | java.net.Inet4Address |
| IPv6 | ✅ | OTHER | java.net.Inet6Address | VARCHAR | java.net.Inet6Address |
| IPv6 | ❌ | OTHER | java.net.InetAddress | VARCHAR | java.net.Inet6Address |
| Dynamic | ❌ | OTHER | java.Object | not supported | not supported |
| Variant | ❌ | OTHER | java.Object | not supported | not supported |

- V1 uses `VARCHAR` for `UUID` but returns `java.util.UUID` object always. V2 fixes this by mapping `UUID` to `OTHER` and returns `java.util.UUID` object.
- V1 uses `VARCHAR` for `IPv4` and `IPv6` but returns `java.net.Inet4Address` and `java.net.Inet6Address` objects always. V2 fixes this by mapping `IPv4` and `IPv6` to `OTHER` and returns `java.net.Inet4Address` and `java.net.Inet6Address` objects.
- V2 returns an IPv4-mapped `IPv6` value (`::ffff:a.b.c.d`) as a `java.net.Inet4Address`, V1 returns it as a `java.net.Inet6Address`. Use `getObject(columnIndex, Inet6Address.class)` to always get a `java.net.Inet6Address`.
- `Dynamic` and `Variant` are new types in V2. Not supported in V1.
- `JSON` is based on `Dynamic` type. Therefore it is supported only in V2.
- IPv4 and IPv6 values can be read as `byte[]` by using `getBytes(columnIndex)` method. However it is recommended to use designated classes for these types.
Expand Down
1 change: 1 addition & 0 deletions docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,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. An `Array(Nested(...))` column is read as a JDBC `ARRAY` whose elements are the nested values, each an array of tuples (`Object[]` of `Object[]`); its `getResultSet()` returns each element as a `java.sql.Array` equal to a top-level `Nested(...)` value.
- 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. The `client_id_metadata` connection property is forwarded to the client and adds key/value pairs to the `User-Agent` header (see the `client-v2` `User-Agent` entry).
- 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