This is an automated email from the ASF dual-hosted git repository.

aleksey pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra.git

commit 9e61249764a3a7e08abded87602b118e2d4e4681
Merge: a821214 c61f390
Author: Aleksey Yeshchenko <alek...@apache.org>
AuthorDate: Mon Jul 27 16:20:02 2020 +0100

    Merge branch 'cassandra-3.11' into trunk

 CHANGES.txt                                        |  1 +
 .../cql3/statements/schema/AlterTypeStatement.java | 22 ++++++++++++++++++++++
 .../cql3/validation/operations/AlterTest.java      | 20 ++++++++++++++++++++
 3 files changed, 43 insertions(+)

diff --cc CHANGES.txt
index a36f379,812e020..1acd5a2
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@@ -1,10 -1,7 +1,11 @@@
 -3.11.8
 +4.0-beta2
++ * Forbid altering UDTs used in partition keys (CASSANDRA-15933)
 + * Fix version parsing logic when upgrading from 3.0 (CASSANDRA-15973)
 + * Optimize NoSpamLogger use in hot paths (CASSANDRA-15766)
 + * Verify sstable components on startup (CASSANDRA-15945)
 +Merged from 3.11:
   * 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 --cc 
src/java/org/apache/cassandra/cql3/statements/schema/AlterTypeStatement.java
index f883038,0000000..9228f34
mode 100644,000000..100644
--- 
a/src/java/org/apache/cassandra/cql3/statements/schema/AlterTypeStatement.java
+++ 
b/src/java/org/apache/cassandra/cql3/statements/schema/AlterTypeStatement.java
@@@ -1,234 -1,0 +1,256 @@@
 +/*
 + * Licensed to the Apache Software Foundation (ASF) under one
 + * or more contributor license agreements.  See the NOTICE file
 + * distributed with this work for additional information
 + * regarding copyright ownership.  The ASF licenses this file
 + * to you under the Apache License, Version 2.0 (the
 + * "License"); you may not use this file except in compliance
 + * with the License.  You may obtain a copy of the License at
 + *
 + *     http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing, software
 + * distributed under the License is distributed on an "AS IS" BASIS,
 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 + * See the License for the specific language governing permissions and
 + * limitations under the License.
 + */
 +package org.apache.cassandra.cql3.statements.schema;
 +
 +import java.util.ArrayList;
++import java.util.Collection;
 +import java.util.HashMap;
 +import java.util.List;
 +import java.util.Map;
 +
 +import org.apache.cassandra.audit.AuditLogContext;
 +import org.apache.cassandra.audit.AuditLogEntryType;
 +import org.apache.cassandra.auth.Permission;
 +import org.apache.cassandra.cql3.*;
 +import org.apache.cassandra.db.marshal.AbstractType;
 +import org.apache.cassandra.db.marshal.UserType;
 +import org.apache.cassandra.schema.KeyspaceMetadata;
 +import org.apache.cassandra.schema.Keyspaces;
++import org.apache.cassandra.schema.TableMetadata;
 +import org.apache.cassandra.service.ClientState;
 +import org.apache.cassandra.transport.Event.SchemaChange;
 +import org.apache.cassandra.transport.Event.SchemaChange.Change;
 +import org.apache.cassandra.transport.Event.SchemaChange.Target;
 +
++import static com.google.common.collect.Iterables.any;
++import static com.google.common.collect.Iterables.filter;
++import static com.google.common.collect.Iterables.transform;
 +import static java.lang.String.join;
 +import static java.util.function.Predicate.isEqual;
 +import static java.util.stream.Collectors.toList;
 +
 +import static org.apache.cassandra.utils.ByteBufferUtil.bytes;
 +
 +public abstract class AlterTypeStatement extends AlterSchemaStatement
 +{
 +    protected final String typeName;
 +
 +    public AlterTypeStatement(String keyspaceName, String typeName)
 +    {
 +        super(keyspaceName);
 +        this.typeName = typeName;
 +    }
 +
 +    public void authorize(ClientState client)
 +    {
 +        client.ensureKeyspacePermission(keyspaceName, Permission.ALTER);
 +    }
 +
 +    SchemaChange schemaChangeEvent(Keyspaces.KeyspacesDiff diff)
 +    {
 +        return new SchemaChange(Change.UPDATED, Target.TYPE, keyspaceName, 
typeName);
 +    }
 +
 +    public Keyspaces apply(Keyspaces schema)
 +    {
 +        KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
 +
 +        UserType type = null == keyspace
 +                      ? null
 +                      : keyspace.types.getNullable(bytes(typeName));
 +
 +        if (null == type)
 +            throw ire("Type %s.%s doesn't exist", keyspaceName, typeName);
 +
 +        return 
schema.withAddedOrUpdated(keyspace.withUpdatedUserType(apply(keyspace, type)));
 +    }
 +
 +    abstract UserType apply(KeyspaceMetadata keyspace, UserType type);
 +
 +    @Override
 +    public AuditLogContext getAuditLogContext()
 +    {
 +        return new AuditLogContext(AuditLogEntryType.ALTER_TYPE, 
keyspaceName, typeName);
 +    }
 +
 +    public String toString()
 +    {
 +        return String.format("%s (%s, %s)", getClass().getSimpleName(), 
keyspaceName, typeName);
 +    }
 +
 +    private static final class AddField extends AlterTypeStatement
 +    {
 +        private final FieldIdentifier fieldName;
 +        private final CQL3Type.Raw type;
 +
 +        private AddField(String keyspaceName, String typeName, 
FieldIdentifier fieldName, CQL3Type.Raw type)
 +        {
 +            super(keyspaceName, typeName);
 +            this.fieldName = fieldName;
 +            this.type = type;
 +        }
 +
 +        UserType apply(KeyspaceMetadata keyspace, UserType userType)
 +        {
 +            if (userType.fieldPosition(fieldName) >= 0)
 +                throw ire("Cannot add field %s to type %s: a field with name 
%s already exists", fieldName, userType.getCqlTypeName(), fieldName);
 +
 +            AbstractType<?> fieldType = type.prepare(keyspaceName, 
keyspace.types).getType();
 +            if (fieldType.referencesUserType(userType.name))
 +                throw ire("Cannot add new field %s of type %s to user type %s 
as it would create a circular reference", fieldName, type, 
userType.getCqlTypeName());
 +
++            Collection<TableMetadata> tablesWithTypeInPartitionKey = 
findTablesReferencingTypeInPartitionKey(keyspace, userType);
++            if (!tablesWithTypeInPartitionKey.isEmpty())
++            {
++                throw ire("Cannot add new field %s of type %s to user type %s 
as the type is being used in partition key by the following tables: %s",
++                          fieldName, type, userType.getCqlTypeName(),
++                          String.join(", ", 
transform(tablesWithTypeInPartitionKey, TableMetadata::toString)));
++            }
++
 +            List<FieldIdentifier> fieldNames = new 
ArrayList<>(userType.fieldNames()); fieldNames.add(fieldName);
 +            List<AbstractType<?>> fieldTypes = new 
ArrayList<>(userType.fieldTypes()); fieldTypes.add(fieldType);
 +
 +            return new UserType(keyspaceName, userType.name, fieldNames, 
fieldTypes, true);
 +        }
++
++        private static Collection<TableMetadata> 
findTablesReferencingTypeInPartitionKey(KeyspaceMetadata keyspace, UserType 
userType)
++        {
++            Collection<TableMetadata> tables = new ArrayList<>();
++            filter(keyspace.tablesAndViews(),
++                   table -> any(table.partitionKeyColumns(), column -> 
column.type.referencesUserType(userType.name)))
++                  .forEach(tables::add);
++            return tables;
++        }
 +    }
 +
 +    private static final class RenameFields extends AlterTypeStatement
 +    {
 +        private final Map<FieldIdentifier, FieldIdentifier> renamedFields;
 +
 +        private RenameFields(String keyspaceName, String typeName, 
Map<FieldIdentifier, FieldIdentifier> renamedFields)
 +        {
 +            super(keyspaceName, typeName);
 +            this.renamedFields = renamedFields;
 +        }
 +
 +        UserType apply(KeyspaceMetadata keyspace, UserType userType)
 +        {
 +            List<String> dependentAggregates =
 +                keyspace.functions
 +                        .udas()
 +                        .filter(uda -> null != uda.initialCondition() && 
uda.stateType().referencesUserType(userType.name))
 +                        .map(uda -> uda.name().toString())
 +                        .collect(toList());
 +
 +            if (!dependentAggregates.isEmpty())
 +            {
 +                throw ire("Cannot alter user type %s as it is still used in 
INITCOND by aggregates %s",
 +                          userType.getCqlTypeName(),
 +                          join(", ", dependentAggregates));
 +            }
 +
 +            List<FieldIdentifier> fieldNames = new 
ArrayList<>(userType.fieldNames());
 +
 +            renamedFields.forEach((oldName, newName) ->
 +            {
 +                int idx = userType.fieldPosition(oldName);
 +                if (idx < 0)
 +                    throw ire("Unkown field %s in user type %s", oldName, 
keyspaceName, userType.getCqlTypeName());
 +                fieldNames.set(idx, newName);
 +            });
 +
 +            fieldNames.forEach(name ->
 +            {
 +                if (fieldNames.stream().filter(isEqual(name)).count() > 1)
 +                    throw ire("Duplicate field name %s in type %s", name, 
keyspaceName, userType.getCqlTypeName());
 +            });
 +
 +            return new UserType(keyspaceName, userType.name, fieldNames, 
userType.fieldTypes(), true);
 +        }
 +    }
 +
 +    private static final class AlterField extends AlterTypeStatement
 +    {
 +        private AlterField(String keyspaceName, String typeName)
 +        {
 +            super(keyspaceName, typeName);
 +        }
 +
 +        UserType apply(KeyspaceMetadata keyspace, UserType userType)
 +        {
 +            throw ire("Alterting field types is no longer supported");
 +        }
 +    }
 +
 +    public static final class Raw extends CQLStatement.Raw
 +    {
 +        private enum Kind
 +        {
 +            ADD_FIELD, RENAME_FIELDS, ALTER_FIELD
 +        }
 +
 +        private final UTName name;
 +
 +        private Kind kind;
 +
 +        // ADD
 +        private FieldIdentifier newFieldName;
 +        private CQL3Type.Raw newFieldType;
 +
 +        // RENAME
 +        private final Map<FieldIdentifier, FieldIdentifier> renamedFields = 
new HashMap<>();
 +
 +        public Raw(UTName name)
 +        {
 +            this.name = name;
 +        }
 +
 +        public AlterTypeStatement prepare(ClientState state)
 +        {
 +            String keyspaceName = name.hasKeyspace() ? name.getKeyspace() : 
state.getKeyspace();
 +            String typeName = name.getStringTypeName();
 +
 +            switch (kind)
 +            {
 +                case     ADD_FIELD: return new AddField(keyspaceName, 
typeName, newFieldName, newFieldType);
 +                case RENAME_FIELDS: return new RenameFields(keyspaceName, 
typeName, renamedFields);
 +                case   ALTER_FIELD: return new AlterField(keyspaceName, 
typeName);
 +            }
 +
 +            throw new AssertionError();
 +        }
 +
 +        public void add(FieldIdentifier name, CQL3Type.Raw type)
 +        {
 +            kind = Kind.ADD_FIELD;
 +            newFieldName = name;
 +            newFieldType = type;
 +        }
 +
 +        public void rename(FieldIdentifier from, FieldIdentifier to)
 +        {
 +            kind = Kind.RENAME_FIELDS;
 +            renamedFields.put(from, to);
 +        }
 +
 +        public void alter(FieldIdentifier name, CQL3Type.Raw type)
 +        {
 +            kind = Kind.ALTER_FIELD;
 +        }
 +    }
 +}
diff --cc 
test/unit/org/apache/cassandra/cql3/validation/operations/AlterTest.java
index fc3a61e,1d6b064..069669d
--- a/test/unit/org/apache/cassandra/cql3/validation/operations/AlterTest.java
+++ b/test/unit/org/apache/cassandra/cql3/validation/operations/AlterTest.java
@@@ -631,4 -496,74 +631,24 @@@ public class AlterTest extends CQLTeste
  
          assertEmpty(execute("SELECT * FROM %s"));
      }
+ 
 -    /**
 -     * Test for CASSANDRA-13917
 -     */
 -    @Test
 -    public void testAlterWithCompactStaticFormat() throws Throwable
 -    {
 -        createTable("CREATE TABLE %s (a int PRIMARY KEY, b int, c int) WITH 
COMPACT STORAGE");
 -
 -        assertInvalidMessage("Cannot rename unknown column column1 in 
keyspace",
 -                             "ALTER TABLE %s RENAME column1 TO column2");
 -
 -        assertInvalidMessage("Cannot rename unknown column value in keyspace",
 -                             "ALTER TABLE %s RENAME value TO value2");
 -    }
 -
 -    /**
 -     * Test for CASSANDRA-13917
 -     */
 -    @Test
 -    public void testAlterWithCompactNonStaticFormat() throws Throwable
 -    {
 -        createTable("CREATE TABLE %s (a int, b int, PRIMARY KEY (a, b)) WITH 
COMPACT STORAGE");
 -        assertInvalidMessage("Cannot rename unknown column column1 in 
keyspace",
 -                             "ALTER TABLE %s RENAME column1 TO column2");
 -
 -        createTable("CREATE TABLE %s (a int, b int, v int, PRIMARY KEY (a, 
b)) WITH COMPACT STORAGE");
 -        assertInvalidMessage("Cannot rename unknown column column1 in 
keyspace",
 -                             "ALTER TABLE %s RENAME column1 TO column2");
 -    }
 -
 -    @Test
 -    public void testAlterTableAlterType() throws Throwable
 -    {
 -        createTable("CREATE TABLE %s (a int, b int, PRIMARY KEY (a,b)) WITH 
COMPACT STORAGE");
 -        assertInvalidMessage(String.format("Compact value type can only be 
changed to BytesType, but %s was given.",
 -                                           IntegerType.instance),
 -                             "ALTER TABLE %s ALTER value TYPE 
'org.apache.cassandra.db.marshal.IntegerType'");
 -
 -        execute("ALTER TABLE %s ALTER value TYPE 
'org.apache.cassandra.db.marshal.BytesType'");
 -
 -        createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY (a,b)) 
WITH COMPACT STORAGE");
 -        assertInvalidMessage("Altering of types is not allowed",
 -                             "ALTER TABLE %s ALTER c TYPE 
'org.apache.cassandra.db.marshal.BytesType'");
 -
 -        createTable("CREATE TABLE %s (a int, value int, PRIMARY KEY 
(a,value)) WITH COMPACT STORAGE");
 -        assertInvalidMessage("Altering of types is not allowed",
 -                             "ALTER TABLE %s ALTER value TYPE 
'org.apache.cassandra.db.marshal.IntegerType'");
 -        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<list<frozen<" 
+ type1 + ">>>, 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));
+     }
  }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@cassandra.apache.org
For additional commands, e-mail: commits-h...@cassandra.apache.org

Reply via email to