| /* |
| * 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("Altering 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; |
| } |
| } |
| } |