| /* |
| * 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.Set; |
| |
| import com.google.common.collect.ImmutableSet; |
| |
| import org.apache.cassandra.auth.AuthenticatedUser; |
| import org.apache.cassandra.auth.IResource; |
| import org.apache.cassandra.config.DatabaseDescriptor; |
| import org.apache.cassandra.cql3.CQLStatement; |
| import org.apache.cassandra.cql3.QueryOptions; |
| import org.apache.cassandra.exceptions.InvalidRequestException; |
| import org.apache.cassandra.schema.*; |
| import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff; |
| import org.apache.cassandra.service.ClientState; |
| import org.apache.cassandra.service.ClientWarn; |
| import org.apache.cassandra.service.QueryState; |
| import org.apache.cassandra.transport.Event.SchemaChange; |
| import org.apache.cassandra.transport.messages.ResultMessage; |
| |
| abstract public class AlterSchemaStatement implements CQLStatement.SingleKeyspaceCqlStatement, SchemaTransformation |
| { |
| protected final String keyspaceName; // name of the keyspace affected by the statement |
| |
| protected AlterSchemaStatement(String keyspaceName) |
| { |
| this.keyspaceName = keyspaceName; |
| } |
| |
| public final void validate(ClientState state) |
| { |
| // no-op; validation is performed while executing the statement, in apply() |
| } |
| |
| public ResultMessage execute(QueryState state, QueryOptions options, long queryStartNanoTime) |
| { |
| return execute(state, false); |
| } |
| |
| @Override |
| public String keyspace() |
| { |
| return keyspaceName; |
| } |
| |
| public ResultMessage executeLocally(QueryState state, QueryOptions options) |
| { |
| return execute(state, true); |
| } |
| |
| /** |
| * TODO: document |
| */ |
| abstract SchemaChange schemaChangeEvent(KeyspacesDiff diff); |
| |
| /** |
| * 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 indicate which resources to to perform that grant on when the statement is executed. |
| * |
| * Only called if the transformation resulted in a non-empty diff. |
| */ |
| Set<IResource> createdResources(KeyspacesDiff diff) |
| { |
| return ImmutableSet.of(); |
| } |
| |
| /** |
| * Schema alteration might produce a client warning (e.g. a warning to run full repair when increading RF of a keyspace). |
| * This method should be used to generate them instead of calling warn() in transformation code. |
| * |
| * Only called if the transformation resulted in a non-empty diff. |
| */ |
| Set<String> clientWarnings(KeyspacesDiff diff) |
| { |
| return ImmutableSet.of(); |
| } |
| |
| public ResultMessage execute(QueryState state, boolean locally) |
| { |
| if (SchemaConstants.isLocalSystemKeyspace(keyspaceName)) |
| throw ire("System keyspace '%s' is not user-modifiable", keyspaceName); |
| |
| KeyspaceMetadata keyspace = Schema.instance.getKeyspaceMetadata(keyspaceName); |
| if (null != keyspace && keyspace.isVirtual()) |
| throw ire("Virtual keyspace '%s' is not user-modifiable", keyspaceName); |
| |
| validateKeyspaceName(); |
| |
| KeyspacesDiff diff = MigrationManager.announce(this, locally); |
| |
| clientWarnings(diff).forEach(ClientWarn.instance::warn); |
| |
| if (diff.isEmpty()) |
| 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 (null != user && !user.isAnonymous()) |
| createdResources(diff).forEach(r -> grantPermissionsOnResource(r, user)); |
| |
| return new ResultMessage.SchemaChange(schemaChangeEvent(diff)); |
| } |
| |
| private void validateKeyspaceName() |
| { |
| if (!SchemaConstants.isValidName(keyspaceName)) |
| { |
| throw ire("Keyspace name must not be empty, more than %d characters long, " + |
| "or contain non-alphanumeric-underscore characters (got '%s')", |
| SchemaConstants.NAME_LENGTH, keyspaceName); |
| } |
| } |
| |
| private void grantPermissionsOnResource(IResource resource, AuthenticatedUser user) |
| { |
| try |
| { |
| DatabaseDescriptor.getAuthorizer() |
| .grant(AuthenticatedUser.SYSTEM_USER, |
| resource.applicablePermissions(), |
| resource, |
| user.getPrimaryRole()); |
| } |
| catch (UnsupportedOperationException e) |
| { |
| // not a problem - grant is an optional method on IAuthorizer |
| } |
| } |
| |
| static InvalidRequestException ire(String format, Object... args) |
| { |
| return new InvalidRequestException(String.format(format, args)); |
| } |
| } |