Repository: cassandra Updated Branches: refs/heads/cassandra-3.0 6528fbf25 -> bb56193a3
Fix queries with empty ByteBuffer values in clustering column restrictions patch by Benjamin Lerer; reviewed by jason Brown and Tyler Hobbs for CASSANDRA-12127 Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/527d1897 Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/527d1897 Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/527d1897 Branch: refs/heads/cassandra-3.0 Commit: 527d1897c0973491bb0db098ce5ea1e0bd78898f Parents: bd66547 Author: Benjamin Lerer <b.le...@gmail.com> Authored: Tue Aug 16 15:17:59 2016 +0200 Committer: Benjamin Lerer <b.le...@gmail.com> Committed: Tue Aug 16 15:17:59 2016 +0200 ---------------------------------------------------------------------- CHANGES.txt | 1 + NEWS.txt | 10 + .../cql3/statements/SelectStatement.java | 90 ++++- .../cql3/statements/UpdateStatement.java | 2 +- .../cassandra/db/compaction/Scrubber.java | 157 ++++++-- .../cassandra/db/marshal/ReversedType.java | 10 - .../validation/entities/SecondaryIndexTest.java | 142 ++++++- .../cql3/validation/operations/DeleteTest.java | 42 +++ .../cql3/validation/operations/SelectTest.java | 373 +++++++++++++++++++ .../cassandra/db/marshal/ReversedTypeTest.java | 4 +- 10 files changed, 782 insertions(+), 49 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/527d1897/CHANGES.txt ---------------------------------------------------------------------- diff --git a/CHANGES.txt b/CHANGES.txt index 1275631..dee669a 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 2.1.16 + * Fix queries with empty ByteBuffer values in clustering column restrictions (CASSANDRA-12127) * Disable passing control to post-flush after flush failure to prevent data loss (CASSANDRA-11828) * Allow STCS-in-L0 compactions to reduce scope with LCS (CASSANDRA-12040) * cannot use cql since upgrading python to 2.7.11+ (CASSANDRA-11850) http://git-wip-us.apache.org/repos/asf/cassandra/blob/527d1897/NEWS.txt ---------------------------------------------------------------------- diff --git a/NEWS.txt b/NEWS.txt index 7f1b54a..6a70adc 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -13,6 +13,16 @@ restore snapshots created with the previous major version using the 'sstableloader' tool. You can upgrade the file format of your snapshots using the provided 'sstableupgrade' tool. +2.1.16 +====== + +Upgrading +--------- + - The ReversedType behaviour has been corrected for clustering columns of + BYTES type containing empty value. Scrub should be run on the existing + SSTables containing a descending clustering column of BYTES type to correct + their ordering. See CASSANDRA-12127 for details. + 2.1.15 ====== http://git-wip-us.apache.org/repos/asf/cassandra/blob/527d1897/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java index 245e64e..40f3f33 100644 --- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java @@ -30,6 +30,7 @@ import com.google.common.collect.Iterators; import org.apache.cassandra.auth.Permission; import org.apache.cassandra.cql3.*; +import org.apache.cassandra.cql3.statements.Restriction.Slice; import org.apache.cassandra.cql3.statements.SingleColumnRestriction.Contains; import org.apache.cassandra.db.composites.*; import org.apache.cassandra.transport.messages.ResultMessage; @@ -246,6 +247,9 @@ public class SelectStatement implements CQLStatement private Pageable getPageableCommand(QueryOptions options, int limit, long now) throws RequestValidationException { + if (isNotReturningAnyRows(options)) + return null; + int limitForQuery = updateLimitForQuery(limit); if (isKeyRange || usesSecondaryIndexing) return getRangeCommand(options, limitForQuery, now); @@ -254,6 +258,47 @@ public class SelectStatement implements CQLStatement return commands == null ? null : new Pageable.ReadCommands(commands, limitForQuery); } + /** + * Checks if the query will never return any rows. + * + * @param options the query options + * @return {@code true} if the query will never return any rows, {@false} otherwise + * @throws InvalidRequestException if the request is invalid + */ + private boolean isNotReturningAnyRows(QueryOptions options) throws InvalidRequestException + { + // Dense non-compound tables do not accept empty ByteBuffers. By consequence, we know that: + // - any query with an EQ restriction containing an empty value will not return any result + // - any query with a slice restriction with an empty value for the END bound will not return any result + + if (cfm.comparator.isDense() && !cfm.comparator.isCompound()) + { + for (Restriction restriction : columnRestrictions) + { + if (restriction != null) + { + if (restriction.isEQ()) + { + for (ByteBuffer value : restriction.values(options)) + { + if (!value.hasRemaining()) + return true; + } + } + else if (restriction.isSlice() && ((Slice) restriction).hasBound(Bound.END)) + { + ByteBuffer value = restriction.isMultiColumn() + ? ((MultiColumnRestriction.Slice) restriction).componentBounds(Bound.END, options).get(0) + : ((Slice) restriction).bound(Bound.END, options); + + return !value.hasRemaining(); + } + } + } + } + return false; + } + public Pageable getPageableCommand(QueryOptions options) throws RequestValidationException { return getPageableCommand(options, getLimit(options), System.currentTimeMillis()); @@ -820,6 +865,9 @@ public class SelectStatement implements CQLStatement if (val == null) throw new InvalidRequestException(String.format("Invalid null value for clustering key part %s", def.name)); + if (ignoreInValue(val)) + continue; + Composite prefix = builder.buildWith(val); columns.addAll(addSelectedColumns(prefix)); } @@ -836,6 +884,9 @@ public class SelectStatement implements CQLStatement throw new InvalidRequestException("Invalid null value in condition for column " + cfm.clusteringColumns().get(i + def.position()).name); + if (ignoreInValue(components)) + continue; + Composite prefix = builder.buildWith(components); inValues.addAll(addSelectedColumns(prefix)); } @@ -846,6 +897,32 @@ public class SelectStatement implements CQLStatement return addSelectedColumns(builder.build()); } + /** + * Checks if we should ignore the specified IN value for a clustering column as it will not return any result. + * + * @param val the IN value to check + * @return {@code true} if we should ignore the value, {@code false} otherwise. + */ + private boolean ignoreInValue(ByteBuffer val) + { + // Dense non-compound tables do not accept empty ByteBuffers. By consequence, we know that we can + // ignore any IN value which is an empty byte buffer an which otherwise will trigger an error. + return !cfm.comparator.isCompound() && !val.hasRemaining(); + } + + /** + * Checks if we should ignore the specified IN components for a clustering column as it will not return any result. + * + * @param components the IN components to check + * @return {@code true} if we should ignore the value, {@code false} otherwise. + */ + private boolean ignoreInValue(List<ByteBuffer> components) + { + // Dense non-compound tables do not accept empty ByteBuffers. By consequence, we know that we can + // ignore any IN value which is an empty byte buffer an which otherwise will trigger an error. + return !cfm.comparator.isCompound() && !components.get(0).hasRemaining(); + } + private SortedSet<CellName> addSelectedColumns(Composite prefix) { if (cfm.comparator.isDense()) @@ -1200,12 +1277,17 @@ public class SelectStatement implements CQLStatement if (sliceRestriction.isInclusive(bound)) return null; - if (sliceRestriction.isMultiColumn()) - return type.makeCellName(((MultiColumnRestriction.Slice) sliceRestriction).componentBounds(bound, options).toArray()); - else - return type.makeCellName(sliceRestriction.bound(bound, options)); + // We can only reach that point if cfm.comparator.isCompound() = false and the table has some clustering columns. + // By consequence, we know that the table is a COMPACT table with only one clustering column. + ByteBuffer value = sliceRestriction.isMultiColumn() ? ((MultiColumnRestriction.Slice) sliceRestriction).componentBounds(bound, options).get(0) + : sliceRestriction.bound(bound, options); + + // Dense non-compound tables do not accept empty ByteBuffers. By consequence, if the slice value is empty + // we know that we can treat the slice as inclusive. + return value.hasRemaining() ? type.makeCellName(value) : null; } + private Iterator<Cell> applySliceRestriction(final Iterator<Cell> cells, final QueryOptions options) throws InvalidRequestException { assert sliceRestriction != null; http://git-wip-us.apache.org/repos/asf/cassandra/blob/527d1897/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 bf9a059..39e632a 100644 --- a/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java @@ -84,7 +84,7 @@ public class UpdateStatement extends ModificationStatement if (cfm.comparator.isDense()) { if (prefix.isEmpty()) - throw new InvalidRequestException(String.format("Missing PRIMARY KEY part %s", cfm.clusteringColumns().iterator().next())); + throw new InvalidRequestException(String.format("Missing PRIMARY KEY part %s", cfm.clusteringColumns().iterator().next().name)); // An empty name for the compact value is what we use to recognize the case where there is not column // outside the PK, see CreateStatement. http://git-wip-us.apache.org/repos/asf/cassandra/blob/527d1897/src/java/org/apache/cassandra/db/compaction/Scrubber.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/compaction/Scrubber.java b/src/java/org/apache/cassandra/db/compaction/Scrubber.java index 8bfd37b..2df3665 100644 --- a/src/java/org/apache/cassandra/db/compaction/Scrubber.java +++ b/src/java/org/apache/cassandra/db/compaction/Scrubber.java @@ -23,9 +23,11 @@ import java.util.*; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Throwables; +import com.google.common.collect.AbstractIterator; import com.google.common.collect.Sets; import org.apache.cassandra.db.*; +import org.apache.cassandra.db.columniterator.OnDiskAtomIterator; import org.apache.cassandra.io.sstable.*; import org.apache.cassandra.io.util.FileUtils; import org.apache.cassandra.io.util.RandomAccessReader; @@ -223,20 +225,8 @@ public class Scrubber implements Closeable if (indexFile != null && dataSize != dataSizeFromIndex) outputHandler.warn(String.format("Data file row size %d different from index file row size %d", dataSize, dataSizeFromIndex)); - SSTableIdentityIterator atoms = new SSTableIdentityIterator(sstable, dataFile, key, dataSize, validateColumns); - if (prevKey != null && prevKey.compareTo(key) > 0) - { - saveOutOfOrderRow(prevKey, key, atoms); - continue; - } - - AbstractCompactedRow compactedRow = new LazilyCompactedRow(controller, Collections.singletonList(atoms)); - if (writer.tryAppend(compactedRow) == null) - emptyRows++; - else - goodRows++; - - prevKey = key; + if (tryAppend(prevKey, key, dataSize, writer)) + prevKey = key; } catch (Throwable th) { @@ -252,21 +242,8 @@ public class Scrubber implements Closeable try { dataFile.seek(dataStartFromIndex); - - SSTableIdentityIterator atoms = new SSTableIdentityIterator(sstable, dataFile, key, dataSize, validateColumns); - if (prevKey != null && prevKey.compareTo(key) > 0) - { - saveOutOfOrderRow(prevKey, key, atoms); - continue; - } - - AbstractCompactedRow compactedRow = new LazilyCompactedRow(controller, Collections.singletonList(atoms)); - if (writer.tryAppend(compactedRow) == null) - emptyRows++; - else - goodRows++; - - prevKey = key; + if (tryAppend(prevKey, key, dataSize, writer)) + prevKey = key; } catch (Throwable th2) { @@ -339,6 +316,32 @@ public class Scrubber implements Closeable } } + @SuppressWarnings("resource") + private boolean tryAppend(DecoratedKey prevKey, DecoratedKey key, long dataSize, SSTableRewriter writer) + { + // OrderCheckerIterator will check, at iteration time, that the cells are in the proper order. If it detects + // that one cell is out of order, it will stop returning them. The remaining cells will be sorted and added + // to the outOfOrderRows that will be later written to a new SSTable. + OrderCheckerIterator atoms = new OrderCheckerIterator(new SSTableIdentityIterator(sstable, dataFile, key, dataSize, validateColumns), + cfs.metadata.comparator.onDiskAtomComparator()); + if (prevKey != null && prevKey.compareTo(key) > 0) + { + saveOutOfOrderRow(prevKey, key, atoms); + return false; + } + + AbstractCompactedRow compactedRow = new LazilyCompactedRow(controller, Collections.singletonList(atoms)); + if (writer.tryAppend(compactedRow) == null) + emptyRows++; + else + goodRows++; + + if (atoms.hasOutOfOrderCells()) + saveOutOfOrderRow(key, atoms); + + return true; + } + private void updateIndexKey() { currentIndexKey = nextIndexKey; @@ -385,12 +388,12 @@ public class Scrubber implements Closeable } } - private void saveOutOfOrderRow(DecoratedKey prevKey, DecoratedKey key, SSTableIdentityIterator atoms) + private void saveOutOfOrderRow(DecoratedKey prevKey, DecoratedKey key, OnDiskAtomIterator atoms) { saveOutOfOrderRow(key, atoms, String.format("Out of order row detected (%s found after %s)", key, prevKey)); } - void saveOutOfOrderRow(DecoratedKey key, SSTableIdentityIterator atoms, String message) + void saveOutOfOrderRow(DecoratedKey key, OnDiskAtomIterator atoms, String message) { // TODO bitch if the row is too large? if it is there's not much we can do ... outputHandler.warn(message); @@ -405,6 +408,12 @@ public class Scrubber implements Closeable outOfOrderRows.add(new Row(key, cf)); } + void saveOutOfOrderRow(DecoratedKey key, OrderCheckerIterator atoms) + { + outputHandler.warn(String.format("Out of order cells found at key %s", key)); + outOfOrderRows.add(new Row(key, atoms.getOutOfOrderCells())); + } + public SSTableReader getNewSSTable() { return newSstable; @@ -506,4 +515,90 @@ public class Scrubber implements Closeable this.emptyRows = scrubber.emptyRows; } } + + /** + * In some case like CASSANDRA-12127 the cells might have been stored in the wrong order. This decorator check the + * cells order and collect the out of order cells to correct the problem. + */ + private static final class OrderCheckerIterator extends AbstractIterator<OnDiskAtom> implements OnDiskAtomIterator + { + /** + * The decorated iterator. + */ + private final OnDiskAtomIterator iterator; + + /** + * The atom comparator. + */ + private final Comparator<OnDiskAtom> comparator; + + /** + * The Column family containing the cells which are out of order. + */ + private ColumnFamily outOfOrderCells; + + /** + * The previous atom returned + */ + private OnDiskAtom previous; + + public OrderCheckerIterator(OnDiskAtomIterator iterator, Comparator<OnDiskAtom> comparator) + { + this.iterator = iterator; + this.comparator = comparator; + } + + public ColumnFamily getColumnFamily() + { + return iterator.getColumnFamily(); + } + + public DecoratedKey getKey() + { + return iterator.getKey(); + } + + public void close() throws IOException + { + iterator.close(); + } + + @Override + protected OnDiskAtom computeNext() + { + if (!iterator.hasNext()) + return endOfData(); + + OnDiskAtom next = iterator.next(); + + // If we detect that some cells are out of order we will store and sort the remaining once to insert them + // in a separate SSTable. + if (previous != null && comparator.compare(next, previous) < 0) + { + outOfOrderCells = collectOutOfOrderCells(next, iterator); + return endOfData(); + } + previous = next; + return next; + } + + public boolean hasOutOfOrderCells() + { + return outOfOrderCells != null; + } + + public ColumnFamily getOutOfOrderCells() + { + return outOfOrderCells; + } + + private static ColumnFamily collectOutOfOrderCells(OnDiskAtom atom, OnDiskAtomIterator iterator) + { + ColumnFamily cf = iterator.getColumnFamily().cloneMeShallow(ArrayBackedSortedColumns.factory, false); + cf.addAtom(atom); + while (iterator.hasNext()) + cf.addAtom(iterator.next()); + return cf; + } + } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/527d1897/src/java/org/apache/cassandra/db/marshal/ReversedType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/ReversedType.java b/src/java/org/apache/cassandra/db/marshal/ReversedType.java index 0389a32..53798f8 100644 --- a/src/java/org/apache/cassandra/db/marshal/ReversedType.java +++ b/src/java/org/apache/cassandra/db/marshal/ReversedType.java @@ -60,16 +60,6 @@ public class ReversedType<T> extends AbstractType<T> public int compare(ByteBuffer o1, ByteBuffer o2) { - // An empty byte buffer is always smaller - if (o1.remaining() == 0) - { - return o2.remaining() == 0 ? 0 : -1; - } - if (o2.remaining() == 0) - { - return 1; - } - return baseType.compare(o2, o1); } http://git-wip-us.apache.org/repos/asf/cassandra/blob/527d1897/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java b/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java index a433d06..4a54a9a 100644 --- a/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java +++ b/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java @@ -34,6 +34,8 @@ import org.apache.cassandra.utils.FBUtilities; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.apache.cassandra.utils.ByteBufferUtil.EMPTY_BYTE_BUFFER; +import static org.apache.cassandra.utils.ByteBufferUtil.bytes; public class SecondaryIndexTest extends CQLTester { @@ -562,7 +564,7 @@ public class SecondaryIndexTest extends CQLTester { createTable("CREATE TABLE %s(a int, b frozen<map<int, blob>>, PRIMARY KEY (a))"); createIndex("CREATE INDEX ON %s(full(b))"); - Map<Integer, ByteBuffer> map = new HashMap(); + Map<Integer, ByteBuffer> map = new HashMap<>(); map.put(0, ByteBuffer.allocate(1024 * 65)); failInsert("INSERT INTO %s (a, b) VALUES (0, ?)", map); } @@ -641,4 +643,142 @@ public class SecondaryIndexTest extends CQLTester assertInvalid("CREATE INDEX ON %s (c)"); } + @Test + public void testWithEmptyRestrictionValueAndSecondaryIndex() throws Throwable + { + createTable("CREATE TABLE %s (pk blob, c blob, v blob, PRIMARY KEY ((pk), c))"); + createIndex("CREATE INDEX on %s(c)"); + createIndex("CREATE INDEX on %s(v)"); + + execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?)", bytes("foo123"), bytes("1"), bytes("1")); + execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?)", bytes("foo123"), bytes("2"), bytes("1")); + + for (boolean flush : new boolean[]{false, true}) + { + if (flush) + flush(); + + // Test clustering columns restrictions + assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c = textAsBlob('');")); + + assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c) = (textAsBlob(''));")); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c IN (textAsBlob(''), textAsBlob('1'));"), + row(bytes("foo123"), bytes("1"), bytes("1"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c) IN ((textAsBlob('')), (textAsBlob('1')));"), + row(bytes("foo123"), bytes("1"), bytes("1"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c > textAsBlob('') AND v = textAsBlob('1') ALLOW FILTERING;"), + row(bytes("foo123"), bytes("1"), bytes("1")), + row(bytes("foo123"), bytes("2"), bytes("1"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c >= textAsBlob('') AND v = textAsBlob('1') ALLOW FILTERING;"), + row(bytes("foo123"), bytes("1"), bytes("1")), + row(bytes("foo123"), bytes("2"), bytes("1"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c) >= (textAsBlob('')) AND v = textAsBlob('1') ALLOW FILTERING;"), + row(bytes("foo123"), bytes("1"), bytes("1")), + row(bytes("foo123"), bytes("2"), bytes("1"))); + + assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c <= textAsBlob('') AND v = textAsBlob('1') ALLOW FILTERING;")); + + assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c) <= (textAsBlob('')) AND v = textAsBlob('1') ALLOW FILTERING;")); + + assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c) < (textAsBlob('')) AND v = textAsBlob('1') ALLOW FILTERING;")); + + assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c < textAsBlob('') AND v = textAsBlob('1') ALLOW FILTERING;")); + + assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c > textAsBlob('') AND c < textAsBlob('') AND v = textAsBlob('1') ALLOW FILTERING;")); + + assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c) > (textAsBlob('')) AND (c) < (textAsBlob('')) AND v = textAsBlob('1') ALLOW FILTERING;")); + } + + execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?)", + bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1")); + + for (boolean flush : new boolean[]{false, true}) + { + if (flush) + flush(); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c = textAsBlob('');"), + row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c) = (textAsBlob(''));"), + row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c IN (textAsBlob(''), textAsBlob('1'));"), + row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1")), + row(bytes("foo123"), bytes("1"), bytes("1"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c) IN ((textAsBlob('')), (textAsBlob('1')));"), + row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1")), + row(bytes("foo123"), bytes("1"), bytes("1"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c > textAsBlob('') AND v = textAsBlob('1') ALLOW FILTERING;"), + row(bytes("foo123"), bytes("1"), bytes("1")), + row(bytes("foo123"), bytes("2"), bytes("1"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c >= textAsBlob('') AND v = textAsBlob('1') ALLOW FILTERING;"), + row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1")), + row(bytes("foo123"), bytes("1"), bytes("1")), + row(bytes("foo123"), bytes("2"), bytes("1"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c) >= (textAsBlob('')) AND v = textAsBlob('1') ALLOW FILTERING;"), + row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1")), + row(bytes("foo123"), bytes("1"), bytes("1")), + row(bytes("foo123"), bytes("2"), bytes("1"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c <= textAsBlob('') AND v = textAsBlob('1') ALLOW FILTERING;"), + row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c) <= (textAsBlob('')) AND v = textAsBlob('1') ALLOW FILTERING;"), + row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1"))); + + assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c < textAsBlob('') AND v = textAsBlob('1') ALLOW FILTERING;")); + + assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c) < (textAsBlob('')) AND v = textAsBlob('1') ALLOW FILTERING;")); + + assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c >= textAsBlob('') AND c < textAsBlob('') AND v = textAsBlob('1') ALLOW FILTERING;")); + + assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c) >= (textAsBlob('')) AND c < textAsBlob('') AND v = textAsBlob('1') ALLOW FILTERING;")); + + // Test restrictions on non-primary key value + assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND v = textAsBlob('');")); + } + + execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?)", + bytes("foo123"), bytes("3"), EMPTY_BYTE_BUFFER); + + for (boolean flush : new boolean[]{false, true}) + { + if (flush) + flush(); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND v = textAsBlob('');"), + row(bytes("foo123"), bytes("3"), EMPTY_BYTE_BUFFER)); + } + } + + @Test + public void testEmptyRestrictionValueWithSecondaryIndexAndCompactTables() throws Throwable + { + createTable("CREATE TABLE %s (pk blob, c blob, v blob, PRIMARY KEY ((pk), c)) WITH COMPACT STORAGE"); + assertInvalidMessage("Secondary indexes are not supported on PRIMARY KEY columns in COMPACT STORAGE tables", + "CREATE INDEX on %s(c)"); + + createTable("CREATE TABLE %s (pk blob PRIMARY KEY, v blob) WITH COMPACT STORAGE"); + createIndex("CREATE INDEX on %s(v)"); + + execute("INSERT INTO %s (pk, v) VALUES (?, ?)", bytes("foo123"), bytes("1")); + + // Test restrictions on non-primary key value + assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND v = textAsBlob('');")); + + execute("INSERT INTO %s (pk, v) VALUES (?, ?)", bytes("foo124"), EMPTY_BYTE_BUFFER); + + assertRows(execute("SELECT * FROM %s WHERE v = textAsBlob('');"), + row(bytes("foo124"), EMPTY_BYTE_BUFFER)); + } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/527d1897/test/unit/org/apache/cassandra/cql3/validation/operations/DeleteTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/validation/operations/DeleteTest.java b/test/unit/org/apache/cassandra/cql3/validation/operations/DeleteTest.java index 476ec83..6bd5f26 100644 --- a/test/unit/org/apache/cassandra/cql3/validation/operations/DeleteTest.java +++ b/test/unit/org/apache/cassandra/cql3/validation/operations/DeleteTest.java @@ -23,11 +23,15 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import org.apache.commons.lang3.StringUtils; + import org.junit.Test; import org.apache.cassandra.cql3.CQLTester; import static org.junit.Assert.assertEquals; +import static org.apache.cassandra.utils.ByteBufferUtil.EMPTY_BYTE_BUFFER; +import static org.apache.cassandra.utils.ByteBufferUtil.bytes; public class DeleteTest extends CQLTester { @@ -326,4 +330,42 @@ public class DeleteTest extends CQLTester assertEmpty(execute("select * from %s where a=1 and b=1")); } + + @Test + public void testDeleteWithEmptyRestrictionValue() throws Throwable + { + for (String options : new String[] { "", " WITH COMPACT STORAGE" }) + { + createTable("CREATE TABLE %s (pk blob, c blob, v blob, PRIMARY KEY (pk, c))" + options); + + if (StringUtils.isEmpty(options)) + { + execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?)", bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1")); + execute("DELETE FROM %s WHERE pk = textAsBlob('foo123') AND c = textAsBlob('');"); + + assertEmpty(execute("SELECT * FROM %s")); + } + else + { + assertInvalid("Invalid empty or null value for column c", + "DELETE FROM %s WHERE pk = textAsBlob('foo123') AND c = textAsBlob('')"); + assertInvalid("Invalid empty or null value for column c", + "DELETE FROM %s WHERE pk = textAsBlob('foo123') AND c IN (textAsBlob(''), textAsBlob('1'))"); + } + } + } + + @Test + public void testDeleteWithMultipleClusteringColumnsAndEmptyRestrictionValue() throws Throwable + { + for (String options : new String[] { "", " WITH COMPACT STORAGE" }) + { + createTable("CREATE TABLE %s (pk blob, c1 blob, c2 blob, v blob, PRIMARY KEY (pk, c1, c2))" + options); + + execute("INSERT INTO %s (pk, c1, c2, v) VALUES (?, ?, ?, ?)", bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1"), bytes("1")); + execute("DELETE FROM %s WHERE pk = textAsBlob('foo123') AND c1 = textAsBlob('');"); + + assertEmpty(execute("SELECT * FROM %s")); + } + } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/527d1897/test/unit/org/apache/cassandra/cql3/validation/operations/SelectTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/validation/operations/SelectTest.java b/test/unit/org/apache/cassandra/cql3/validation/operations/SelectTest.java index 68cf6f8..cef4635 100644 --- a/test/unit/org/apache/cassandra/cql3/validation/operations/SelectTest.java +++ b/test/unit/org/apache/cassandra/cql3/validation/operations/SelectTest.java @@ -32,6 +32,8 @@ import org.apache.cassandra.exceptions.InvalidRequestException; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.apache.cassandra.utils.ByteBufferUtil.EMPTY_BYTE_BUFFER; +import static org.apache.cassandra.utils.ByteBufferUtil.bytes; /** * Test column ranges and ordering with static column in table @@ -1328,4 +1330,375 @@ public class SelectTest extends CQLTester row(1, 2, 3), row(1, 1, 3)); } + + @Test + public void testEmptyRestrictionValue() throws Throwable + { + for (String options : new String[] { "", " WITH COMPACT STORAGE" }) + { + createTable("CREATE TABLE %s (pk blob, c blob, v blob, PRIMARY KEY ((pk), c))" + options); + execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?)", + bytes("foo123"), bytes("1"), bytes("1")); + execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?)", + bytes("foo123"), bytes("2"), bytes("2")); + + for (boolean flush : new boolean[]{false, true}) + { + if (flush) + flush(); + + assertInvalidMessage("Key may not be empty", "SELECT * FROM %s WHERE pk = textAsBlob('');"); + assertInvalidMessage("Key may not be empty", "SELECT * FROM %s WHERE pk IN (textAsBlob(''), textAsBlob('1'));"); + + assertInvalidMessage("Key may not be empty", + "INSERT INTO %s (pk, c, v) VALUES (?, ?, ?)", + EMPTY_BYTE_BUFFER, bytes("2"), bytes("2")); + + // Test clustering columns restrictions + assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c = textAsBlob('');")); + + assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c) = (textAsBlob(''));")); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c IN (textAsBlob(''), textAsBlob('1'));"), + row(bytes("foo123"), bytes("1"), bytes("1"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c) IN ((textAsBlob('')), (textAsBlob('1')));"), + row(bytes("foo123"), bytes("1"), bytes("1"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c > textAsBlob('');"), + row(bytes("foo123"), bytes("1"), bytes("1")), + row(bytes("foo123"), bytes("2"), bytes("2"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c) > (textAsBlob(''));"), + row(bytes("foo123"), bytes("1"), bytes("1")), + row(bytes("foo123"), bytes("2"), bytes("2"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c >= textAsBlob('');"), + row(bytes("foo123"), bytes("1"), bytes("1")), + row(bytes("foo123"), bytes("2"), bytes("2"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c) >= (textAsBlob(''));"), + row(bytes("foo123"), bytes("1"), bytes("1")), + row(bytes("foo123"), bytes("2"), bytes("2"))); + + assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c <= textAsBlob('');")); + + assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c) <= (textAsBlob(''));")); + + assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c < textAsBlob('');")); + + assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c) < (textAsBlob(''));")); + + assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c > textAsBlob('') AND c < textAsBlob('');")); + } + + if (options.contains("COMPACT")) + { + assertInvalidMessage("Missing PRIMARY KEY part c", + "INSERT INTO %s (pk, c, v) VALUES (?, ?, ?)", + bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("4")); + + // Test restrictions on non-primary key value + assertInvalidMessage("Predicates on the non-primary-key column (v) of a COMPACT table are not yet supported", + "SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND v = textAsBlob('') ALLOW FILTERING;"); + } + else + { + execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?)", + bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("4")); + + for (boolean flush : new boolean[]{false, true}) + { + if (flush) + flush(); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c = textAsBlob('');"), + row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("4"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c) = (textAsBlob(''));"), + row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("4"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c IN (textAsBlob(''), textAsBlob('1'));"), + row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("4")), + row(bytes("foo123"), bytes("1"), bytes("1"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c) IN ((textAsBlob('')), (textAsBlob('1')));"), + row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("4")), + row(bytes("foo123"), bytes("1"), bytes("1"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c > textAsBlob('');"), + row(bytes("foo123"), bytes("1"), bytes("1")), + row(bytes("foo123"), bytes("2"), bytes("2"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c) > (textAsBlob(''));"), + row(bytes("foo123"), bytes("1"), bytes("1")), + row(bytes("foo123"), bytes("2"), bytes("2"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c >= textAsBlob('');"), + row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("4")), + row(bytes("foo123"), bytes("1"), bytes("1")), + row(bytes("foo123"), bytes("2"), bytes("2"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c) >= (textAsBlob(''));"), + row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("4")), + row(bytes("foo123"), bytes("1"), bytes("1")), + row(bytes("foo123"), bytes("2"), bytes("2"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c <= textAsBlob('');"), + row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("4"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c) <= (textAsBlob(''));"), + row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("4"))); + + assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c < textAsBlob('');")); + + assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c) < (textAsBlob(''));")); + + assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c >= textAsBlob('') AND c < textAsBlob('');")); + + // Test restrictions on non-primary key value + assertInvalidMessage("No secondary indexes on the restricted columns support the provided operators", + "SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND v = textAsBlob('') ALLOW FILTERING;"); + } + } + } + } + + @Test + public void testEmptyRestrictionValueWithMultipleClusteringColumns() throws Throwable + { + for (String options : new String[] { "", " WITH COMPACT STORAGE" }) + { + createTable("CREATE TABLE %s (pk blob, c1 blob, c2 blob, v blob, PRIMARY KEY (pk, c1, c2))" + options); + execute("INSERT INTO %s (pk, c1, c2, v) VALUES (?, ?, ?, ?)", bytes("foo123"), bytes("1"), bytes("1"), bytes("1")); + execute("INSERT INTO %s (pk, c1, c2, v) VALUES (?, ?, ?, ?)", bytes("foo123"), bytes("1"), bytes("2"), bytes("2")); + + for (boolean flush : new boolean[]{false, true}) + { + if (flush) + flush(); + + assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c1 = textAsBlob('');")); + + assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c1 = textAsBlob('1') AND c2 = textAsBlob('');")); + + assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c1, c2) = (textAsBlob('1'), textAsBlob(''));")); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c1 = textAsBlob('1') AND c2 IN (textAsBlob(''), textAsBlob('1'));"), + row(bytes("foo123"), bytes("1"), bytes("1"), bytes("1"))); + + assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c1 = textAsBlob('') AND c2 = textAsBlob('1');")); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c1, c2) IN ((textAsBlob(''), textAsBlob('1')), (textAsBlob('1'), textAsBlob('1')));"), + row(bytes("foo123"), bytes("1"), bytes("1"), bytes("1"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c1 > textAsBlob('');"), + row(bytes("foo123"), bytes("1"), bytes("1"), bytes("1")), + row(bytes("foo123"), bytes("1"), bytes("2"), bytes("2"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c1 = textAsBlob('1') AND c2 > textAsBlob('');"), + row(bytes("foo123"), bytes("1"), bytes("1"), bytes("1")), + row(bytes("foo123"), bytes("1"), bytes("2"), bytes("2"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c1, c2) > (textAsBlob(''), textAsBlob('1'));"), + row(bytes("foo123"), bytes("1"), bytes("1"), bytes("1")), + row(bytes("foo123"), bytes("1"), bytes("2"), bytes("2"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c1 = textAsBlob('1') AND c2 >= textAsBlob('');"), + row(bytes("foo123"), bytes("1"), bytes("1"), bytes("1")), + row(bytes("foo123"), bytes("1"), bytes("2"), bytes("2"))); + + assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c1 = textAsBlob('1') AND c2 <= textAsBlob('');")); + + assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c1, c2) <= (textAsBlob('1'), textAsBlob(''));")); + } + + execute("INSERT INTO %s (pk, c1, c2, v) VALUES (?, ?, ?, ?)", + bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1"), bytes("4")); + + for (boolean flush : new boolean[]{false, true}) + { + if (flush) + flush(); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c1 = textAsBlob('');"), + row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1"), bytes("4"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c1 = textAsBlob('') AND c2 = textAsBlob('1');"), + row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1"), bytes("4"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c1, c2) = (textAsBlob(''), textAsBlob('1'));"), + row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1"), bytes("4"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c1 = textAsBlob('') AND c2 IN (textAsBlob(''), textAsBlob('1'));"), + row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1"), bytes("4"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c1 = textAsBlob('1') AND c2 IN (textAsBlob(''), textAsBlob('1'));"), + row(bytes("foo123"), bytes("1"), bytes("1"), bytes("1"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c1, c2) IN ((textAsBlob(''), textAsBlob('1')), (textAsBlob('1'), textAsBlob('1')));"), + row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1"), bytes("4")), + row(bytes("foo123"), bytes("1"), bytes("1"), bytes("1"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c1, c2) > (textAsBlob(''), textAsBlob('1'));"), + row(bytes("foo123"), bytes("1"), bytes("1"), bytes("1")), + row(bytes("foo123"), bytes("1"), bytes("2"), bytes("2"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c1, c2) >= (textAsBlob(''), textAsBlob('1'));"), + row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1"), bytes("4")), + row(bytes("foo123"), bytes("1"), bytes("1"), bytes("1")), + row(bytes("foo123"), bytes("1"), bytes("2"), bytes("2"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c1, c2) <= (textAsBlob(''), textAsBlob('1'));"), + row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1"), bytes("4"))); + + assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c1, c2) < (textAsBlob(''), textAsBlob('1'));")); + } + } + } + + @Test + public void testEmptyRestrictionValueWithOrderBy() throws Throwable + { + for (String options : new String[] { "", + " WITH COMPACT STORAGE", + " WITH CLUSTERING ORDER BY (c DESC)", + " WITH COMPACT STORAGE AND CLUSTERING ORDER BY (c DESC)"}) + { + String orderingClause = options.contains("ORDER") ? "" : "ORDER BY c DESC" ; + + createTable("CREATE TABLE %s (pk blob, c blob, v blob, PRIMARY KEY ((pk), c))" + options); + execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?)", + bytes("foo123"), + bytes("1"), + bytes("1")); + execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?)", + bytes("foo123"), + bytes("2"), + bytes("2")); + + for (boolean flush : new boolean[]{false, true}) + { + if (flush) + flush(); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c > textAsBlob('')" + orderingClause), + row(bytes("foo123"), bytes("2"), bytes("2")), + row(bytes("foo123"), bytes("1"), bytes("1"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c >= textAsBlob('')" + orderingClause), + row(bytes("foo123"), bytes("2"), bytes("2")), + row(bytes("foo123"), bytes("1"), bytes("1"))); + + assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c < textAsBlob('')" + orderingClause)); + + assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c <= textAsBlob('')" + orderingClause)); + + } + + if (options.contains("COMPACT")) + { + assertInvalidMessage("Missing PRIMARY KEY part c", + "INSERT INTO %s (pk, c, v) VALUES (?, ?, ?)", + bytes("foo123"), + EMPTY_BYTE_BUFFER, + bytes("4")); + } + else + { + execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?)", + bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("4")); + + for (boolean flush : new boolean[]{false, true}) + { + if (flush) + flush(); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c IN (textAsBlob(''), textAsBlob('1'))" + orderingClause), + row(bytes("foo123"), bytes("1"), bytes("1")), + row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("4"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c > textAsBlob('')" + orderingClause), + row(bytes("foo123"), bytes("2"), bytes("2")), + row(bytes("foo123"), bytes("1"), bytes("1"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c >= textAsBlob('')" + orderingClause), + row(bytes("foo123"), bytes("2"), bytes("2")), + row(bytes("foo123"), bytes("1"), bytes("1")), + row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("4"))); + + assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c < textAsBlob('')" + orderingClause)); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c <= textAsBlob('')" + orderingClause), + row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("4"))); + } + } + } + } + + @Test + public void testEmptyRestrictionValueWithMultipleClusteringColumnsAndOrderBy() throws Throwable + { + for (String options : new String[] { "", + " WITH COMPACT STORAGE", + " WITH CLUSTERING ORDER BY (c1 DESC, c2 DESC)", + " WITH COMPACT STORAGE AND CLUSTERING ORDER BY (c1 DESC, c2 DESC)"}) + { + String orderingClause = options.contains("ORDER") ? "" : "ORDER BY c1 DESC, c2 DESC" ; + + createTable("CREATE TABLE %s (pk blob, c1 blob, c2 blob, v blob, PRIMARY KEY (pk, c1, c2))" + options); + execute("INSERT INTO %s (pk, c1, c2, v) VALUES (?, ?, ?, ?)", bytes("foo123"), bytes("1"), bytes("1"), bytes("1")); + execute("INSERT INTO %s (pk, c1, c2, v) VALUES (?, ?, ?, ?)", bytes("foo123"), bytes("1"), bytes("2"), bytes("2")); + + for (boolean flush : new boolean[]{false, true}) + { + if (flush) + flush(); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c1 > textAsBlob('')" + orderingClause), + row(bytes("foo123"), bytes("1"), bytes("2"), bytes("2")), + row(bytes("foo123"), bytes("1"), bytes("1"), bytes("1"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c1 = textAsBlob('1') AND c2 > textAsBlob('')" + orderingClause), + row(bytes("foo123"), bytes("1"), bytes("2"), bytes("2")), + row(bytes("foo123"), bytes("1"), bytes("1"), bytes("1"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c1, c2) > (textAsBlob(''), textAsBlob('1'))" + orderingClause), + row(bytes("foo123"), bytes("1"), bytes("2"), bytes("2")), + row(bytes("foo123"), bytes("1"), bytes("1"), bytes("1"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c1 = textAsBlob('1') AND c2 >= textAsBlob('')" + orderingClause), + row(bytes("foo123"), bytes("1"), bytes("2"), bytes("2")), + row(bytes("foo123"), bytes("1"), bytes("1"), bytes("1"))); + } + + execute("INSERT INTO %s (pk, c1, c2, v) VALUES (?, ?, ?, ?)", + bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1"), bytes("4")); + + for (boolean flush : new boolean[]{false, true}) + { + if (flush) + flush(); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c1 = textAsBlob('1') AND c2 IN (textAsBlob(''), textAsBlob('1'))" + orderingClause), + row(bytes("foo123"), bytes("1"), bytes("1"), bytes("1"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c1 = textAsBlob('') AND c2 IN (textAsBlob(''), textAsBlob('1'))" + orderingClause), + row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1"), bytes("4"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c1, c2) IN ((textAsBlob(''), textAsBlob('1')), (textAsBlob('1'), textAsBlob('1')))" + orderingClause), + row(bytes("foo123"), bytes("1"), bytes("1"), bytes("1")), + row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1"), bytes("4"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c1, c2) > (textAsBlob(''), textAsBlob('1'))" + orderingClause), + row(bytes("foo123"), bytes("1"), bytes("2"), bytes("2")), + row(bytes("foo123"), bytes("1"), bytes("1"), bytes("1"))); + + assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND (c1, c2) >= (textAsBlob(''), textAsBlob('1'))" + orderingClause), + row(bytes("foo123"), bytes("1"), bytes("2"), bytes("2")), + row(bytes("foo123"), bytes("1"), bytes("1"), bytes("1")), + row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1"), bytes("4"))); + } + } + } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/527d1897/test/unit/org/apache/cassandra/db/marshal/ReversedTypeTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/db/marshal/ReversedTypeTest.java b/test/unit/org/apache/cassandra/db/marshal/ReversedTypeTest.java index 1553a20..7dfbc27 100644 --- a/test/unit/org/apache/cassandra/db/marshal/ReversedTypeTest.java +++ b/test/unit/org/apache/cassandra/db/marshal/ReversedTypeTest.java @@ -34,7 +34,7 @@ public class ReversedTypeTest assert t.compare(bytes(4L), bytes(2L)) < 0; // the empty byte buffer is always the smaller - assert t.compare(EMPTY_BYTE_BUFFER, bytes(2L)) < 0; - assert t.compare(bytes(2L), EMPTY_BYTE_BUFFER) > 0; + assert t.compare(EMPTY_BYTE_BUFFER, bytes(2L)) > 0; + assert t.compare(bytes(2L), EMPTY_BYTE_BUFFER) < 0; } }