Skip to content

Commit aaffb6f

Browse files
committed
Fixed breaking change and documented more
1 parent a6e969c commit aaffb6f

12 files changed

Lines changed: 139 additions & 46 deletions

File tree

client-v2/src/main/java/com/clickhouse/client/api/Client.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import com.clickhouse.client.api.command.CommandResponse;
44
import com.clickhouse.client.api.command.CommandSettings;
55
import com.clickhouse.client.api.data_formats.ClickHouseBinaryFormatReader;
6-
import com.clickhouse.client.api.data_formats.JsonParserFactory;
76
import com.clickhouse.client.api.data_formats.NativeFormatReader;
87
import com.clickhouse.client.api.data_formats.RowBinaryFormatReader;
98
import com.clickhouse.client.api.data_formats.RowBinaryWithNamesAndTypesFormatReader;
@@ -2223,21 +2222,21 @@ private Map<String, Object> buildRequestSettings(Map<String, Object> opSettings)
22232222
* Must be called after {@link #buildRequestSettings(Map)} and after the request format has been resolved
22242223
* (either provided by the caller or defaulted), so that the inspected format reflects the final value.
22252224
*
2226-
* <p>For {@link ClickHouseFormat#JSONEachRow} the JSON output flags below are forced to {@code 0} so that the
2227-
* stream contains plain JSON numbers (and not quoted strings or non-standard tokens), which is what
2228-
* {@link com.clickhouse.client.api.data_formats.JSONEachRowFormatReader} expects:</p>
2225+
* <p>For {@link ClickHouseFormat#JSONEachRow}, callers may opt in to plain JSON numbers by setting
2226+
* {@link ClientConfigProperties#JSON_DISABLE_NUMBER_QUOTING}. Explicit server settings are otherwise
2227+
* left untouched.</p>
22292228
* <ul>
22302229
* <li>{@code output_format_json_quote_64bit_integers}</li>
22312230
* <li>{@code output_format_json_quote_64bit_floats}</li>
2232-
* <li>{@code output_format_json_quote_denormals}</li>
22332231
* <li>{@code output_format_json_quote_decimals}</li>
22342232
* </ul>
22352233
*/
22362234
private static void applyFormatSpecificSettings(QuerySettings requestSettings) {
2237-
if (requestSettings.getFormat() == ClickHouseFormat.JSONEachRow) {
2235+
boolean disableNumberQuoting = ClientConfigProperties.JSON_DISABLE_NUMBER_QUOTING
2236+
.getOrDefault(requestSettings.getAllSettings());
2237+
if (requestSettings.getFormat() == ClickHouseFormat.JSONEachRow && disableNumberQuoting) {
22382238
requestSettings.serverSetting("output_format_json_quote_64bit_integers", "0");
22392239
requestSettings.serverSetting("output_format_json_quote_64bit_floats", "0");
2240-
requestSettings.serverSetting("output_format_json_quote_denormals", "0");
22412240
requestSettings.serverSetting("output_format_json_quote_decimals", "0");
22422241
}
22432242
}

client-v2/src/main/java/com/clickhouse/client/api/ClientConfigProperties.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,11 @@ public Object parseValue(String value) {
190190
*/
191191
HTTP_SEND_PARAMS_IN_BODY("client.http.use_form_request_for_query", Boolean.class, "false"),
192192

193+
/**
194+
* When enabled for JSONEachRow queries, asks ClickHouse to emit large integer,
195+
* floating-point, and decimal values as JSON numbers instead of quoted strings.
196+
*/
197+
JSON_DISABLE_NUMBER_QUOTING("json_disable_number_quoting", Boolean.class, "false"),
193198

194199
/**
195200
* Prefix for custom settings. Should be aligned with server configuration.

client-v2/src/test/java/com/clickhouse/client/ClientTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ public void testDefaultSettings() {
329329
Assert.assertEquals(config.get(p.getKey()), p.getDefaultValue(), "Default value doesn't match");
330330
}
331331
}
332-
Assert.assertEquals(config.size(), 34); // to check everything is set. Increment when new added.
332+
Assert.assertEquals(config.size(), 35); // to check everything is set. Increment when new added.
333333
}
334334

335335
try (Client client = new Client.Builder()
@@ -362,7 +362,7 @@ public void testDefaultSettings() {
362362
.setSocketSndbuf(100000)
363363
.build()) {
364364
Map<String, String> config = client.getConfiguration();
365-
Assert.assertEquals(config.size(), 35); // to check everything is set. Increment when new added.
365+
Assert.assertEquals(config.size(), 36); // to check everything is set. Increment when new added.
366366
Assert.assertEquals(config.get(ClientConfigProperties.DATABASE.getKey()), "mydb");
367367
Assert.assertEquals(config.get(ClientConfigProperties.MAX_EXECUTION_TIME.getKey()), "10");
368368
Assert.assertEquals(config.get(ClientConfigProperties.COMPRESSION_LZ4_UNCOMPRESSED_BUF_SIZE.getKey()), "300000");
@@ -429,7 +429,7 @@ public void testWithOldDefaults() {
429429
Assert.assertEquals(config.get(p.getKey()), p.getDefaultValue(), "Default value doesn't match");
430430
}
431431
}
432-
Assert.assertEquals(config.size(), 34); // to check everything is set. Increment when new added.
432+
Assert.assertEquals(config.size(), 35); // to check everything is set. Increment when new added.
433433
}
434434
}
435435

client-v2/src/test/java/com/clickhouse/client/query/QueryTests.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import com.clickhouse.client.ClickHouseProtocol;
88
import com.clickhouse.client.ClickHouseServerForTest;
99
import com.clickhouse.client.api.Client;
10+
import com.clickhouse.client.api.ClientConfigProperties;
1011
import com.clickhouse.client.api.ClientException;
1112
import com.clickhouse.client.api.ServerException;
1213
import com.clickhouse.client.api.command.CommandSettings;
@@ -380,6 +381,28 @@ public void testQueryJSONEachRow() throws ExecutionException, InterruptedExcepti
380381
}
381382
}
382383

384+
@Test(groups = {"integration"})
385+
public void testJsonEachRowNumberQuoteSettingsAreOptIn() throws Exception {
386+
String sql = "SELECT toInt64(1234567890123) AS v";
387+
388+
QuerySettings settings = new QuerySettings()
389+
.setFormat(ClickHouseFormat.JSONEachRow)
390+
.serverSetting("output_format_json_quote_64bit_integers", "1");
391+
try (QueryResponse response = client.query(sql, settings).get();
392+
BufferedReader reader = new BufferedReader(new InputStreamReader(response.getInputStream()))) {
393+
Assert.assertTrue(reader.readLine().contains("\"v\":\"1234567890123\""));
394+
}
395+
396+
QuerySettings unquotedSettings = new QuerySettings()
397+
.setFormat(ClickHouseFormat.JSONEachRow)
398+
.serverSetting("output_format_json_quote_64bit_integers", "1")
399+
.setOption(ClientConfigProperties.JSON_DISABLE_NUMBER_QUOTING.getKey(), true);
400+
try (QueryResponse response = client.query(sql, unquotedSettings).get();
401+
BufferedReader reader = new BufferedReader(new InputStreamReader(response.getInputStream()))) {
402+
Assert.assertTrue(reader.readLine().contains("\"v\":1234567890123"));
403+
}
404+
}
405+
383406
@DataProvider(name = "rowBinaryFormats")
384407
ClickHouseFormat[] getRowBinaryFormats() {
385408
return new ClickHouseFormat[]{

docs/client-v2-json-support.md

Lines changed: 51 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,9 @@ provides additional advantages beyond what the format alone delivers:
6767
`com.clickhouse.client.api.data_formats`, consisting of `JsonParser`,
6868
`JsonParserFactory`, `JacksonJsonParserFactory`, and
6969
`GsonJsonParserFactory`.
70-
- Forces a fixed set of server settings for `JSONEachRow` requests so that
71-
the response stream contains plain JSON numbers (see
72-
[Forced server settings](#forced-server-settings-for-jsoneachrow)).
70+
- Adds an opt-in client flag for `JSONEachRow` requests that asks ClickHouse
71+
to emit large integers, floats, and decimals as plain JSON numbers (see
72+
[JSON number output settings](#json-number-output-settings)).
7373
- Declares Jackson and Gson as `provided` Maven dependencies, so that
7474
applications must include the chosen processor on their own classpath.
7575

@@ -179,6 +179,18 @@ have a public no-argument constructor. There is no equivalent `client-v2`
179179
configuration key; direct client users pass a factory instance to their own
180180
reader construction code.
181181

182+
### JSON number quoting flag
183+
184+
`client-v2` can opt in to numeric JSON output settings through
185+
`ClientConfigProperties.JSON_DISABLE_NUMBER_QUOTING`:
186+
187+
| Property key | Default | Effect |
188+
| --------------------------------------------- | ------- | ------ |
189+
| `json_disable_number_quoting` | `false` | When `true` and the resolved request format is `JSONEachRow`, sets `output_format_json_quote_64bit_integers=0`, `output_format_json_quote_64bit_floats=0`, and `output_format_json_quote_decimals=0` for that request. |
190+
191+
The flag can be set on the client builder or on a specific `QuerySettings`
192+
instance. It does not change `output_format_json_quote_denormals`.
193+
182194
## Runtime dependencies
183195

184196
`client-v2` and `jdbc-v2` declare the JSON libraries with `provided` scope,
@@ -216,7 +228,8 @@ Client client = new Client.Builder()
216228
JsonParserFactory parserFactory = new JacksonJsonParserFactory();
217229

218230
QuerySettings settings = new QuerySettings()
219-
.setFormat(ClickHouseFormat.JSONEachRow);
231+
.setFormat(ClickHouseFormat.JSONEachRow)
232+
.setOption(ClientConfigProperties.JSON_DISABLE_NUMBER_QUOTING.getKey(), true);
220233

221234
try (QueryResponse response = client.query(
222235
"SELECT id, name, active, score, payload FROM events ORDER BY id",
@@ -237,9 +250,9 @@ try (QueryResponse response = client.query(
237250
Notes:
238251

239252
- Set `ClickHouseFormat.JSONEachRow` in `QuerySettings`. Do not rely on an SQL
240-
`FORMAT JSONEachRow` clause for direct `client-v2` examples, because the
241-
client applies JSON-specific server settings only when the request settings
242-
identify the format as `JSONEachRow`.
253+
`FORMAT JSONEachRow` clause for direct `client-v2` examples when you also
254+
want client-side JSON number output settings, because those settings are
255+
applied only when the request settings identify the format as `JSONEachRow`.
243256
- `client.newBinaryFormatReader(response)` continues to return a
244257
`ClickHouseBinaryFormatReader` for binary output formats and rejects text
245258
formats such as `JSONEachRow` with `IllegalArgumentException`. Callers that
@@ -267,7 +280,6 @@ props.setProperty(DriverProperties.JSON_PARSER_FACTORY.getKey(),
267280
props.setProperty(ClientConfigProperties.serverSetting("allow_experimental_json_type"), "1");
268281
props.setProperty(ClientConfigProperties.serverSetting("output_format_json_quote_64bit_integers"), "0");
269282
props.setProperty(ClientConfigProperties.serverSetting("output_format_json_quote_64bit_floats"), "0");
270-
props.setProperty(ClientConfigProperties.serverSetting("output_format_json_quote_denormals"), "0");
271283
props.setProperty(ClientConfigProperties.serverSetting("output_format_json_quote_decimals"), "0");
272284

273285
try (Connection conn = DriverManager.getConnection(
@@ -294,35 +306,46 @@ Behavior:
294306
and `JSONEachRow` as output formats; any other text format causes
295307
`SQLException("Only RowBinaryWithNameAndTypes and JSONEachRow are supported
296308
for output format. ...")` to be thrown.
297-
- `ResultSet.getObject(...)` returns parser-native `Map` and scalar values
298-
without an additional string round-trip. JSON arrays are exposed as JDBC
299-
`Array` values, wrapping the parser-produced list.
309+
- `ResultSet.getObject(...)` returns parser-native `Map`, `List`, and scalar
310+
values without an additional string round-trip. JSON arrays are returned as
311+
the `List` implementation produced by the selected JSON library. Because
312+
`JSONEachRow` has no array element metadata, `ResultSet.getArray(...)` is
313+
not supported for these inferred JSON arrays.
300314
- The JSON processor is selected at the connection level through the
301315
`jdbc_json_parser_factory` driver property. It cannot be changed per
302316
statement, in line with the lifecycle of other connection options.
303317
- Because JDBC selects `JSONEachRow` through SQL text, set the JSON output
304318
server settings explicitly as connection properties when integer or decimal
305319
numeric accessors are used.
306320

307-
## Forced server settings for `JSONEachRow`
321+
## JSON number output settings
308322

309323
`Client.applyFormatSpecificSettings(...)` runs after request settings have
310324
been merged and after the request format has been resolved. When the format
311-
is `JSONEachRow`, the following server-side settings are forced for the
312-
request:
325+
is `JSONEachRow` and
326+
`ClientConfigProperties.JSON_DISABLE_NUMBER_QUOTING` is enabled, the
327+
following server-side settings are set to `0` for the request:
313328

314-
| Setting | Forced value | Rationale |
329+
| Setting | Value | Rationale |
315330
| ----------------------------------------- | ------------ | -------------------------------------------------------------------------- |
316-
| `output_format_json_quote_64bit_integers` | `0` | Emits `Int64` and `UInt64` as JSON numbers rather than quoted strings. |
317-
| `output_format_json_quote_64bit_floats` | `0` | Emits 64-bit floating-point values as JSON numbers. |
318-
| `output_format_json_quote_denormals` | `0` | Avoids quoting `NaN` and `Inf`, allowing materialization as `Double`. |
319-
| `output_format_json_quote_decimals` | `0` | Emits decimals as JSON numbers, allowing materialization as `BigDecimal` or `Double`. |
331+
| `output_format_json_quote_64bit_integers` | `0` | Emits `Int64` and `UInt64` as JSON numbers rather than quoted strings. |
332+
| `output_format_json_quote_64bit_floats` | `0` | Emits 64-bit floating-point values as JSON numbers. |
333+
| `output_format_json_quote_decimals` | `0` | Emits decimals as JSON numbers, allowing materialization as `BigDecimal` or `Double`. |
334+
335+
These overrides are scoped to the individual request and apply only when both
336+
conditions are true: the request format in `QuerySettings` is `JSONEachRow`,
337+
and `json_disable_number_quoting` is enabled through the client
338+
or request settings. Explicit server settings are otherwise preserved.
339+
340+
Denormal floating-point values (`NaN`, `Inf`, `-Inf`) are not yet handled by
341+
the built-in JSON reader. The client does not set
342+
`output_format_json_quote_denormals`; keep the server default or set
343+
`output_format_json_quote_denormals=1` so these values are quoted, then handle
344+
them as strings at the application boundary.
320345

321-
These overrides are scoped to the individual request and apply only when the
322-
request format in `QuerySettings` is `JSONEachRow`. They are required for the
323-
typed accessors of the reader to operate correctly. JDBC callers that use SQL
324-
`FORMAT JSONEachRow` should set the same server settings explicitly through
325-
connection properties.
346+
JDBC callers that use SQL `FORMAT JSONEachRow` should set the same numeric
347+
server settings explicitly through connection properties when integer or
348+
decimal numeric accessors are used.
326349

327350
## Row parsing, schema, and typed accessors
328351

@@ -349,9 +372,10 @@ behavior.
349372
### Integer precision with Gson
350373

351374
ClickHouse `Int64` and `UInt64` values can exceed the exactly representable
352-
integer range of a JSON floating-point number. The client intentionally emits
353-
them as JSON numbers for `JSONEachRow`, so the selected JSON library's number
354-
materialization policy matters.
375+
integer range of a JSON floating-point number. When
376+
`json_disable_number_quoting` is enabled, the client asks
377+
ClickHouse to emit them as JSON numbers for `JSONEachRow`, so the selected
378+
JSON library's number materialization policy matters.
355379

356380
Jackson's default `Map.class` materialization keeps ordinary integer tokens as
357381
integer `Number` implementations. Gson's default `Map<String, Object>`

docs/features.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ Compatibility-sensitive traits:
4040
- `Geometry` handling is shape-sensitive: supported values are 1D through 4D Java arrays representing the nested geometry variants, and unsupported shapes or non-array values are rejected during serialization.
4141
- `Geometry` write inference is dimension-based rather than fully type-specific: point, ring/line string, polygon/multi-line string, and multi-polygon are selected from array depth, so writing `Geometry` cannot currently distinguish `Ring` from `LineString` or `Polygon` from `MultiLineString`.
4242
- Session precedence is part of the contract: client session defaults apply to each request, operation settings may override them, and only the client `session_id` is mutable at runtime while other client session properties remain fixed for the lifetime of the client.
43-
- JSONEachRow reading depends on the selected parser factory and request format settings: parser materialization determines Java value types, the reader infers minimal schema from the first row, and JSON-specific server settings are applied only when `QuerySettings` resolves to `ClickHouseFormat.JSONEachRow`.
43+
- JSONEachRow reading depends on the selected parser factory and request format settings: parser materialization determines Java value types, the reader infers minimal schema from the first row, and JSON number server settings are applied only when `QuerySettings` resolves to `ClickHouseFormat.JSONEachRow` and `json_disable_number_quoting` is enabled.
4444
- JSONEachRow schema inference is intentionally best-effort: scalar values use Java-to-ClickHouse type mappings, while JSON arrays and objects are identified structurally as `Array` and `Map`. For arrays, maps, and some nested or ambiguous values, the inferred type may not include the most specific element, key, value, or nested ClickHouse type.
4545

4646

@@ -80,7 +80,7 @@ Compatibility-sensitive traits:
8080
- String-like ClickHouse values have stable JDBC expectations: `String`, `FixedString`, and `Enum` values are returned as strings, while `UUID` is available both as `getString()` and `getObject(..., UUID.class)`.
8181
- `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.
8282
- 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.
83-
- 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.
83+
- 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.
8484
- Binary parameters passed through `setBytes()` are encoded as ClickHouse `unhex(...)` expressions rather than text literals; empty byte arrays map to an empty string expression.
8585
- 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.
8686
- `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.

examples/client-v2-json-processors/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ Each read call in `run()` follows the same three-step shape:
4646
The client example selects the output format with
4747
`new QuerySettings().setFormat(ClickHouseFormat.JSONEachRow)`. Use that form
4848
instead of appending `FORMAT JSONEachRow` to the SQL when calling `client-v2`
49-
directly, because the client applies JSON-specific server settings from the
50-
request format.
49+
directly when you enable client-side JSON number output settings, because
50+
those settings depend on the request format.
5151

5252
## Integer Precision
5353

0 commit comments

Comments
 (0)