Skip to content

Conversation

@peter-leonov-ch
Copy link
Collaborator

@peter-leonov-ch peter-leonov-ch commented Dec 30, 2025

Why

The current exception detection code is implemented in a way that makes any combination of \r\n throw an exception. This works fine for most of the use cases of the JS client as the JSON-based family of response formats is the most widely used and JSON guarantees that the output stream is not going to have a non-escaped \r byte.

This heuristics does not work for \r\n terminated CSV. A CH client created with the following configuration will trip on the legitimate output:

const client = createClient({
  clickhouse_settings: {
    output_format_csv_crlf_end_of_line: 1,
    http_write_exception_in_output_format: 0, // is the default in the new versions of ClickHouse
  },
})

The JS exception in this scenario describes that there was an exception on the server side but parsing of that exception did not go well. This might be confusing.

What

This PR proposes to use a more strict error parser for the supported raw formats (based on the list in SupportedRawFormats). This stricter parser is less likely to trip on valid input.

The downside is that neither of the parsers are byte-level (meaning they require a certain amount of the stream to be buffered). This introduces a higher risk of the exception marker and message not being recognized (or only partially recovered) when the exception byte sequence is split by a (network, in our case HTTP) chunk boundary. For example:

[
  "0\r\n0\r\n0\r\n0\r\n\r\n__exception__\r\nlehhsmpwopswzufu\r\nCode: 395. DB::Exception: valid exception 2: while executing 'FUNCTION throwIf(equals(__table1.number, 1000_UInt16) :: 0, 'valid exception 2'_String :: 2) -> throwIf(equals(__table1.number, 1000_UInt16), 'valid exception 2'_String) UInt8 : 1'. (FUNCTION_THROW_IF_VALUE_IS_NON_ZERO) (version 26.1.1.200 (",
  "official build))\n320 uxhzkfvbzrfigznc\r\n__exception__\r\n"
]

My take on this is that for raw formats it seems to be a better trade-off to potentially miss an exception compared to not being able to process output at all.

This PR also fixes an infinite loop case scenario that happens on a certain exception message split.

Also

The PR uses blanket implementation for all the raw formats not taking the \r setting into account. It might be better to introduce a separate client config option instead that default to the simpler JSON-friendly parser. This may help get consistent results for those rare cases with \r terminated SCV and custom output format.

Also, one of the added tests has to have a certain number of output rows to run in both local and local_cluster modes. This is because in local_cluster mode nginx repacks the HTTP chunks using it's internal HTTP acceleration-focused logic and changes this way the resulting chunking conditions as seen by the client.

Checklist

Delete items not relevant to your PR:

  • Unit and integration tests covering the common scenarios were added
  • A human-readable description of the changes was provided to include in CHANGELOG

@peter-leonov-ch peter-leonov-ch force-pushed the faster_exception_detection branch from de2e8ad to 26f1c31 Compare December 30, 2025 23:15
@peter-leonov-ch peter-leonov-ch force-pushed the exception_detection_respect_tag branch 2 times, most recently from d6e84b5 to 5998181 Compare December 31, 2025 16:34
@codecov
Copy link

codecov bot commented Dec 31, 2025

Codecov Report

❌ Patch coverage is 76.00000% with 6 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
packages/client-common/src/utils/stream.ts 71.42% 4 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

@peter-leonov-ch peter-leonov-ch force-pushed the faster_exception_detection branch from 26f1c31 to 16c55a7 Compare January 12, 2026 14:42
Base automatically changed from faster_exception_detection to main January 15, 2026 18:27
@peter-leonov-ch peter-leonov-ch force-pushed the exception_detection_respect_tag branch from 6543958 to 6d4e067 Compare January 25, 2026 12:18
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.

2 participants