| /* |
| * 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 org.apache.cassandra.auth.AuthenticatedUser; |
| import org.apache.cassandra.config.CFMetaData; |
| import org.apache.cassandra.config.Schema; |
| import org.apache.cassandra.cql3.CFName; |
| import org.apache.cassandra.cql3.CQLStatement; |
| import org.apache.cassandra.cql3.QueryOptions; |
| import org.apache.cassandra.exceptions.InvalidRequestException; |
| import org.apache.cassandra.exceptions.RequestValidationException; |
| import org.apache.cassandra.service.ClientState; |
| import org.apache.cassandra.service.QueryState; |
| import org.apache.cassandra.thrift.ThriftValidation; |
| import org.apache.cassandra.transport.Event; |
| import org.apache.cassandra.transport.messages.ResultMessage; |
| |
| import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest; |
| |
| /** |
| * Abstract class for statements that alter the schema. |
| */ |
| public abstract class SchemaAlteringStatement extends CFStatement implements CQLStatement |
| { |
| private final boolean isColumnFamilyLevel; |
| |
| protected SchemaAlteringStatement() |
| { |
| super(null); |
| this.isColumnFamilyLevel = false; |
| } |
| |
| protected SchemaAlteringStatement(CFName name) |
| { |
| super(name); |
| this.isColumnFamilyLevel = true; |
| } |
| |
| public int getBoundTerms() |
| { |
| return 0; |
| } |
| |
| @Override |
| public void prepareKeyspace(ClientState state) throws InvalidRequestException |
| { |
| if (isColumnFamilyLevel) |
| super.prepareKeyspace(state); |
| } |
| |
| @Override |
| public Prepared prepare(ClientState clientState) |
| { |
| // We don't allow schema changes in no-compact mode on compact tables because it feels like unnecessary |
| // complication: applying the change on the non compact version of the table might be unsafe (the table is |
| // still compact in general), and applying it to the compact version in a no-compact connection feels |
| // confusing/unintuitive. If user want to alter the compact version, they can simply do so in a normal |
| // connection; if they want to alter the non-compact version, they should finish their transition and properly |
| // DROP COMPACT STORAGE on the table before doing so. |
| if (isColumnFamilyLevel && clientState.isNoCompactMode()) |
| { |
| CFMetaData table = ThriftValidation.validateColumnFamily(keyspace(), columnFamily()); |
| if (table.isCompactTable()) |
| { |
| throw invalidRequest("Cannot alter schema of compact table %s.%s from a connection in NO-COMPACT mode", |
| table.ksName, table.cfName); |
| } |
| else if (table.isView()) |
| { |
| CFMetaData baseTable = Schema.instance.getView(table.ksName, table.cfName).baseTableMetadata(); |
| if (baseTable.isCompactTable()) |
| throw new InvalidRequestException(String.format("Cannot ALTER schema of view %s.%s on compact table %s from " |
| + "a connection in NO-COMPACT mode", |
| table.ksName, table.cfName, |
| baseTable.ksName, baseTable.cfName)); |
| } |
| } |
| |
| return new Prepared(this); |
| } |
| |
| /** |
| * Schema alteration may result in a new database object (keyspace, table, role, function) being created capable of |
| * having permissions GRANTed on it. The creator of the object (the primary role assigned to the AuthenticatedUser |
| * performing the operation) is automatically granted ALL applicable permissions on the object. This is a hook for |
| * subclasses to override in order to perform that grant when the statement is executed. |
| */ |
| protected void grantPermissionsToCreator(QueryState state) |
| { |
| // no-op by default |
| } |
| |
| /** |
| * Announces the migration to other nodes in the cluster. |
| * |
| * @return the schema change event corresponding to the execution of this statement, or {@code null} if no schema change |
| * has occurred (when IF NOT EXISTS is used, for example) |
| * |
| * @throws RequestValidationException |
| */ |
| protected abstract Event.SchemaChange announceMigration(QueryState queryState, boolean isLocalOnly) throws RequestValidationException; |
| |
| public ResultMessage execute(QueryState state, QueryOptions options) throws RequestValidationException |
| { |
| // If an IF [NOT] EXISTS clause was used, this may not result in an actual schema change. To avoid doing |
| // extra work in the drivers to handle schema changes, we return an empty message in this case. (CASSANDRA-7600) |
| Event.SchemaChange ce = announceMigration(state, false); |
| if (ce == null) |
| return new ResultMessage.Void(); |
| |
| // when a schema alteration results in a new db object being created, we grant permissions on the new |
| // object to the user performing the request if: |
| // * the user is not anonymous |
| // * the configured IAuthorizer supports granting of permissions (not all do, AllowAllAuthorizer doesn't and |
| // custom external implementations may not) |
| AuthenticatedUser user = state.getClientState().getUser(); |
| if (user != null && !user.isAnonymous() && ce.change == Event.SchemaChange.Change.CREATED) |
| { |
| try |
| { |
| grantPermissionsToCreator(state); |
| } |
| catch (UnsupportedOperationException e) |
| { |
| // not a problem, grant is an optional method on IAuthorizer |
| } |
| } |
| |
| return new ResultMessage.SchemaChange(ce); |
| } |
| |
| public ResultMessage executeInternal(QueryState state, QueryOptions options) |
| { |
| Event.SchemaChange ce = announceMigration(state, true); |
| return ce == null ? new ResultMessage.Void() : new ResultMessage.SchemaChange(ce); |
| } |
| } |