Description
PR #99281 introduced && affected_materialized.contains(column.name) at line 863 of MutationsInterpreter.cpp to avoid recalculating MATERIALIZED columns that depend on EPHEMERAL columns (which cannot be read from disk during mutations). However, this guard also silently skips transitively-dependent MATERIALIZED columns, freezing them at their INSERT-time value permanently.
How to reproduce
CREATE TABLE t_transitive (
x Int32,
m1 Int32 MATERIALIZED x + 1,
m2 Int32 MATERIALIZED m1 + 1
) ENGINE = MergeTree ORDER BY tuple();
INSERT INTO t_transitive (x) VALUES (10);
-- m1=11, m2=12
SET mutations_sync = 1;
ALTER TABLE t_transitive UPDATE x = 20 WHERE 1;
ALTER TABLE t_transitive UPDATE x = 30 WHERE 1;
SELECT x, m1, m2 FROM t_transitive;
-- Returns: 30 31 12 ← m2 frozen at INSERT-time value
-- Expected: 30 31 32 (or at minimum 30 31 22 as before #99281)
Behavior before and after #99281
After mutation UPDATE x=30 |
x |
m1 |
m2 |
| Before #99281 |
30 |
31 |
22 (recalculated each mutation using current on-disk m1) |
| After #99281 |
30 |
31 |
12 (frozen at INSERT-time, never recalculated again) |
Before #99281, m2 was recalculated on every mutation (using the pre-stage m1 value — a pre-existing issue tracked in #50237). After #99281, m2 is never recalculated at all because m1 is not in updated_columns, so it never appears in affected_materialized, so the guard skips it.
Root cause
In MutationsInterpreter::prepare, affected_materialized is built from direct dependencies only (updated_columns → materialized columns). The new guard at line 863:
if (column.default_desc.kind == ColumnDefaultKind::Materialized
&& affected_materialized.contains(column.name)) // ← regression
was intended to exclude ephemeral-dependent MATERIALIZED columns from recalculation (which is correct — they cannot be recalculated from disk). But it also excludes all transitively-dependent MATERIALIZED columns that don't directly depend on an updated regular column.
Related
Description
PR #99281 introduced
&& affected_materialized.contains(column.name)at line 863 ofMutationsInterpreter.cppto avoid recalculating MATERIALIZED columns that depend on EPHEMERAL columns (which cannot be read from disk during mutations). However, this guard also silently skips transitively-dependent MATERIALIZED columns, freezing them at their INSERT-time value permanently.How to reproduce
Behavior before and after #99281
UPDATE x=30Before #99281,
m2was recalculated on every mutation (using the pre-stagem1value — a pre-existing issue tracked in #50237). After #99281,m2is never recalculated at all becausem1is not inupdated_columns, so it never appears inaffected_materialized, so the guard skips it.Root cause
In
MutationsInterpreter::prepare,affected_materializedis built from direct dependencies only (updated_columns → materialized columns). The new guard at line 863:was intended to exclude ephemeral-dependent MATERIALIZED columns from recalculation (which is correct — they cannot be recalculated from disk). But it also excludes all transitively-dependent MATERIALIZED columns that don't directly depend on an updated regular column.
Related
MATERIALIZEDcolumns after mutation #50237 (pre-existing issue: transitive MATERIALIZED columns recalculated with stale intermediate value — separate problem)