Repository navigation
Description
Describe the bug
Since #482 (on main, not released yet), Compression::Lz4 (the default) sends the setting enable_http_compression=1 on every query. Before #482 it sent the HTTP parameter compress=1, which is not a setting.
A user with readonly = 1 can send a setting only if the value is equal to its current value. When enable_http_compression is 0 for that user, the server rejects every query with Code 164 (READONLY). The value is 0 for the user when:
- the server is 25.9 or older (server default is
0; it changed to1in 25.10, see Verify compression settings used by the client after 25.10 #345), or - the server is 25.10+ and the user's settings or settings profile set
enable_http_compression = 0.
Compression::Zstd has the same problem. It already sent enable_http_compression=1 in 0.15.2.
0.15.2 is not affected for Lz4. The regression ships with the next release if it is not fixed.
Steps to reproduce
- As admin:
CREATE USER ro IDENTIFIED WITH no_password SETTINGS readonly = 1, enable_http_compression = 0(on 25.9 and older,SETTINGS readonly = 1is sufficient). - With the default client (Lz4), run
SELECT 1as userro. - The query fails with Code 164.
Expected behaviour
The query returns 1, as it does with 0.15.2 on the same server and user.
The server accepts the old parameter from a readonly user and rejects the new setting (25.8.28.1, plain readonly = 1 user, POST SELECT 1):
?compress=1 -> HTTP 200 (compressed body)
?enable_http_compression=0 -> HTTP 200 (value not changed)
?enable_http_compression=1 -> HTTP 500: Code: 164. DB::Exception: Cannot modify 'enable_http_compression' setting in readonly mode. (READONLY) (version 25.8.28.1 (official build))
Code example
use clickhouse::{Client, Compression};
// user `ro`: readonly = 1, enable_http_compression = 0
let client = Client::default()
.with_url("http://localhost:8123")
.with_user("ro");
let r = client.query("SELECT 1").fetch_one::<u8>().await;
// main: Err(BadResponse("Code: 164. ... Cannot modify 'enable_http_compression' setting in readonly mode. (READONLY) ..."))
// with `.with_compression(Compression::None)`: Ok(1)Results of fetch_one on server 26.3.28.5, user with readonly = 1, enable_http_compression = 0:
| Compression | 0.15.2 (fcb8b02) |
main (af76d45) |
|---|---|---|
None |
Ok(1) |
Ok(1) |
Lz4 (default) |
Ok(1) |
Code 164 |
Zstd(3) |
Code 164 | Code 164 |
Same result for main on 26.9.4.3. On 26.3 and 26.9, a plain readonly = 1 user (no override) passes, because the server default is already 1.
Error log
BadResponse("Code: 164. DB::Exception: Cannot modify 'enable_http_compression' setting in readonly mode. (READONLY) (version 25.8.28.1 (official build))")
On server 25.8, the integration tests query_readonly::readonly_user (tests/it/query_readonly.rs:178) and query_syntax::query_with_readonly_user (tests/it/query_syntax.rs:213) fail with this error on main and pass on fcb8b02. They fail with cargo test and with cargo test --no-default-features (the clickhouse-ext-arrow dev-dependency enables the default lz4 feature). They pass with --all-features, because test-util makes Compression::default() return None.
CI does not see this: the test-local matrix is [ latest, head, 26.7 ], and all three default enable_http_compression to 1.
Root cause
src/query.rs, Query::do_execute (used by execute, fetch*, fetch_bytes, fetch_native):
- lines 265-267:
if self.client.compression.is_enabled()appendsenable_http_compression=1. - lines 274-277: the
Lz4arm appendsenable_http_compression=1again, so the parameter is sent twice. - lines 279-284: the
Zstdarm appends it again, plushttp_zlib_compression_level.
The response side does not need the setting: Decompress::new (src/response.rs:290) selects the decoder from Content-Encoding, and uses the plain stream when the header is absent.
Suggested fix
One possible shape:
- Do not send
enable_http_compressionforLz4/Zstd. Send onlyAccept-Encoding. When the effective value is1(server default since 25.10), the server compresses. When it is0, the server sends a plain body, whichDecompress::newalready handles. This matches the plan in Verify compression settings used by the client after 25.10 #345. Trade-off: no response compression when the effective value is0. Servers 25.9 and older are out of support per the README policy (SECURITY.md). - Do not go back to
compress=1for Lz4: that returns native-framed blocks in the server's default codec, which is Default (LZ4) compression breaks on ClickHouse 26.9+: Lz4Decoder hardcodes magic 0x82 but the server default codec is now ZSTD(3) #468. - Remove the duplicate append (lines 265-267 vs 275).
http_zlib_compression_levelis also a setting. For a readonly user it fails the same way if the level is not equal to the user's current value (server default3;Compression::zstd()uses level 3, so the default passes).- Add an integration test with a
readonly = 1, enable_http_compression = 0user, so CI catches this on every server version in the matrix.
Contrast case to keep: writable users on 25.10+ must still get compressed responses.
Configuration
Environment
- Client version:
mainataf76d45; regression fromd72ea54(fix: use HTTP compression when requesting Lz4 for result sets #482). 0.15.2 is not affected for Lz4. - OS: Linux (Docker, Rust 1.89)
ClickHouse server
- ClickHouse Server version: 25.8.28.1, 26.3.28.5, 26.9.4.3
- ClickHouse Server non-default settings, if any: user
readonly = 1; on 25.10+ alsoenable_http_compression = 0for the user or its profile. CREATE TABLEstatements for tables involved: none (SELECT 1).
Found by automated analysis (Polyglot AI) while running the test suite of #462 against server 25.8. Verified against live servers 25.8.28.1, 26.3.28.5 and 26.9.4.3, with the client built from main and from 0.15.2.
Related: #345, #468, #373 (an earlier READONLY Code 164 regression, for the readonly setting).
Activity
fix: don't send HTTP compression settings unconditionally
abonander commentedon Oct 13, 2026
Fixed in 0.15.3 release PR