diff --git a/docs/en/sql-reference/statements/alter/column.md b/docs/en/sql-reference/statements/alter/column.md index fb16dacb7c8..45829ea3d8f 100644 --- a/docs/en/sql-reference/statements/alter/column.md +++ b/docs/en/sql-reference/statements/alter/column.md @@ -272,7 +272,7 @@ ALTER TABLE table_name MODIFY COLUMN column_name RESET SETTING max_compress_bloc ## MATERIALIZE COLUMN -Materializes a column with a `DEFAULT` or `MATERIALIZED` value expression. When adding a materialized column using `ALTER TABLE table_name ADD COLUMN column_name MATERIALIZED`, existing rows without materialized values are not automatically filled. `MATERIALIZE COLUMN` statement can be used to rewrite existing column data after a `DEFAULT` or `MATERIALIZED` expression has been added or updated (which only updates the metadata but does not change existing data). +Materializes a column with a `DEFAULT` or `MATERIALIZED` value expression. When adding a materialized column using `ALTER TABLE table_name ADD COLUMN column_name MATERIALIZED`, existing rows without materialized values are not automatically filled. `MATERIALIZE COLUMN` statement can be used to rewrite existing column data after a `DEFAULT` or `MATERIALIZED` expression has been added or updated (which only updates the metadata but does not change existing data). Note that materializing a column in the sort key is an invalid operation because it would break the sort order. Implemented as a [mutation](/docs/en/sql-reference/statements/alter/index.md#mutations). For columns with a new or updated `MATERIALIZED` value expression, all existing rows are rewritten. diff --git a/src/Interpreters/MutationsInterpreter.cpp b/src/Interpreters/MutationsInterpreter.cpp index a35353a6b2a..a874515b255 100644 --- a/src/Interpreters/MutationsInterpreter.cpp +++ b/src/Interpreters/MutationsInterpreter.cpp @@ -791,6 +791,13 @@ void MutationsInterpreter::prepare(bool dry_run) if (stages.size() == 1) /// First stage only supports filtering and can't update columns. stages.emplace_back(context); + // Can't materialize a column in the sort key + Names sort_columns = metadata_snapshot->getSortingKeyColumns(); + if (std::find(sort_columns.begin(), sort_columns.end(), command.column_name) != sort_columns.end()) + { + throw Exception(ErrorCodes::CANNOT_UPDATE_COLUMN, "Failed to materialize column {} because it's in the sort key. Doing so would break sort order", command.column_name); + } + const auto & column = columns_desc.get(command.column_name); if (!column.default_desc.expression) diff --git a/tests/queries/0_stateless/03263_forbid_materialize_sort_key.reference b/tests/queries/0_stateless/03263_forbid_materialize_sort_key.reference new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/queries/0_stateless/03263_forbid_materialize_sort_key.sql b/tests/queries/0_stateless/03263_forbid_materialize_sort_key.sql new file mode 100644 index 00000000000..a27be98e0aa --- /dev/null +++ b/tests/queries/0_stateless/03263_forbid_materialize_sort_key.sql @@ -0,0 +1,24 @@ +CREATE TABLE IF NOT EXISTS test (a UInt64) ENGINE=MergeTree() ORDER BY a; + +INSERT INTO test (a) SELECT 1 FROM numbers(1000); + +ALTER TABLE test ADD COLUMN b Float64 AFTER a, MODIFY ORDER BY (a, b); +ALTER TABLE test MODIFY COLUMN b DEFAULT rand64() % 100000; +ALTER TABLE test MATERIALIZE COLUMN b; -- { serverError CANNOT_UPDATE_COLUMN } +DROP TABLE IF EXISTS test; + + +CREATE TABLE IF NOT EXISTS tab (x UInt32, y UInt32) engine = MergeTree ORDER BY tuple(); +CREATE DICTIONARY IF NOT EXISTS dict (x UInt32, y UInt32) primary key x source(clickhouse(table 'tab')) LAYOUT(FLAT()) LIFETIME(MIN 0 MAX 1000); +INSERT INTO tab VALUES (1, 2), (3, 4); +SYSTEM RELOAD DICTIONARY dict; +CREATE TABLE IF NOT EXISTS tab2 (x UInt32, y UInt32 materialized dictGet(dict, 'y', x)) engine = MergeTree ORDER BY (y); +INSERT INTO tab2 (x) VALUES (1), (3); +TRUNCATE TABLE tab; +INSERT INTO tab VALUES (1, 4), (3, 2); +SYSTEM RELOAD DICTIONARY dict; +SET mutations_sync=2; +ALTER TABLE tab2 materialize column y; -- { serverError CANNOT_UPDATE_COLUMN } +DROP TABLE IF EXISTS tab2; +DROP DICTIONARY IF EXISTS dict; +DROP TABLE IF EXISTS tab;