Skip to content

fix(clickhouse): suppress ON CLUSTER for object DDL in a Replicated database - #6003

Merged
nicksspirit merged 6 commits into
SQLMesh:mainfrom
mday-io:mday/clickhouse-replicated-on-cluster
Oct 15, 2026
Merged

nicksspirit merged 6 commits into
SQLMesh:mainfrom
mday-io:mday/clickhouse-replicated-on-cluster

Conversation

@mday-io

@mday-io mday-io commented Aug 31, 2026 •

Copy link
Copy Markdown
Collaborator

Description

Fix ClickHouse error 80 when SQLMesh creates or alters objects in a Replicated database with a cluster configured.

  • Omit ON CLUSTER where the database already propagates metadata changes.
  • Keep database creation/removal and partition DROP/REPLACE operations cluster-wide.
  • Preserve existing behavior for Atomic databases and failed lookups; reject rename/exchange operations between databases with different replication policies.
  • Retry failed metadata lookups on the next operation, and cache successful results.

Test Plan

  • make style passes.
  • make fast-test: 2,691 tests passed in the main batch; all 196 isolation tests passed (5 isolated, 1 registry, 190 dialect). Skips are recorded per batch in the local logs.
  • All 68 ClickHouse adapter tests pass, including 12 partition regression cases and three metadata-recovery cases. The recovery cases fail before this fix and pass afterward; they also verify successful cache reuse.
  • Independent review passed for the conflict resolution and metadata-recovery fix.
  • Earlier live test on ClickHouse 26.6.3.62: table creation fails before the fix and succeeds across three replicas afterward. Not rerun for this update.
  • All required CI checks pass.

Checklist

  • I have run make style and fixed any issues
  • I have added tests for my changes (if applicable)
  • All existing tests pass (make fast-test)
  • My commits are signed off (git commit -s) per the DCO

@mday-io
mday-io force-pushed the mday/clickhouse-replicated-on-cluster branch from f68f12f to b6c3e72 Compare August 31, 2026 15:07
@mday-io
mday-io force-pushed the mday/clickhouse-replicated-on-cluster branch from 7cf6528 to 80d884f Compare September 10, 2026 17:41
@mday-io
mday-io marked this pull request as ready for review September 10, 2026 17:45
…atabase

Inside a `Replicated` database Keeper already propagates object DDL, so adding
`ON CLUSTER` asks for a second, redundant fan-out and ClickHouse rejects the
statement outright:

    Code: 80. DB::Exception: It's not initial query.
    ON CLUSTER is not allowed for Replicated database. (INCORRECT_QUERY)

The ClickHouse adapter had no way to avoid this: `_on_cluster_sql()` took no
argument and keyed only on `engine_run_mode.is_cluster`. The practical effect is
total -- with a cluster configured, no table or view can be created in a
`Replicated` database at all. Reproduced end to end against a 1x3 cluster on
26.6.3.62: `create_table` into such a database fails with the error above before
this change and succeeds after it, propagating to all three replicas under one
shared UUID.

This cannot be a connection-level flag. One connection can hold both an Atomic
and a `Replicated` database, and objects in the Atomic one still need
`ON CLUSTER`, so the decision is made per target database by
`_should_use_on_cluster()` and threaded through all 14 emission sites.

The policy fails open throughout. An unknown target, an unresolvable database,
or any introspection failure keeps the previous behaviour, and a single cached
probe short-circuits the whole path when the server hosts no `Replicated`
database at all -- so this is a no-op, and costs no extra round-trips, for any
deployment that does not have one.

Two carve-outs are deliberate. `CREATE DATABASE` is how a `Replicated` database
comes into existence, so there is no engine to consult, and a database's own DDL
log cannot propagate its own removal; both stay cluster-wide unconditionally.
`alter_table` decides per expression rather than per call, because one call can
carry alters against both kinds of database, and `RENAME` / `EXCHANGE` across a
Replicated and a non-Replicated database are refused rather than rendered
silently half-correct.

`_build_table_properties_exp` and `_build_view_properties_exp` receive no
reference to the object being created, so the base class now passes the target
table down. The parameter is additive and every override across the nine adapter
files already accepts `**kwargs`, so no other adapter changes; Athena already
declares and receives the same `table` parameter via its own override.

This is a correctness fix independent of managed models: any SQLMesh user
running against a `Replicated` database is affected today for ordinary tables
and views.

Signed-off-by: mday-io <mdaytn@gmail.com>
Signed-off-by: mday-io <mdaytn@gmail.com>
@mday-io
mday-io force-pushed the mday/clickhouse-replicated-on-cluster branch from 80d884f to f1cfe4c Compare October 14, 2026 18:13
Signed-off-by: Michael Day <mdaytn@gmail.com>
@mday-io

mday-io commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator Author

ready for review

@nicksspirit

Copy link
Copy Markdown
Collaborator

Will look into this one

@nicksspirit

Copy link
Copy Markdown
Collaborator

Hi @mday-io — thanks for tackling the error 80 on Replicated databases. Checking the target database's engine per statement, and keeping ON CLUSTER for CREATE/DROP DATABASE, looks right to me, and the tests cover it well.

Two small things:

The three except Exception blocks in the engine lookups return silently:

. If the lookup fails, for example a user without access to system.databases, the fix quietly turns off and error 80 comes back with no clue why. Could you add a logger.debug or logger.warning there?

The ClickHouse docs say SQLMesh injects ON CLUSTER into all DDL (

If you provide a cluster name in your SQLMesh connection configuration, SQLMesh will automatically inject the `ON CLUSTER` statement into the DDL commands for all objects created while executing the project. We provide more information about clusters in SQLMesh [below](#cluster-specification).
, and line 99). Could you add a sentence saying it's skipped for Replicated databases?

One question, and this is already on main, not from this PR. Partition ALTERs still get ON CLUSTER:

alters_partitions = any(
isinstance(action, (exp.DropPartition, exp.ReplacePartition))
for action in alter_expression.args.get("actions", [])
)
if (
self.engine_run_mode.is_cluster
if alters_partitions
else self._should_use_on_cluster(altered_table)
):
alter_expression.set(
"cluster", exp.OnCluster(this=exp.to_identifier(self.cluster))
)
. SQLMesh emits REPLACE PARTITION and DROP PARTITION in one ALTER. As far as I can tell from shouldReplicateQuery in the ClickHouse source (https://github.com/ClickHouse/ClickHouse/blob/master/src/Databases/DatabaseReplicated.cpp), the exemption only applies when every command in the ALTER is the same kind, so a mixed one is still replicated by the database. On a multi-shard Replicated database that should still hit error 80. Was your live test single-shard? If so, it may be worth a note, or a follow-up.

Thanks again for taking the time on this.

Signed-off-by: Michael Day <mdaytn@gmail.com>
@mday-io

mday-io commented Oct 15, 2026

Copy link
Copy Markdown
Collaborator Author

Added warnings for all three metadata lookup failures in 92864ad76. Each warning identifies the failed lookup and includes the exception. The existing ON CLUSTER fallback and subsequent retry remain unchanged.

Both docs claims now explain that metadata DDL targeting Replicated databases skips ON CLUSTER. They also clarify that database creation/removal and partition operations retain it.

On the topology question, the original commit describes the 26.6.3.62 run as one shard with three replicas. The retained PR result records three replicas without explicitly recording the shard count. Neither provides multi-shard mixed-partition validation. Your concern is supported by ClickHouse's source and predates this PR. The 26.6.3.62 implementation also lacks the newer REPLACE-only exemption, so splitting the mixed ALTER alone is not an established fix on that version. I added a documentation caveat and recorded a version-aware partition validation follow-up. No live multi-shard reproduction was performed for this update.

Validation passed. All three diagnostic regression cases failed before the fix and passed afterward. The complete ClickHouse adapter module passed 68 tests. make style passed, and all four make fast-test batches passed with 2,887 tests and 13 skips. New-head CI is separate from these local results.

@nicksspirit

Copy link
Copy Markdown
Collaborator

Thanks @mday-io, the warnings and the docs caveat look good, and the test checking the exact message per failure is a nice touch. Appreciate the honest answer on the multi-shard case and the follow-up.

One small thing: could you switch the three new warnings to f-strings? The engine adapters mostly use them, for example logger.info(f"Loading dataframe to BigQuery. Table Path: {table.path}") in bigquery.py. The three are here:

, L1169 and L1196. For example:

logger.warning(f"Failed to determine engine for ClickHouse database '{database}'; keeping ON CLUSTER: {ex}")

The tests compare the exact message text, so they should still pass once the text matches.

The repo mixes both styles today, so this isn't something you could have known. I'll put up a separate PR to add a lint rule so only f-strings are allowed from here on.

Everything else looks good to me. Thanks again!

Signed-off-by: Michael Day <mdaytn@gmail.com>
@mday-io

mday-io commented Oct 15, 2026

Copy link
Copy Markdown
Collaborator Author

Switched all three warnings to f-strings in 658ab8b8f, preserving the exact message text. The existing three message/recovery cases, make style, and all four make fast-test batches passed (2,891 tests, 13 skips). Independent review found no issues.

@nicksspirit
nicksspirit merged commit 3891d65 into SQLMesh:main Oct 15, 2026
34 checks passed
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