Summary
When a query fails after the ClickHouse HTTP interface has already committed a 200 OK and started streaming rows (for example a runtime error such as throwIf or a division-by-zero partway through a large result), the server appends an in-band exception block to the response body and closes the connection. The native-format read path detects this and raises a clean ClickHouse error, but the Arrow query methods hand the raw bytes straight to pyarrow and never consult the in-band exception. As a result a mid-stream failure surfaces as a truncated/garbled Arrow stream, a pyarrow parse error, or a raw transport error (urllib3.ProtocolError / aiohttp.ClientPayloadError) instead of the actual server error.
Affected methods
AsyncClient.query_arrow
AsyncClient.query_arrow_stream
AsyncClient.query_df_arrow_stream
Client.query_arrow_stream
Client.query_df_arrow_stream
Client.query_arrow (sync, buffered, wait_end_of_query=1) already surfaces a clean error because the whole body is read before parsing.
Reproduction
import clickhouse_connect
client = clickhouse_connect.get_client(host="localhost")
q = "SELECT number, throwIf(number = 1000000, 'boom') FROM system.numbers"
# native path -> clean DatabaseError / StreamFailureError with "Code: 395 ... boom"
# Arrow streaming path -> raw transport error, no server message:
with client.query_arrow_stream(q) as s:
for batch in s:
pass
# raises urllib3.exceptions.ProtocolError: ('Connection broken: IncompleteRead ...')
The native path (client.query / query_rows_stream) raises a clean StreamFailureError/DatabaseError carrying Code: 395 ... boom for the same query.
Expected
The Arrow streaming methods should surface the same clean ClickHouse server error (matching the native streaming path) instead of a truncated stream or a raw transport error.
Environment
- clickhouse-connect (current
main)
- Server 26.5 (in-band exception delivered via the leading
X-ClickHouse-Exception-Tag header and __exception__-delimited body markers)
Summary
When a query fails after the ClickHouse HTTP interface has already committed a
200 OKand started streaming rows (for example a runtime error such asthrowIfor a division-by-zero partway through a large result), the server appends an in-band exception block to the response body and closes the connection. The native-format read path detects this and raises a clean ClickHouse error, but the Arrow query methods hand the raw bytes straight topyarrowand never consult the in-band exception. As a result a mid-stream failure surfaces as a truncated/garbled Arrow stream, apyarrowparse error, or a raw transport error (urllib3.ProtocolError/aiohttp.ClientPayloadError) instead of the actual server error.Affected methods
AsyncClient.query_arrowAsyncClient.query_arrow_streamAsyncClient.query_df_arrow_streamClient.query_arrow_streamClient.query_df_arrow_streamClient.query_arrow(sync, buffered,wait_end_of_query=1) already surfaces a clean error because the whole body is read before parsing.Reproduction
The native path (
client.query/query_rows_stream) raises a cleanStreamFailureError/DatabaseErrorcarryingCode: 395 ... boomfor the same query.Expected
The Arrow streaming methods should surface the same clean ClickHouse server error (matching the native streaming path) instead of a truncated stream or a raw transport error.
Environment
main)X-ClickHouse-Exception-Tagheader and__exception__-delimited body markers)