Skip to content

Commit c4d4ca8

Browse files
Merge pull request ClickHouse#57474 from ClickHouse/backport/23.3/56948
Backport ClickHouse#56948 to 23.3: Prevent incompatible ALTER of projection columns
2 parents 9478b99 + 5b27800 commit c4d4ca8

7 files changed

+128
-9
lines changed

src/Storages/AlterCommands.cpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
#include <Interpreters/TreeRewriter.h>
1515
#include <Interpreters/RenameColumnVisitor.h>
1616
#include <Interpreters/GinFilter.h>
17+
#include <Interpreters/inplaceBlockConversions.h>
18+
#include <Interpreters/InterpreterSelectWithUnionQuery.h>
19+
#include <Interpreters/InterpreterSelectQueryAnalyzer.h>
1720
#include <Parsers/ASTAlterQuery.h>
1821
#include <Parsers/ASTColumnDeclaration.h>
1922
#include <Parsers/ASTConstraintDeclaration.h>
@@ -42,6 +45,8 @@ namespace ErrorCodes
4245
extern const int LOGICAL_ERROR;
4346
extern const int DUPLICATE_COLUMN;
4447
extern const int NOT_IMPLEMENTED;
48+
extern const int SUPPORT_IS_DISABLED;
49+
extern const int ALTER_OF_COLUMN_IS_FORBIDDEN;
4550
}
4651

4752
namespace
@@ -962,7 +967,15 @@ void AlterCommands::apply(StorageInMemoryMetadata & metadata, ContextPtr context
962967
{
963968
try
964969
{
965-
new_projections.add(ProjectionDescription::getProjectionFromAST(projection.definition_ast, metadata_copy.columns, context));
970+
/// Check if we can still build projection from new metadata.
971+
auto new_projection = ProjectionDescription::getProjectionFromAST(projection.definition_ast, metadata_copy.columns, context);
972+
/// Check if new metadata has the same keys as the old one.
973+
if (!blocksHaveEqualStructure(projection.sample_block_for_keys, new_projection.sample_block_for_keys))
974+
throw Exception(ErrorCodes::ALTER_OF_COLUMN_IS_FORBIDDEN, "Cannot ALTER column");
975+
/// Check if new metadata is convertible from old metadata for projection.
976+
Block old_projection_block = projection.sample_block;
977+
performRequiredConversions(old_projection_block, new_projection.sample_block.getNamesAndTypesList(), context);
978+
new_projections.add(std::move(new_projection));
966979
}
967980
catch (Exception & exception)
968981
{

src/Storages/MergeTree/MergeTreeData.cpp

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2931,12 +2931,6 @@ void MergeTreeData::checkAlterIsPossible(const AlterCommands & commands, Context
29312931
columns_alter_type_check_safe_for_partition.insert(col);
29322932
}
29332933

2934-
for (const auto & index : old_metadata.getSecondaryIndices())
2935-
{
2936-
for (const String & col : index.expression->getRequiredColumns())
2937-
columns_alter_type_forbidden.insert(col);
2938-
}
2939-
29402934
if (old_metadata.hasSortingKey())
29412935
{
29422936
auto sorting_key_expr = old_metadata.getSortingKey().expression;
@@ -2960,6 +2954,20 @@ void MergeTreeData::checkAlterIsPossible(const AlterCommands & commands, Context
29602954
columns_in_keys.insert(columns_alter_type_metadata_only.begin(), columns_alter_type_metadata_only.end());
29612955
columns_in_keys.insert(columns_alter_type_check_safe_for_partition.begin(), columns_alter_type_check_safe_for_partition.end());
29622956

2957+
std::unordered_map<String, String> columns_in_indices;
2958+
for (const auto & index : old_metadata.getSecondaryIndices())
2959+
{
2960+
for (const String & col : index.expression->getRequiredColumns())
2961+
columns_in_indices.emplace(col, index.name);
2962+
}
2963+
2964+
std::unordered_map<String, String> columns_in_projections;
2965+
for (const auto & projection : old_metadata.getProjections())
2966+
{
2967+
for (const String & col : projection.getRequiredColumns())
2968+
columns_in_projections.emplace(col, projection.name);
2969+
}
2970+
29632971
NameSet dropped_columns;
29642972

29652973
std::map<String, const IDataType *> old_types;
@@ -3038,6 +3046,17 @@ void MergeTreeData::checkAlterIsPossible(const AlterCommands & commands, Context
30383046
"Trying to ALTER RENAME key {} column which is a part of key expression",
30393047
backQuoteIfNeed(command.column_name));
30403048
}
3049+
3050+
/// Don't check columns in indices here. RENAME works fine with index columns.
3051+
3052+
if (auto it = columns_in_projections.find(command.column_name); it != columns_in_projections.end())
3053+
{
3054+
throw Exception(
3055+
ErrorCodes::ALTER_OF_COLUMN_IS_FORBIDDEN,
3056+
"Trying to ALTER RENAME {} column which is a part of projection {}",
3057+
backQuoteIfNeed(command.column_name),
3058+
it->second);
3059+
}
30413060
}
30423061
else if (command.type == AlterCommand::DROP_COLUMN)
30433062
{
@@ -3047,6 +3066,11 @@ void MergeTreeData::checkAlterIsPossible(const AlterCommands & commands, Context
30473066
"Trying to ALTER DROP key {} column which is a part of key expression", backQuoteIfNeed(command.column_name));
30483067
}
30493068

3069+
/// Don't check columns in indices or projections here. If required columns of indices
3070+
/// or projections get dropped, it will be checked later in AlterCommands::apply. This
3071+
/// allows projections with * to drop columns. One example can be found in
3072+
/// 02691_drop_column_with_projections_replicated.sql.
3073+
30503074
if (!command.clear)
30513075
{
30523076
const auto & deps_mv = name_deps[command.column_name];
@@ -3088,6 +3112,18 @@ void MergeTreeData::checkAlterIsPossible(const AlterCommands & commands, Context
30883112
throw Exception(ErrorCodes::ALTER_OF_COLUMN_IS_FORBIDDEN, "ALTER of key column {} is forbidden",
30893113
backQuoteIfNeed(command.column_name));
30903114

3115+
if (auto it = columns_in_indices.find(command.column_name); it != columns_in_indices.end())
3116+
{
3117+
throw Exception(
3118+
ErrorCodes::ALTER_OF_COLUMN_IS_FORBIDDEN,
3119+
"Trying to ALTER {} column which is a part of index {}",
3120+
backQuoteIfNeed(command.column_name),
3121+
it->second);
3122+
}
3123+
3124+
/// Don't check columns in projections here. If required columns of projections get
3125+
/// modified, it will be checked later in AlterCommands::apply.
3126+
30913127
if (command.type == AlterCommand::MODIFY_COLUMN)
30923128
{
30933129
if (columns_alter_type_check_safe_for_partition.contains(command.column_name))

tests/queries/0_stateless/01213_alter_rename_primary_key_zookeeper_long.sql

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,4 @@ ALTER TABLE table_for_rename_with_primary_key RENAME COLUMN key2 TO renamed_key2
5252

5353
ALTER TABLE table_for_rename_with_primary_key RENAME COLUMN key3 TO renamed_key3; --{serverError 524}
5454

55-
ALTER TABLE table_for_rename_with_primary_key RENAME COLUMN value1 TO renamed_value1; --{serverError 524}
56-
5755
DROP TABLE IF EXISTS table_for_rename_with_primary_key;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
1
2+
1
3+
1
4+
1
5+
1
6+
1
7+
1
8+
1
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
DROP TABLE IF EXISTS t;
2+
3+
CREATE TABLE t (uid Int16, name String, age Nullable(Int8), i Int16, j Int16, projection p1 (select name, age, uniq(i), count(j) group by name, age)) ENGINE=MergeTree order by uid settings index_granularity = 1;
4+
5+
INSERT INTO t VALUES (1231, 'John', 11, 1, 1), (6666, 'Ksenia', 1, 2, 2), (8888, 'Alice', 1, 3, 3), (6667, 'Ksenia', null, 4, 4);
6+
7+
-- Cannot ALTER, which breaks key column of projection.
8+
ALTER TABLE t MODIFY COLUMN age Nullable(Int32); -- { serverError ALTER_OF_COLUMN_IS_FORBIDDEN }
9+
10+
-- Cannot ALTER, uniq(Int16) is not compatible with uniq(Int32).
11+
ALTER TABLE t MODIFY COLUMN i Int32; -- { serverError CANNOT_CONVERT_TYPE }
12+
13+
SYSTEM STOP MERGES t;
14+
15+
SET alter_sync = 0;
16+
17+
-- Can ALTER, count(Int16) is compatible with count(Int32).
18+
ALTER TABLE t MODIFY COLUMN j Int32;
19+
20+
-- Projection query works without mutation applied.
21+
SELECT count(j) FROM t GROUP BY name, age;
22+
23+
SYSTEM START MERGES t;
24+
25+
SET alter_sync = 1;
26+
27+
-- Another ALTER to wait for.
28+
ALTER TABLE t MODIFY COLUMN j Int64 SETTINGS mutations_sync = 2;
29+
30+
-- Projection query works with mutation applied.
31+
SELECT count(j) FROM t GROUP BY name, age;
32+
33+
DROP TABLE t;

tests/queries/0_stateless/02920_rename_column_of_skip_indices.reference

Whitespace-only changes.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
DROP TABLE IF EXISTS t;
2+
3+
CREATE TABLE t
4+
(
5+
key1 UInt64,
6+
value1 String,
7+
value2 String,
8+
INDEX idx (value1) TYPE set(10) GRANULARITY 1
9+
)
10+
ENGINE MergeTree ORDER BY key1 SETTINGS index_granularity = 1;
11+
12+
INSERT INTO t SELECT toDate('2019-10-01') + number % 3, toString(number), toString(number) from numbers(9);
13+
14+
SYSTEM STOP MERGES t;
15+
16+
SET alter_sync = 0;
17+
18+
ALTER TABLE t RENAME COLUMN value1 TO value11;
19+
20+
-- Index works without mutation applied.
21+
SELECT * FROM t WHERE value11 = '000' SETTINGS max_rows_to_read = 0;
22+
23+
SYSTEM START MERGES t;
24+
25+
-- Another ALTER to wait for.
26+
ALTER TABLE t RENAME COLUMN value11 TO value12 SETTINGS mutations_sync = 2;
27+
28+
-- Index works with mutation applied.
29+
SELECT * FROM t WHERE value12 = '000' SETTINGS max_rows_to_read = 0;
30+
31+
DROP TABLE t;

0 commit comments

Comments
 (0)