Merge commit 'e4c344c58f8ca8f69224855080de4ec266fb671e' into cassandra-3.9 * commit 'e4c344c58f8ca8f69224855080de4ec266fb671e': Avoid deserialization error after altering column type
Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/1f014b2c Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/1f014b2c Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/1f014b2c Branch: refs/heads/trunk Commit: 1f014b2cac739439bcf97adb34ae5b8ede6d2fed Parents: c7547e0 e4c344c Author: Sylvain Lebresne <sylv...@datastax.com> Authored: Thu Jun 30 11:14:48 2016 +0200 Committer: Sylvain Lebresne <sylv...@datastax.com> Committed: Thu Jun 30 11:16:06 2016 +0200 ---------------------------------------------------------------------- CHANGES.txt | 2 + .../apache/cassandra/db/rows/BufferCell.java | 1 - src/java/org/apache/cassandra/db/rows/Cell.java | 16 +++---- .../cassandra/db/rows/UnfilteredSerializer.java | 30 +++++++++---- .../org/apache/cassandra/cql3/CQLTester.java | 39 +++++++++++++++-- .../cql3/validation/operations/AlterTest.java | 46 +++++++++++++------- 6 files changed, 97 insertions(+), 37 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/1f014b2c/CHANGES.txt ---------------------------------------------------------------------- diff --cc CHANGES.txt index 463cf78,573f704..ed884a9 --- a/CHANGES.txt +++ b/CHANGES.txt @@@ -1,35 -1,10 +1,37 @@@ -3.0.9 +3.9 ++Merged from 3.0: + * Fix EOF exception when altering column type (CASSANDRA-11820) Merged from 2.2: * MemoryUtil.getShort() should return an unsigned short also for architectures not supporting unaligned memory accesses (CASSANDRA-11973) +Merged from 2.1: + * Avoid stalling paxos when the paxos state expires (CASSANDRA-12043) + * Remove finished incoming streaming connections from MessagingService (CASSANDRA-11854) + -3.0.8 - * Fix potential race in schema during new table creation (CASSANDRA-12083) +3.8 + * Improve details in compaction log message (CASSANDRA-12080) + * Allow unset values in CQLSSTableWriter (CASSANDRA-11911) + * Chunk cache to request compressor-compatible buffers if pool space is exhausted (CASSANDRA-11993) + * Remove DatabaseDescriptor dependencies from SequentialWriter (CASSANDRA-11579) + * Move skip_stop_words filter before stemming (CASSANDRA-12078) + * Support seek() in EncryptedFileSegmentInputStream (CASSANDRA-11957) + * SSTable tools mishandling LocalPartitioner (CASSANDRA-12002) + * When SEPWorker assigned work, set thread name to match pool (CASSANDRA-11966) + * Add cross-DC latency metrics (CASSANDRA-11596) + * Allow terms in selection clause (CASSANDRA-10783) + * Add bind variables to trace (CASSANDRA-11719) + * Switch counter shards' clock to timestamps (CASSANDRA-9811) + * Introduce HdrHistogram and response/service/wait separation to stress tool (CASSANDRA-11853) + * entry-weighers in QueryProcessor should respect partitionKeyBindIndexes field (CASSANDRA-11718) + * Support older ant versions (CASSANDRA-11807) + * Estimate compressed on disk size when deciding if sstable size limit reached (CASSANDRA-11623) + * cassandra-stress profiles should support case sensitive schemas (CASSANDRA-11546) + * Remove DatabaseDescriptor dependency from FileUtils (CASSANDRA-11578) + * Faster streaming (CASSANDRA-9766) + * Add prepared query parameter to trace for "Execute CQL3 prepared query" session (CASSANDRA-11425) + * Add repaired percentage metric (CASSANDRA-11503) + * Add Change-Data-Capture (CASSANDRA-8844) +Merged from 3.0: * cqlsh: fix error handling in rare COPY FROM failure scenario (CASSANDRA-12070) * Disable autocompaction during drain (CASSANDRA-11878) * Add a metrics timer to MemtablePool and use it to track time spent blocked on memory in MemtableAllocator (CASSANDRA-11327) http://git-wip-us.apache.org/repos/asf/cassandra/blob/1f014b2c/src/java/org/apache/cassandra/db/rows/BufferCell.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/db/rows/BufferCell.java index d998d69,db0ded5..7bb1f4b --- a/src/java/org/apache/cassandra/db/rows/BufferCell.java +++ b/src/java/org/apache/cassandra/db/rows/BufferCell.java @@@ -125,5 -200,166 +125,4 @@@ public class BufferCell extends Abstrac { return EMPTY_SIZE + ObjectSizes.sizeOnHeapExcludingData(value) + (path == null ? 0 : path.unsharedHeapSizeExcludingData()); } - - /** - * The serialization format for cell is: - * [ flags ][ timestamp ][ deletion time ][ ttl ][ path size ][ path ][ value size ][ value ] - * [ 1b ][ 8b (vint) ][ 4b (vint) ][ 4b (vint) ][ 4b (vint) ][ arb ][ 4b (vint) ][ arb ] - * - * where not all field are always present (in fact, only the [ flags ] are guaranteed to be present). The fields have the following - * meaning: - * - [ flags ] is the cell flags. It is a byte for which each bit represents a flag whose meaning is explained below (*_MASK constants) - * - [ timestamp ] is the cell timestamp. Present unless the cell has the USE_TIMESTAMP_MASK. - * - [ deletion time]: the local deletion time for the cell. Present if either the cell is deleted (IS_DELETED_MASK) - * or it is expiring (IS_EXPIRING_MASK) but doesn't have the USE_ROW_TTL_MASK. - * - [ ttl ]: the ttl for the cell. Present if the row is expiring (IS_EXPIRING_MASK) but doesn't have the - * USE_ROW_TTL_MASK. - * - [ value size ] is the size of the [ value ] field. It's present unless either the cell has the HAS_EMPTY_VALUE_MASK, or the value - * for columns of this type have a fixed length. - * - [ path size ] is the size of the [ path ] field. Present iff this is the cell of a complex column. - * - [ value ]: the cell value, unless it has the HAS_EMPTY_VALUE_MASK. - * - [ path ]: the cell path if the column this is a cell of is complex. - */ - static class Serializer implements Cell.Serializer - { - private final static int IS_DELETED_MASK = 0x01; // Whether the cell is a tombstone or not. - private final static int IS_EXPIRING_MASK = 0x02; // Whether the cell is expiring. - private final static int HAS_EMPTY_VALUE_MASK = 0x04; // Wether the cell has an empty value. This will be the case for tombstone in particular. - private final static int USE_ROW_TIMESTAMP_MASK = 0x08; // Wether the cell has the same timestamp than the row this is a cell of. - private final static int USE_ROW_TTL_MASK = 0x10; // Wether the cell has the same ttl than the row this is a cell of. - - public void serialize(Cell cell, ColumnDefinition column, DataOutputPlus out, LivenessInfo rowLiveness, SerializationHeader header) throws IOException - { - assert cell != null; - boolean hasValue = cell.value().hasRemaining(); - boolean isDeleted = cell.isTombstone(); - boolean isExpiring = cell.isExpiring(); - boolean useRowTimestamp = !rowLiveness.isEmpty() && cell.timestamp() == rowLiveness.timestamp(); - boolean useRowTTL = isExpiring && rowLiveness.isExpiring() && cell.ttl() == rowLiveness.ttl() && cell.localDeletionTime() == rowLiveness.localExpirationTime(); - int flags = 0; - if (!hasValue) - flags |= HAS_EMPTY_VALUE_MASK; - - if (isDeleted) - flags |= IS_DELETED_MASK; - else if (isExpiring) - flags |= IS_EXPIRING_MASK; - - if (useRowTimestamp) - flags |= USE_ROW_TIMESTAMP_MASK; - if (useRowTTL) - flags |= USE_ROW_TTL_MASK; - - out.writeByte((byte)flags); - - if (!useRowTimestamp) - header.writeTimestamp(cell.timestamp(), out); - - if ((isDeleted || isExpiring) && !useRowTTL) - header.writeLocalDeletionTime(cell.localDeletionTime(), out); - if (isExpiring && !useRowTTL) - header.writeTTL(cell.ttl(), out); - - if (column.isComplex()) - column.cellPathSerializer().serialize(cell.path(), out); - - if (hasValue) - header.getType(column).writeValue(cell.value(), out); - } - - public Cell deserialize(DataInputPlus in, LivenessInfo rowLiveness, ColumnDefinition column, SerializationHeader header, SerializationHelper helper) throws IOException - { - int flags = in.readUnsignedByte(); - boolean hasValue = (flags & HAS_EMPTY_VALUE_MASK) == 0; - boolean isDeleted = (flags & IS_DELETED_MASK) != 0; - boolean isExpiring = (flags & IS_EXPIRING_MASK) != 0; - boolean useRowTimestamp = (flags & USE_ROW_TIMESTAMP_MASK) != 0; - boolean useRowTTL = (flags & USE_ROW_TTL_MASK) != 0; - - long timestamp = useRowTimestamp ? rowLiveness.timestamp() : header.readTimestamp(in); - - int localDeletionTime = useRowTTL - ? rowLiveness.localExpirationTime() - : (isDeleted || isExpiring ? header.readLocalDeletionTime(in) : NO_DELETION_TIME); - - int ttl = useRowTTL ? rowLiveness.ttl() : (isExpiring ? header.readTTL(in) : NO_TTL); - - CellPath path = column.isComplex() - ? column.cellPathSerializer().deserialize(in) - : null; - - boolean isCounter = localDeletionTime == NO_DELETION_TIME && column.type.isCounter(); - - ByteBuffer value = ByteBufferUtil.EMPTY_BYTE_BUFFER; - if (hasValue) - { - if (helper.canSkipValue(column) || (path != null && helper.canSkipValue(path))) - { - header.getType(column).skipValue(in); - } - else - { - value = header.getType(column).readValue(in, DatabaseDescriptor.getMaxValueSize()); - if (isCounter) - value = helper.maybeClearCounterValue(value); - } - } - - return new BufferCell(column, timestamp, ttl, localDeletionTime, value, path); - } - - public long serializedSize(Cell cell, ColumnDefinition column, LivenessInfo rowLiveness, SerializationHeader header) - { - long size = 1; // flags - boolean hasValue = cell.value().hasRemaining(); - boolean isDeleted = cell.isTombstone(); - boolean isExpiring = cell.isExpiring(); - boolean useRowTimestamp = !rowLiveness.isEmpty() && cell.timestamp() == rowLiveness.timestamp(); - boolean useRowTTL = isExpiring && rowLiveness.isExpiring() && cell.ttl() == rowLiveness.ttl() && cell.localDeletionTime() == rowLiveness.localExpirationTime(); - - if (!useRowTimestamp) - size += header.timestampSerializedSize(cell.timestamp()); - - if ((isDeleted || isExpiring) && !useRowTTL) - size += header.localDeletionTimeSerializedSize(cell.localDeletionTime()); - if (isExpiring && !useRowTTL) - size += header.ttlSerializedSize(cell.ttl()); - - if (column.isComplex()) - size += column.cellPathSerializer().serializedSize(cell.path()); - - if (hasValue) - size += header.getType(column).writtenLength(cell.value()); - - return size; - } - - // Returns if the skipped cell was an actual cell (i.e. it had its presence flag). - public boolean skip(DataInputPlus in, ColumnDefinition column, SerializationHeader header) throws IOException - { - int flags = in.readUnsignedByte(); - boolean hasValue = (flags & HAS_EMPTY_VALUE_MASK) == 0; - boolean isDeleted = (flags & IS_DELETED_MASK) != 0; - boolean isExpiring = (flags & IS_EXPIRING_MASK) != 0; - boolean useRowTimestamp = (flags & USE_ROW_TIMESTAMP_MASK) != 0; - boolean useRowTTL = (flags & USE_ROW_TTL_MASK) != 0; - - if (!useRowTimestamp) - header.skipTimestamp(in); - - if (!useRowTTL && (isDeleted || isExpiring)) - header.skipLocalDeletionTime(in); - - if (!useRowTTL && isExpiring) - header.skipTTL(in); - - if (column.isComplex()) - column.cellPathSerializer().skip(in); - - if (hasValue) - header.getType(column).skipValue(in); -- - return true; - } - } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/1f014b2c/src/java/org/apache/cassandra/db/rows/Cell.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/db/rows/Cell.java index 0b7c46e,d10cc74..19d1f30 --- a/src/java/org/apache/cassandra/db/rows/Cell.java +++ b/src/java/org/apache/cassandra/db/rows/Cell.java @@@ -144,165 -143,15 +144,165 @@@ public abstract class Cell extends Colu // Overrides super type to provide a more precise return type. public abstract Cell purge(DeletionPurger purger, int nowInSec); - public interface Serializer + /** + * The serialization format for cell is: + * [ flags ][ timestamp ][ deletion time ][ ttl ][ path size ][ path ][ value size ][ value ] + * [ 1b ][ 8b (vint) ][ 4b (vint) ][ 4b (vint) ][ 4b (vint) ][ arb ][ 4b (vint) ][ arb ] + * + * where not all field are always present (in fact, only the [ flags ] are guaranteed to be present). The fields have the following + * meaning: + * - [ flags ] is the cell flags. It is a byte for which each bit represents a flag whose meaning is explained below (*_MASK constants) + * - [ timestamp ] is the cell timestamp. Present unless the cell has the USE_TIMESTAMP_MASK. + * - [ deletion time]: the local deletion time for the cell. Present if either the cell is deleted (IS_DELETED_MASK) + * or it is expiring (IS_EXPIRING_MASK) but doesn't have the USE_ROW_TTL_MASK. + * - [ ttl ]: the ttl for the cell. Present if the row is expiring (IS_EXPIRING_MASK) but doesn't have the + * USE_ROW_TTL_MASK. + * - [ value size ] is the size of the [ value ] field. It's present unless either the cell has the HAS_EMPTY_VALUE_MASK, or the value + * for columns of this type have a fixed length. + * - [ path size ] is the size of the [ path ] field. Present iff this is the cell of a complex column. + * - [ value ]: the cell value, unless it has the HAS_EMPTY_VALUE_MASK. + * - [ path ]: the cell path if the column this is a cell of is complex. + */ + static class Serializer { - public void serialize(Cell cell, ColumnDefinition column, DataOutputPlus out, LivenessInfo rowLiveness, SerializationHeader header) throws IOException; + private final static int IS_DELETED_MASK = 0x01; // Whether the cell is a tombstone or not. + private final static int IS_EXPIRING_MASK = 0x02; // Whether the cell is expiring. + private final static int HAS_EMPTY_VALUE_MASK = 0x04; // Wether the cell has an empty value. This will be the case for tombstone in particular. + private final static int USE_ROW_TIMESTAMP_MASK = 0x08; // Wether the cell has the same timestamp than the row this is a cell of. + private final static int USE_ROW_TTL_MASK = 0x10; // Wether the cell has the same ttl than the row this is a cell of. + - public void serialize(Cell cell, DataOutputPlus out, LivenessInfo rowLiveness, SerializationHeader header) throws IOException ++ public void serialize(Cell cell, ColumnDefinition column, DataOutputPlus out, LivenessInfo rowLiveness, SerializationHeader header) throws IOException + { + assert cell != null; + boolean hasValue = cell.value().hasRemaining(); + boolean isDeleted = cell.isTombstone(); + boolean isExpiring = cell.isExpiring(); + boolean useRowTimestamp = !rowLiveness.isEmpty() && cell.timestamp() == rowLiveness.timestamp(); + boolean useRowTTL = isExpiring && rowLiveness.isExpiring() && cell.ttl() == rowLiveness.ttl() && cell.localDeletionTime() == rowLiveness.localExpirationTime(); + int flags = 0; + if (!hasValue) + flags |= HAS_EMPTY_VALUE_MASK; + + if (isDeleted) + flags |= IS_DELETED_MASK; + else if (isExpiring) + flags |= IS_EXPIRING_MASK; + + if (useRowTimestamp) + flags |= USE_ROW_TIMESTAMP_MASK; + if (useRowTTL) + flags |= USE_ROW_TTL_MASK; + + out.writeByte((byte)flags); + + if (!useRowTimestamp) + header.writeTimestamp(cell.timestamp(), out); + + if ((isDeleted || isExpiring) && !useRowTTL) + header.writeLocalDeletionTime(cell.localDeletionTime(), out); + if (isExpiring && !useRowTTL) + header.writeTTL(cell.ttl(), out); + - if (cell.column().isComplex()) - cell.column().cellPathSerializer().serialize(cell.path(), out); ++ if (column.isComplex()) ++ column.cellPathSerializer().serialize(cell.path(), out); + + if (hasValue) - header.getType(cell.column()).writeValue(cell.value(), out); ++ header.getType(column).writeValue(cell.value(), out); + } + + public Cell deserialize(DataInputPlus in, LivenessInfo rowLiveness, ColumnDefinition column, SerializationHeader header, SerializationHelper helper) throws IOException + { + int flags = in.readUnsignedByte(); + boolean hasValue = (flags & HAS_EMPTY_VALUE_MASK) == 0; + boolean isDeleted = (flags & IS_DELETED_MASK) != 0; + boolean isExpiring = (flags & IS_EXPIRING_MASK) != 0; + boolean useRowTimestamp = (flags & USE_ROW_TIMESTAMP_MASK) != 0; + boolean useRowTTL = (flags & USE_ROW_TTL_MASK) != 0; + + long timestamp = useRowTimestamp ? rowLiveness.timestamp() : header.readTimestamp(in); + + int localDeletionTime = useRowTTL + ? rowLiveness.localExpirationTime() + : (isDeleted || isExpiring ? header.readLocalDeletionTime(in) : NO_DELETION_TIME); + + int ttl = useRowTTL ? rowLiveness.ttl() : (isExpiring ? header.readTTL(in) : NO_TTL); - public Cell deserialize(DataInputPlus in, LivenessInfo rowLiveness, ColumnDefinition column, SerializationHeader header, SerializationHelper helper) throws IOException; + CellPath path = column.isComplex() + ? column.cellPathSerializer().deserialize(in) + : null; - public long serializedSize(Cell cell, ColumnDefinition column, LivenessInfo rowLiveness, SerializationHeader header); + ByteBuffer value = ByteBufferUtil.EMPTY_BYTE_BUFFER; + if (hasValue) + { + if (helper.canSkipValue(column) || (path != null && helper.canSkipValue(path))) + { + header.getType(column).skipValue(in); + } + else + { + boolean isCounter = localDeletionTime == NO_DELETION_TIME && column.type.isCounter(); + + value = header.getType(column).readValue(in, DatabaseDescriptor.getMaxValueSize()); + if (isCounter) + value = helper.maybeClearCounterValue(value); + } + } + + return new BufferCell(column, timestamp, ttl, localDeletionTime, value, path); + } + - public long serializedSize(Cell cell, LivenessInfo rowLiveness, SerializationHeader header) ++ public long serializedSize(Cell cell, ColumnDefinition column, LivenessInfo rowLiveness, SerializationHeader header) + { + long size = 1; // flags + boolean hasValue = cell.value().hasRemaining(); + boolean isDeleted = cell.isTombstone(); + boolean isExpiring = cell.isExpiring(); + boolean useRowTimestamp = !rowLiveness.isEmpty() && cell.timestamp() == rowLiveness.timestamp(); + boolean useRowTTL = isExpiring && rowLiveness.isExpiring() && cell.ttl() == rowLiveness.ttl() && cell.localDeletionTime() == rowLiveness.localExpirationTime(); + + if (!useRowTimestamp) + size += header.timestampSerializedSize(cell.timestamp()); + + if ((isDeleted || isExpiring) && !useRowTTL) + size += header.localDeletionTimeSerializedSize(cell.localDeletionTime()); + if (isExpiring && !useRowTTL) + size += header.ttlSerializedSize(cell.ttl()); + - if (cell.column().isComplex()) - size += cell.column().cellPathSerializer().serializedSize(cell.path()); ++ if (column.isComplex()) ++ size += column.cellPathSerializer().serializedSize(cell.path()); + + if (hasValue) - size += header.getType(cell.column()).writtenLength(cell.value()); ++ size += header.getType(column).writtenLength(cell.value()); + + return size; + } // Returns if the skipped cell was an actual cell (i.e. it had its presence flag). - public boolean skip(DataInputPlus in, ColumnDefinition column, SerializationHeader header) throws IOException; + public boolean skip(DataInputPlus in, ColumnDefinition column, SerializationHeader header) throws IOException + { + int flags = in.readUnsignedByte(); + boolean hasValue = (flags & HAS_EMPTY_VALUE_MASK) == 0; + boolean isDeleted = (flags & IS_DELETED_MASK) != 0; + boolean isExpiring = (flags & IS_EXPIRING_MASK) != 0; + boolean useRowTimestamp = (flags & USE_ROW_TIMESTAMP_MASK) != 0; + boolean useRowTTL = (flags & USE_ROW_TTL_MASK) != 0; + + if (!useRowTimestamp) + header.skipTimestamp(in); + + if (!useRowTTL && (isDeleted || isExpiring)) + header.skipLocalDeletionTime(in); + + if (!useRowTTL && isExpiring) + header.skipTTL(in); + + if (column.isComplex()) + column.cellPathSerializer().skip(in); + + if (hasValue) + header.getType(column).skipValue(in); + + return true; + } } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/1f014b2c/src/java/org/apache/cassandra/db/rows/UnfilteredSerializer.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/db/rows/UnfilteredSerializer.java index 890806c,dc6f187..5ca7e03 --- a/src/java/org/apache/cassandra/db/rows/UnfilteredSerializer.java +++ b/src/java/org/apache/cassandra/db/rows/UnfilteredSerializer.java @@@ -25,8 -24,8 +25,9 @@@ import net.nicoulaj.compilecommand.anno import org.apache.cassandra.config.ColumnDefinition; import org.apache.cassandra.db.*; import org.apache.cassandra.io.util.DataInputPlus; +import org.apache.cassandra.io.util.DataOutputBuffer; import org.apache.cassandra.io.util.DataOutputPlus; + import org.apache.cassandra.utils.SearchIterator; /** * Serialize/deserialize a single Unfiltered (both on-wire and on-disk). @@@ -222,15 -175,24 +223,24 @@@ public class UnfilteredSerialize if ((flags & HAS_DELETION) != 0) header.writeDeletionTime(deletion.time(), out); - if (!hasAllColumns) + if ((flags & HAS_ALL_COLUMNS) == 0) Columns.serializer.serializeSubset(Collections2.transform(row, ColumnData::column), headerColumns, out); + SearchIterator<ColumnDefinition, ColumnDefinition> si = headerColumns.iterator(); for (ColumnData data : row) { + // We can obtain the column for data directly from data.column(). However, if the cell/complex data + // originates from a sstable, the column we'll get will have the type used when the sstable was serialized, + // and if that type have been recently altered, that may not be the type we want to serialize the column + // with. So we use the ColumnDefinition from the "header" which is "current". Also see #11810 for what + // happens if we don't do that. + ColumnDefinition column = si.next(data.column()); + assert column != null; + if (data.column.isSimple()) - Cell.serializer.serialize((Cell) data, out, pkLiveness, header); + Cell.serializer.serialize((Cell) data, column, out, pkLiveness, header); else - writeComplexColumn((ComplexColumnData) data, (flags & HAS_COMPLEX_DELETION) != 0, pkLiveness, header, out); - writeComplexColumn((ComplexColumnData) data, column, hasComplexDeletion, pkLiveness, header, out); ++ writeComplexColumn((ComplexColumnData) data, column, (flags & HAS_COMPLEX_DELETION) != 0, pkLiveness, header, out); } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/1f014b2c/test/unit/org/apache/cassandra/cql3/CQLTester.java ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/1f014b2c/test/unit/org/apache/cassandra/cql3/validation/operations/AlterTest.java ---------------------------------------------------------------------- diff --cc test/unit/org/apache/cassandra/cql3/validation/operations/AlterTest.java index 3eb55fd,509aeac..bb4bf48 --- a/test/unit/org/apache/cassandra/cql3/validation/operations/AlterTest.java +++ b/test/unit/org/apache/cassandra/cql3/validation/operations/AlterTest.java @@@ -325,84 -323,19 +323,100 @@@ public class AlterTest extends CQLTeste assertInvalidThrow(InvalidRequestException.class, "ALTER TABLE %s ALTER column1 TYPE ascii"); } + /* + * Test case to check addition of one column + */ + @Test + public void testAlterAddOneColumn() throws Throwable + { + createTable("CREATE TABLE IF NOT EXISTS %s (id int, name text, PRIMARY KEY (id))"); + alterTable("ALTER TABLE %s add mail text;"); + + assertColumnNames(execute("SELECT * FROM %s"), "id", "mail", "name"); + } + + /* + * Test case to check addition of more than one column + */ + @Test + public void testAlterAddMultiColumn() throws Throwable + { + createTable("CREATE TABLE IF NOT EXISTS %s (id int, yearofbirth int, PRIMARY KEY (id))"); + alterTable("ALTER TABLE %s add (firstname text, password blob, lastname text, \"SOME escaped col\" bigint)"); + + assertColumnNames(execute("SELECT * FROM %s"), "id", "SOME escaped col", "firstname", "lastname", "password", "yearofbirth"); + } + + /* + * Should throw SyntaxException if multiple columns are added using wrong syntax. + * Expected Syntax : Alter table T1 add (C1 datatype,C2 datatype,C3 datatype) + */ + @Test(expected = SyntaxException.class) + public void testAlterAddMultiColumnWithoutBraces() throws Throwable + { + execute("ALTER TABLE %s.users add lastname text, password blob, yearofbirth int;"); + } + + /* + * Test case to check deletion of one column + */ + @Test + public void testAlterDropOneColumn() throws Throwable + { + createTable("CREATE TABLE IF NOT EXISTS %s (id text, telephone int, yearofbirth int, PRIMARY KEY (id))"); + alterTable("ALTER TABLE %s drop telephone"); + + assertColumnNames(execute("SELECT * FROM %s"), "id", "yearofbirth"); + } + + @Test + /* + * Test case to check deletion of more than one column + */ + public void testAlterDropMultiColumn() throws Throwable + { + createTable("CREATE TABLE IF NOT EXISTS %s (id text, address text, telephone int, yearofbirth int, \"SOME escaped col\" bigint, PRIMARY KEY (id))"); + alterTable("ALTER TABLE %s drop (address, telephone, \"SOME escaped col\");"); + + assertColumnNames(execute("SELECT * FROM %s"), "id", "yearofbirth"); + } + + /* + * Should throw SyntaxException if multiple columns are dropped using wrong syntax. + */ + @Test(expected = SyntaxException.class) + public void testAlterDeletionColumnWithoutBraces() throws Throwable + { + execute("ALTER TABLE %s.users drop name,address;"); + } + + @Test(expected = InvalidRequestException.class) + public void testAlterAddDuplicateColumn() throws Throwable + { + createTable("CREATE TABLE IF NOT EXISTS %s (id text, address text, telephone int, yearofbirth int, PRIMARY KEY (id))"); + execute("ALTER TABLE %s add (salary int, salary int);"); + } + + @Test(expected = InvalidRequestException.class) + public void testAlterDropDuplicateColumn() throws Throwable + { + createTable("CREATE TABLE IF NOT EXISTS %s (id text, address text, telephone int, yearofbirth int, PRIMARY KEY (id))"); + execute("ALTER TABLE %s drop (address, address);"); + } ++ + @Test + public void testAlterToBlob() throws Throwable + { + // This tests for the bug from #11820 in particular + + createTable("CREATE TABLE %s (a int PRIMARY KEY, b int)"); + + execute("INSERT INTO %s (a, b) VALUES (1, 1)"); + + executeNet(Server.CURRENT_VERSION, "ALTER TABLE %s ALTER b TYPE BLOB"); + + assertRowsNet(Server.CURRENT_VERSION, executeNet(Server.CURRENT_VERSION, "SELECT * FROM %s WHERE a = 1"), + row(1, ByteBufferUtil.bytes(1)) + ); + } }