Files
Raúl MarínandClaude Opus 4.8 e6ccc02b4b Fix client-side compatibility by not transmitting compatibility-derived settings
The earlier server-side approach could not distinguish a compatibility-synthesized
setting from a genuine explicit override (both arrive as identical changes), so it
broke when the client's `compatibility` matched the profile's pinned version (the
reverted value was then the only setting transmitted, with no `compatibility` in the
batch to explain it) and it silently dropped genuine HTTP/SQL overrides.

Fix it on the client instead: the native client resets settings whose value was set
only by `compatibility` before transmitting them, so the server re-derives them from
`compatibility` itself and honors its own constraints (for example a profile pinning a
setting read-only). Server-to-server settings propagation is left unchanged. The
server-side `SettingsConstraints` heuristic is reverted.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-20 21:10:35 +02:00

65 lines
2.5 KiB
Python

import pytest
from helpers.cluster import ClickHouseCluster
cluster = ClickHouseCluster(__file__)
node = cluster.add_instance("node", user_configs=["configs/users.xml"])
# Any version before `s3_allow_server_credentials_in_user_queries` was introduced (26.7), so that
# `compatibility` reverts it to its old default and the client resolves that reverted value.
OLD_COMPATIBILITY = "24.1"
# The version the `tenant` profile pins for `compatibility` (see configs/users.xml).
TENANT_COMPATIBILITY = "25.8"
SETTING = "s3_allow_server_credentials_in_user_queries"
@pytest.fixture(scope="module")
def start_cluster():
try:
cluster.start()
yield cluster
finally:
cluster.shutdown()
def test_old_compatibility_keeps_readonly_pinned_setting(start_cluster):
# The native client resolves `compatibility` locally; it must not force the reverted value of the pinned
# setting on the server. With the pin in place the query must still succeed and the pinned value stays.
assert node.query("SELECT 1", settings={"compatibility": OLD_COMPATIBILITY}).strip() == "1"
assert (
node.query(
f"SELECT getSetting('{SETTING}')", settings={"compatibility": OLD_COMPATIBILITY}
).strip()
== "false"
)
def test_client_compatibility_matching_profile_keeps_pin(start_cluster):
# The `tenant` profile pins `compatibility=25.8`; the client passes the same version. The reverted value
# of the pinned setting would then be the only setting differing from the server, transmitted with no
# `compatibility` in the batch. The client must still not force it, so the query succeeds and the pin holds.
assert (
node.query(
f"SELECT getSetting('{SETTING}')",
user="tenant",
settings={"compatibility": TENANT_COMPATIBILITY},
).strip()
== "false"
)
def test_explicit_override_of_readonly_setting_is_rejected(start_cluster):
# A genuine attempt to change the read-only setting (no `compatibility` involved) is still refused.
error = node.query_and_get_error("SELECT 1", settings={SETTING: 1})
assert "should not be changed" in error
def test_http_compatibility_unaffected(start_cluster):
# Over HTTP `compatibility` is resolved server-side against the already-pinned value, so this path
# was never broken; guard it so the native and HTTP paths stay consistent.
assert (
node.http_query("SELECT 1", params={"compatibility": OLD_COMPATIBILITY}).strip() == "1"
)