Skip to content

Commit 98ac1a7

Browse files
committed
docs(jdbc-v2): document raw FORMAT JSON access via client
Document how jdbc-v2 callers can unwrap ConnectionImpl and use the underlying client to consume document-oriented FORMAT JSON responses through QueryResponse. Add integration coverage for the supported path while preserving existing ResultSet format constraints. Refs #2715
1 parent fe86ee2 commit 98ac1a7

4 files changed

Lines changed: 64 additions & 2 deletions

File tree

docs/client-v2-json-support.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,34 @@ Notes:
352352

353353
## Usage in `jdbc-v2`
354354

355+
### Document-oriented JSON output
356+
357+
The standard ClickHouse `FORMAT JSON` response is a single JSON document that
358+
contains metadata, data, row counts, and statistics together. It is not mapped
359+
to a JDBC `ResultSet` by `Statement.executeQuery(...)`; callers that need the
360+
server-rendered JSON should use the underlying `client-v2` instance exposed by
361+
the JDBC connection and consume the `QueryResponse` stream directly.
362+
363+
```java
364+
try (Connection conn = DriverManager.getConnection(
365+
"jdbc:clickhouse://localhost:8123/default", props);
366+
QueryResponse response = conn.unwrap(ConnectionImpl.class)
367+
.getClient()
368+
.query("SELECT 1 AS x FORMAT JSON")
369+
.get();
370+
BufferedReader reader = new BufferedReader(new InputStreamReader(
371+
response.getInputStream(), StandardCharsets.UTF_8))) {
372+
String json = reader.lines().collect(Collectors.joining("\n"));
373+
// consume the JSON document
374+
}
375+
```
376+
377+
The returned `Client` is owned by the JDBC connection. Close each
378+
`QueryResponse`, as shown above, but do not close the client returned by
379+
`ConnectionImpl#getClient()`.
380+
381+
### Row-oriented JSONEachRow output
382+
355383
The output format is selected by appending `FORMAT JSONEachRow` to the SQL
356384
statement. The driver does not rewrite the SQL and does not apply a default
357385
format on the caller's behalf.

docs/features.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ Compatibility-sensitive traits:
5757
- Driver and client properties: Separates JDBC-specific properties from passthrough client options used by the underlying `client-v2` transport.
5858
- DataSource support: Provides a JDBC `DataSource` implementation backed by the same driver configuration model.
5959
- Connection lifecycle: Supports connection close, validity checks, ping-based health checks, and network timeout management.
60+
- Underlying client access: `ConnectionImpl#getClient()` exposes the connection-owned `client-v2` instance for operations that are not representable through the JDBC API, including direct consumption of raw response streams.
6061
- Schema and database context: Supports database selection through URL, `setSchema`, `USE`, and statement-level settings.
6162
- Non-transactional operation: Exposes ClickHouse-appropriate transaction behavior with auto-commit semantics and unsupported transactional features.
6263
- Statement execution: Supports `execute`, `executeQuery`, `executeUpdate`, large update counts, and forward-only/read-only statements.
@@ -87,6 +88,7 @@ Compatibility-sensitive traits:
8788
- `Geometry` has a stable JDBC mapping: metadata reports SQL type `ARRAY` with type name `Geometry`, read paths return nested Java arrays rather than custom wrappers, and write paths depend on the caller preserving the intended point/array nesting shape.
8889
- JDBC `Geometry` writes share the same ambiguity as the client serializer: variant selection is inferred from nesting depth, so `Ring` versus `LineString` and `Polygon` versus `MultiLineString` are not currently distinguishable when writing through the generic `Geometry` path.
8990
- JDBC `FORMAT JSONEachRow` support is opt-in through the `jdbc_json_parser_factory` driver property, whose value must be a fully-qualified `JsonParserFactory` class name with a public no-argument constructor; JSONEachRow numeric and structured value behavior follows the selected parser and configured server output settings. Inferred JSON arrays are returned from `ResultSet.getObject(...)` as parser-native `List` values rather than JDBC `Array` values because JSONEachRow does not include element metadata. JDBC temporal typed accessors such as `getTimestamp(...)` are not guaranteed for JSONEachRow result sets; callers that need stable JDBC temporal conversions should use the binary default format or perform application-level conversion from string/object values.
91+
- Standard `FORMAT JSON` output is document-oriented and is not exposed as a JDBC `ResultSet`. JDBC callers that need the server-rendered JSON document should unwrap to `ConnectionImpl`, call `getClient()`, and read the `QueryResponse` stream directly.
9092
- Binary parameters passed through `setBytes()` are encoded as ClickHouse `unhex(...)` expressions rather than text literals; empty byte arrays map to an empty string expression.
9193
- Stream and reader setters (`setAsciiStream`, `setUnicodeStream`, `setBinaryStream`, `setCharacterStream`, `setNCharacterStream`) are treated as text input encoded with the same string-escaping rules, including length-based truncation when a length is supplied.
9294
- `getString()` formatting for temporal values is stable output: `Date` uses `yyyy-MM-dd`, `DateTime` uses `yyyy-MM-dd HH:mm:ss`, and `DateTime64` preserves fractional precision, all interpreted in server timezone context where applicable.

jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -706,8 +706,15 @@ public int getNetworkTimeout() throws SQLException {
706706
}
707707

708708
/**
709-
* Returns instance of the client used to execute queries by this connection.
710-
* @return - client instance
709+
* Returns the {@link Client} instance used by this connection.
710+
* <p>
711+
* This can be used for operations that are not representable through the JDBC
712+
* API, such as consuming raw or document-oriented response formats directly
713+
* from {@link com.clickhouse.client.api.query.QueryResponse#getInputStream()}.
714+
* The returned client is owned by this connection and must not be closed by
715+
* callers; closing the connection closes the client.
716+
*
717+
* @return client instance
711718
*/
712719
public Client getClient() throws SQLException {
713720
ensureOpen();

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import com.clickhouse.client.api.DataTypeUtils;
99
import com.clickhouse.client.api.ServerException;
1010
import com.clickhouse.client.api.internal.ServerSettings;
11+
import com.clickhouse.client.api.query.QueryResponse;
12+
import com.clickhouse.data.ClickHouseFormat;
1113
import com.github.tomakehurst.wiremock.WireMockServer;
1214
import com.github.tomakehurst.wiremock.client.WireMock;
1315
import com.github.tomakehurst.wiremock.common.ConsoleNotifier;
@@ -17,6 +19,8 @@
1719
import org.testng.annotations.DataProvider;
1820
import org.testng.annotations.Test;
1921

22+
import java.io.BufferedReader;
23+
import java.io.InputStreamReader;
2024
import java.math.BigDecimal;
2125
import java.net.Inet4Address;
2226
import java.net.Inet6Address;
@@ -47,6 +51,7 @@
4751
import java.util.concurrent.ScheduledExecutorService;
4852
import java.util.concurrent.TimeUnit;
4953
import java.util.function.BiConsumer;
54+
import java.util.stream.Collectors;
5055

5156
import static org.testng.Assert.assertEquals;
5257
import static org.testng.Assert.assertFalse;
@@ -921,11 +926,31 @@ public void testUnwrapping() throws Exception {
921926
Connection conn = getJdbcConnection();
922927
Assert.assertTrue(conn.isWrapperFor(Connection.class));
923928
Assert.assertTrue(conn.isWrapperFor(JdbcV2Wrapper.class));
929+
Assert.assertTrue(conn.isWrapperFor(ConnectionImpl.class));
924930
Assert.assertEquals(conn.unwrap(Connection.class), conn);
925931
Assert.assertEquals(conn.unwrap(JdbcV2Wrapper.class), conn);
932+
Assert.assertEquals(conn.unwrap(ConnectionImpl.class), conn);
926933
assertThrows(SQLException.class, () -> conn.unwrap(ResultSet.class));
927934
}
928935

936+
@Test(groups = { "integration" })
937+
public void testRawJSONQueryThroughUnderlyingClient() throws Exception {
938+
try (Connection conn = getJdbcConnection();
939+
QueryResponse response = conn.unwrap(ConnectionImpl.class).getClient()
940+
.query("SELECT 1 AS x FORMAT JSON")
941+
.get();
942+
BufferedReader reader = new BufferedReader(
943+
new InputStreamReader(response.getInputStream(), StandardCharsets.UTF_8))) {
944+
assertEquals(response.getFormat(), ClickHouseFormat.JSON);
945+
946+
String output = reader.lines().collect(Collectors.joining("\n"));
947+
assertTrue(output.contains("\"meta\""), output);
948+
assertTrue(output.contains("\"data\""), output);
949+
assertTrue(output.contains("\"rows\""), output);
950+
assertTrue(output.matches("(?s).*\"x\"\\s*:\\s*1.*"), output);
951+
}
952+
}
953+
929954
@Test(groups = { "integration" })
930955
public void testBearerTokenAuth() throws Exception {
931956
if (isCloud()) {

0 commit comments

Comments
 (0)