/* * 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; import java.util.*; import org.apache.cassandra.auth.Permission; import org.apache.cassandra.cql3.*; import org.apache.cassandra.db.marshal.*; import org.apache.cassandra.exceptions.*; import org.apache.cassandra.schema.KeyspaceMetadata; import org.apache.cassandra.schema.MigrationManager; import org.apache.cassandra.schema.Schema; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.QueryState; import org.apache.cassandra.transport.Event; public abstract class AlterTypeStatement extends SchemaAlteringStatement { protected final UTName name; protected AlterTypeStatement(UTName name) { this.name = name; } @Override public void prepareKeyspace(ClientState state) throws InvalidRequestException { if (!name.hasKeyspace()) name.setKeyspace(state.getKeyspace()); if (name.getKeyspace() == null) throw new InvalidRequestException("You need to be logged in a keyspace or use a fully qualified user type name"); } protected abstract UserType makeUpdatedType(UserType toUpdate, KeyspaceMetadata ksm) throws InvalidRequestException; public static AlterTypeStatement addition(UTName name, FieldIdentifier fieldName, CQL3Type.Raw type) { return new Add(name, fieldName, type); } public static AlterTypeStatement alter(UTName name, FieldIdentifier fieldName, CQL3Type.Raw type) { throw new InvalidRequestException("Altering of types is not allowed"); } public static AlterTypeStatement renames(UTName name, Map<FieldIdentifier, FieldIdentifier> renames) { return new Renames(name, renames); } public void checkAccess(ClientState state) throws UnauthorizedException, InvalidRequestException { state.hasKeyspaceAccess(keyspace(), Permission.ALTER); } public void validate(ClientState state) throws RequestValidationException { // Validation is left to announceMigration as it's easier to do it while constructing the updated type. // It doesn't really change anything anyway. } @Override public String keyspace() { return name.getKeyspace(); } public Event.SchemaChange announceMigration(QueryState queryState, boolean isLocalOnly) throws InvalidRequestException, ConfigurationException { KeyspaceMetadata ksm = Schema.instance.getKeyspaceMetadata(name.getKeyspace()); if (ksm == null) throw new InvalidRequestException(String.format("Cannot alter type in unknown keyspace %s", name.getKeyspace())); UserType toUpdate = ksm.types.get(name.getUserTypeName()) .orElseThrow(() -> new InvalidRequestException(String.format("No user type named %s exists.", name))); UserType updated = makeUpdatedType(toUpdate, ksm); // Now, we need to announce the type update to basically change it for new tables using this type, // but we also need to find all existing user types and CF using it and change them. MigrationManager.announceTypeUpdate(updated, isLocalOnly); return new Event.SchemaChange(Event.SchemaChange.Change.UPDATED, Event.SchemaChange.Target.TYPE, keyspace(), name.getStringTypeName()); } protected void checkTypeNotUsedByAggregate(KeyspaceMetadata ksm) { ksm.functions.udas().filter(aggregate -> aggregate.initialCondition() != null && aggregate.stateType().referencesUserType(name.getStringTypeName())) .findAny() .ifPresent((aggregate) -> { throw new InvalidRequestException(String.format("Cannot alter user type %s as it is still used as an INITCOND by aggregate %s", name, aggregate)); }); } private static class Add extends AlterTypeStatement { private final FieldIdentifier fieldName; private final CQL3Type.Raw type; public Add(UTName name, FieldIdentifier fieldName, CQL3Type.Raw type) { super(name); this.fieldName = fieldName; this.type = type; } protected UserType makeUpdatedType(UserType toUpdate, KeyspaceMetadata ksm) throws InvalidRequestException { if (toUpdate.fieldPosition(fieldName) >= 0) throw new InvalidRequestException(String.format("Cannot add new field %s to type %s: a field of the same name already exists", fieldName, name)); List<FieldIdentifier> newNames = new ArrayList<>(toUpdate.size() + 1); newNames.addAll(toUpdate.fieldNames()); newNames.add(fieldName); AbstractType<?> addType = type.prepare(keyspace()).getType(); 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)); List<AbstractType<?>> 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 class Renames extends AlterTypeStatement { private final Map<FieldIdentifier, FieldIdentifier> renames; public Renames(UTName name, Map<FieldIdentifier, FieldIdentifier> renames) { super(name); this.renames = renames; } protected UserType makeUpdatedType(UserType toUpdate, KeyspaceMetadata ksm) throws InvalidRequestException { checkTypeNotUsedByAggregate(ksm); List<FieldIdentifier> newNames = new ArrayList<>(toUpdate.fieldNames()); List<AbstractType<?>> newTypes = new ArrayList<>(toUpdate.fieldTypes()); for (Map.Entry<FieldIdentifier, FieldIdentifier> entry : renames.entrySet()) { FieldIdentifier from = entry.getKey(); FieldIdentifier to = entry.getValue(); int idx = toUpdate.fieldPosition(from); if (idx < 0) throw new InvalidRequestException(String.format("Unknown field %s in type %s", from, name)); newNames.set(idx, to); } UserType updated = new UserType(toUpdate.keyspace, toUpdate.name, newNames, newTypes, toUpdate.isMultiCell()); CreateTypeStatement.checkForDuplicateNames(updated); return updated; } } }