Author: jbellis Date: Wed Jun 1 15:58:44 2011 New Revision: 1130200 URL: http://svn.apache.org/viewvc?rev=1130200&view=rev Log: add CQL ALTER TABLE patch by pyaskevich; reviewed by jbellis for CASSANDRA-1709
Added: cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/AlterTableStatement.java Modified: cassandra/branches/cassandra-0.8/CHANGES.txt cassandra/branches/cassandra-0.8/doc/cql/CQL.textile cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/Cql.g cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/CreateColumnFamilyStatement.java cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/QueryProcessor.java cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/StatementType.java cassandra/branches/cassandra-0.8/test/system/test_cql.py Modified: cassandra/branches/cassandra-0.8/CHANGES.txt URL: http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.8/CHANGES.txt?rev=1130200&r1=1130199&r2=1130200&view=diff ============================================================================== --- cassandra/branches/cassandra-0.8/CHANGES.txt (original) +++ cassandra/branches/cassandra-0.8/CHANGES.txt Wed Jun 1 15:58:44 2011 @@ -32,6 +32,7 @@ * Fixed rows being cached if they do not exist (CASSANDRA-2723) * fix truncate/compaction race (CASSANDRA-2673) * improve CQL JDBC spec compliance (CASSANDRA-2720) + * add CQL ALTER TABLE (CASSANDRA-1709) 0.8.0-final Modified: cassandra/branches/cassandra-0.8/doc/cql/CQL.textile URL: http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.8/doc/cql/CQL.textile?rev=1130200&r1=1130199&r2=1130200&view=diff ============================================================================== --- cassandra/branches/cassandra-0.8/doc/cql/CQL.textile (original) +++ cassandra/branches/cassandra-0.8/doc/cql/CQL.textile Wed Jun 1 15:58:44 2011 @@ -69,6 +69,17 @@ SELECT ... WHERE <CLAUSE> [LIMIT N] ... Limiting the number of rows returned can be achieved by adding the @LIMIT@ option to a @SELECT@ expression. @LIMIT@ defaults to 10,000 when left unset. +h2. ALTER TABLE + +_Synopsis:_ + +bc. +ALTER TABLE <columnFamily> ADD <column> <validator>; +ALTER TABLE <columnFamily> ALTER <column> TYPE <validator>; +ALTER TABLE <columnFamily> DROP <column>; + +An @ALTER@ is used to manipulate with ColumnFamily columns. It allows you to add new columns, alter and drop existing columns. No results are returned. + h2. INSERT _Synopsis:_ Added: cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/AlterTableStatement.java URL: http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/AlterTableStatement.java?rev=1130200&view=auto ============================================================================== --- cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/AlterTableStatement.java (added) +++ cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/AlterTableStatement.java Wed Jun 1 15:58:44 2011 @@ -0,0 +1,122 @@ +/* + * + * 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.cql; + +import org.apache.cassandra.config.CFMetaData; +import org.apache.cassandra.config.ColumnDefinition; +import org.apache.cassandra.config.ConfigurationException; +import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.db.marshal.TypeParser; +import org.apache.cassandra.db.migration.avro.CfDef; +import org.apache.cassandra.db.migration.avro.ColumnDef; +import org.apache.cassandra.thrift.InvalidRequestException; + +import java.nio.ByteBuffer; + +public class AlterTableStatement +{ + public static enum OperationType + { + ADD, ALTER, DROP + } + + public final OperationType oType; + public final String columnFamily, columnName, validator; + + public AlterTableStatement(String columnFamily, OperationType type, String columnName) + { + this(columnFamily, type, columnName, null); + } + + public AlterTableStatement(String columnFamily, OperationType type, String columnName, String validator) + { + this.columnFamily = columnFamily; + this.oType = type; + this.columnName = columnName; + this.validator = CreateColumnFamilyStatement.comparators.get(validator); // used only for ADD/ALTER commands + } + + public CfDef getCfDef(String keyspace) throws ConfigurationException, InvalidRequestException + { + CFMetaData meta = DatabaseDescriptor.getCFMetaData(keyspace, columnFamily); + + CfDef cfDef = CFMetaData.convertToAvro(meta); + + ByteBuffer columnName = meta.comparator.fromString(this.columnName); + + switch (oType) + { + case ADD: + cfDef.column_metadata.add(new ColumnDefinition(columnName, + TypeParser.parse(validator), + null, + null).deflate()); + break; + + case ALTER: + ColumnDefinition column = meta.getColumnDefinition(columnName); + + if (column == null) + throw new InvalidRequestException(String.format("Column '%s' was not found in CF '%s'", + this.columnName, + columnFamily)); + + column.setValidator(TypeParser.parse(validator)); + + cfDef.column_metadata.add(column.deflate()); + break; + + case DROP: + ColumnDef toDelete = null; + + for (ColumnDef columnDef : cfDef.column_metadata) + { + if (columnDef.name.equals(columnName)) + { + toDelete = columnDef; + } + } + + if (toDelete == null) + throw new InvalidRequestException(String.format("Column '%s' was not found in CF '%s'", + this.columnName, + columnFamily)); + + // it is impossible to use ColumnDefinition.deflate() in remove() method + // it will throw java.lang.ClassCastException: java.lang.String cannot be cast to org.apache.avro.util.Utf8 + // some where deep inside of Avro + cfDef.column_metadata.remove(toDelete); + break; + } + + return cfDef; + } + + public String toString() + { + return String.format("AlterTableStatement(cf=%s, type=%s, column=%s, validator=%s)", + columnFamily, + oType, + columnName, + validator); + } + +} Modified: cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/Cql.g URL: http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/Cql.g?rev=1130200&r1=1130199&r2=1130200&view=diff ============================================================================== --- cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/Cql.g (original) +++ cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/Cql.g Wed Jun 1 15:58:44 2011 @@ -35,6 +35,8 @@ options { import java.util.ArrayList; import org.apache.cassandra.thrift.ConsistencyLevel; import org.apache.cassandra.thrift.InvalidRequestException; + + import static org.apache.cassandra.cql.AlterTableStatement.OperationType; } @members { @@ -113,6 +115,7 @@ query returns [CQLStatement stmnt] | createIndexStatement { $stmnt = new CQLStatement(StatementType.CREATE_INDEX, $createIndexStatement.expr); } | dropKeyspaceStatement { $stmnt = new CQLStatement(StatementType.DROP_KEYSPACE, $dropKeyspaceStatement.ksp); } | dropColumnFamilyStatement { $stmnt = new CQLStatement(StatementType.DROP_COLUMNFAMILY, $dropColumnFamilyStatement.cfam); } + | alterTableStatement { $stmnt = new CQLStatement(StatementType.ALTER_TABLE, $alterTableStatement.expr); } ; // USE <KEYSPACE>; @@ -386,6 +389,27 @@ dropKeyspaceStatement returns [String ks : K_DROP K_KEYSPACE name=( IDENT | STRING_LITERAL | INTEGER ) endStmnt { $ksp = $name.text; } ; + +alterTableStatement returns [AlterTableStatement expr] + : + { + OperationType type = null; + String columnFamily = null, columnName = null, validator = null; + } + K_ALTER K_TABLE name=( IDENT | STRING_LITERAL | INTEGER ) { columnFamily = $name.text; } + ( K_ALTER { type = OperationType.ALTER; } + (col=( IDENT | STRING_LITERAL | INTEGER ) { columnName = $col.text; }) + K_TYPE alterValidator=comparatorType { validator = $alterValidator.text; } + | K_ADD { type = OperationType.ADD; } + (col=( IDENT | STRING_LITERAL | INTEGER ) { columnName = $col.text; }) + addValidator=comparatorType { validator = $addValidator.text; } + | K_DROP { type = OperationType.DROP; } + (col=( IDENT | STRING_LITERAL | INTEGER ) { columnName = $col.text; })) + endStmnt + { + $expr = new AlterTableStatement(columnFamily, type, columnName, validator); + } + ; /** DROP COLUMNFAMILY <CF>; */ dropColumnFamilyStatement returns [String cfam] : K_DROP K_COLUMNFAMILY name=( IDENT | STRING_LITERAL | INTEGER ) endStmnt { $cfam = $name.text; } @@ -468,6 +492,10 @@ K_INTO: I N T O; K_VALUES: V A L U E S; K_TIMESTAMP: T I M E S T A M P; K_TTL: T T L; +K_ALTER: A L T E R; +K_TABLE: T A B L E; +K_ADD: A D D; +K_TYPE: T Y P E; // Case-insensitive alpha characters fragment A: ('a'|'A'); Modified: cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/CreateColumnFamilyStatement.java URL: http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/CreateColumnFamilyStatement.java?rev=1130200&r1=1130199&r2=1130200&view=diff ============================================================================== --- cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/CreateColumnFamilyStatement.java (original) +++ cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/CreateColumnFamilyStatement.java Wed Jun 1 15:58:44 2011 @@ -58,7 +58,7 @@ public class CreateColumnFamilyStatement private static final String KW_ROW_CACHE_PROVIDER = "row_cache_provider"; // Maps CQL short names to the respective Cassandra comparator/validator class names - private static final Map<String, String> comparators = new HashMap<String, String>(); + public static final Map<String, String> comparators = new HashMap<String, String>(); private static final Set<String> keywords = new HashSet<String>(); static Modified: cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/QueryProcessor.java URL: http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/QueryProcessor.java?rev=1130200&r1=1130199&r2=1130200&view=diff ============================================================================== --- cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/QueryProcessor.java (original) +++ cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/QueryProcessor.java Wed Jun 1 15:58:44 2011 @@ -48,7 +48,6 @@ import org.apache.cassandra.db.migration import org.apache.cassandra.db.migration.avro.CfDef; import org.apache.cassandra.dht.*; import org.apache.cassandra.dht.Token; -import org.apache.cassandra.locator.AbstractReplicationStrategy; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.StorageProxy; import org.apache.cassandra.service.StorageService; @@ -791,7 +790,35 @@ public class QueryProcessor result.type = CqlResultType.VOID; return result; - + + case ALTER_TABLE: + AlterTableStatement alterTable = (AlterTableStatement) statement.statement; + + System.out.println(alterTable); + + validateColumnFamily(keyspace, alterTable.columnFamily); + clientState.hasColumnFamilyAccess(alterTable.columnFamily, Permission.WRITE); + validateSchemaAgreement(); + + try + { + applyMigrationOnStage(new UpdateColumnFamily(alterTable.getCfDef(keyspace))); + } + catch (ConfigurationException e) + { + InvalidRequestException ex = new InvalidRequestException(e.getMessage()); + ex.initCause(e); + throw ex; + } + catch (IOException e) + { + InvalidRequestException ex = new InvalidRequestException(e.getMessage()); + ex.initCause(e); + throw ex; + } + + result.type = CqlResultType.VOID; + return result; } return null; // We should never get here. Modified: cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/StatementType.java URL: http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/StatementType.java?rev=1130200&r1=1130199&r2=1130200&view=diff ============================================================================== --- cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/StatementType.java (original) +++ cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/cql/StatementType.java Wed Jun 1 15:58:44 2011 @@ -25,7 +25,7 @@ import java.util.EnumSet; public enum StatementType { SELECT, INSERT, UPDATE, BATCH, USE, TRUNCATE, DELETE, CREATE_KEYSPACE, CREATE_COLUMNFAMILY, CREATE_INDEX, - DROP_KEYSPACE, DROP_COLUMNFAMILY; + DROP_KEYSPACE, DROP_COLUMNFAMILY, ALTER_TABLE; // Statement types that don't require a keyspace to be set. private static final EnumSet<StatementType> topLevel = EnumSet.of(USE, CREATE_KEYSPACE, DROP_KEYSPACE); Modified: cassandra/branches/cassandra-0.8/test/system/test_cql.py URL: http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.8/test/system/test_cql.py?rev=1130200&r1=1130199&r2=1130200&view=diff ============================================================================== --- cassandra/branches/cassandra-0.8/test/system/test_cql.py (original) +++ cassandra/branches/cassandra-0.8/test/system/test_cql.py Wed Jun 1 15:58:44 2011 @@ -1006,3 +1006,78 @@ class TestCql(ThriftTester): r = cursor.fetchone() assert len(r) == 1, "expected 0 results, got %d" % len(r) + + def test_alter_table_statement(self): + "test ALTER TABLE statement" + cursor = init() + cursor.execute(""" + CREATE KEYSPACE AlterTableKS WITH strategy_options:replication_factor = '1' + AND strategy_class = 'SimpleStrategy'; + """) + cursor.execute("USE AlterTableKS;") + + cursor.execute(""" + CREATE COLUMNFAMILY NewCf1 (KEY varint PRIMARY KEY) WITH default_validation = ascii; + """) + + # TODO: temporary (until this can be done with CQL). + ksdef = thrift_client.describe_keyspace("AlterTableKS") + assert len(ksdef.cf_defs) == 1, \ + "expected 1 column family total, found %d" % len(ksdef.cf_defs) + cfam = ksdef.cf_defs[0] + + assert len(cfam.column_metadata) == 0 + + # testing "add a new column" + cursor.execute("ALTER TABLE NewCf1 ADD name varchar") + + ksdef = thrift_client.describe_keyspace("AlterTableKS") + assert len(ksdef.cf_defs) == 1, \ + "expected 1 column family total, found %d" % len(ksdef.cf_defs) + columns = ksdef.cf_defs[0].column_metadata + + assert len(columns) == 1 + assert columns[0].name == 'name' + assert columns[0].validation_class == 'org.apache.cassandra.db.marshal.UTF8Type' + + # testing "alter a column type" + cursor.execute("ALTER TABLE NewCf1 ALTER name TYPE ascii") + + ksdef = thrift_client.describe_keyspace("AlterTableKS") + assert len(ksdef.cf_defs) == 1, \ + "expected 1 column family total, found %d" % len(ksdef.cf_defs) + columns = ksdef.cf_defs[0].column_metadata + + assert len(columns) == 1 + assert columns[0].name == 'name' + assert columns[0].validation_class == 'org.apache.cassandra.db.marshal.AsciiType' + + # alter column with unknown validator + assert_raises(cql.ProgrammingError, + cursor.execute, + "ALTER TABLE NewCf1 ADD name utf8") + + # testing 'drop an existing column' + cursor.execute("ALTER TABLE NewCf1 DROP name") + + ksdef = thrift_client.describe_keyspace("AlterTableKS") + assert len(ksdef.cf_defs) == 1, \ + "expected 1 column family total, found %d" % len(ksdef.cf_defs) + columns = ksdef.cf_defs[0].column_metadata + + assert len(columns) == 0 + + # add column with unknown validator + assert_raises(cql.ProgrammingError, + cursor.execute, + "ALTER TABLE NewCf1 ADD name utf8") + + # alter not existing column + assert_raises(cql.ProgrammingError, + cursor.execute, + "ALTER TABLE NewCf1 ALTER name TYPE uuid") + + # drop not existing column + assert_raises(cql.ProgrammingError, + cursor.execute, + "ALTER TABLE NewCf1 DROP name")