Skip to content

fix(client-v2): throw on in-band server exception row in JSONEachRowFormatReader - #3313

Open
pcbeingused333 wants to merge 1 commit into
ClickHouse:mainfrom
pcbeingused333:c1800-jsoneachrow-inband-exception
Open

pcbeingused333 wants to merge 1 commit into
ClickHouse:mainfrom
pcbeingused333:c1800-jsoneachrow-inband-exception

Conversation

@pcbeingused333

@pcbeingused333 pcbeingused333 commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #3220

Cause: with http_write_exception_in_output_format (default on, 25.8), a query that fails after the response started makes the server append {"exception": "Code: ..."} as one more JSONEachRow row and end the body normally. JSONEachRowFormatReader handed it out as an ordinary data row, so the failure was silent (truncated result plus an all-null row).

Fix: when the prefetched row has exactly one key, exception, with a string value starting with Code: , it is turned into a ServerException (code parsed from Code: N., as the issue's expected behaviour asks) through the existing deferred-error path (the valid preceding rows are still returned, the error surfaces on the next next()). Rows with other columns are untouched, and so is a query whose whole result is a single exception column (detected from the schema inferred from the first row), even when its values start with Code: . A query that selects exception next to other columns (e.g. SELECT query_id, exception FROM system.query_log) is still protected, since JSONEachRow writes every column in every row and a one-key row cannot be data there.

Compatibility: previously such a row was returned as data; it now throws from next(). Queries whose only column is exception are unaffected. jdbc-v2 (StatementImpl) uses the same reader, so it gets the same behaviour. Added history/latest/3220.md and a line in docs/features.md.

Tests: JSONEachRowFormatReaderTest adds a case for the in-band error row (fails before, passes after), a case with a multi-column schema that includes exception, and ones confirming that queries with only an exception column (including values like Code: 60. DB::Exception...) are not treated as errors.

This change was prepared with the assistance of an AI tool (Claude) and reviewed before submission.


Note

Medium Risk
Changes JSONEachRow iteration semantics: in-band error rows throw instead of appearing as data, which can break callers that did not handle failures but fixes previously silent partial results.

Overview
Fixes silent truncation when ClickHouse appends an in-band error row to JSONEachRow responses (http_write_exception_in_output_format, default on): {"exception": "Code: ..."}.

JSONEachRowFormatReader now recognizes that row when exception is the only key and the inferred schema has other columns, parses the numeric code from the message, and surfaces a ServerException on the following next() via the existing deferred-error path—rows already returned stay valid. Queries whose result is only an exception column (or multi-column rows that include real exception data) are left unchanged.

Unit tests cover the happy path, mixed schemas, and false-positive guards; docs/features.md and history/latest/3220.md document the contract (same behavior for jdbc-v2 JSONEachRow streaming).

Reviewed by Cursor Bugbot for commit 283b2df. Bugbot is set up for automated code reviews on this repo. Configure here.

…ormatReader

With http_write_exception_in_output_format a query that fails after the
response started ends with an extra {"exception": "Code: ..."} row. The
reader now throws ServerException from next() for that row instead of
returning it as data.

Signed-off-by: Alex Castillo <alex.castillog33@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[client-v2, jdbc-v2] JSONEachRow: a query that fails mid-stream on ClickHouse 25.8 returns the {"exception": ...} row as data and raises no error

1 participant