diff --git a/CHANGES.txt b/CHANGES.txt index 5f08069bda..812e02051a 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,7 @@ 3.11.8 * Frozen RawTuple is not annotated with frozen in the toString method (CASSANDRA-15857) Merged from 3.0: + * Forbid altering UDTs used in partition keys (CASSANDRA-15933) * Fix empty/null json string representation (CASSANDRA-15896) Merged from 2.2: * Fix CQL parsing of collections when the column type is reversed (CASSANDRA-15814) diff --git a/src/java/org/apache/cassandra/cql3/statements/AlterTypeStatement.java b/src/java/org/apache/cassandra/cql3/statements/AlterTypeStatement.java index 71d19fa37e..d0e2f33979 100644 --- a/src/java/org/apache/cassandra/cql3/statements/AlterTypeStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/AlterTypeStatement.java @@ -31,6 +31,10 @@ import org.apache.cassandra.service.MigrationManager; import org.apache.cassandra.service.QueryState; import org.apache.cassandra.transport.Event; +import static com.google.common.collect.Iterables.any; +import static com.google.common.collect.Iterables.filter; +import static com.google.common.collect.Iterables.transform; + public abstract class AlterTypeStatement extends SchemaAlteringStatement { protected final UTName name; @@ -262,12 +266,31 @@ public abstract class AlterTypeStatement extends SchemaAlteringStatement if (addType.referencesUserType(toUpdate.getNameAsString())) throw new InvalidRequestException(String.format("Cannot add new field %s of type %s to type %s as this would create a circular reference", fieldName, type, name)); + Collection tablesWithTypeInPartitionKey = findTablesReferencingTypeInPartitionKey(ksm, name.getStringTypeName()); + if (!tablesWithTypeInPartitionKey.isEmpty()) + { + String message = + String.format("Cannot add new field %s of type %s to user type %s.%s as the type is being used in partition key by the following tables: %s", + fieldName, type, ksm.name, toUpdate.getNameAsString(), + String.join(", ", transform(tablesWithTypeInPartitionKey, table -> table.ksName + '.' + table.cfName))); + throw new InvalidRequestException(message); + } + List> newTypes = new ArrayList<>(toUpdate.size() + 1); newTypes.addAll(toUpdate.fieldTypes()); newTypes.add(addType); return new UserType(toUpdate.keyspace, toUpdate.name, newNames, newTypes, toUpdate.isMultiCell()); } + + private static Collection findTablesReferencingTypeInPartitionKey(KeyspaceMetadata keyspace, String userTypeName) + { + Collection tables = new ArrayList<>(); + filter(keyspace.tablesAndViews(), + table -> any(table.partitionKeyColumns(), column -> column.type.referencesUserType(userTypeName))) + .forEach(tables::add); + return tables; + } } private static class Renames extends AlterTypeStatement diff --git a/test/unit/org/apache/cassandra/cql3/validation/operations/AlterTest.java b/test/unit/org/apache/cassandra/cql3/validation/operations/AlterTest.java index 238dd072b8..1d6b064086 100644 --- a/test/unit/org/apache/cassandra/cql3/validation/operations/AlterTest.java +++ b/test/unit/org/apache/cassandra/cql3/validation/operations/AlterTest.java @@ -547,4 +547,23 @@ public class AlterTest extends CQLTester execute("ALTER TABLE %s ALTER value1 TYPE 'org.apache.cassandra.db.marshal.BytesType'"); } + @Test + public void testAlterTypeUsedInPartitionKey() throws Throwable + { + // frozen UDT used directly in a partition key + String type1 = createType("CREATE TYPE %s (v1 int)"); + String table1 = createTable("CREATE TABLE %s (pk frozen<" + type1 + ">, val int, PRIMARY KEY(pk));"); + + // frozen UDT used in a frozen UDT used in a partition key + String type2 = createType("CREATE TYPE %s (v1 frozen<" + type1 + ">, v2 frozen<" + type1 + ">)"); + String table2 = createTable("CREATE TABLE %s (pk frozen<" + type2 + ">, val int, PRIMARY KEY(pk));"); + + // frozen UDT used in a frozen collection used in a partition key + String table3 = createTable("CREATE TABLE %s (pk frozen>>, val int, PRIMARY KEY(pk));"); + + // assert that ALTER fails and that the error message contains all the names of the table referencing it + assertInvalidMessage(table1, format("ALTER TYPE %s.%s ADD v2 int;", keyspace(), type1)); + assertInvalidMessage(table2, format("ALTER TYPE %s.%s ADD v2 int;", keyspace(), type1)); + assertInvalidMessage(table3, format("ALTER TYPE %s.%s ADD v2 int;", keyspace(), type1)); + } }