You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by al...@apache.org on 2017/05/31 17:51:45 UTC
[06/11] cassandra git commit: 13426: work in progress
http://git-wip-us.apache.org/repos/asf/cassandra/blob/aaddbd49/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java b/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java
index 7a2a1ba..deaf375 100644
--- a/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java
@@ -47,19 +47,14 @@ public class UpdateStatement extends ModificationStatement
private static final Constants.Value EMPTY = new Constants.Value(ByteBufferUtil.EMPTY_BYTE_BUFFER);
private UpdateStatement(StatementType type,
- int boundTerms,
+ VariableSpecifications bindVariables,
TableMetadata metadata,
Operations operations,
StatementRestrictions restrictions,
Conditions conditions,
Attributes attrs)
{
- super(type, boundTerms, metadata, operations, restrictions, conditions, attrs);
- }
-
- public boolean requireFullClusteringKey()
- {
- return true;
+ super(type, bindVariables, metadata, operations, restrictions, conditions, attrs);
}
@Override
@@ -124,7 +119,7 @@ public class UpdateStatement extends ModificationStatement
* @param columnValues list of column values (corresponds to names)
* @param ifNotExists true if an IF NOT EXISTS condition was specified, false otherwise
*/
- public ParsedInsert(CFName name,
+ public ParsedInsert(QualifiedName name,
Attributes.Raw attrs,
List<ColumnMetadata.Raw> columnNames,
List<Term.Raw> columnValues,
@@ -137,7 +132,7 @@ public class UpdateStatement extends ModificationStatement
@Override
protected ModificationStatement prepareInternal(TableMetadata metadata,
- VariableSpecifications boundNames,
+ VariableSpecifications bindVariables,
Conditions conditions,
Attributes attrs)
{
@@ -170,7 +165,7 @@ public class UpdateStatement extends ModificationStatement
else
{
Operation operation = new Operation.SetValue(value).prepare(metadata, def);
- operation.collectMarkerSpecification(boundNames);
+ operation.collectMarkerSpecification(bindVariables);
operations.add(operation);
}
}
@@ -180,13 +175,13 @@ public class UpdateStatement extends ModificationStatement
StatementRestrictions restrictions = new StatementRestrictions(type,
metadata,
whereClause.build(),
- boundNames,
+ bindVariables,
applyOnlyToStaticColumns,
false,
false);
return new UpdateStatement(type,
- boundNames.size(),
+ bindVariables,
metadata,
operations,
restrictions,
@@ -203,7 +198,7 @@ public class UpdateStatement extends ModificationStatement
private final Json.Raw jsonValue;
private final boolean defaultUnset;
- public ParsedInsertJson(CFName name, Attributes.Raw attrs, Json.Raw jsonValue, boolean defaultUnset, boolean ifNotExists)
+ public ParsedInsertJson(QualifiedName name, Attributes.Raw attrs, Json.Raw jsonValue, boolean defaultUnset, boolean ifNotExists)
{
super(name, StatementType.INSERT, attrs, null, ifNotExists, false);
this.jsonValue = jsonValue;
@@ -212,14 +207,14 @@ public class UpdateStatement extends ModificationStatement
@Override
protected ModificationStatement prepareInternal(TableMetadata metadata,
- VariableSpecifications boundNames,
+ VariableSpecifications bindVariables,
Conditions conditions,
Attributes attrs)
{
checkFalse(metadata.isCounter(), "INSERT statements are not allowed on counter tables, use UPDATE instead");
Collection<ColumnMetadata> defs = metadata.columns();
- Json.Prepared prepared = jsonValue.prepareAndCollectMarkers(metadata, defs, boundNames);
+ Json.Prepared prepared = jsonValue.prepareAndCollectMarkers(metadata, defs, bindVariables);
WhereClause.Builder whereClause = new WhereClause.Builder();
Operations operations = new Operations(type);
@@ -238,7 +233,7 @@ public class UpdateStatement extends ModificationStatement
else
{
Operation operation = new Operation.SetValue(raw).prepare(metadata, def);
- operation.collectMarkerSpecification(boundNames);
+ operation.collectMarkerSpecification(bindVariables);
operations.add(operation);
}
}
@@ -248,13 +243,13 @@ public class UpdateStatement extends ModificationStatement
StatementRestrictions restrictions = new StatementRestrictions(type,
metadata,
whereClause.build(),
- boundNames,
+ bindVariables,
applyOnlyToStaticColumns,
false,
false);
return new UpdateStatement(type,
- boundNames.size(),
+ bindVariables,
metadata,
operations,
restrictions,
@@ -279,7 +274,7 @@ public class UpdateStatement extends ModificationStatement
* @param whereClause the where clause
* @param ifExists flag to check if row exists
* */
- public ParsedUpdate(CFName name,
+ public ParsedUpdate(QualifiedName name,
Attributes.Raw attrs,
List<Pair<ColumnMetadata.Raw, Operation.RawUpdate>> updates,
WhereClause whereClause,
@@ -293,7 +288,7 @@ public class UpdateStatement extends ModificationStatement
@Override
protected ModificationStatement prepareInternal(TableMetadata metadata,
- VariableSpecifications boundNames,
+ VariableSpecifications bindVariables,
Conditions conditions,
Attributes attrs)
{
@@ -306,18 +301,18 @@ public class UpdateStatement extends ModificationStatement
checkFalse(def.isPrimaryKeyColumn(), "PRIMARY KEY part %s found in SET part", def.name);
Operation operation = entry.right.prepare(metadata, def);
- operation.collectMarkerSpecification(boundNames);
+ operation.collectMarkerSpecification(bindVariables);
operations.add(operation);
}
StatementRestrictions restrictions = newRestrictions(metadata,
- boundNames,
+ bindVariables,
operations,
whereClause,
conditions);
return new UpdateStatement(type,
- boundNames.size(),
+ bindVariables,
metadata,
operations,
restrictions,
http://git-wip-us.apache.org/repos/asf/cassandra/blob/aaddbd49/src/java/org/apache/cassandra/cql3/statements/UseStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/UseStatement.java b/src/java/org/apache/cassandra/cql3/statements/UseStatement.java
index 02a678a..d48ff62 100644
--- a/src/java/org/apache/cassandra/cql3/statements/UseStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/UseStatement.java
@@ -25,7 +25,7 @@ import org.apache.cassandra.transport.messages.ResultMessage;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.QueryState;
-public class UseStatement extends ParsedStatement implements CQLStatement
+public class UseStatement extends CQLStatement.Raw implements CQLStatement
{
private final String keyspace;
@@ -34,17 +34,12 @@ public class UseStatement extends ParsedStatement implements CQLStatement
this.keyspace = keyspace;
}
- public int getBoundTerms()
+ public UseStatement prepare(ClientState state)
{
- return 0;
+ return this;
}
- public Prepared prepare() throws InvalidRequestException
- {
- return new Prepared(this);
- }
-
- public void checkAccess(ClientState state) throws UnauthorizedException
+ public void authorize(ClientState state) throws UnauthorizedException
{
state.validateLogin();
}
@@ -59,7 +54,7 @@ public class UseStatement extends ParsedStatement implements CQLStatement
return new ResultMessage.SetKeyspace(keyspace);
}
- public ResultMessage executeInternal(QueryState state, QueryOptions options) throws InvalidRequestException
+ public ResultMessage executeLocally(QueryState state, QueryOptions options) throws InvalidRequestException
{
// In production, internal queries are exclusively on the system keyspace and 'use' is thus useless
// but for some unit tests we need to set the keyspace (e.g. for tests with DROP INDEX)
http://git-wip-us.apache.org/repos/asf/cassandra/blob/aaddbd49/src/java/org/apache/cassandra/cql3/statements/schema/AlterKeyspaceStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/AlterKeyspaceStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/AlterKeyspaceStatement.java
new file mode 100644
index 0000000..bbd5746
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/AlterKeyspaceStatement.java
@@ -0,0 +1,104 @@
+/*
+ * 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.Permission;
+import org.apache.cassandra.cql3.CQLStatement;
+import org.apache.cassandra.cql3.statements.KeyspaceAttributes;
+import org.apache.cassandra.locator.AbstractReplicationStrategy;
+import org.apache.cassandra.locator.LocalStrategy;
+import org.apache.cassandra.schema.KeyspaceMetadata;
+import org.apache.cassandra.schema.KeyspaceMetadata.KeyspaceDiff;
+import org.apache.cassandra.schema.Keyspaces;
+import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
+import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.transport.Event.SchemaChange;
+import org.apache.cassandra.transport.Event.SchemaChange.Change;
+
+public final class AlterKeyspaceStatement extends AlterSchemaStatement
+{
+ private final KeyspaceAttributes attrs;
+
+ public AlterKeyspaceStatement(String keyspaceName, KeyspaceAttributes attrs)
+ {
+ super(keyspaceName);
+ this.attrs = attrs;
+ }
+
+ public Keyspaces apply(Keyspaces schema)
+ {
+ attrs.validate();
+
+ KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
+ if (null == keyspace)
+ throw ire("Keyspace '%s' doesn't exist", keyspaceName);
+
+ KeyspaceMetadata newKeyspace = keyspace.withSwapped(attrs.asAlteredKeyspaceParams(keyspace.params));
+
+ if (newKeyspace.params.replication.klass.equals(LocalStrategy.class))
+ throw ire("Unable to use given strategy class: LocalStrategy is reserved for internal use.");
+
+ newKeyspace.params.validate(keyspaceName);
+
+ return schema.withAddedOrUpdated(newKeyspace);
+ }
+
+ SchemaChange schemaChangeEvent(KeyspacesDiff diff)
+ {
+ return new SchemaChange(Change.UPDATED, keyspaceName);
+ }
+
+ public void authorize(ClientState client)
+ {
+ client.ensureKeyspacePermission(keyspaceName, Permission.ALTER);
+ }
+
+ @Override
+ Set<String> clientWarnings(KeyspacesDiff diff)
+ {
+ KeyspaceDiff keyspaceDiff = diff.altered.get(0);
+
+ AbstractReplicationStrategy before = keyspaceDiff.before.createReplicationStrategy();
+ AbstractReplicationStrategy after = keyspaceDiff.after.createReplicationStrategy();
+
+ return before.getReplicationFactor() < after.getReplicationFactor()
+ ? ImmutableSet.of("When increasing replication factor you need to run a full (-full) repair to distribute the data.")
+ : ImmutableSet.of();
+ }
+
+ public static final class Raw extends CQLStatement.Raw
+ {
+ private final String keyspaceName;
+ private final KeyspaceAttributes attrs;
+
+ public Raw(String keyspaceName, KeyspaceAttributes attrs)
+ {
+ this.keyspaceName = keyspaceName;
+ this.attrs = attrs;
+ }
+
+ public AlterKeyspaceStatement prepare(ClientState state)
+ {
+ return new AlterKeyspaceStatement(keyspaceName, attrs);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/aaddbd49/src/java/org/apache/cassandra/cql3/statements/schema/AlterSchemaStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/AlterSchemaStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/AlterSchemaStatement.java
new file mode 100644
index 0000000..1776bf1
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/AlterSchemaStatement.java
@@ -0,0 +1,161 @@
+/*
+ * 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.auth.RoleResource;
+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.Keyspaces.KeyspacesDiff;
+import org.apache.cassandra.schema.MigrationManager;
+import org.apache.cassandra.schema.SchemaConstants;
+import org.apache.cassandra.schema.SchemaTransformation;
+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;
+import org.apache.cassandra.utils.FBUtilities;
+
+abstract class AlterSchemaStatement implements CQLStatement, SchemaTransformation
+{
+ protected final String keyspaceName; // name of the keyspace affected by the statement
+
+ protected AlterSchemaStatement(String keyspaceName)
+ {
+ this.keyspaceName = keyspaceName;
+ }
+
+ public void validate(ClientState state)
+ {
+ // no-op; validation is performed while executing the statement, in apply()
+ }
+
+ public void authorize(ClientState client)
+ {
+ // TODO: drop default impl
+ }
+
+ public ResultMessage execute(QueryState state, QueryOptions options, long queryStartNanoTime)
+ {
+ return execute(state, false);
+ }
+
+ public ResultMessage executeLocally(QueryState state, QueryOptions options)
+ {
+ return execute(state, true);
+ }
+
+ /**
+ * TODO: drop default impl, document
+ */
+ SchemaChange schemaChangeEvent(KeyspacesDiff diff)
+ {
+ return null;
+ }
+
+ /**
+ * 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.isSystemKeyspace(keyspaceName))
+ throw ire("System keyspace '%s' is not user-modifiable", keyspaceName);
+
+ validateKeyspaceName();
+
+ KeyspacesDiff diff = MigrationManager.alterSchema(this, FBUtilities.timestampMicros(), locally);
+
+ 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));
+
+ clientWarnings(diff).forEach(ClientWarn.instance::warn);
+
+ 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,
+ RoleResource.role(user.getName()));
+ }
+ 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));
+ }
+}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/aaddbd49/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java
new file mode 100644
index 0000000..f0cd58d
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java
@@ -0,0 +1,341 @@
+/*
+ * 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.Collection;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.cassandra.auth.Permission;
+import org.apache.cassandra.cql3.CQL3Type;
+import org.apache.cassandra.cql3.ColumnIdentifier;
+import org.apache.cassandra.cql3.statements.TableAttributes;
+import org.apache.cassandra.db.Keyspace;
+import org.apache.cassandra.db.marshal.AbstractType;
+import org.apache.cassandra.schema.ColumnMetadata;
+import org.apache.cassandra.schema.IndexMetadata;
+import org.apache.cassandra.schema.KeyspaceMetadata;
+import org.apache.cassandra.schema.Keyspaces;
+import org.apache.cassandra.schema.TableMetadata;
+import org.apache.cassandra.schema.TableParams;
+import org.apache.cassandra.schema.ViewMetadata;
+import org.apache.cassandra.schema.Views;
+import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.transport.Event.SchemaChange;
+
+import static java.lang.String.join;
+
+import static com.google.common.collect.Iterables.filter;
+import static com.google.common.collect.Iterables.isEmpty;
+import static com.google.common.collect.Iterables.transform;
+
+public abstract class AlterTableStatement extends AlterSchemaStatement
+{
+ protected final String tableName;
+
+ public AlterTableStatement(String keyspaceName, String tableName)
+ {
+ super(keyspaceName);
+ this.tableName = tableName;
+ }
+
+ public void authorize(ClientState client)
+ {
+ client.ensureTablePermission(keyspaceName, tableName, Permission.ALTER);
+ }
+
+ SchemaChange schemaChangeEvent(Keyspaces.KeyspacesDiff diff)
+ {
+ return new SchemaChange(SchemaChange.Change.UPDATED, SchemaChange.Target.TABLE, keyspaceName, tableName);
+ }
+
+ public Keyspaces apply(Keyspaces schema)
+ {
+ KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
+
+ TableMetadata table = null == keyspace
+ ? null
+ : keyspace.getTableOrViewNullable(tableName);
+
+ if (null == table)
+ throw ire("Table '%s.%s' doesn't exist", keyspaceName, tableName);
+
+ if (table.isView())
+ throw ire("Cannot use ALTER TABLE on a materialized view; use ALTER MATERIALIZED VIEW instead");
+
+ return schema.withAddedOrUpdated(apply(keyspace, table));
+ }
+
+ abstract KeyspaceMetadata apply(KeyspaceMetadata keyspace, TableMetadata table);
+
+ public static class AddColumns extends AlterTableStatement
+ {
+ private final Collection<ColumnAdded> newColumns;
+
+ public AddColumns(String keyspaceName, String tableName, Collection<ColumnAdded> newColumns)
+ {
+ super(keyspaceName, tableName);
+ this.newColumns = newColumns;
+ }
+
+ public KeyspaceMetadata apply(KeyspaceMetadata keyspace, TableMetadata table)
+ {
+ if (table.isCompactTable())
+ throw ire("Cannot add new column to a COMPACT STORAGE table");
+
+ TableMetadata.Builder tableBuilder = table.unbuild();
+ Views.Builder viewsBuilder = keyspace.views.unbuild();
+ newColumns.forEach(c -> addColumn(keyspace, table, c, tableBuilder, viewsBuilder));
+
+ return keyspace.withSwapped(keyspace.tables.withSwapped(tableBuilder.build()))
+ .withSwapped(viewsBuilder.build());
+ }
+
+ private void addColumn(KeyspaceMetadata keyspace,
+ TableMetadata table,
+ ColumnAdded column,
+ TableMetadata.Builder tableBuilder,
+ Views.Builder viewsBuilder)
+ {
+ ColumnIdentifier name = column.name.getIdentifier(table);
+ AbstractType<?> type = column.type.prepare(keyspaceName, keyspace.types).getType();
+ boolean isStatic = column.isStatic;
+
+ if (null != tableBuilder.getColumn(name))
+ throw ire("Column with name '%s' already exists", name);
+
+ if (isStatic && table.clusteringColumns().isEmpty())
+ throw ire("Static columns are only useful (and thus allowed) if the table has at least one clustering column");
+
+ ColumnMetadata droppedColumn = table.getDroppedColumn(name.bytes);
+ if (null != droppedColumn)
+ {
+ // After #8099, not safe to re-add columns of incompatible types - until *maybe* deser logic with dropped
+ // columns is pushed deeper down the line. The latter would still be problematic in cases of schema races.
+ if (!droppedColumn.type.isValueCompatibleWith(type))
+ {
+ throw ire("Cannot re-add a previously dropped column '%s' of type %s, incompatible with previous type %s",
+ name,
+ type.asCQL3Type(),
+ droppedColumn.type.asCQL3Type());
+ }
+
+ // Cannot re-add a dropped counter column. See #7831.
+ if (table.isCounter())
+ throw ire("Cannot re-add previously dropped counter column %s", name);
+ }
+
+ if (type.isCollection() && type.isMultiCell())
+ {
+ if (table.isCompactTable())
+ throw ire("Cannot use non-frozen collections in COMPACT STORAGE tables");
+
+ if (table.isSuper())
+ throw ire("Cannot use non-frozen collections with super column families");
+ }
+
+ if (isStatic)
+ tableBuilder.addStaticColumn(name, type);
+ else
+ tableBuilder.addRegularColumn(name, type);
+
+ if (!isStatic)
+ {
+ for (ViewMetadata view : keyspace.views.forTable(table.id))
+ {
+ if (view.includeAllColumns)
+ {
+ ColumnMetadata viewColumn = ColumnMetadata.regularColumn(view.metadata, name.bytes, type);
+ viewsBuilder.put(viewsBuilder.get(view.name()).withAddedRegularColumn(viewColumn));
+ }
+ }
+ }
+ }
+ }
+
+ public static class DropColumns extends AlterTableStatement
+ {
+ private final Collection<ColumnMetadata.Raw> removedColumns;
+ private final long timestamp;
+
+ public DropColumns(String keyspaceName, String tableName, Collection<ColumnMetadata.Raw> removedColumns, long timestamp)
+ {
+ super(keyspaceName, tableName);
+ this.removedColumns = removedColumns;
+ this.timestamp = timestamp;
+ }
+
+ public KeyspaceMetadata apply(KeyspaceMetadata keyspace, TableMetadata table)
+ {
+ if (!table.isCQLTable())
+ throw ire("Cannot drop columns from a non-CQL3 table");
+
+ TableMetadata.Builder builder = table.unbuild();
+ removedColumns.forEach(c -> dropColumn(keyspace, table, c, builder));
+
+ return keyspace.withSwapped(keyspace.tables.withSwapped(builder.build()));
+ }
+
+ private void dropColumn(KeyspaceMetadata keyspace, TableMetadata table, ColumnMetadata.Raw column, TableMetadata.Builder builder)
+ {
+ ColumnIdentifier name = column.getIdentifier(table);
+
+ ColumnMetadata currentColumn = table.getColumn(name);
+ if (null == currentColumn)
+ throw ire("Column %s was not found in table '%s'", name, table);
+
+ if (currentColumn.isPrimaryKeyColumn())
+ throw ire("Cannot drop PRIMARY KEY column %s", name);
+
+ // TODO: some day try and find a way to not rely on Keyspace/IndexManager/Index to find dependent indexes
+ Set<IndexMetadata> dependentIndexes = Keyspace.openAndGetStore(table).indexManager.getDependentIndexes(currentColumn);
+ if (!dependentIndexes.isEmpty())
+ {
+ throw ire("Cannot drop column %s because it has dependent secondary indexes (%s)",
+ currentColumn,
+ join(", ", transform(dependentIndexes, i -> i.name)));
+ }
+
+ Iterable<ViewMetadata> dependentViews = filter(keyspace.views.forTable(table.id), v -> v.includes(name));
+ if (!isEmpty(dependentViews))
+ {
+ throw ire("Cannot drop column %s because it has dependent materialized views (%s)",
+ currentColumn,
+ join(", ", transform(dependentViews, ViewMetadata::name)));
+ }
+
+ builder.removeRegularOrStaticColumn(name);
+ builder.recordColumnDrop(currentColumn, timestamp);
+ }
+ }
+
+ public static class RenameColumns extends AlterTableStatement
+ {
+ private final Map<ColumnMetadata.Raw, ColumnMetadata.Raw> renamedColumns;
+
+ public RenameColumns(String keyspaceName, String tableName, Map<ColumnMetadata.Raw, ColumnMetadata.Raw> renamedColumns)
+ {
+ super(keyspaceName, tableName);
+ this.renamedColumns = renamedColumns;
+ }
+
+ public KeyspaceMetadata apply(KeyspaceMetadata keyspace, TableMetadata table)
+ {
+ TableMetadata.Builder tableBuilder = table.unbuild();
+ Views.Builder viewsBuilder = keyspace.views.unbuild();
+ renamedColumns.forEach((o, n) -> renameColumn(keyspace, table, o, n, tableBuilder, viewsBuilder));
+
+ return keyspace.withSwapped(keyspace.tables.withSwapped(tableBuilder.build()))
+ .withSwapped(viewsBuilder.build());
+ }
+
+ private void renameColumn(KeyspaceMetadata keyspace,
+ TableMetadata table,
+ ColumnMetadata.Raw oldName,
+ ColumnMetadata.Raw newName,
+ TableMetadata.Builder tableBuilder,
+ Views.Builder viewsBuilder)
+ {
+ ColumnIdentifier oldColumnName = oldName.getIdentifier(table);
+ ColumnIdentifier newColumnName = newName.getIdentifier(table);
+
+ ColumnMetadata column = table.getColumn(oldColumnName);
+ if (null == column)
+ throw ire("Column %s was not found in table %s", oldColumnName, table);
+
+ if (!column.isPrimaryKeyColumn())
+ throw ire("Cannot rename non PRIMARY KEY column %s", oldColumnName);
+
+ if (null != table.getColumn(newColumnName))
+ {
+ throw ire("Cannot rename column %s to %s in table '%s'; another column with that name already exists",
+ oldColumnName,
+ newColumnName,
+ table);
+ }
+
+ // TODO: some day try and find a way to not rely on Keyspace/IndexManager/Index to find dependent indexes
+ Set<IndexMetadata> dependentIndexes = Keyspace.openAndGetStore(table).indexManager.getDependentIndexes(column);
+ if (!dependentIndexes.isEmpty())
+ {
+ throw ire("Can't rename column %s because it has dependent secondary indexes (%s)",
+ oldColumnName,
+ join(", ", transform(dependentIndexes, i -> i.name)));
+ }
+
+ for (ViewMetadata view : keyspace.views.forTable(table.id))
+ {
+ if (view.includes(oldColumnName))
+ {
+ ColumnIdentifier oldViewColumn = oldName.getIdentifier(view.metadata);
+ ColumnIdentifier newViewColumn = newName.getIdentifier(view.metadata);
+
+ viewsBuilder.put(viewsBuilder.get(view.name()).withRenamedPrimaryKeyColumn(oldViewColumn, newViewColumn));
+ }
+ }
+
+ tableBuilder.renamePrimaryKeyColumn(oldColumnName, newColumnName);
+ }
+ }
+
+ public static class AlterOptions extends AlterTableStatement
+ {
+ private final TableAttributes attrs;
+
+ public AlterOptions(String keyspaceName, String tableName, TableAttributes attrs)
+ {
+ super(keyspaceName, tableName);
+ this.attrs = attrs;
+ }
+
+ public KeyspaceMetadata apply(KeyspaceMetadata keyspace, TableMetadata table)
+ {
+ attrs.validate();
+
+ TableParams params = attrs.asAlteredTableParams(table.params);
+
+ if (table.isCounter() && params.defaultTimeToLive > 0)
+ throw ire("Cannot set default_time_to_live on a table with counters");
+
+ if (!isEmpty(keyspace.views.forTable(table.id)) && params.gcGraceSeconds == 0)
+ {
+ throw ire("Cannot alter gc_grace_seconds of the base table of a " +
+ "materialized view to 0, since this value is used to TTL " +
+ "undelivered updates. Setting gc_grace_seconds too low might " +
+ "cause undelivered updates to expire " +
+ "before being replayed.");
+ }
+
+ return keyspace.withSwapped(keyspace.tables.withSwapped(table.withSwapped(params)));
+ }
+ }
+
+ public static class ColumnAdded
+ {
+ final ColumnMetadata.Raw name;
+ final CQL3Type.Raw type;
+ final boolean isStatic;
+
+ ColumnAdded(ColumnMetadata.Raw name, CQL3Type.Raw type, boolean isStatic)
+ {
+ this.name = name;
+ this.type = type;
+ this.isStatic = isStatic;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/aaddbd49/src/java/org/apache/cassandra/cql3/statements/schema/AlterTypeStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/AlterTypeStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/AlterTypeStatement.java
new file mode 100644
index 0000000..42adfe0
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/AlterTypeStatement.java
@@ -0,0 +1,149 @@
+/*
+ * 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.List;
+import java.util.Map;
+
+import org.apache.cassandra.auth.Permission;
+import org.apache.cassandra.cql3.CQL3Type;
+import org.apache.cassandra.cql3.FieldIdentifier;
+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.service.ClientState;
+import org.apache.cassandra.transport.Event.SchemaChange;
+
+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(SchemaChange.Change.UPDATED, SchemaChange.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);
+
+ public static final class AddField extends AlterTypeStatement
+ {
+ private final FieldIdentifier fieldName;
+ private final CQL3Type.Raw type;
+
+ public 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, 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);
+
+ 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, userType.isMultiCell());
+ }
+ }
+
+ public static final class RenameFields extends AlterTypeStatement
+ {
+ private final Map<FieldIdentifier, FieldIdentifier> renamedFields;
+
+ public 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",
+ typeName,
+ 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 type %s", oldName, typeName);
+ 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, typeName);
+ });
+
+ return new UserType(keyspaceName, userType.name, fieldNames, userType.fieldTypes(), userType.isMultiCell());
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/aaddbd49/src/java/org/apache/cassandra/cql3/statements/schema/AlterViewStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/AlterViewStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/AlterViewStatement.java
new file mode 100644
index 0000000..2de352e
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/AlterViewStatement.java
@@ -0,0 +1,105 @@
+/*
+ * 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 org.apache.cassandra.auth.Permission;
+import org.apache.cassandra.cql3.CQLStatement;
+import org.apache.cassandra.cql3.QualifiedName;
+import org.apache.cassandra.cql3.statements.TableAttributes;
+import org.apache.cassandra.schema.*;
+import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
+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;
+
+public final class AlterViewStatement extends AlterSchemaStatement
+{
+ private final String viewName;
+ private final TableAttributes attrs;
+
+ public AlterViewStatement(String keyspaceName, String viewName, TableAttributes attrs)
+ {
+ super(keyspaceName);
+ this.viewName = viewName;
+ this.attrs = attrs;
+ }
+
+ public Keyspaces apply(Keyspaces schema)
+ {
+ KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
+
+ ViewMetadata view = null == keyspace
+ ? null
+ : keyspace.views.getNullable(viewName);
+
+ if (null == view)
+ throw ire("Materialized view '%s.%s' doesn't exist", keyspaceName, viewName);
+
+ attrs.validate();
+
+ TableParams params = attrs.asAlteredTableParams(view.metadata.params);
+
+ if (params.gcGraceSeconds == 0)
+ {
+ throw ire("Cannot alter gc_grace_seconds of a materialized view to 0, since this " +
+ "value is used to TTL undelivered updates. Setting gc_grace_seconds too " +
+ "low might cause undelivered updates to expire before being replayed.");
+ }
+
+ if (params.defaultTimeToLive > 0)
+ {
+ throw ire("Cannot set or alter default_time_to_live for a materialized view. " +
+ "Data in a materialized view always expire at the same time than " +
+ "the corresponding data in the parent table.");
+ }
+
+ ViewMetadata newView = view.copy(view.metadata.withSwapped(params));
+ return schema.withAddedOrUpdated(keyspace.withSwapped(keyspace.views.withSwapped(newView)));
+ }
+
+ SchemaChange schemaChangeEvent(KeyspacesDiff diff)
+ {
+ return new SchemaChange(Change.UPDATED, Target.TABLE, keyspaceName, viewName);
+ }
+
+ public void authorize(ClientState client)
+ {
+ ViewMetadata view = Schema.instance.getView(keyspaceName, viewName);
+ if (null != view)
+ client.ensureTablePermission(keyspaceName, view.baseTableName, Permission.ALTER);
+ }
+
+ public static final class Raw extends CQLStatement.Raw
+ {
+ private final QualifiedName name;
+ private final TableAttributes attrs;
+
+ public Raw(QualifiedName name, TableAttributes attrs)
+ {
+ this.name = name;
+ this.attrs = attrs;
+ }
+
+ public AlterViewStatement prepare(ClientState state)
+ {
+ String keyspaceName = name.hasKeyspace() ? name.getKeyspace() : state.getKeyspace();
+ return new AlterViewStatement(keyspaceName, name.getName(), attrs);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/aaddbd49/src/java/org/apache/cassandra/cql3/statements/schema/CreateAggregateStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateAggregateStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateAggregateStatement.java
new file mode 100644
index 0000000..04e3c81
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateAggregateStatement.java
@@ -0,0 +1,231 @@
+/*
+ * 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.nio.ByteBuffer;
+import java.util.List;
+import java.util.Set;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+
+import org.apache.cassandra.auth.FunctionResource;
+import org.apache.cassandra.auth.IResource;
+import org.apache.cassandra.cql3.*;
+import org.apache.cassandra.cql3.functions.*;
+import org.apache.cassandra.db.marshal.AbstractType;
+import org.apache.cassandra.schema.KeyspaceMetadata;
+import org.apache.cassandra.schema.Keyspaces;
+import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
+import org.apache.cassandra.serializers.MarshalException;
+import org.apache.cassandra.transport.Event.SchemaChange;
+import org.apache.cassandra.transport.ProtocolVersion;
+
+import static java.lang.String.format;
+import static java.lang.String.join;
+import static java.util.Collections.singleton;
+import static java.util.Collections.singletonList;
+
+import static com.google.common.collect.Iterables.concat;
+import static com.google.common.collect.Iterables.transform;
+
+public final class CreateAggregateStatement extends AlterSchemaStatement
+{
+ private final String aggregateName;
+ private final List<CQL3Type.Raw> rawArgumentTypes;
+ private final CQL3Type.Raw rawStateType;
+ private final FunctionName stateFunctionName;
+ private final FunctionName finalFunctionName;
+ private final Term.Raw rawInitialValue;
+ private final boolean orReplace;
+ private final boolean ifNotExists;
+
+ public CreateAggregateStatement(String keyspaceName,
+ String aggregateName,
+ List<CQL3Type.Raw> rawArgumentTypes,
+ CQL3Type.Raw rawStateType,
+ FunctionName stateFunctionName,
+ FunctionName finalFunctionName,
+ Term.Raw rawInitialValue,
+ boolean orReplace,
+ boolean ifNotExists)
+ {
+ super(keyspaceName);
+ this.aggregateName = aggregateName;
+ this.rawArgumentTypes = rawArgumentTypes;
+ this.rawStateType = rawStateType;
+ this.stateFunctionName = stateFunctionName;
+ this.finalFunctionName = finalFunctionName;
+ this.rawInitialValue = rawInitialValue;
+ this.orReplace = orReplace;
+ this.ifNotExists = ifNotExists;
+ }
+
+ SchemaChange schemaChangeEvent(KeyspacesDiff diff)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ Set<IResource> createdResources(KeyspacesDiff diff)
+ {
+ return ImmutableSet.of(FunctionResource.functionFromCql(keyspaceName, aggregateName, rawArgumentTypes));
+ }
+
+ public Keyspaces apply(Keyspaces schema)
+ {
+ if (ifNotExists && orReplace)
+ throw ire("Cannot use both 'OR REPLACE' and 'IF NOT EXISTS' directives");
+
+ rawArgumentTypes.stream()
+ .filter(CQL3Type.Raw::isFrozen)
+ .findFirst()
+ .ifPresent(t -> { throw ire("Argument '%s' cannot be frozen; remove frozen<> modifier from '%s'", t, t); });
+
+ if (rawStateType.isFrozen())
+ throw ire("State type '%s' cannot be frozen; remove frozen<> modifier from '%s'", rawStateType, rawStateType);
+
+ KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
+ if (null == keyspace)
+ throw ire("Keyspace '%s' doesn't exist", keyspaceName);
+
+ /*
+ * Resolve the state function
+ */
+
+ // TODO replace Lists.transform use
+ List<AbstractType<?>> argumentTypes = Lists.transform(rawArgumentTypes, t -> t.prepare(keyspaceName, keyspace.types).getType());
+ AbstractType<?> stateType = rawStateType.prepare(keyspaceName, keyspace.types).getType();
+ List<AbstractType<?>> stateFunctionArguments = Lists.newArrayList(concat(singleton(stateType), argumentTypes));
+
+ Function stateFunction = keyspace.functions.find(stateFunctionName, stateFunctionArguments).orElse(null);
+ if (null == stateFunction)
+ throw ire("State function %s does not exist", stateFunctionString());
+
+ if (stateFunction.isAggregate())
+ throw ire("State function %s is not a scalar function", stateFunctionString());
+
+ if (!stateFunction.returnType().equals(stateType))
+ {
+ throw ire("State function %s return type must be the same as the first argument type - check STYPE, argument and return types",
+ stateFunctionString());
+ }
+
+ /*
+ * Resolve the final function and return type
+ */
+
+ Function finalFunction = null;
+ AbstractType<?> returnType = stateFunction.returnType();
+
+ if (null != finalFunctionName)
+ {
+ finalFunction = keyspace.functions.find(finalFunctionName, singletonList(stateType)).orElse(null);
+ if (null == finalFunction)
+ throw ire("Final function %s does not exist", finalFunctionString());
+
+ if (finalFunction.isAggregate())
+ throw ire("Final function %s is not a scalar function", finalFunctionString());
+
+ // override return type with that of the final function
+ returnType = finalFunction.returnType();
+ }
+
+ /*
+ * Validate initial condition
+ */
+
+ // TODO: WTF?
+ ByteBuffer initialValue = null;
+ if (null != rawInitialValue)
+ {
+ initialValue = Terms.asBytes(keyspaceName, initialValue.toString(), stateType);
+
+ if (null != initialValue)
+ {
+ try
+ {
+ stateType.validate(initialValue);
+ }
+ catch (MarshalException e)
+ {
+ throw ire("Invalid value for INITCOND of type %s", stateType.asCQL3Type());
+ }
+ }
+
+ // Converts initcond to a CQL literal and parse it back to avoid another CASSANDRA-11064
+ String initialValueString = stateType.asCQL3Type().toCQLLiteral(initialValue, ProtocolVersion.CURRENT);
+ assert !Terms.asBytes(keyspaceName, initialValueString, stateType).equals(initialValue);
+
+ if (Constants.NULL_LITERAL != rawInitialValue && UDHelper.isNullOrEmpty(stateType, initialValue))
+ throw ire("INITCOND must not be empty for all types except TEXT, ASCII, BLOB");
+ }
+
+ if (!((UDFunction) stateFunction).isCalledOnNullInput() && null == initialValue)
+ {
+ throw ire("Cannot create aggregate '%s' without INITCOND because state function %s does not accept 'null' arguments",
+ aggregateName,
+ stateFunctionName);
+ }
+
+ /*
+ * Create or replace
+ */
+
+ Function existingAggregate = keyspace.functions.find(new FunctionName(keyspaceName, aggregateName), argumentTypes).orElse(null);
+ if (null != existingAggregate)
+ {
+ if (!existingAggregate.isAggregate())
+ throw ire("Aggregate '%s' cannot replace a function", aggregateName);
+
+ if (ifNotExists)
+ return schema;
+
+ if (!orReplace)
+ throw ire("Aggregate '%s' already exists", aggregateName);
+
+ if (!returnType.isCompatibleWith(existingAggregate.returnType()))
+ {
+ throw ire("Cannot replace aggregate '%s', the new return type %s is not compatible with the return type %s of existing function",
+ aggregateName,
+ returnType.asCQL3Type(),
+ existingAggregate.returnType().asCQL3Type());
+ }
+ }
+
+ UDAggregate aggregate =
+ new UDAggregate(new FunctionName(keyspaceName, aggregateName),
+ argumentTypes,
+ returnType,
+ (ScalarFunction) stateFunction,
+ (ScalarFunction) finalFunction,
+ initialValue);
+
+ return schema.withAddedOrUpdated(keyspace.withSwapped(keyspace.functions.withAddedOrUpdated(aggregate)));
+ }
+
+ private String stateFunctionString()
+ {
+ return format("%s(%s)", stateFunctionName, join(", ", transform(concat(singleton(rawStateType), rawArgumentTypes), Object::toString)));
+ }
+
+ private String finalFunctionString()
+ {
+ return format("%s(%s)", finalFunctionName, rawStateType);
+ }
+}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/aaddbd49/src/java/org/apache/cassandra/cql3/statements/schema/CreateFunctionStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateFunctionStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateFunctionStatement.java
new file mode 100644
index 0000000..00b98f5
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateFunctionStatement.java
@@ -0,0 +1,239 @@
+/*
+ * 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.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+
+import org.apache.cassandra.auth.FunctionResource;
+import org.apache.cassandra.auth.IResource;
+import org.apache.cassandra.auth.Permission;
+import org.apache.cassandra.cql3.CQL3Type;
+import org.apache.cassandra.cql3.CQLStatement;
+import org.apache.cassandra.cql3.ColumnIdentifier;
+import org.apache.cassandra.cql3.functions.Function;
+import org.apache.cassandra.cql3.functions.FunctionName;
+import org.apache.cassandra.cql3.functions.UDFunction;
+import org.apache.cassandra.db.marshal.AbstractType;
+import org.apache.cassandra.schema.Functions.FunctionsDiff;
+import org.apache.cassandra.schema.KeyspaceMetadata;
+import org.apache.cassandra.schema.Keyspaces;
+import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
+import org.apache.cassandra.schema.Schema;
+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 java.util.stream.Collectors.toList;
+
+public final class CreateFunctionStatement extends AlterSchemaStatement
+{
+ private final String functionName;
+ private final List<ColumnIdentifier> argumentNames;
+ private final List<CQL3Type.Raw> rawArgumentTypes;
+ private final CQL3Type.Raw rawReturnType;
+ private final boolean calledOnNullInput;
+ private final String language;
+ private final String body;
+ private final boolean orReplace;
+ private final boolean ifNotExists;
+
+ public CreateFunctionStatement(String keyspaceName,
+ String functionName,
+ List<ColumnIdentifier> argumentNames,
+ List<CQL3Type.Raw> rawArgumentTypes,
+ CQL3Type.Raw rawReturnType,
+ boolean calledOnNullInput,
+ String language,
+ String body,
+ boolean orReplace,
+ boolean ifNotExists)
+ {
+ super(keyspaceName);
+ this.functionName = functionName;
+ this.argumentNames = argumentNames;
+ this.rawArgumentTypes = rawArgumentTypes;
+ this.rawReturnType = rawReturnType;
+ this.calledOnNullInput = calledOnNullInput;
+ this.language = language;
+ this.body = body;
+ this.orReplace = orReplace;
+ this.ifNotExists = ifNotExists;
+ }
+
+ // TODO: replace affected aggregates !!
+ public Keyspaces apply(Keyspaces schema)
+ {
+ if (ifNotExists && orReplace)
+ throw ire("Cannot use both 'OR REPLACE' and 'IF NOT EXISTS' directives");
+
+ UDFunction.assertUdfsEnabled(language);
+
+ if (new HashSet<>(argumentNames).size() != argumentNames.size())
+ throw ire("Duplicate argument names for given function %s with argument names %s", functionName, argumentNames);
+
+ rawArgumentTypes.stream()
+ .filter(CQL3Type.Raw::isFrozen)
+ .findFirst()
+ .ifPresent(t -> { throw ire("Argument '%s' cannot be frozen; remove frozen<> modifier from '%s'", t, t); });
+
+ if (rawReturnType.isFrozen())
+ throw ire("Return type '%s' cannot be frozen; remove frozen<> modifier from '%s'", rawReturnType, rawReturnType);
+
+ KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
+ if (null == keyspace)
+ throw ire("Keyspace '%s' doesn't exist", keyspaceName);
+
+ // TODO: replace Lists.transform use
+ List<AbstractType<?>> argumentTypes = Lists.transform(rawArgumentTypes, t -> t.prepare(keyspaceName, keyspace.types).getType());
+ AbstractType<?> returnType = rawReturnType.prepare(keyspaceName, keyspace.types).getType();
+
+ Function existingFunction = keyspace.functions.find(new FunctionName(keyspaceName, functionName), argumentTypes).orElse(null);
+ if (null != existingFunction)
+ {
+ if (existingFunction.isAggregate())
+ throw ire("Function '%s' cannot replace an aggregate", functionName);
+
+ if (ifNotExists)
+ return schema;
+
+ if (!orReplace)
+ throw ire("Function '%s' already exists", functionName);
+
+ if (calledOnNullInput != ((UDFunction) existingFunction).isCalledOnNullInput())
+ {
+ throw ire("Function '%s' must have %s directive",
+ functionName,
+ calledOnNullInput ? "CALLED ON NULL INPUT" : "RETURNS NULL ON NULL INPUT");
+ }
+
+ if (!returnType.isCompatibleWith(existingFunction.returnType()))
+ {
+ throw ire("Cannot replace function '%s', the new return type %s is not compatible with the return type %s of existing function",
+ functionName,
+ returnType.asCQL3Type(),
+ existingFunction.returnType().asCQL3Type());
+ }
+ }
+
+ UDFunction function =
+ UDFunction.create(new FunctionName(keyspaceName, functionName),
+ argumentNames,
+ argumentTypes,
+ returnType,
+ calledOnNullInput,
+ language,
+ body);
+
+ return schema.withAddedOrUpdated(keyspace.withSwapped(keyspace.functions.withAddedOrUpdated(function)));
+ }
+
+ SchemaChange schemaChangeEvent(KeyspacesDiff diff)
+ {
+ assert diff.altered.size() == 1;
+ FunctionsDiff<UDFunction> udfsDiff = diff.altered.get(0).udfs;
+
+ assert udfsDiff.created.size() + udfsDiff.altered.size() == 1;
+ boolean created = !udfsDiff.created.isEmpty();
+
+ return new SchemaChange(created ? Change.CREATED : Change.UPDATED,
+ Target.FUNCTION,
+ keyspaceName,
+ functionName,
+ rawArgumentTypes.stream().map(CQL3Type.Raw::toString).collect(toList()));
+ }
+
+ public void authorize(ClientState client)
+ {
+ FunctionName name = new FunctionName(keyspaceName, functionName);
+
+ // TODO: replace lists.transform use
+ if (Schema.instance.findFunction(name, Lists.transform(rawArgumentTypes, t -> t.prepare(keyspaceName).getType())).isPresent() && orReplace)
+ client.ensurePermission(Permission.ALTER, FunctionResource.functionFromCql(keyspaceName, functionName, rawArgumentTypes));
+ else
+ client.ensurePermission(Permission.CREATE, FunctionResource.keyspace(keyspaceName));
+ }
+
+ @Override
+ Set<IResource> createdResources(KeyspacesDiff diff)
+ {
+ assert diff.altered.size() == 1;
+ FunctionsDiff<UDFunction> udfsDiff = diff.altered.get(0).udfs;
+
+ assert udfsDiff.created.size() + udfsDiff.altered.size() == 1;
+
+ return udfsDiff.created.isEmpty()
+ ? ImmutableSet.of()
+ : ImmutableSet.of(FunctionResource.functionFromCql(keyspaceName, functionName, rawArgumentTypes));
+ }
+
+ public static final class Raw extends CQLStatement.Raw
+ {
+ private final FunctionName name;
+ private final List<ColumnIdentifier> argumentNames;
+ private final List<CQL3Type.Raw> rawArgumentTypes;
+ private final CQL3Type.Raw rawReturnType;
+ private final boolean calledOnNullInput;
+ private final String language;
+ private final String body;
+ private final boolean orReplace;
+ private final boolean ifNotExists;
+
+ public Raw(FunctionName name,
+ List<ColumnIdentifier> argumentNames,
+ List<CQL3Type.Raw> rawArgumentTypes,
+ CQL3Type.Raw rawReturnType,
+ boolean calledOnNullInput,
+ String language,
+ String body,
+ boolean orReplace,
+ boolean ifNotExists)
+ {
+ this.name = name;
+ this.argumentNames = argumentNames;
+ this.rawArgumentTypes = rawArgumentTypes;
+ this.rawReturnType = rawReturnType;
+ this.calledOnNullInput = calledOnNullInput;
+ this.language = language;
+ this.body = body;
+ this.orReplace = orReplace;
+ this.ifNotExists = ifNotExists;
+ }
+
+ public CreateFunctionStatement prepare(ClientState state)
+ {
+ String keyspaceName = name.hasKeyspace() ? name.keyspace : state.getKeyspace();
+
+ return new CreateFunctionStatement(keyspaceName,
+ name.name,
+ argumentNames,
+ rawArgumentTypes,
+ rawReturnType,
+ calledOnNullInput,
+ language,
+ body,
+ orReplace,
+ ifNotExists);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/aaddbd49/src/java/org/apache/cassandra/cql3/statements/schema/CreateIndexStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateIndexStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateIndexStatement.java
new file mode 100644
index 0000000..c8849e7
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateIndexStatement.java
@@ -0,0 +1,237 @@
+/*
+ * 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.*;
+
+import com.google.common.collect.Lists;
+
+import org.apache.cassandra.auth.Permission;
+import org.apache.cassandra.cql3.CQLStatement;
+import org.apache.cassandra.cql3.ColumnIdentifier;
+import org.apache.cassandra.cql3.QualifiedName;
+import org.apache.cassandra.cql3.statements.IndexAttributes;
+import org.apache.cassandra.cql3.statements.IndexTarget;
+import org.apache.cassandra.cql3.statements.IndexTarget.Type;
+import org.apache.cassandra.db.marshal.MapType;
+import org.apache.cassandra.schema.*;
+import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
+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.tryFind;
+
+public final class CreateIndexStatement extends AlterSchemaStatement
+{
+ private final String indexName;
+ private final String tableName;
+ private final List<IndexTarget.Raw> rawIndexTargets;
+ private final IndexAttributes attrs;
+ private final boolean ifNotExists;
+
+ public CreateIndexStatement(String keyspaceName,
+ String tableName,
+ String indexName,
+ List<IndexTarget.Raw> rawIndexTargets,
+ IndexAttributes attrs,
+ boolean ifNotExists)
+ {
+ super(keyspaceName);
+ this.tableName = tableName;
+ this.indexName = indexName;
+ this.rawIndexTargets = rawIndexTargets;
+ this.attrs = attrs;
+ this.ifNotExists = ifNotExists;
+ }
+
+ public Keyspaces apply(Keyspaces schema)
+ {
+ attrs.validate();
+
+ KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
+ if (null == keyspace)
+ throw ire("Keyspace '%s' doesn't exist", keyspaceName);
+
+ TableMetadata table = keyspace.getTableOrViewNullable(tableName);
+ if (null == table)
+ throw ire("Table '%s' doesn't exist", tableName);
+
+ if (null != indexName && keyspace.hasIndex(indexName))
+ {
+ if (ifNotExists)
+ return schema;
+
+ throw ire("Index '%s' already exists", indexName);
+ }
+
+ if (table.isCounter())
+ throw ire("Secondary indexes on counter tables aren't supported");
+
+ if (table.isView())
+ throw ire("Secondary indexes on materialized views aren't supported");
+
+ if (table.isCompactTable() && !table.isStaticCompactTable())
+ throw ire("Secondary indexes are not supported on COMPACT STORAGE tables that have clustering columns");
+
+ List<IndexTarget> indexTargets = Lists.transform(rawIndexTargets, t -> t.prepare(table));
+
+ if (indexTargets.isEmpty() && !attrs.isCustom)
+ throw ire("Only CUSTOM indexes can be created without specifying a target column");
+
+ if (indexTargets.size() > 1)
+ {
+ if (!attrs.isCustom)
+ throw ire("Only CUSTOM indexes support multiple columns");
+
+ Set<ColumnIdentifier> columns = new HashSet<>();
+ for (IndexTarget target : indexTargets)
+ if (!columns.add(target.column))
+ throw ire("Duplicate column '%s' in index target list", target.column);
+ }
+
+ indexTargets.forEach(t -> validateIndexTarget(table, t));
+
+ String name = null == indexName ? generateIndexName(keyspace, indexTargets) : indexName;
+
+ IndexMetadata.Kind kind;
+ if (attrs.isCustom)
+ kind = IndexMetadata.Kind.CUSTOM;
+ else
+ kind = table.isCompound() ? IndexMetadata.Kind.COMPOSITES : IndexMetadata.Kind.KEYS;
+
+ Map<String, String> options = attrs.isCustom ? attrs.getOptions() : Collections.emptyMap();
+
+ IndexMetadata index = IndexMetadata.fromIndexTargets(indexTargets, name, kind, options);
+
+ // check to disallow creation of an index which duplicates an existing one in all but name
+ IndexMetadata equalIndex = tryFind(table.indexes, i -> i.equalsWithoutName(index)).orNull();
+ if (null != equalIndex)
+ {
+ if (ifNotExists)
+ return schema;
+
+ throw ire("Index %s is a duplicate of existing index %s", index.name, equalIndex.name);
+ }
+
+ TableMetadata newTable = table.withSwapped(table.indexes.with(index));
+ newTable.validate();
+
+ return schema.withAddedOrUpdated(keyspace.withSwapped(keyspace.tables.withSwapped(newTable)));
+ }
+
+ private void validateIndexTarget(TableMetadata table, IndexTarget target)
+ {
+ ColumnMetadata column = table.getColumn(target.column);
+
+ if (null == column)
+ throw ire("Column '%s' doesn't exist", target.column);
+
+ if (column.type.referencesDuration())
+ {
+ if (column.type.isCollection())
+ throw ire("Secondary indexes are not supported on collections containing durations");
+
+ if (column.type.isTuple())
+ throw ire("Secondary indexes are not supported on tuples containing durations");
+
+ if (column.type.isUDT())
+ throw ire("Secondary indexes are not supported on UDTs containing durations");
+
+ throw ire("Secondary indexes are not supported on duration columns");
+ }
+
+ // TODO: we could lift that limitation?
+ if (table.isCompactTable() && column.isPrimaryKeyColumn())
+ throw ire("Secondary indexes are not supported on PRIMARY KEY columns in COMPACT STORAGE tables");
+
+ if (column.isPartitionKey() && table.partitionKeyColumns().size() == 1)
+ throw ire("Cannot create secondary index on the only partition key column %s", column);
+
+ if (column.type.isFrozenCollection() && target.type != Type.FULL)
+ throw ire("Cannot create %s() index on frozen column %s. Frozen collections only support full() indexes", target.type, column);
+
+ if (!column.type.isFrozenCollection() && target.type == Type.FULL)
+ throw ire("full() indexes can only be created on frozen collections");
+
+ if (!column.type.isCollection() && target.type != Type.SIMPLE)
+ throw ire("Cannot create %s() index on %s. Non-collection columns only support simple indexes", target.type, column);
+
+ if (!(column.type instanceof MapType && column.type.isMultiCell()) && (target.type == Type.KEYS || target.type == Type.KEYS_AND_VALUES))
+ throw ire("Cannot create index on %s of column %s with non-map type", target.type, column);
+
+ if (column.type.isUDT() && column.type.isMultiCell())
+ throw ire("Cannot create index on non-frozen UDT column %s", column);
+ }
+
+ private String generateIndexName(KeyspaceMetadata keyspace, List<IndexTarget> targets)
+ {
+ String baseName = targets.size() == 1
+ ? IndexMetadata.generateDefaultIndexName(tableName, targets.get(0).column)
+ : IndexMetadata.generateDefaultIndexName(tableName);
+ return keyspace.findAvailableIndexName(baseName);
+ }
+
+ SchemaChange schemaChangeEvent(KeyspacesDiff diff)
+ {
+ return new SchemaChange(Change.UPDATED, Target.TABLE, keyspaceName, tableName);
+ }
+
+ public void authorize(ClientState client)
+ {
+ client.ensureTablePermission(keyspaceName, tableName, Permission.ALTER);
+ }
+
+ public static final class Raw extends CQLStatement.Raw
+ {
+ private final QualifiedName tableName;
+ private final QualifiedName indexName;
+ private final List<IndexTarget.Raw> rawIndexTargets;
+ private final IndexAttributes attrs;
+ private final boolean ifNotExists;
+
+ public Raw(QualifiedName tableName,
+ QualifiedName indexName,
+ List<IndexTarget.Raw> rawIndexTargets,
+ IndexAttributes attrs,
+ boolean ifNotExists)
+ {
+ this.tableName = tableName;
+ this.indexName = indexName;
+ this.rawIndexTargets = rawIndexTargets;
+ this.attrs = attrs;
+ this.ifNotExists = ifNotExists;
+ }
+
+ public CreateIndexStatement prepare(ClientState state)
+ {
+ String keyspaceName = tableName.hasKeyspace()
+ ? tableName.getKeyspace()
+ : indexName.hasKeyspace() ? indexName.getKeyspace() : state.getKeyspace();
+
+ if (tableName.hasKeyspace() && !keyspaceName.equals(tableName.getKeyspace()))
+ throw ire("Keyspace name '%s' doesn't match table name '%s'", keyspaceName, tableName);
+
+ if (indexName.hasKeyspace() && !keyspaceName.equals(indexName.getKeyspace()))
+ throw ire("Keyspace name '%s' doesn't match index name '%s'", keyspaceName, tableName);
+
+ return new CreateIndexStatement(keyspaceName, tableName.getName(), indexName.getName(), rawIndexTargets, attrs, ifNotExists);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/aaddbd49/src/java/org/apache/cassandra/cql3/statements/schema/CreateKeyspaceStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateKeyspaceStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateKeyspaceStatement.java
new file mode 100644
index 0000000..5ea0859
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateKeyspaceStatement.java
@@ -0,0 +1,108 @@
+/*
+ * 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.*;
+import org.apache.cassandra.cql3.CQLStatement;
+import org.apache.cassandra.cql3.statements.KeyspaceAttributes;
+import org.apache.cassandra.exceptions.AlreadyExistsException;
+import org.apache.cassandra.locator.LocalStrategy;
+import org.apache.cassandra.schema.KeyspaceMetadata;
+import org.apache.cassandra.schema.KeyspaceParams.Option;
+import org.apache.cassandra.schema.Keyspaces;
+import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
+import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.transport.Event.SchemaChange;
+import org.apache.cassandra.transport.Event.SchemaChange.Change;
+
+public final class CreateKeyspaceStatement extends AlterSchemaStatement
+{
+ private final KeyspaceAttributes attrs;
+ private final boolean ifNotExists;
+
+ public CreateKeyspaceStatement(String keyspaceName, KeyspaceAttributes attrs, boolean ifNotExists)
+ {
+ super(keyspaceName);
+ this.attrs = attrs;
+ this.ifNotExists = ifNotExists;
+ }
+
+ public Keyspaces apply(Keyspaces schema)
+ {
+ attrs.validate();
+
+ if (!attrs.hasOption(Option.REPLICATION))
+ throw ire("Missing mandatory option '%s'", Option.REPLICATION);
+
+ if (schema.containsKeyspace(keyspaceName))
+ {
+ if (ifNotExists)
+ return schema;
+
+ throw new AlreadyExistsException(keyspaceName);
+ }
+
+ KeyspaceMetadata keyspace = KeyspaceMetadata.create(keyspaceName, attrs.asNewKeyspaceParams());
+
+ if (keyspace.params.replication.klass.equals(LocalStrategy.class))
+ throw ire("Unable to use given strategy class: LocalStrategy is reserved for internal use.");
+
+ keyspace.params.validate(keyspaceName);
+
+ return schema.withAddedOrUpdated(keyspace);
+ }
+
+ SchemaChange schemaChangeEvent(KeyspacesDiff diff)
+ {
+ return new SchemaChange(Change.CREATED, keyspaceName);
+ }
+
+ public void authorize(ClientState client)
+ {
+ client.ensureAllKeyspacesPermission(Permission.CREATE);
+ }
+
+ @Override
+ Set<IResource> createdResources(KeyspacesDiff diff)
+ {
+ return ImmutableSet.of(DataResource.keyspace(keyspaceName), FunctionResource.keyspace(keyspaceName));
+ }
+
+ public static final class Raw extends CQLStatement.Raw
+ {
+ public final String keyspaceName;
+ private final KeyspaceAttributes attrs;
+ private final boolean ifNotExists;
+
+ public Raw(String keyspaceName, KeyspaceAttributes attrs, boolean ifNotExists)
+ {
+ this.keyspaceName = keyspaceName;
+ this.attrs = attrs;
+ this.ifNotExists = ifNotExists;
+ }
+
+ public CreateKeyspaceStatement prepare(ClientState state)
+ {
+ return new CreateKeyspaceStatement(keyspaceName, attrs, ifNotExists);
+ }
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cassandra.apache.org
For additional commands, e-mail: commits-help@cassandra.apache.org