Merge branch 'cassandra-2.0' into cassandra-2.1
Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/664efd41 Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/664efd41 Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/664efd41 Branch: refs/heads/trunk Commit: 664efd41b037d17a7e2c991a9ad660c5d2ec2ce5 Parents: d088f02 e4d5eda Author: Tyler Hobbs <ty...@datastax.com> Authored: Thu Aug 21 14:23:07 2014 -0500 Committer: Tyler Hobbs <ty...@datastax.com> Committed: Thu Aug 21 14:23:07 2014 -0500 ---------------------------------------------------------------------- CHANGES.txt | 2 ++ .../cql3/statements/AlterKeyspaceStatement.java | 3 ++- .../cql3/statements/AlterTableStatement.java | 3 ++- .../cql3/statements/AlterTypeStatement.java | 3 ++- .../cql3/statements/CreateIndexStatement.java | 5 +++-- .../cql3/statements/CreateKeyspaceStatement.java | 8 +++++--- .../cql3/statements/CreateTableStatement.java | 10 ++++++---- .../cql3/statements/CreateTriggerStatement.java | 3 ++- .../cql3/statements/CreateTypeStatement.java | 5 +++-- .../cql3/statements/DropIndexStatement.java | 5 +++-- .../cql3/statements/DropKeyspaceStatement.java | 8 +++++--- .../cql3/statements/DropTableStatement.java | 8 +++++--- .../cql3/statements/DropTriggerStatement.java | 3 ++- .../cql3/statements/DropTypeStatement.java | 9 ++++++--- .../cql3/statements/SchemaAlteringStatement.java | 18 +++++++++++++----- 15 files changed, 61 insertions(+), 32 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/664efd41/CHANGES.txt ---------------------------------------------------------------------- diff --cc CHANGES.txt index 701fd38,9aeeb29..80eb279 --- a/CHANGES.txt +++ b/CHANGES.txt @@@ -1,52 -1,7 +1,54 @@@ -2.0.10 +2.1.1 + * (cqlsh) Order UDTs according to cross-type dependencies in DESCRIBE + output (CASSANDRA-7659) + * (cqlsh) Fix handling of CAS statement results (CASSANDRA-7671) + * (cqlsh) COPY TO/FROM improvements (CASSANDRA-7405) + * Support list index operations with conditions (CASSANDRA-7499) + * Add max live/tombstoned cells to nodetool cfstats output (CASSANDRA-7731) + * Validate IPv6 wildcard addresses properly (CASSANDRA-7680) + * (cqlsh) Error when tracing query (CASSANDRA-7613) + * Avoid IOOBE when building SyntaxError message snippet (CASSANDRA-7569) + * SSTableExport uses correct validator to create string representation of partition + keys (CASSANDRA-7498) + * Avoid NPEs when receiving type changes for an unknown keyspace (CASSANDRA-7689) + * Add support for custom 2i validation (CASSANDRA-7575) + * Pig support for hadoop CqlInputFormat (CASSANDRA-6454) + * Add listen_interface and rpc_interface options (CASSANDRA-7417) + * Improve schema merge performance (CASSANDRA-7444) + * Adjust MT depth based on # of partition validating (CASSANDRA-5263) + * Optimise NativeCell comparisons (CASSANDRA-6755) + * Configurable client timeout for cqlsh (CASSANDRA-7516) + * Include snippet of CQL query near syntax error in messages (CASSANDRA-7111) +Merged from 2.0: + * Don't send schema change responses and events for no-op DDL + statements (CASSANDRA-7600) * (Hadoop) fix cluster initialisation for a split fetching (CASSANDRA-7774) + * Throw InvalidRequestException when queries contain relations on entire + collection columns (CASSANDRA-7506) + * (cqlsh) enable CTRL-R history search with libedit (CASSANDRA-7577) + * (Hadoop) allow ACFRW to limit nodes to local DC (CASSANDRA-7252) + * (cqlsh) cqlsh should automatically disable tracing when selecting + from system_traces (CASSANDRA-7641) + * (Hadoop) Add CqlOutputFormat (CASSANDRA-6927) + * Don't depend on cassandra config for nodetool ring (CASSANDRA-7508) + * (cqlsh) Fix failing cqlsh formatting tests (CASSANDRA-7703) + * Fix IncompatibleClassChangeError from hadoop2 (CASSANDRA-7229) + * Add 'nodetool sethintedhandoffthrottlekb' (CASSANDRA-7635) + * (cqlsh) Add tab-completion for CREATE/DROP USER IF [NOT] EXISTS (CASSANDRA-7611) + * Catch errors when the JVM pulls the rug out from GCInspector (CASSANDRA-5345) + * cqlsh fails when version number parts are not int (CASSANDRA-7524) +Merged from 1.2: + * Improve PasswordAuthenticator default super user setup (CASSANDRA-7788) + + +2.1.0 + * Correctly remove tmplink files (CASSANDRA-7803) + * (cqlsh) Fix column name formatting for functions, CAS operations, + and UDT field selections (CASSANDRA-7806) + * (cqlsh) Fix COPY FROM handling of null/empty primary key + values (CASSANDRA-7792) + * Fix ordering of static cells (CASSANDRA-7763) +Merged from 2.0: * Configure system.paxos with LeveledCompactionStrategy (CASSANDRA-7753) * Fix ALTER clustering column type from DateType to TimestampType when using DESC clustering order (CASSANRDA-7797) http://git-wip-us.apache.org/repos/asf/cassandra/blob/664efd41/src/java/org/apache/cassandra/cql3/statements/AlterKeyspaceStatement.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/statements/AlterKeyspaceStatement.java index 27cda49,4f6d1f2..e65a51e --- a/src/java/org/apache/cassandra/cql3/statements/AlterKeyspaceStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/AlterKeyspaceStatement.java @@@ -79,18 -79,19 +79,19 @@@ public class AlterKeyspaceStatement ext } } - public void announceMigration(boolean isLocalOnly) throws RequestValidationException - public boolean announceMigration() throws RequestValidationException ++ public boolean announceMigration(boolean isLocalOnly) throws RequestValidationException { KSMetaData ksm = Schema.instance.getKSMetaData(name); // In the (very) unlikely case the keyspace was dropped since validate() if (ksm == null) throw new InvalidRequestException("Unknown keyspace " + name); - MigrationManager.announceKeyspaceUpdate(attrs.asKSMetadataUpdate(ksm)); + MigrationManager.announceKeyspaceUpdate(attrs.asKSMetadataUpdate(ksm), isLocalOnly); + return true; } - public ResultMessage.SchemaChange.Change changeType() + public Event.SchemaChange changeEvent() { - return ResultMessage.SchemaChange.Change.UPDATED; + return new Event.SchemaChange(Event.SchemaChange.Change.UPDATED, keyspace()); } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/664efd41/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java index be28943,dfcd601..3005ac7 --- a/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java @@@ -74,14 -76,13 +74,14 @@@ public class AlterTableStatement extend // validated in announceMigration() } - public void announceMigration(boolean isLocalOnly) throws RequestValidationException - public boolean announceMigration() throws RequestValidationException ++ public boolean announceMigration(boolean isLocalOnly) throws RequestValidationException { CFMetaData meta = validateColumnFamily(keyspace(), columnFamily()); - CFMetaData cfm = meta.clone(); + CFMetaData cfm = meta.copy(); - CFDefinition cfDef = meta.getCfDef(); - CFDefinition.Name name = columnName == null ? null : cfDef.get(columnName); + CQL3Type validator = this.validator == null ? null : this.validator.prepare(keyspace()); + + ColumnDefinition def = columnName == null ? null : cfm.getColumnDefinition(columnName); switch (oType) { case ADD: @@@ -256,7 -265,8 +256,8 @@@ break; } - MigrationManager.announceColumnFamilyUpdate(cfm, false); + MigrationManager.announceColumnFamilyUpdate(cfm, false, isLocalOnly); + return true; } public String toString() http://git-wip-us.apache.org/repos/asf/cassandra/blob/664efd41/src/java/org/apache/cassandra/cql3/statements/AlterTypeStatement.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/statements/AlterTypeStatement.java index 94f7c87,0000000..cfdd65f mode 100644,000000..100644 --- a/src/java/org/apache/cassandra/cql3/statements/AlterTypeStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/AlterTypeStatement.java @@@ -1,343 -1,0 +1,344 @@@ +/* + * 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.nio.ByteBuffer; +import java.util.*; + +import org.apache.cassandra.auth.Permission; +import org.apache.cassandra.config.*; +import org.apache.cassandra.cql3.*; +import org.apache.cassandra.db.composites.CellNames; +import org.apache.cassandra.db.marshal.*; +import org.apache.cassandra.exceptions.*; +import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.service.MigrationManager; +import org.apache.cassandra.transport.Event; + +public abstract class AlterTypeStatement extends SchemaAlteringStatement +{ + protected final UTName name; + + protected AlterTypeStatement(UTName name) + { + super(); + 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) throws InvalidRequestException; + + public static AlterTypeStatement addition(UTName name, ColumnIdentifier fieldName, CQL3Type.Raw type) + { + return new AddOrAlter(name, true, fieldName, type); + } + + public static AlterTypeStatement alter(UTName name, ColumnIdentifier fieldName, CQL3Type.Raw type) + { + return new AddOrAlter(name, false, fieldName, type); + } + + public static AlterTypeStatement renames(UTName name, Map<ColumnIdentifier, ColumnIdentifier> 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. + } + + public Event.SchemaChange changeEvent() + { + return new Event.SchemaChange(Event.SchemaChange.Change.UPDATED, Event.SchemaChange.Target.TYPE, keyspace(), name.getStringTypeName()); + } + + @Override + public String keyspace() + { + return name.getKeyspace(); + } + - public void announceMigration(boolean isLocalOnly) throws InvalidRequestException, ConfigurationException ++ public boolean announceMigration(boolean isLocalOnly) throws InvalidRequestException, ConfigurationException + { + KSMetaData ksm = Schema.instance.getKSMetaData(name.getKeyspace()); + if (ksm == null) + throw new InvalidRequestException(String.format("Cannot alter type in unknown keyspace %s", name.getKeyspace())); + + UserType toUpdate = ksm.userTypes.getType(name.getUserTypeName()); + // Shouldn't happen, unless we race with a drop + if (toUpdate == null) + throw new InvalidRequestException(String.format("No user type named %s exists.", name)); + + UserType updated = makeUpdatedType(toUpdate); + + // 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); + + for (KSMetaData ksm2 : Schema.instance.getKeyspaceDefinitions()) + { + for (CFMetaData cfm : ksm2.cfMetaData().values()) + { + CFMetaData copy = cfm.copy(); + boolean modified = false; + for (ColumnDefinition def : copy.allColumns()) + modified |= updateDefinition(copy, def, toUpdate.keyspace, toUpdate.name, updated); + if (modified) + MigrationManager.announceColumnFamilyUpdate(copy, false, isLocalOnly); + } + + // Other user types potentially using the updated type + for (UserType ut : ksm2.userTypes.getAllTypes().values()) + { + // Re-updating the type we've just updated would be harmless but useless so we avoid it. + // Besides, we use the occasion to drop the old version of the type if it's a type rename + if (ut.keyspace.equals(toUpdate.keyspace) && ut.name.equals(toUpdate.name)) + { + if (!ut.keyspace.equals(updated.keyspace) || !ut.name.equals(updated.name)) + MigrationManager.announceTypeDrop(ut); + continue; + } + AbstractType<?> upd = updateWith(ut, toUpdate.keyspace, toUpdate.name, updated); + if (upd != null) + MigrationManager.announceTypeUpdate((UserType)upd, isLocalOnly); + } + } ++ return true; + } + + private static int getIdxOfField(UserType type, ColumnIdentifier field) + { + for (int i = 0; i < type.size(); i++) + if (field.bytes.equals(type.fieldName(i))) + return i; + return -1; + } + + private boolean updateDefinition(CFMetaData cfm, ColumnDefinition def, String keyspace, ByteBuffer toReplace, UserType updated) + { + AbstractType<?> t = updateWith(def.type, keyspace, toReplace, updated); + if (t == null) + return false; + + // We need to update this validator ... + cfm.addOrReplaceColumnDefinition(def.withNewType(t)); + + // ... but if it's part of the comparator or key validator, we need to go update those too. + switch (def.kind) + { + case PARTITION_KEY: + cfm.keyValidator(updateWith(cfm.getKeyValidator(), keyspace, toReplace, updated)); + break; + case CLUSTERING_COLUMN: + cfm.comparator = CellNames.fromAbstractType(updateWith(cfm.comparator.asAbstractType(), keyspace, toReplace, updated), cfm.comparator.isDense()); + break; + default: + // If it's a collection, we still want to modify the comparator because the collection is aliased in it + if (def.type instanceof CollectionType) + cfm.comparator = CellNames.fromAbstractType(updateWith(cfm.comparator.asAbstractType(), keyspace, toReplace, updated), cfm.comparator.isDense()); + } + return true; + } + + // Update the provided type were all instance of a given userType is replaced by a new version + // Note that this methods reaches inside other UserType, CompositeType and CollectionType. + private static AbstractType<?> updateWith(AbstractType<?> type, String keyspace, ByteBuffer toReplace, UserType updated) + { + if (type instanceof UserType) + { + UserType ut = (UserType)type; + + // If it's directly the type we've updated, then just use the new one. + if (keyspace.equals(ut.keyspace) && toReplace.equals(ut.name)) + return updated; + + // Otherwise, check for nesting + List<AbstractType<?>> updatedTypes = updateTypes(ut.fieldTypes(), keyspace, toReplace, updated); + return updatedTypes == null ? null : new UserType(ut.keyspace, ut.name, new ArrayList<>(ut.fieldNames()), updatedTypes); + } + else if (type instanceof CompositeType) + { + CompositeType ct = (CompositeType)type; + List<AbstractType<?>> updatedTypes = updateTypes(ct.types, keyspace, toReplace, updated); + return updatedTypes == null ? null : CompositeType.getInstance(updatedTypes); + } + else if (type instanceof ColumnToCollectionType) + { + ColumnToCollectionType ctct = (ColumnToCollectionType)type; + Map<ByteBuffer, CollectionType> updatedTypes = null; + for (Map.Entry<ByteBuffer, CollectionType> entry : ctct.defined.entrySet()) + { + AbstractType<?> t = updateWith(entry.getValue(), keyspace, toReplace, updated); + if (t == null) + continue; + + if (updatedTypes == null) + updatedTypes = new HashMap<>(ctct.defined); + + updatedTypes.put(entry.getKey(), (CollectionType)t); + } + return updatedTypes == null ? null : ColumnToCollectionType.getInstance(updatedTypes); + } + else if (type instanceof CollectionType) + { + if (type instanceof ListType) + { + AbstractType<?> t = updateWith(((ListType)type).elements, keyspace, toReplace, updated); + return t == null ? null : ListType.getInstance(t); + } + else if (type instanceof SetType) + { + AbstractType<?> t = updateWith(((SetType)type).elements, keyspace, toReplace, updated); + return t == null ? null : SetType.getInstance(t); + } + else + { + assert type instanceof MapType; + MapType mt = (MapType)type; + AbstractType<?> k = updateWith(mt.keys, keyspace, toReplace, updated); + AbstractType<?> v = updateWith(mt.values, keyspace, toReplace, updated); + if (k == null && v == null) + return null; + return MapType.getInstance(k == null ? mt.keys : k, v == null ? mt.values : v); + } + } + else + { + return null; + } + } + + private static List<AbstractType<?>> updateTypes(List<AbstractType<?>> toUpdate, String keyspace, ByteBuffer toReplace, UserType updated) + { + // But this can also be nested. + List<AbstractType<?>> updatedTypes = null; + for (int i = 0; i < toUpdate.size(); i++) + { + AbstractType<?> t = updateWith(toUpdate.get(i), keyspace, toReplace, updated); + if (t == null) + continue; + + if (updatedTypes == null) + updatedTypes = new ArrayList<>(toUpdate); + + updatedTypes.set(i, t); + } + return updatedTypes; + } + + private static class AddOrAlter extends AlterTypeStatement + { + private final boolean isAdd; + private final ColumnIdentifier fieldName; + private final CQL3Type.Raw type; + + public AddOrAlter(UTName name, boolean isAdd, ColumnIdentifier fieldName, CQL3Type.Raw type) + { + super(name); + this.isAdd = isAdd; + this.fieldName = fieldName; + this.type = type; + } + + private UserType doAdd(UserType toUpdate) throws InvalidRequestException + { + if (getIdxOfField(toUpdate, 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<ByteBuffer> newNames = new ArrayList<>(toUpdate.size() + 1); + newNames.addAll(toUpdate.fieldNames()); + newNames.add(fieldName.bytes); + + List<AbstractType<?>> newTypes = new ArrayList<>(toUpdate.size() + 1); + newTypes.addAll(toUpdate.fieldTypes()); + newTypes.add(type.prepare(keyspace()).getType()); + + return new UserType(toUpdate.keyspace, toUpdate.name, newNames, newTypes); + } + + private UserType doAlter(UserType toUpdate) throws InvalidRequestException + { + int idx = getIdxOfField(toUpdate, fieldName); + if (idx < 0) + throw new InvalidRequestException(String.format("Unknown field %s in type %s", fieldName, name)); + + AbstractType<?> previous = toUpdate.fieldType(idx); + if (!type.prepare(keyspace()).getType().isCompatibleWith(previous)) + throw new InvalidRequestException(String.format("Type %s is incompatible with previous type %s of field %s in user type %s", type, previous.asCQL3Type(), fieldName, name)); + + List<ByteBuffer> newNames = new ArrayList<>(toUpdate.fieldNames()); + List<AbstractType<?>> newTypes = new ArrayList<>(toUpdate.fieldTypes()); + newTypes.set(idx, type.prepare(keyspace()).getType()); + + return new UserType(toUpdate.keyspace, toUpdate.name, newNames, newTypes); + } + + protected UserType makeUpdatedType(UserType toUpdate) throws InvalidRequestException + { + return isAdd ? doAdd(toUpdate) : doAlter(toUpdate); + } + } + + private static class Renames extends AlterTypeStatement + { + private final Map<ColumnIdentifier, ColumnIdentifier> renames; + + public Renames(UTName name, Map<ColumnIdentifier, ColumnIdentifier> renames) + { + super(name); + this.renames = renames; + } + + protected UserType makeUpdatedType(UserType toUpdate) throws InvalidRequestException + { + List<ByteBuffer> newNames = new ArrayList<>(toUpdate.fieldNames()); + List<AbstractType<?>> newTypes = new ArrayList<>(toUpdate.fieldTypes()); + + for (Map.Entry<ColumnIdentifier, ColumnIdentifier> entry : renames.entrySet()) + { + ColumnIdentifier from = entry.getKey(); + ColumnIdentifier to = entry.getValue(); + int idx = getIdxOfField(toUpdate, from); + if (idx < 0) + throw new InvalidRequestException(String.format("Unknown field %s in type %s", from, name)); + newNames.set(idx, to.bytes); + } + + UserType updated = new UserType(toUpdate.keyspace, toUpdate.name, newNames, newTypes); + CreateTypeStatement.checkForDuplicateNames(updated); + return updated; + } + + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/664efd41/src/java/org/apache/cassandra/cql3/statements/CreateIndexStatement.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/statements/CreateIndexStatement.java index 3f2635f,8b40978..4809187 --- a/src/java/org/apache/cassandra/cql3/statements/CreateIndexStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/CreateIndexStatement.java @@@ -110,49 -93,41 +110,50 @@@ public class CreateIndexStatement exten // would pull the full partition every time the static column of partition is 'bar', which sounds like offering a // fair potential for foot-shooting, so I prefer leaving that to a follow up ticket once we have identified cases where // such indexing is actually useful. - if (cd.type == ColumnDefinition.Type.STATIC) + if (cd.isStatic()) throw new InvalidRequestException("Secondary indexes are not allowed on static columns"); - if (cd.getValidator().isCollection() && !properties.isCustom) - throw new InvalidRequestException("Indexes on collections are no yet supported"); - - if (cd.type == ColumnDefinition.Type.PARTITION_KEY && cd.componentIndex == null) - throw new InvalidRequestException(String.format("Cannot add secondary index to already primarily indexed column %s", columnName)); + if (cd.kind == ColumnDefinition.Kind.PARTITION_KEY && cd.isOnAllComponents()) + throw new InvalidRequestException(String.format("Cannot add secondary index to already primarily indexed column %s", target.column)); } - public void announceMigration(boolean isLocalOnly) throws RequestValidationException - public boolean announceMigration() throws RequestValidationException ++ public boolean announceMigration(boolean isLocalOnly) throws RequestValidationException { - logger.debug("Updating column {} definition for index {}", columnName, indexName); - CFMetaData cfm = Schema.instance.getCFMetaData(keyspace(), columnFamily()).clone(); - ColumnDefinition cd = cfm.getColumnDefinition(columnName.key); + logger.debug("Updating column {} definition for index {}", target.column, indexName); + CFMetaData cfm = Schema.instance.getCFMetaData(keyspace(), columnFamily()).copy(); + ColumnDefinition cd = cfm.getColumnDefinition(target.column); if (cd.getIndexType() != null && ifNotExists) - return; + return false; if (properties.isCustom) + { cd.setIndexType(IndexType.CUSTOM, properties.getOptions()); - else if (cfm.getCfDef().isComposite) - cd.setIndexType(IndexType.COMPOSITES, Collections.<String, String>emptyMap()); + } + else if (cfm.comparator.isCompound()) + { + Map<String, String> options = Collections.emptyMap(); + // For now, we only allow indexing values for collections, but we could later allow + // to also index map keys, so we record that this is the values we index to make our + // lives easier then. + if (cd.type.isCollection()) + options = ImmutableMap.of(target.isCollectionKeys ? "index_keys" : "index_values", ""); + cd.setIndexType(IndexType.COMPOSITES, options); + } else + { cd.setIndexType(IndexType.KEYS, Collections.<String, String>emptyMap()); + } cd.setIndexName(indexName); cfm.addDefaultIndexNames(); - MigrationManager.announceColumnFamilyUpdate(cfm, false); + MigrationManager.announceColumnFamilyUpdate(cfm, false, isLocalOnly); + return true; } - public ResultMessage.SchemaChange.Change changeType() + public Event.SchemaChange changeEvent() { // Creating an index is akin to updating the CF - return ResultMessage.SchemaChange.Change.UPDATED; + return new Event.SchemaChange(Event.SchemaChange.Change.UPDATED, Event.SchemaChange.Target.TABLE, keyspace(), columnFamily()); } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/664efd41/src/java/org/apache/cassandra/cql3/statements/CreateKeyspaceStatement.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/statements/CreateKeyspaceStatement.java index 78263b6,7a8473a..8281cbd --- a/src/java/org/apache/cassandra/cql3/statements/CreateKeyspaceStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/CreateKeyspaceStatement.java @@@ -97,11 -97,12 +97,12 @@@ public class CreateKeyspaceStatement ex attrs.getReplicationOptions()); } - public void announceMigration(boolean isLocalOnly) throws RequestValidationException - public boolean announceMigration() throws RequestValidationException ++ public boolean announceMigration(boolean isLocalOnly) throws RequestValidationException { try { - MigrationManager.announceNewKeyspace(attrs.asKSMetadata(name)); + MigrationManager.announceNewKeyspace(attrs.asKSMetadata(name), isLocalOnly); + return true; } catch (AlreadyExistsException e) { http://git-wip-us.apache.org/repos/asf/cassandra/blob/664efd41/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java index 47f05bb,b7f43d3..891a895 --- a/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java @@@ -107,11 -114,12 +107,12 @@@ public class CreateTableStatement exten return columnDefs; } - public void announceMigration(boolean isLocalOnly) throws RequestValidationException - public boolean announceMigration() throws RequestValidationException ++ public boolean announceMigration(boolean isLocalOnly) throws RequestValidationException { try { - MigrationManager.announceNewColumnFamily(getCFMetaData(), isLocalOnly); - MigrationManager.announceNewColumnFamily(getCFMetaData()); ++ MigrationManager.announceNewColumnFamily(getCFMetaData(), isLocalOnly); + return true; } catch (AlreadyExistsException e) { http://git-wip-us.apache.org/repos/asf/cassandra/blob/664efd41/src/java/org/apache/cassandra/cql3/statements/CreateTriggerStatement.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/statements/CreateTriggerStatement.java index 9b7313f,70b3acb..db0cc22 --- a/src/java/org/apache/cassandra/cql3/statements/CreateTriggerStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/CreateTriggerStatement.java @@@ -65,16 -65,17 +65,17 @@@ public class CreateTriggerStatement ext } } - public void announceMigration(boolean isLocalOnly) throws ConfigurationException - public boolean announceMigration() throws ConfigurationException ++ public boolean announceMigration(boolean isLocalOnly) throws ConfigurationException { - CFMetaData cfm = Schema.instance.getCFMetaData(keyspace(), columnFamily()).clone(); + CFMetaData cfm = Schema.instance.getCFMetaData(keyspace(), columnFamily()).copy(); cfm.addTriggerDefinition(TriggerDefinition.create(triggerName, triggerClass)); logger.info("Adding trigger with name {} and class {}", triggerName, triggerClass); - MigrationManager.announceColumnFamilyUpdate(cfm, false); + MigrationManager.announceColumnFamilyUpdate(cfm, false, isLocalOnly); + return true; } - public ResultMessage.SchemaChange.Change changeType() + public Event.SchemaChange changeEvent() { - return ResultMessage.SchemaChange.Change.UPDATED; + return new Event.SchemaChange(Event.SchemaChange.Change.UPDATED, Event.SchemaChange.Target.TABLE, keyspace(), columnFamily()); } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/664efd41/src/java/org/apache/cassandra/cql3/statements/CreateTypeStatement.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/statements/CreateTypeStatement.java index 5224474,0000000..82c2808 mode 100644,000000..100644 --- a/src/java/org/apache/cassandra/cql3/statements/CreateTypeStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/CreateTypeStatement.java @@@ -1,132 -1,0 +1,133 @@@ +/* + * 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.nio.ByteBuffer; +import java.util.*; + +import org.apache.cassandra.auth.Permission; +import org.apache.cassandra.config.*; +import org.apache.cassandra.cql3.*; +import org.apache.cassandra.db.marshal.AbstractType; +import org.apache.cassandra.db.marshal.UTF8Type; +import org.apache.cassandra.db.marshal.UserType; +import org.apache.cassandra.exceptions.*; +import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.service.MigrationManager; +import org.apache.cassandra.transport.Event; + +public class CreateTypeStatement extends SchemaAlteringStatement +{ + private final UTName name; + private final List<ColumnIdentifier> columnNames = new ArrayList<>(); + private final List<CQL3Type.Raw> columnTypes = new ArrayList<>(); + private final boolean ifNotExists; + + public CreateTypeStatement(UTName name, boolean ifNotExists) + { + super(); + this.name = name; + this.ifNotExists = ifNotExists; + } + + @Override + public void prepareKeyspace(ClientState state) throws InvalidRequestException + { + if (!name.hasKeyspace()) + name.setKeyspace(state.getKeyspace()); + } + + public void addDefinition(ColumnIdentifier name, CQL3Type.Raw type) + { + columnNames.add(name); + columnTypes.add(type); + } + + public void checkAccess(ClientState state) throws UnauthorizedException, InvalidRequestException + { + state.hasKeyspaceAccess(keyspace(), Permission.CREATE); + } + + public void validate(ClientState state) throws RequestValidationException + { + KSMetaData ksm = Schema.instance.getKSMetaData(name.getKeyspace()); + if (ksm == null) + throw new InvalidRequestException(String.format("Cannot add type in unknown keyspace %s", name.getKeyspace())); + + if (ksm.userTypes.getType(name.getUserTypeName()) != null && !ifNotExists) + throw new InvalidRequestException(String.format("A user type of name %s already exists", name)); + + for (CQL3Type.Raw type : columnTypes) + if (type.isCounter()) + throw new InvalidRequestException("A user type cannot contain counters"); + } + + public static void checkForDuplicateNames(UserType type) throws InvalidRequestException + { + for (int i = 0; i < type.size() - 1; i++) + { + ByteBuffer fieldName = type.fieldName(i); + for (int j = i+1; j < type.size(); j++) + { + if (fieldName.equals(type.fieldName(j))) + throw new InvalidRequestException(String.format("Duplicate field name %s in type %s", + UTF8Type.instance.getString(fieldName), + UTF8Type.instance.getString(type.name))); + } + } + } + + public Event.SchemaChange changeEvent() + { + return new Event.SchemaChange(Event.SchemaChange.Change.CREATED, Event.SchemaChange.Target.TYPE, keyspace(), name.getStringTypeName()); + } + + @Override + public String keyspace() + { + return name.getKeyspace(); + } + + private UserType createType() throws InvalidRequestException + { + List<ByteBuffer> names = new ArrayList<>(columnNames.size()); + for (ColumnIdentifier name : columnNames) + names.add(name.bytes); + + List<AbstractType<?>> types = new ArrayList<>(columnTypes.size()); + for (CQL3Type.Raw type : columnTypes) + types.add(type.prepare(keyspace()).getType()); + + return new UserType(name.getKeyspace(), name.getUserTypeName(), names, types); + } + - public void announceMigration(boolean isLocalOnly) throws InvalidRequestException, ConfigurationException ++ public boolean announceMigration(boolean isLocalOnly) throws InvalidRequestException, ConfigurationException + { + KSMetaData ksm = Schema.instance.getKSMetaData(name.getKeyspace()); + assert ksm != null; // should haven't validate otherwise + + // Can happen with ifNotExists + if (ksm.userTypes.getType(name.getUserTypeName()) != null) - return; ++ return false; + + UserType type = createType(); + checkForDuplicateNames(type); + MigrationManager.announceNewType(type, isLocalOnly); ++ return true; + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/664efd41/src/java/org/apache/cassandra/cql3/statements/DropIndexStatement.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/statements/DropIndexStatement.java index f70f526,ac5262e..5df8188 --- a/src/java/org/apache/cassandra/cql3/statements/DropIndexStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/DropIndexStatement.java @@@ -60,28 -54,21 +60,29 @@@ public class DropIndexStatement extend // validated in findIndexedCf() } - public ResultMessage.SchemaChange.Change changeType() + public Event.SchemaChange changeEvent() { // Dropping an index is akin to updating the CF - return ResultMessage.SchemaChange.Change.UPDATED; + return new Event.SchemaChange(Event.SchemaChange.Change.UPDATED, Event.SchemaChange.Target.TABLE, keyspace(), columnFamily()); + } + + @Override + public ResultMessage execute(QueryState state, QueryOptions options) throws RequestValidationException + { + announceMigration(false); + return indexedCF == null ? null : new ResultMessage.SchemaChange(changeEvent()); } - public void announceMigration(boolean isLocalOnly) throws InvalidRequestException, ConfigurationException - public boolean announceMigration() throws InvalidRequestException, ConfigurationException ++ public boolean announceMigration(boolean isLocalOnly) throws InvalidRequestException, ConfigurationException { CFMetaData cfm = findIndexedCF(); if (cfm == null) - return; + return false; CFMetaData updatedCfm = updateCFMetadata(cfm); - MigrationManager.announceColumnFamilyUpdate(updatedCfm, false); + indexedCF = updatedCfm.cfName; + MigrationManager.announceColumnFamilyUpdate(updatedCfm, false, isLocalOnly); + return true; } private CFMetaData updateCFMetadata(CFMetaData cfm) http://git-wip-us.apache.org/repos/asf/cassandra/blob/664efd41/src/java/org/apache/cassandra/cql3/statements/DropKeyspaceStatement.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/statements/DropKeyspaceStatement.java index 0a3a510,7582af0..ba6b917 --- a/src/java/org/apache/cassandra/cql3/statements/DropKeyspaceStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/DropKeyspaceStatement.java @@@ -55,11 -55,12 +55,12 @@@ public class DropKeyspaceStatement exte return keyspace; } - public void announceMigration(boolean isLocalOnly) throws ConfigurationException - public boolean announceMigration() throws ConfigurationException ++ public boolean announceMigration(boolean isLocalOnly) throws ConfigurationException { try { - MigrationManager.announceKeyspaceDrop(keyspace); + MigrationManager.announceKeyspaceDrop(keyspace, isLocalOnly); + return true; } catch(ConfigurationException e) { http://git-wip-us.apache.org/repos/asf/cassandra/blob/664efd41/src/java/org/apache/cassandra/cql3/statements/DropTableStatement.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/statements/DropTableStatement.java index 49979b1,65a3f14..e690c3e --- a/src/java/org/apache/cassandra/cql3/statements/DropTableStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/DropTableStatement.java @@@ -54,11 -54,12 +54,12 @@@ public class DropTableStatement extend // validated in announceMigration() } - public void announceMigration(boolean isLocalOnly) throws ConfigurationException - public boolean announceMigration() throws ConfigurationException ++ public boolean announceMigration(boolean isLocalOnly) throws ConfigurationException { try { - MigrationManager.announceColumnFamilyDrop(keyspace(), columnFamily()); + MigrationManager.announceColumnFamilyDrop(keyspace(), columnFamily(), isLocalOnly); + return true; } catch (ConfigurationException e) { http://git-wip-us.apache.org/repos/asf/cassandra/blob/664efd41/src/java/org/apache/cassandra/cql3/statements/DropTriggerStatement.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/statements/DropTriggerStatement.java index 594aeac,f0bd637..4fdc21e --- a/src/java/org/apache/cassandra/cql3/statements/DropTriggerStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/DropTriggerStatement.java @@@ -53,17 -53,18 +53,18 @@@ public class DropTriggerStatement exten ThriftValidation.validateColumnFamily(keyspace(), columnFamily()); } - public void announceMigration(boolean isLocalOnly) throws ConfigurationException - public boolean announceMigration() throws ConfigurationException ++ public boolean announceMigration(boolean isLocalOnly) throws ConfigurationException { - CFMetaData cfm = Schema.instance.getCFMetaData(keyspace(), columnFamily()).clone(); + CFMetaData cfm = Schema.instance.getCFMetaData(keyspace(), columnFamily()).copy(); if (!cfm.removeTrigger(triggerName)) throw new ConfigurationException(String.format("Trigger %s was not found", triggerName)); logger.info("Dropping trigger with name {}", triggerName); - MigrationManager.announceColumnFamilyUpdate(cfm, false); + MigrationManager.announceColumnFamilyUpdate(cfm, false, isLocalOnly); + return true; } - public ResultMessage.SchemaChange.Change changeType() + public Event.SchemaChange changeEvent() { - return ResultMessage.SchemaChange.Change.UPDATED; + return new Event.SchemaChange(Event.SchemaChange.Change.UPDATED, Event.SchemaChange.Target.TABLE, keyspace(), columnFamily()); } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/664efd41/src/java/org/apache/cassandra/cql3/statements/DropTypeStatement.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/statements/DropTypeStatement.java index 5acfdea,0000000..8bcaaf6 mode 100644,000000..100644 --- a/src/java/org/apache/cassandra/cql3/statements/DropTypeStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/DropTypeStatement.java @@@ -1,150 -1,0 +1,153 @@@ +/* + * 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.Permission; +import org.apache.cassandra.config.*; +import org.apache.cassandra.cql3.*; +import org.apache.cassandra.db.marshal.*; +import org.apache.cassandra.exceptions.*; +import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.service.MigrationManager; +import org.apache.cassandra.transport.Event; + +public class DropTypeStatement extends SchemaAlteringStatement +{ + private final UTName name; + private final boolean ifExists; + + public DropTypeStatement(UTName name, boolean ifExists) + { + super(); + this.name = name; + this.ifExists = ifExists; + } + + @Override + public void prepareKeyspace(ClientState state) throws InvalidRequestException + { + if (!name.hasKeyspace()) + name.setKeyspace(state.getKeyspace()); + } + + public void checkAccess(ClientState state) throws UnauthorizedException, InvalidRequestException + { + state.hasKeyspaceAccess(keyspace(), Permission.DROP); + } + + public void validate(ClientState state) throws RequestValidationException + { + KSMetaData ksm = Schema.instance.getKSMetaData(name.getKeyspace()); + if (ksm == null) + throw new InvalidRequestException(String.format("Cannot drop type in unknown keyspace %s", name.getKeyspace())); + + UserType old = ksm.userTypes.getType(name.getUserTypeName()); + if (old == null) + { + if (ifExists) + return; + else + throw new InvalidRequestException(String.format("No user type named %s exists.", name)); + } + + // We don't want to drop a type unless it's not used anymore (mainly because + // if someone drops a type and recreates one with the same name but different + // definition with the previous name still in use, things can get messy). + // We have two places to check: 1) other user type that can nest the one + // we drop and 2) existing tables referencing the type (maybe in a nested + // way). + + for (KSMetaData ksm2 : Schema.instance.getKeyspaceDefinitions()) + { + for (UserType ut : ksm2.userTypes.getAllTypes().values()) + { + if (ut.keyspace.equals(name.getKeyspace()) && ut.name.equals(name.getUserTypeName())) + continue; + if (isUsedBy(ut)) + throw new InvalidRequestException(String.format("Cannot drop user type %s as it is still used by user type %s", name, ut.asCQL3Type())); + } + + for (CFMetaData cfm : ksm2.cfMetaData().values()) + for (ColumnDefinition def : cfm.allColumns()) + if (isUsedBy(def.type)) + throw new InvalidRequestException(String.format("Cannot drop user type %s as it is still used by table %s.%s", name, cfm.ksName, cfm.cfName)); + } + } + + private boolean isUsedBy(AbstractType<?> toCheck) throws RequestValidationException + { + if (toCheck instanceof UserType) + { + UserType ut = (UserType)toCheck; + if (name.getKeyspace().equals(ut.keyspace) && name.getUserTypeName().equals(ut.name)) + return true; + + for (AbstractType<?> subtype : ut.fieldTypes()) + if (isUsedBy(subtype)) + return true; + } + else if (toCheck instanceof CompositeType) + { + CompositeType ct = (CompositeType)toCheck; + for (AbstractType<?> subtype : ct.types) + if (isUsedBy(subtype)) + return true; + } + else if (toCheck instanceof ColumnToCollectionType) + { + for (CollectionType collection : ((ColumnToCollectionType)toCheck).defined.values()) + if (isUsedBy(collection)) + return true; + } + else if (toCheck instanceof CollectionType) + { + if (toCheck instanceof ListType) + return isUsedBy(((ListType)toCheck).elements); + else if (toCheck instanceof SetType) + return isUsedBy(((SetType)toCheck).elements); + else + return isUsedBy(((MapType)toCheck).keys) || isUsedBy(((MapType)toCheck).keys); + } + return false; + } + + public Event.SchemaChange changeEvent() + { + return new Event.SchemaChange(Event.SchemaChange.Change.DROPPED, Event.SchemaChange.Target.TYPE, keyspace(), name.getStringTypeName()); + } + + @Override + public String keyspace() + { + return name.getKeyspace(); + } + - public void announceMigration(boolean isLocalOnly) throws InvalidRequestException, ConfigurationException ++ public boolean announceMigration(boolean isLocalOnly) throws InvalidRequestException, ConfigurationException + { + KSMetaData ksm = Schema.instance.getKSMetaData(name.getKeyspace()); + assert ksm != null; + + UserType toDrop = ksm.userTypes.getType(name.getUserTypeName()); + // Can be null with ifExists - if (toDrop != null) - MigrationManager.announceTypeDrop(toDrop, isLocalOnly); ++ if (toDrop == null) ++ return false; ++ ++ MigrationManager.announceTypeDrop(toDrop, isLocalOnly); ++ return true; + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/664efd41/src/java/org/apache/cassandra/cql3/statements/SchemaAlteringStatement.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/statements/SchemaAlteringStatement.java index e70aac9,845d8cc..8882871 --- a/src/java/org/apache/cassandra/cql3/statements/SchemaAlteringStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/SchemaAlteringStatement.java @@@ -63,26 -62,31 +63,34 @@@ public abstract class SchemaAlteringSta return new Prepared(this); } - public abstract ResultMessage.SchemaChange.Change changeType(); + public abstract Event.SchemaChange changeEvent(); - public abstract void announceMigration(boolean isLocalOnly) throws RequestValidationException; + /** + * Announces the migration to other nodes in the cluster. + * @return true if the execution of this statement resulted in a schema change, false otherwise (when IF NOT EXISTS + * is used, for example) + * @throws RequestValidationException + */ - public abstract boolean announceMigration() throws RequestValidationException; ++ public abstract boolean announceMigration(boolean isLocalOnly) throws RequestValidationException; public ResultMessage execute(QueryState state, QueryOptions options) throws RequestValidationException { - announceMigration(false); - return new ResultMessage.SchemaChange(changeEvent()); + // 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) - boolean didChangeSchema = announceMigration(); - if (!didChangeSchema) - return new ResultMessage.Void(); - - String tableName = cfName == null || columnFamily() == null ? "" : columnFamily(); - return new ResultMessage.SchemaChange(changeType(), keyspace(), tableName); ++ boolean didChangeSchema = announceMigration(false); ++ return didChangeSchema ? new ResultMessage.SchemaChange(changeEvent()) : new ResultMessage.Void(); } public ResultMessage executeInternal(QueryState state, QueryOptions options) { - // executeInternal is for local query only, thus altering schema is not supported - throw new UnsupportedOperationException(); + try + { - announceMigration(true); - return new ResultMessage.SchemaChange(changeEvent()); ++ boolean didChangeSchema = announceMigration(true); ++ return didChangeSchema ? new ResultMessage.SchemaChange(changeEvent()) : new ResultMessage.Void(); + } + catch (RequestValidationException e) + { + throw new RuntimeException(e); + } } }