Skip to content

Commit d790e77

Browse files
Merge pull request ClickHouse#86959 from ClickHouse/backport/25.8/85985
Backport ClickHouse#85985 to 25.8: Fix alter update of a column with a subcolumn used in other column materialized expression
2 parents 8ace61b + 4f3d2bd commit d790e77

5 files changed

+133
-2
lines changed

src/Interpreters/MutationsInterpreter.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <Interpreters/MutationsInterpreter.h>
77
#include <Interpreters/TreeRewriter.h>
88
#include <Interpreters/MutationsNonDeterministicHelpers.h>
9+
#include <Interpreters/replaceSubcolumnsToGetSubcolumnFunctionInQuery.h>
910
#include <Storages/MergeTree/MergeTreeData.h>
1011
#include <Storages/MergeTree/StorageFromMergeTreeDataPart.h>
1112
#include <Storages/StorageMergeTree.h>
@@ -528,7 +529,7 @@ static void validateUpdateColumns(
528529
{
529530
throw Exception(ErrorCodes::CANNOT_UPDATE_COLUMN,
530531
"Updated column {} affects MATERIALIZED column {}, which is a key column. "
531-
"Cannot UPDATE it.", backQuote(column_name), backQuote(materialized));
532+
"Cannot UPDATE it", backQuote(column_name), backQuote(materialized));
532533
}
533534
}
534535
}
@@ -676,6 +677,8 @@ void MutationsInterpreter::prepare(bool dry_run)
676677
if (column.default_desc.kind == ColumnDefaultKind::Materialized && available_columns_set.contains(column.name))
677678
{
678679
auto query = column.default_desc.expression->clone();
680+
/// Replace all subcolumns to the getSubcolumn() to get only top level columns as required source columns.
681+
replaceSubcolumnsToGetSubcolumnFunctionInQuery(query, all_columns);
679682
auto syntax_result = TreeRewriter(context).analyze(query, all_columns);
680683
for (const auto & dependency : syntax_result->requiredSourceColumns())
681684
if (updated_columns.contains(dependency))
@@ -855,10 +858,15 @@ void MutationsInterpreter::prepare(bool dry_run)
855858
{
856859
auto type_literal = std::make_shared<ASTLiteral>(column.type->getName());
857860

858-
auto materialized_column = makeASTFunction("_CAST",
861+
ASTPtr materialized_column = makeASTFunction("_CAST",
859862
column.default_desc.expression->clone(),
860863
type_literal);
861864

865+
/// We need to replace all subcolumns used in materialized expression to getSubcolumn() function,
866+
/// because otherwise subcolumns are extracted before the source column is updated and we get
867+
/// old subcolumns values.
868+
replaceSubcolumnsToGetSubcolumnFunctionInQuery(materialized_column, all_columns);
869+
862870
stages.back().column_to_updated.emplace(
863871
column.name,
864872
materialized_column);
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#include <Interpreters/replaceSubcolumnsToGetSubcolumnFunctionInQuery.h>
2+
#include <Parsers/ASTFunction.h>
3+
#include <Parsers/ASTIdentifier.h>
4+
#include <Parsers/ASTLiteral.h>
5+
#include <DataTypes/IDataType.h>
6+
#include <DataTypes/NestedUtils.h>
7+
8+
namespace DB
9+
{
10+
11+
void replaceSubcolumnsToGetSubcolumnFunctionInQuery(ASTPtr & ast, const NamesAndTypesList & columns)
12+
{
13+
if (auto * identifier = ast->as<ASTIdentifier>())
14+
{
15+
if (columns.contains(identifier->getColumnName()))
16+
return;
17+
18+
auto [column_name, subcolumn_name] = Nested::splitName(identifier->getColumnName());
19+
auto column = columns.tryGetByName(column_name);
20+
if (!column || !column->type->hasSubcolumn(subcolumn_name))
21+
return;
22+
23+
ast = makeASTFunction("getSubcolumn", std::make_shared<ASTIdentifier>(column_name), std::make_shared<ASTLiteral>(subcolumn_name));
24+
}
25+
else if (auto * node = ast->as<ASTFunction>())
26+
{
27+
if (node->arguments)
28+
{
29+
for (auto & child : node->arguments->children)
30+
replaceSubcolumnsToGetSubcolumnFunctionInQuery(child, columns);
31+
}
32+
}
33+
else
34+
{
35+
for (auto & child : ast->children)
36+
replaceSubcolumnsToGetSubcolumnFunctionInQuery(child, columns);
37+
}
38+
}
39+
40+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#pragma once
2+
3+
#include <Core/NamesAndTypes.h>
4+
#include <Parsers/IAST_fwd.h>
5+
#include <base/types.h>
6+
7+
namespace DB
8+
{
9+
10+
/// Replace subcolumns to getSubcolumn() function.
11+
void replaceSubcolumnsToGetSubcolumnFunctionInQuery(ASTPtr & ast, const NamesAndTypesList & columns);
12+
13+
}
14+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
(1) 1
2+
(2) 2
3+
(3) 3
4+
(1) 43
5+
(2) 44
6+
(3) 45
7+
(1) 1
8+
{"a":1} 1
9+
{"a":2} 2
10+
{"a":3} 3
11+
{"a":1} 43
12+
{"a":2} 44
13+
{"a":3} 45
14+
{"a":1} 1
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
drop table if exists test;
2+
create table test (t Tuple(a UInt32), a UInt32 materialized t.a) engine=MergeTree() order by tuple();
3+
insert into test select tuple(1);
4+
select t, a from test;
5+
alter table test update t = tuple(2) where 1 settings mutations_sync=1;
6+
select t, a from test;
7+
alter table test update t = tuple(3) where 1 settings mutations_sync=1;
8+
select t, a from test;
9+
drop table test;
10+
11+
drop table if exists test;
12+
create table test (t Tuple(a UInt32), a UInt32 materialized t.a + 42) engine=MergeTree() order by tuple();
13+
insert into test select tuple(1);
14+
select t, a from test;
15+
alter table test update t = tuple(2) where 1 settings mutations_sync=1;
16+
select t, a from test;
17+
alter table test update t = tuple(3) where 1 settings mutations_sync=1;
18+
select t, a from test;
19+
drop table test;
20+
21+
22+
create table test (t Tuple(a UInt32), a UInt32 materialized t.a) engine=MergeTree() order by a;
23+
insert into test select tuple(1);
24+
select t, a from test;
25+
alter table test update t = tuple(2) where 1 settings mutations_sync=1; -- {serverError CANNOT_UPDATE_COLUMN}
26+
drop table test;
27+
28+
drop table if exists test;
29+
create table test (json JSON, a UInt32 materialized json.a::UInt32) engine=MergeTree() order by tuple();
30+
insert into test select '{"a" : 1}';
31+
select json, a from test;
32+
alter table test update json = '{"a" : 2}' where 1 settings mutations_sync=1;
33+
select json, a from test;
34+
alter table test update json = '{"a" : 3}' where 1 settings mutations_sync=1;
35+
select json, a from test;
36+
drop table test;
37+
38+
drop table if exists test;
39+
create table test (json JSON, a UInt32 materialized json.a::UInt32 + 42) engine=MergeTree() order by tuple();
40+
insert into test select '{"a" : 1}';
41+
select json, a from test;
42+
alter table test update json = '{"a" : 2}' where 1 settings mutations_sync=1;
43+
select json, a from test;
44+
alter table test update json = '{"a" : 3}' where 1 settings mutations_sync=1;
45+
select json, a from test;
46+
drop table test;
47+
48+
49+
50+
create table test (json JSON, a UInt32 materialized json.a::UInt32) engine=MergeTree() order by a;
51+
insert into test select '{"a" : 1}';
52+
select json, a from test;
53+
alter table test update json = '{"a" : 2}' where 1 settings mutations_sync=1; -- {serverError CANNOT_UPDATE_COLUMN}
54+
drop table test;
55+

0 commit comments

Comments
 (0)