JDBC driver

- numeric types are interconvertible. So `Int8` can be get as `Float64` and vice versa.:
  - `rs.getObject(1, Float64.class)` will return `Float64` value of `Int8` column.
  - `rs.getLong(1)` will return `Long` value of `Int8` column.
  - `rs.getByte(1)` can return `Byte` value of `Int16` column if it fits into `Byte`.
- conversion from wider to narrower type isn't recommend because of data coruption risk.
- `Bool` type acts as number, too.
- All number types can be read as `java.lang.String`.
- Storing java `Float.MAX_VALUE` as `Float` has issue (<https://github.com/ClickHouse/clickhouse-java/issues/809>). Saving same value as `Double` solves the issue.

**String Types**

|  |  |  |  |  |  |  |  |  |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| ClickHouse Type JDBC Type Java Class|  |  |  |  |  |  | | --- | --- | --- | --- | --- | --- | | String VARCHAR java.lang.String|  |  |  | | --- | --- | --- | | FixedString VARCHAR java.lang.String | | | | | | | | |

- `String` can be read only as `java.lang.String` or `byte[]`.
- `FixedString` is read as is and will be padded with zeros to the length of the column. (For example `FixedString(10)` for `'John'` will be read as `'John\0\0\0\0\0\0\0\0\0'`.)

**Enum Types**

|  |  |  |  |  |  |  |  |  |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| ClickHouse Type JDBC Type Java Class|  |  |  |  |  |  | | --- | --- | --- | --- | --- | --- | | Enum8 OTHER java.lang.String|  |  |  | | --- | --- | --- | | Enum16 OTHER java.lang.String | | | | | | | | |

- `Enum8` and `Enum16` are mapped to `java.lang.String` by default.
- Enum values can be read as numeric values using designtated getter method or `getObject(columnIndex, Integer.class)` method.
- `Enum16` is mapped to short and Enum8 is mapped to byte internally. Reading `Enum16` as byte should be avoided because of data coruption risk.
- Enum values can be set as string or numeric value in `PreparedStatement`.

**Date/Time Types**

|  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| ClickHouse Type JDBC Type Java Class|  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | Date DATE java.sql.Date|  |  |  |  |  |  |  |  |  |  |  |  |  |  |  | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | Date32 DATE java.sql.Date|  |  |  |  |  |  |  |  |  |  |  |  | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | DateTime TIMESTAMP java.sql.Timestamp|  |  |  |  |  |  |  |  |  | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | DateTime64 TIMESTAMP java.sql.Timestamp|  |  |  |  |  |  | | --- | --- | --- | --- | --- | --- | | Time TIME java.sql.Time|  |  |  | | --- | --- | --- | | Time64 TIME java.sql.Time | | | | | | | | | | | | | | | | | | | | |

- Date / Time types are mapped to `java.sql` types for better compatibility with JDBC. However getting `java.time.LocalDate`, `java.time.LocalDateTime`, `java.time.LocalTime` is possible by using `ResultSet#getObject(columnIndex, Class<T>)` with the corresponding class as the second argument.
  - `rs.getObject(1, java.time.LocalDate.class)` will return `java.time.LocalDate` value of `Date` column.
  - `rs.getObject(1, java.time.LocalDateTime.class)` will return `java.time.LocalDateTime` value of `DateTime` column.
  - `rs.getObject(1, java.time.LocalTime.class)` will return `java.time.LocalTime` value of `Time` column.
- `Date`, `Date32`, `Time`, `Time64` isn't affected by the timezone of the server.
- `DateTime`, `DateTime64` is affected by the timezone of the server or session timezone.
- `DateTime` and `DateTime64` can be retrieved as `ZonedDateTime` by using `getObject(colIndex, ZonedDateTime.class)`.

**Nested Types**

|  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| ClickHouse Type JDBC Type Java Class|  |  |  |  |  |  |  |  |  |  |  |  | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | Array ARRAY java.sql.Array|  |  |  |  |  |  |  |  |  | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | Tuple OTHER com.clickhouse.data.Tuple|  |  |  |  |  |  | | --- | --- | --- | --- | --- | --- | | Map JAVA\_OBJECT java.util.Map|  |  |  | | --- | --- | --- | | Nested ARRAY java.sql.Array | | | | | | | | | | | | | | |

- `Array` is mapped to `java.sql.Array` by default to be compatible with JDBC. This is also done to give more information about returned array value. Useful for type inference.
- `Array` implements `getResultSet()` method to return `java.sql.ResultSet` with the same content as the original array.
- Collection types shouldn't be read as `java.lang.String` because it isn't a valid way to represent the data (Ex. there is no quoting for string values in array).
- `Map` is mapped to `JAVA_OBJECT` because value can be read only with `getObject(columnIndex, Class<T>)` method.
  - `Map` isn't a `java.sql.Struct` because it doesn't have named columns.
- `Tuple` is mapped to `Object[]` because it can contain different types and using `List` isn't valid.
- `Tuple` can be read as `Array` by using `getObject(columnIndex, Array.class)` method. In this case `Array#baseTypeName` will return `Tuple` column definition.

**Writing Arrays**

Use `java.sql.Connection#createArrayOf` to instantiate `java.sql.Array` object. This object is designed to make array handling unified across different databases.
Connection is required to pass configuration to Array factory method.

The method accepts two arguments:

- `typeName` - type name of the array elements. For example `Array(Int32)` -> `"Int32"`.
- `elements` - actual array elements. For example `[[1, 2, 3], [4, 5, 6]]` -> `new Integer[][] {{1, 2, 3}, {4, 5, 6}}`.

Tuple can be presented as `Object[]` or as `java.sql.Struct` (See how to write tuples bellow).

**Example**

```
try (Connection conn = ...) {
    Array array = conn.createArrayOf("Int32", new Integer[][] {{1, 2, 3}, {4, 5, 6}});
    try (PreparedStatement ps = conn.prepareStatement("INSERT INTO mytable (arr) VALUES (?)")) {
        ps.setArray(1, array);
        ps.executeUpdate();
    }
}
```

**Reading Arrays**

Use `ResultSet#getArray(columnIndex)` to read `Array` object. Object can be used to access array of any nested depth.
`Array#getResultSet()` method can be used to read array elements in more unified way as `java.sql.ResultSet`. It is useful
when exact type of array elements is unknown.

**Example**

```
try (Connection conn = ...) {
    try (PreparedStatement ps = conn.prepareStatement("SELECT ?::Array(Int32)")) {
        ps.setArray(1, array);
        try (ResultSet rs = ps.executeQuery()) {
            while (rs.next()) {
                Array array = rs.getArray(1);

                Object[] arr = (Object[]) array;
                Arrays.stream(arr).forEach(this::handleArrayElement);

                // or by using `ResultSet`
                ResultSet resultSet = array.getResultSet();
                while (resultSet.next()) {
                    // ...
                }
            }
        }
    } 
}
```

**Writing Tuples**

Tuples are mapped to `com.clickhouse.data.Tuple` object and should be written as this object by calling `setObject(columnIndex, tuple)` method.
It is possible to use `java.sql.Struct` object to write tuples for better portability.

**Example**

```
try (Connection conn = ...) {
    Tuple tuple = new Tuple(1, "test", LocalDate.parse("2026-03-02"));
    try (PreparedStatement ps = conn.prepareStatement("INSERT INTO mytable (tuple) VALUES (?)")) {
        ps.setObject(1, tuple);
        ps.executeUpdate();
    }
}