You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by bl...@apache.org on 2016/08/16 13:53:01 UTC

[01/12] cassandra git commit: Fix queries with empty ByteBuffer values in clustering column restrictions

Repository: cassandra
Updated Branches:
  refs/heads/trunk e22170e99 -> d1bd71c4e


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/trunk
Commit: 527d1897c0973491bb0db098ce5ea1e0bd78898f
Parents: bd66547
Author: Benjamin Lerer <b....@gmail.com>
Authored: Tue Aug 16 15:17:59 2016 +0200
Committer: Benjamin Lerer <b....@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;
     }
 }


[08/12] cassandra git commit: Merge branch cassandra-2.2 into cassandra-3.0

Posted by bl...@apache.org.
http://git-wip-us.apache.org/repos/asf/cassandra/blob/bb56193a/test/unit/org/apache/cassandra/cql3/validation/operations/SelectOrderByTest.java
----------------------------------------------------------------------
diff --cc test/unit/org/apache/cassandra/cql3/validation/operations/SelectOrderByTest.java
index 6e06419,32d800a..bb35458
--- a/test/unit/org/apache/cassandra/cql3/validation/operations/SelectOrderByTest.java
+++ b/test/unit/org/apache/cassandra/cql3/validation/operations/SelectOrderByTest.java
@@@ -36,31 -36,37 +36,33 @@@ public class SelectOrderByTest extends 
              execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, 1, 1);
              execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, 2, 2);
  
-             assertRows(execute("SELECT * FROM %s WHERE a=? ORDER BY b ASC", 0),
-                     row(0, 0, 0),
-                     row(0, 1, 1),
-                     row(0, 2, 2)
-             );
- 
-             assertRows(execute("SELECT * FROM %s WHERE a=? ORDER BY b DESC", 0),
-                     row(0, 2, 2),
-                     row(0, 1, 1),
-                     row(0, 0, 0)
-             );
- 
-             // order by the only column in the selection
-             assertRows(execute("SELECT b FROM %s WHERE a=? ORDER BY b ASC", 0),
-                     row(0), row(1), row(2));
- 
-             assertRows(execute("SELECT b FROM %s WHERE a=? ORDER BY b DESC", 0),
-                     row(2), row(1), row(0));
- 
-             // order by a column not in the selection
-             assertRows(execute("SELECT c FROM %s WHERE a=? ORDER BY b ASC", 0),
-                     row(0), row(1), row(2));
 -            for (boolean flush : new boolean[]{true, false})
 -            {
 -                if (flush)
 -                    flush();
--
-             assertRows(execute("SELECT c FROM %s WHERE a=? ORDER BY b DESC", 0),
-                     row(2), row(1), row(0));
++            beforeAndAfterFlush(() -> {
+                 assertRows(execute("SELECT * FROM %s WHERE a=? ORDER BY b ASC", 0),
+                            row(0, 0, 0),
+                            row(0, 1, 1),
+                            row(0, 2, 2)
+                         );
+ 
+                 assertRows(execute("SELECT * FROM %s WHERE a=? ORDER BY b DESC", 0),
+                            row(0, 2, 2),
+                            row(0, 1, 1),
+                            row(0, 0, 0)
+                         );
+ 
+                 // order by the only column in the selection
+                 assertRows(execute("SELECT b FROM %s WHERE a=? ORDER BY b ASC", 0),
+                            row(0), row(1), row(2));
+ 
+                 assertRows(execute("SELECT b FROM %s WHERE a=? ORDER BY b DESC", 0),
+                            row(2), row(1), row(0));
+ 
+                 // order by a column not in the selection
+                 assertRows(execute("SELECT c FROM %s WHERE a=? ORDER BY b ASC", 0),
+                            row(0), row(1), row(2));
+ 
+                 assertRows(execute("SELECT c FROM %s WHERE a=? ORDER BY b DESC", 0),
+                            row(2), row(1), row(0));
 -            }
++            });
          }
      }
  
@@@ -74,22 -80,28 +76,24 @@@
              execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, 1, 1);
              execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, 2, 2);
  
-             // order by the only column in the selection
-             assertRows(execute("SELECT blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY b ASC", 0),
-                     row(0), row(1), row(2));
 -            for (boolean flush : new boolean[]{true, false})
 -            {
 -                if (flush)
 -                    flush();
 -
++            beforeAndAfterFlush(() -> {
+                 // order by the only column in the selection
+                 assertRows(execute("SELECT blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY b ASC", 0),
+                            row(0), row(1), row(2));
  
-             assertRows(execute("SELECT blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY b DESC", 0),
-                     row(2), row(1), row(0));
+                 assertRows(execute("SELECT blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY b DESC", 0),
+                            row(2), row(1), row(0));
  
-             // order by a column not in the selection
-             assertRows(execute("SELECT blobAsInt(intAsBlob(c)) FROM %s WHERE a=? ORDER BY b ASC", 0),
-                     row(0), row(1), row(2));
+                 // order by a column not in the selection
+                 assertRows(execute("SELECT blobAsInt(intAsBlob(c)) FROM %s WHERE a=? ORDER BY b ASC", 0),
+                            row(0), row(1), row(2));
  
-             assertRows(execute("SELECT blobAsInt(intAsBlob(c)) FROM %s WHERE a=? ORDER BY b DESC", 0),
-                     row(2), row(1), row(0));
+                 assertRows(execute("SELECT blobAsInt(intAsBlob(c)) FROM %s WHERE a=? ORDER BY b DESC", 0),
+                            row(2), row(1), row(0));
  
-             assertInvalid("SELECT * FROM %s WHERE a=? ORDER BY c ASC", 0);
-             assertInvalid("SELECT * FROM %s WHERE a=? ORDER BY c DESC", 0);
+                 assertInvalid("SELECT * FROM %s WHERE a=? ORDER BY c ASC", 0);
+                 assertInvalid("SELECT * FROM %s WHERE a=? ORDER BY c DESC", 0);
 -            }
++            });
          }
      }
  
@@@ -105,15 -117,20 +109,17 @@@
              execute("INSERT INTO %s (a, b, c) VALUES (?, ?, {a: ?})", 0, 1, 1);
              execute("INSERT INTO %s (a, b, c) VALUES (?, ?, {a: ?})", 0, 2, 2);
  
-             // order by a column not in the selection
-             assertRows(execute("SELECT c.a FROM %s WHERE a=? ORDER BY b ASC", 0),
-                     row(0), row(1), row(2));
- 
-             assertRows(execute("SELECT c.a FROM %s WHERE a=? ORDER BY b DESC", 0),
-                     row(2), row(1), row(0));
- 
-             assertRows(execute("SELECT blobAsInt(intAsBlob(c.a)) FROM %s WHERE a=? ORDER BY b DESC", 0),
-                        row(2), row(1), row(0));
 -            for (boolean flush : new boolean[]{true, false})
 -            {
 -                if (flush)
 -                    flush();
++            beforeAndAfterFlush(() -> {
+                 // order by a column not in the selection
+                 assertRows(execute("SELECT c.a FROM %s WHERE a=? ORDER BY b ASC", 0),
 -                           row(0), row(1), row(2));
 -
++                        row(0), row(1), row(2));
++    
+                 assertRows(execute("SELECT c.a FROM %s WHERE a=? ORDER BY b DESC", 0),
 -                           row(2), row(1), row(0));
 -
++                        row(2), row(1), row(0));
++    
+                 assertRows(execute("SELECT blobAsInt(intAsBlob(c.a)) FROM %s WHERE a=? ORDER BY b DESC", 0),
+                            row(2), row(1), row(0));
 -            }
++            });
              dropTable("DROP TABLE %s");
          }
      }
@@@ -129,62 -146,68 +135,64 @@@
          execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, 1, 1, 4);
          execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, 1, 2, 5);
  
-         assertRows(execute("SELECT * FROM %s WHERE a=? ORDER BY b ASC", 0),
-                 row(0, 0, 0, 0),
-                 row(0, 0, 1, 1),
-                 row(0, 0, 2, 2),
-                 row(0, 1, 0, 3),
-                 row(0, 1, 1, 4),
-                 row(0, 1, 2, 5)
-         );
- 
-         assertRows(execute("SELECT * FROM %s WHERE a=? ORDER BY b DESC", 0),
-                 row(0, 1, 2, 5),
-                 row(0, 1, 1, 4),
-                 row(0, 1, 0, 3),
-                 row(0, 0, 2, 2),
-                 row(0, 0, 1, 1),
-                 row(0, 0, 0, 0)
-         );
- 
-         assertRows(execute("SELECT * FROM %s WHERE a=? ORDER BY b DESC, c DESC", 0),
-                 row(0, 1, 2, 5),
-                 row(0, 1, 1, 4),
-                 row(0, 1, 0, 3),
-                 row(0, 0, 2, 2),
-                 row(0, 0, 1, 1),
-                 row(0, 0, 0, 0)
-         );
- 
-         assertInvalid("SELECT * FROM %s WHERE a=? ORDER BY c ASC", 0);
-         assertInvalid("SELECT * FROM %s WHERE a=? ORDER BY c DESC", 0);
-         assertInvalid("SELECT * FROM %s WHERE a=? ORDER BY b ASC, c DESC", 0);
-         assertInvalid("SELECT * FROM %s WHERE a=? ORDER BY b DESC, c ASC", 0);
-         assertInvalid("SELECT * FROM %s WHERE a=? ORDER BY d ASC", 0);
- 
-         // select and order by b
-         assertRows(execute("SELECT b FROM %s WHERE a=? ORDER BY b ASC", 0),
-                 row(0), row(0), row(0), row(1), row(1), row(1));
-         assertRows(execute("SELECT b FROM %s WHERE a=? ORDER BY b DESC", 0),
-                 row(1), row(1), row(1), row(0), row(0), row(0));
- 
-         // select c, order by b
-         assertRows(execute("SELECT c FROM %s WHERE a=? ORDER BY b ASC", 0),
-                 row(0), row(1), row(2), row(0), row(1), row(2));
-         assertRows(execute("SELECT c FROM %s WHERE a=? ORDER BY b DESC", 0),
-                 row(2), row(1), row(0), row(2), row(1), row(0));
- 
-         // select c, order by b, c
-         assertRows(execute("SELECT c FROM %s WHERE a=? ORDER BY b ASC, c ASC", 0),
-                 row(0), row(1), row(2), row(0), row(1), row(2));
-         assertRows(execute("SELECT c FROM %s WHERE a=? ORDER BY b DESC, c DESC", 0),
-                 row(2), row(1), row(0), row(2), row(1), row(0));
 -        for (boolean flush : new boolean[]{true, false})
 -        {
 -            if (flush)
 -                flush();
--
-         // select d, order by b, c
-         assertRows(execute("SELECT d FROM %s WHERE a=? ORDER BY b ASC, c ASC", 0),
-                 row(0), row(1), row(2), row(3), row(4), row(5));
-         assertRows(execute("SELECT d FROM %s WHERE a=? ORDER BY b DESC, c DESC", 0),
-                 row(5), row(4), row(3), row(2), row(1), row(0));
++        beforeAndAfterFlush(() -> {
+             assertRows(execute("SELECT * FROM %s WHERE a=? ORDER BY b ASC", 0),
+                        row(0, 0, 0, 0),
+                        row(0, 0, 1, 1),
+                        row(0, 0, 2, 2),
+                        row(0, 1, 0, 3),
+                        row(0, 1, 1, 4),
+                        row(0, 1, 2, 5)
+                     );
+ 
+             assertRows(execute("SELECT * FROM %s WHERE a=? ORDER BY b DESC", 0),
+                        row(0, 1, 2, 5),
+                        row(0, 1, 1, 4),
+                        row(0, 1, 0, 3),
+                        row(0, 0, 2, 2),
+                        row(0, 0, 1, 1),
+                        row(0, 0, 0, 0)
+                     );
+ 
+             assertRows(execute("SELECT * FROM %s WHERE a=? ORDER BY b DESC, c DESC", 0),
+                        row(0, 1, 2, 5),
+                        row(0, 1, 1, 4),
+                        row(0, 1, 0, 3),
+                        row(0, 0, 2, 2),
+                        row(0, 0, 1, 1),
+                        row(0, 0, 0, 0)
+                     );
+ 
+             assertInvalid("SELECT * FROM %s WHERE a=? ORDER BY c ASC", 0);
+             assertInvalid("SELECT * FROM %s WHERE a=? ORDER BY c DESC", 0);
+             assertInvalid("SELECT * FROM %s WHERE a=? ORDER BY b ASC, c DESC", 0);
+             assertInvalid("SELECT * FROM %s WHERE a=? ORDER BY b DESC, c ASC", 0);
+             assertInvalid("SELECT * FROM %s WHERE a=? ORDER BY d ASC", 0);
+ 
+             // select and order by b
+             assertRows(execute("SELECT b FROM %s WHERE a=? ORDER BY b ASC", 0),
+                        row(0), row(0), row(0), row(1), row(1), row(1));
+             assertRows(execute("SELECT b FROM %s WHERE a=? ORDER BY b DESC", 0),
+                        row(1), row(1), row(1), row(0), row(0), row(0));
+ 
+             // select c, order by b
+             assertRows(execute("SELECT c FROM %s WHERE a=? ORDER BY b ASC", 0),
+                        row(0), row(1), row(2), row(0), row(1), row(2));
+             assertRows(execute("SELECT c FROM %s WHERE a=? ORDER BY b DESC", 0),
+                        row(2), row(1), row(0), row(2), row(1), row(0));
+ 
+             // select c, order by b, c
+             assertRows(execute("SELECT c FROM %s WHERE a=? ORDER BY b ASC, c ASC", 0),
+                        row(0), row(1), row(2), row(0), row(1), row(2));
+             assertRows(execute("SELECT c FROM %s WHERE a=? ORDER BY b DESC, c DESC", 0),
+                        row(2), row(1), row(0), row(2), row(1), row(0));
+ 
+             // select d, order by b, c
+             assertRows(execute("SELECT d FROM %s WHERE a=? ORDER BY b ASC, c ASC", 0),
+                        row(0), row(1), row(2), row(3), row(4), row(5));
+             assertRows(execute("SELECT d FROM %s WHERE a=? ORDER BY b DESC, c DESC", 0),
+                        row(5), row(4), row(3), row(2), row(1), row(0));
 -        }
++        });
      }
  
      @Test
@@@ -198,41 -221,46 +206,42 @@@
          execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, 1, 1, 4);
          execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, 1, 2, 5);
  
-         assertInvalid("SELECT blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY c ASC", 0);
-         assertInvalid("SELECT blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY c DESC", 0);
-         assertInvalid("SELECT blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY b ASC, c DESC", 0);
-         assertInvalid("SELECT blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY b DESC, c ASC", 0);
-         assertInvalid("SELECT blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY d ASC", 0);
- 
-         // select and order by b
-         assertRows(execute("SELECT blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY b ASC", 0),
-                    row(0), row(0), row(0), row(1), row(1), row(1));
-         assertRows(execute("SELECT blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY b DESC", 0),
-                    row(1), row(1), row(1), row(0), row(0), row(0));
- 
-         assertRows(execute("SELECT b, blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY b ASC", 0),
-                 row(0, 0), row(0, 0), row(0, 0), row(1, 1), row(1, 1), row(1, 1));
-         assertRows(execute("SELECT b, blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY b DESC", 0),
-                 row(1, 1), row(1, 1), row(1, 1), row(0, 0), row(0, 0), row(0, 0));
- 
-         // select c, order by b
-         assertRows(execute("SELECT blobAsInt(intAsBlob(c)) FROM %s WHERE a=? ORDER BY b ASC", 0),
-                 row(0), row(1), row(2), row(0), row(1), row(2));
-         assertRows(execute("SELECT blobAsInt(intAsBlob(c)) FROM %s WHERE a=? ORDER BY b DESC", 0),
-                 row(2), row(1), row(0), row(2), row(1), row(0));
- 
-         // select c, order by b, c
-         assertRows(execute("SELECT blobAsInt(intAsBlob(c)) FROM %s WHERE a=? ORDER BY b ASC, c ASC", 0),
-                 row(0), row(1), row(2), row(0), row(1), row(2));
-         assertRows(execute("SELECT blobAsInt(intAsBlob(c)) FROM %s WHERE a=? ORDER BY b DESC, c DESC", 0),
-                 row(2), row(1), row(0), row(2), row(1), row(0));
 -        for (boolean flush : new boolean[]{true, false})
 -        {
 -            if (flush)
 -                flush();
--
-         // select d, order by b, c
-         assertRows(execute("SELECT blobAsInt(intAsBlob(d)) FROM %s WHERE a=? ORDER BY b ASC, c ASC", 0),
-                 row(0), row(1), row(2), row(3), row(4), row(5));
-         assertRows(execute("SELECT blobAsInt(intAsBlob(d)) FROM %s WHERE a=? ORDER BY b DESC, c DESC", 0),
-                 row(5), row(4), row(3), row(2), row(1), row(0));
++        beforeAndAfterFlush(() -> {
+             assertInvalid("SELECT blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY c ASC", 0);
+             assertInvalid("SELECT blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY c DESC", 0);
+             assertInvalid("SELECT blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY b ASC, c DESC", 0);
+             assertInvalid("SELECT blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY b DESC, c ASC", 0);
+             assertInvalid("SELECT blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY d ASC", 0);
  
+             // select and order by b
+             assertRows(execute("SELECT blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY b ASC", 0),
+                        row(0), row(0), row(0), row(1), row(1), row(1));
+             assertRows(execute("SELECT blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY b DESC", 0),
+                        row(1), row(1), row(1), row(0), row(0), row(0));
+ 
+             assertRows(execute("SELECT b, blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY b ASC", 0),
+                        row(0, 0), row(0, 0), row(0, 0), row(1, 1), row(1, 1), row(1, 1));
+             assertRows(execute("SELECT b, blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY b DESC", 0),
+                        row(1, 1), row(1, 1), row(1, 1), row(0, 0), row(0, 0), row(0, 0));
+ 
+             // select c, order by b
+             assertRows(execute("SELECT blobAsInt(intAsBlob(c)) FROM %s WHERE a=? ORDER BY b ASC", 0),
+                        row(0), row(1), row(2), row(0), row(1), row(2));
+             assertRows(execute("SELECT blobAsInt(intAsBlob(c)) FROM %s WHERE a=? ORDER BY b DESC", 0),
+                        row(2), row(1), row(0), row(2), row(1), row(0));
+ 
+             // select c, order by b, c
+             assertRows(execute("SELECT blobAsInt(intAsBlob(c)) FROM %s WHERE a=? ORDER BY b ASC, c ASC", 0),
+                        row(0), row(1), row(2), row(0), row(1), row(2));
+             assertRows(execute("SELECT blobAsInt(intAsBlob(c)) FROM %s WHERE a=? ORDER BY b DESC, c DESC", 0),
+                        row(2), row(1), row(0), row(2), row(1), row(0));
+ 
+             // select d, order by b, c
+             assertRows(execute("SELECT blobAsInt(intAsBlob(d)) FROM %s WHERE a=? ORDER BY b ASC, c ASC", 0),
+                        row(0), row(1), row(2), row(3), row(4), row(5));
+             assertRows(execute("SELECT blobAsInt(intAsBlob(d)) FROM %s WHERE a=? ORDER BY b DESC, c DESC", 0),
+                        row(5), row(4), row(3), row(2), row(1), row(0));
 -        }
++        });
      }
  
      /**
@@@ -247,8 -275,14 +256,10 @@@
          for (int i = 0; i < 10; i++)
              execute("INSERT INTO %s (k, c, v) VALUES (0, ?, ?)", i, i);
  
-         assertRows(execute("SELECT v FROM %s WHERE k = 0 ORDER BY c DESC"),
-                    row(9), row(8), row(7), row(6), row(5), row(4), row(3), row(2), row(1), row(0));
 -        for (boolean flush : new boolean[]{true, false})
 -        {
 -            if (flush)
 -                flush();
 -
++        beforeAndAfterFlush(() -> {
+             assertRows(execute("SELECT v FROM %s WHERE k = 0 ORDER BY c DESC"),
+                        row(9), row(8), row(7), row(6), row(5), row(4), row(3), row(2), row(1), row(0));
 -        }
++        });
  
          createTable("CREATE TABLE %s (k int, c1 int, c2 int, v int, PRIMARY KEY (k, c1, c2)) WITH COMPACT STORAGE");
  
@@@ -256,15 -290,21 +267,17 @@@
              for (int j = 0; j < 2; j++)
                  execute("INSERT INTO %s (k, c1, c2, v) VALUES (0, ?, ?, ?)", i, j, i * 2 + j);
  
-         assertInvalid("SELECT v FROM %s WHERE k = 0 ORDER BY c DESC");
-         assertInvalid("SELECT v FROM %s WHERE k = 0 ORDER BY c2 DESC");
-         assertInvalid("SELECT v FROM %s WHERE k = 0 ORDER BY k DESC");
 -        for (boolean flush : new boolean[]{true, false})
 -        {
 -            if (flush)
 -                flush();
 -
++        beforeAndAfterFlush(() -> {
+             assertInvalid("SELECT v FROM %s WHERE k = 0 ORDER BY c DESC");
+             assertInvalid("SELECT v FROM %s WHERE k = 0 ORDER BY c2 DESC");
+             assertInvalid("SELECT v FROM %s WHERE k = 0 ORDER BY k DESC");
  
-         assertRows(execute("SELECT v FROM %s WHERE k = 0 ORDER BY c1 DESC"),
-                    row(7), row(6), row(5), row(4), row(3), row(2), row(1), row(0));
+             assertRows(execute("SELECT v FROM %s WHERE k = 0 ORDER BY c1 DESC"),
+                        row(7), row(6), row(5), row(4), row(3), row(2), row(1), row(0));
  
-         assertRows(execute("SELECT v FROM %s WHERE k = 0 ORDER BY c1"),
-                    row(0), row(1), row(2), row(3), row(4), row(5), row(6), row(7));
+             assertRows(execute("SELECT v FROM %s WHERE k = 0 ORDER BY c1"),
+                        row(0), row(1), row(2), row(3), row(4), row(5), row(6), row(7));
 -        }
++        });
      }
  
      /**
@@@ -281,23 -321,29 +294,25 @@@
          execute("INSERT INTO %s (row, number, string) VALUES ('row', 3, 'three')");
          execute("INSERT INTO %s (row, number, string) VALUES ('row', 4, 'four')");
  
-         assertRows(execute("SELECT number FROM %s WHERE row='row' AND number < 3 ORDER BY number ASC"),
-                    row(1), row(2));
 -        for (boolean flush : new boolean[]{true, false})
 -        {
 -            if (flush)
 -                flush();
 -
++        beforeAndAfterFlush(() -> {
+             assertRows(execute("SELECT number FROM %s WHERE row='row' AND number < 3 ORDER BY number ASC"),
+                        row(1), row(2));
  
-         assertRows(execute("SELECT number FROM %s WHERE row='row' AND number >= 3 ORDER BY number ASC"),
-                    row(3), row(4));
+             assertRows(execute("SELECT number FROM %s WHERE row='row' AND number >= 3 ORDER BY number ASC"),
+                        row(3), row(4));
  
-         assertRows(execute("SELECT number FROM %s WHERE row='row' AND number < 3 ORDER BY number DESC"),
-                    row(2), row(1));
+             assertRows(execute("SELECT number FROM %s WHERE row='row' AND number < 3 ORDER BY number DESC"),
+                        row(2), row(1));
  
-         assertRows(execute("SELECT number FROM %s WHERE row='row' AND number >= 3 ORDER BY number DESC"),
-                    row(4), row(3));
+             assertRows(execute("SELECT number FROM %s WHERE row='row' AND number >= 3 ORDER BY number DESC"),
+                        row(4), row(3));
  
-         assertRows(execute("SELECT number FROM %s WHERE row='row' AND number > 3 ORDER BY number DESC"),
-                    row(4));
+             assertRows(execute("SELECT number FROM %s WHERE row='row' AND number > 3 ORDER BY number DESC"),
+                        row(4));
  
-         assertRows(execute("SELECT number FROM %s WHERE row='row' AND number <= 3 ORDER BY number DESC"),
-                    row(3), row(2), row(1));
+             assertRows(execute("SELECT number FROM %s WHERE row='row' AND number <= 3 ORDER BY number DESC"),
+                        row(3), row(2), row(1));
 -        }
++        });
      }
  
      /**
@@@ -330,20 -376,20 +345,22 @@@
          execute("INSERT INTO %s (my_id, col1, value) VALUES ( 'key3', 2, 'b')");
          execute("INSERT INTO %s (my_id, col1, value) VALUES ( 'key4', 4, 'd')");
  
-         assertRows(execute("SELECT col1 FROM %s WHERE my_id in('key1', 'key2', 'key3') ORDER BY col1"),
-                    row(1), row(2), row(3));
 -        for (boolean flush : new boolean[]{true, false})
 -        {
 -            if (flush)
 -                flush();
 -
++        beforeAndAfterFlush(() -> {
+             assertRows(execute("SELECT col1 FROM %s WHERE my_id in('key1', 'key2', 'key3') ORDER BY col1"),
+                        row(1), row(2), row(3));
  
-         assertRows(execute("SELECT col1 FROM %s WHERE my_id in('key1', 'key2', 'key3') ORDER BY col1 LIMIT 2"),
-                    row(1), row(2));
++            assertRows(execute("SELECT col1 FROM %s WHERE my_id in('key1', 'key2', 'key3') ORDER BY col1 LIMIT 2"),
++                       row(1), row(2));
 +
-         assertRows(execute("SELECT col1 FROM %s WHERE my_id in('key1', 'key2', 'key3') ORDER BY col1 LIMIT 10"),
-                    row(1), row(2), row(3));
++            assertRows(execute("SELECT col1 FROM %s WHERE my_id in('key1', 'key2', 'key3') ORDER BY col1 LIMIT 10"),
++                       row(1), row(2), row(3));
 +
-         assertRows(execute("SELECT col1, my_id FROM %s WHERE my_id in('key1', 'key2', 'key3') ORDER BY col1"),
-                    row(1, "key1"), row(2, "key3"), row(3, "key2"));
+             assertRows(execute("SELECT col1, my_id FROM %s WHERE my_id in('key1', 'key2', 'key3') ORDER BY col1"),
+                        row(1, "key1"), row(2, "key3"), row(3, "key2"));
  
-         assertRows(execute("SELECT my_id, col1 FROM %s WHERE my_id in('key1', 'key2', 'key3') ORDER BY col1"),
-                    row("key1", 1), row("key3", 2), row("key2", 3));
+             assertRows(execute("SELECT my_id, col1 FROM %s WHERE my_id in('key1', 'key2', 'key3') ORDER BY col1"),
+                        row("key1", 1), row("key3", 2), row("key2", 3));
 -        }
++        });
  
          createTable("CREATE TABLE %s (pk1 int, pk2 int, c int, v text, PRIMARY KEY ((pk1, pk2), c) )");
          execute("INSERT INTO %s (pk1, pk2, c, v) VALUES (?, ?, ?, ?)", 1, 1, 2, "A");
@@@ -351,34 -397,31 +368,36 @@@
          execute("INSERT INTO %s (pk1, pk2, c, v) VALUES (?, ?, ?, ?)", 1, 3, 3, "C");
          execute("INSERT INTO %s (pk1, pk2, c, v) VALUES (?, ?, ?, ?)", 1, 1, 4, "D");
  
 -        for (boolean flush : new boolean[]{true, false})
 -        {
 -            if (flush)
 -                flush();
 -
 -            assertRows(execute("SELECT v, ttl(v), c FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c; ", 1, 1, 2),
 -                       row("B", null, 1),
 -                       row("A", null, 2),
 -                       row("D", null, 4));
 -
 -            assertRows(execute("SELECT v, ttl(v), c as name_1 FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c; ", 1, 1, 2),
 -                       row("B", null, 1),
 -                       row("A", null, 2),
 -                       row("D", null, 4));
 -
 -            assertRows(execute("SELECT v FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c; ", 1, 1, 2),
 -                       row("B"),
 -                       row("A"),
 -                       row("D"));
 -
 -            assertRows(execute("SELECT v as c FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c; ", 1, 1, 2),
 -                       row("B"),
 -                       row("A"),
 -                       row("D"));
 -        }
++        beforeAndAfterFlush(() -> {
 +        assertRows(execute("SELECT v, ttl(v), c FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c; ", 1, 1, 2),
 +                   row("B", null, 1),
 +                   row("A", null, 2),
 +                   row("D", null, 4));
 +
 +        assertRows(execute("SELECT v, ttl(v), c as name_1 FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c; ", 1, 1, 2),
 +                   row("B", null, 1),
 +                   row("A", null, 2),
 +                   row("D", null, 4));
 +
 +        assertRows(execute("SELECT v FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c; ", 1, 1, 2),
 +                   row("B"),
 +                   row("A"),
 +                   row("D"));
 +
 +        assertRows(execute("SELECT v FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c LIMIT 2; ", 1, 1, 2),
 +                   row("B"),
 +                   row("A"));
 +
 +        assertRows(execute("SELECT v FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c LIMIT 10; ", 1, 1, 2),
 +                   row("B"),
 +                   row("A"),
 +                   row("D"));
 +
 +        assertRows(execute("SELECT v as c FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c; ", 1, 1, 2),
 +                   row("B"),
 +                   row("A"),
 +                   row("D"));
++        });
  
          createTable("CREATE TABLE %s (pk1 int, pk2 int, c1 int, c2 int, v text, PRIMARY KEY ((pk1, pk2), c1, c2) )");
          execute("INSERT INTO %s (pk1, pk2, c1, c2, v) VALUES (?, ?, ?, ?, ?)", 1, 1, 4, 4, "A");
@@@ -386,51 -429,31 +405,53 @@@
          execute("INSERT INTO %s (pk1, pk2, c1, c2, v) VALUES (?, ?, ?, ?, ?)", 1, 3, 3, 3, "C");
          execute("INSERT INTO %s (pk1, pk2, c1, c2, v) VALUES (?, ?, ?, ?, ?)", 1, 1, 4, 1, "D");
  
-         assertRows(execute("SELECT v, ttl(v), c1, c2 FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c1, c2; ", 1, 1, 2),
-                    row("B", null, 1, 2),
-                    row("D", null, 4, 1),
-                    row("A", null, 4, 4));
- 
-         assertRows(execute("SELECT v, ttl(v), c1 as name_1, c2 as name_2 FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c1, c2; ", 1, 1, 2),
-                    row("B", null, 1, 2),
-                    row("D", null, 4, 1),
-                    row("A", null, 4, 4));
- 
-         assertRows(execute("SELECT v FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c1, c2; ", 1, 1, 2),
-                    row("B"),
-                    row("D"),
-                    row("A"));
- 
-         assertRows(execute("SELECT v as c2 FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c1, c2; ", 1, 1, 2),
-                    row("B"),
-                    row("D"),
-                    row("A"));
- 
-         assertRows(execute("SELECT v as c2 FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c1, c2 LIMIT 2; ", 1, 1, 2),
-                    row("B"),
-                    row("D"));
- 
-         assertRows(execute("SELECT v as c2 FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c1, c2 LIMIT 10; ", 1, 1, 2),
-                    row("B"),
-                    row("D"),
-                    row("A"));
- 
-         assertRows(execute("SELECT v as c2 FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c1 DESC , c2 DESC; ", 1, 1, 2),
-                    row("A"),
-                    row("D"),
-                    row("B"));
- 
-         assertRows(execute("SELECT v as c2 FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c1 DESC , c2 DESC LIMIT 2; ", 1, 1, 2),
-                    row("A"),
-                    row("D"));
- 
-         assertRows(execute("SELECT v as c2 FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c1 DESC , c2 DESC LIMIT 10; ", 1, 1, 2),
-                    row("A"),
-                    row("D"),
-                    row("B"));
 -        for (boolean flush : new boolean[]{true, false})
 -        {
 -            if (flush)
 -                flush();
--
-         assertInvalidMessage("LIMIT must be strictly positive",
-                              "SELECT v as c2 FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c1 DESC , c2 DESC LIMIT 0; ", 1, 1, 2);
++        beforeAndAfterFlush(() -> {
+             assertRows(execute("SELECT v, ttl(v), c1, c2 FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c1, c2; ", 1, 1, 2),
+                        row("B", null, 1, 2),
+                        row("D", null, 4, 1),
+                        row("A", null, 4, 4));
+ 
+             assertRows(execute("SELECT v, ttl(v), c1 as name_1, c2 as name_2 FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c1, c2; ", 1, 1, 2),
+                        row("B", null, 1, 2),
+                        row("D", null, 4, 1),
+                        row("A", null, 4, 4));
+ 
+             assertRows(execute("SELECT v FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c1, c2; ", 1, 1, 2),
+                        row("B"),
+                        row("D"),
+                        row("A"));
+ 
+             assertRows(execute("SELECT v as c2 FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c1, c2; ", 1, 1, 2),
+                        row("B"),
+                        row("D"),
+                        row("A"));
 -        }
++
++            assertRows(execute("SELECT v as c2 FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c1, c2 LIMIT 2; ", 1, 1, 2),
++                       row("B"),
++                       row("D"));
++
++            assertRows(execute("SELECT v as c2 FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c1, c2 LIMIT 10; ", 1, 1, 2),
++                       row("B"),
++                       row("D"),
++                       row("A"));
++
++            assertRows(execute("SELECT v as c2 FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c1 DESC , c2 DESC; ", 1, 1, 2),
++                       row("A"),
++                       row("D"),
++                       row("B"));
++
++            assertRows(execute("SELECT v as c2 FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c1 DESC , c2 DESC LIMIT 2; ", 1, 1, 2),
++                       row("A"),
++                       row("D"));
++
++            assertRows(execute("SELECT v as c2 FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c1 DESC , c2 DESC LIMIT 10; ", 1, 1, 2),
++                       row("A"),
++                       row("D"),
++                       row("B"));
++
++            assertInvalidMessage("LIMIT must be strictly positive",
++                                 "SELECT v as c2 FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c1 DESC , c2 DESC LIMIT 0; ", 1, 1, 2);
++        });
      }
  
      @Test
@@@ -447,33 -470,39 +468,35 @@@
          execute("UPDATE %s SET s = 2 WHERE a = 2");
          execute("UPDATE %s SET s = 3 WHERE a = 3");
  
-         assertRows(execute("SELECT a, b, c, d, s FROM %s WHERE a IN (1, 2, 3) ORDER BY b DESC"),
-                    row(2, 2, 2, 1, 2),
-                    row(2, 2, 1, 1, 2),
-                    row(1, 1, 2, 1, 1),
-                    row(1, 1, 1, 1, 1),
-                    row(3, null, null, null, 3));
- 
-         assertRows(execute("SELECT a, b, c, d, s FROM %s WHERE a IN (1, 2, 3) ORDER BY b ASC"),
-                    row(3, null, null, null, 3),
-                    row(1, 1, 1, 1, 1),
-                    row(1, 1, 2, 1, 1),
-                    row(2, 2, 1, 1, 2),
-                    row(2, 2, 2, 1, 2));
- 
-         assertRows(execute("SELECT a, b, c, d, s FROM %s WHERE a IN (1, 2, 3) ORDER BY b DESC , c DESC"),
-                    row(2, 2, 2, 1, 2),
-                    row(2, 2, 1, 1, 2),
-                    row(1, 1, 2, 1, 1),
-                    row(1, 1, 1, 1, 1),
-                    row(3, null, null, null, 3));
 -        for (boolean flush : new boolean[]{true, false})
 -        {
 -            if (flush)
 -                flush();
--
-         assertRows(execute("SELECT a, b, c, d, s FROM %s WHERE a IN (1, 2, 3) ORDER BY b ASC, c ASC"),
-                    row(3, null, null, null, 3),
-                    row(1, 1, 1, 1, 1),
-                    row(1, 1, 2, 1, 1),
-                    row(2, 2, 1, 1, 2),
-                    row(2, 2, 2, 1, 2));
++        beforeAndAfterFlush(() -> {
+             assertRows(execute("SELECT a, b, c, d, s FROM %s WHERE a IN (1, 2, 3) ORDER BY b DESC"),
+                        row(2, 2, 2, 1, 2),
+                        row(2, 2, 1, 1, 2),
+                        row(1, 1, 2, 1, 1),
+                        row(1, 1, 1, 1, 1),
+                        row(3, null, null, null, 3));
+ 
+             assertRows(execute("SELECT a, b, c, d, s FROM %s WHERE a IN (1, 2, 3) ORDER BY b ASC"),
+                        row(3, null, null, null, 3),
+                        row(1, 1, 1, 1, 1),
+                        row(1, 1, 2, 1, 1),
+                        row(2, 2, 1, 1, 2),
+                        row(2, 2, 2, 1, 2));
+ 
+             assertRows(execute("SELECT a, b, c, d, s FROM %s WHERE a IN (1, 2, 3) ORDER BY b DESC , c DESC"),
+                        row(2, 2, 2, 1, 2),
+                        row(2, 2, 1, 1, 2),
+                        row(1, 1, 2, 1, 1),
+                        row(1, 1, 1, 1, 1),
+                        row(3, null, null, null, 3));
+ 
+             assertRows(execute("SELECT a, b, c, d, s FROM %s WHERE a IN (1, 2, 3) ORDER BY b ASC, c ASC"),
+                        row(3, null, null, null, 3),
+                        row(1, 1, 1, 1, 1),
+                        row(1, 1, 2, 1, 1),
+                        row(2, 2, 1, 1, 2),
+                        row(2, 2, 2, 1, 2));
 -        }
++        });
      }
  
      /**
@@@ -488,13 -517,19 +511,15 @@@
          for(int i =0; i < 10; i++)
              execute("INSERT INTO %s (k, c, v) VALUES (0, ?, ?)", i, i);
  
-         assertRows(execute("SELECT c, v FROM %s WHERE k = 0 ORDER BY c ASC"),
-                    row(0, 0), row(1, 1), row(2, 2), row(3, 3), row(4, 4),
-                    row(5, 5), row(6, 6), row(7, 7), row(8, 8), row(9, 9));
 -        for (boolean flush : new boolean[]{true, false})
 -        {
 -            if (flush)
 -                flush();
 -
++        beforeAndAfterFlush(() -> {
+             assertRows(execute("SELECT c, v FROM %s WHERE k = 0 ORDER BY c ASC"),
+                        row(0, 0), row(1, 1), row(2, 2), row(3, 3), row(4, 4),
+                        row(5, 5), row(6, 6), row(7, 7), row(8, 8), row(9, 9));
  
-         assertRows(execute("SELECT c, v FROM %s WHERE k = 0 ORDER BY c DESC"),
-                    row(9, 9), row(8, 8), row(7, 7), row(6, 6), row(5, 5),
-                    row(4, 4), row(3, 3), row(2, 2), row(1, 1), row(0, 0));
+             assertRows(execute("SELECT c, v FROM %s WHERE k = 0 ORDER BY c DESC"),
+                        row(9, 9), row(8, 8), row(7, 7), row(6, 6), row(5, 5),
+                        row(4, 4), row(3, 3), row(2, 2), row(1, 1), row(0, 0));
 -        }
++        });
  
          createTable("CREATE TABLE %s (k int, c1 int, c2 int, v text, PRIMARY KEY (k, c1, c2)) WITH CLUSTERING ORDER BY (c1 ASC, c2 DESC)");
  
@@@ -502,28 -537,34 +527,30 @@@
              for(int j = 0; j < 10; j++)
                  execute("INSERT INTO %s (k, c1, c2, v) VALUES (0, ?, ?, ?)", i, j, String.format("%d%d", i, j));
  
-         assertInvalid("SELECT c1, c2, v FROM %s WHERE k = 0 ORDER BY c1 ASC, c2 ASC");
-         assertInvalid("SELECT c1, c2, v FROM %s WHERE k = 0 ORDER BY c1 DESC, c2 DESC");
 -        for (boolean flush : new boolean[]{true, false})
 -        {
 -            if (flush)
 -                flush();
 -
++        beforeAndAfterFlush(() -> {
+             assertInvalid("SELECT c1, c2, v FROM %s WHERE k = 0 ORDER BY c1 ASC, c2 ASC");
+             assertInvalid("SELECT c1, c2, v FROM %s WHERE k = 0 ORDER BY c1 DESC, c2 DESC");
  
-         Object[][] expectedRows = new Object[100][];
-         for(int i = 0; i < 10; i++)
-             for(int j = 9; j >= 0; j--)
-                 expectedRows[i * 10 + (9 - j)] = row(i, j, String.format("%d%d", i, j));
+             Object[][] expectedRows = new Object[100][];
+             for(int i = 0; i < 10; i++)
+                 for(int j = 9; j >= 0; j--)
+                     expectedRows[i * 10 + (9 - j)] = row(i, j, String.format("%d%d", i, j));
  
-         assertRows(execute("SELECT c1, c2, v FROM %s WHERE k = 0 ORDER BY c1 ASC"),
-                    expectedRows);
+             assertRows(execute("SELECT c1, c2, v FROM %s WHERE k = 0 ORDER BY c1 ASC"),
+                        expectedRows);
  
-         assertRows(execute("SELECT c1, c2, v FROM %s WHERE k = 0 ORDER BY c1 ASC, c2 DESC"),
-                    expectedRows);
+             assertRows(execute("SELECT c1, c2, v FROM %s WHERE k = 0 ORDER BY c1 ASC, c2 DESC"),
+                        expectedRows);
  
-         for(int i = 9; i >= 0; i--)
-             for(int j = 0; j < 10; j++)
-                 expectedRows[(9 - i) * 10 + j] = row(i, j, String.format("%d%d", i, j));
+             for(int i = 9; i >= 0; i--)
+                 for(int j = 0; j < 10; j++)
+                     expectedRows[(9 - i) * 10 + j] = row(i, j, String.format("%d%d", i, j));
  
-         assertRows(execute("SELECT c1, c2, v FROM %s WHERE k = 0 ORDER BY c1 DESC, c2 ASC"),
-                    expectedRows);
+             assertRows(execute("SELECT c1, c2, v FROM %s WHERE k = 0 ORDER BY c1 DESC, c2 ASC"),
+                        expectedRows);
  
-         assertInvalid("SELECT c1, c2, v FROM %s WHERE k = 0 ORDER BY c2 DESC, c1 ASC");
+             assertInvalid("SELECT c1, c2, v FROM %s WHERE k = 0 ORDER BY c2 DESC, c1 ASC");
 -        }
++        });
      }
  
      /**
@@@ -534,22 -575,28 +561,24 @@@
      {
          createTable("CREATE TABLE %s (k text, c1 int, c2 int, PRIMARY KEY (k, c1, c2) ) WITH CLUSTERING ORDER BY (c1 ASC, c2 DESC)");
  
--        for (int i = 0; i < 2; i++)
--            for (int j = 0; j < 2; j++)
--                execute("INSERT INTO %s (k, c1, c2) VALUES ('foo', ?, ?)", i, j);
 -
 -        for (boolean flush : new boolean[]{true, false})
 -        {
 -            if (flush)
 -                flush();
++        beforeAndAfterFlush(() -> {
++            for (int i = 0; i < 2; i++)
++                for (int j = 0; j < 2; j++)
++                    execute("INSERT INTO %s (k, c1, c2) VALUES ('foo', ?, ?)", i, j);
  
-         assertRows(execute("SELECT c1, c2 FROM %s WHERE k = 'foo'"),
-                    row(0, 1), row(0, 0), row(1, 1), row(1, 0));
+             assertRows(execute("SELECT c1, c2 FROM %s WHERE k = 'foo'"),
+                        row(0, 1), row(0, 0), row(1, 1), row(1, 0));
  
-         assertRows(execute("SELECT c1, c2 FROM %s WHERE k = 'foo' ORDER BY c1 ASC, c2 DESC"),
-                    row(0, 1), row(0, 0), row(1, 1), row(1, 0));
+             assertRows(execute("SELECT c1, c2 FROM %s WHERE k = 'foo' ORDER BY c1 ASC, c2 DESC"),
+                        row(0, 1), row(0, 0), row(1, 1), row(1, 0));
  
-         assertRows(execute("SELECT c1, c2 FROM %s WHERE k = 'foo' ORDER BY c1 DESC, c2 ASC"),
-                    row(1, 0), row(1, 1), row(0, 0), row(0, 1));
+             assertRows(execute("SELECT c1, c2 FROM %s WHERE k = 'foo' ORDER BY c1 DESC, c2 ASC"),
+                        row(1, 0), row(1, 1), row(0, 0), row(0, 1));
  
-         assertInvalid("SELECT c1, c2 FROM %s WHERE k = 'foo' ORDER BY c2 DESC");
-         assertInvalid("SELECT c1, c2 FROM %s WHERE k = 'foo' ORDER BY c2 ASC");
-         assertInvalid("SELECT c1, c2 FROM %s WHERE k = 'foo' ORDER BY c1 ASC, c2 ASC");
+             assertInvalid("SELECT c1, c2 FROM %s WHERE k = 'foo' ORDER BY c2 DESC");
+             assertInvalid("SELECT c1, c2 FROM %s WHERE k = 'foo' ORDER BY c2 ASC");
+             assertInvalid("SELECT c1, c2 FROM %s WHERE k = 'foo' ORDER BY c1 ASC, c2 ASC");
 -        }
++        });
      }
  
      /**
@@@ -563,9 -610,15 +592,11 @@@
          execute("INSERT INTO %s(k, c1, c2) VALUES (0, 0, 1)");
          execute("INSERT INTO %s(k, c1, c2) VALUES (0, 0, 2)");
  
-         assertRows(execute("SELECT * FROM %s WHERE k=0 AND c1 = 0 AND c2 IN (2, 0) ORDER BY c1 DESC"),
-                    row(0, 0, 2),
-                    row(0, 0, 0));
 -        for (boolean flush : new boolean[]{true, false})
 -        {
 -            if (flush)
 -                flush();
 -
++        beforeAndAfterFlush(() -> {
+             assertRows(execute("SELECT * FROM %s WHERE k=0 AND c1 = 0 AND c2 IN (2, 0) ORDER BY c1 DESC"),
+                        row(0, 0, 2),
+                        row(0, 0, 0));
 -        }
++        });
      }
  
      /**
@@@ -584,45 -637,51 +615,47 @@@
          execute("INSERT INTO %s (k, c1, c2, v) VALUES (1, 1, 1, 4)");
          execute("INSERT INTO %s (k, c1, c2, v) VALUES (1, 1, 2, 5)");
  
-         assertRows(execute("SELECT * FROM %s WHERE k=0 AND c1 = 0 AND c2 IN (2, 0)"),
-                    row(0, 0, 0, 0),
-                    row(0, 0, 2, 2));
-         assertRows(execute("SELECT * FROM %s WHERE k=0 AND c1 = 0 AND c2 IN (2, 0) ORDER BY c1 ASC, c2 ASC"),
-                    row(0, 0, 0, 0),
-                    row(0, 0, 2, 2));
- 
-         // check that we don 't need to select the column on which we order
-         assertRows(execute("SELECT v FROM %s WHERE k=0 AND c1 = 0 AND c2 IN (2, 0)"),
-                    row(0),
-                    row(2));
-         assertRows(execute("SELECT v FROM %s WHERE k=0 AND c1 = 0 AND c2 IN (2, 0) ORDER BY c1 ASC"),
-                    row(0),
-                    row(2));
-         assertRows(execute("SELECT v FROM %s WHERE k=0 AND c1 = 0 AND c2 IN (2, 0) ORDER BY c1 DESC"),
-                    row(2),
-                    row(0));
- 
-         assertRows(execute("SELECT v FROM %s WHERE k IN (1, 0)"),
-                    row(0),
-                    row(1),
-                    row(2),
-                    row(3),
-                    row(4),
-                    row(5));
- 
-         assertRows(execute("SELECT v FROM %s WHERE k IN (1, 0) ORDER BY c1 ASC"),
-                    row(0),
-                    row(1),
-                    row(2),
-                    row(3),
-                    row(4),
-                    row(5));
- 
-         // we should also be able to use functions in the select clause (additional test for CASSANDRA - 8286)
-         Object[][] results = getRows(execute("SELECT writetime(v) FROM %s WHERE k IN (1, 0) ORDER BY c1 ASC"));
 -        for (boolean flush : new boolean[]{true, false})
 -        {
 -            if (flush)
 -                flush();
--
-         // since we don 't know the write times, just assert that the order matches the order we expect
-         assertTrue(isFirstIntSorted(results));
++        beforeAndAfterFlush(() -> {
+             assertRows(execute("SELECT * FROM %s WHERE k=0 AND c1 = 0 AND c2 IN (2, 0)"),
+                        row(0, 0, 0, 0),
+                        row(0, 0, 2, 2));
+             assertRows(execute("SELECT * FROM %s WHERE k=0 AND c1 = 0 AND c2 IN (2, 0) ORDER BY c1 ASC, c2 ASC"),
+                        row(0, 0, 0, 0),
+                        row(0, 0, 2, 2));
+ 
+             // check that we don 't need to select the column on which we order
+             assertRows(execute("SELECT v FROM %s WHERE k=0 AND c1 = 0 AND c2 IN (2, 0)"),
+                        row(0),
+                        row(2));
+             assertRows(execute("SELECT v FROM %s WHERE k=0 AND c1 = 0 AND c2 IN (2, 0) ORDER BY c1 ASC"),
+                        row(0),
+                        row(2));
+             assertRows(execute("SELECT v FROM %s WHERE k=0 AND c1 = 0 AND c2 IN (2, 0) ORDER BY c1 DESC"),
+                        row(2),
+                        row(0));
+ 
+             assertRows(execute("SELECT v FROM %s WHERE k IN (1, 0)"),
+                        row(0),
+                        row(1),
+                        row(2),
+                        row(3),
+                        row(4),
+                        row(5));
+ 
+             assertRows(execute("SELECT v FROM %s WHERE k IN (1, 0) ORDER BY c1 ASC"),
+                        row(0),
+                        row(1),
+                        row(2),
+                        row(3),
+                        row(4),
+                        row(5));
+ 
+             // we should also be able to use functions in the select clause (additional test for CASSANDRA - 8286)
+             Object[][] results = getRows(execute("SELECT writetime(v) FROM %s WHERE k IN (1, 0) ORDER BY c1 ASC"));
+ 
+             // since we don 't know the write times, just assert that the order matches the order we expect
+             assertTrue(isFirstIntSorted(results));
 -        }
++        });
      }
  
      @Test
@@@ -637,33 -696,39 +670,35 @@@
              execute("INSERT INTO %s (col_1, col_2, col_3) VALUES(?, ?, ?)", 1, 2, 10);
              execute("INSERT INTO %s (col_1, col_2, col_3) VALUES(?, ?, ?)", 1, 2, 11);
  
-             assertRows(execute("select * from %s where col_1=? and col_2 IN (?, ?) order by col_3;", 1, 1, 2),
-                        row(1, 1, 1),
-                        row(1, 1, 2),
-                        row(1, 2, 10),
-                        row(1, 2, 11),
-                        row(1, 1, 13));
- 
-             assertRows(execute("select * from %s where col_1=? and col_2 IN (?, ?) order by col_3 desc;", 1, 1, 2),
-                        row(1, 1, 13),
-                        row(1, 2, 11),
-                        row(1, 2, 10),
-                        row(1, 1, 2),
-                        row(1, 1, 1));
- 
-             assertRows(execute("select * from %s where col_2 IN (?, ?) and col_1=? order by col_3;", 1, 2, 1),
-                        row(1, 1, 1),
-                        row(1, 1, 2),
-                        row(1, 2, 10),
-                        row(1, 2, 11),
-                        row(1, 1, 13));
 -            for (boolean flush : new boolean[]{true, false})
 -            {
 -                if (flush)
 -                    flush();
--
-             assertRows(execute("select * from %s where col_2 IN (?, ?) and col_1=? order by col_3 desc;", 1, 2, 1),
-                        row(1, 1, 13),
-                        row(1, 2, 11),
-                        row(1, 2, 10),
-                        row(1, 1, 2),
-                        row(1, 1, 1));
++            beforeAndAfterFlush(() -> {
+                 assertRows(execute("select * from %s where col_1=? and col_2 IN (?, ?) order by col_3;", 1, 1, 2),
+                            row(1, 1, 1),
+                            row(1, 1, 2),
+                            row(1, 2, 10),
+                            row(1, 2, 11),
+                            row(1, 1, 13));
+ 
+                 assertRows(execute("select * from %s where col_1=? and col_2 IN (?, ?) order by col_3 desc;", 1, 1, 2),
+                            row(1, 1, 13),
+                            row(1, 2, 11),
+                            row(1, 2, 10),
+                            row(1, 1, 2),
+                            row(1, 1, 1));
+ 
+                 assertRows(execute("select * from %s where col_2 IN (?, ?) and col_1=? order by col_3;", 1, 2, 1),
+                            row(1, 1, 1),
+                            row(1, 1, 2),
+                            row(1, 2, 10),
+                            row(1, 2, 11),
+                            row(1, 1, 13));
+ 
+                 assertRows(execute("select * from %s where col_2 IN (?, ?) and col_1=? order by col_3 desc;", 1, 2, 1),
+                            row(1, 1, 13),
+                            row(1, 2, 11),
+                            row(1, 2, 10),
+                            row(1, 1, 2),
+                            row(1, 1, 1));
 -            }
++            });
          }
      }
  

http://git-wip-us.apache.org/repos/asf/cassandra/blob/bb56193a/test/unit/org/apache/cassandra/cql3/validation/operations/SelectTest.java
----------------------------------------------------------------------
diff --cc test/unit/org/apache/cassandra/cql3/validation/operations/SelectTest.java
index 1b6fe9b,ac1ba4c..a5f153e
--- a/test/unit/org/apache/cassandra/cql3/validation/operations/SelectTest.java
+++ b/test/unit/org/apache/cassandra/cql3/validation/operations/SelectTest.java
@@@ -22,16 -22,19 +22,18 @@@ import java.util.UUID
  
  import org.junit.Test;
  
--import junit.framework.Assert;
++import org.apache.cassandra.cql3.CQLTester;
  import org.apache.cassandra.cql3.UntypedResultSet;
  import org.apache.cassandra.cql3.restrictions.StatementRestrictions;
  import org.apache.cassandra.exceptions.InvalidRequestException;
- import org.apache.cassandra.utils.ByteBufferUtil;
- import org.apache.cassandra.cql3.CQLTester;
  
 -import org.apache.cassandra.cql3.CQLTester;
 -
++import static org.apache.cassandra.utils.ByteBufferUtil.EMPTY_BYTE_BUFFER;
++import static org.apache.cassandra.utils.ByteBufferUtil.bytes;
  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;
++import junit.framework.Assert;
+ 
  /**
   * Test column ranges and ordering with static column in table
   */
@@@ -2576,4 -2324,385 +2578,349 @@@ public class SelectTest extends CQLTest
          assertInvalidMessage("PRIMARY KEY column \"c2\" cannot be restricted as preceding column \"c1\" is not restricted",
                               "SELECT * FROM %s WHERE pk = 1 AND  c2 = 1 ALLOW FILTERING;");
      }
+ 
+     @Test
 -    public void testIndexQueryWithCompositePartitionKey() throws Throwable
 -    {
 -        createTable("CREATE TABLE %s (p1 int, p2 int, v int, PRIMARY KEY ((p1, p2)))");
 -        assertInvalidMessage("Partition key parts: p2 must be restricted as other parts are",
 -                             "SELECT * FROM %s WHERE p1 = 1 AND v = 3 ALLOW FILTERING");
 -
 -        createIndex("CREATE INDEX ON %s(v)");
 -
 -        execute("INSERT INTO %s(p1, p2, v) values (?, ?, ?)", 1, 1, 3);
 -        execute("INSERT INTO %s(p1, p2, v) values (?, ?, ?)", 1, 2, 3);
 -        execute("INSERT INTO %s(p1, p2, v) values (?, ?, ?)", 2, 1, 3);
 -
 -        assertRows(execute("SELECT * FROM %s WHERE p1 = 1 AND v = 3 ALLOW FILTERING"),
 -                   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();
++            beforeAndAfterFlush(() -> {
+ 
+                 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",
++                assertInvalidMessage("Invalid empty or null value for column 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();
 -
++                beforeAndAfterFlush(() -> {
+                     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("Predicates on non-primary-key columns (v) are not yet supported for non secondary index queries",
 -                                 "SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND v = textAsBlob('') ALLOW FILTERING;");
++            assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND v = textAsBlob('') ALLOW FILTERING;"));
++
++            execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?)",
++                    bytes("foo123"), bytes("3"), EMPTY_BYTE_BUFFER);
++
++            beforeAndAfterFlush(() -> {
++                assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND v = textAsBlob('') ALLOW FILTERING;"),
++                           row(bytes("foo123"), bytes("3"), EMPTY_BYTE_BUFFER));
++            });
+         }
+     }
+ 
+     @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();
++            beforeAndAfterFlush(() -> {
+ 
+                 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 IN (textAsBlob(''), textAsBlob('1')) AND c2 = textAsBlob('1');"),
+                            row(bytes("foo123"), bytes("1"), bytes("1"), bytes("1")));
+ 
+                 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"), 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();
 -
++            beforeAndAfterFlush(() -> {
+                 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 IN (textAsBlob(''), textAsBlob('1')) AND c2 = 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) 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();
++            beforeAndAfterFlush(() -> {
+ 
+                 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",
++                assertInvalidMessage("Invalid empty or null value for column 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();
++                beforeAndAfterFlush(() -> {
+ 
+                     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();
++            beforeAndAfterFlush(() -> {
+ 
+                 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();
++            beforeAndAfterFlush(() -> {
+ 
+                 assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c1 IN (textAsBlob(''), textAsBlob('1')) AND c2 = 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) 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")));
 -            }
++            });
+         }
+     }
  }


[11/12] cassandra git commit: Merge branch cassandra-3.0 into cassandra-3.9

Posted by bl...@apache.org.
Merge branch cassandra-3.0 into cassandra-3.9


Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/cb97649f
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/cb97649f
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/cb97649f

Branch: refs/heads/trunk
Commit: cb97649f51bb01285e813daae65cc0287e33b6c2
Parents: de2c449 bb56193
Author: Benjamin Lerer <b....@gmail.com>
Authored: Tue Aug 16 15:39:11 2016 +0200
Committer: Benjamin Lerer <b....@gmail.com>
Committed: Tue Aug 16 15:41:34 2016 +0200

----------------------------------------------------------------------
 CHANGES.txt                                     |   1 +
 NEWS.txt                                        |   9 +
 .../apache/cassandra/db/BufferClustering.java   |   4 -
 .../columniterator/AbstractSSTableIterator.java |  24 +-
 .../db/columniterator/SSTableIterator.java      |  17 +
 .../columniterator/SSTableReversedIterator.java |  19 +-
 .../cassandra/db/compaction/Scrubber.java       | 169 +++-
 .../cassandra/db/marshal/ReversedType.java      |  10 -
 .../db/partitions/AbstractBTreePartition.java   |   7 +-
 .../db/partitions/ImmutableBTreePartition.java  |  34 +-
 .../db/rows/UnfilteredRowIterators.java         |  28 +
 .../validation/entities/SecondaryIndexTest.java | 131 ++++
 .../cql3/validation/operations/DeleteTest.java  | 113 +++
 .../operations/SelectOrderByTest.java           | 765 ++++++++++---------
 .../cql3/validation/operations/SelectTest.java  | 355 ++++++++-
 .../cassandra/db/marshal/ReversedTypeTest.java  |   4 +-
 16 files changed, 1268 insertions(+), 422 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/cb97649f/CHANGES.txt
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/cassandra/blob/cb97649f/NEWS.txt
----------------------------------------------------------------------
diff --cc NEWS.txt
index 249077d,0bd3920..2edff8e
--- a/NEWS.txt
+++ b/NEWS.txt
@@@ -13,35 -13,18 +13,44 @@@ restore snapshots created with the prev
  'sstableloader' tool. You can upgrade the file format of your snapshots
  using the provided 'sstableupgrade' tool.
  
 -3.0.9
 -=====
++3.9
++===
+ 
+ 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 more details.
  
 -3.0.8
 -=====
 +3.8
 +===
 +
 +New features
 +------------
 +   - Shared pool threads are now named according to the stage they are executing
 +     tasks for. Thread names mentioned in traced queries change accordingly.
 +   - A new option has been added to cassandra-stress "-rate fixed={number}/s"
 +     that forces a scheduled rate of operations/sec over time. Using this, stress can
 +     accurately account for coordinated ommission from the stress process.
 +   - The cassandra-stress "-rate limit=" option has been renamed to "-rate throttle="
 +   - hdr histograms have been added to stress runs, it's output can be saved to disk using:
 +     "-log hdrfile=" option. This histogram includes response/service/wait times when used with the
 +     fixed or throttle rate options.  The histogram file can be plotted on
 +     http://hdrhistogram.github.io/HdrHistogram/plotFiles.html
 +   - TimeWindowCompactionStrategy has been added. This has proven to be a better approach
 +     to time series compaction and new tables should use this instead of DTCS. See
 +     CASSANDRA-9666 for details.
 +   - Change-Data-Capture is now available. See cassandra.yaml and for cdc-specific flags and
 +     a brief explanation of on-disk locations for archived data in CommitLog form. This can
 +     be enabled via ALTER TABLE ... WITH cdc=true.
 +     Upon flush, CommitLogSegments containing data for CDC-enabled tables are moved to
 +     the data/cdc_raw directory until removed by the user and writes to CDC-enabled tables
 +     will be rejected with a WriteTimeoutException once cdc_total_space_in_mb is reached
 +     between unflushed CommitLogSegments and cdc_raw.
 +     NOTE: CDC is disabled by default in the .yaml file. Do not enable CDC on a mixed-version
 +     cluster as it will lead to exceptions which can interrupt traffic. Once all nodes
 +     have been upgraded to 3.8 it is safe to enable this feature and restart the cluster.
  
  Upgrading
  ---------

http://git-wip-us.apache.org/repos/asf/cassandra/blob/cb97649f/src/java/org/apache/cassandra/db/BufferClustering.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/db/BufferClustering.java
index 7c6bb20,0000000..df6a473
mode 100644,000000..100644
--- a/src/java/org/apache/cassandra/db/BufferClustering.java
+++ b/src/java/org/apache/cassandra/db/BufferClustering.java
@@@ -1,44 -1,0 +1,40 @@@
 +/*
 + * 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.db;
 +
 +import java.nio.ByteBuffer;
 +
- import org.apache.cassandra.config.CFMetaData;
- import org.apache.cassandra.config.ColumnDefinition;
- import org.apache.cassandra.utils.memory.AbstractAllocator;
- 
 +/**
 + * The clustering column values for a row.
 + * <p>
 + * A {@code Clustering} is a {@code ClusteringPrefix} that must always be "complete", i.e. have
 + * as many values as there is clustering columns in the table it is part of. It is the clustering
 + * prefix used by rows.
 + * <p>
 + * Note however that while it's size must be equal to the table clustering size, a clustering can have
 + * {@code null} values, and this mostly for thrift backward compatibility (in practice, if a value is null,
 + * all of the following ones will be too because that's what thrift allows, but it's never assumed by the
 + * code so we could start generally allowing nulls for clustering columns if we wanted to).
 + */
 +public class BufferClustering extends AbstractBufferClusteringPrefix implements Clustering
 +{
 +    BufferClustering(ByteBuffer... values)
 +    {
 +        super(Kind.CLUSTERING, values);
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/cb97649f/src/java/org/apache/cassandra/db/columniterator/AbstractSSTableIterator.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/db/columniterator/AbstractSSTableIterator.java
index 005bb2c,d57e6bc..5959a20
--- a/src/java/org/apache/cassandra/db/columniterator/AbstractSSTableIterator.java
+++ b/src/java/org/apache/cassandra/db/columniterator/AbstractSSTableIterator.java
@@@ -47,13 -46,8 +47,12 @@@ public abstract class AbstractSSTableIt
  
      private final boolean isForThrift;
  
 +    protected final SegmentedFile ifile;
 +
      private boolean isClosed;
  
 +    protected final Slices slices;
-     protected int slice;
 +
      @SuppressWarnings("resource") // We need this because the analysis is not able to determine that we do close
                                    // file on every path where we created it.
      protected AbstractSSTableIterator(SSTableReader sstable,
@@@ -119,9 -109,6 +118,9 @@@
                      this.reader = needsReader ? createReader(indexEntry, file, shouldCloseFile) : null;
                  }
  
 +                if (reader != null && !slices.isEmpty())
-                     reader.setForSlice(slices.get(0));
++                    reader.setForSlice(nextSlice());
 +
                  if (reader == null && file != null && shouldCloseFile)
                      file.close();
              }
@@@ -145,6 -132,6 +144,23 @@@
          }
      }
  
++    private Slice nextSlice()
++    {
++        return slices.get(nextSliceIndex());
++    }
++
++    /**
++     * Returns the index of the next slice to process.
++     * @return the index of the next slice to process
++     */
++    protected abstract int nextSliceIndex();
++
++    /**
++     * Checks if there are more slice to process.
++     * @return {@code true} if there are more slice to process, {@code false} otherwise.
++     */
++    protected abstract boolean hasMoreSlices();
++
      private static Row readStaticRow(SSTableReader sstable,
                                       FileDataInput file,
                                       SerializationHelper helper,
@@@ -233,19 -216,7 +249,19 @@@
  
      public boolean hasNext()
      {
 -        return reader != null && reader.hasNext();
 +        while (true)
 +        {
 +            if (reader == null)
 +                return false;
 +
 +            if (reader.hasNext())
 +                return true;
 +
-             if (++slice >= slices.size())
++            if (!hasMoreSlices())
 +                return false;
 +
-             slice(slices.get(slice));
++            slice(nextSlice());
 +        }
      }
  
      public Unfiltered next()

http://git-wip-us.apache.org/repos/asf/cassandra/blob/cb97649f/src/java/org/apache/cassandra/db/columniterator/SSTableIterator.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/db/columniterator/SSTableIterator.java
index e4f6700,0409310..56ba8f4
--- a/src/java/org/apache/cassandra/db/columniterator/SSTableIterator.java
+++ b/src/java/org/apache/cassandra/db/columniterator/SSTableIterator.java
@@@ -32,6 -31,11 +32,11 @@@ import org.apache.cassandra.io.util.Seg
   */
  public class SSTableIterator extends AbstractSSTableIterator
  {
 -    public SSTableIterator(SSTableReader sstable, DecoratedKey key, ColumnFilter columns, boolean isForThrift)
 -    {
 -        this(sstable, null, key, sstable.getPosition(key, SSTableReader.Operator.EQ), columns, isForThrift);
 -    }
++    /**
++     * The index of the slice being processed.
++     */
++    private int slice;
+ 
      public SSTableIterator(SSTableReader sstable,
                             FileDataInput file,
                             DecoratedKey key,
@@@ -51,6 -53,6 +56,18 @@@
               : new ForwardReader(file, shouldCloseFile);
      }
  
++    protected int nextSliceIndex()
++    {
++        int next = slice;
++        slice++;
++        return next;
++    }
++
++    protected boolean hasMoreSlices()
++    {
++        return slice < slices.size();
++    }
++
      public boolean isReverseOrder()
      {
          return false;

http://git-wip-us.apache.org/repos/asf/cassandra/blob/cb97649f/src/java/org/apache/cassandra/db/columniterator/SSTableReversedIterator.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/db/columniterator/SSTableReversedIterator.java
index 903fd59,3e49a3a..b8b6f76
--- a/src/java/org/apache/cassandra/db/columniterator/SSTableReversedIterator.java
+++ b/src/java/org/apache/cassandra/db/columniterator/SSTableReversedIterator.java
@@@ -35,6 -34,11 +35,11 @@@ import org.apache.cassandra.utils.btree
   */
  public class SSTableReversedIterator extends AbstractSSTableIterator
  {
 -    public SSTableReversedIterator(SSTableReader sstable, DecoratedKey key, ColumnFilter columns, boolean isForThrift)
 -    {
 -        this(sstable, null, key, sstable.getPosition(key, SSTableReader.Operator.EQ), columns, isForThrift);
 -    }
++    /**
++     * The index of the slice being processed.
++     */
++    private int slice;
+ 
      public SSTableReversedIterator(SSTableReader sstable,
                                     FileDataInput file,
                                     DecoratedKey key,
@@@ -59,6 -61,6 +64,18 @@@
          return true;
      }
  
++    protected int nextSliceIndex()
++    {
++        int next = slice;
++        slice++;
++        return slices.size() - (next + 1);
++    }
++
++    protected boolean hasMoreSlices()
++    {
++        return slice < slices.size();
++    }
++
      private class ReverseReader extends Reader
      {
          protected ReusablePartitionData buffer;
@@@ -106,7 -108,7 +123,7 @@@
                  buffer = createBuffer(1);
                  // Note that we can reuse that buffer between slices (we could alternatively re-read from disk
                  // every time, but that feels more wasteful) so we want to include everything from the beginning.
--                // We can stop at the slice end however since any following slice will be before that.
++                // We can stop at the last slice end however since any following slice will be before that.
                  loadFromDisk(null, slice.end(), true);
              }
              setIterator(slice);

http://git-wip-us.apache.org/repos/asf/cassandra/blob/cb97649f/src/java/org/apache/cassandra/db/compaction/Scrubber.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/cassandra/blob/cb97649f/src/java/org/apache/cassandra/db/partitions/AbstractBTreePartition.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/db/partitions/AbstractBTreePartition.java
index 7fb01a3,c63acc2..4a8e4f2
--- a/src/java/org/apache/cassandra/db/partitions/AbstractBTreePartition.java
+++ b/src/java/org/apache/cassandra/db/partitions/AbstractBTreePartition.java
@@@ -270,8 -282,47 +270,13 @@@ public abstract class AbstractBTreePart
          }
      }
  
 -    public class SliceableIterator extends AbstractIterator implements SliceableUnfilteredRowIterator
 -    {
 -        private Iterator<Unfiltered> iterator;
 -
 -        protected SliceableIterator(ColumnFilter selection, boolean isReversed)
 -        {
 -            super(selection, isReversed);
 -        }
 -
 -        protected Unfiltered computeNext()
 -        {
 -            if (iterator == null)
 -                iterator = unfilteredIterator(selection, Slices.ALL, isReverseOrder);
 -            if (!iterator.hasNext())
 -                return endOfData();
 -            return iterator.next();
 -        }
 -
 -        public Iterator<Unfiltered> slice(Slice slice)
 -        {
 -            return sliceIterator(selection, slice, isReverseOrder, current, staticRow);
 -        }
 -    }
 -
 -    public SliceableUnfilteredRowIterator sliceableUnfilteredIterator(ColumnFilter columns, boolean reversed)
 -    {
 -        return new SliceableIterator(columns, reversed);
 -    }
 -
 -    protected SliceableUnfilteredRowIterator sliceableUnfilteredIterator()
 -    {
 -        return sliceableUnfilteredIterator(ColumnFilter.all(metadata), false);
 -    }
 -
      protected static Holder build(UnfilteredRowIterator iterator, int initialRowCapacity)
      {
+         return build(iterator, initialRowCapacity, true);
+     }
+ 
+     protected static Holder build(UnfilteredRowIterator iterator, int initialRowCapacity, boolean ordered)
+     {
          CFMetaData metadata = iterator.metadata();
          PartitionColumns columns = iterator.columns();
          boolean reversed = iterator.isReverseOrder();

http://git-wip-us.apache.org/repos/asf/cassandra/blob/cb97649f/src/java/org/apache/cassandra/db/rows/UnfilteredRowIterators.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/db/rows/UnfilteredRowIterators.java
index d324190,43653a9..46447ec
--- a/src/java/org/apache/cassandra/db/rows/UnfilteredRowIterators.java
+++ b/src/java/org/apache/cassandra/db/rows/UnfilteredRowIterators.java
@@@ -230,6 -213,60 +230,34 @@@ public abstract class UnfilteredRowIter
      }
  
      /**
+      * Returns an iterator that concatenate the specified atom with the iterator.
+      */
+     public static UnfilteredRowIterator concat(final Unfiltered first, final UnfilteredRowIterator rest)
+     {
+         return new WrappingUnfilteredRowIterator(rest)
+         {
+             private boolean hasReturnedFirst;
+ 
+             @Override
+             public boolean hasNext()
+             {
+                 return hasReturnedFirst ? super.hasNext() : true;
+             }
+ 
+             @Override
+             public Unfiltered next()
+             {
+                 if (!hasReturnedFirst)
+                 {
+                     hasReturnedFirst = true;
+                     return first;
+                 }
+                 return super.next();
+             }
+         };
+     }
+ 
 -    public static UnfilteredRowIterator cloningIterator(UnfilteredRowIterator iterator, final AbstractAllocator allocator)
 -    {
 -        class Cloner extends Transformation
 -        {
 -            private final Row.Builder builder = allocator.cloningBTreeRowBuilder();
 -
 -            public Row applyToStatic(Row row)
 -            {
 -                return Rows.copy(row, builder).build();
 -            }
 -
 -            @Override
 -            public Row applyToRow(Row row)
 -            {
 -                return Rows.copy(row, builder).build();
 -            }
 -
 -            @Override
 -            public RangeTombstoneMarker applyToMarker(RangeTombstoneMarker marker)
 -            {
 -                return marker.copy(allocator);
 -            }
 -        }
 -        return Transformation.apply(iterator, new Cloner());
 -    }
 -
+     /**
       * Validate that the data of the provided iterator is valid, that is that the values
       * it contains are valid for the type they represent, and more generally that the
       * infos stored are sensible.

http://git-wip-us.apache.org/repos/asf/cassandra/blob/cb97649f/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/cassandra/blob/cb97649f/test/unit/org/apache/cassandra/cql3/validation/operations/DeleteTest.java
----------------------------------------------------------------------
diff --cc test/unit/org/apache/cassandra/cql3/validation/operations/DeleteTest.java
index 9b92ebb,b9ccafb..118a1e2
--- a/test/unit/org/apache/cassandra/cql3/validation/operations/DeleteTest.java
+++ b/test/unit/org/apache/cassandra/cql3/validation/operations/DeleteTest.java
@@@ -1107,28 -1058,142 +1112,136 @@@ public class DeleteTest extends CQLTest
      }
  
      @Test
 +    public void testDeleteAndReverseQueries() throws Throwable
 +    {
 +        // This test insert rows in one sstable and a range tombstone covering some of those rows in another, and it
 +        // validates we correctly get only the non-removed rows when doing reverse queries.
 +
 +        createTable("CREATE TABLE %s (k text, i int, PRIMARY KEY (k, i))");
 +
 +        for (int i = 0; i < 10; i++)
 +            execute("INSERT INTO %s(k, i) values (?, ?)", "a", i);
 +
 +        flush();
 +
 +        execute("DELETE FROM %s WHERE k = ? AND i >= ? AND i <= ?", "a", 2, 7);
 +
 +        assertRows(execute("SELECT i FROM %s WHERE k = ? ORDER BY i DESC", "a"),
 +            row(9), row(8), row(1), row(0)
 +        );
 +
 +        flush();
 +
 +        assertRows(execute("SELECT i FROM %s WHERE k = ? ORDER BY i DESC", "a"),
 +            row(9), row(8), row(1), row(0)
 +        );
 +    }
++
++    @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"));
+ 
+                 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 IN (textAsBlob(''), textAsBlob('1'));");
+ 
+                 assertEmpty(execute("SELECT * FROM %s"));
+ 
+                 execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?)", bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1"));
+                 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"));
+ 
+                 execute("DELETE FROM %s WHERE pk = textAsBlob('foo123') AND c > textAsBlob('')");
+ 
+                 assertRows(execute("SELECT * FROM %s"),
+                            row(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'))");
+             }
+ 
+             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"));
+ 
+             execute("DELETE FROM %s WHERE pk = textAsBlob('foo123') AND c > textAsBlob('')");
+ 
+             assertEmpty(execute("SELECT * FROM %s"));
+ 
+             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"));
+ 
+             execute("DELETE FROM %s WHERE pk = textAsBlob('foo123') AND c <= textAsBlob('')");
+             execute("DELETE FROM %s WHERE pk = textAsBlob('foo123') AND c < textAsBlob('')");
+ 
+             assertRows(execute("SELECT * FROM %s"),
+                        row(bytes("foo123"), bytes("1"), bytes("1")),
+                        row(bytes("foo123"), bytes("2"), bytes("2")));
+         }
+     }
+ 
+     @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"));
+ 
+             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 IN (textAsBlob(''), textAsBlob('1')) AND c2 = textAsBlob('1');");
+ 
+             assertEmpty(execute("SELECT * FROM %s"));
+ 
+             execute("INSERT INTO %s (pk, c1, c2, v) VALUES (?, ?, ?, ?)", bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1"), bytes("0"));
+             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("3"));
+ 
+             execute("DELETE FROM %s WHERE pk = textAsBlob('foo123') AND c1 > textAsBlob('')");
+ 
+             assertRows(execute("SELECT * FROM %s"),
+                        row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1"), bytes("0")));
+ 
+             execute("DELETE FROM %s WHERE pk = textAsBlob('foo123') AND c1 >= textAsBlob('')");
+ 
+             assertEmpty(execute("SELECT * FROM %s"));
+ 
+             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("3"));
+ 
+             execute("DELETE FROM %s WHERE pk = textAsBlob('foo123') AND c1 > textAsBlob('')");
+ 
+             assertEmpty(execute("SELECT * FROM %s"));
+ 
+             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("3"));
+ 
+             execute("DELETE FROM %s WHERE pk = textAsBlob('foo123') AND c1 <= textAsBlob('')");
+             execute("DELETE FROM %s WHERE pk = textAsBlob('foo123') AND c1 < textAsBlob('')");
+ 
+             assertRows(execute("SELECT * FROM %s"),
+                        row(bytes("foo123"), bytes("1"), bytes("1"), bytes("1")),
+                        row(bytes("foo123"), bytes("1"), bytes("2"), bytes("3")));
+         }
+     }
 -
 -    private void flush(boolean forceFlush)
 -    {
 -        if (forceFlush)
 -            flush();
 -    }
 -
 -    @Test
 -    public void testDeleteAndReverseQueries() throws Throwable
 -    {
 -        // This test insert rows in one sstable and a range tombstone covering some of those rows in another, and it
 -        // validates we correctly get only the non-removed rows when doing reverse queries.
 -
 -        createTable("CREATE TABLE %s (k text, i int, PRIMARY KEY (k, i))");
 -
 -        for (int i = 0; i < 10; i++)
 -            execute("INSERT INTO %s(k, i) values (?, ?)", "a", i);
 -
 -        flush();
 -
 -        execute("DELETE FROM %s WHERE k = ? AND i >= ? AND i <= ?", "a", 2, 7);
 -
 -        assertRows(execute("SELECT i FROM %s WHERE k = ? ORDER BY i DESC", "a"),
 -            row(9), row(8), row(1), row(0)
 -        );
 -
 -        flush();
 -
 -        assertRows(execute("SELECT i FROM %s WHERE k = ? ORDER BY i DESC", "a"),
 -            row(9), row(8), row(1), row(0)
 -        );
 -    }
  }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/cb97649f/test/unit/org/apache/cassandra/cql3/validation/operations/SelectOrderByTest.java
----------------------------------------------------------------------
diff --cc test/unit/org/apache/cassandra/cql3/validation/operations/SelectOrderByTest.java
index 6e06419,bb35458..bfc0c86
--- a/test/unit/org/apache/cassandra/cql3/validation/operations/SelectOrderByTest.java
+++ b/test/unit/org/apache/cassandra/cql3/validation/operations/SelectOrderByTest.java
@@@ -105,15 -109,17 +109,17 @@@ public class SelectOrderByTest extends 
              execute("INSERT INTO %s (a, b, c) VALUES (?, ?, {a: ?})", 0, 1, 1);
              execute("INSERT INTO %s (a, b, c) VALUES (?, ?, {a: ?})", 0, 2, 2);
  
-             // order by a column not in the selection
-             assertRows(execute("SELECT c.a FROM %s WHERE a=? ORDER BY b ASC", 0),
-                     row(0), row(1), row(2));
+             beforeAndAfterFlush(() -> {
+                 // order by a column not in the selection
+                 assertRows(execute("SELECT c.a FROM %s WHERE a=? ORDER BY b ASC", 0),
 -                        row(0), row(1), row(2));
 -    
++                           row(0), row(1), row(2));
 +
-             assertRows(execute("SELECT c.a FROM %s WHERE a=? ORDER BY b DESC", 0),
-                     row(2), row(1), row(0));
+                 assertRows(execute("SELECT c.a FROM %s WHERE a=? ORDER BY b DESC", 0),
 -                        row(2), row(1), row(0));
 -    
++                           row(2), row(1), row(0));
 +
-             assertRows(execute("SELECT blobAsInt(intAsBlob(c.a)) FROM %s WHERE a=? ORDER BY b DESC", 0),
-                        row(2), row(1), row(0));
+                 assertRows(execute("SELECT blobAsInt(intAsBlob(c.a)) FROM %s WHERE a=? ORDER BY b DESC", 0),
+                            row(2), row(1), row(0));
+             });
              dropTable("DROP TABLE %s");
          }
      }
@@@ -351,34 -368,36 +368,36 @@@
          execute("INSERT INTO %s (pk1, pk2, c, v) VALUES (?, ?, ?, ?)", 1, 3, 3, "C");
          execute("INSERT INTO %s (pk1, pk2, c, v) VALUES (?, ?, ?, ?)", 1, 1, 4, "D");
  
-         assertRows(execute("SELECT v, ttl(v), c FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c; ", 1, 1, 2),
-                    row("B", null, 1),
-                    row("A", null, 2),
-                    row("D", null, 4));
- 
-         assertRows(execute("SELECT v, ttl(v), c as name_1 FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c; ", 1, 1, 2),
-                    row("B", null, 1),
-                    row("A", null, 2),
-                    row("D", null, 4));
- 
-         assertRows(execute("SELECT v FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c; ", 1, 1, 2),
-                    row("B"),
-                    row("A"),
-                    row("D"));
- 
-         assertRows(execute("SELECT v FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c LIMIT 2; ", 1, 1, 2),
-                    row("B"),
-                    row("A"));
- 
-         assertRows(execute("SELECT v FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c LIMIT 10; ", 1, 1, 2),
-                    row("B"),
-                    row("A"),
-                    row("D"));
- 
-         assertRows(execute("SELECT v as c FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c; ", 1, 1, 2),
-                    row("B"),
-                    row("A"),
-                    row("D"));
+         beforeAndAfterFlush(() -> {
 -        assertRows(execute("SELECT v, ttl(v), c FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c; ", 1, 1, 2),
 -                   row("B", null, 1),
 -                   row("A", null, 2),
 -                   row("D", null, 4));
 -
 -        assertRows(execute("SELECT v, ttl(v), c as name_1 FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c; ", 1, 1, 2),
 -                   row("B", null, 1),
 -                   row("A", null, 2),
 -                   row("D", null, 4));
 -
 -        assertRows(execute("SELECT v FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c; ", 1, 1, 2),
 -                   row("B"),
 -                   row("A"),
 -                   row("D"));
 -
 -        assertRows(execute("SELECT v FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c LIMIT 2; ", 1, 1, 2),
 -                   row("B"),
 -                   row("A"));
 -
 -        assertRows(execute("SELECT v FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c LIMIT 10; ", 1, 1, 2),
 -                   row("B"),
 -                   row("A"),
 -                   row("D"));
 -
 -        assertRows(execute("SELECT v as c FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c; ", 1, 1, 2),
 -                   row("B"),
 -                   row("A"),
 -                   row("D"));
++            assertRows(execute("SELECT v, ttl(v), c FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c; ", 1, 1, 2),
++                       row("B", null, 1),
++                       row("A", null, 2),
++                       row("D", null, 4));
++
++            assertRows(execute("SELECT v, ttl(v), c as name_1 FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c; ", 1, 1, 2),
++                       row("B", null, 1),
++                       row("A", null, 2),
++                       row("D", null, 4));
++
++            assertRows(execute("SELECT v FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c; ", 1, 1, 2),
++                       row("B"),
++                       row("A"),
++                       row("D"));
++
++            assertRows(execute("SELECT v FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c LIMIT 2; ", 1, 1, 2),
++                       row("B"),
++                       row("A"));
++
++            assertRows(execute("SELECT v FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c LIMIT 10; ", 1, 1, 2),
++                       row("B"),
++                       row("A"),
++                       row("D"));
++
++            assertRows(execute("SELECT v as c FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c; ", 1, 1, 2),
++                       row("B"),
++                       row("A"),
++                       row("D"));
+         });
  
          createTable("CREATE TABLE %s (pk1 int, pk2 int, c1 int, c2 int, v text, PRIMARY KEY ((pk1, pk2), c1, c2) )");
          execute("INSERT INTO %s (pk1, pk2, c1, c2, v) VALUES (?, ?, ?, ?, ?)", 1, 1, 4, 4, "A");
@@@ -534,22 -561,24 +561,24 @@@
      {
          createTable("CREATE TABLE %s (k text, c1 int, c2 int, PRIMARY KEY (k, c1, c2) ) WITH CLUSTERING ORDER BY (c1 ASC, c2 DESC)");
  
 -        beforeAndAfterFlush(() -> {
 -            for (int i = 0; i < 2; i++)
 -                for (int j = 0; j < 2; j++)
 -                    execute("INSERT INTO %s (k, c1, c2) VALUES ('foo', ?, ?)", i, j);
 +        for (int i = 0; i < 2; i++)
 +            for (int j = 0; j < 2; j++)
 +                execute("INSERT INTO %s (k, c1, c2) VALUES ('foo', ?, ?)", i, j);
  
-         assertRows(execute("SELECT c1, c2 FROM %s WHERE k = 'foo'"),
-                    row(0, 1), row(0, 0), row(1, 1), row(1, 0));
++        beforeAndAfterFlush(() -> {
+             assertRows(execute("SELECT c1, c2 FROM %s WHERE k = 'foo'"),
+                        row(0, 1), row(0, 0), row(1, 1), row(1, 0));
  
-         assertRows(execute("SELECT c1, c2 FROM %s WHERE k = 'foo' ORDER BY c1 ASC, c2 DESC"),
-                    row(0, 1), row(0, 0), row(1, 1), row(1, 0));
+             assertRows(execute("SELECT c1, c2 FROM %s WHERE k = 'foo' ORDER BY c1 ASC, c2 DESC"),
+                        row(0, 1), row(0, 0), row(1, 1), row(1, 0));
  
-         assertRows(execute("SELECT c1, c2 FROM %s WHERE k = 'foo' ORDER BY c1 DESC, c2 ASC"),
-                    row(1, 0), row(1, 1), row(0, 0), row(0, 1));
+             assertRows(execute("SELECT c1, c2 FROM %s WHERE k = 'foo' ORDER BY c1 DESC, c2 ASC"),
+                        row(1, 0), row(1, 1), row(0, 0), row(0, 1));
  
-         assertInvalid("SELECT c1, c2 FROM %s WHERE k = 'foo' ORDER BY c2 DESC");
-         assertInvalid("SELECT c1, c2 FROM %s WHERE k = 'foo' ORDER BY c2 ASC");
-         assertInvalid("SELECT c1, c2 FROM %s WHERE k = 'foo' ORDER BY c1 ASC, c2 ASC");
+             assertInvalid("SELECT c1, c2 FROM %s WHERE k = 'foo' ORDER BY c2 DESC");
+             assertInvalid("SELECT c1, c2 FROM %s WHERE k = 'foo' ORDER BY c2 ASC");
+             assertInvalid("SELECT c1, c2 FROM %s WHERE k = 'foo' ORDER BY c1 ASC, c2 ASC");
+         });
      }
  
      /**

http://git-wip-us.apache.org/repos/asf/cassandra/blob/cb97649f/test/unit/org/apache/cassandra/cql3/validation/operations/SelectTest.java
----------------------------------------------------------------------
diff --cc test/unit/org/apache/cassandra/cql3/validation/operations/SelectTest.java
index 9a1493b,a5f153e..db0a4cd
--- a/test/unit/org/apache/cassandra/cql3/validation/operations/SelectTest.java
+++ b/test/unit/org/apache/cassandra/cql3/validation/operations/SelectTest.java
@@@ -21,16 -21,18 +21,18 @@@ import java.nio.ByteBuffer
  import java.util.UUID;
  
  import org.junit.Test;
++import org.junit.Assert;
  
- import junit.framework.Assert;
  import org.apache.cassandra.cql3.CQLTester;
  import org.apache.cassandra.cql3.UntypedResultSet;
  import org.apache.cassandra.cql3.restrictions.StatementRestrictions;
  import org.apache.cassandra.exceptions.InvalidRequestException;
- import org.apache.cassandra.utils.ByteBufferUtil;
  
 -import static org.apache.cassandra.utils.ByteBufferUtil.EMPTY_BYTE_BUFFER;
 -import static org.apache.cassandra.utils.ByteBufferUtil.bytes;
  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;
+ 
 -import junit.framework.Assert;
  
  /**
   * Test column ranges and ordering with static column in table
@@@ -2973,22 -2571,356 +2975,367 @@@ public class SelectTest extends CQLTest
  
          assertRows(execute("SELECT * FROM %s WHERE pk = 1 AND  c1 IN(0,1,2) AND c2 = 1 AND v = 3"),
                     row(1, 1, 1, 3, 3));
 +    }
  
 -        assertInvalidMessage("Clustering column \"c2\" cannot be restricted (preceding column \"c1\" is restricted by a non-EQ relation)",
 -                             "SELECT * FROM %s WHERE pk = 1 AND  c1 > 0 AND c1 < 5 AND c2 = 1 ALLOW FILTERING;");
 +    @Test
 +    public void testIndexQueryWithCompositePartitionKey() throws Throwable
 +    {
 +        createTable("CREATE TABLE %s (p1 int, p2 int, v int, PRIMARY KEY ((p1, p2)))");
 +        assertInvalidMessage("Partition key parts: p2 must be restricted as other parts are",
 +                             "SELECT * FROM %s WHERE p1 = 1 AND v = 3 ALLOW FILTERING");
 +        createIndex("CREATE INDEX ON %s(v)");
  
 -        assertInvalidMessage("PRIMARY KEY column \"c2\" cannot be restricted as preceding column \"c1\" is not restricted",
 -                             "SELECT * FROM %s WHERE pk = 1 AND  c2 = 1 ALLOW FILTERING;");
 +        execute("INSERT INTO %s(p1, p2, v) values (?, ?, ?)", 1, 1, 3);
 +        execute("INSERT INTO %s(p1, p2, v) values (?, ?, ?)", 1, 2, 3);
 +        execute("INSERT INTO %s(p1, p2, v) values (?, ?, ?)", 2, 1, 3);
 +
 +        assertRows(execute("SELECT * FROM %s WHERE p1 = 1 AND v = 3 ALLOW FILTERING"),
 +                   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"));
+ 
+             beforeAndAfterFlush(() -> {
+ 
+                 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("Invalid empty or null value for column 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"));
+ 
+                 beforeAndAfterFlush(() -> {
+                     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
+             assertEmpty(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND v = textAsBlob('') ALLOW FILTERING;"));
+ 
+             execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?)",
+                     bytes("foo123"), bytes("3"), EMPTY_BYTE_BUFFER);
+ 
+             beforeAndAfterFlush(() -> {
+                 assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND v = textAsBlob('') ALLOW FILTERING;"),
+                            row(bytes("foo123"), bytes("3"), EMPTY_BYTE_BUFFER));
+             });
+         }
+     }
+ 
+     @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"));
+ 
+             beforeAndAfterFlush(() -> {
+ 
+                 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 IN (textAsBlob(''), textAsBlob('1')) AND c2 = textAsBlob('1');"),
+                            row(bytes("foo123"), bytes("1"), bytes("1"), bytes("1")));
+ 
+                 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"), 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"));
+ 
+             beforeAndAfterFlush(() -> {
+                 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 IN (textAsBlob(''), textAsBlob('1')) AND c2 = 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) 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"));
+ 
+             beforeAndAfterFlush(() -> {
+ 
+                 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("Invalid empty or null value for column 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"));
+ 
+                 beforeAndAfterFlush(() -> {
+ 
+                     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"));
+ 
+             beforeAndAfterFlush(() -> {
+ 
+                 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"));
+ 
+             beforeAndAfterFlush(() -> {
+ 
+                 assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c1 IN (textAsBlob(''), textAsBlob('1')) AND c2 = 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) 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")));
+             });
+         }
+     }
  }


[07/12] cassandra git commit: Merge branch cassandra-2.1 into cassandra-2.2

Posted by bl...@apache.org.
Merge branch cassandra-2.1 into cassandra-2.2


Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/9583b6b3
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/9583b6b3
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/9583b6b3

Branch: refs/heads/trunk
Commit: 9583b6b312866a4861df1a795329695b3fc09328
Parents: 88b3cfc 527d189
Author: Benjamin Lerer <b....@gmail.com>
Authored: Tue Aug 16 15:25:42 2016 +0200
Committer: Benjamin Lerer <b....@gmail.com>
Committed: Tue Aug 16 15:26:26 2016 +0200

----------------------------------------------------------------------
 CHANGES.txt                                     |   1 +
 NEWS.txt                                        |  10 +
 .../AbstractPrimaryKeyRestrictions.java         |   5 +-
 .../cql3/restrictions/AbstractRestriction.java  |  10 +-
 .../ForwardingPrimaryKeyRestrictions.java       |  31 +-
 .../restrictions/MultiColumnRestriction.java    |  48 +-
 .../restrictions/PrimaryKeyRestrictionSet.java  |  36 +-
 .../restrictions/PrimaryKeyRestrictions.java    |   9 +-
 .../cql3/restrictions/Restriction.java          |  16 +-
 .../ReversedPrimaryKeyRestrictions.java         |   9 +-
 .../restrictions/SingleColumnRestriction.java   |  57 +-
 .../restrictions/StatementRestrictions.java     |  23 +-
 .../cql3/restrictions/TokenFilter.java          |  36 +-
 .../cql3/restrictions/TokenRestriction.java     |  13 +-
 .../cql3/statements/ModificationStatement.java  |   4 +-
 .../cql3/statements/SelectStatement.java        |  11 +-
 .../cql3/statements/UpdateStatement.java        |   2 +-
 .../cassandra/db/compaction/Scrubber.java       | 159 +++-
 .../cassandra/db/marshal/ReversedType.java      |  10 -
 .../PrimaryKeyRestrictionSetTest.java           | 372 ++++-----
 .../validation/entities/SecondaryIndexTest.java | 142 ++++
 .../cql3/validation/operations/DeleteTest.java  |  42 +
 .../operations/SelectOrderByTest.java           | 758 +++++++++++--------
 .../cql3/validation/operations/SelectTest.java  | 372 ++++++++-
 .../cassandra/db/marshal/ReversedTypeTest.java  |   4 +-
 25 files changed, 1540 insertions(+), 640 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/9583b6b3/CHANGES.txt
----------------------------------------------------------------------
diff --cc CHANGES.txt
index 394598a,dee669a..c421398
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@@ -1,24 -1,5 +1,25 @@@
 -2.1.16
 - * Fix queries with empty ByteBuffer values in clustering column restrictions (CASSANDRA-12127) 
 +2.2.8
 + * Only set broadcast_rpc_address on Ec2MultiRegionSnitch if it's not set (CASSANDRA-11357)
 + * Update StorageProxy range metrics for timeouts, failures and unavailables (CASSANDRA-9507)
 + * Add Sigar to classes included in clientutil.jar (CASSANDRA-11635)
 + * Add decay to histograms and timers used for metrics (CASSANDRA-11752)
 + * Fix hanging stream session (CASSANDRA-10992)
 + * Add byteman support for testing (CASSANDRA-12377)
 + * Fix INSERT JSON, fromJson() support of smallint, tinyint types (CASSANDRA-12371)
 + * Restore JVM metric export for metric reporters (CASSANDRA-12312)
 + * Release sstables of failed stream sessions only when outgoing transfers are finished (CASSANDRA-11345)
 + * Revert CASSANDRA-11427 (CASSANDRA-12351)
 + * Wait for tracing events before returning response and query at same consistency level client side (CASSANDRA-11465)
 + * cqlsh copyutil should get host metadata by connected address (CASSANDRA-11979)
 + * Fixed cqlshlib.test.remove_test_db (CASSANDRA-12214)
 + * Synchronize ThriftServer::stop() (CASSANDRA-12105)
 + * Use dedicated thread for JMX notifications (CASSANDRA-12146)
 + * NPE when trying to remove purgable tombstones from result (CASSANDRA-12143)
 + * Improve streaming synchronization and fault tolerance (CASSANDRA-11414)
 + * MemoryUtil.getShort() should return an unsigned short also for architectures not supporting unaligned memory accesses (CASSANDRA-11973)
 + * Don't write shadowed range tombstone (CASSANDRA-12030)
 +Merged from 2.1:
++ * 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/9583b6b3/NEWS.txt
----------------------------------------------------------------------
diff --cc NEWS.txt
index f0712eb,6a70adc..0a3ab36
--- a/NEWS.txt
+++ b/NEWS.txt
@@@ -13,13 -13,18 +13,23 @@@ restore snapshots created with the prev
  'sstableloader' tool. You can upgrade the file format of your snapshots
  using the provided 'sstableupgrade' tool.
  
 -2.1.16
 -======
++2.2.8
++=====
+ 
+ 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.
++      their ordering. See CASSANDRA-12127 for more details.
+ 
 -2.1.15
 -======
 +2.2.7
 +=====
 +
 +New features
 +------------
 +    - JSON timestamps are now in UTC and contain the timezone information, see
 +      CASSANDRA-11137 for more details.
  
  Upgrading
  ---------

http://git-wip-us.apache.org/repos/asf/cassandra/blob/9583b6b3/src/java/org/apache/cassandra/cql3/restrictions/AbstractPrimaryKeyRestrictions.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/cql3/restrictions/AbstractPrimaryKeyRestrictions.java
index 2eaa386,0000000..51c3e26
mode 100644,000000..100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/AbstractPrimaryKeyRestrictions.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/AbstractPrimaryKeyRestrictions.java
@@@ -1,60 -1,0 +1,61 @@@
 +/*
 + * 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.restrictions;
 +
 +import java.nio.ByteBuffer;
 +import java.util.List;
 +
++import org.apache.cassandra.config.CFMetaData;
 +import org.apache.cassandra.cql3.QueryOptions;
 +import org.apache.cassandra.cql3.statements.Bound;
 +import org.apache.cassandra.db.composites.CType;
 +import org.apache.cassandra.exceptions.InvalidRequestException;
 +
 +/**
 + * Base class for <code>PrimaryKeyRestrictions</code>.
 + */
 +abstract class AbstractPrimaryKeyRestrictions extends AbstractRestriction implements PrimaryKeyRestrictions
 +{
 +    /**
 +     * The composite type.
 +     */
 +    protected final CType ctype;
 +
 +    public AbstractPrimaryKeyRestrictions(CType ctype)
 +    {
 +        this.ctype = ctype;
 +    }
 +
 +    @Override
-     public List<ByteBuffer> bounds(Bound b, QueryOptions options) throws InvalidRequestException
++    public List<ByteBuffer> bounds(CFMetaData cfm, Bound b, QueryOptions options) throws InvalidRequestException
 +    {
-         return values(options);
++        return values(cfm, options);
 +    }
 +
 +    @Override
 +    public final boolean isEmpty()
 +    {
 +        return getColumnDefs().isEmpty();
 +    }
 +
 +    @Override
 +    public final int size()
 +    {
 +        return getColumnDefs().size();
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/9583b6b3/src/java/org/apache/cassandra/cql3/restrictions/AbstractRestriction.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/cql3/restrictions/AbstractRestriction.java
index 385bfbc,0000000..4093780
mode 100644,000000..100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/AbstractRestriction.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/AbstractRestriction.java
@@@ -1,113 -1,0 +1,119 @@@
 +/*
 + * 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.restrictions;
 +
 +import java.nio.ByteBuffer;
 +
++import org.apache.cassandra.config.CFMetaData;
 +import org.apache.cassandra.config.ColumnDefinition;
 +
 +import org.apache.cassandra.cql3.ColumnSpecification;
 +import org.apache.cassandra.cql3.QueryOptions;
 +import org.apache.cassandra.cql3.statements.Bound;
 +import org.apache.cassandra.db.composites.CompositesBuilder;
 +import org.apache.cassandra.exceptions.InvalidRequestException;
 +import static org.apache.cassandra.cql3.statements.RequestValidations.checkBindValueSet;
 +import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse;
 +import static org.apache.cassandra.cql3.statements.RequestValidations.checkNotNull;
 +
 +/**
 + * Base class for <code>Restriction</code>s
 + */
 +abstract class AbstractRestriction  implements Restriction
 +{
 +    @Override
 +    public  boolean isOnToken()
 +    {
 +        return false;
 +    }
 +
 +    @Override
 +    public boolean isMultiColumn()
 +    {
 +        return false;
 +    }
 +
 +    @Override
 +    public boolean isSlice()
 +    {
 +        return false;
 +    }
 +
 +    @Override
 +    public boolean isEQ()
 +    {
 +        return false;
 +    }
 +
 +    @Override
 +    public boolean isIN()
 +    {
 +        return false;
 +    }
 +
 +    @Override
 +    public boolean isContains()
 +    {
 +        return false;
 +    }
 +
 +    @Override
 +    public boolean hasBound(Bound b)
 +    {
 +        return true;
 +    }
 +
 +    @Override
-     public CompositesBuilder appendBoundTo(CompositesBuilder builder, Bound bound, QueryOptions options)
++    public CompositesBuilder appendBoundTo(CFMetaData cfm, CompositesBuilder builder, Bound bound, QueryOptions options)
 +    {
-         return appendTo(builder, options);
++        return appendTo(cfm, builder, options);
 +    }
 +
 +    @Override
 +    public boolean isInclusive(Bound b)
 +    {
 +        return true;
++    }
++
++    public boolean isNotReturningAnyRows(CFMetaData cfm, QueryOptions options)
++    {
++        return false;
 +    }
 +
 +    protected static ByteBuffer validateIndexedValue(ColumnSpecification columnSpec,
 +                                                     ByteBuffer value)
 +                                                     throws InvalidRequestException
 +    {
 +        checkNotNull(value, "Unsupported null value for column %s", columnSpec.name);
 +        checkBindValueSet(value, "Unsupported unset value for column %s", columnSpec.name);
 +        checkFalse(value.remaining() > 0xFFFF, "Index expression values may not be larger than 64K");
 +        return value;
 +    }
 +
 +    /**
 +     * Reverses the specified bound if the column type is a reversed one.
 +     *
 +     * @param columnDefinition the column definition
 +     * @param bound the bound
 +     * @return the bound reversed if the column type was a reversed one or the original bound
 +     */
 +    protected static Bound reverseBoundIfNeeded(ColumnDefinition columnDefinition, Bound bound)
 +    {
 +        return columnDefinition.isReversedType() ? bound.reverse() : bound;
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/9583b6b3/src/java/org/apache/cassandra/cql3/restrictions/ForwardingPrimaryKeyRestrictions.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/cql3/restrictions/ForwardingPrimaryKeyRestrictions.java
index 71855a0,0000000..76d0233
mode 100644,000000..100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/ForwardingPrimaryKeyRestrictions.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/ForwardingPrimaryKeyRestrictions.java
@@@ -1,185 -1,0 +1,192 @@@
 +/*
 + * 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.restrictions;
 +
 +import java.nio.ByteBuffer;
 +import java.util.List;
 +
++import org.apache.cassandra.config.CFMetaData;
 +import org.apache.cassandra.config.ColumnDefinition;
 +import org.apache.cassandra.cql3.QueryOptions;
 +import org.apache.cassandra.cql3.functions.Function;
 +import org.apache.cassandra.cql3.statements.Bound;
 +import org.apache.cassandra.db.IndexExpression;
 +import org.apache.cassandra.db.composites.Composite;
 +import org.apache.cassandra.db.composites.CompositesBuilder;
 +import org.apache.cassandra.db.index.SecondaryIndexManager;
 +import org.apache.cassandra.exceptions.InvalidRequestException;
 +
 +/**
 + * A <code>PrimaryKeyRestrictions</code> which forwards all its method calls to another 
 + * <code>PrimaryKeyRestrictions</code>. Subclasses should override one or more methods to modify the behavior 
 + * of the backing <code>PrimaryKeyRestrictions</code> as desired per the decorator pattern. 
 + */
 +abstract class ForwardingPrimaryKeyRestrictions implements PrimaryKeyRestrictions
 +{
 +    /**
 +     * Returns the backing delegate instance that methods are forwarded to.
 +     * @return the backing delegate instance that methods are forwarded to.
 +     */
 +    protected abstract PrimaryKeyRestrictions getDelegate();
 +
 +    @Override
 +    public Iterable<Function> getFunctions()
 +    {
 +        return getDelegate().getFunctions();
 +    }
 +
 +    @Override
 +    public List<ColumnDefinition> getColumnDefs()
 +    {
 +        return getDelegate().getColumnDefs();
 +    }
 +
 +    @Override
 +    public ColumnDefinition getFirstColumn()
 +    {
 +        return getDelegate().getFirstColumn();
 +    }
 +
 +    @Override
 +    public ColumnDefinition getLastColumn()
 +    {
 +        return getDelegate().getLastColumn();
 +    }
 +
 +    @Override
 +    public PrimaryKeyRestrictions mergeWith(Restriction restriction) throws InvalidRequestException
 +    {
 +        return getDelegate().mergeWith(restriction);
 +    }
 +
 +    @Override
 +    public boolean hasSupportingIndex(SecondaryIndexManager secondaryIndexManager)
 +    {
 +        return getDelegate().hasSupportingIndex(secondaryIndexManager);
 +    }
 +
 +    @Override
-     public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException
++    public List<ByteBuffer> values(CFMetaData cfm, QueryOptions options) throws InvalidRequestException
 +    {
-         return getDelegate().values(options);
++        return getDelegate().values(cfm, options);
 +    }
 +
 +    @Override
-     public CompositesBuilder appendTo(CompositesBuilder builder, QueryOptions options)
++    public CompositesBuilder appendTo(CFMetaData cfm, CompositesBuilder builder, QueryOptions options)
 +    {
-         return getDelegate().appendTo(builder, options);
++        return getDelegate().appendTo(cfm, builder, options);
 +    }
 +
 +    @Override
-     public List<Composite> valuesAsComposites(QueryOptions options) throws InvalidRequestException
++    public List<Composite> valuesAsComposites(CFMetaData cfm, QueryOptions options) throws InvalidRequestException
 +    {
-         return getDelegate().valuesAsComposites(options);
++        return getDelegate().valuesAsComposites(cfm, options);
 +    }
 +
 +    @Override
-     public List<ByteBuffer> bounds(Bound bound, QueryOptions options) throws InvalidRequestException
++    public List<ByteBuffer> bounds(CFMetaData cfm, Bound bound, QueryOptions options) throws InvalidRequestException
 +    {
-         return getDelegate().bounds(bound, options);
++        return getDelegate().bounds(cfm, bound, options);
 +    }
 +
 +    @Override
-     public List<Composite> boundsAsComposites(Bound bound, QueryOptions options) throws InvalidRequestException
++    public List<Composite> boundsAsComposites(CFMetaData cfm, Bound bound, QueryOptions options) throws InvalidRequestException
 +    {
-         return getDelegate().boundsAsComposites(bound, options);
++        return getDelegate().boundsAsComposites(cfm, bound, options);
 +    }
 +
 +    @Override
-     public CompositesBuilder appendBoundTo(CompositesBuilder builder, Bound bound, QueryOptions options)
++    public CompositesBuilder appendBoundTo(CFMetaData cfm, CompositesBuilder builder, Bound bound, QueryOptions options)
 +    {
-         return getDelegate().appendBoundTo(builder, bound, options);
++        return getDelegate().appendBoundTo(cfm, builder, bound, options);
 +    }
 +
 +    @Override
 +    public boolean isInclusive(Bound bound)
 +    {
 +        return getDelegate().isInclusive(bound.reverse());
 +    }
 +
 +    @Override
 +    public boolean isEmpty()
 +    {
 +        return getDelegate().isEmpty();
 +    }
 +
 +    @Override
 +    public int size()
 +    {
 +        return getDelegate().size();
 +    }
 +
 +    @Override
 +    public boolean isOnToken()
 +    {
 +        return getDelegate().isOnToken();
 +    }
 +
 +    @Override
 +    public boolean isSlice()
 +    {
 +        return getDelegate().isSlice();
 +    }
 +
 +    @Override
 +    public boolean isEQ()
 +    {
 +        return getDelegate().isEQ();
 +    }
 +
 +    @Override
 +    public boolean isIN()
 +    {
 +        return getDelegate().isIN();
 +    }
 +
 +    @Override
 +    public boolean isContains()
 +    {
 +        return getDelegate().isContains();
 +    }
 +
 +    @Override
 +    public boolean isMultiColumn()
 +    {
 +        return getDelegate().isMultiColumn();
 +    }
 +
 +    @Override
 +    public boolean hasBound(Bound b)
 +    {
 +        return getDelegate().hasBound(b);
 +    }
 +
 +    @Override
 +    public void addIndexExpressionTo(List<IndexExpression> expressions,
 +                                     SecondaryIndexManager indexManager,
 +                                     QueryOptions options) throws InvalidRequestException
 +    {
 +        getDelegate().addIndexExpressionTo(expressions, indexManager, options);
 +    }
++
++    @Override
++    public boolean isNotReturningAnyRows(CFMetaData cfm, QueryOptions options)
++    {
++        return getDelegate().isNotReturningAnyRows(cfm, options);
++    }
 +}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/9583b6b3/src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java
index 51e2ce4,0000000..44f25ec
mode 100644,000000..100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java
@@@ -1,515 -1,0 +1,553 @@@
 +/*
 + * 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.restrictions;
 +
 +import java.nio.ByteBuffer;
 +import java.util.*;
 +
++import org.apache.cassandra.config.CFMetaData;
 +import org.apache.cassandra.config.ColumnDefinition;
 +import org.apache.cassandra.cql3.*;
 +import org.apache.cassandra.cql3.Term.Terminal;
 +import org.apache.cassandra.cql3.functions.Function;
 +import org.apache.cassandra.cql3.statements.Bound;
 +import org.apache.cassandra.db.IndexExpression;
 +import org.apache.cassandra.db.composites.CompositesBuilder;
 +import org.apache.cassandra.db.index.SecondaryIndex;
 +import org.apache.cassandra.db.index.SecondaryIndexManager;
 +import org.apache.cassandra.exceptions.InvalidRequestException;
 +import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse;
 +import static org.apache.cassandra.cql3.statements.RequestValidations.checkNotNull;
 +import static org.apache.cassandra.cql3.statements.RequestValidations.checkTrue;
 +import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest;
 +
 +public abstract class MultiColumnRestriction extends AbstractRestriction
 +{
 +    /**
 +     * The columns to which the restriction apply.
 +     */
 +    protected final List<ColumnDefinition> columnDefs;
 +
 +    public MultiColumnRestriction(List<ColumnDefinition> columnDefs)
 +    {
 +        this.columnDefs = columnDefs;
 +    }
 +
 +    @Override
 +    public boolean isMultiColumn()
 +    {
 +        return true;
 +    }
 +
 +    @Override
 +    public ColumnDefinition getFirstColumn()
 +    {
 +        return columnDefs.get(0);
 +    }
 +
 +    @Override
 +    public ColumnDefinition getLastColumn()
 +    {
 +        return columnDefs.get(columnDefs.size() - 1);
 +    }
 +
 +    @Override
 +    public List<ColumnDefinition> getColumnDefs()
 +    {
 +        return columnDefs;
 +    }
 +
 +    @Override
 +    public final Restriction mergeWith(Restriction otherRestriction) throws InvalidRequestException
 +    {
 +        // We want to allow query like: (b,c) > (?, ?) AND b < ?
 +        if (!otherRestriction.isMultiColumn()
 +                && ((SingleColumnRestriction) otherRestriction).canBeConvertedToMultiColumnRestriction())
 +        {
 +            return doMergeWith(((SingleColumnRestriction) otherRestriction).toMultiColumnRestriction());
 +        }
 +
 +        return doMergeWith(otherRestriction);
 +    }
 +
 +    protected abstract Restriction doMergeWith(Restriction otherRestriction) throws InvalidRequestException;
 +
 +    /**
 +     * Returns the names of the columns that are specified within this <code>Restrictions</code> and the other one
 +     * as a comma separated <code>String</code>.
 +     *
 +     * @param otherRestriction the other restrictions
 +     * @return the names of the columns that are specified within this <code>Restrictions</code> and the other one
 +     * as a comma separated <code>String</code>.
 +     */
 +    protected final String getColumnsInCommons(Restriction otherRestriction)
 +    {
 +        Set<ColumnDefinition> commons = new HashSet<>(getColumnDefs());
 +        commons.retainAll(otherRestriction.getColumnDefs());
 +        StringBuilder builder = new StringBuilder();
 +        for (ColumnDefinition columnDefinition : commons)
 +        {
 +            if (builder.length() != 0)
 +                builder.append(" ,");
 +            builder.append(columnDefinition.name);
 +        }
 +        return builder.toString();
 +    }
 +
 +    @Override
 +    public final boolean hasSupportingIndex(SecondaryIndexManager indexManager)
 +    {
 +        for (ColumnDefinition columnDef : columnDefs)
 +        {
 +            SecondaryIndex index = indexManager.getIndexForColumn(columnDef.name.bytes);
 +            if (index != null && isSupportedBy(index))
 +                return true;
 +        }
 +        return false;
 +    }
 +
 +    /**
 +     * Check if this type of restriction is supported for by the specified index.
 +     * @param index the Secondary index
 +     *
 +     * @return <code>true</code> this type of restriction is supported by the specified index,
 +     * <code>false</code> otherwise.
 +     */
 +    protected abstract boolean isSupportedBy(SecondaryIndex index);
 +
 +    public static class EQ  extends MultiColumnRestriction
 +    {
 +        protected final Term value;
 +
 +        public EQ(List<ColumnDefinition> columnDefs, Term value)
 +        {
 +            super(columnDefs);
 +            this.value = value;
 +        }
 +
 +        @Override
 +        public Iterable<Function> getFunctions()
 +        {
 +            return value.getFunctions();
 +        }
 +
 +        @Override
 +        public String toString()
 +        {
 +            return String.format("EQ(%s)", value);
 +        }
 +
 +        @Override
 +        public Restriction doMergeWith(Restriction otherRestriction) throws InvalidRequestException
 +        {
 +            throw invalidRequest("%s cannot be restricted by more than one relation if it includes an Equal",
 +                                 getColumnsInCommons(otherRestriction));
 +        }
 +
 +        @Override
 +        protected boolean isSupportedBy(SecondaryIndex index)
 +        {
 +            return index.supportsOperator(Operator.EQ);
 +        }
 +
 +        @Override
-         public CompositesBuilder appendTo(CompositesBuilder builder, QueryOptions options)
++        public CompositesBuilder appendTo(CFMetaData cfm, CompositesBuilder builder, QueryOptions options)
 +        {
 +            Tuples.Value t = ((Tuples.Value) value.bind(options));
 +            List<ByteBuffer> values = t.getElements();
 +            for (int i = 0, m = values.size(); i < m; i++)
 +            {
 +                builder.addElementToAll(values.get(i));
 +                checkFalse(builder.containsNull(), "Invalid null value for column %s", columnDefs.get(i).name);
 +            }
 +            return builder;
 +        }
 +
 +        @Override
 +        public final void addIndexExpressionTo(List<IndexExpression> expressions,
 +                                               SecondaryIndexManager indexManager,
 +                                               QueryOptions options) throws InvalidRequestException
 +        {
 +            Tuples.Value t = ((Tuples.Value) value.bind(options));
 +            List<ByteBuffer> values = t.getElements();
 +
 +            for (int i = 0, m = columnDefs.size(); i < m; i++)
 +            {
 +                ColumnDefinition columnDef = columnDefs.get(i);
 +                ByteBuffer component = validateIndexedValue(columnDef, values.get(i));
 +                expressions.add(new IndexExpression(columnDef.name.bytes, Operator.EQ, component));
 +            }
 +        }
++
++        @Override
++        public boolean isNotReturningAnyRows(CFMetaData cfm, QueryOptions options)
++        {
++            // 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 results.
++            return !cfm.comparator.isCompound()
++                    && !((Tuples.Value) value.bind(options)).getElements().get(0).hasRemaining();
++        }
 +    }
 +
 +    public abstract static class IN extends MultiColumnRestriction
 +    {
 +        /**
 +         * {@inheritDoc}
 +         */
 +        @Override
-         public CompositesBuilder appendTo(CompositesBuilder builder, QueryOptions options)
++        public CompositesBuilder appendTo(CFMetaData cfm, CompositesBuilder builder, QueryOptions options)
 +        {
-             List<List<ByteBuffer>> splitInValues = splitValues(options);
++            List<List<ByteBuffer>> splitInValues = filterValuesIfNeeded(cfm, splitValues(options));
 +            builder.addAllElementsToAll(splitInValues);
 +
 +            if (builder.containsNull())
 +                throw invalidRequest("Invalid null value in condition for columns: %s", ColumnDefinition.toIdentifiers(columnDefs));
 +            return builder;
 +        }
 +
++        private List<List<ByteBuffer>> filterValuesIfNeeded(CFMetaData cfm, List<List<ByteBuffer>> splitInValues)
++        {
++            if (cfm.comparator.isCompound())
++                return splitInValues;
++
++            // 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.
++
++            // As some List implementations do not support remove, we copy the list to be on the safe side.
++            List<List<ByteBuffer>> filteredValues = new ArrayList<>(splitInValues.size());
++            for (List<ByteBuffer> values : splitInValues)
++            {
++                if (values.get(0).hasRemaining())
++                    filteredValues.add(values);
++            }
++            return filteredValues;
++        }
++
 +        public IN(List<ColumnDefinition> columnDefs)
 +        {
 +            super(columnDefs);
 +        }
 +
 +        @Override
 +        public boolean isIN()
 +        {
 +            return true;
 +        }
 +
 +        @Override
 +        public Restriction doMergeWith(Restriction otherRestriction) throws InvalidRequestException
 +        {
 +            throw invalidRequest("%s cannot be restricted by more than one relation if it includes a IN",
 +                                 getColumnsInCommons(otherRestriction));
 +        }
 +
 +        @Override
 +        protected boolean isSupportedBy(SecondaryIndex index)
 +        {
 +            return index.supportsOperator(Operator.IN);
 +        }
 +
 +        @Override
 +        public final void addIndexExpressionTo(List<IndexExpression> expressions,
 +                                               SecondaryIndexManager indexManager,
 +                                               QueryOptions options) throws InvalidRequestException
 +        {
 +            List<List<ByteBuffer>> splitInValues = splitValues(options);
 +            checkTrue(splitInValues.size() == 1, "IN restrictions are not supported on indexed columns");
 +            List<ByteBuffer> values = splitInValues.get(0);
 +
 +            for (int i = 0, m = columnDefs.size(); i < m; i++)
 +            {
 +                ColumnDefinition columnDef = columnDefs.get(i);
 +                ByteBuffer component = validateIndexedValue(columnDef, values.get(i));
 +                expressions.add(new IndexExpression(columnDef.name.bytes, Operator.EQ, component));
 +            }
 +        }
 +
 +        protected abstract List<List<ByteBuffer>> splitValues(QueryOptions options) throws InvalidRequestException;
 +    }
 +
 +    /**
 +     * An IN restriction that has a set of terms for in values.
 +     * For example: "SELECT ... WHERE (a, b, c) IN ((1, 2, 3), (4, 5, 6))" or "WHERE (a, b, c) IN (?, ?)"
 +     */
 +    public static class InWithValues extends MultiColumnRestriction.IN
 +    {
 +        protected final List<Term> values;
 +
 +        public InWithValues(List<ColumnDefinition> columnDefs, List<Term> values)
 +        {
 +            super(columnDefs);
 +            this.values = values;
 +        }
 +
 +        @Override
 +        public Iterable<Function> getFunctions()
 +        {
 +            return Terms.getFunctions(values);
 +        }
 +
 +        @Override
 +        public String toString()
 +        {
 +            return String.format("IN(%s)", values);
 +        }
 +
 +        @Override
 +        protected List<List<ByteBuffer>> splitValues(QueryOptions options) throws InvalidRequestException
 +        {
 +            List<List<ByteBuffer>> buffers = new ArrayList<>(values.size());
 +            for (Term value : values)
 +            {
 +                Term.MultiItemTerminal term = (Term.MultiItemTerminal) value.bind(options);
 +                buffers.add(term.getElements());
 +            }
 +            return buffers;
 +        }
 +    }
 +
 +    /**
 +     * An IN restriction that uses a single marker for a set of IN values that are tuples.
 +     * For example: "SELECT ... WHERE (a, b, c) IN ?"
 +     */
 +    public static class InWithMarker extends MultiColumnRestriction.IN
 +    {
 +        protected final AbstractMarker marker;
 +
 +        public InWithMarker(List<ColumnDefinition> columnDefs, AbstractMarker marker)
 +        {
 +            super(columnDefs);
 +            this.marker = marker;
 +        }
 +
 +        @Override
 +        public Iterable<Function> getFunctions()
 +        {
 +            return Collections.emptySet();
 +        }
 +
 +        @Override
 +        public String toString()
 +        {
 +            return "IN ?";
 +        }
 +
 +        @Override
 +        protected List<List<ByteBuffer>> splitValues(QueryOptions options) throws InvalidRequestException
 +        {
 +            Tuples.InMarker inMarker = (Tuples.InMarker) marker;
 +            Tuples.InValue inValue = inMarker.bind(options);
 +            checkNotNull(inValue, "Invalid null value for IN restriction");
 +            return inValue.getSplitValues();
 +        }
 +    }
 +
 +    public static class Slice extends MultiColumnRestriction
 +    {
 +        private final TermSlice slice;
 +
 +        public Slice(List<ColumnDefinition> columnDefs, Bound bound, boolean inclusive, Term term)
 +        {
 +            this(columnDefs, TermSlice.newInstance(bound, inclusive, term));
 +        }
 +
 +        Slice(List<ColumnDefinition> columnDefs, TermSlice slice)
 +        {
 +            super(columnDefs);
 +            this.slice = slice;
 +        }
 +
 +        @Override
 +        public boolean isSlice()
 +        {
 +            return true;
 +        }
 +
 +        @Override
-         public CompositesBuilder appendTo(CompositesBuilder builder, QueryOptions options)
++        public CompositesBuilder appendTo(CFMetaData cfm, CompositesBuilder builder, QueryOptions options)
 +        {
 +            throw new UnsupportedOperationException();
 +        }
 +
 +        @Override
-         public CompositesBuilder appendBoundTo(CompositesBuilder builder, Bound bound, QueryOptions options)
++        public CompositesBuilder appendBoundTo(CFMetaData cfm, CompositesBuilder builder, Bound bound, QueryOptions options)
 +        {
 +            boolean reversed = getFirstColumn().isReversedType();
 +
 +            EnumMap<Bound, List<ByteBuffer>> componentBounds = new EnumMap<Bound, List<ByteBuffer>>(Bound.class);
 +            componentBounds.put(Bound.START, componentBounds(Bound.START, options));
 +            componentBounds.put(Bound.END, componentBounds(Bound.END, options));
 +
 +            List<List<ByteBuffer>> toAdd = new ArrayList<>();
 +            List<ByteBuffer> values = new ArrayList<>();
 +
 +            for (int i = 0, m = columnDefs.size(); i < m; i++)
 +            {
 +                ColumnDefinition column = columnDefs.get(i);
 +                Bound b = reverseBoundIfNeeded(column, bound);
 +
 +                // For mixed order columns, we need to create additional slices when 2 columns are in reverse order
 +                if (reversed != column.isReversedType())
 +                {
 +                    reversed = column.isReversedType();
 +                    // As we are switching direction we need to add the current composite
 +                    toAdd.add(values);
 +
 +                    // The new bound side has no value for this component.  just stop
 +                    if (!hasComponent(b, i, componentBounds))
 +                        continue;
 +
 +                    // The other side has still some components. We need to end the slice that we have just open.
 +                    if (hasComponent(b.reverse(), i, componentBounds))
 +                        toAdd.add(values);
 +
 +                    // We need to rebuild where we are in this bound side
 +                    values = new ArrayList<ByteBuffer>();
 +
 +                    List<ByteBuffer> vals = componentBounds.get(b);
 +
 +                    int n = Math.min(i, vals.size());
 +                    for (int j = 0; j < n; j++)
 +                    {
 +                        ByteBuffer v = checkNotNull(vals.get(j),
 +                                                    "Invalid null value in condition for column %s",
 +                                                    columnDefs.get(j).name);
 +                        values.add(v);
 +                    }
 +                }
 +
 +                if (!hasComponent(b, i, componentBounds))
 +                    continue;
 +
 +                ByteBuffer v = checkNotNull(componentBounds.get(b).get(i), "Invalid null value in condition for column %s", columnDefs.get(i).name);
 +                values.add(v);
 +            }
 +            toAdd.add(values);
 +
 +            if (bound.isEnd())
 +                Collections.reverse(toAdd);
 +
 +            return builder.addAllElementsToAll(toAdd);
 +        }
 +
 +        @Override
 +        protected boolean isSupportedBy(SecondaryIndex index)
 +        {
 +            return slice.isSupportedBy(index);
 +        }
 +
 +        @Override
 +        public boolean hasBound(Bound bound)
 +        {
 +            return slice.hasBound(bound);
 +        }
 +
 +        @Override
 +        public Iterable<Function> getFunctions()
 +        {
 +            return slice.getFunctions();
 +        }
 +
 +        @Override
 +        public boolean isInclusive(Bound bound)
 +        {
 +            return slice.isInclusive(bound);
 +        }
 +
 +        @Override
 +        public Restriction doMergeWith(Restriction otherRestriction) throws InvalidRequestException
 +        {
 +            checkTrue(otherRestriction.isSlice(),
 +                      "Column \"%s\" cannot be restricted by both an equality and an inequality relation",
 +                      getColumnsInCommons(otherRestriction));
 +
 +            if (!getFirstColumn().equals(otherRestriction.getFirstColumn()))
 +            {
 +                ColumnDefinition column = getFirstColumn().position() > otherRestriction.getFirstColumn().position()
 +                        ? getFirstColumn() : otherRestriction.getFirstColumn();
 +
 +                throw invalidRequest("Column \"%s\" cannot be restricted by two inequalities not starting with the same column",
 +                                     column.name);
 +            }
 +
 +            checkFalse(hasBound(Bound.START) && otherRestriction.hasBound(Bound.START),
 +                       "More than one restriction was found for the start bound on %s",
 +                       getColumnsInCommons(otherRestriction));
 +            checkFalse(hasBound(Bound.END) && otherRestriction.hasBound(Bound.END),
 +                       "More than one restriction was found for the end bound on %s",
 +                       getColumnsInCommons(otherRestriction));
 +
 +            Slice otherSlice = (Slice) otherRestriction;
 +            List<ColumnDefinition> newColumnDefs = columnDefs.size() >= otherSlice.columnDefs.size() ?  columnDefs : otherSlice.columnDefs;
 +
 +            return new Slice(newColumnDefs, slice.merge(otherSlice.slice));
 +        }
 +
 +        @Override
 +        public final void addIndexExpressionTo(List<IndexExpression> expressions,
 +                                               SecondaryIndexManager indexManager,
 +                                               QueryOptions options) throws InvalidRequestException
 +        {
 +            throw invalidRequest("Multi-column slice restrictions cannot be used for filtering.");
 +        }
 +
 +        @Override
 +        public String toString()
 +        {
 +            return "SLICE" + slice;
 +        }
 +
 +        /**
 +         * Similar to bounds(), but returns one ByteBuffer per-component in the bound instead of a single
 +         * ByteBuffer to represent the entire bound.
 +         * @param b the bound type
 +         * @param options the query options
 +         * @return one ByteBuffer per-component in the bound
 +         * @throws InvalidRequestException if the components cannot be retrieved
 +         */
 +        private List<ByteBuffer> componentBounds(Bound b, QueryOptions options) throws InvalidRequestException
 +        {
 +            if (!slice.hasBound(b))
 +                return Collections.emptyList();
 +
 +            Terminal terminal = slice.bound(b).bind(options);
 +
 +            if (terminal instanceof Tuples.Value)
 +            {
 +                return ((Tuples.Value) terminal).getElements();
 +            }
 +
 +            return Collections.singletonList(terminal.get(options.getProtocolVersion()));
 +        }
 +
++        @Override
++        public boolean isNotReturningAnyRows(CFMetaData cfm, QueryOptions options)
++        {
++            // Dense non-compound tables do not accept empty ByteBuffers. By consequence, we know that
++            // any query with a slice restriction with an empty value for the END bound will not return any results.
++            return !cfm.comparator.isCompound()
++                    && hasBound(Bound.END)
++                    && !componentBounds(Bound.END, options).get(0).hasRemaining();
++        }
++
 +        private boolean hasComponent(Bound b, int index, EnumMap<Bound, List<ByteBuffer>> componentBounds)
 +        {
 +            return componentBounds.get(b).size() > index;
 +        }
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/9583b6b3/src/java/org/apache/cassandra/cql3/restrictions/PrimaryKeyRestrictionSet.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/cql3/restrictions/PrimaryKeyRestrictionSet.java
index e1cbc29,0000000..2549bdf
mode 100644,000000..100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/PrimaryKeyRestrictionSet.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/PrimaryKeyRestrictionSet.java
@@@ -1,405 -1,0 +1,417 @@@
 +/*
 + * 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.restrictions;
 +
 +import java.nio.ByteBuffer;
 +import java.util.*;
 +
 +import com.google.common.collect.Lists;
 +
++import org.apache.cassandra.config.CFMetaData;
 +import org.apache.cassandra.config.ColumnDefinition;
 +import org.apache.cassandra.cql3.QueryOptions;
 +import org.apache.cassandra.cql3.functions.Function;
 +import org.apache.cassandra.cql3.statements.Bound;
 +import org.apache.cassandra.db.IndexExpression;
 +import org.apache.cassandra.db.composites.*;
 +import org.apache.cassandra.db.composites.Composite.EOC;
 +import org.apache.cassandra.db.index.SecondaryIndexManager;
 +import org.apache.cassandra.exceptions.InvalidRequestException;
 +
 +/**
 + * A set of single column restrictions on a primary key part (partition key or clustering key).
 + */
 +final class PrimaryKeyRestrictionSet extends AbstractPrimaryKeyRestrictions implements Iterable<Restriction>
 +{
 +    /**
 +     * The restrictions.
 +     */
 +    private final RestrictionSet restrictions;
 +
 +    /**
 +     * <code>true</code> if the restrictions are corresponding to an EQ, <code>false</code> otherwise.
 +     */
 +    private boolean eq;
 +
 +    /**
 +     * <code>true</code> if the restrictions are corresponding to an IN, <code>false</code> otherwise.
 +     */
 +    private boolean in;
 +
 +    /**
 +     * <code>true</code> if the restrictions are corresponding to a Slice, <code>false</code> otherwise.
 +     */
 +    private boolean slice;
 +
 +    /**
 +     * <code>true</code> if the restrictions are corresponding to a Contains, <code>false</code> otherwise.
 +     */
 +    private boolean contains;
 +
 +    public PrimaryKeyRestrictionSet(CType ctype)
 +    {
 +        super(ctype);
 +        this.restrictions = new RestrictionSet();
 +        this.eq = true;
 +    }
 +
 +    private PrimaryKeyRestrictionSet(PrimaryKeyRestrictionSet primaryKeyRestrictions,
 +                                     Restriction restriction) throws InvalidRequestException
 +    {
 +        super(primaryKeyRestrictions.ctype);
 +        this.restrictions = primaryKeyRestrictions.restrictions.addRestriction(restriction);
 +
 +        if (restriction.isSlice() || primaryKeyRestrictions.isSlice())
 +            this.slice = true;
 +        else if (restriction.isContains() || primaryKeyRestrictions.isContains())
 +            this.contains = true;
 +        else if (restriction.isIN() || primaryKeyRestrictions.isIN())
 +            this.in = true;
 +        else
 +            this.eq = true;
 +    }
 +
 +    @Override
 +    public boolean isSlice()
 +    {
 +        return slice;
 +    }
 +
 +    @Override
 +    public boolean isEQ()
 +    {
 +        return eq;
 +    }
 +
 +    @Override
 +    public boolean isIN()
 +    {
 +        return in;
 +    }
 +
 +    @Override
 +    public boolean isOnToken()
 +    {
 +        return false;
 +    }
 +
 +    @Override
 +    public boolean isContains()
 +    {
 +        return contains;
 +    }
 +
 +    @Override
 +    public boolean isMultiColumn()
 +    {
 +        return false;
 +    }
 +
 +    @Override
 +    public Iterable<Function> getFunctions()
 +    {
 +        return restrictions.getFunctions();
 +    }
 +
 +    @Override
 +    public PrimaryKeyRestrictions mergeWith(Restriction restriction) throws InvalidRequestException
 +    {
 +        if (restriction.isOnToken())
 +        {
 +            if (isEmpty())
 +                return (PrimaryKeyRestrictions) restriction;
 +
 +            return new TokenFilter(this, (TokenRestriction) restriction);
 +        }
 +
 +        return new PrimaryKeyRestrictionSet(this, restriction);
 +    }
 +
 +    @Override
-     public List<Composite> valuesAsComposites(QueryOptions options) throws InvalidRequestException
++    public List<Composite> valuesAsComposites(CFMetaData cfm, QueryOptions options) throws InvalidRequestException
 +    {
-         return filterAndSort(appendTo(new CompositesBuilder(ctype), options).build());
++        return filterAndSort(appendTo(cfm, new CompositesBuilder(ctype), options).build());
 +    }
 +
 +    @Override
-     public CompositesBuilder appendTo(CompositesBuilder builder, QueryOptions options)
++    public CompositesBuilder appendTo(CFMetaData cfm, CompositesBuilder builder, QueryOptions options)
 +    {
 +        for (Restriction r : restrictions)
 +        {
-             r.appendTo(builder, options);
++            r.appendTo(cfm, builder, options);
 +            if (builder.hasMissingElements())
 +                break;
 +        }
 +        return builder;
 +    }
 +
 +    @Override
-     public CompositesBuilder appendBoundTo(CompositesBuilder builder, Bound bound, QueryOptions options)
++    public CompositesBuilder appendBoundTo(CFMetaData cfm, CompositesBuilder builder, Bound bound, QueryOptions options)
 +    {
 +        throw new UnsupportedOperationException();
 +    }
 +
 +    @Override
-     public List<Composite> boundsAsComposites(Bound bound, QueryOptions options) throws InvalidRequestException
++    public List<Composite> boundsAsComposites(CFMetaData cfm, Bound bound, QueryOptions options) throws InvalidRequestException
 +    {
 +        CompositesBuilder builder = new CompositesBuilder(ctype);
 +        // The end-of-component of composite doesn't depend on whether the
 +        // component type is reversed or not (i.e. the ReversedType is applied
 +        // to the component comparator but not to the end-of-component itself),
 +        // it only depends on whether the slice is reversed
 +        int keyPosition = 0;
 +        for (Restriction r : restrictions)
 +        {
 +            ColumnDefinition def = r.getFirstColumn();
 +
 +            if (keyPosition != def.position() || r.isContains())
 +                break;
 +
 +            if (r.isSlice())
 +            {
-                 r.appendBoundTo(builder, bound, options);
++                r.appendBoundTo(cfm, builder, bound, options);
 +
 +                // Since CASSANDRA-7281, the composites might not end with the same components and it is possible
 +                // that one of the composites is an empty one. Unfortunatly, AbstractCType will always sort
 +                // Composites.EMPTY before all the other components due to its EOC, even if it is not the desired
 +                // behaviour in some cases. To avoid that problem the code will use normal composites for the empty
 +                // ones until the composites are properly sorted. They will then be replaced by Composites.EMPTY as
 +                // it is what is expected by the intra-node serialization.
 +                // It is clearly a hack but it does not make a lot of sense to refactor 2.2 for that as the problem is
 +                // already solved in 3.0.
 +                List<Composite> composites = filterAndSort(setEocs(r, bound, builder.build()));
 +                return Lists.transform(composites, new com.google.common.base.Function<Composite, Composite>()
 +                {
 +                    @Override
 +                    public Composite apply(Composite composite)
 +                    {
 +                        return composite.isEmpty() ? Composites.EMPTY: composite;
 +                    }
 +                });
 +            }
 +
-             r.appendBoundTo(builder, bound, options);
++            r.appendBoundTo(cfm, builder, bound, options);
 +
 +            if (builder.hasMissingElements())
 +                return Collections.emptyList();
 +
 +            keyPosition = r.getLastColumn().position() + 1;
 +        }
 +        // Means no relation at all or everything was an equal
 +        // Note: if the builder is "full", there is no need to use the end-of-component bit. For columns selection,
 +        // it would be harmless to do it. However, we use this method got the partition key too. And when a query
 +        // with 2ndary index is done, and with the the partition provided with an EQ, we'll end up here, and in that
 +        // case using the eoc would be bad, since for the random partitioner we have no guarantee that
 +        // prefix.end() will sort after prefix (see #5240).
 +        EOC eoc = !builder.hasRemaining() ? EOC.NONE : (bound.isEnd() ? EOC.END : EOC.START);
 +        return filterAndSort(builder.buildWithEOC(eoc));
 +    }
 +
 +    /**
 +     * Removes duplicates and sort the specified composites.
 +     *
 +     * @param composites the composites to filter and sort
 +     * @return the composites sorted and without duplicates
 +     */
 +    private List<Composite> filterAndSort(List<Composite> composites)
 +    {
 +        if (composites.size() <= 1)
 +            return composites;
 +
 +        TreeSet<Composite> set = new TreeSet<Composite>(ctype);
 +        set.addAll(composites);
 +
 +        return new ArrayList<>(set);
 +    }
 +
 +    /**
 +     * Sets EOCs for the composites returned by the specified slice restriction for the given bound.
 +     *
 +     * @param r the slice restriction
 +     * @param bound the bound
 +     * @param composites the composites
 +     * @return the composites with their EOCs properly set
 +     */
 +    private List<Composite> setEocs(Restriction r, Bound bound, List<Composite> composites)
 +    {
 +        List<Composite> list = new ArrayList<>(composites.size());
 +
 +        // The first column of the slice might not be the first clustering column (e.g. clustering_0 = ? AND (clustering_1, clustering_2) >= (?, ?)
 +        int offset = r.getFirstColumn().position();
 +
 +        for (int i = 0, m = composites.size(); i < m; i++)
 +        {
 +            Composite composite = composites.get(i);
 +
 +            // Handle the no bound case
 +            if (composite.size() == offset)
 +            {
 +                list.add(composite.withEOC(bound.isEnd() ? EOC.END : EOC.START));
 +                continue;
 +            }
 +
 +            // In the case of mixed order columns, we will have some extra slices where the columns change directions.
 +            // For example: if we have clustering_0 DESC and clustering_1 ASC a slice like (clustering_0, clustering_1) > (1, 2)
 +            // will produce 2 slices: [EMPTY, 1.START] and [1.2.END, 1.END]
 +            // So, the END bound will return 2 composite with the same values 1
 +            if (composite.size() <= r.getLastColumn().position() && i < m - 1 && composite.equals(composites.get(i + 1)))
 +            {
 +                list.add(composite.withEOC(EOC.START));
 +                list.add(composites.get(i++).withEOC(EOC.END));
 +                continue;
 +            }
 +
 +            // Handle the normal bounds
 +            ColumnDefinition column = r.getColumnDefs().get(composite.size() - 1 - offset);
 +            Bound b = reverseBoundIfNeeded(column, bound);
 +
 +            Composite.EOC eoc = eocFor(r, bound, b);
 +            list.add(composite.withEOC(eoc));
 +        }
 +
 +        return list;
 +    }
 +
 +    @Override
-     public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException
++    public List<ByteBuffer> values(CFMetaData cfm, QueryOptions options) throws InvalidRequestException
 +    {
-         return Composites.toByteBuffers(valuesAsComposites(options));
++        return Composites.toByteBuffers(valuesAsComposites(cfm, options));
 +    }
 +
 +    @Override
-     public List<ByteBuffer> bounds(Bound b, QueryOptions options) throws InvalidRequestException
++    public List<ByteBuffer> bounds(CFMetaData cfm, Bound b, QueryOptions options) throws InvalidRequestException
 +    {
-         return Composites.toByteBuffers(boundsAsComposites(b, options));
++        return Composites.toByteBuffers(boundsAsComposites(cfm, b, options));
 +    }
 +
 +    private static Composite.EOC eocFor(Restriction r, Bound eocBound, Bound inclusiveBound)
 +    {
 +        if (eocBound.isStart())
 +            return r.isInclusive(inclusiveBound) ? Composite.EOC.NONE : Composite.EOC.END;
 +
 +        return r.isInclusive(inclusiveBound) ? Composite.EOC.END : Composite.EOC.START;
 +    }
 +
 +    @Override
 +    public boolean hasBound(Bound b)
 +    {
 +        if (isEmpty())
 +            return false;
 +        return restrictions.lastRestriction().hasBound(b);
 +    }
 +
 +    @Override
 +    public boolean isInclusive(Bound b)
 +    {
 +        if (isEmpty())
 +            return false;
 +        return restrictions.lastRestriction().isInclusive(b);
 +    }
 +
 +    @Override
 +    public boolean hasSupportingIndex(SecondaryIndexManager indexManager)
 +    {
 +        return restrictions.hasSupportingIndex(indexManager);
 +    }
 +
 +    @Override
 +    public void addIndexExpressionTo(List<IndexExpression> expressions,
 +                                     SecondaryIndexManager indexManager,
 +                                     QueryOptions options) throws InvalidRequestException
 +    {
 +        Boolean clusteringColumns = null;
 +        int position = 0;
 +
 +        for (Restriction restriction : restrictions)
 +        {
 +            ColumnDefinition columnDef = restriction.getFirstColumn();
 +
 +            // PrimaryKeyRestrictionSet contains only one kind of column, either partition key or clustering columns.
 +            // Therefore we only need to check the column kind once. All the other columns will be of the same kind.
 +            if (clusteringColumns == null)
 +                clusteringColumns = columnDef.isClusteringColumn() ? Boolean.TRUE : Boolean.FALSE;
 +
 +            // We ignore all the clustering columns that can be handled by slices.
 +            if (!clusteringColumns || handleInFilter(restriction, position) || restriction.hasSupportingIndex(indexManager))
 +            {
 +                restriction.addIndexExpressionTo(expressions, indexManager, options);
 +                continue;
 +            }
 +
 +            if (!restriction.isSlice())
 +                position = restriction.getLastColumn().position() + 1;
 +        }
 +    }
 +
 +    @Override
 +    public List<ColumnDefinition> getColumnDefs()
 +    {
 +        return restrictions.getColumnDefs();
 +    }
 +
 +    @Override
 +    public ColumnDefinition getFirstColumn()
 +    {
 +        return restrictions.firstColumn();
 +    }
 +
 +    @Override
 +    public ColumnDefinition getLastColumn()
 +    {
 +        return restrictions.lastColumn();
 +    }
 +
 +    public final boolean needsFiltering()
 +    {
 +        // Backported from ClusteringColumnRestrictions from CASSANDRA-11310 for 3.6
 +        // As that suggests, this should only be called on clustering column
 +        // and not partition key restrictions.
 +        int position = 0;
 +        for (Restriction restriction : restrictions)
 +        {
 +            if (handleInFilter(restriction, position))
 +                return true;
 +
 +            if (!restriction.isSlice())
 +                position = restriction.getLastColumn().position() + 1;
 +        }
 +
 +        return false;
 +    }
 +
++    @Override
++    public boolean isNotReturningAnyRows(CFMetaData cfm, QueryOptions options)
++    {
++        for (Restriction restriction : restrictions)
++        {
++            if (restriction.isNotReturningAnyRows(cfm, options))
++                return true;
++        }
++        return false;
++    }
++
 +    private boolean handleInFilter(Restriction restriction, int index)
 +    {
 +        return restriction.isContains() || index != restriction.getFirstColumn().position();
 +    }
 +
 +    public Iterator<Restriction> iterator()
 +    {
 +        return restrictions.iterator();
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/9583b6b3/src/java/org/apache/cassandra/cql3/restrictions/PrimaryKeyRestrictions.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/cql3/restrictions/PrimaryKeyRestrictions.java
index 7d7b492,0000000..5e5e3f5
mode 100644,000000..100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/PrimaryKeyRestrictions.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/PrimaryKeyRestrictions.java
@@@ -1,44 -1,0 +1,45 @@@
 +/*
 + * 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.restrictions;
 +
 +import java.nio.ByteBuffer;
 +import java.util.List;
 +
++import org.apache.cassandra.config.CFMetaData;
 +import org.apache.cassandra.cql3.QueryOptions;
 +import org.apache.cassandra.cql3.statements.Bound;
 +import org.apache.cassandra.db.composites.Composite;
 +import org.apache.cassandra.exceptions.InvalidRequestException;
 +
 +/**
 + * A set of restrictions on a primary key part (partition key or clustering key).
 + *
 + */
 +interface PrimaryKeyRestrictions extends Restriction, Restrictions
 +{
 +    @Override
 +    public PrimaryKeyRestrictions mergeWith(Restriction restriction) throws InvalidRequestException;
 +
-     public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException;
++    public List<ByteBuffer> values(CFMetaData cfm, QueryOptions options) throws InvalidRequestException;
 +
-     public List<Composite> valuesAsComposites(QueryOptions options) throws InvalidRequestException;
++    public List<Composite> valuesAsComposites(CFMetaData cfm, QueryOptions options) throws InvalidRequestException;
 +
-     public List<ByteBuffer> bounds(Bound b, QueryOptions options) throws InvalidRequestException;
++    public List<ByteBuffer> bounds(CFMetaData cfm, Bound b, QueryOptions options) throws InvalidRequestException;
 +
-     public List<Composite> boundsAsComposites(Bound bound, QueryOptions options) throws InvalidRequestException;
++    public List<Composite> boundsAsComposites(CFMetaData cfm, Bound bound, QueryOptions options) throws InvalidRequestException;
 +}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/9583b6b3/src/java/org/apache/cassandra/cql3/restrictions/Restriction.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/cql3/restrictions/Restriction.java
index f0ea6a7,0000000..9df100a
mode 100644,000000..100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/Restriction.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/Restriction.java
@@@ -1,138 -1,0 +1,150 @@@
 +/*
 + * 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.restrictions;
 +
 +import java.util.List;
 +
++import org.apache.cassandra.config.CFMetaData;
 +import org.apache.cassandra.config.ColumnDefinition;
 +import org.apache.cassandra.cql3.QueryOptions;
 +import org.apache.cassandra.cql3.functions.Function;
 +import org.apache.cassandra.cql3.statements.Bound;
 +import org.apache.cassandra.db.IndexExpression;
 +import org.apache.cassandra.db.composites.CompositesBuilder;
 +import org.apache.cassandra.db.index.SecondaryIndexManager;
 +import org.apache.cassandra.exceptions.InvalidRequestException;
 +
 +/**
 + * A restriction/clause on a column.
 + * The goal of this class being to group all conditions for a column in a SELECT.
 + *
 + * <p>Implementation of this class must be immutable. See {@link #mergeWith(Restriction)} for more explanation.</p>
 + */
 +public interface Restriction
 +{
 +    public boolean isOnToken();
 +    public boolean isSlice();
 +    public boolean isEQ();
 +    public boolean isIN();
 +    public boolean isContains();
 +    public boolean isMultiColumn();
 +
 +    /**
 +     * Returns the definition of the first column.
 +     * @return the definition of the first column.
 +     */
 +    public ColumnDefinition getFirstColumn();
 +
 +    /**
 +     * Returns the definition of the last column.
 +     * @return the definition of the last column.
 +     */
 +    public ColumnDefinition getLastColumn();
 +
 +    /**
 +     * Returns the column definitions in position order.
 +     * @return the column definitions in position order.
 +     */
 +    public List<ColumnDefinition> getColumnDefs();
 +
 +    /**
 +     * Return an Iterable over all of the functions (both native and user-defined) used by any component
 +     * of the restriction
 +     * @return functions all functions found (may contain duplicates)
 +     */
 +    public Iterable<Function> getFunctions();
 +
 +    /**
 +     * Checks if the specified bound is set or not.
 +     * @param b the bound type
 +     * @return <code>true</code> if the specified bound is set, <code>false</code> otherwise
 +     */
 +    public boolean hasBound(Bound b);
 +
 +    /**
 +     * Checks if the specified bound is inclusive or not.
 +     * @param b the bound type
 +     * @return <code>true</code> if the specified bound is inclusive, <code>false</code> otherwise
 +     */
 +    public boolean isInclusive(Bound b);
 +
 +    /**
 +     * Merges this restriction with the specified one.
 +     *
 +     * <p>Restriction are immutable. Therefore merging two restrictions result in a new one.
 +     * The reason behind this choice is that it allow a great flexibility in the way the merging can done while
 +     * preventing any side effect.</p>
 +     *
 +     * @param otherRestriction the restriction to merge into this one
 +     * @return the restriction resulting of the merge
 +     * @throws InvalidRequestException if the restrictions cannot be merged
 +     */
 +    public Restriction mergeWith(Restriction otherRestriction) throws InvalidRequestException;
 +
 +    /**
 +     * Check if the restriction is on indexed columns.
 +     *
 +     * @param indexManager the index manager
 +     * @return <code>true</code> if the restriction is on indexed columns, <code>false</code>
 +     */
 +    public boolean hasSupportingIndex(SecondaryIndexManager indexManager);
 +
 +    /**
 +     * Adds to the specified list the <code>IndexExpression</code>s corresponding to this <code>Restriction</code>.
 +     *
 +     * @param expressions the list to add the <code>IndexExpression</code>s to
 +     * @param indexManager the secondary index manager
 +     * @param options the query options
 +     * @throws InvalidRequestException if this <code>Restriction</code> cannot be converted into 
 +     * <code>IndexExpression</code>s
 +     */
 +    public void addIndexExpressionTo(List<IndexExpression> expressions,
 +                                     SecondaryIndexManager indexManager,
 +                                     QueryOptions options)
 +                                     throws InvalidRequestException;
 +
 +    /**
 +     * Appends the values of this <code>Restriction</code> to the specified builder.
 +     *
++     * @param cfm the table metadata
 +     * @param builder the <code>CompositesBuilder</code> to append to.
 +     * @param options the query options
 +     * @return the <code>CompositesBuilder</code>
 +     */
-     public CompositesBuilder appendTo(CompositesBuilder builder, QueryOptions options);
++    public CompositesBuilder appendTo(CFMetaData cfm, CompositesBuilder builder, QueryOptions options);
 +
 +    /**
 +     * Appends the values of the <code>Restriction</code> for the specified bound to the specified builder.
 +     *
++     * @param cfm the table metadata
 +     * @param builder the <code>CompositesBuilder</code> to append to.
 +     * @param bound the bound
 +     * @param options the query options
 +     * @return the <code>CompositesBuilder</code>
 +     */
-     public CompositesBuilder appendBoundTo(CompositesBuilder builder, Bound bound, QueryOptions options);
++    public CompositesBuilder appendBoundTo(CFMetaData cfm, CompositesBuilder builder, Bound bound, QueryOptions options);
++
++    /**
++     * Checks if this restriction will prevent the query to return any rows.
++     *
++     * @param cfm the table metadata
++     * @param options the query options
++     * @return {@code true} if this restriction will prevent the query to return any rows, {@false} otherwise
++     */
++    public boolean isNotReturningAnyRows(CFMetaData cfm, QueryOptions options);
 +}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/9583b6b3/src/java/org/apache/cassandra/cql3/restrictions/ReversedPrimaryKeyRestrictions.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/cql3/restrictions/ReversedPrimaryKeyRestrictions.java
index 9b33161,0000000..9107acd
mode 100644,000000..100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/ReversedPrimaryKeyRestrictions.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/ReversedPrimaryKeyRestrictions.java
@@@ -1,77 -1,0 +1,78 @@@
 +/*
 + * 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.restrictions;
 +
 +import java.nio.ByteBuffer;
 +import java.util.Collections;
 +import java.util.List;
 +
++import org.apache.cassandra.config.CFMetaData;
 +import org.apache.cassandra.cql3.QueryOptions;
 +import org.apache.cassandra.cql3.statements.Bound;
 +import org.apache.cassandra.db.composites.Composite;
 +import org.apache.cassandra.exceptions.InvalidRequestException;
 +
 +/**
 + * <code>PrimaryKeyRestrictions</code> decorator that reverse the slices.
 + */
 +final class ReversedPrimaryKeyRestrictions extends ForwardingPrimaryKeyRestrictions
 +{
 +    /**
 +     * The decorated restrictions.
 +     */
 +    private PrimaryKeyRestrictions restrictions;
 +
 +    public ReversedPrimaryKeyRestrictions(PrimaryKeyRestrictions restrictions)
 +    {
 +        this.restrictions = restrictions;
 +    }
 +
 +    @Override
 +    public PrimaryKeyRestrictions mergeWith(Restriction restriction) throws InvalidRequestException
 +    {
 +        return new ReversedPrimaryKeyRestrictions(this.restrictions.mergeWith(restriction));
 +    }
 +
 +    @Override
-     public List<ByteBuffer> bounds(Bound bound, QueryOptions options) throws InvalidRequestException
++    public List<ByteBuffer> bounds(CFMetaData cfm, Bound bound, QueryOptions options) throws InvalidRequestException
 +    {
-         List<ByteBuffer> buffers = restrictions.bounds(bound.reverse(), options);
++        List<ByteBuffer> buffers = restrictions.bounds(cfm, bound.reverse(), options);
 +        Collections.reverse(buffers);
 +        return buffers;
 +    }
 +
 +    @Override
-     public List<Composite> boundsAsComposites(Bound bound, QueryOptions options) throws InvalidRequestException
++    public List<Composite> boundsAsComposites(CFMetaData cfm, Bound bound, QueryOptions options) throws InvalidRequestException
 +    {
-         List<Composite> composites = restrictions.boundsAsComposites(bound.reverse(), options);
++        List<Composite> composites = restrictions.boundsAsComposites(cfm, bound.reverse(), options);
 +        Collections.reverse(composites);
 +        return composites;
 +    }
 +
 +    @Override
 +    public boolean isInclusive(Bound bound)
 +    {
 +        return this.restrictions.isInclusive(bound.reverse());
 +    }
 +
 +    @Override
 +    protected PrimaryKeyRestrictions getDelegate()
 +    {
 +        return this.restrictions;
 +    }
 +}


[06/12] cassandra git commit: Merge branch cassandra-2.1 into cassandra-2.2

Posted by bl...@apache.org.
http://git-wip-us.apache.org/repos/asf/cassandra/blob/9583b6b3/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java
index 735a2e2,0000000..1f4960b
mode 100644,000000..100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java
@@@ -1,626 -1,0 +1,669 @@@
 +/*
 + * 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.restrictions;
 +
 +import java.nio.ByteBuffer;
 +import java.util.*;
 +
 +import com.google.common.collect.Iterables;
 +
++import org.apache.cassandra.config.CFMetaData;
 +import org.apache.cassandra.config.ColumnDefinition;
 +import org.apache.cassandra.cql3.*;
 +import org.apache.cassandra.cql3.Term.Terminal;
 +import org.apache.cassandra.cql3.functions.Function;
 +import org.apache.cassandra.cql3.statements.Bound;
 +import org.apache.cassandra.db.IndexExpression;
 +import org.apache.cassandra.db.composites.CompositesBuilder;
 +import org.apache.cassandra.db.index.SecondaryIndex;
 +import org.apache.cassandra.db.index.SecondaryIndexManager;
 +import org.apache.cassandra.db.marshal.CompositeType;
 +import org.apache.cassandra.exceptions.InvalidRequestException;
 +import static org.apache.cassandra.cql3.statements.RequestValidations.checkBindValueSet;
 +import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse;
 +import static org.apache.cassandra.cql3.statements.RequestValidations.checkNotNull;
 +import static org.apache.cassandra.cql3.statements.RequestValidations.checkTrue;
 +import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest;
 +
 +public abstract class SingleColumnRestriction extends AbstractRestriction
 +{
 +    /**
 +     * The definition of the column to which apply the restriction.
 +     */
 +    protected final ColumnDefinition columnDef;
 +
 +    public SingleColumnRestriction(ColumnDefinition columnDef)
 +    {
 +        this.columnDef = columnDef;
 +    }
 +
 +    @Override
 +    public List<ColumnDefinition> getColumnDefs()
 +    {
 +        return Collections.singletonList(columnDef);
 +    }
 +
 +    @Override
 +    public ColumnDefinition getFirstColumn()
 +    {
 +        return columnDef;
 +    }
 +
 +    @Override
 +    public ColumnDefinition getLastColumn()
 +    {
 +        return columnDef;
 +    }
 +
 +    @Override
 +    public boolean hasSupportingIndex(SecondaryIndexManager indexManager)
 +    {
 +        SecondaryIndex index = indexManager.getIndexForColumn(columnDef.name.bytes);
 +        return index != null && isSupportedBy(index);
 +    }
 +
 +    @Override
 +    public final Restriction mergeWith(Restriction otherRestriction) throws InvalidRequestException
 +    {
 +        // We want to allow query like: b > ? AND (b,c) < (?, ?)
 +        if (otherRestriction.isMultiColumn() && canBeConvertedToMultiColumnRestriction())
 +        {
 +            return toMultiColumnRestriction().mergeWith(otherRestriction);
 +        }
 +
 +        return doMergeWith(otherRestriction);
 +    }
 +
 +    protected abstract Restriction doMergeWith(Restriction otherRestriction) throws InvalidRequestException;
 +
 +    /**
 +     * Converts this <code>SingleColumnRestriction</code> into a {@link MultiColumnRestriction}
 +     *
 +     * @return the <code>MultiColumnRestriction</code> corresponding to this
 +     */
 +    abstract MultiColumnRestriction toMultiColumnRestriction();
 +
 +    /**
 +     * Checks if this <code>Restriction</code> can be converted into a {@link MultiColumnRestriction}
 +     *
 +     * @return <code>true</code> if this <code>Restriction</code> can be converted into a
 +     * {@link MultiColumnRestriction}, <code>false</code> otherwise.
 +     */
 +    boolean canBeConvertedToMultiColumnRestriction()
 +    {
 +        return true;
 +    }
 +
 +    /**
 +     * Check if this type of restriction is supported by the specified index.
 +     *
 +     * @param index the Secondary index
 +     * @return <code>true</code> this type of restriction is supported by the specified index,
 +     * <code>false</code> otherwise.
 +     */
 +    protected abstract boolean isSupportedBy(SecondaryIndex index);
 +
 +    public static final class EQ extends SingleColumnRestriction
 +    {
 +        private final Term value;
 +
 +        public EQ(ColumnDefinition columnDef, Term value)
 +        {
 +            super(columnDef);
 +            this.value = value;
 +        }
 +
 +        @Override
 +        public Iterable<Function> getFunctions()
 +        {
 +            return value.getFunctions();
 +        }
 +
 +        @Override
 +        public boolean isEQ()
 +        {
 +            return true;
 +        }
 +
 +        @Override
 +        MultiColumnRestriction toMultiColumnRestriction()
 +        {
 +            return new MultiColumnRestriction.EQ(Collections.singletonList(columnDef), value);
 +        }
 +
 +        @Override
 +        public void addIndexExpressionTo(List<IndexExpression> expressions,
 +                                         SecondaryIndexManager indexManager,
 +                                         QueryOptions options) throws InvalidRequestException
 +        {
 +            ByteBuffer buffer = validateIndexedValue(columnDef, value.bindAndGet(options));
 +            expressions.add(new IndexExpression(columnDef.name.bytes, Operator.EQ, buffer));
 +        }
 +
 +        @Override
-         public CompositesBuilder appendTo(CompositesBuilder builder, QueryOptions options)
++        public CompositesBuilder appendTo(CFMetaData cfm, CompositesBuilder builder, QueryOptions options)
 +        {
 +            builder.addElementToAll(value.bindAndGet(options));
 +            checkFalse(builder.containsNull(), "Invalid null value in condition for column %s", columnDef.name);
 +            checkFalse(builder.containsUnset(), "Invalid unset value for column %s", columnDef.name);
 +            return builder;
 +        }
 +
 +        @Override
 +        public String toString()
 +        {
 +            return String.format("EQ(%s)", value);
 +        }
 +
 +        @Override
 +        public Restriction doMergeWith(Restriction otherRestriction) throws InvalidRequestException
 +        {
 +            throw invalidRequest("%s cannot be restricted by more than one relation if it includes an Equal", columnDef.name);
 +        }
 +
 +        @Override
 +        protected boolean isSupportedBy(SecondaryIndex index)
 +        {
 +            return index.supportsOperator(Operator.EQ);
 +        }
++
++        @Override
++        public boolean isNotReturningAnyRows(CFMetaData cfm, QueryOptions options)
++        {
++            assert columnDef.isClusteringColumn();
++
++            // 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 results.
++            return !cfm.comparator.isCompound() && !value.bindAndGet(options).hasRemaining();
++        }
 +    }
 +
 +    public static abstract class IN extends SingleColumnRestriction
 +    {
 +        public IN(ColumnDefinition columnDef)
 +        {
 +            super(columnDef);
 +        }
 +
 +        @Override
 +        public final boolean isIN()
 +        {
 +            return true;
 +        }
 +
 +        @Override
 +        public final Restriction doMergeWith(Restriction otherRestriction) throws InvalidRequestException
 +        {
 +            throw invalidRequest("%s cannot be restricted by more than one relation if it includes a IN", columnDef.name);
 +        }
 +
 +        @Override
-         public CompositesBuilder appendTo(CompositesBuilder builder, QueryOptions options)
++        public CompositesBuilder appendTo(CFMetaData cfm, CompositesBuilder builder, QueryOptions options)
 +        {
-             builder.addEachElementToAll(getValues(options));
++            List<ByteBuffer> values = filterValuesIfNeeded(cfm, getValues(options));
++
++            builder.addEachElementToAll(values);
 +            checkFalse(builder.containsNull(), "Invalid null value in condition for column %s", columnDef.name);
 +            checkFalse(builder.containsUnset(), "Invalid unset value for column %s", columnDef.name);
 +            return builder;
 +        }
 +
++        private List<ByteBuffer> filterValuesIfNeeded(CFMetaData cfm, List<ByteBuffer> values)
++        {
++            if (!columnDef.isClusteringColumn() || cfm.comparator.isCompound())
++                return values;
++
++            // 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.
++
++            // As some List implementations do not support remove, we copy the list to be on the safe side.
++            List<ByteBuffer> filteredValues = new ArrayList<>(values.size());
++            for (ByteBuffer value : values)
++            {
++                if (value.hasRemaining())
++                    filteredValues.add(value);
++            }
++            return filteredValues;
++        }
++
 +        @Override
 +        public void addIndexExpressionTo(List<IndexExpression> expressions,
 +                                         SecondaryIndexManager indexManager,
 +                                         QueryOptions options) throws InvalidRequestException
 +        {
 +            List<ByteBuffer> values = getValues(options);
 +            checkTrue(values.size() == 1, "IN restrictions are not supported on indexed columns");
 +
 +            ByteBuffer value = validateIndexedValue(columnDef, values.get(0));
 +            expressions.add(new IndexExpression(columnDef.name.bytes, Operator.EQ, value));
 +        }
 +
 +        @Override
 +        protected final boolean isSupportedBy(SecondaryIndex index)
 +        {
 +            return index.supportsOperator(Operator.IN);
 +        }
 +
 +        protected abstract List<ByteBuffer> getValues(QueryOptions options) throws InvalidRequestException;
 +    }
 +
 +    public static class InWithValues extends IN
 +    {
 +        protected final List<Term> values;
 +
 +        public InWithValues(ColumnDefinition columnDef, List<Term> values)
 +        {
 +            super(columnDef);
 +            this.values = values;
 +        }
 +
 +        @Override
 +        MultiColumnRestriction toMultiColumnRestriction()
 +        {
 +            return new MultiColumnRestriction.InWithValues(Collections.singletonList(columnDef), values);
 +        }
 +
 +        @Override
 +        public Iterable<Function> getFunctions()
 +        {
 +            return Terms.getFunctions(values);
 +        }
 +
 +        @Override
 +        protected List<ByteBuffer> getValues(QueryOptions options) throws InvalidRequestException
 +        {
 +            List<ByteBuffer> buffers = new ArrayList<>(values.size());
 +            for (Term value : values)
 +                buffers.add(value.bindAndGet(options));
 +            return buffers;
 +        }
 +
 +        @Override
 +        public String toString()
 +        {
 +            return String.format("IN(%s)", values);
 +        }
 +    }
 +
 +    public static class InWithMarker extends IN
 +    {
 +        protected final AbstractMarker marker;
 +
 +        public InWithMarker(ColumnDefinition columnDef, AbstractMarker marker)
 +        {
 +            super(columnDef);
 +            this.marker = marker;
 +        }
 +
 +        @Override
 +        public Iterable<Function> getFunctions()
 +        {
 +            return Collections.emptySet();
 +        }
 +
 +        @Override
 +        MultiColumnRestriction toMultiColumnRestriction()
 +        {
 +            return new MultiColumnRestriction.InWithMarker(Collections.singletonList(columnDef), marker);
 +        }
 +
 +        @Override
 +        protected List<ByteBuffer> getValues(QueryOptions options) throws InvalidRequestException
 +        {
 +            Terminal term = marker.bind(options);
 +            checkNotNull(term, "Invalid null value for column %s", columnDef.name);
 +            checkFalse(term == Constants.UNSET_VALUE, "Invalid unset value for column %s", columnDef.name);
 +            Term.MultiItemTerminal lval = (Term.MultiItemTerminal) term;
 +            return lval.getElements();
 +        }
 +
 +        @Override
 +        public String toString()
 +        {
 +            return "IN ?";
 +        }
 +    }
 +
 +    public static final class Slice extends SingleColumnRestriction
 +    {
 +        private final TermSlice slice;
 +
 +        public Slice(ColumnDefinition columnDef, Bound bound, boolean inclusive, Term term)
 +        {
 +            super(columnDef);
 +            slice = TermSlice.newInstance(bound, inclusive, term);
 +        }
 +
 +        @Override
 +        public Iterable<Function> getFunctions()
 +        {
 +            return slice.getFunctions();
 +        }
 +
 +        @Override
 +        MultiColumnRestriction toMultiColumnRestriction()
 +        {
 +            return new MultiColumnRestriction.Slice(Collections.singletonList(columnDef), slice);
 +        }
 +
 +        @Override
 +        public boolean isSlice()
 +        {
 +            return true;
 +        }
 +
 +        @Override
-         public CompositesBuilder appendTo(CompositesBuilder builder, QueryOptions options)
++        public CompositesBuilder appendTo(CFMetaData cfm, CompositesBuilder builder, QueryOptions options)
 +        {
 +            throw new UnsupportedOperationException();
 +        }
 +
 +        @Override
 +        public boolean hasBound(Bound b)
 +        {
 +            return slice.hasBound(b);
 +        }
 +
 +        @Override
-         public CompositesBuilder appendBoundTo(CompositesBuilder builder, Bound bound, QueryOptions options)
++        public CompositesBuilder appendBoundTo(CFMetaData cfm, CompositesBuilder builder, Bound bound, QueryOptions options)
 +        {
 +            Bound b = reverseBoundIfNeeded(getFirstColumn(), bound);
 +
 +            if (!hasBound(b))
 +                return builder;
 +
 +            ByteBuffer value = slice.bound(b).bindAndGet(options);
 +            checkBindValueSet(value, "Invalid unset value for column %s", columnDef.name);
 +            return builder.addElementToAll(value);
 +
 +        }
 +
 +        @Override
 +        public boolean isInclusive(Bound b)
 +        {
 +            return slice.isInclusive(b);
 +        }
 +
 +        @Override
 +        public Restriction doMergeWith(Restriction otherRestriction) throws InvalidRequestException
 +        {
 +            checkTrue(otherRestriction.isSlice(),
 +                      "Column \"%s\" cannot be restricted by both an equality and an inequality relation",
 +                      columnDef.name);
 +
 +            SingleColumnRestriction.Slice otherSlice = (SingleColumnRestriction.Slice) otherRestriction;
 +
 +            checkFalse(hasBound(Bound.START) && otherSlice.hasBound(Bound.START),
 +                       "More than one restriction was found for the start bound on %s", columnDef.name);
 +
 +            checkFalse(hasBound(Bound.END) && otherSlice.hasBound(Bound.END),
 +                       "More than one restriction was found for the end bound on %s", columnDef.name);
 +
 +            return new Slice(columnDef,  slice.merge(otherSlice.slice));
 +        }
 +
 +        @Override
 +        public void addIndexExpressionTo(List<IndexExpression> expressions,
 +                                         SecondaryIndexManager indexManager,
 +                                         QueryOptions options) throws InvalidRequestException
 +        {
 +            for (Bound b : Bound.values())
 +            {
 +                if (hasBound(b))
 +                {
 +                    ByteBuffer value = validateIndexedValue(columnDef, slice.bound(b).bindAndGet(options));
 +                    Operator op = slice.getIndexOperator(b);
 +                    // If the underlying comparator for name is reversed, we need to reverse the IndexOperator: user operation
 +                    // always refer to the "forward" sorting even if the clustering order is reversed, but the 2ndary code does
 +                    // use the underlying comparator as is.
 +                    op = columnDef.isReversedType() ? op.reverse() : op;
 +                    expressions.add(new IndexExpression(columnDef.name.bytes, op, value));
 +                }
 +            }
 +        }
 +
 +        @Override
 +        protected boolean isSupportedBy(SecondaryIndex index)
 +        {
 +            return slice.isSupportedBy(index);
 +        }
 +
 +        @Override
 +        public String toString()
 +        {
 +            return String.format("SLICE%s", slice);
 +        }
 +
++        @Override
++        public boolean isNotReturningAnyRows(CFMetaData cfm, QueryOptions options)
++        {
++            assert columnDef.isClusteringColumn();
++
++            // Dense non-compound tables do not accept empty ByteBuffers. By consequence, we know that
++            // any query with a slice restriction with an empty value for the END bound will not return any results.
++            return !cfm.comparator.isCompound()
++                    && hasBound(Bound.END)
++                    && !slice.bound(Bound.END).bindAndGet(options).hasRemaining();
++        }
++
 +        private Slice(ColumnDefinition columnDef, TermSlice slice)
 +        {
 +            super(columnDef);
 +            this.slice = slice;
 +        }
 +    }
 +
 +    // This holds CONTAINS, CONTAINS_KEY, and map[key] = value restrictions because we might want to have any combination of them.
 +    public static final class Contains extends SingleColumnRestriction
 +    {
 +        private List<Term> values = new ArrayList<>(); // for CONTAINS
 +        private List<Term> keys = new ArrayList<>(); // for CONTAINS_KEY
 +        private List<Term> entryKeys = new ArrayList<>(); // for map[key] = value
 +        private List<Term> entryValues = new ArrayList<>(); // for map[key] = value
 +
 +        public Contains(ColumnDefinition columnDef, Term t, boolean isKey)
 +        {
 +            super(columnDef);
 +            if (isKey)
 +                keys.add(t);
 +            else
 +                values.add(t);
 +        }
 +
 +        public Contains(ColumnDefinition columnDef, Term mapKey, Term mapValue)
 +        {
 +            super(columnDef);
 +            entryKeys.add(mapKey);
 +            entryValues.add(mapValue);
 +        }
 +
 +        @Override
 +        MultiColumnRestriction toMultiColumnRestriction()
 +        {
 +            throw new UnsupportedOperationException();
 +        }
 +
 +        @Override
 +        boolean canBeConvertedToMultiColumnRestriction()
 +        {
 +            return false;
 +        }
 +
 +        @Override
-         public CompositesBuilder appendTo(CompositesBuilder builder, QueryOptions options)
++        public CompositesBuilder appendTo(CFMetaData cfm, CompositesBuilder builder, QueryOptions options)
 +        {
 +            throw new UnsupportedOperationException();
 +        }
 +
 +        @Override
 +        public boolean isContains()
 +        {
 +            return true;
 +        }
 +
 +        @Override
 +        public Restriction doMergeWith(Restriction otherRestriction) throws InvalidRequestException
 +        {
 +            checkTrue(otherRestriction.isContains(),
 +                      "Collection column %s can only be restricted by CONTAINS, CONTAINS KEY, or map-entry equality",
 +                      columnDef.name);
 +
 +            SingleColumnRestriction.Contains newContains = new Contains(columnDef);
 +
 +            copyKeysAndValues(this, newContains);
 +            copyKeysAndValues((Contains) otherRestriction, newContains);
 +
 +            return newContains;
 +        }
 +
 +        @Override
 +        public void addIndexExpressionTo(List<IndexExpression> expressions,
 +                                         SecondaryIndexManager indexManager,
 +                                         QueryOptions options)
 +                                         throws InvalidRequestException
 +        {
 +            addExpressionsFor(expressions, bindAndGet(values, options), Operator.CONTAINS);
 +            addExpressionsFor(expressions, bindAndGet(keys, options), Operator.CONTAINS_KEY);
 +            addExpressionsFor(expressions, entries(options), Operator.EQ);
 +        }
 +
 +        private void addExpressionsFor(List<IndexExpression> target, List<ByteBuffer> values,
 +                                       Operator op) throws InvalidRequestException
 +        {
 +            for (ByteBuffer value : values)
 +            {
 +                validateIndexedValue(columnDef, value);
 +                target.add(new IndexExpression(columnDef.name.bytes, op, value));
 +            }
 +        }
 +
 +        @Override
 +        protected boolean isSupportedBy(SecondaryIndex index)
 +        {
 +            boolean supported = false;
 +
 +            if (numberOfValues() > 0)
 +                supported |= index.supportsOperator(Operator.CONTAINS);
 +
 +            if (numberOfKeys() > 0)
 +                supported |= index.supportsOperator(Operator.CONTAINS_KEY);
 +
 +            if (numberOfEntries() > 0)
 +                supported |= index.supportsOperator(Operator.EQ);
 +
 +            return supported;
 +        }
 +
 +        public int numberOfValues()
 +        {
 +            return values.size();
 +        }
 +
 +        public int numberOfKeys()
 +        {
 +            return keys.size();
 +        }
 +
 +        public int numberOfEntries()
 +        {
 +            return entryKeys.size();
 +        }
 +
 +        @Override
 +        public Iterable<Function> getFunctions()
 +        {
 +            return Iterables.concat(Terms.getFunctions(values),
 +                                    Terms.getFunctions(keys),
 +                                    Terms.getFunctions(entryKeys),
 +                                    Terms.getFunctions(entryValues));
 +        }
 +
 +        @Override
 +        public String toString()
 +        {
 +            return String.format("CONTAINS(values=%s, keys=%s, entryKeys=%s, entryValues=%s)", values, keys, entryKeys, entryValues);
 +        }
 +
 +        @Override
 +        public boolean hasBound(Bound b)
 +        {
 +            throw new UnsupportedOperationException();
 +        }
 +
 +        @Override
-         public CompositesBuilder appendBoundTo(CompositesBuilder builder, Bound bound, QueryOptions options)
++        public CompositesBuilder appendBoundTo(CFMetaData cfm, CompositesBuilder builder, Bound bound, QueryOptions options)
 +        {
 +            throw new UnsupportedOperationException();
 +        }
 +
 +        @Override
 +        public boolean isInclusive(Bound b)
 +        {
 +            throw new UnsupportedOperationException();
 +        }
 +
 +        private List<ByteBuffer> entries(QueryOptions options) throws InvalidRequestException
 +        {
 +            List<ByteBuffer> entryBuffers = new ArrayList<>(entryKeys.size());
 +            List<ByteBuffer> keyBuffers = bindAndGet(entryKeys, options);
 +            List<ByteBuffer> valueBuffers = bindAndGet(entryValues, options);
 +            for (int i = 0; i < entryKeys.size(); i++)
 +            {
 +                if (valueBuffers.get(i) == null)
 +                    throw new InvalidRequestException("Unsupported null value for map-entry equality");
 +                entryBuffers.add(CompositeType.build(keyBuffers.get(i), valueBuffers.get(i)));
 +            }
 +            return entryBuffers;
 +        }
 +
 +        /**
 +         * Binds the query options to the specified terms and returns the resulting values.
 +         *
 +         * @param terms the terms
 +         * @param options the query options
 +         * @return the value resulting from binding the query options to the specified terms
 +         * @throws InvalidRequestException if a problem occurs while binding the query options
 +         */
 +        private static List<ByteBuffer> bindAndGet(List<Term> terms, QueryOptions options) throws InvalidRequestException
 +        {
 +            List<ByteBuffer> buffers = new ArrayList<>(terms.size());
 +            for (Term value : terms)
 +                buffers.add(value.bindAndGet(options));
 +            return buffers;
 +        }
 +
 +        /**
 +         * Copies the keys and value from the first <code>Contains</code> to the second one.
 +         *
 +         * @param from the <code>Contains</code> to copy from
 +         * @param to the <code>Contains</code> to copy to
 +         */
 +        private static void copyKeysAndValues(Contains from, Contains to)
 +        {
 +            to.values.addAll(from.values);
 +            to.keys.addAll(from.keys);
 +            to.entryKeys.addAll(from.entryKeys);
 +            to.entryValues.addAll(from.entryValues);
 +        }
 +
 +        private Contains(ColumnDefinition columnDef)
 +        {
 +            super(columnDef);
 +        }
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/9583b6b3/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java
index 1547210,0000000..8035877
mode 100644,000000..100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java
@@@ -1,645 -1,0 +1,656 @@@
 +/*
 + * 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.restrictions;
 +
 +import java.nio.ByteBuffer;
 +import java.util.*;
 +
 +import com.google.common.base.Joiner;
 +import com.google.common.collect.Iterables;
 +
 +import org.apache.cassandra.config.CFMetaData;
 +import org.apache.cassandra.config.ColumnDefinition;
 +import org.apache.cassandra.cql3.*;
 +import org.apache.cassandra.cql3.functions.Function;
 +import org.apache.cassandra.cql3.statements.Bound;
 +import org.apache.cassandra.db.*;
 +import org.apache.cassandra.db.composites.Composite;
 +import org.apache.cassandra.db.index.SecondaryIndexManager;
 +import org.apache.cassandra.dht.*;
 +import org.apache.cassandra.exceptions.InvalidRequestException;
 +import org.apache.cassandra.service.StorageService;
 +import org.apache.cassandra.utils.ByteBufferUtil;
 +
 +import static org.apache.cassandra.config.ColumnDefinition.toIdentifiers;
 +import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse;
 +import static org.apache.cassandra.cql3.statements.RequestValidations.checkNotNull;
 +import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest;
 +
 +/**
 + * The restrictions corresponding to the relations specified on the where-clause of CQL query.
 + */
 +public final class StatementRestrictions
 +{
 +    public static final String REQUIRES_ALLOW_FILTERING_MESSAGE =
 +            "Cannot execute this query as it might involve data filtering and " +
 +            "thus may have unpredictable performance. If you want to execute " +
 +            "this query despite the performance unpredictability, use ALLOW FILTERING";
 +
 +    /**
 +     * The Column Family meta data
 +     */
 +    public final CFMetaData cfm;
 +
 +    /**
 +     * Restrictions on partitioning columns
 +     */
 +    private PrimaryKeyRestrictions partitionKeyRestrictions;
 +
 +    /**
 +     * Restrictions on clustering columns
 +     */
 +    private PrimaryKeyRestrictions clusteringColumnsRestrictions;
 +
 +    /**
 +     * Restriction on non-primary key columns (i.e. secondary index restrictions)
 +     */
 +    private RestrictionSet nonPrimaryKeyRestrictions;
 +
 +    /**
 +     * The restrictions used to build the index expressions
 +     */
 +    private final List<Restrictions> indexRestrictions = new ArrayList<>();
 +
 +    /**
 +     * <code>true</code> if the secondary index need to be queried, <code>false</code> otherwise
 +     */
 +    private boolean usesSecondaryIndexing;
 +
 +    /**
 +     * Specify if the query will return a range of partition keys.
 +     */
 +    private boolean isKeyRange;
 +
 +    /**
 +     * Creates a new empty <code>StatementRestrictions</code>.
 +     *
 +     * @param cfm the column family meta data
 +     * @return a new empty <code>StatementRestrictions</code>.
 +     */
 +    public static StatementRestrictions empty(CFMetaData cfm)
 +    {
 +        return new StatementRestrictions(cfm);
 +    }
 +
 +    private StatementRestrictions(CFMetaData cfm)
 +    {
 +        this.cfm = cfm;
 +        this.partitionKeyRestrictions = new PrimaryKeyRestrictionSet(cfm.getKeyValidatorAsCType());
 +        this.clusteringColumnsRestrictions = new PrimaryKeyRestrictionSet(cfm.comparator);
 +        this.nonPrimaryKeyRestrictions = new RestrictionSet();
 +    }
 +
 +    public StatementRestrictions(CFMetaData cfm,
 +                                 List<Relation> whereClause,
 +                                 VariableSpecifications boundNames,
 +                                 boolean selectsOnlyStaticColumns,
 +                                 boolean selectACollection,
 +                                 boolean allowFiltering)
 +    {
 +        this.cfm = cfm;
 +        this.partitionKeyRestrictions = new PrimaryKeyRestrictionSet(cfm.getKeyValidatorAsCType());
 +        this.clusteringColumnsRestrictions = new PrimaryKeyRestrictionSet(cfm.comparator);
 +        this.nonPrimaryKeyRestrictions = new RestrictionSet();
 +
 +        /*
 +         * WHERE clause. For a given entity, rules are: - EQ relation conflicts with anything else (including a 2nd EQ)
 +         * - Can't have more than one LT(E) relation (resp. GT(E) relation) - IN relation are restricted to row keys
 +         * (for now) and conflicts with anything else (we could allow two IN for the same entity but that doesn't seem
 +         * very useful) - The value_alias cannot be restricted in any way (we don't support wide rows with indexed value
 +         * in CQL so far)
 +         */
 +        for (Relation relation : whereClause)
 +            addRestriction(relation.toRestriction(cfm, boundNames));
 +
 +        SecondaryIndexManager secondaryIndexManager = Keyspace.open(cfm.ksName).getColumnFamilyStore(cfm.cfName).indexManager;
 +        boolean hasQueriableClusteringColumnIndex = clusteringColumnsRestrictions.hasSupportingIndex(secondaryIndexManager);
 +        boolean hasQueriableIndex = hasQueriableClusteringColumnIndex
 +                || partitionKeyRestrictions.hasSupportingIndex(secondaryIndexManager)
 +                || nonPrimaryKeyRestrictions.hasSupportingIndex(secondaryIndexManager);
 +
 +        // At this point, the select statement if fully constructed, but we still have a few things to validate
 +        processPartitionKeyRestrictions(hasQueriableIndex);
 +
 +        // Some but not all of the partition key columns have been specified;
 +        // hence we need turn these restrictions into index expressions.
 +        if (usesSecondaryIndexing)
 +            indexRestrictions.add(partitionKeyRestrictions);
 +
 +        checkFalse(selectsOnlyStaticColumns && hasClusteringColumnsRestriction(),
 +                   "Cannot restrict clustering columns when selecting only static columns");
 +
 +        processClusteringColumnsRestrictions(hasQueriableIndex, selectACollection);
 +
 +        // Covers indexes on the first clustering column (among others).
 +        if (isKeyRange && hasQueriableClusteringColumnIndex)
 +            usesSecondaryIndexing = true;
 +
 +        usesSecondaryIndexing = usesSecondaryIndexing || clusteringColumnsRestrictions.isContains();
 +
 +        if (usesSecondaryIndexing)
 +            indexRestrictions.add(clusteringColumnsRestrictions);
 +
 +        // Even if usesSecondaryIndexing is false at this point, we'll still have to use one if
 +        // there is restrictions not covered by the PK.
 +        if (!nonPrimaryKeyRestrictions.isEmpty())
 +        {
 +            if (!hasQueriableIndex)
 +            {
 +                // Filtering for non-index query is only supported for thrift static CFs
 +                if (cfm.comparator.isDense() ||  cfm.comparator.isCompound())
 +                    throw invalidRequest("Predicates on non-primary-key columns (%s) are not yet supported for non secondary index queries",
 +                                         Joiner.on(", ").join(toIdentifiers(nonPrimaryKeyRestrictions.getColumnDefs())));
 +
 +                if (!allowFiltering)
 +                    throw invalidRequest(REQUIRES_ALLOW_FILTERING_MESSAGE);
 +            }
 +            usesSecondaryIndexing = true;
 +            indexRestrictions.add(nonPrimaryKeyRestrictions);
 +        }
 +
 +        if (usesSecondaryIndexing)
 +            validateSecondaryIndexSelections(selectsOnlyStaticColumns);
 +    }
 +
 +    private void addRestriction(Restriction restriction) throws InvalidRequestException
 +    {
 +        if (restriction.isMultiColumn())
 +            clusteringColumnsRestrictions = clusteringColumnsRestrictions.mergeWith(restriction);
 +        else if (restriction.isOnToken())
 +            partitionKeyRestrictions = partitionKeyRestrictions.mergeWith(restriction);
 +        else
 +            addSingleColumnRestriction((SingleColumnRestriction) restriction);
 +    }
 +
 +    public Iterable<Function> getFunctions()
 +    {
 +        return Iterables.concat(partitionKeyRestrictions.getFunctions(),
 +                                clusteringColumnsRestrictions.getFunctions(),
 +                                nonPrimaryKeyRestrictions.getFunctions());
 +    }
 +
 +    private void addSingleColumnRestriction(SingleColumnRestriction restriction) throws InvalidRequestException
 +    {
 +        ColumnDefinition def = restriction.columnDef;
 +        if (def.isPartitionKey())
 +            partitionKeyRestrictions = partitionKeyRestrictions.mergeWith(restriction);
 +        else if (def.isClusteringColumn())
 +            clusteringColumnsRestrictions = clusteringColumnsRestrictions.mergeWith(restriction);
 +        else
 +            nonPrimaryKeyRestrictions = nonPrimaryKeyRestrictions.addRestriction(restriction);
 +    }
 +
 +    /**
 +     * Checks if the restrictions on the partition key is an IN restriction.
 +     *
 +     * @return <code>true</code> the restrictions on the partition key is an IN restriction, <code>false</code>
 +     * otherwise.
 +     */
 +    public boolean keyIsInRelation()
 +    {
 +        return partitionKeyRestrictions.isIN();
 +    }
 +
 +    /**
 +     * Checks if the query request a range of partition keys.
 +     *
 +     * @return <code>true</code> if the query request a range of partition keys, <code>false</code> otherwise.
 +     */
 +    public boolean isKeyRange()
 +    {
 +        return this.isKeyRange;
 +    }
 +
 +    /**
 +     * Checks if the secondary index need to be queried.
 +     *
 +     * @return <code>true</code> if the secondary index need to be queried, <code>false</code> otherwise.
 +     */
 +    public boolean usesSecondaryIndexing()
 +    {
 +        return this.usesSecondaryIndexing;
 +    }
 +
 +    private void processPartitionKeyRestrictions(boolean hasQueriableIndex) throws InvalidRequestException
 +    {
 +        // If there is a queriable index, no special condition are required on the other restrictions.
 +        // But we still need to know 2 things:
 +        // - If we don't have a queriable index, is the query ok
 +        // - Is it queriable without 2ndary index, which is always more efficient
 +        // If a component of the partition key is restricted by a relation, all preceding
 +        // components must have a EQ. Only the last partition key component can be in IN relation.
 +        if (partitionKeyRestrictions.isOnToken())
 +            isKeyRange = true;
 +
 +        if (hasPartitionKeyUnrestrictedComponents())
 +        {
 +            if (!partitionKeyRestrictions.isEmpty())
 +            {
 +                if (!hasQueriableIndex)
 +                    throw invalidRequest("Partition key parts: %s must be restricted as other parts are",
 +                                         Joiner.on(", ").join(getPartitionKeyUnrestrictedComponents()));
 +            }
 +
 +            isKeyRange = true;
 +            usesSecondaryIndexing = hasQueriableIndex;
 +        }
 +    }
 +
 +    /**
 +     * Checks if the partition key has some unrestricted components.
 +     * @return <code>true</code> if the partition key has some unrestricted components, <code>false</code> otherwise.
 +     */
 +    private boolean hasPartitionKeyUnrestrictedComponents()
 +    {
 +        return partitionKeyRestrictions.size() <  cfm.partitionKeyColumns().size();
 +    }
 +
 +    public boolean hasPartitionKeyRestrictions()
 +    {
 +        return !partitionKeyRestrictions.isEmpty();
 +    }
 +
 +    /**
 +     * Checks if the restrictions contain any non-primary key restrictions
 +     * @return <code>true</code> if the restrictions contain any non-primary key restrictions, <code>false</code> otherwise.
 +     */
 +    public boolean hasNonPrimaryKeyRestrictions()
 +    {
 +        return !nonPrimaryKeyRestrictions.isEmpty();
 +    }
 +
 +    /**
 +     * Returns the partition key components that are not restricted.
 +     * @return the partition key components that are not restricted.
 +     */
 +    private List<ColumnIdentifier> getPartitionKeyUnrestrictedComponents()
 +    {
 +        List<ColumnDefinition> list = new ArrayList<>(cfm.partitionKeyColumns());
 +        list.removeAll(partitionKeyRestrictions.getColumnDefs());
 +        return ColumnDefinition.toIdentifiers(list);
 +    }
 +
 +    /**
 +     * Processes the clustering column restrictions.
 +     *
 +     * @param hasQueriableIndex <code>true</code> if some of the queried data are indexed, <code>false</code> otherwise
 +     * @param selectACollection <code>true</code> if the query should return a collection column
 +     * @throws InvalidRequestException if the request is invalid
 +     */
 +    private void processClusteringColumnsRestrictions(boolean hasQueriableIndex,
 +                                                      boolean selectACollection) throws InvalidRequestException
 +    {
 +        validateClusteringRestrictions(hasQueriableIndex);
 +
 +        checkFalse(clusteringColumnsRestrictions.isIN() && selectACollection,
 +                   "Cannot restrict clustering columns by IN relations when a collection is selected by the query");
 +        checkFalse(clusteringColumnsRestrictions.isContains() && !hasQueriableIndex,
 +                   "Cannot restrict clustering columns by a CONTAINS relation without a secondary index");
 +
 +        if (hasClusteringColumnsRestriction() && clusteringRestrictionsNeedFiltering())
 +        {
 +            if (hasQueriableIndex)
 +            {
 +                usesSecondaryIndexing = true;
 +                return;
 +            }
 +
 +            List<ColumnDefinition> clusteringColumns = cfm.clusteringColumns();
 +            List<ColumnDefinition> restrictedColumns = new LinkedList<>(clusteringColumnsRestrictions.getColumnDefs());
 +
 +            for (int i = 0, m = restrictedColumns.size(); i < m; i++)
 +            {
 +                ColumnDefinition clusteringColumn = clusteringColumns.get(i);
 +                ColumnDefinition restrictedColumn = restrictedColumns.get(i);
 +
 +                if (!clusteringColumn.equals(restrictedColumn))
 +                {
 +                    throw invalidRequest(
 +                              "PRIMARY KEY column \"%s\" cannot be restricted as preceding column \"%s\" is not restricted",
 +                              restrictedColumn.name,
 +                              clusteringColumn.name);
 +                }
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Validates whether or not restrictions are allowed for execution when secondary index is not used.
 +     */
 +    public final void validateClusteringRestrictions(boolean hasQueriableIndex)
 +    {
 +        assert clusteringColumnsRestrictions instanceof PrimaryKeyRestrictionSet;
 +
 +        // If there's a queriable index, filtering will take care of clustering restrictions
 +        if (hasQueriableIndex)
 +            return;
 +
 +        Iterator<Restriction> iter = ((PrimaryKeyRestrictionSet)clusteringColumnsRestrictions).iterator();
 +        Restriction previousRestriction = null;
 +
 +        while (iter.hasNext())
 +        {
 +            Restriction restriction = iter.next();
 +
 +            if (previousRestriction != null)
 +            {
 +                ColumnDefinition lastRestrictionStart = previousRestriction.getFirstColumn();
 +                ColumnDefinition newRestrictionStart = restriction.getFirstColumn();
 +
 +                if (previousRestriction.isSlice() && newRestrictionStart.position() > lastRestrictionStart.position())
 +                    throw invalidRequest("Clustering column \"%s\" cannot be restricted (preceding column \"%s\" is restricted by a non-EQ relation)",
 +                                         newRestrictionStart.name,
 +                                         lastRestrictionStart.name);
 +            }
 +            previousRestriction = restriction;
 +        }
 +    }
 +
 +    public final boolean clusteringRestrictionsNeedFiltering()
 +    {
 +        assert clusteringColumnsRestrictions instanceof PrimaryKeyRestrictionSet;
 +        return ((PrimaryKeyRestrictionSet) clusteringColumnsRestrictions).needsFiltering();
 +    }
 +
 +    public List<IndexExpression> getIndexExpressions(SecondaryIndexManager indexManager,
 +                                                     QueryOptions options) throws InvalidRequestException
 +    {
 +        if (!usesSecondaryIndexing || indexRestrictions.isEmpty())
 +            return Collections.emptyList();
 +
 +        List<IndexExpression> expressions = new ArrayList<>();
 +        for (Restrictions restrictions : indexRestrictions)
 +            restrictions.addIndexExpressionTo(expressions, indexManager, options);
 +
 +        return expressions;
 +    }
 +
 +    /**
 +     * Returns the partition keys for which the data is requested.
 +     *
 +     * @param options the query options
 +     * @return the partition keys for which the data is requested.
 +     * @throws InvalidRequestException if the partition keys cannot be retrieved
 +     */
 +    public Collection<ByteBuffer> getPartitionKeys(final QueryOptions options) throws InvalidRequestException
 +    {
-         return partitionKeyRestrictions.values(options);
++        return partitionKeyRestrictions.values(cfm, options);
 +    }
 +
 +    /**
 +     * Returns the specified bound of the partition key.
 +     *
 +     * @param b the boundary type
 +     * @param options the query options
 +     * @return the specified bound of the partition key
 +     * @throws InvalidRequestException if the boundary cannot be retrieved
 +     */
 +    private ByteBuffer getPartitionKeyBound(Bound b, QueryOptions options) throws InvalidRequestException
 +    {
 +        // Deal with unrestricted partition key components (special-casing is required to deal with 2i queries on the
 +        // first
 +        // component of a composite partition key).
 +        if (hasPartitionKeyUnrestrictedComponents())
 +            return ByteBufferUtil.EMPTY_BYTE_BUFFER;
 +
 +        // We deal with IN queries for keys in other places, so we know buildBound will return only one result
-         return partitionKeyRestrictions.bounds(b, options).get(0);
++        return partitionKeyRestrictions.bounds(cfm, b, options).get(0);
 +    }
 +
 +    /**
 +     * Returns the partition key bounds.
 +     *
 +     * @param options the query options
 +     * @return the partition key bounds
 +     * @throws InvalidRequestException if the query is invalid
 +     */
 +    public AbstractBounds<RowPosition> getPartitionKeyBounds(QueryOptions options) throws InvalidRequestException
 +    {
 +        IPartitioner p = StorageService.getPartitioner();
 +
 +        if (partitionKeyRestrictions.isOnToken())
 +        {
 +            return getPartitionKeyBoundsForTokenRestrictions(p, options);
 +        }
 +
 +        return getPartitionKeyBounds(p, options);
 +    }
 +
 +    private AbstractBounds<RowPosition> getPartitionKeyBounds(IPartitioner p,
 +                                                              QueryOptions options) throws InvalidRequestException
 +    {
 +        ByteBuffer startKeyBytes = getPartitionKeyBound(Bound.START, options);
 +        ByteBuffer finishKeyBytes = getPartitionKeyBound(Bound.END, options);
 +
 +        RowPosition startKey = RowPosition.ForKey.get(startKeyBytes, p);
 +        RowPosition finishKey = RowPosition.ForKey.get(finishKeyBytes, p);
 +
 +        if (startKey.compareTo(finishKey) > 0 && !finishKey.isMinimum())
 +            return null;
 +
 +        if (partitionKeyRestrictions.isInclusive(Bound.START))
 +        {
 +            return partitionKeyRestrictions.isInclusive(Bound.END)
 +                    ? new Bounds<>(startKey, finishKey)
 +                    : new IncludingExcludingBounds<>(startKey, finishKey);
 +        }
 +
 +        return partitionKeyRestrictions.isInclusive(Bound.END)
 +                ? new Range<>(startKey, finishKey)
 +                : new ExcludingBounds<>(startKey, finishKey);
 +    }
 +
 +    private AbstractBounds<RowPosition> getPartitionKeyBoundsForTokenRestrictions(IPartitioner p,
 +                                                                                  QueryOptions options)
 +                                                                                          throws InvalidRequestException
 +    {
 +        Token startToken = getTokenBound(Bound.START, options, p);
 +        Token endToken = getTokenBound(Bound.END, options, p);
 +
 +        boolean includeStart = partitionKeyRestrictions.isInclusive(Bound.START);
 +        boolean includeEnd = partitionKeyRestrictions.isInclusive(Bound.END);
 +
 +        /*
 +         * If we ask SP.getRangeSlice() for (token(200), token(200)], it will happily return the whole ring.
 +         * However, wrapping range doesn't really make sense for CQL, and we want to return an empty result in that
 +         * case (CASSANDRA-5573). So special case to create a range that is guaranteed to be empty.
 +         *
 +         * In practice, we want to return an empty result set if either startToken > endToken, or both are equal but
 +         * one of the bound is excluded (since [a, a] can contains something, but not (a, a], [a, a) or (a, a)).
 +         * Note though that in the case where startToken or endToken is the minimum token, then this special case
 +         * rule should not apply.
 +         */
 +        int cmp = startToken.compareTo(endToken);
 +        if (!startToken.isMinimum() && !endToken.isMinimum()
 +                && (cmp > 0 || (cmp == 0 && (!includeStart || !includeEnd))))
 +            return null;
 +
 +        RowPosition start = includeStart ? startToken.minKeyBound() : startToken.maxKeyBound();
 +        RowPosition end = includeEnd ? endToken.maxKeyBound() : endToken.minKeyBound();
 +
 +        return new Range<>(start, end);
 +    }
 +
 +    private Token getTokenBound(Bound b, QueryOptions options, IPartitioner p) throws InvalidRequestException
 +    {
 +        if (!partitionKeyRestrictions.hasBound(b))
 +            return p.getMinimumToken();
 +
-         ByteBuffer value = partitionKeyRestrictions.bounds(b, options).get(0);
++        ByteBuffer value = partitionKeyRestrictions.bounds(cfm, b, options).get(0);
 +        checkNotNull(value, "Invalid null token value");
 +        return p.getTokenFactory().fromByteArray(value);
 +    }
 +
 +    /**
 +     * Checks if the query does not contains any restriction on the clustering columns.
 +     *
 +     * @return <code>true</code> if the query does not contains any restriction on the clustering columns,
 +     * <code>false</code> otherwise.
 +     */
 +    public boolean hasNoClusteringColumnsRestriction()
 +    {
 +        return clusteringColumnsRestrictions.isEmpty();
 +    }
 +
 +    /**
 +     * Checks if the query has some restrictions on the clustering columns.
 +     *
 +     * @return <code>true</code> if the query has some restrictions on the clustering columns,
 +     * <code>false</code> otherwise.
 +     */
 +    public boolean hasClusteringColumnsRestriction()
 +    {
 +        return !clusteringColumnsRestrictions.isEmpty();
 +    }
 +
 +    // For non-composite slices, we don't support internally the difference between exclusive and
 +    // inclusive bounds, so we deal with it manually.
 +    public boolean isNonCompositeSliceWithExclusiveBounds()
 +    {
 +        return !cfm.comparator.isCompound()
 +                && clusteringColumnsRestrictions.isSlice()
 +                && (!clusteringColumnsRestrictions.isInclusive(Bound.START) || !clusteringColumnsRestrictions.isInclusive(Bound.END));
 +    }
 +
 +    /**
 +     * Returns the requested clustering columns as <code>Composite</code>s.
 +     *
 +     * @param options the query options
 +     * @return the requested clustering columns as <code>Composite</code>s
 +     * @throws InvalidRequestException if the query is not valid
 +     */
 +    public List<Composite> getClusteringColumnsAsComposites(QueryOptions options) throws InvalidRequestException
 +    {
-         return clusteringColumnsRestrictions.valuesAsComposites(options);
++        return clusteringColumnsRestrictions.valuesAsComposites(cfm, options);
 +    }
 +
 +    /**
 +     * Returns the bounds (start or end) of the clustering columns as <code>Composites</code>.
 +     *
 +     * @param b the bound type
 +     * @param options the query options
 +     * @return the bounds (start or end) of the clustering columns as <code>Composites</code>
 +     * @throws InvalidRequestException if the request is not valid
 +     */
 +    public List<Composite> getClusteringColumnsBoundsAsComposites(Bound b,
 +                                                                  QueryOptions options) throws InvalidRequestException
 +    {
-         List<Composite> bounds = clusteringColumnsRestrictions.boundsAsComposites(b, options);
++        List<Composite> bounds = clusteringColumnsRestrictions.boundsAsComposites(cfm, b, options);
 +        for (Composite c : bounds) {
 +            if (!c.isEmpty())
 +                QueryProcessor.validateComposite(c, cfm.comparator);
 +        }
 +        return bounds;
 +    }
 +
 +    /**
 +     * Returns the bounds (start or end) of the clustering columns.
 +     *
 +     * @param b the bound type
 +     * @param options the query options
 +     * @return the bounds (start or end) of the clustering columns
 +     * @throws InvalidRequestException if the request is not valid
 +     */
 +    public List<ByteBuffer> getClusteringColumnsBounds(Bound b, QueryOptions options) throws InvalidRequestException
 +    {
-         return clusteringColumnsRestrictions.bounds(b, options);
++        return clusteringColumnsRestrictions.bounds(cfm, b, options);
 +    }
 +
 +    /**
 +     * Checks if the bounds (start or end) of the clustering columns are inclusive.
 +     *
 +     * @param bound the bound type
 +     * @return <code>true</code> if the bounds (start or end) of the clustering columns are inclusive,
 +     * <code>false</code> otherwise
 +     */
 +    public boolean areRequestedBoundsInclusive(Bound bound)
 +    {
 +        return clusteringColumnsRestrictions.isInclusive(bound);
 +    }
 +
 +    /**
 +     * Checks if the query returns a range of columns.
 +     *
 +     * @return <code>true</code> if the query returns a range of columns, <code>false</code> otherwise.
 +     */
 +    public boolean isColumnRange()
 +    {
 +        // Due to CASSANDRA-5762, we always do a slice for CQL3 tables (not dense, composite).
 +        // Static CF (non dense but non composite) never entails a column slice however
 +        if (!cfm.comparator.isDense())
 +            return cfm.comparator.isCompound();
 +
 +        // Otherwise (i.e. for compact table where we don't have a row marker anyway and thus don't care about
 +        // CASSANDRA-5762),
 +        // it is a range query if it has at least one the column alias for which no relation is defined or is not EQ.
 +        return clusteringColumnsRestrictions.size() < cfm.clusteringColumns().size() || clusteringColumnsRestrictions.isSlice();
 +    }
 +
 +    /**
 +     * Checks if the query need to use filtering.
 +     * @return <code>true</code> if the query need to use filtering, <code>false</code> otherwise.
 +     */
 +    public boolean needFiltering()
 +    {
 +        int numberOfRestrictedColumns = 0;
 +        for (Restrictions restrictions : indexRestrictions)
 +            numberOfRestrictedColumns += restrictions.size();
 +
 +        return numberOfRestrictedColumns > 1
 +                || (numberOfRestrictedColumns == 0 && !clusteringColumnsRestrictions.isEmpty())
 +                || (numberOfRestrictedColumns != 0
 +                        && nonPrimaryKeyRestrictions.hasMultipleContains());
 +    }
 +
 +    private void validateSecondaryIndexSelections(boolean selectsOnlyStaticColumns) throws InvalidRequestException
 +    {
 +        checkFalse(keyIsInRelation(),
 +                   "Select on indexed columns and with IN clause for the PRIMARY KEY are not supported");
 +        // When the user only select static columns, the intent is that we don't query the whole partition but just
 +        // the static parts. But 1) we don't have an easy way to do that with 2i and 2) since we don't support index on
 +        // static columns
 +        // so far, 2i means that you've restricted a non static column, so the query is somewhat non-sensical.
 +        checkFalse(selectsOnlyStaticColumns, "Queries using 2ndary indexes don't support selecting only static columns");
 +    }
 +
 +    public void reverse()
 +    {
 +        clusteringColumnsRestrictions = new ReversedPrimaryKeyRestrictions(clusteringColumnsRestrictions);
 +    }
++
++    /**
++     * 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
++     */
++    public boolean isNotReturningAnyRows(QueryOptions options)
++    {
++        return clusteringColumnsRestrictions.isNotReturningAnyRows(cfm, options);
++    }
 +}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/9583b6b3/src/java/org/apache/cassandra/cql3/restrictions/TokenFilter.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/cql3/restrictions/TokenFilter.java
index bd04610,0000000..18444ec
mode 100644,000000..100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/TokenFilter.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/TokenFilter.java
@@@ -1,237 -1,0 +1,245 @@@
 +/*
 + * 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.restrictions;
 +
 +import java.nio.ByteBuffer;
 +import java.util.ArrayList;
 +import java.util.List;
 +
 +import com.google.common.collect.BoundType;
 +import com.google.common.collect.ImmutableRangeSet;
 +import com.google.common.collect.Range;
 +import com.google.common.collect.RangeSet;
 +
++import org.apache.cassandra.config.CFMetaData;
 +import org.apache.cassandra.cql3.QueryOptions;
 +import org.apache.cassandra.cql3.statements.Bound;
 +import org.apache.cassandra.db.composites.Composite;
 +import org.apache.cassandra.dht.IPartitioner;
 +import org.apache.cassandra.dht.Token;
 +import org.apache.cassandra.exceptions.InvalidRequestException;
 +import org.apache.cassandra.service.StorageService;
 +
 +import static org.apache.cassandra.cql3.statements.Bound.END;
 +import static org.apache.cassandra.cql3.statements.Bound.START;
 +
 +/**
 + * <code>Restriction</code> decorator used to merge non-token restriction and token restriction on partition keys.
 + */
 +final class TokenFilter extends ForwardingPrimaryKeyRestrictions
 +{
 +    /**
 +     * The decorated restriction
 +     */
 +    private PrimaryKeyRestrictions restrictions;
 +
 +    /**
 +     * The restriction on the token
 +     */
 +    private TokenRestriction tokenRestriction;
 +
 +    /**
 +     * The partitioner
 +     */
 +    private static final IPartitioner partitioner = StorageService.getPartitioner();
 +
 +    @Override
 +    protected PrimaryKeyRestrictions getDelegate()
 +    {
 +        return restrictions;
 +    }
 +
 +    @Override
 +    public boolean isOnToken()
 +    {
 +        // if all partition key columns have non-token restrictions, we can simply use the token range to filter
 +        // those restrictions and then ignore the token range
 +        return restrictions.size() < tokenRestriction.size();
 +    }
 +
 +    public TokenFilter(PrimaryKeyRestrictions restrictions, TokenRestriction tokenRestriction)
 +    {
 +        this.restrictions = restrictions;
 +        this.tokenRestriction = tokenRestriction;
 +    }
 +
 +    @Override
-     public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException
++    public List<ByteBuffer> values(CFMetaData cfm, QueryOptions options) throws InvalidRequestException
 +    {
-         return filter(restrictions.values(options), options);
++        return filter(cfm, restrictions.values(cfm, options), options);
 +    }
 +
 +    @Override
-     public List<Composite> valuesAsComposites(QueryOptions options) throws InvalidRequestException
++    public List<Composite> valuesAsComposites(CFMetaData cfm, QueryOptions options) throws InvalidRequestException
 +    {
 +        throw new UnsupportedOperationException();
 +    }
 +
 +    @Override
 +    public PrimaryKeyRestrictions mergeWith(Restriction restriction) throws InvalidRequestException
 +    {
 +        if (restriction.isOnToken())
 +            return new TokenFilter(restrictions, (TokenRestriction) tokenRestriction.mergeWith(restriction));
 +
 +        return new TokenFilter(super.mergeWith(restriction), tokenRestriction);
 +    }
 +
 +    @Override
 +    public boolean isInclusive(Bound bound)
 +    {
 +        return tokenRestriction.isInclusive(bound);
 +    }
 +
 +    @Override
 +    public boolean hasBound(Bound b)
 +    {
 +        return tokenRestriction.hasBound(b);
 +    }
 +
 +    @Override
-     public List<ByteBuffer> bounds(Bound bound, QueryOptions options) throws InvalidRequestException
++    public List<ByteBuffer> bounds(CFMetaData cfm, Bound bound, QueryOptions options) throws InvalidRequestException
 +    {
-         return tokenRestriction.bounds(bound, options);
++        return tokenRestriction.bounds(cfm, bound, options);
 +    }
 +
 +    @Override
-     public List<Composite> boundsAsComposites(Bound bound, QueryOptions options) throws InvalidRequestException
++    public List<Composite> boundsAsComposites(CFMetaData cfm, Bound bound, QueryOptions options) throws InvalidRequestException
 +    {
-         return tokenRestriction.boundsAsComposites(bound, options);
++        return tokenRestriction.boundsAsComposites(cfm, bound, options);
 +    }
 +
 +    /**
 +     * Filter the values returned by the restriction.
 +     *
++     * @param cfm the table metadata
 +     * @param values the values returned by the decorated restriction
 +     * @param options the query options
 +     * @return the values matching the token restriction
 +     * @throws InvalidRequestException if the request is invalid
 +     */
-     private List<ByteBuffer> filter(List<ByteBuffer> values, QueryOptions options) throws InvalidRequestException
++    private List<ByteBuffer> filter(CFMetaData cfm, List<ByteBuffer> values, QueryOptions options) throws InvalidRequestException
 +    {
-         RangeSet<Token> rangeSet = tokenRestriction.isSlice() ? toRangeSet(tokenRestriction, options)
-                                                               : toRangeSet(tokenRestriction.values(options));
++        RangeSet<Token> rangeSet = tokenRestriction.isSlice() ? toRangeSet(cfm, tokenRestriction, options)
++                                                              : toRangeSet(tokenRestriction.values(cfm, options));
 +
 +        return filterWithRangeSet(rangeSet, values);
 +    }
 +
 +    /**
 +     * Filter out the values for which the tokens are not included within the specified range.
 +     *
 +     * @param tokens the tokens range
 +     * @param values the restricted values
 +     * @return the values for which the tokens are not included within the specified range.
 +     */
 +    private static List<ByteBuffer> filterWithRangeSet(RangeSet<Token> tokens, List<ByteBuffer> values)
 +    {
 +        List<ByteBuffer> remaining = new ArrayList<>();
 +
 +        for (ByteBuffer value : values)
 +        {
 +            Token token = partitioner.getToken(value);
 +
 +            if (!tokens.contains(token))
 +                continue;
 +
 +            remaining.add(value);
 +        }
 +        return remaining;
 +    }
 +
 +    /**
 +     * Converts the specified list into a range set.
 +     *
 +     * @param buffers the token restriction values
 +     * @return the range set corresponding to the specified list
 +     */
 +    private static RangeSet<Token> toRangeSet(List<ByteBuffer> buffers)
 +    {
 +        ImmutableRangeSet.Builder<Token> builder = ImmutableRangeSet.builder();
 +
 +        for (ByteBuffer buffer : buffers)
 +            builder.add(Range.singleton(deserializeToken(buffer)));
 +
 +        return builder.build();
 +    }
 +
 +    /**
 +     * Converts the specified slice into a range set.
 +     *
++     * @param cfm the table metadata
 +     * @param slice the slice to convert
 +     * @param options the query option
 +     * @return the range set corresponding to the specified slice
 +     * @throws InvalidRequestException if the request is invalid
 +     */
-     private static RangeSet<Token> toRangeSet(TokenRestriction slice, QueryOptions options) throws InvalidRequestException
++    private static RangeSet<Token> toRangeSet(CFMetaData cfm, TokenRestriction slice, QueryOptions options) throws InvalidRequestException
 +    {
 +        if (slice.hasBound(START))
 +        {
-             Token start = deserializeToken(slice.bounds(START, options).get(0));
++            Token start = deserializeToken(slice.bounds(cfm, START, options).get(0));
 +
 +            BoundType startBoundType = toBoundType(slice.isInclusive(START));
 +
 +            if (slice.hasBound(END))
 +            {
 +                BoundType endBoundType = toBoundType(slice.isInclusive(END));
-                 Token end = deserializeToken(slice.bounds(END, options).get(0));
++                Token end = deserializeToken(slice.bounds(cfm, END, options).get(0));
 +
 +                if (start.equals(end) && (BoundType.OPEN == startBoundType || BoundType.OPEN == endBoundType))
 +                    return ImmutableRangeSet.of();
 +
 +                if (start.compareTo(end) <= 0)
 +                    return ImmutableRangeSet.of(Range.range(start,
 +                                                            startBoundType,
 +                                                            end,
 +                                                            endBoundType));
 +
 +                return ImmutableRangeSet.<Token> builder()
 +                                        .add(Range.upTo(end, endBoundType))
 +                                        .add(Range.downTo(start, startBoundType))
 +                                        .build();
 +            }
 +            return ImmutableRangeSet.of(Range.downTo(start,
 +                                                     startBoundType));
 +        }
-         Token end = deserializeToken(slice.bounds(END, options).get(0));
++        Token end = deserializeToken(slice.bounds(cfm, END, options).get(0));
 +        return ImmutableRangeSet.of(Range.upTo(end, toBoundType(slice.isInclusive(END))));
 +    }
 +
++    public boolean isNotReturningAnyRows(CFMetaData cfm, QueryOptions options)
++    {
++        return false;
++    }
++
 +    /**
 +     * Deserializes the token corresponding to the specified buffer.
 +     *
 +     * @param buffer the buffer
 +     * @return the token corresponding to the specified buffer
 +     */
 +    private static Token deserializeToken(ByteBuffer buffer)
 +    {
 +        return partitioner.getTokenFactory().fromByteArray(buffer);
 +    }
 +
 +    private static BoundType toBoundType(boolean inclusive)
 +    {
 +        return inclusive ? BoundType.CLOSED : BoundType.OPEN;
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/9583b6b3/src/java/org/apache/cassandra/cql3/restrictions/TokenRestriction.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/cql3/restrictions/TokenRestriction.java
index 3cd3304,0000000..97c55c4
mode 100644,000000..100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/TokenRestriction.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/TokenRestriction.java
@@@ -1,273 -1,0 +1,274 @@@
 +/*
 + * 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.restrictions;
 +
 +import java.nio.ByteBuffer;
 +import java.util.Collections;
 +import java.util.List;
 +
 +import com.google.common.base.Joiner;
 +
++import org.apache.cassandra.config.CFMetaData;
 +import org.apache.cassandra.config.ColumnDefinition;
 +import org.apache.cassandra.cql3.QueryOptions;
 +import org.apache.cassandra.cql3.Term;
 +import org.apache.cassandra.cql3.functions.Function;
 +import org.apache.cassandra.cql3.statements.Bound;
 +import org.apache.cassandra.db.IndexExpression;
 +import org.apache.cassandra.db.composites.CType;
 +import org.apache.cassandra.db.composites.Composite;
 +import org.apache.cassandra.db.composites.CompositesBuilder;
 +import org.apache.cassandra.db.index.SecondaryIndexManager;
 +import org.apache.cassandra.exceptions.InvalidRequestException;
 +
 +import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest;
 +
 +/**
 + * <code>Restriction</code> using the token function.
 + */
 +public abstract class TokenRestriction extends AbstractPrimaryKeyRestrictions
 +{
 +    /**
 +     * The definition of the columns to which apply the token restriction.
 +     */
 +    protected final List<ColumnDefinition> columnDefs;
 +
 +    /**
 +     * Creates a new <code>TokenRestriction</code> that apply to the specified columns.
 +     *
 +     * @param ctype the composite type
 +     * @param columnDefs the definition of the columns to which apply the token restriction
 +     */
 +    public TokenRestriction(CType ctype, List<ColumnDefinition> columnDefs)
 +    {
 +        super(ctype);
 +        this.columnDefs = columnDefs;
 +    }
 +
 +    @Override
 +    public  boolean isOnToken()
 +    {
 +        return true;
 +    }
 +
 +    @Override
 +    public List<ColumnDefinition> getColumnDefs()
 +    {
 +        return columnDefs;
 +    }
 +
 +    @Override
 +    public ColumnDefinition getFirstColumn()
 +    {
 +        return columnDefs.get(0);
 +    }
 +
 +    @Override
 +    public ColumnDefinition getLastColumn()
 +    {
 +        return columnDefs.get(columnDefs.size() - 1);
 +    }
 +
 +    @Override
 +    public boolean hasSupportingIndex(SecondaryIndexManager secondaryIndexManager)
 +    {
 +        return false;
 +    }
 +
 +    @Override
 +    public final void addIndexExpressionTo(List<IndexExpression> expressions,
 +                                     SecondaryIndexManager indexManager,
 +                                     QueryOptions options)
 +    {
 +        throw new UnsupportedOperationException("Index expression cannot be created for token restriction");
 +    }
 +
 +    @Override
-     public CompositesBuilder appendTo(CompositesBuilder builder, QueryOptions options)
++    public CompositesBuilder appendTo(CFMetaData cfm, CompositesBuilder builder, QueryOptions options)
 +    {
 +        throw new UnsupportedOperationException();
 +    }
 +
 +    @Override
-     public List<Composite> valuesAsComposites(QueryOptions options) throws InvalidRequestException
++    public List<Composite> valuesAsComposites(CFMetaData cfm, QueryOptions options) throws InvalidRequestException
 +    {
 +        throw new UnsupportedOperationException();
 +    }
 +
 +    @Override
-     public List<Composite> boundsAsComposites(Bound bound, QueryOptions options) throws InvalidRequestException
++    public List<Composite> boundsAsComposites(CFMetaData cfm, Bound bound, QueryOptions options) throws InvalidRequestException
 +    {
 +        throw new UnsupportedOperationException();
 +    }
 +
 +    /**
 +     * Returns the column names as a comma separated <code>String</code>.
 +     *
 +     * @return the column names as a comma separated <code>String</code>.
 +     */
 +    protected final String getColumnNamesAsString()
 +    {
 +        return Joiner.on(", ").join(ColumnDefinition.toIdentifiers(columnDefs));
 +    }
 +
 +    @Override
 +    public final PrimaryKeyRestrictions mergeWith(Restriction otherRestriction) throws InvalidRequestException
 +    {
 +        if (!otherRestriction.isOnToken())
 +            return new TokenFilter(toPrimaryKeyRestriction(otherRestriction), this);
 +
 +        return doMergeWith((TokenRestriction) otherRestriction);
 +    }
 +
 +    /**
 +     * Merges this restriction with the specified <code>TokenRestriction</code>.
 +     * @param otherRestriction the <code>TokenRestriction</code> to merge with.
 +     */
 +    protected abstract PrimaryKeyRestrictions doMergeWith(TokenRestriction otherRestriction) throws InvalidRequestException;
 +
 +    /**
 +     * Converts the specified restriction into a <code>PrimaryKeyRestrictions</code>.
 +     *
 +     * @param restriction the restriction to convert
 +     * @return a <code>PrimaryKeyRestrictions</code>
 +     * @throws InvalidRequestException if a problem occurs while converting the restriction
 +     */
 +    private PrimaryKeyRestrictions toPrimaryKeyRestriction(Restriction restriction) throws InvalidRequestException
 +    {
 +        if (restriction instanceof PrimaryKeyRestrictions)
 +            return (PrimaryKeyRestrictions) restriction;
 +
 +        return new PrimaryKeyRestrictionSet(ctype).mergeWith(restriction);
 +    }
 +
 +    public static final class EQ extends TokenRestriction
 +    {
 +        private final Term value;
 +
 +        public EQ(CType ctype, List<ColumnDefinition> columnDefs, Term value)
 +        {
 +            super(ctype, columnDefs);
 +            this.value = value;
 +        }
 +
 +        @Override
 +        public boolean isEQ()
 +        {
 +            return true;
 +        }
 +
 +        @Override
 +        public Iterable<Function> getFunctions()
 +        {
 +            return value.getFunctions();
 +        }
 +
 +        @Override
 +        protected PrimaryKeyRestrictions doMergeWith(TokenRestriction otherRestriction) throws InvalidRequestException
 +        {
 +            throw invalidRequest("%s cannot be restricted by more than one relation if it includes an Equal",
 +                                 Joiner.on(", ").join(ColumnDefinition.toIdentifiers(columnDefs)));
 +        }
 +
 +        @Override
-         public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException
++        public List<ByteBuffer> values(CFMetaData cfm, QueryOptions options) throws InvalidRequestException
 +        {
 +            return Collections.singletonList(value.bindAndGet(options));
 +        }
 +    }
 +
 +    public static class Slice extends TokenRestriction
 +    {
 +        private final TermSlice slice;
 +
 +        public Slice(CType ctype, List<ColumnDefinition> columnDefs, Bound bound, boolean inclusive, Term term)
 +        {
 +            super(ctype, columnDefs);
 +            slice = TermSlice.newInstance(bound, inclusive, term);
 +        }
 +
 +        @Override
 +        public boolean isSlice()
 +        {
 +            return true;
 +        }
 +
 +        @Override
-         public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException
++        public List<ByteBuffer> values(CFMetaData cfm, QueryOptions options) throws InvalidRequestException
 +        {
 +            throw new UnsupportedOperationException();
 +        }
 +
 +        @Override
 +        public boolean hasBound(Bound b)
 +        {
 +            return slice.hasBound(b);
 +        }
 +
 +        @Override
-         public List<ByteBuffer> bounds(Bound b, QueryOptions options) throws InvalidRequestException
++        public List<ByteBuffer> bounds(CFMetaData cfm, Bound b, QueryOptions options) throws InvalidRequestException
 +        {
 +            return Collections.singletonList(slice.bound(b).bindAndGet(options));
 +        }
 +
 +        @Override
 +        public Iterable<Function> getFunctions()
 +        {
 +            return slice.getFunctions();
 +        }
 +
 +        @Override
 +        public boolean isInclusive(Bound b)
 +        {
 +            return slice.isInclusive(b);
 +        }
 +
 +        @Override
 +        protected PrimaryKeyRestrictions doMergeWith(TokenRestriction otherRestriction)
 +        throws InvalidRequestException
 +        {
 +            if (!otherRestriction.isSlice())
 +                throw invalidRequest("Columns \"%s\" cannot be restricted by both an equality and an inequality relation",
 +                                     getColumnNamesAsString());
 +
 +            TokenRestriction.Slice otherSlice = (TokenRestriction.Slice) otherRestriction;
 +
 +            if (hasBound(Bound.START) && otherSlice.hasBound(Bound.START))
 +                throw invalidRequest("More than one restriction was found for the start bound on %s",
 +                                     getColumnNamesAsString());
 +
 +            if (hasBound(Bound.END) && otherSlice.hasBound(Bound.END))
 +                throw invalidRequest("More than one restriction was found for the end bound on %s",
 +                                     getColumnNamesAsString());
 +
 +            return new Slice(ctype, columnDefs,  slice.merge(otherSlice.slice));
 +        }
 +
 +        @Override
 +        public String toString()
 +        {
 +            return String.format("SLICE%s", slice);
 +        }
 +
 +        private Slice(CType ctype, List<ColumnDefinition> columnDefs, TermSlice slice)
 +        {
 +            super(ctype, columnDefs);
 +            this.slice = slice;
 +        }
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/9583b6b3/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
index 5ffcc8a,f84188a..3d134b5
--- a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
@@@ -311,23 -301,38 +311,23 @@@ public abstract class ModificationState
      public List<ByteBuffer> buildPartitionKeyNames(QueryOptions options)
      throws InvalidRequestException
      {
 -        CBuilder keyBuilder = cfm.getKeyValidatorAsCType().builder();
 -        List<ByteBuffer> keys = new ArrayList<ByteBuffer>();
 +        CompositesBuilder keyBuilder = new CompositesBuilder(cfm.getKeyValidatorAsCType());
          for (ColumnDefinition def : cfm.partitionKeyColumns())
          {
 -            Restriction r = processedKeys.get(def.name);
 -            if (r == null)
 -                throw new InvalidRequestException(String.format("Missing mandatory PRIMARY KEY part %s", def.name));
 -
 -            List<ByteBuffer> values = r.values(options);
 +            Restriction r = checkNotNull(processedKeys.get(def.name), "Missing mandatory PRIMARY KEY part %s", def.name);
-             r.appendTo(keyBuilder, options);
++            r.appendTo(cfm, keyBuilder, options);
 +        }
  
 -            if (keyBuilder.remainingCount() == 1)
 -            {
 -                for (ByteBuffer val : values)
 -                {
 -                    if (val == null)
 -                        throw new InvalidRequestException(String.format("Invalid null value for partition key part %s", def.name));
 -                    ByteBuffer key = keyBuilder.buildWith(val).toByteBuffer();
 -                    ThriftValidation.validateKey(cfm, key);
 -                    keys.add(key);
 -                }
 -            }
 -            else
 +        return Lists.transform(filterAndSort(keyBuilder.build()), new com.google.common.base.Function<Composite, ByteBuffer>()
 +        {
 +            @Override
 +            public ByteBuffer apply(Composite composite)
              {
 -                if (values.size() != 1)
 -                    throw new InvalidRequestException("IN is only supported on the last column of the partition key");
 -                ByteBuffer val = values.get(0);
 -                if (val == null)
 -                    throw new InvalidRequestException(String.format("Invalid null value for partition key part %s", def.name));
 -                keyBuilder.add(val);
 +                ByteBuffer byteBuffer = composite.toByteBuffer();
 +                ThriftValidation.validateKey(cfm, byteBuffer);
 +                return byteBuffer;
              }
 -        }
 -        return keys;
 +        });
      }
  
      public Composite createClusteringPrefix(QueryOptions options)
@@@ -406,26 -411,15 +406,26 @@@
              }
              else
              {
-                 r.appendTo(builder, options);
 -                List<ByteBuffer> values = r.values(options);
 -                assert values.size() == 1; // We only allow IN for row keys so far
 -                ByteBuffer val = values.get(0);
 -                if (val == null)
 -                    throw new InvalidRequestException(String.format("Invalid null value for clustering key part %s", def.name));
 -                builder.add(val);
++                r.appendTo(cfm, builder, options);
              }
          }
 -        return builder.build();
 +        return builder.build().get(0); // We only allow IN for row keys so far
 +    }
 +
 +    /**
 +     * Removes duplicates and sort the specified composites.
 +     *
 +     * @param composites the composites to filter and sort
 +     * @return the composites sorted and without duplicates
 +     */
 +    private List<Composite> filterAndSort(List<Composite> composites)
 +    {
 +        if (composites.size() <= 1)
 +            return composites;
 +
 +        TreeSet<Composite> set = new TreeSet<Composite>(cfm.getKeyValidatorAsCType());
 +        set.addAll(composites);
 +        return new ArrayList<>(set);
      }
  
      protected ColumnDefinition getFirstEmptyKey()


[04/12] cassandra git commit: Merge branch cassandra-2.1 into cassandra-2.2

Posted by bl...@apache.org.
http://git-wip-us.apache.org/repos/asf/cassandra/blob/9583b6b3/test/unit/org/apache/cassandra/cql3/restrictions/PrimaryKeyRestrictionSetTest.java
----------------------------------------------------------------------
diff --cc test/unit/org/apache/cassandra/cql3/restrictions/PrimaryKeyRestrictionSetTest.java
index bf5128f,0000000..bd5395a
mode 100644,000000..100644
--- a/test/unit/org/apache/cassandra/cql3/restrictions/PrimaryKeyRestrictionSetTest.java
+++ b/test/unit/org/apache/cassandra/cql3/restrictions/PrimaryKeyRestrictionSetTest.java
@@@ -1,1957 -1,0 +1,1957 @@@
 +/*
 + * 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.restrictions;
 +
 +import java.nio.ByteBuffer;
 +import java.util.ArrayList;
 +import java.util.List;
 +
 +import org.junit.Test;
 +
 +import org.apache.cassandra.config.CFMetaData;
 +import org.apache.cassandra.config.ColumnDefinition;
 +import org.apache.cassandra.cql3.*;
 +import org.apache.cassandra.cql3.Term.MultiItemTerminal;
 +import org.apache.cassandra.cql3.statements.Bound;
 +import org.apache.cassandra.db.ColumnFamilyType;
 +import org.apache.cassandra.db.composites.Composite;
 +import org.apache.cassandra.db.composites.Composite.EOC;
 +import org.apache.cassandra.db.composites.Composites;
 +import org.apache.cassandra.db.composites.CompoundSparseCellNameType;
 +import org.apache.cassandra.db.marshal.AbstractType;
 +import org.apache.cassandra.db.marshal.Int32Type;
 +import org.apache.cassandra.db.marshal.ReversedType;
 +import org.apache.cassandra.utils.ByteBufferUtil;
 +
 +import static java.util.Arrays.asList;
 +import static org.junit.Assert.assertEquals;
 +import static org.junit.Assert.assertTrue;
 +
 +public class PrimaryKeyRestrictionSetTest
 +{
 +    @Test
 +    public void testBoundsAsCompositesWithNoRestrictions()
 +    {
 +        CFMetaData cfMetaData = newCFMetaData(Sort.ASC);
 +
 +        PrimaryKeyRestrictions restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +
-         List<Composite> bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        List<Composite> bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertEmptyComposite(bounds.get(0));
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertEmptyComposite(bounds.get(0));
 +    }
 +
 +    /**
 +     * Test 'clustering_0 = 1' with only one clustering column
 +     */
 +    @Test
 +    public void testBoundsAsCompositesWithOneEqRestrictionsAndOneClusteringColumn()
 +    {
 +        CFMetaData cfMetaData = newCFMetaData(Sort.ASC);
 +
 +        ByteBuffer clustering_0 = ByteBufferUtil.bytes(1);
 +        Restriction eq = newSingleEq(cfMetaData, 0, clustering_0);
 +
 +        PrimaryKeyRestrictions restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(eq);
 +
-         List<Composite> bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        List<Composite> bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), clustering_0, EOC.START);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), clustering_0, EOC.END);
 +    }
 +
 +    /**
 +     * Test 'clustering_1 = 1' with 2 clustering columns
 +     */
 +    @Test
 +    public void testBoundsAsCompositesWithOneEqRestrictionsAndTwoClusteringColumns()
 +    {
 +        CFMetaData cfMetaData = newCFMetaData(Sort.ASC, Sort.ASC);
 +
 +        ByteBuffer clustering_0 = ByteBufferUtil.bytes(1);
 +        Restriction eq = newSingleEq(cfMetaData, 0, clustering_0);
 +
 +        PrimaryKeyRestrictions restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(eq);
 +
-         List<Composite> bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        List<Composite> bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), clustering_0, EOC.START);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), clustering_0, EOC.END);
 +    }
 +
 +    /**
 +     * Test 'clustering_0 IN (1, 2, 3)' with only one clustering column
 +     */
 +    @Test
 +    public void testBoundsAsCompositesWithOneInRestrictionsAndOneClusteringColumn()
 +    {
 +        ByteBuffer value1 = ByteBufferUtil.bytes(1);
 +        ByteBuffer value2 = ByteBufferUtil.bytes(2);
 +        ByteBuffer value3 = ByteBufferUtil.bytes(3);
 +
 +        CFMetaData cfMetaData = newCFMetaData(Sort.ASC, Sort.ASC);
 +
 +        Restriction in = newSingleIN(cfMetaData, 0, value1, value2, value3);
 +
 +        PrimaryKeyRestrictions restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(in);
 +
-         List<Composite> bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        List<Composite> bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(3, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.START);
 +        assertComposite(bounds.get(1), value2, EOC.START);
 +        assertComposite(bounds.get(2), value3, EOC.START);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(3, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.END);
 +        assertComposite(bounds.get(1), value2, EOC.END);
 +        assertComposite(bounds.get(2), value3, EOC.END);
 +    }
 +
 +    /**
 +     * Test slice restriction (e.g 'clustering_0 > 1') with only one clustering column
 +     */
 +    @Test
 +    public void testBoundsAsCompositesWithSliceRestrictionsAndOneClusteringColumn()
 +    {
 +        CFMetaData cfMetaData = newCFMetaData(Sort.ASC, Sort.ASC);
 +
 +        ByteBuffer value1 = ByteBufferUtil.bytes(1);
 +        ByteBuffer value2 = ByteBufferUtil.bytes(2);
 +
 +        Restriction slice = newSingleSlice(cfMetaData, 0, Bound.START, false, value1);
 +        PrimaryKeyRestrictions restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         List<Composite> bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        List<Composite> bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.END);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertEmptyComposite(bounds.get(0));
 +
 +        slice = newSingleSlice(cfMetaData, 0, Bound.START, true, value1);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.NONE);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertEmptyComposite(bounds.get(0));
 +
 +        slice = newSingleSlice(cfMetaData, 0, Bound.END, true, value1);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertEmptyComposite(bounds.get(0));
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.END);
 +
 +        slice = newSingleSlice(cfMetaData, 0, Bound.END, false, value1);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertEmptyComposite(bounds.get(0));
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.START);
 +
 +        slice = newSingleSlice(cfMetaData, 0, Bound.START, false, value1);
 +        Restriction slice2 = newSingleSlice(cfMetaData, 0, Bound.END, false, value2);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice).mergeWith(slice2);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.END);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value2, EOC.START);
 +
 +        slice = newSingleSlice(cfMetaData, 0, Bound.START, true, value1);
 +        slice2 = newSingleSlice(cfMetaData, 0, Bound.END, true, value2);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice).mergeWith(slice2);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.NONE);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value2, EOC.END);
 +    }
 +
 +    /**
 +     * Test slice restriction (e.g 'clustering_0 > 1') with only one descending clustering column
 +     */
 +    @Test
 +    public void testBoundsAsCompositesWithSliceRestrictionsAndOneDescendingClusteringColumn()
 +    {
 +        CFMetaData cfMetaData = newCFMetaData(Sort.DESC, Sort.DESC);
 +
 +        ByteBuffer value1 = ByteBufferUtil.bytes(1);
 +        ByteBuffer value2 = ByteBufferUtil.bytes(2);
 +
 +        Restriction slice = newSingleSlice(cfMetaData, 0, Bound.START, false, value1);
 +        PrimaryKeyRestrictions restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         List<Composite> bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        List<Composite> bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertEmptyComposite(bounds.get(0));
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.START);
 +
 +        slice = newSingleSlice(cfMetaData, 0, Bound.START, true, value1);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertEmptyComposite(bounds.get(0));
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.END);
 +
 +        slice = newSingleSlice(cfMetaData, 0, Bound.END, true, value1);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.NONE);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertEmptyComposite(bounds.get(0));
 +
 +        slice = newSingleSlice(cfMetaData, 0, Bound.END, false, value1);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.END);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertEmptyComposite(bounds.get(0));
 +
 +        slice = newSingleSlice(cfMetaData, 0, Bound.START, false, value1);
 +        Restriction slice2 = newSingleSlice(cfMetaData, 0, Bound.END, false, value2);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice).mergeWith(slice2);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value2, EOC.END);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.START);
 +
 +        slice = newSingleSlice(cfMetaData, 0, Bound.START, true, value1);
 +        slice2 = newSingleSlice(cfMetaData, 0, Bound.END, true, value2);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice).mergeWith(slice2);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value2, EOC.NONE);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.END);
 +    }
 +
 +    /**
 +     * Test 'clustering_0 = 1 AND clustering_1 IN (1, 2, 3)'
 +     */
 +    @Test
 +    public void testBoundsAsCompositesWithEqAndInRestrictions()
 +    {
 +        CFMetaData cfMetaData = newCFMetaData(Sort.ASC, Sort.ASC);
 +
 +        ByteBuffer value1 = ByteBufferUtil.bytes(1);
 +        ByteBuffer value2 = ByteBufferUtil.bytes(2);
 +        ByteBuffer value3 = ByteBufferUtil.bytes(3);
 +        Restriction eq = newSingleEq(cfMetaData, 0, value1);
 +        Restriction in = newSingleIN(cfMetaData, 1, value1, value2, value3);
 +        PrimaryKeyRestrictions restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(eq).mergeWith(in);
 +
-         List<Composite> bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        List<Composite> bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(3, bounds.size());
 +        assertComposite(bounds.get(0), value1, value1, EOC.START);
 +        assertComposite(bounds.get(1), value1, value2, EOC.START);
 +        assertComposite(bounds.get(2), value1, value3, EOC.START);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(3, bounds.size());
 +        assertComposite(bounds.get(0), value1, value1, EOC.END);
 +        assertComposite(bounds.get(1), value1, value2, EOC.END);
 +        assertComposite(bounds.get(2), value1, value3, EOC.END);
 +    }
 +
 +    /**
 +     * Test equal and slice restrictions (e.g 'clustering_0 = 0 clustering_1 > 1')
 +     */
 +    @Test
 +    public void testBoundsAsCompositesWithEqAndSliceRestrictions()
 +    {
 +        CFMetaData cfMetaData = newCFMetaData(Sort.ASC, Sort.ASC);
 +
 +        ByteBuffer value1 = ByteBufferUtil.bytes(1);
 +        ByteBuffer value2 = ByteBufferUtil.bytes(2);
 +        ByteBuffer value3 = ByteBufferUtil.bytes(3);
 +
 +        Restriction eq = newSingleEq(cfMetaData, 0, value3);
 +
 +        Restriction slice = newSingleSlice(cfMetaData, 1, Bound.START, false, value1);
 +        PrimaryKeyRestrictions restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(eq).mergeWith(slice);
 +
-         List<Composite> bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        List<Composite> bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value3, value1, EOC.END);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value3, EOC.END);
 +
 +        slice = newSingleSlice(cfMetaData, 1, Bound.START, true, value1);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(eq).mergeWith(slice);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value3, value1, EOC.NONE);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value3, EOC.END);
 +
 +        slice = newSingleSlice(cfMetaData, 1, Bound.END, true, value1);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(eq).mergeWith(slice);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value3, EOC.START);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value3, value1, EOC.END);
 +
 +        slice = newSingleSlice(cfMetaData, 1, Bound.END, false, value1);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(eq).mergeWith(slice);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value3, EOC.START);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value3, value1, EOC.START);
 +
 +        slice = newSingleSlice(cfMetaData, 1, Bound.START, false, value1);
 +        Restriction slice2 = newSingleSlice(cfMetaData, 1, Bound.END, false, value2);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(eq).mergeWith(slice).mergeWith(slice2);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value3, value1, EOC.END);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value3, value2, EOC.START);
 +
 +        slice = newSingleSlice(cfMetaData, 1, Bound.START, true, value1);
 +        slice2 = newSingleSlice(cfMetaData, 1, Bound.END, true, value2);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(eq).mergeWith(slice).mergeWith(slice2);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value3, value1, EOC.NONE);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value3, value2, EOC.END);
 +    }
 +
 +    /**
 +     * Test '(clustering_0, clustering_1) = (1, 2)' with two clustering column
 +     */
 +    @Test
 +    public void testBoundsAsCompositesWithMultiEqRestrictions()
 +    {
 +        CFMetaData cfMetaData = newCFMetaData(Sort.ASC, Sort.ASC);
 +
 +        ByteBuffer value1 = ByteBufferUtil.bytes(1);
 +        ByteBuffer value2 = ByteBufferUtil.bytes(2);
 +        Restriction eq = newMultiEq(cfMetaData, 0, value1, value2);
 +        PrimaryKeyRestrictions restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(eq);
 +
-         List<Composite> bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        List<Composite> bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, EOC.START);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, EOC.END);
 +    }
 +
 +    /**
 +     * Test '(clustering_0, clustering_1) IN ((1, 2), (2, 3))' with two clustering column
 +     */
 +    @Test
 +    public void testBoundsAsCompositesWithMultiInRestrictions()
 +    {
 +        CFMetaData cfMetaData = newCFMetaData(Sort.ASC, Sort.ASC);
 +
 +        ByteBuffer value1 = ByteBufferUtil.bytes(1);
 +        ByteBuffer value2 = ByteBufferUtil.bytes(2);
 +        ByteBuffer value3 = ByteBufferUtil.bytes(3);
 +        Restriction in = newMultiIN(cfMetaData, 0, asList(value1, value2), asList(value2, value3));
 +        PrimaryKeyRestrictions restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(in);
 +
-         List<Composite> bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        List<Composite> bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, EOC.START);
 +        assertComposite(bounds.get(1), value2, value3, EOC.START);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, EOC.END);
 +        assertComposite(bounds.get(1), value2, value3, EOC.END);
 +    }
 +
 +    /**
 +     * Test multi-column slice restrictions (e.g '(clustering_0) > (1)') with only one clustering column
 +     */
 +    @Test
 +    public void testBoundsAsCompositesWithMultiSliceRestrictionsWithOneClusteringColumn()
 +    {
 +        CFMetaData cfMetaData = newCFMetaData(Sort.ASC);
 +
 +        ByteBuffer value1 = ByteBufferUtil.bytes(1);
 +        ByteBuffer value2 = ByteBufferUtil.bytes(2);
 +
 +        Restriction slice = newMultiSlice(cfMetaData, 0, Bound.START, false, value1);
 +        PrimaryKeyRestrictions restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         List<Composite> bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        List<Composite> bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.END);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertEmptyComposite(bounds.get(0));
 +
 +        slice = newMultiSlice(cfMetaData, 0, Bound.START, true, value1);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.NONE);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertEmptyComposite(bounds.get(0));
 +
 +        slice = newMultiSlice(cfMetaData, 0, Bound.END, true, value1);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertEmptyComposite(bounds.get(0));
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.END);
 +
 +        slice = newMultiSlice(cfMetaData, 0, Bound.END, false, value1);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertEmptyComposite(bounds.get(0));
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.START);
 +
 +        slice = newMultiSlice(cfMetaData, 0, Bound.START, false, value1);
 +        Restriction slice2 = newMultiSlice(cfMetaData, 0, Bound.END, false, value2);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice).mergeWith(slice2);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.END);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value2, EOC.START);
 +
 +        slice = newMultiSlice(cfMetaData, 0, Bound.START, true, value1);
 +        slice2 = newMultiSlice(cfMetaData, 0, Bound.END, true, value2);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice).mergeWith(slice2);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.NONE);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value2, EOC.END);
 +    }
 +
 +    /**
 +     * Test multi-column slice restrictions (e.g '(clustering_0) > (1)') with only one clustering column in reverse
 +     * order
 +     */
 +    @Test
 +    public void testBoundsAsCompositesWithMultiSliceRestrictionsWithOneDescendingClusteringColumn()
 +    {
 +        CFMetaData cfMetaData = newCFMetaData(Sort.DESC);
 +
 +        ByteBuffer value1 = ByteBufferUtil.bytes(1);
 +        ByteBuffer value2 = ByteBufferUtil.bytes(2);
 +
 +        Restriction slice = newMultiSlice(cfMetaData, 0, Bound.START, false, value1);
 +        PrimaryKeyRestrictions restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         List<Composite> bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        List<Composite> bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertEmptyComposite(bounds.get(0));
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.START);
 +
 +        slice = newMultiSlice(cfMetaData, 0, Bound.START, true, value1);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertEmptyComposite(bounds.get(0));
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.END);
 +
 +        slice = newMultiSlice(cfMetaData, 0, Bound.END, true, value1);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.NONE);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertEmptyComposite(bounds.get(0));
 +
 +        slice = newMultiSlice(cfMetaData, 0, Bound.END, false, value1);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.END);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertEmptyComposite(bounds.get(0));
 +
 +        slice = newMultiSlice(cfMetaData, 0, Bound.START, false, value1);
 +        Restriction slice2 = newMultiSlice(cfMetaData, 0, Bound.END, false, value2);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice).mergeWith(slice2);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value2, EOC.END);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.START);
 +
 +        slice = newMultiSlice(cfMetaData, 0, Bound.START, true, value1);
 +        slice2 = newMultiSlice(cfMetaData, 0, Bound.END, true, value2);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice).mergeWith(slice2);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value2, EOC.NONE);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.END);
 +    }
 +
 +    /**
 +     * Test multi-column slice restrictions (e.g '(clustering_0, clustering_1) > (1, 2)')
 +     */
 +    @Test
 +    public void testBoundsAsCompositesWithMultiSliceRestrictionsWithTwoClusteringColumn()
 +    {
 +        CFMetaData cfMetaData = newCFMetaData(Sort.ASC, Sort.ASC);
 +
 +        ByteBuffer value1 = ByteBufferUtil.bytes(1);
 +        ByteBuffer value2 = ByteBufferUtil.bytes(2);
 +
 +        // (clustering_0, clustering1) > (1, 2)
 +        Restriction slice = newMultiSlice(cfMetaData, 0, Bound.START, false, value1, value2);
 +        PrimaryKeyRestrictions restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         List<Composite> bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        List<Composite> bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, EOC.END);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertEmptyComposite(bounds.get(0));
 +
 +        // (clustering_0, clustering1) >= (1, 2)
 +        slice = newMultiSlice(cfMetaData, 0, Bound.START, true, value1, value2);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, EOC.NONE);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertEmptyComposite(bounds.get(0));
 +
 +        // (clustering_0, clustering1) <= (1, 2)
 +        slice = newMultiSlice(cfMetaData, 0, Bound.END, true, value1, value2);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertEmptyComposite(bounds.get(0));
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, EOC.END);
 +
 +        // (clustering_0, clustering1) < (1, 2)
 +        slice = newMultiSlice(cfMetaData, 0, Bound.END, false, value1, value2);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertEmptyComposite(bounds.get(0));
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, EOC.START);
 +
 +        // (clustering_0, clustering1) > (1, 2) AND (clustering_0) < (2)
 +        slice = newMultiSlice(cfMetaData, 0, Bound.START, false, value1, value2);
 +        Restriction slice2 = newMultiSlice(cfMetaData, 0, Bound.END, false, value2);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice).mergeWith(slice2);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, EOC.END);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value2, EOC.START);
 +
 +        // (clustering_0, clustering1) >= (1, 2) AND (clustering_0, clustering1) <= (2, 1)
 +        slice = newMultiSlice(cfMetaData, 0, Bound.START, true, value1, value2);
 +        slice2 = newMultiSlice(cfMetaData, 0, Bound.END, true, value2, value1);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice).mergeWith(slice2);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, EOC.NONE);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value2, value1, EOC.END);
 +    }
 +
 +    /**
 +     * Test multi-column slice restrictions with 2 descending clustering columns (e.g '(clustering_0, clustering_1) > (1, 2)')
 +     */
 +    @Test
 +    public void testBoundsAsCompositesWithMultiSliceRestrictionsWithTwoDescendingClusteringColumns()
 +    {
 +        CFMetaData cfMetaData = newCFMetaData(Sort.DESC, Sort.DESC);
 +
 +        ByteBuffer value1 = ByteBufferUtil.bytes(1);
 +        ByteBuffer value2 = ByteBufferUtil.bytes(2);
 +
 +        // (clustering_0, clustering1) > (1, 2)
 +        Restriction slice = newMultiSlice(cfMetaData, 0, Bound.START, false, value1, value2);
 +        PrimaryKeyRestrictions restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         List<Composite> bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        List<Composite> bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertEmptyComposite(bounds.get(0));
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, EOC.START);
 +
 +        // (clustering_0, clustering1) >= (1, 2)
 +        slice = newMultiSlice(cfMetaData, 0, Bound.START, true, value1, value2);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertEmptyComposite(bounds.get(0));
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, EOC.END);
 +
 +        // (clustering_0, clustering1) <= (1, 2)
 +        slice = newMultiSlice(cfMetaData, 0, Bound.END, true, value1, value2);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, EOC.NONE);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertEmptyComposite(bounds.get(0));
 +
 +        // (clustering_0, clustering1) < (1, 2)
 +        slice = newMultiSlice(cfMetaData, 0, Bound.END, false, value1, value2);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, EOC.END);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertEmptyComposite(bounds.get(0));
 +
 +
 +        // (clustering_0, clustering1) > (1, 2) AND (clustering_0) < (2)
 +        slice = newMultiSlice(cfMetaData, 0, Bound.START, false, value1, value2);
 +        Restriction slice2 = newMultiSlice(cfMetaData, 0, Bound.END, false, value2);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice).mergeWith(slice2);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value2, EOC.END);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, EOC.START);
 +
 +        // (clustering_0, clustering1) >= (1, 2) AND (clustering_0, clustering1) <= (2, 1)
 +        slice = newMultiSlice(cfMetaData, 0, Bound.START, true, value1, value2);
 +        slice2 = newMultiSlice(cfMetaData, 0, Bound.END, true, value2, value1);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice).mergeWith(slice2);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value2, value1, EOC.NONE);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, EOC.END);
 +    }
 +
 +    /**
 +     * Test multi-column slice restrictions with 1 descending clustering column and 1 ascending
 +     * (e.g '(clustering_0, clustering_1) > (1, 2)')
 +     */
 +    @Test
 +    public void testBoundsAsCompositesWithMultiSliceRestrictionsWithOneDescendingAndOneAscendingClusteringColumns()
 +    {
 +        CFMetaData cfMetaData = newCFMetaData(Sort.DESC, Sort.ASC);
 +
 +        ByteBuffer value1 = ByteBufferUtil.bytes(1);
 +        ByteBuffer value2 = ByteBufferUtil.bytes(2);
 +
 +        // (clustering_0, clustering1) > (1, 2)
 +        Restriction slice = newMultiSlice(cfMetaData, 0, Bound.START, false, value1, value2);
 +        PrimaryKeyRestrictions restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         List<Composite> bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        List<Composite> bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertEmptyComposite(bounds.get(0));
 +        assertComposite(bounds.get(1), value1, value2, EOC.END);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.START);
 +        assertComposite(bounds.get(1), value1, EOC.END);
 +
 +        // (clustering_0, clustering1) >= (1, 2)
 +        slice = newMultiSlice(cfMetaData, 0, Bound.START, true, value1, value2);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertEmptyComposite(bounds.get(0));
 +        assertComposite(bounds.get(1), value1, value2, EOC.NONE);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.START);
 +        assertComposite(bounds.get(1), value1, EOC.END);
 +
 +        // (clustering_0, clustering1) <= (1, 2)
 +        slice = newMultiSlice(cfMetaData, 0, Bound.END, true, value1, value2);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.START);
 +        assertComposite(bounds.get(1), value1, EOC.END);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, EOC.END);
 +        assertEmptyComposite(bounds.get(1));
 +
 +        // (clustering_0, clustering1) < (1, 2)
 +        slice = newMultiSlice(cfMetaData, 0, Bound.END, false, value1, value2);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.START);
 +        assertComposite(bounds.get(1), value1, EOC.END);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, EOC.START);
 +        assertEmptyComposite(bounds.get(1));
 +
 +        // (clustering_0, clustering1) > (1, 2) AND (clustering_0) < (2)
 +        slice = newMultiSlice(cfMetaData, 0, Bound.START, false, value1, value2);
 +        Restriction slice2 = newMultiSlice(cfMetaData, 0, Bound.END, false, value2);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice).mergeWith(slice2);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertComposite(bounds.get(0), value2, EOC.END);
 +        assertComposite(bounds.get(1), value1, value2, EOC.END);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.START);
 +        assertComposite(bounds.get(1), value1, EOC.END);
 +
 +        // (clustering_0) > (1) AND (clustering_0, clustering1) < (2, 1)
 +        slice = newMultiSlice(cfMetaData, 0, Bound.START, false, value1);
 +        slice2 = newMultiSlice(cfMetaData, 0, Bound.END, false, value2, value1);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice).mergeWith(slice2);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertComposite(bounds.get(0), value2, EOC.START);
 +        assertComposite(bounds.get(1), value2, EOC.END);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertComposite(bounds.get(0), value2, value1, EOC.START);
 +        assertComposite(bounds.get(1), value1, EOC.START);
 +
 +        // (clustering_0, clustering1) >= (1, 2) AND (clustering_0, clustering1) <= (2, 1)
 +        slice = newMultiSlice(cfMetaData, 0, Bound.START, true, value1, value2);
 +        slice2 = newMultiSlice(cfMetaData, 0, Bound.END, true, value2, value1);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice).mergeWith(slice2);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(3, bounds.size());
 +        assertComposite(bounds.get(0), value2, EOC.START);
 +        assertComposite(bounds.get(1), value2, EOC.END);
 +        assertComposite(bounds.get(2), value1, value2, EOC.NONE);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(3, bounds.size());
 +        assertComposite(bounds.get(0), value2, value1, EOC.END);
 +        assertComposite(bounds.get(1), value1, EOC.START);
 +        assertComposite(bounds.get(2), value1, EOC.END);
 +    }
 +
 +    /**
 +     * Test multi-column slice restrictions with 1 descending clustering column and 1 ascending
 +     * (e.g '(clustering_0, clustering_1) > (1, 2)')
 +     */
 +    @Test
 +    public void testBoundsAsCompositesWithMultiSliceRestrictionsWithOneAscendingAndOneDescendingClusteringColumns()
 +    {
 +        CFMetaData cfMetaData = newCFMetaData(Sort.ASC, Sort.DESC);
 +
 +        ByteBuffer value1 = ByteBufferUtil.bytes(1);
 +        ByteBuffer value2 = ByteBufferUtil.bytes(2);
 +
 +        // (clustering_0, clustering1) > (1, 2)
 +        Restriction slice = newMultiSlice(cfMetaData, 0, Bound.START, false, value1, value2);
 +        PrimaryKeyRestrictions restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         List<Composite> bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        List<Composite> bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.START);
 +        assertComposite(bounds.get(1), value1, EOC.END);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, EOC.START);
 +        assertEmptyComposite(bounds.get(1));
 +
 +        // (clustering_0, clustering1) >= (1, 2)
 +        slice = newMultiSlice(cfMetaData, 0, Bound.START, true, value1, value2);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.START);
 +        assertComposite(bounds.get(1), value1, EOC.END);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, EOC.END);
 +        assertEmptyComposite(bounds.get(1));
 +
 +        // (clustering_0, clustering1) <= (1, 2)
 +        slice = newMultiSlice(cfMetaData, 0, Bound.END, true, value1, value2);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertEmptyComposite(bounds.get(0));
 +        assertComposite(bounds.get(1), value1, value2, EOC.NONE);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.START);
 +        assertComposite(bounds.get(1), value1, EOC.END);
 +
 +        // (clustering_0, clustering1) < (1, 2)
 +        slice = newMultiSlice(cfMetaData, 0, Bound.END, false, value1, value2);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertEmptyComposite(bounds.get(0));
 +        assertComposite(bounds.get(1), value1, value2, EOC.END);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.START);
 +        assertComposite(bounds.get(1), value1, EOC.END);
 +
 +        // (clustering_0, clustering1) > (1, 2) AND (clustering_0) < (2)
 +        slice = newMultiSlice(cfMetaData, 0, Bound.START, false, value1, value2);
 +        Restriction slice2 = newMultiSlice(cfMetaData, 0, Bound.END, false, value2);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice).mergeWith(slice2);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.START);
 +        assertComposite(bounds.get(1), value1, EOC.END);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, EOC.START);
 +        assertComposite(bounds.get(1), value2, EOC.START);
 +
 +        // (clustering_0, clustering1) >= (1, 2) AND (clustering_0, clustering1) <= (2, 1)
 +        slice = newMultiSlice(cfMetaData, 0, Bound.START, true, value1, value2);
 +        slice2 = newMultiSlice(cfMetaData, 0, Bound.END, true, value2, value1);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice).mergeWith(slice2);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(3, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.START);
 +        assertComposite(bounds.get(1), value1, EOC.END);
 +        assertComposite(bounds.get(2), value2, value1, EOC.NONE);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(3, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, EOC.END);
 +        assertComposite(bounds.get(1), value2, EOC.START);
 +        assertComposite(bounds.get(2), value2, EOC.END);
 +    }
 +
 +    /**
 +     * Test multi-column slice restrictions with 2 ascending clustering column and 2 descending
 +     * (e.g '(clustering_0, clustering1, clustering_3, clustering4) > (1, 2, 3, 4)')
 +     */
 +    @Test
 +    public void testBoundsAsCompositesWithMultiSliceRestrictionsWithTwoAscendingAndTwoDescendingClusteringColumns()
 +    {
 +        CFMetaData cfMetaData = newCFMetaData(Sort.ASC, Sort.ASC, Sort.DESC, Sort.DESC);
 +
 +        ByteBuffer value1 = ByteBufferUtil.bytes(1);
 +        ByteBuffer value2 = ByteBufferUtil.bytes(2);
 +        ByteBuffer value3 = ByteBufferUtil.bytes(3);
 +        ByteBuffer value4 = ByteBufferUtil.bytes(4);
 +
 +        // (clustering_0, clustering1, clustering_2, clustering_3) > (1, 2, 3, 4)
 +        Restriction slice = newMultiSlice(cfMetaData, 0, Bound.START, false, value1, value2, value3, value4);
 +        PrimaryKeyRestrictions restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         List<Composite> bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        List<Composite> bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, EOC.START);
 +        assertComposite(bounds.get(1), value1, value2, EOC.END);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, value3, value4, EOC.START);
 +        assertEmptyComposite(bounds.get(1));
 +
 +        // clustering_0 = 1 AND (clustering_1, clustering_2, clustering_3) > (2, 3, 4)
 +        Restriction eq = newSingleEq(cfMetaData, 0, value1);
 +        slice = newMultiSlice(cfMetaData, 1, Bound.START, false, value2, value3, value4);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +        restrictions = restrictions.mergeWith(eq);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, EOC.START);
 +        assertComposite(bounds.get(1), value1, value2, EOC.END);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, value3, value4, EOC.START);
 +        assertComposite(bounds.get(1), value1, EOC.END);
 +
 +        // clustering_0 = 1 AND (clustering_1, clustering_2, clustering_3) > (2, 3, 4)
 +        Restriction in = newSingleIN(cfMetaData, 0, value1, value2);
 +        slice = newMultiSlice(cfMetaData, 1, Bound.START, false, value2, value3, value4);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +        restrictions = restrictions.mergeWith(in);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(4, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, EOC.START);
 +        assertComposite(bounds.get(1), value1, value2, EOC.END);
 +        assertComposite(bounds.get(2), value2, value2, EOC.START);
 +        assertComposite(bounds.get(3), value2, value2, EOC.END);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(4, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, value3, value4, EOC.START);
 +        assertComposite(bounds.get(1), value1, EOC.END);
 +        assertComposite(bounds.get(2), value2, value2, value3, value4, EOC.START);
 +        assertComposite(bounds.get(3), value2, EOC.END);
 +
 +        // (clustering_0, clustering1) >= (1, 2)
 +        slice = newMultiSlice(cfMetaData, 0, Bound.START, true, value1, value2);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, EOC.NONE);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertEmptyComposite(bounds.get(0));
 +
 +        // (clustering_0, clustering1, clustering_2, clustering_3) >= (1, 2, 3, 4)
 +        slice = newMultiSlice(cfMetaData, 0, Bound.START, true, value1, value2, value3, value4);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, EOC.START);
 +        assertComposite(bounds.get(1), value1, value2, EOC.END);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, value3, value4, EOC.END);
 +        assertEmptyComposite(bounds.get(1));
 +
 +        // (clustering_0, clustering1, clustering_2, clustering_3) <= (1, 2, 3, 4)
 +        slice = newMultiSlice(cfMetaData, 0, Bound.END, true, value1, value2, value3, value4);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertEmptyComposite(bounds.get(0));
 +        assertComposite(bounds.get(1), value1, value2, value3, value4, EOC.NONE);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, EOC.START);
 +        assertComposite(bounds.get(1), value1, value2, EOC.END);
 +
 +        // (clustering_0, clustering1, clustering_2, clustering_3) < (1, 2, 3, 4)
 +        slice = newMultiSlice(cfMetaData, 0, Bound.END, false, value1, value2, value3, value4);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertEmptyComposite(bounds.get(0));
 +        assertComposite(bounds.get(1), value1, value2, value3, value4, EOC.END);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, EOC.START);
 +        assertComposite(bounds.get(1), value1, value2, EOC.END);
 +
 +        // (clustering_0, clustering1, clustering_2, clustering_3) > (1, 2, 3, 4) AND (clustering_0, clustering_1) < (2, 3)
 +        slice = newMultiSlice(cfMetaData, 0, Bound.START, false, value1, value2, value3, value4);
 +        Restriction slice2 = newMultiSlice(cfMetaData, 0, Bound.END, false, value2, value3);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice).mergeWith(slice2);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, EOC.START);
 +        assertComposite(bounds.get(1), value1, value2, EOC.END);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, value3, value4, EOC.START);
 +        assertComposite(bounds.get(1), value2, value3, EOC.START);
 +
 +        // (clustering_0, clustering1, clustering_2, clustering_3) >= (1, 2, 3, 4) AND (clustering_0, clustering1, clustering_2, clustering_3) <= (4, 3, 2, 1)
 +        slice = newMultiSlice(cfMetaData, 0, Bound.START, true, value1, value2, value3, value4);
 +        slice2 = newMultiSlice(cfMetaData, 0, Bound.END, true, value4, value3, value2, value1);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice).mergeWith(slice2);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(3, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2,  EOC.START);
 +        assertComposite(bounds.get(1), value1, value2, EOC.END);
 +        assertComposite(bounds.get(2), value4, value3, value2, value1, EOC.NONE);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(3, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, value3, value4, EOC.END);
 +        assertComposite(bounds.get(1), value4, value3, EOC.START);
 +        assertComposite(bounds.get(2), value4, value3, EOC.END);
 +    }
 +
 +    /**
 +     * Test multi-column slice restrictions with ascending, descending, ascending and descending columns
 +     * (e.g '(clustering_0, clustering1, clustering_3, clustering4) > (1, 2, 3, 4)')
 +     */
 +    @Test
 +    public void testBoundsAsCompositesWithMultiSliceRestrictionsMixingAscendingDescendingClusteringColumns()
 +    {
 +        CFMetaData cfMetaData = newCFMetaData(Sort.ASC, Sort.DESC, Sort.ASC, Sort.DESC);
 +
 +        ByteBuffer value1 = ByteBufferUtil.bytes(1);
 +        ByteBuffer value2 = ByteBufferUtil.bytes(2);
 +        ByteBuffer value3 = ByteBufferUtil.bytes(3);
 +        ByteBuffer value4 = ByteBufferUtil.bytes(4);
 +
 +        // (clustering_0, clustering1, clustering_2, clustering_3) > (1, 2, 3, 4)
 +        Restriction slice = newMultiSlice(cfMetaData, 0, Bound.START, false, value1, value2, value3, value4);
 +        PrimaryKeyRestrictions restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         List<Composite> bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        List<Composite> bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(4, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.START);
 +        assertComposite(bounds.get(1), value1, value2, value3, EOC.START);
 +        assertComposite(bounds.get(2), value1, value2, value3, EOC.END);
 +        assertComposite(bounds.get(3), value1, EOC.END);
 +
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(4, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, EOC.START);
 +        assertComposite(bounds.get(1), value1, value2, value3, value4, EOC.START);
 +        assertComposite(bounds.get(2), value1, value2, EOC.END);
 +        assertEmptyComposite(bounds.get(3));
 +
 +        // clustering_0 = 1 AND (clustering_1, clustering_2, clustering_3) > (2, 3, 4)
 +        Restriction eq = newSingleEq(cfMetaData, 0, value1);
 +        slice = newMultiSlice(cfMetaData, 1, Bound.START, false, value2, value3, value4);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +        restrictions = restrictions.mergeWith(eq);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(3, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.START);
 +        assertComposite(bounds.get(1), value1, value2, value3, EOC.START);
 +        assertComposite(bounds.get(2), value1, value2, value3, EOC.END);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(3, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, EOC.START);
 +        assertComposite(bounds.get(1), value1, value2, value3, value4, EOC.START);
 +        assertComposite(bounds.get(2), value1, value2, EOC.END);
 +
 +        // (clustering_0, clustering1) >= (1, 2)
 +        slice = newMultiSlice(cfMetaData, 0, Bound.START, true, value1, value2);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.START);
 +        assertComposite(bounds.get(1), value1, EOC.END);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(2, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, EOC.END);
 +        assertEmptyComposite(bounds.get(1));
 +
 +        // (clustering_0, clustering1, clustering_2, clustering_3) >= (1, 2, 3, 4)
 +        slice = newMultiSlice(cfMetaData, 0, Bound.START, true, value1, value2, value3, value4);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(4, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.START);
 +        assertComposite(bounds.get(1), value1, value2, value3, EOC.START);
 +        assertComposite(bounds.get(2), value1, value2, value3, EOC.END);
 +        assertComposite(bounds.get(3), value1, EOC.END);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(4, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, EOC.START);
 +        assertComposite(bounds.get(1), value1, value2, value3, value4, EOC.END);
 +        assertComposite(bounds.get(2), value1, value2, EOC.END);
 +        assertEmptyComposite(bounds.get(3));
 +
 +
 +        // (clustering_0, clustering1, clustering_2, clustering_3) <= (1, 2, 3, 4)
 +        slice = newMultiSlice(cfMetaData, 0, Bound.END, true, value1, value2, value3, value4);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(4, bounds.size());
 +        assertEmptyComposite(bounds.get(0));
 +        assertComposite(bounds.get(1), value1, value2, EOC.START);
 +        assertComposite(bounds.get(2), value1, value2, value3, value4, EOC.NONE);
 +        assertComposite(bounds.get(3), value1, value2, EOC.END);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(4, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.START);
 +        assertComposite(bounds.get(1), value1, value2, value3, EOC.START);
 +        assertComposite(bounds.get(2), value1, value2, value3, EOC.END);
 +        assertComposite(bounds.get(3), value1, EOC.END);
 +
 +        // (clustering_0, clustering1, clustering_2, clustering_3) < (1, 2, 3, 4)
 +        slice = newMultiSlice(cfMetaData, 0, Bound.END, false, value1, value2, value3, value4);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(4, bounds.size());
 +        assertEmptyComposite(bounds.get(0));
 +        assertComposite(bounds.get(1), value1, value2, EOC.START);
 +        assertComposite(bounds.get(2), value1, value2, value3, value4, EOC.END);
 +        assertComposite(bounds.get(3), value1, value2, EOC.END);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(4, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.START);
 +        assertComposite(bounds.get(1), value1, value2, value3, EOC.START);
 +        assertComposite(bounds.get(2), value1, value2, value3, EOC.END);
 +        assertComposite(bounds.get(3), value1, EOC.END);
 +
 +        // (clustering_0, clustering1, clustering_2, clustering_3) > (1, 2, 3, 4) AND (clustering_0, clustering_1) < (2, 3)
 +        slice = newMultiSlice(cfMetaData, 0, Bound.START, false, value1, value2, value3, value4);
 +        Restriction slice2 = newMultiSlice(cfMetaData, 0, Bound.END, false, value2, value3);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice).mergeWith(slice2);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(5, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.START);
 +        assertComposite(bounds.get(1), value1, value2, value3, EOC.START);
 +        assertComposite(bounds.get(2), value1, value2, value3, EOC.END);
 +        assertComposite(bounds.get(3), value1, EOC.END);
 +        assertComposite(bounds.get(4), value2, value3, EOC.END);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(5, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, EOC.START);
 +        assertComposite(bounds.get(1), value1, value2, value3, value4, EOC.START);
 +        assertComposite(bounds.get(2), value1, value2, EOC.END);
 +        assertComposite(bounds.get(3), value2, EOC.START);
 +        assertComposite(bounds.get(4), value2, EOC.END);
 +
 +        // (clustering_0, clustering1, clustering_2, clustering_3) >= (1, 2, 3, 4) AND (clustering_0, clustering1, clustering_2, clustering_3) <= (4, 3, 2, 1)
 +        slice = newMultiSlice(cfMetaData, 0, Bound.START, true, value1, value2, value3, value4);
 +        slice2 = newMultiSlice(cfMetaData, 0, Bound.END, true, value4, value3, value2, value1);
 +        restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(slice).mergeWith(slice2);
 +
-         bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(7, bounds.size());
 +        assertComposite(bounds.get(0), value1, EOC.START);
 +        assertComposite(bounds.get(1), value1, value2, value3, EOC.START);
 +        assertComposite(bounds.get(2), value1, value2, value3, EOC.END);
 +        assertComposite(bounds.get(3), value1, EOC.END);
 +        assertComposite(bounds.get(4), value4, value3, EOC.START);
 +        assertComposite(bounds.get(5), value4, value3, value2, value1, EOC.NONE);
 +        assertComposite(bounds.get(6), value4, value3, EOC.END);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComposites(cfMetaData, Bound.END, QueryOptions.DEFAULT);
 +        assertEquals(7, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, EOC.START);
 +        assertComposite(bounds.get(1), value1, value2, value3, value4, EOC.END);
 +        assertComposite(bounds.get(2), value1, value2, EOC.END);
 +        assertComposite(bounds.get(3), value4, EOC.START);
 +        assertComposite(bounds.get(4), value4, value3, value2, EOC.START);
 +        assertComposite(bounds.get(5), value4, value3, value2, EOC.END);
 +        assertComposite(bounds.get(6), value4, EOC.END);
 +    }
 +
 +    /**
 +     * Test mixing single and multi equals restrictions (e.g. clustering_0 = 1 AND (clustering_1, clustering_2) = (2, 3))
 +     */
 +    @Test
 +    public void testBoundsAsCompositesWithSingleEqAndMultiEqRestrictions()
 +    {
 +        CFMetaData cfMetaData = newCFMetaData(Sort.ASC, Sort.ASC, Sort.ASC, Sort.ASC);
 +
 +        ByteBuffer value1 = ByteBufferUtil.bytes(1);
 +        ByteBuffer value2 = ByteBufferUtil.bytes(2);
 +        ByteBuffer value3 = ByteBufferUtil.bytes(3);
 +        ByteBuffer value4 = ByteBufferUtil.bytes(4);
 +
 +        // clustering_0 = 1 AND (clustering_1, clustering_2) = (2, 3)
 +        Restriction singleEq = newSingleEq(cfMetaData, 0, value1);
 +        Restriction multiEq = newMultiEq(cfMetaData, 1, value2, value3);
 +        PrimaryKeyRestrictions restrictions = new PrimaryKeyRestrictionSet(cfMetaData.comparator);
 +        restrictions = restrictions.mergeWith(singleEq).mergeWith(multiEq);
 +
-         List<Composite> bounds = restrictions.boundsAsComposites(Bound.START, QueryOptions.DEFAULT);
++        List<Composite> bounds = restrictions.boundsAsComposites(cfMetaData, Bound.START, QueryOptions.DEFAULT);
 +        assertEquals(1, bounds.size());
 +        assertComposite(bounds.get(0), value1, value2, value3, EOC.START);
 +
-         bounds = restrictions.boundsAsComposites(Bound.END, QueryOptions.DEFAULT);
++        bounds = restrictions.boundsAsComp

<TRUNCATED>

[03/12] cassandra git commit: Merge branch cassandra-2.1 into cassandra-2.2

Posted by bl...@apache.org.
http://git-wip-us.apache.org/repos/asf/cassandra/blob/9583b6b3/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java
----------------------------------------------------------------------
diff --cc test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java
index 365abe0,4a54a9a..11d2462
--- a/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java
+++ b/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java
@@@ -17,36 -17,26 +17,39 @@@
   */
  package org.apache.cassandra.cql3.validation.entities;
  
 +import static org.junit.Assert.assertTrue;
 +import static org.junit.Assert.fail;
 +
  import java.nio.ByteBuffer;
  import java.util.HashMap;
 +import java.util.List;
  import java.util.Locale;
  import java.util.Map;
 +import java.util.Set;
  import java.util.UUID;
 -
 -import org.apache.commons.lang.StringUtils;
 -
 -import org.junit.Test;
 +import java.util.concurrent.CountDownLatch;
  
  import org.apache.cassandra.cql3.CQLTester;
 +import org.apache.cassandra.db.ColumnFamily;
 +import org.apache.cassandra.db.ColumnFamilyStore;
 +import org.apache.cassandra.db.DecoratedKey;
 +import org.apache.cassandra.db.IndexExpression;
 +import org.apache.cassandra.db.composites.CellName;
 +import org.apache.cassandra.db.index.IndexNotAvailableException;
 +import org.apache.cassandra.db.index.PerRowSecondaryIndex;
 +import org.apache.cassandra.db.index.SecondaryIndexSearcher;
 +import org.apache.cassandra.db.index.composites.CompositesSearcher;
  import org.apache.cassandra.exceptions.ConfigurationException;
 +import org.apache.cassandra.exceptions.InvalidRequestException;
  import org.apache.cassandra.exceptions.SyntaxException;
  import org.apache.cassandra.utils.FBUtilities;
 +import org.apache.cassandra.utils.concurrent.OpOrder.Group;
 +import org.apache.commons.lang3.StringUtils;
 +import org.junit.Test;
  
 -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
  {
      private static final int TOO_BIG = 1024 * 65;
@@@ -655,134 -644,141 +658,273 @@@
      }
  
      @Test
 +    public void testIndexQueriesWithIndexNotReady() throws Throwable
 +    {
 +        createTable("CREATE TABLE %s (pk int, ck int, value int, PRIMARY KEY (pk, ck))");
 +
 +        for (int i = 0; i < 10; i++)
 +            for (int j = 0; j < 10; j++)
 +                execute("INSERT INTO %s (pk, ck, value) VALUES (?, ?, ?)", i, j, i + j);
 +
 +        createIndex("CREATE CUSTOM INDEX testIndex ON %s (value) USING '" + IndexBlockingOnInitialization.class.getName()
 +                + "'");
 +        try
 +        {
 +            execute("SELECT value FROM %s WHERE value = 2");
 +            fail();
 +        }
 +        catch (IndexNotAvailableException e)
 +        {
 +            assertTrue(true);
 +        }
 +        finally
 +        {
 +            execute("DROP index " + KEYSPACE + ".testIndex");
 +        }
 +    }
 +
++    @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));
+     }
++
 +    /**
 +     * Custom index used to test the behavior of the system when the index is not ready.
 +     * As Custom indices cannot by <code>PerColumnSecondaryIndex</code> we use a <code>PerRowSecondaryIndex</code>
 +     * to avoid the check but return a <code>CompositesSearcher</code>.
 +     */
 +    public static class IndexBlockingOnInitialization extends PerRowSecondaryIndex
 +    {
 +        private volatile CountDownLatch latch = new CountDownLatch(1);
 +
 +        @Override
 +        public void index(ByteBuffer rowKey, ColumnFamily cf)
 +        {
 +            try
 +            {
 +                latch.await();
 +            }
 +            catch (InterruptedException e)
 +            {
 +                Thread.interrupted();
 +            }
 +        }
 +
 +        @Override
 +        public void delete(DecoratedKey key, Group opGroup)
 +        {
 +        }
 +
 +        @Override
 +        public void init()
 +        {
 +        }
 +
 +        @Override
 +        public void reload()
 +        {
 +        }
 +
 +        @Override
 +        public void validateOptions() throws ConfigurationException
 +        {
 +        }
 +
 +        @Override
 +        public String getIndexName()
 +        {
 +            return "testIndex";
 +        }
 +
 +        @Override
 +        protected SecondaryIndexSearcher createSecondaryIndexSearcher(Set<ByteBuffer> columns)
 +        {
 +            return new CompositesSearcher(baseCfs.indexManager, columns)
 +            {
 +                @Override
 +                public boolean canHandleIndexClause(List<IndexExpression> clause)
 +                {
 +                    return true;
 +                }
 +
 +                @Override
 +                public void validate(IndexExpression indexExpression) throws InvalidRequestException
 +                {
 +                }
 +            };
 +        }
 +
 +        @Override
 +        public void forceBlockingFlush()
 +        {
 +        }
 +
 +        @Override
 +        public ColumnFamilyStore getIndexCfs()
 +        {
 +            return baseCfs;
 +        }
 +
 +        @Override
 +        public void removeIndex(ByteBuffer columnName)
 +        {
 +            latch.countDown();
 +        }
 +
 +        @Override
 +        public void invalidate()
 +        {
 +        }
 +
 +        @Override
 +        public void truncateBlocking(long truncatedAt)
 +        {
 +        }
 +
 +        @Override
 +        public boolean indexes(CellName name)
 +        {
 +            return false;
 +        }
 +
 +        @Override
 +        public long estimateResultRows()
 +        {
 +            return 0;
 +        }
 +    }
  }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/9583b6b3/test/unit/org/apache/cassandra/cql3/validation/operations/SelectOrderByTest.java
----------------------------------------------------------------------
diff --cc test/unit/org/apache/cassandra/cql3/validation/operations/SelectOrderByTest.java
index f8ec13c,cf923bc..32d800a
--- a/test/unit/org/apache/cassandra/cql3/validation/operations/SelectOrderByTest.java
+++ b/test/unit/org/apache/cassandra/cql3/validation/operations/SelectOrderByTest.java
@@@ -36,31 -35,31 +36,37 @@@ public class SelectOrderByTest extends 
              execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, 1, 1);
              execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, 2, 2);
  
--            assertRows(execute("SELECT * FROM %s WHERE a=? ORDER BY b ASC", 0),
--                    row(0, 0, 0),
--                    row(0, 1, 1),
--                    row(0, 2, 2)
--            );
--
--            assertRows(execute("SELECT * FROM %s WHERE a=? ORDER BY b DESC", 0),
--                    row(0, 2, 2),
--                    row(0, 1, 1),
--                    row(0, 0, 0)
--            );
--
--            // order by the only column in the selection
--            assertRows(execute("SELECT b FROM %s WHERE a=? ORDER BY b ASC", 0),
--                    row(0), row(1), row(2));
--
--            assertRows(execute("SELECT b FROM %s WHERE a=? ORDER BY b DESC", 0),
--                    row(2), row(1), row(0));
--
--            // order by a column not in the selection
--            assertRows(execute("SELECT c FROM %s WHERE a=? ORDER BY b ASC", 0),
--                    row(0), row(1), row(2));
--
--            assertRows(execute("SELECT c FROM %s WHERE a=? ORDER BY b DESC", 0),
--                    row(2), row(1), row(0));
++            for (boolean flush : new boolean[]{true, false})
++            {
++                if (flush)
++                    flush();
++
++                assertRows(execute("SELECT * FROM %s WHERE a=? ORDER BY b ASC", 0),
++                           row(0, 0, 0),
++                           row(0, 1, 1),
++                           row(0, 2, 2)
++                        );
++
++                assertRows(execute("SELECT * FROM %s WHERE a=? ORDER BY b DESC", 0),
++                           row(0, 2, 2),
++                           row(0, 1, 1),
++                           row(0, 0, 0)
++                        );
++
++                // order by the only column in the selection
++                assertRows(execute("SELECT b FROM %s WHERE a=? ORDER BY b ASC", 0),
++                           row(0), row(1), row(2));
++
++                assertRows(execute("SELECT b FROM %s WHERE a=? ORDER BY b DESC", 0),
++                           row(2), row(1), row(0));
++
++                // order by a column not in the selection
++                assertRows(execute("SELECT c FROM %s WHERE a=? ORDER BY b ASC", 0),
++                           row(0), row(1), row(2));
++
++                assertRows(execute("SELECT c FROM %s WHERE a=? ORDER BY b DESC", 0),
++                           row(2), row(1), row(0));
++            }
          }
      }
  
@@@ -74,22 -73,22 +80,28 @@@
              execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, 1, 1);
              execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, 2, 2);
  
--            // order by the only column in the selection
--            assertRows(execute("SELECT blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY b ASC", 0),
--                    row(0), row(1), row(2));
++            for (boolean flush : new boolean[]{true, false})
++            {
++                if (flush)
++                    flush();
  
--            assertRows(execute("SELECT blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY b DESC", 0),
--                    row(2), row(1), row(0));
++                // order by the only column in the selection
++                assertRows(execute("SELECT blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY b ASC", 0),
++                           row(0), row(1), row(2));
  
--            // order by a column not in the selection
--            assertRows(execute("SELECT blobAsInt(intAsBlob(c)) FROM %s WHERE a=? ORDER BY b ASC", 0),
--                    row(0), row(1), row(2));
++                assertRows(execute("SELECT blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY b DESC", 0),
++                           row(2), row(1), row(0));
  
--            assertRows(execute("SELECT blobAsInt(intAsBlob(c)) FROM %s WHERE a=? ORDER BY b DESC", 0),
--                    row(2), row(1), row(0));
++                // order by a column not in the selection
++                assertRows(execute("SELECT blobAsInt(intAsBlob(c)) FROM %s WHERE a=? ORDER BY b ASC", 0),
++                           row(0), row(1), row(2));
  
--            assertInvalid("SELECT * FROM %s WHERE a=? ORDER BY c ASC", 0);
--            assertInvalid("SELECT * FROM %s WHERE a=? ORDER BY c DESC", 0);
++                assertRows(execute("SELECT blobAsInt(intAsBlob(c)) FROM %s WHERE a=? ORDER BY b DESC", 0),
++                           row(2), row(1), row(0));
++
++                assertInvalid("SELECT * FROM %s WHERE a=? ORDER BY c ASC", 0);
++                assertInvalid("SELECT * FROM %s WHERE a=? ORDER BY c DESC", 0);
++            }
          }
      }
  
@@@ -105,15 -104,15 +117,20 @@@
              execute("INSERT INTO %s (a, b, c) VALUES (?, ?, {a: ?})", 0, 1, 1);
              execute("INSERT INTO %s (a, b, c) VALUES (?, ?, {a: ?})", 0, 2, 2);
  
--            // order by a column not in the selection
--            assertRows(execute("SELECT c.a FROM %s WHERE a=? ORDER BY b ASC", 0),
--                    row(0), row(1), row(2));
++            for (boolean flush : new boolean[]{true, false})
++            {
++                if (flush)
++                    flush();
++                // order by a column not in the selection
++                assertRows(execute("SELECT c.a FROM %s WHERE a=? ORDER BY b ASC", 0),
++                           row(0), row(1), row(2));
  
--            assertRows(execute("SELECT c.a FROM %s WHERE a=? ORDER BY b DESC", 0),
--                    row(2), row(1), row(0));
++                assertRows(execute("SELECT c.a FROM %s WHERE a=? ORDER BY b DESC", 0),
++                           row(2), row(1), row(0));
  
--            assertRows(execute("SELECT blobAsInt(intAsBlob(c.a)) FROM %s WHERE a=? ORDER BY b DESC", 0),
--                       row(2), row(1), row(0));
++                assertRows(execute("SELECT blobAsInt(intAsBlob(c.a)) FROM %s WHERE a=? ORDER BY b DESC", 0),
++                           row(2), row(1), row(0));
++            }
              dropTable("DROP TABLE %s");
          }
      }
@@@ -129,62 -128,62 +146,68 @@@
          execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, 1, 1, 4);
          execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, 1, 2, 5);
  
--        assertRows(execute("SELECT * FROM %s WHERE a=? ORDER BY b ASC", 0),
--                row(0, 0, 0, 0),
--                row(0, 0, 1, 1),
--                row(0, 0, 2, 2),
--                row(0, 1, 0, 3),
--                row(0, 1, 1, 4),
--                row(0, 1, 2, 5)
--        );
--
--        assertRows(execute("SELECT * FROM %s WHERE a=? ORDER BY b DESC", 0),
--                row(0, 1, 2, 5),
--                row(0, 1, 1, 4),
--                row(0, 1, 0, 3),
--                row(0, 0, 2, 2),
--                row(0, 0, 1, 1),
--                row(0, 0, 0, 0)
--        );
--
--        assertRows(execute("SELECT * FROM %s WHERE a=? ORDER BY b DESC, c DESC", 0),
--                row(0, 1, 2, 5),
--                row(0, 1, 1, 4),
--                row(0, 1, 0, 3),
--                row(0, 0, 2, 2),
--                row(0, 0, 1, 1),
--                row(0, 0, 0, 0)
--        );
--
--        assertInvalid("SELECT * FROM %s WHERE a=? ORDER BY c ASC", 0);
--        assertInvalid("SELECT * FROM %s WHERE a=? ORDER BY c DESC", 0);
--        assertInvalid("SELECT * FROM %s WHERE a=? ORDER BY b ASC, c DESC", 0);
--        assertInvalid("SELECT * FROM %s WHERE a=? ORDER BY b DESC, c ASC", 0);
--        assertInvalid("SELECT * FROM %s WHERE a=? ORDER BY d ASC", 0);
--
--        // select and order by b
--        assertRows(execute("SELECT b FROM %s WHERE a=? ORDER BY b ASC", 0),
--                row(0), row(0), row(0), row(1), row(1), row(1));
--        assertRows(execute("SELECT b FROM %s WHERE a=? ORDER BY b DESC", 0),
--                row(1), row(1), row(1), row(0), row(0), row(0));
--
--        // select c, order by b
--        assertRows(execute("SELECT c FROM %s WHERE a=? ORDER BY b ASC", 0),
--                row(0), row(1), row(2), row(0), row(1), row(2));
--        assertRows(execute("SELECT c FROM %s WHERE a=? ORDER BY b DESC", 0),
--                row(2), row(1), row(0), row(2), row(1), row(0));
--
--        // select c, order by b, c
--        assertRows(execute("SELECT c FROM %s WHERE a=? ORDER BY b ASC, c ASC", 0),
--                row(0), row(1), row(2), row(0), row(1), row(2));
--        assertRows(execute("SELECT c FROM %s WHERE a=? ORDER BY b DESC, c DESC", 0),
--                row(2), row(1), row(0), row(2), row(1), row(0));
--
--        // select d, order by b, c
--        assertRows(execute("SELECT d FROM %s WHERE a=? ORDER BY b ASC, c ASC", 0),
--                row(0), row(1), row(2), row(3), row(4), row(5));
--        assertRows(execute("SELECT d FROM %s WHERE a=? ORDER BY b DESC, c DESC", 0),
--                row(5), row(4), row(3), row(2), row(1), row(0));
++        for (boolean flush : new boolean[]{true, false})
++        {
++            if (flush)
++                flush();
++
++            assertRows(execute("SELECT * FROM %s WHERE a=? ORDER BY b ASC", 0),
++                       row(0, 0, 0, 0),
++                       row(0, 0, 1, 1),
++                       row(0, 0, 2, 2),
++                       row(0, 1, 0, 3),
++                       row(0, 1, 1, 4),
++                       row(0, 1, 2, 5)
++                    );
++
++            assertRows(execute("SELECT * FROM %s WHERE a=? ORDER BY b DESC", 0),
++                       row(0, 1, 2, 5),
++                       row(0, 1, 1, 4),
++                       row(0, 1, 0, 3),
++                       row(0, 0, 2, 2),
++                       row(0, 0, 1, 1),
++                       row(0, 0, 0, 0)
++                    );
++
++            assertRows(execute("SELECT * FROM %s WHERE a=? ORDER BY b DESC, c DESC", 0),
++                       row(0, 1, 2, 5),
++                       row(0, 1, 1, 4),
++                       row(0, 1, 0, 3),
++                       row(0, 0, 2, 2),
++                       row(0, 0, 1, 1),
++                       row(0, 0, 0, 0)
++                    );
++
++            assertInvalid("SELECT * FROM %s WHERE a=? ORDER BY c ASC", 0);
++            assertInvalid("SELECT * FROM %s WHERE a=? ORDER BY c DESC", 0);
++            assertInvalid("SELECT * FROM %s WHERE a=? ORDER BY b ASC, c DESC", 0);
++            assertInvalid("SELECT * FROM %s WHERE a=? ORDER BY b DESC, c ASC", 0);
++            assertInvalid("SELECT * FROM %s WHERE a=? ORDER BY d ASC", 0);
++
++            // select and order by b
++            assertRows(execute("SELECT b FROM %s WHERE a=? ORDER BY b ASC", 0),
++                       row(0), row(0), row(0), row(1), row(1), row(1));
++            assertRows(execute("SELECT b FROM %s WHERE a=? ORDER BY b DESC", 0),
++                       row(1), row(1), row(1), row(0), row(0), row(0));
++
++            // select c, order by b
++            assertRows(execute("SELECT c FROM %s WHERE a=? ORDER BY b ASC", 0),
++                       row(0), row(1), row(2), row(0), row(1), row(2));
++            assertRows(execute("SELECT c FROM %s WHERE a=? ORDER BY b DESC", 0),
++                       row(2), row(1), row(0), row(2), row(1), row(0));
++
++            // select c, order by b, c
++            assertRows(execute("SELECT c FROM %s WHERE a=? ORDER BY b ASC, c ASC", 0),
++                       row(0), row(1), row(2), row(0), row(1), row(2));
++            assertRows(execute("SELECT c FROM %s WHERE a=? ORDER BY b DESC, c DESC", 0),
++                       row(2), row(1), row(0), row(2), row(1), row(0));
++
++            // select d, order by b, c
++            assertRows(execute("SELECT d FROM %s WHERE a=? ORDER BY b ASC, c ASC", 0),
++                       row(0), row(1), row(2), row(3), row(4), row(5));
++            assertRows(execute("SELECT d FROM %s WHERE a=? ORDER BY b DESC, c DESC", 0),
++                       row(5), row(4), row(3), row(2), row(1), row(0));
++        }
      }
  
      @Test
@@@ -198,41 -197,41 +221,46 @@@
          execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, 1, 1, 4);
          execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, 1, 2, 5);
  
--        assertInvalid("SELECT blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY c ASC", 0);
--        assertInvalid("SELECT blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY c DESC", 0);
--        assertInvalid("SELECT blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY b ASC, c DESC", 0);
--        assertInvalid("SELECT blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY b DESC, c ASC", 0);
--        assertInvalid("SELECT blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY d ASC", 0);
--
--        // select and order by b
--        assertRows(execute("SELECT blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY b ASC", 0),
--                   row(0), row(0), row(0), row(1), row(1), row(1));
--        assertRows(execute("SELECT blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY b DESC", 0),
--                   row(1), row(1), row(1), row(0), row(0), row(0));
--
--        assertRows(execute("SELECT b, blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY b ASC", 0),
--                row(0, 0), row(0, 0), row(0, 0), row(1, 1), row(1, 1), row(1, 1));
--        assertRows(execute("SELECT b, blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY b DESC", 0),
--                row(1, 1), row(1, 1), row(1, 1), row(0, 0), row(0, 0), row(0, 0));
--
--        // select c, order by b
--        assertRows(execute("SELECT blobAsInt(intAsBlob(c)) FROM %s WHERE a=? ORDER BY b ASC", 0),
--                row(0), row(1), row(2), row(0), row(1), row(2));
--        assertRows(execute("SELECT blobAsInt(intAsBlob(c)) FROM %s WHERE a=? ORDER BY b DESC", 0),
--                row(2), row(1), row(0), row(2), row(1), row(0));
--
--        // select c, order by b, c
--        assertRows(execute("SELECT blobAsInt(intAsBlob(c)) FROM %s WHERE a=? ORDER BY b ASC, c ASC", 0),
--                row(0), row(1), row(2), row(0), row(1), row(2));
--        assertRows(execute("SELECT blobAsInt(intAsBlob(c)) FROM %s WHERE a=? ORDER BY b DESC, c DESC", 0),
--                row(2), row(1), row(0), row(2), row(1), row(0));
--
--        // select d, order by b, c
--        assertRows(execute("SELECT blobAsInt(intAsBlob(d)) FROM %s WHERE a=? ORDER BY b ASC, c ASC", 0),
--                row(0), row(1), row(2), row(3), row(4), row(5));
--        assertRows(execute("SELECT blobAsInt(intAsBlob(d)) FROM %s WHERE a=? ORDER BY b DESC, c DESC", 0),
--                row(5), row(4), row(3), row(2), row(1), row(0));
++        for (boolean flush : new boolean[]{true, false})
++        {
++            if (flush)
++                flush();
++
++            assertInvalid("SELECT blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY c ASC", 0);
++            assertInvalid("SELECT blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY c DESC", 0);
++            assertInvalid("SELECT blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY b ASC, c DESC", 0);
++            assertInvalid("SELECT blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY b DESC, c ASC", 0);
++            assertInvalid("SELECT blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY d ASC", 0);
  
++            // select and order by b
++            assertRows(execute("SELECT blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY b ASC", 0),
++                       row(0), row(0), row(0), row(1), row(1), row(1));
++            assertRows(execute("SELECT blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY b DESC", 0),
++                       row(1), row(1), row(1), row(0), row(0), row(0));
++
++            assertRows(execute("SELECT b, blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY b ASC", 0),
++                       row(0, 0), row(0, 0), row(0, 0), row(1, 1), row(1, 1), row(1, 1));
++            assertRows(execute("SELECT b, blobAsInt(intAsBlob(b)) FROM %s WHERE a=? ORDER BY b DESC", 0),
++                       row(1, 1), row(1, 1), row(1, 1), row(0, 0), row(0, 0), row(0, 0));
++
++            // select c, order by b
++            assertRows(execute("SELECT blobAsInt(intAsBlob(c)) FROM %s WHERE a=? ORDER BY b ASC", 0),
++                       row(0), row(1), row(2), row(0), row(1), row(2));
++            assertRows(execute("SELECT blobAsInt(intAsBlob(c)) FROM %s WHERE a=? ORDER BY b DESC", 0),
++                       row(2), row(1), row(0), row(2), row(1), row(0));
++
++            // select c, order by b, c
++            assertRows(execute("SELECT blobAsInt(intAsBlob(c)) FROM %s WHERE a=? ORDER BY b ASC, c ASC", 0),
++                       row(0), row(1), row(2), row(0), row(1), row(2));
++            assertRows(execute("SELECT blobAsInt(intAsBlob(c)) FROM %s WHERE a=? ORDER BY b DESC, c DESC", 0),
++                       row(2), row(1), row(0), row(2), row(1), row(0));
++
++            // select d, order by b, c
++            assertRows(execute("SELECT blobAsInt(intAsBlob(d)) FROM %s WHERE a=? ORDER BY b ASC, c ASC", 0),
++                       row(0), row(1), row(2), row(3), row(4), row(5));
++            assertRows(execute("SELECT blobAsInt(intAsBlob(d)) FROM %s WHERE a=? ORDER BY b DESC, c DESC", 0),
++                       row(5), row(4), row(3), row(2), row(1), row(0));
++        }
      }
  
      /**
@@@ -247,8 -246,8 +275,14 @@@
          for (int i = 0; i < 10; i++)
              execute("INSERT INTO %s (k, c, v) VALUES (0, ?, ?)", i, i);
  
--        assertRows(execute("SELECT v FROM %s WHERE k = 0 ORDER BY c DESC"),
--                   row(9), row(8), row(7), row(6), row(5), row(4), row(3), row(2), row(1), row(0));
++        for (boolean flush : new boolean[]{true, false})
++        {
++            if (flush)
++                flush();
++
++            assertRows(execute("SELECT v FROM %s WHERE k = 0 ORDER BY c DESC"),
++                       row(9), row(8), row(7), row(6), row(5), row(4), row(3), row(2), row(1), row(0));
++        }
  
          createTable("CREATE TABLE %s (k int, c1 int, c2 int, v int, PRIMARY KEY (k, c1, c2)) WITH COMPACT STORAGE");
  
@@@ -256,15 -255,15 +290,21 @@@
              for (int j = 0; j < 2; j++)
                  execute("INSERT INTO %s (k, c1, c2, v) VALUES (0, ?, ?, ?)", i, j, i * 2 + j);
  
--        assertInvalid("SELECT v FROM %s WHERE k = 0 ORDER BY c DESC");
--        assertInvalid("SELECT v FROM %s WHERE k = 0 ORDER BY c2 DESC");
--        assertInvalid("SELECT v FROM %s WHERE k = 0 ORDER BY k DESC");
++        for (boolean flush : new boolean[]{true, false})
++        {
++            if (flush)
++                flush();
++
++            assertInvalid("SELECT v FROM %s WHERE k = 0 ORDER BY c DESC");
++            assertInvalid("SELECT v FROM %s WHERE k = 0 ORDER BY c2 DESC");
++            assertInvalid("SELECT v FROM %s WHERE k = 0 ORDER BY k DESC");
  
--        assertRows(execute("SELECT v FROM %s WHERE k = 0 ORDER BY c1 DESC"),
--                   row(7), row(6), row(5), row(4), row(3), row(2), row(1), row(0));
++            assertRows(execute("SELECT v FROM %s WHERE k = 0 ORDER BY c1 DESC"),
++                       row(7), row(6), row(5), row(4), row(3), row(2), row(1), row(0));
  
--        assertRows(execute("SELECT v FROM %s WHERE k = 0 ORDER BY c1"),
--                   row(0), row(1), row(2), row(3), row(4), row(5), row(6), row(7));
++            assertRows(execute("SELECT v FROM %s WHERE k = 0 ORDER BY c1"),
++                       row(0), row(1), row(2), row(3), row(4), row(5), row(6), row(7));
++        }
      }
  
      /**
@@@ -281,23 -280,23 +321,29 @@@
          execute("INSERT INTO %s (row, number, string) VALUES ('row', 3, 'three')");
          execute("INSERT INTO %s (row, number, string) VALUES ('row', 4, 'four')");
  
--        assertRows(execute("SELECT number FROM %s WHERE row='row' AND number < 3 ORDER BY number ASC"),
--                   row(1), row(2));
++        for (boolean flush : new boolean[]{true, false})
++        {
++            if (flush)
++                flush();
++
++            assertRows(execute("SELECT number FROM %s WHERE row='row' AND number < 3 ORDER BY number ASC"),
++                       row(1), row(2));
  
--        assertRows(execute("SELECT number FROM %s WHERE row='row' AND number >= 3 ORDER BY number ASC"),
--                   row(3), row(4));
++            assertRows(execute("SELECT number FROM %s WHERE row='row' AND number >= 3 ORDER BY number ASC"),
++                       row(3), row(4));
  
--        assertRows(execute("SELECT number FROM %s WHERE row='row' AND number < 3 ORDER BY number DESC"),
--                   row(2), row(1));
++            assertRows(execute("SELECT number FROM %s WHERE row='row' AND number < 3 ORDER BY number DESC"),
++                       row(2), row(1));
  
--        assertRows(execute("SELECT number FROM %s WHERE row='row' AND number >= 3 ORDER BY number DESC"),
--                   row(4), row(3));
++            assertRows(execute("SELECT number FROM %s WHERE row='row' AND number >= 3 ORDER BY number DESC"),
++                       row(4), row(3));
  
--        assertRows(execute("SELECT number FROM %s WHERE row='row' AND number > 3 ORDER BY number DESC"),
--                   row(4));
++            assertRows(execute("SELECT number FROM %s WHERE row='row' AND number > 3 ORDER BY number DESC"),
++                       row(4));
  
--        assertRows(execute("SELECT number FROM %s WHERE row='row' AND number <= 3 ORDER BY number DESC"),
--                   row(3), row(2), row(1));
++            assertRows(execute("SELECT number FROM %s WHERE row='row' AND number <= 3 ORDER BY number DESC"),
++                       row(3), row(2), row(1));
++        }
      }
  
      /**
@@@ -330,14 -329,14 +376,20 @@@
          execute("INSERT INTO %s (my_id, col1, value) VALUES ( 'key3', 2, 'b')");
          execute("INSERT INTO %s (my_id, col1, value) VALUES ( 'key4', 4, 'd')");
  
--        assertRows(execute("SELECT col1 FROM %s WHERE my_id in('key1', 'key2', 'key3') ORDER BY col1"),
--                   row(1), row(2), row(3));
++        for (boolean flush : new boolean[]{true, false})
++        {
++            if (flush)
++                flush();
 +
-         assertRows(execute("SELECT col1, my_id FROM %s WHERE my_id in('key1', 'key2', 'key3') ORDER BY col1"),
-                    row(1, "key1"), row(2, "key3"), row(3, "key2"));
++            assertRows(execute("SELECT col1 FROM %s WHERE my_id in('key1', 'key2', 'key3') ORDER BY col1"),
++                       row(1), row(2), row(3));
  
-         assertRows(execute("SELECT my_id, col1 FROM %s WHERE my_id in('key1', 'key2', 'key3') ORDER BY col1"),
-                    row("key1", 1), row("key3", 2), row("key2", 3));
 -        assertRows(execute("SELECT col1, my_id FROM %s WHERE my_id in('key1', 'key2', 'key3') ORDER BY col1"),
 -                   row(1, "key1"), row(2, "key3"), row(3, "key2"));
++            assertRows(execute("SELECT col1, my_id FROM %s WHERE my_id in('key1', 'key2', 'key3') ORDER BY col1"),
++                       row(1, "key1"), row(2, "key3"), row(3, "key2"));
+ 
 -        assertRows(execute("SELECT my_id, col1 FROM %s WHERE my_id in('key1', 'key2', 'key3') ORDER BY col1"),
 -                   row("key1", 1), row("key3", 2), row("key2", 3));
++            assertRows(execute("SELECT my_id, col1 FROM %s WHERE my_id in('key1', 'key2', 'key3') ORDER BY col1"),
++                       row("key1", 1), row("key3", 2), row("key2", 3));
++        }
  
          createTable("CREATE TABLE %s (pk1 int, pk2 int, c int, v text, PRIMARY KEY ((pk1, pk2), c) )");
          execute("INSERT INTO %s (pk1, pk2, c, v) VALUES (?, ?, ?, ?)", 1, 1, 2, "A");
@@@ -345,25 -344,25 +397,31 @@@
          execute("INSERT INTO %s (pk1, pk2, c, v) VALUES (?, ?, ?, ?)", 1, 3, 3, "C");
          execute("INSERT INTO %s (pk1, pk2, c, v) VALUES (?, ?, ?, ?)", 1, 1, 4, "D");
  
--        assertRows(execute("SELECT v, ttl(v), c FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c; ", 1, 1, 2),
--                   row("B", null, 1),
--                   row("A", null, 2),
--                   row("D", null, 4));
--
--        assertRows(execute("SELECT v, ttl(v), c as name_1 FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c; ", 1, 1, 2),
--                   row("B", null, 1),
--                   row("A", null, 2),
--                   row("D", null, 4));
--
--        assertRows(execute("SELECT v FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c; ", 1, 1, 2),
--                   row("B"),
--                   row("A"),
--                   row("D"));
--
--        assertRows(execute("SELECT v as c FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c; ", 1, 1, 2),
--                   row("B"),
--                   row("A"),
--                   row("D"));
++        for (boolean flush : new boolean[]{true, false})
++        {
++            if (flush)
++                flush();
++
++            assertRows(execute("SELECT v, ttl(v), c FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c; ", 1, 1, 2),
++                       row("B", null, 1),
++                       row("A", null, 2),
++                       row("D", null, 4));
++
++            assertRows(execute("SELECT v, ttl(v), c as name_1 FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c; ", 1, 1, 2),
++                       row("B", null, 1),
++                       row("A", null, 2),
++                       row("D", null, 4));
++
++            assertRows(execute("SELECT v FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c; ", 1, 1, 2),
++                       row("B"),
++                       row("A"),
++                       row("D"));
++
++            assertRows(execute("SELECT v as c FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c; ", 1, 1, 2),
++                       row("B"),
++                       row("A"),
++                       row("D"));
++        }
  
          createTable("CREATE TABLE %s (pk1 int, pk2 int, c1 int, c2 int, v text, PRIMARY KEY ((pk1, pk2), c1, c2) )");
          execute("INSERT INTO %s (pk1, pk2, c1, c2, v) VALUES (?, ?, ?, ?, ?)", 1, 1, 4, 4, "A");
@@@ -371,68 -370,25 +429,80 @@@
          execute("INSERT INTO %s (pk1, pk2, c1, c2, v) VALUES (?, ?, ?, ?, ?)", 1, 3, 3, 3, "C");
          execute("INSERT INTO %s (pk1, pk2, c1, c2, v) VALUES (?, ?, ?, ?, ?)", 1, 1, 4, 1, "D");
  
--        assertRows(execute("SELECT v, ttl(v), c1, c2 FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c1, c2; ", 1, 1, 2),
--                   row("B", null, 1, 2),
--                   row("D", null, 4, 1),
--                   row("A", null, 4, 4));
--
--        assertRows(execute("SELECT v, ttl(v), c1 as name_1, c2 as name_2 FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c1, c2; ", 1, 1, 2),
--                   row("B", null, 1, 2),
--                   row("D", null, 4, 1),
--                   row("A", null, 4, 4));
--
--        assertRows(execute("SELECT v FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c1, c2; ", 1, 1, 2),
--                   row("B"),
--                   row("D"),
--                   row("A"));
--
--        assertRows(execute("SELECT v as c2 FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c1, c2; ", 1, 1, 2),
--                   row("B"),
--                   row("D"),
--                   row("A"));
++        for (boolean flush : new boolean[]{true, false})
++        {
++            if (flush)
++                flush();
++
++            assertRows(execute("SELECT v, ttl(v), c1, c2 FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c1, c2; ", 1, 1, 2),
++                       row("B", null, 1, 2),
++                       row("D", null, 4, 1),
++                       row("A", null, 4, 4));
++
++            assertRows(execute("SELECT v, ttl(v), c1 as name_1, c2 as name_2 FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c1, c2; ", 1, 1, 2),
++                       row("B", null, 1, 2),
++                       row("D", null, 4, 1),
++                       row("A", null, 4, 4));
++
++            assertRows(execute("SELECT v FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c1, c2; ", 1, 1, 2),
++                       row("B"),
++                       row("D"),
++                       row("A"));
++
++            assertRows(execute("SELECT v as c2 FROM %s where pk1 = ? AND pk2 IN (?, ?) ORDER BY c1, c2; ", 1, 1, 2),
++                       row("B"),
++                       row("D"),
++                       row("A"));
++        }
 +    }
 +
 +    @Test
 +    public void testOrderByForInClauseWithNullValue() throws Throwable
 +    {
 +        createTable("CREATE TABLE %s (a int, b int, c int, s int static, d int, PRIMARY KEY (a, b, c))");
 +
 +        execute("INSERT INTO %s (a, b, c, d) VALUES (1, 1, 1, 1)");
 +        execute("INSERT INTO %s (a, b, c, d) VALUES (1, 1, 2, 1)");
 +        execute("INSERT INTO %s (a, b, c, d) VALUES (2, 2, 1, 1)");
 +        execute("INSERT INTO %s (a, b, c, d) VALUES (2, 2, 2, 1)");
 +
 +        execute("UPDATE %s SET s = 1 WHERE a = 1");
 +        execute("UPDATE %s SET s = 2 WHERE a = 2");
 +        execute("UPDATE %s SET s = 3 WHERE a = 3");
 +
-         assertRows(execute("SELECT a, b, c, d, s FROM %s WHERE a IN (1, 2, 3) ORDER BY b DESC"),
-                    row(2, 2, 2, 1, 2),
-                    row(2, 2, 1, 1, 2),
-                    row(1, 1, 2, 1, 1),
-                    row(1, 1, 1, 1, 1),
-                    row(3, null, null, null, 3));
- 
-         assertRows(execute("SELECT a, b, c, d, s FROM %s WHERE a IN (1, 2, 3) ORDER BY b ASC"),
-                    row(3, null, null, null, 3),
-                    row(1, 1, 1, 1, 1),
-                    row(1, 1, 2, 1, 1),
-                    row(2, 2, 1, 1, 2),
-                    row(2, 2, 2, 1, 2));
- 
-         assertRows(execute("SELECT a, b, c, d, s FROM %s WHERE a IN (1, 2, 3) ORDER BY b DESC , c DESC"),
-                    row(2, 2, 2, 1, 2),
-                    row(2, 2, 1, 1, 2),
-                    row(1, 1, 2, 1, 1),
-                    row(1, 1, 1, 1, 1),
-                    row(3, null, null, null, 3));
- 
-         assertRows(execute("SELECT a, b, c, d, s FROM %s WHERE a IN (1, 2, 3) ORDER BY b ASC, c ASC"),
-                    row(3, null, null, null, 3),
-                    row(1, 1, 1, 1, 1),
-                    row(1, 1, 2, 1, 1),
-                    row(2, 2, 1, 1, 2),
-                    row(2, 2, 2, 1, 2));
++        for (boolean flush : new boolean[]{true, false})
++        {
++            if (flush)
++                flush();
++
++            assertRows(execute("SELECT a, b, c, d, s FROM %s WHERE a IN (1, 2, 3) ORDER BY b DESC"),
++                       row(2, 2, 2, 1, 2),
++                       row(2, 2, 1, 1, 2),
++                       row(1, 1, 2, 1, 1),
++                       row(1, 1, 1, 1, 1),
++                       row(3, null, null, null, 3));
++
++            assertRows(execute("SELECT a, b, c, d, s FROM %s WHERE a IN (1, 2, 3) ORDER BY b ASC"),
++                       row(3, null, null, null, 3),
++                       row(1, 1, 1, 1, 1),
++                       row(1, 1, 2, 1, 1),
++                       row(2, 2, 1, 1, 2),
++                       row(2, 2, 2, 1, 2));
++
++            assertRows(execute("SELECT a, b, c, d, s FROM %s WHERE a IN (1, 2, 3) ORDER BY b DESC , c DESC"),
++                       row(2, 2, 2, 1, 2),
++                       row(2, 2, 1, 1, 2),
++                       row(1, 1, 2, 1, 1),
++                       row(1, 1, 1, 1, 1),
++                       row(3, null, null, null, 3));
++
++            assertRows(execute("SELECT a, b, c, d, s FROM %s WHERE a IN (1, 2, 3) ORDER BY b ASC, c ASC"),
++                       row(3, null, null, null, 3),
++                       row(1, 1, 1, 1, 1),
++                       row(1, 1, 2, 1, 1),
++                       row(2, 2, 1, 1, 2),
++                       row(2, 2, 2, 1, 2));
++        }
      }
  
      /**
@@@ -447,13 -403,13 +517,19 @@@
          for(int i =0; i < 10; i++)
              execute("INSERT INTO %s (k, c, v) VALUES (0, ?, ?)", i, i);
  
--        assertRows(execute("SELECT c, v FROM %s WHERE k = 0 ORDER BY c ASC"),
--                   row(0, 0), row(1, 1), row(2, 2), row(3, 3), row(4, 4),
--                   row(5, 5), row(6, 6), row(7, 7), row(8, 8), row(9, 9));
++        for (boolean flush : new boolean[]{true, false})
++        {
++            if (flush)
++                flush();
++
++            assertRows(execute("SELECT c, v FROM %s WHERE k = 0 ORDER BY c ASC"),
++                       row(0, 0), row(1, 1), row(2, 2), row(3, 3), row(4, 4),
++                       row(5, 5), row(6, 6), row(7, 7), row(8, 8), row(9, 9));
  
--        assertRows(execute("SELECT c, v FROM %s WHERE k = 0 ORDER BY c DESC"),
--                   row(9, 9), row(8, 8), row(7, 7), row(6, 6), row(5, 5),
--                   row(4, 4), row(3, 3), row(2, 2), row(1, 1), row(0, 0));
++            assertRows(execute("SELECT c, v FROM %s WHERE k = 0 ORDER BY c DESC"),
++                       row(9, 9), row(8, 8), row(7, 7), row(6, 6), row(5, 5),
++                       row(4, 4), row(3, 3), row(2, 2), row(1, 1), row(0, 0));
++        }
  
          createTable("CREATE TABLE %s (k int, c1 int, c2 int, v text, PRIMARY KEY (k, c1, c2)) WITH CLUSTERING ORDER BY (c1 ASC, c2 DESC)");
  
@@@ -461,28 -417,28 +537,34 @@@
              for(int j = 0; j < 10; j++)
                  execute("INSERT INTO %s (k, c1, c2, v) VALUES (0, ?, ?, ?)", i, j, String.format("%d%d", i, j));
  
--        assertInvalid("SELECT c1, c2, v FROM %s WHERE k = 0 ORDER BY c1 ASC, c2 ASC");
--        assertInvalid("SELECT c1, c2, v FROM %s WHERE k = 0 ORDER BY c1 DESC, c2 DESC");
++        for (boolean flush : new boolean[]{true, false})
++        {
++            if (flush)
++                flush();
  
--        Object[][] expectedRows = new Object[100][];
--        for(int i = 0; i < 10; i++)
--            for(int j = 9; j >= 0; j--)
--                expectedRows[i * 10 + (9 - j)] = row(i, j, String.format("%d%d", i, j));
++            assertInvalid("SELECT c1, c2, v FROM %s WHERE k = 0 ORDER BY c1 ASC, c2 ASC");
++            assertInvalid("SELECT c1, c2, v FROM %s WHERE k = 0 ORDER BY c1 DESC, c2 DESC");
  
--        assertRows(execute("SELECT c1, c2, v FROM %s WHERE k = 0 ORDER BY c1 ASC"),
--                   expectedRows);
++            Object[][] expectedRows = new Object[100][];
++            for(int i = 0; i < 10; i++)
++                for(int j = 9; j >= 0; j--)
++                    expectedRows[i * 10 + (9 - j)] = row(i, j, String.format("%d%d", i, j));
  
--        assertRows(execute("SELECT c1, c2, v FROM %s WHERE k = 0 ORDER BY c1 ASC, c2 DESC"),
--                   expectedRows);
++            assertRows(execute("SELECT c1, c2, v FROM %s WHERE k = 0 ORDER BY c1 ASC"),
++                       expectedRows);
  
--        for(int i = 9; i >= 0; i--)
--            for(int j = 0; j < 10; j++)
--                expectedRows[(9 - i) * 10 + j] = row(i, j, String.format("%d%d", i, j));
++            assertRows(execute("SELECT c1, c2, v FROM %s WHERE k = 0 ORDER BY c1 ASC, c2 DESC"),
++                       expectedRows);
++
++            for(int i = 9; i >= 0; i--)
++                for(int j = 0; j < 10; j++)
++                    expectedRows[(9 - i) * 10 + j] = row(i, j, String.format("%d%d", i, j));
  
--        assertRows(execute("SELECT c1, c2, v FROM %s WHERE k = 0 ORDER BY c1 DESC, c2 ASC"),
--                   expectedRows);
++            assertRows(execute("SELECT c1, c2, v FROM %s WHERE k = 0 ORDER BY c1 DESC, c2 ASC"),
++                       expectedRows);
  
--        assertInvalid("SELECT c1, c2, v FROM %s WHERE k = 0 ORDER BY c2 DESC, c1 ASC");
++            assertInvalid("SELECT c1, c2, v FROM %s WHERE k = 0 ORDER BY c2 DESC, c1 ASC");
++        }
      }
  
      /**
@@@ -497,18 -453,18 +579,24 @@@
              for (int j = 0; j < 2; j++)
                  execute("INSERT INTO %s (k, c1, c2) VALUES ('foo', ?, ?)", i, j);
  
--        assertRows(execute("SELECT c1, c2 FROM %s WHERE k = 'foo'"),
--                   row(0, 1), row(0, 0), row(1, 1), row(1, 0));
++        for (boolean flush : new boolean[]{true, false})
++        {
++            if (flush)
++                flush();
++
++            assertRows(execute("SELECT c1, c2 FROM %s WHERE k = 'foo'"),
++                       row(0, 1), row(0, 0), row(1, 1), row(1, 0));
  
--        assertRows(execute("SELECT c1, c2 FROM %s WHERE k = 'foo' ORDER BY c1 ASC, c2 DESC"),
--                   row(0, 1), row(0, 0), row(1, 1), row(1, 0));
++            assertRows(execute("SELECT c1, c2 FROM %s WHERE k = 'foo' ORDER BY c1 ASC, c2 DESC"),
++                       row(0, 1), row(0, 0), row(1, 1), row(1, 0));
  
--        assertRows(execute("SELECT c1, c2 FROM %s WHERE k = 'foo' ORDER BY c1 DESC, c2 ASC"),
--                   row(1, 0), row(1, 1), row(0, 0), row(0, 1));
++            assertRows(execute("SELECT c1, c2 FROM %s WHERE k = 'foo' ORDER BY c1 DESC, c2 ASC"),
++                       row(1, 0), row(1, 1), row(0, 0), row(0, 1));
  
--        assertInvalid("SELECT c1, c2 FROM %s WHERE k = 'foo' ORDER BY c2 DESC");
--        assertInvalid("SELECT c1, c2 FROM %s WHERE k = 'foo' ORDER BY c2 ASC");
--        assertInvalid("SELECT c1, c2 FROM %s WHERE k = 'foo' ORDER BY c1 ASC, c2 ASC");
++            assertInvalid("SELECT c1, c2 FROM %s WHERE k = 'foo' ORDER BY c2 DESC");
++            assertInvalid("SELECT c1, c2 FROM %s WHERE k = 'foo' ORDER BY c2 ASC");
++            assertInvalid("SELECT c1, c2 FROM %s WHERE k = 'foo' ORDER BY c1 ASC, c2 ASC");
++        }
      }
  
      /**
@@@ -522,9 -478,9 +610,15 @@@
          execute("INSERT INTO %s(k, c1, c2) VALUES (0, 0, 1)");
          execute("INSERT INTO %s(k, c1, c2) VALUES (0, 0, 2)");
  
--        assertRows(execute("SELECT * FROM %s WHERE k=0 AND c1 = 0 AND c2 IN (2, 0) ORDER BY c1 DESC"),
--                   row(0, 0, 2),
--                   row(0, 0, 0));
++        for (boolean flush : new boolean[]{true, false})
++        {
++            if (flush)
++                flush();
++
++            assertRows(execute("SELECT * FROM %s WHERE k=0 AND c1 = 0 AND c2 IN (2, 0) ORDER BY c1 DESC"),
++                       row(0, 0, 2),
++                       row(0, 0, 0));
++        }
      }
  
      /**
@@@ -543,87 -499,44 +637,99 @@@
          execute("INSERT INTO %s (k, c1, c2, v) VALUES (1, 1, 1, 4)");
          execute("INSERT INTO %s (k, c1, c2, v) VALUES (1, 1, 2, 5)");
  
--        assertRows(execute("SELECT * FROM %s WHERE k=0 AND c1 = 0 AND c2 IN (2, 0)"),
--                   row(0, 0, 0, 0),
--                   row(0, 0, 2, 2));
--        assertRows(execute("SELECT * FROM %s WHERE k=0 AND c1 = 0 AND c2 IN (2, 0) ORDER BY c1 ASC, c2 ASC"),
--                   row(0, 0, 0, 0),
--                   row(0, 0, 2, 2));
--
--        // check that we don 't need to select the column on which we order
--        assertRows(execute("SELECT v FROM %s WHERE k=0 AND c1 = 0 AND c2 IN (2, 0)"),
--                   row(0),
--                   row(2));
--        assertRows(execute("SELECT v FROM %s WHERE k=0 AND c1 = 0 AND c2 IN (2, 0) ORDER BY c1 ASC"),
--                   row(0),
--                   row(2));
--        assertRows(execute("SELECT v FROM %s WHERE k=0 AND c1 = 0 AND c2 IN (2, 0) ORDER BY c1 DESC"),
--                   row(2),
--                   row(0));
- 
--        assertRows(execute("SELECT v FROM %s WHERE k IN (1, 0)"),
-                    row(0),
-                    row(1),
-                    row(2),
--                   row(3),
--                   row(4),
-                    row(5));
 -                   row(5),
 -                   row(0),
 -                   row(1),
 -                   row(2));
--
--        assertRows(execute("SELECT v FROM %s WHERE k IN (1, 0) ORDER BY c1 ASC"),
--                   row(0),
--                   row(1),
--                   row(2),
--                   row(3),
--                   row(4),
--                   row(5));
--
--        // we should also be able to use functions in the select clause (additional test for CASSANDRA - 8286)
--        Object[][] results = getRows(execute("SELECT writetime(v) FROM %s WHERE k IN (1, 0) ORDER BY c1 ASC"));
--
--        // since we don 't know the write times, just assert that the order matches the order we expect
--        assertTrue(isFirstIntSorted(results));
++        for (boolean flush : new boolean[]{true, false})
++        {
++            if (flush)
++                flush();
++
++            assertRows(execute("SELECT * FROM %s WHERE k=0 AND c1 = 0 AND c2 IN (2, 0)"),
++                       row(0, 0, 0, 0),
++                       row(0, 0, 2, 2));
++            assertRows(execute("SELECT * FROM %s WHERE k=0 AND c1 = 0 AND c2 IN (2, 0) ORDER BY c1 ASC, c2 ASC"),
++                       row(0, 0, 0, 0),
++                       row(0, 0, 2, 2));
++
++            // check that we don 't need to select the column on which we order
++            assertRows(execute("SELECT v FROM %s WHERE k=0 AND c1 = 0 AND c2 IN (2, 0)"),
++                       row(0),
++                       row(2));
++            assertRows(execute("SELECT v FROM %s WHERE k=0 AND c1 = 0 AND c2 IN (2, 0) ORDER BY c1 ASC"),
++                       row(0),
++                       row(2));
++            assertRows(execute("SELECT v FROM %s WHERE k=0 AND c1 = 0 AND c2 IN (2, 0) ORDER BY c1 DESC"),
++                       row(2),
++                       row(0));
++
++            assertRows(execute("SELECT v FROM %s WHERE k IN (1, 0)"),
++                       row(0),
++                       row(1),
++                       row(2),
++                       row(3),
++                       row(4),
++                       row(5));
++
++            assertRows(execute("SELECT v FROM %s WHERE k IN (1, 0) ORDER BY c1 ASC"),
++                       row(0),
++                       row(1),
++                       row(2),
++                       row(3),
++                       row(4),
++                       row(5));
++
++            // we should also be able to use functions in the select clause (additional test for CASSANDRA - 8286)
++            Object[][] results = getRows(execute("SELECT writetime(v) FROM %s WHERE k IN (1, 0) ORDER BY c1 ASC"));
++
++            // since we don 't know the write times, just assert that the order matches the order we expect
++            assertTrue(isFirstIntSorted(results));
++        }
 +    }
 +
 +    @Test
 +    public void testInOrderByWithTwoPartitionKeyColumns() throws Throwable
 +    {
 +        for (String option : asList("", "WITH CLUSTERING ORDER BY (col_3 DESC)"))
 +        {
 +            createTable("CREATE TABLE %s (col_1 int, col_2 int, col_3 int, PRIMARY KEY ((col_1, col_2), col_3)) " + option);
 +            execute("INSERT INTO %s (col_1, col_2, col_3) VALUES(?, ?, ?)", 1, 1, 1);
 +            execute("INSERT INTO %s (col_1, col_2, col_3) VALUES(?, ?, ?)", 1, 1, 2);
 +            execute("INSERT INTO %s (col_1, col_2, col_3) VALUES(?, ?, ?)", 1, 1, 13);
 +            execute("INSERT INTO %s (col_1, col_2, col_3) VALUES(?, ?, ?)", 1, 2, 10);
 +            execute("INSERT INTO %s (col_1, col_2, col_3) VALUES(?, ?, ?)", 1, 2, 11);
 +
-             assertRows(execute("select * from %s where col_1=? and col_2 IN (?, ?) order by col_3;", 1, 1, 2),
-                        row(1, 1, 1),
-                        row(1, 1, 2),
-                        row(1, 2, 10),
-                        row(1, 2, 11),
-                        row(1, 1, 13));
- 
-             assertRows(execute("select * from %s where col_1=? and col_2 IN (?, ?) order by col_3 desc;", 1, 1, 2),
-                        row(1, 1, 13),
-                        row(1, 2, 11),
-                        row(1, 2, 10),
-                        row(1, 1, 2),
-                        row(1, 1, 1));
- 
-             assertRows(execute("select * from %s where col_2 IN (?, ?) and col_1=? order by col_3;", 1, 2, 1),
-                        row(1, 1, 1),
-                        row(1, 1, 2),
-                        row(1, 2, 10),
-                        row(1, 2, 11),
-                        row(1, 1, 13));
- 
-             assertRows(execute("select * from %s where col_2 IN (?, ?) and col_1=? order by col_3 desc;", 1, 2, 1),
-                        row(1, 1, 13),
-                        row(1, 2, 11),
-                        row(1, 2, 10),
-                        row(1, 1, 2),
-                        row(1, 1, 1));
++            for (boolean flush : new boolean[]{true, false})
++            {
++                if (flush)
++                    flush();
++
++                assertRows(execute("select * from %s where col_1=? and col_2 IN (?, ?) order by col_3;", 1, 1, 2),
++                           row(1, 1, 1),
++                           row(1, 1, 2),
++                           row(1, 2, 10),
++                           row(1, 2, 11),
++                           row(1, 1, 13));
++
++                assertRows(execute("select * from %s where col_1=? and col_2 IN (?, ?) order by col_3 desc;", 1, 1, 2),
++                           row(1, 1, 13),
++                           row(1, 2, 11),
++                           row(1, 2, 10),
++                           row(1, 1, 2),
++                           row(1, 1, 1));
++
++                assertRows(execute("select * from %s where col_2 IN (?, ?) and col_1=? order by col_3;", 1, 2, 1),
++                           row(1, 1, 1),
++                           row(1, 1, 2),
++                           row(1, 2, 10),
++                           row(1, 2, 11),
++                           row(1, 1, 13));
++
++                assertRows(execute("select * from %s where col_2 IN (?, ?) and col_1=? order by col_3 desc;", 1, 2, 1),
++                           row(1, 1, 13),
++                           row(1, 2, 11),
++                           row(1, 2, 10),
++                           row(1, 1, 2),
++                           row(1, 1, 1));
++            }
 +        }
      }
  
      private boolean isFirstIntSorted(Object[][] rows)


[12/12] cassandra git commit: Merge branch cassandra-3.9 into trunk

Posted by bl...@apache.org.
Merge branch cassandra-3.9 into trunk


Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/d1bd71c4
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/d1bd71c4
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/d1bd71c4

Branch: refs/heads/trunk
Commit: d1bd71c4ea5e02d53bfcd99d5fb390d3ee18d17c
Parents: e22170e cb97649
Author: Benjamin Lerer <b....@gmail.com>
Authored: Tue Aug 16 15:50:14 2016 +0200
Committer: Benjamin Lerer <b....@gmail.com>
Committed: Tue Aug 16 15:52:20 2016 +0200

----------------------------------------------------------------------
 CHANGES.txt                                     |   1 +
 NEWS.txt                                        |  11 +-
 .../columniterator/AbstractSSTableIterator.java |  24 +-
 .../db/columniterator/SSTableIterator.java      |  17 +
 .../columniterator/SSTableReversedIterator.java |  19 +-
 .../cassandra/db/compaction/Scrubber.java       | 170 ++++-
 .../cassandra/db/marshal/ReversedType.java      |  10 -
 .../db/partitions/AbstractBTreePartition.java   |   7 +-
 .../db/partitions/ImmutableBTreePartition.java  |  34 +-
 .../db/rows/UnfilteredRowIterators.java         |  28 +
 .../validation/entities/SecondaryIndexTest.java | 131 ++++
 .../cql3/validation/operations/DeleteTest.java  | 113 +++
 .../operations/SelectOrderByTest.java           | 765 ++++++++++---------
 .../cql3/validation/operations/SelectTest.java  | 355 ++++++++-
 .../cassandra/db/marshal/ReversedTypeTest.java  |   4 +-
 15 files changed, 1269 insertions(+), 420 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/d1bd71c4/CHANGES.txt
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/cassandra/blob/d1bd71c4/NEWS.txt
----------------------------------------------------------------------
diff --cc NEWS.txt
index 56d2827,2edff8e..9bdeec1
--- a/NEWS.txt
+++ b/NEWS.txt
@@@ -13,64 -13,16 +13,73 @@@ restore snapshots created with the prev
  'sstableloader' tool. You can upgrade the file format of your snapshots
  using the provided 'sstableupgrade' tool.
  
- 
 +3.10
 +====
 +
 +New features
 +------------
 +   - Support for GROUP BY queries has been added.
 +   - A new compaction-stress tool has been added to test the throughput of compaction
 +     for any cassandra-stress user schema.  see compaction-stress help for how to use.
 +   - Compaction can now take into account overlapping tables that don't take part
 +     in the compaction to look for deleted or overwritten data in the compacted tables.
 +     Then such data is found, it can be safely discarded, which in turn should enable
 +     the removal of tombstones over that data.
 +
 +     The behavior can be engaged in two ways:
 +       - as a "nodetool garbagecollect -g CELL/ROW" operation, which applies
 +         single-table compaction on all sstables to discard deleted data in one step.
 +       - as a "provide_overlapping_tombstones:CELL/ROW/NONE" compaction strategy flag,
 +         which uses overlapping tables as a source of deletions/overwrites during all
 +         compactions.
 +     The argument specifies the granularity at which deleted data is to be found:
 +       - If ROW is specified, only whole deleted rows (or sets of rows) will be
 +         discarded.
 +       - If CELL is specified, any columns whose value is overwritten or deleted
 +         will also be discarded.
 +       - NONE (default) specifies the old behavior, overlapping tables are not used to
 +         decide when to discard data.
 +     Which option to use depends on your workload, both ROW and CELL increase the
 +     disk load on compaction (especially with the size-tiered compaction strategy),
 +     with CELL being more resource-intensive. Both should lead to better read
 +     performance if deleting rows (resp. overwriting or deleting cells) is common.
 +   - Prepared statements are now persisted in the table prepared_statements in
 +     the system keyspace. Upon startup, this table is used to preload all
 +     previously prepared statements - i.e. in many cases clients do not need to
 +     re-prepare statements against restarted nodes.
 +   - cqlsh can now connect to older Cassandra versions by downgrading the native
 +     protocol version. Please note that this is currently not part of our release
 +     testing and, as a consequence, it is not guaranteed to work in all cases.
 +     See CASSANDRA-12150 for more details.
 +   - Snapshots that are automatically taken before a table is dropped or truncated
 +     will have a "dropped" or "truncated" prefix on their snapshot tag name.
 +   - Metrics are exposed for successful and failed authentication attempts.
 +     These can be located using the object names org.apache.cassandra.metrics:type=Client,name=AuthSuccess
 +     and org.apache.cassandra.metrics:type=Client,name=AuthFailure respectively.
 +   - Add support to "unset" JSON fields in prepared statements by specifying DEFAULT UNSET.
 +     See CASSANDRA-11424 for details
 +
 +Upgrading
 +---------
 +    - Logs written to stdout are now consistent with logs written to files.
 +      Time is now local (it was UTC on the console and local in files). Date, thread, file
 +      and line info where added to stdout. (see CASSANDRA-12004)
 +    - The 'clientutil' jar, which has been somewhat broken on the 3.x branch, is not longer provided.
 +      The features provided by that jar are provided by any good java driver and we advise relying on drivers rather on
 +      that jar, but if you need that jar for backward compatiblity until you do so, you should use the version provided
 +      on previous Cassandra branch, like the 3.0 branch (by design, the functionality provided by that jar are stable
 +      accross versions so using the 3.0 jar for a client connecting to 3.x should work without issues).
 +
+ 3.9
+ ===
+ 
+ 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 more details.
+ 
  3.8
  ===
  

http://git-wip-us.apache.org/repos/asf/cassandra/blob/d1bd71c4/src/java/org/apache/cassandra/db/columniterator/AbstractSSTableIterator.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/cassandra/blob/d1bd71c4/src/java/org/apache/cassandra/db/columniterator/SSTableIterator.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/cassandra/blob/d1bd71c4/src/java/org/apache/cassandra/db/columniterator/SSTableReversedIterator.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/cassandra/blob/d1bd71c4/src/java/org/apache/cassandra/db/compaction/Scrubber.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/db/compaction/Scrubber.java
index 2cfc75d,5443d8e..0007e30
--- a/src/java/org/apache/cassandra/db/compaction/Scrubber.java
+++ b/src/java/org/apache/cassandra/db/compaction/Scrubber.java
@@@ -325,6 -299,38 +299,38 @@@ public class Scrubber implements Closea
          }
      }
  
+     @SuppressWarnings("resource")
+     private boolean tryAppend(DecoratedKey prevKey, DecoratedKey key, SSTableRewriter writer)
+     {
+         // OrderCheckerIterator will check, at iteration time, that the rows are in the proper order. If it detects
+         // that one row is out of order, it will stop returning them. The remaining rows will be sorted and added
+         // to the outOfOrder set that will be later written to a new SSTable.
 -        OrderCheckerIterator sstableIterator = new OrderCheckerIterator(new RowMergingSSTableIterator(sstable, dataFile, key),
++        OrderCheckerIterator sstableIterator = new OrderCheckerIterator(new RowMergingSSTableIterator(SSTableIdentityIterator.create(sstable, dataFile, key)),
+                                                                         cfs.metadata.comparator);
+ 
+         try (UnfilteredRowIterator iterator = withValidation(sstableIterator, dataFile.getPath()))
+         {
+             if (prevKey != null && prevKey.compareTo(key) > 0)
+             {
+                 saveOutOfOrderRow(prevKey, key, iterator);
+                 return false;
+             }
+ 
+             if (writer.tryAppend(iterator) == null)
+                 emptyRows++;
+             else
+                 goodRows++;
+         }
+ 
+         if (sstableIterator.hasRowsOutOfOrder())
+         {
+             outputHandler.warn(String.format("Out of order rows found in partition: %s", key));
+             outOfOrder.add(sstableIterator.getRowsOutOfOrder());
+         }
+ 
+         return true;
+     }
+ 
      private void updateIndexKey()
      {
          currentIndexKey = nextIndexKey;

http://git-wip-us.apache.org/repos/asf/cassandra/blob/d1bd71c4/src/java/org/apache/cassandra/db/partitions/AbstractBTreePartition.java
----------------------------------------------------------------------


[09/12] cassandra git commit: Merge branch cassandra-2.2 into cassandra-3.0

Posted by bl...@apache.org.
http://git-wip-us.apache.org/repos/asf/cassandra/blob/bb56193a/test/unit/org/apache/cassandra/cql3/validation/operations/DeleteTest.java
----------------------------------------------------------------------
diff --cc test/unit/org/apache/cassandra/cql3/validation/operations/DeleteTest.java
index 814e822,6bd5f26..b9ccafb
--- a/test/unit/org/apache/cassandra/cql3/validation/operations/DeleteTest.java
+++ b/test/unit/org/apache/cassandra/cql3/validation/operations/DeleteTest.java
@@@ -26,8 -28,10 +28,11 @@@ import org.apache.commons.lang3.StringU
  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;
 +import static org.apache.commons.lang3.StringUtils.isEmpty;
 +import static org.junit.Assert.assertEquals;
  
  public class DeleteTest extends CQLTester
  {
@@@ -326,761 -330,42 +331,869 @@@
  
          assertEmpty(execute("select * from %s  where a=1 and b=1"));
      }
 +    @Test
 +    public void testDeleteWithNoClusteringColumns() throws Throwable
 +    {
 +        testDeleteWithNoClusteringColumns(false);
 +        testDeleteWithNoClusteringColumns(true);
 +    }
 +
 +    private void testDeleteWithNoClusteringColumns(boolean forceFlush) throws Throwable
 +    {
 +        for (String compactOption : new String[] {"", " WITH COMPACT STORAGE" })
 +        {
 +            createTable("CREATE TABLE %s (partitionKey int PRIMARY KEY," +
 +                                      "value int)" + compactOption);
 +
 +            execute("INSERT INTO %s (partitionKey, value) VALUES (0, 0)");
 +            execute("INSERT INTO %s (partitionKey, value) VALUES (1, 1)");
 +            execute("INSERT INTO %s (partitionKey, value) VALUES (2, 2)");
 +            execute("INSERT INTO %s (partitionKey, value) VALUES (3, 3)");
 +            flush(forceFlush);
 +
 +            execute("DELETE value FROM %s WHERE partitionKey = ?", 0);
 +            flush(forceFlush);
 +
 +            if (isEmpty(compactOption))
 +            {
 +                assertRows(execute("SELECT * FROM %s WHERE partitionKey = ?", 0),
 +                           row(0, null));
 +            }
 +            else
 +            {
 +                assertEmpty(execute("SELECT * FROM %s WHERE partitionKey = ?", 0));
 +            }
 +
 +            execute("DELETE FROM %s WHERE partitionKey IN (?, ?)", 0, 1);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s"),
 +                       row(2, 2),
 +                       row(3, 3));
 +
 +            // test invalid queries
 +
 +            // token function
 +            assertInvalidMessage("The token function cannot be used in WHERE clauses for DELETE statements",
 +                                 "DELETE FROM %s WHERE token(partitionKey) = token(?)", 0);
 +
 +            // multiple time same primary key element in WHERE clause
 +            assertInvalidMessage("partitionkey cannot be restricted by more than one relation if it includes an Equal",
 +                                 "DELETE FROM %s WHERE partitionKey = ? AND partitionKey = ?", 0, 1);
 +
 +            // unknown identifiers
 +            assertInvalidMessage("Unknown identifier unknown",
 +                                 "DELETE unknown FROM %s WHERE partitionKey = ?", 0);
 +
 +            assertInvalidMessage("Undefined name partitionkey1 in where clause ('partitionkey1 = ?')",
 +                                 "DELETE FROM %s WHERE partitionKey1 = ?", 0);
 +
 +            // Invalid operator in the where clause
 +            assertInvalidMessage("Only EQ and IN relation are supported on the partition key (unless you use the token() function)",
 +                                 "DELETE FROM %s WHERE partitionKey > ? ", 0);
 +
 +            assertInvalidMessage("Cannot use CONTAINS on non-collection column partitionkey",
 +                                 "DELETE FROM %s WHERE partitionKey CONTAINS ?", 0);
 +
 +            // Non primary key in the where clause
 +            assertInvalidMessage("Non PRIMARY KEY columns found in where clause: value",
 +                                 "DELETE FROM %s WHERE partitionKey = ? AND value = ?", 0, 1);
 +        }
 +    }
 +
 +    @Test
 +    public void testDeleteWithOneClusteringColumns() throws Throwable
 +    {
 +        testDeleteWithOneClusteringColumns(false);
 +        testDeleteWithOneClusteringColumns(true);
 +    }
 +
 +    private void testDeleteWithOneClusteringColumns(boolean forceFlush) throws Throwable
 +    {
 +        for (String compactOption : new String[] {"", " WITH COMPACT STORAGE" })
 +        {
 +            createTable("CREATE TABLE %s (partitionKey int," +
 +                                      "clustering int," +
 +                                      "value int," +
 +                                      " PRIMARY KEY (partitionKey, clustering))" + compactOption);
 +
 +            execute("INSERT INTO %s (partitionKey, clustering, value) VALUES (0, 0, 0)");
 +            execute("INSERT INTO %s (partitionKey, clustering, value) VALUES (0, 1, 1)");
 +            execute("INSERT INTO %s (partitionKey, clustering, value) VALUES (0, 2, 2)");
 +            execute("INSERT INTO %s (partitionKey, clustering, value) VALUES (0, 3, 3)");
 +            execute("INSERT INTO %s (partitionKey, clustering, value) VALUES (0, 4, 4)");
 +            execute("INSERT INTO %s (partitionKey, clustering, value) VALUES (0, 5, 5)");
 +            execute("INSERT INTO %s (partitionKey, clustering, value) VALUES (1, 0, 6)");
 +            flush(forceFlush);
 +
 +            execute("DELETE value FROM %s WHERE partitionKey = ? AND clustering = ?", 0, 1);
 +            flush(forceFlush);
 +            if (isEmpty(compactOption))
 +            {
 +                assertRows(execute("SELECT * FROM %s WHERE partitionKey = ? AND clustering = ?", 0, 1),
 +                           row(0, 1, null));
 +            }
 +            else
 +            {
 +                assertEmpty(execute("SELECT * FROM %s WHERE partitionKey = ? AND clustering = ?", 0, 1));
 +            }
 +
 +            execute("DELETE FROM %s WHERE partitionKey = ? AND (clustering) = (?)", 0, 1);
 +            flush(forceFlush);
 +            assertEmpty(execute("SELECT value FROM %s WHERE partitionKey = ? AND clustering = ?", 0, 1));
 +
 +            execute("DELETE FROM %s WHERE partitionKey IN (?, ?) AND clustering = ?", 0, 1, 0);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s WHERE partitionKey IN (?, ?)", 0, 1),
 +                       row(0, 2, 2),
 +                       row(0, 3, 3),
 +                       row(0, 4, 4),
 +                       row(0, 5, 5));
 +
 +            execute("DELETE FROM %s WHERE partitionKey = ? AND (clustering) IN ((?), (?))", 0, 4, 5);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s WHERE partitionKey IN (?, ?)", 0, 1),
 +                       row(0, 2, 2),
 +                       row(0, 3, 3));
 +
 +            // test invalid queries
 +
 +            // missing primary key element
 +            assertInvalidMessage("Some partition key parts are missing: partitionkey",
 +                                 "DELETE FROM %s WHERE clustering = ?", 1);
 +
 +            // token function
 +            assertInvalidMessage("The token function cannot be used in WHERE clauses for DELETE statements",
 +                                 "DELETE FROM %s WHERE token(partitionKey) = token(?) AND clustering = ? ", 0, 1);
 +
 +            // multiple time same primary key element in WHERE clause
 +            assertInvalidMessage("clustering cannot be restricted by more than one relation if it includes an Equal",
 +                                 "DELETE FROM %s WHERE partitionKey = ? AND clustering = ? AND clustering = ?", 0, 1, 1);
 +
 +            // unknown identifiers
 +            assertInvalidMessage("Unknown identifier value1",
 +                                 "DELETE value1 FROM %s WHERE partitionKey = ? AND clustering = ?", 0, 1);
 +
 +            assertInvalidMessage("Undefined name partitionkey1 in where clause ('partitionkey1 = ?')",
 +                                 "DELETE FROM %s WHERE partitionKey1 = ? AND clustering = ?", 0, 1);
 +
 +            assertInvalidMessage("Undefined name clustering_3 in where clause ('clustering_3 = ?')",
 +                                 "DELETE FROM %s WHERE partitionKey = ? AND clustering_3 = ?", 0, 1);
 +
 +            // Invalid operator in the where clause
 +            assertInvalidMessage("Only EQ and IN relation are supported on the partition key (unless you use the token() function)",
 +                                 "DELETE FROM %s WHERE partitionKey > ? AND clustering = ?", 0, 1);
 +
 +            assertInvalidMessage("Cannot use CONTAINS on non-collection column partitionkey",
 +                                 "DELETE FROM %s WHERE partitionKey CONTAINS ? AND clustering = ?", 0, 1);
 +
 +            // Non primary key in the where clause
 +            assertInvalidMessage("Non PRIMARY KEY columns found in where clause: value",
 +                                 "DELETE FROM %s WHERE partitionKey = ? AND clustering = ? AND value = ?", 0, 1, 3);
 +        }
 +    }
 +
 +    @Test
 +    public void testDeleteWithTwoClusteringColumns() throws Throwable
 +    {
 +        testDeleteWithTwoClusteringColumns(false);
 +        testDeleteWithTwoClusteringColumns(true);
 +    }
 +
 +    private void testDeleteWithTwoClusteringColumns(boolean forceFlush) throws Throwable
 +    {
 +        for (String compactOption : new String[] { "", " WITH COMPACT STORAGE" })
 +        {
 +            createTable("CREATE TABLE %s (partitionKey int," +
 +                                      "clustering_1 int," +
 +                                      "clustering_2 int," +
 +                                      "value int," +
 +                                      " PRIMARY KEY (partitionKey, clustering_1, clustering_2))" + compactOption);
 +
 +            execute("INSERT INTO %s (partitionKey, clustering_1, clustering_2, value) VALUES (0, 0, 0, 0)");
 +            execute("INSERT INTO %s (partitionKey, clustering_1, clustering_2, value) VALUES (0, 0, 1, 1)");
 +            execute("INSERT INTO %s (partitionKey, clustering_1, clustering_2, value) VALUES (0, 0, 2, 2)");
 +            execute("INSERT INTO %s (partitionKey, clustering_1, clustering_2, value) VALUES (0, 0, 3, 3)");
 +            execute("INSERT INTO %s (partitionKey, clustering_1, clustering_2, value) VALUES (0, 1, 1, 4)");
 +            execute("INSERT INTO %s (partitionKey, clustering_1, clustering_2, value) VALUES (0, 1, 2, 5)");
 +            execute("INSERT INTO %s (partitionKey, clustering_1, clustering_2, value) VALUES (1, 0, 0, 6)");
 +            flush(forceFlush);
 +
 +            execute("DELETE value FROM %s WHERE partitionKey = ? AND clustering_1 = ? AND clustering_2 = ?", 0, 1, 1);
 +            flush(forceFlush);
 +
 +            if (isEmpty(compactOption))
 +            {
 +                assertRows(execute("SELECT * FROM %s WHERE partitionKey = ? AND clustering_1 = ? AND clustering_2 = ?",
 +                                   0, 1, 1),
 +                           row(0, 1, 1, null));
 +            }
 +            else
 +            {
 +                assertEmpty(execute("SELECT * FROM %s WHERE partitionKey = ? AND clustering_1 = ? AND clustering_2 = ?",
 +                                   0, 1, 1));
 +            }
 +
 +            execute("DELETE FROM %s WHERE partitionKey = ? AND (clustering_1, clustering_2) = (?, ?)", 0, 1, 1);
 +            flush(forceFlush);
 +            assertEmpty(execute("SELECT value FROM %s WHERE partitionKey = ? AND clustering_1 = ? AND clustering_2 = ?",
 +                                0, 1, 1));
 +
 +            execute("DELETE FROM %s WHERE partitionKey IN (?, ?) AND clustering_1 = ? AND clustering_2 = ?", 0, 1, 0, 0);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s WHERE partitionKey IN (?, ?)", 0, 1),
 +                       row(0, 0, 1, 1),
 +                       row(0, 0, 2, 2),
 +                       row(0, 0, 3, 3),
 +                       row(0, 1, 2, 5));
 +
 +            Object[][] rows;
 +            if (isEmpty(compactOption))
 +            {
 +                rows = new Object[][]{row(0, 0, 1, 1),
 +                                      row(0, 0, 2, null),
 +                                      row(0, 0, 3, null),
 +                                      row(0, 1, 2, 5)};
 +            }
 +            else
 +            {
 +                rows = new Object[][]{row(0, 0, 1, 1), row(0, 1, 2, 5)};
 +            }
 +
 +            execute("DELETE value FROM %s WHERE partitionKey = ? AND clustering_1 = ? AND clustering_2 IN (?, ?)", 0, 0, 2, 3);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s WHERE partitionKey IN (?, ?)", 0, 1), rows);
 +
 +            if (isEmpty(compactOption))
 +            {
 +                rows = new Object[][]{row(0, 0, 1, 1),
 +                                      row(0, 0, 3, null)};
 +            }
 +            else
 +            {
 +                rows = new Object[][]{row(0, 0, 1, 1)};
 +            }
 +
 +            execute("DELETE FROM %s WHERE partitionKey = ? AND (clustering_1, clustering_2) IN ((?, ?), (?, ?))", 0, 0, 2, 1, 2);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s WHERE partitionKey IN (?, ?)", 0, 1), rows);
 +
 +            execute("DELETE FROM %s WHERE partitionKey = ? AND (clustering_1) IN ((?), (?)) AND clustering_2 = ?", 0, 0, 2, 3);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s WHERE partitionKey IN (?, ?)", 0, 1),
 +                       row(0, 0, 1, 1));
 +
 +            // test invalid queries
 +
 +            // missing primary key element
 +            assertInvalidMessage("Some partition key parts are missing: partitionkey",
 +                                 "DELETE FROM %s WHERE clustering_1 = ? AND clustering_2 = ?", 1, 1);
 +
 +            assertInvalidMessage("PRIMARY KEY column \"clustering_2\" cannot be restricted as preceding column \"clustering_1\" is not restricted",
 +                                 "DELETE FROM %s WHERE partitionKey = ? AND clustering_2 = ?", 0, 1);
 +
 +            // token function
 +            assertInvalidMessage("The token function cannot be used in WHERE clauses for DELETE statements",
 +                                 "DELETE FROM %s WHERE token(partitionKey) = token(?) AND clustering_1 = ? AND clustering_2 = ?", 0, 1, 1);
 +
 +            // multiple time same primary key element in WHERE clause
 +            assertInvalidMessage("clustering_1 cannot be restricted by more than one relation if it includes an Equal",
 +                                 "DELETE FROM %s WHERE partitionKey = ? AND clustering_1 = ? AND clustering_2 = ? AND clustering_1 = ?", 0, 1, 1, 1);
 +
 +            // unknown identifiers
 +            assertInvalidMessage("Unknown identifier value1",
 +                                 "DELETE value1 FROM %s WHERE partitionKey = ? AND clustering_1 = ? AND clustering_2 = ?", 0, 1, 1);
 +
 +            assertInvalidMessage("Undefined name partitionkey1 in where clause ('partitionkey1 = ?')",
 +                                 "DELETE FROM %s WHERE partitionKey1 = ? AND clustering_1 = ? AND clustering_2 = ?", 0, 1, 1);
 +
 +            assertInvalidMessage("Undefined name clustering_3 in where clause ('clustering_3 = ?')",
 +                                 "DELETE FROM %s WHERE partitionKey = ? AND clustering_1 = ? AND clustering_3 = ?", 0, 1, 1);
 +
 +            // Invalid operator in the where clause
 +            assertInvalidMessage("Only EQ and IN relation are supported on the partition key (unless you use the token() function)",
 +                                 "DELETE FROM %s WHERE partitionKey > ? AND clustering_1 = ? AND clustering_2 = ?", 0, 1, 1);
 +
 +            assertInvalidMessage("Cannot use CONTAINS on non-collection column partitionkey",
 +                                 "DELETE FROM %s WHERE partitionKey CONTAINS ? AND clustering_1 = ? AND clustering_2 = ?", 0, 1, 1);
 +
 +            // Non primary key in the where clause
 +            assertInvalidMessage("Non PRIMARY KEY columns found in where clause: value",
 +                                 "DELETE FROM %s WHERE partitionKey = ? AND clustering_1 = ? AND clustering_2 = ? AND value = ?", 0, 1, 1, 3);
 +        }
 +    }
 +
 +    @Test
 +    public void testDeleteWithRangeAndOneClusteringColumn() throws Throwable
 +    {
 +        testDeleteWithRangeAndOneClusteringColumn(false);
 +        testDeleteWithRangeAndOneClusteringColumn(true);
 +    }
 +
 +    private void testDeleteWithRangeAndOneClusteringColumn(boolean forceFlush) throws Throwable
 +    {
 +        for (String compactOption : new String[] { "", " WITH COMPACT STORAGE" })
 +        {
 +            createTable("CREATE TABLE %s (partitionKey int," +
 +                                          "clustering int," +
 +                                          "value int," +
 +                                          " PRIMARY KEY (partitionKey, clustering))" + compactOption);
 +
 +            int value = 0;
 +            for (int partitionKey = 0; partitionKey < 5; partitionKey++)
 +                for (int clustering1 = 0; clustering1 < 5; clustering1++)
 +                        execute("INSERT INTO %s (partitionKey, clustering, value) VALUES (?, ?, ?)",
 +                                partitionKey, clustering1, value++);
 +
 +            flush(forceFlush);
 +
 +            // test delete partition
 +            execute("DELETE FROM %s WHERE partitionKey = ?", 1);
 +            flush(forceFlush);
 +            assertEmpty(execute("SELECT * FROM %s WHERE partitionKey = ?", 1));
 +
 +            // test slices on the first clustering column
 +
 +            execute("DELETE FROM %s WHERE partitionKey = ? AND  clustering >= ?", 0, 4);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s WHERE partitionKey = ?", 0),
 +                       row(0, 0, 0),
 +                       row(0, 1, 1),
 +                       row(0, 2, 2),
 +                       row(0, 3, 3));
 +
 +            execute("DELETE FROM %s WHERE partitionKey = ? AND  clustering > ?", 0, 2);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s WHERE partitionKey = ?", 0),
 +                       row(0, 0, 0),
 +                       row(0, 1, 1),
 +                       row(0, 2, 2));
 +
 +            execute("DELETE FROM %s WHERE partitionKey = ? AND clustering <= ?", 0, 0);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s WHERE partitionKey = ?", 0),
 +                       row(0, 1, 1),
 +                       row(0, 2, 2));
 +
 +            execute("DELETE FROM %s WHERE partitionKey = ? AND clustering < ?", 0, 2);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s WHERE partitionKey = ?", 0),
 +                       row(0, 2, 2));
 +
 +            execute("DELETE FROM %s WHERE partitionKey = ? AND clustering >= ? AND clustering < ?", 2, 0, 3);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s WHERE partitionKey = ?", 2),
 +                       row(2, 3, 13),
 +                       row(2, 4, 14));
 +
 +            execute("DELETE FROM %s WHERE partitionKey = ? AND clustering > ? AND clustering <= ?", 2, 3, 5);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s WHERE partitionKey = ?", 2),
 +                       row(2, 3, 13));
 +
 +            execute("DELETE FROM %s WHERE partitionKey = ? AND clustering < ? AND clustering > ?", 2, 3, 5);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s WHERE partitionKey = ?", 2),
 +                       row(2, 3, 13));
 +
 +            // test multi-column slices
 +            execute("DELETE FROM %s WHERE partitionKey = ? AND (clustering) > (?)", 3, 2);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s WHERE partitionKey = ?", 3),
 +                       row(3, 0, 15),
 +                       row(3, 1, 16),
 +                       row(3, 2, 17));
 +
 +            execute("DELETE FROM %s WHERE partitionKey = ? AND (clustering) < (?)", 3, 1);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s WHERE partitionKey = ?", 3),
 +                       row(3, 1, 16),
 +                       row(3, 2, 17));
 +
 +            execute("DELETE FROM %s WHERE partitionKey = ? AND (clustering) >= (?) AND (clustering) <= (?)", 3, 0, 1);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s WHERE partitionKey = ?", 3),
 +                       row(3, 2, 17));
 +
 +            // Test invalid queries
 +            assertInvalidMessage("Range deletions are not supported for specific columns",
 +                                 "DELETE value FROM %s WHERE partitionKey = ? AND clustering >= ?", 2, 1);
 +            assertInvalidMessage("Range deletions are not supported for specific columns",
 +                                 "DELETE value FROM %s WHERE partitionKey = ?", 2);
 +        }
 +    }
 +
 +    @Test
 +    public void testDeleteWithRangeAndTwoClusteringColumns() throws Throwable
 +    {
 +        testDeleteWithRangeAndTwoClusteringColumns(false);
 +        testDeleteWithRangeAndTwoClusteringColumns(true);
 +    }
 +
 +    private void testDeleteWithRangeAndTwoClusteringColumns(boolean forceFlush) throws Throwable
 +    {
 +        for (String compactOption : new String[] { "", " WITH COMPACT STORAGE" })
 +        {
 +            createTable("CREATE TABLE %s (partitionKey int," +
 +                    "clustering_1 int," +
 +                    "clustering_2 int," +
 +                    "value int," +
 +                    " PRIMARY KEY (partitionKey, clustering_1, clustering_2))" + compactOption);
 +
 +            int value = 0;
 +            for (int partitionKey = 0; partitionKey < 5; partitionKey++)
 +                for (int clustering1 = 0; clustering1 < 5; clustering1++)
 +                    for (int clustering2 = 0; clustering2 < 5; clustering2++) {
 +                        execute("INSERT INTO %s (partitionKey, clustering_1, clustering_2, value) VALUES (?, ?, ?, ?)",
 +                                partitionKey, clustering1, clustering2, value++);}
 +            flush(forceFlush);
 +
 +            // test unspecified second clustering column
 +            execute("DELETE FROM %s WHERE partitionKey = ? AND clustering_1 = ?", 0, 1);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s WHERE partitionKey = ? AND clustering_1 < ?", 0, 2),
 +                       row(0, 0, 0, 0),
 +                       row(0, 0, 1, 1),
 +                       row(0, 0, 2, 2),
 +                       row(0, 0, 3, 3),
 +                       row(0, 0, 4, 4));
 +
 +            // test delete partition
 +            execute("DELETE FROM %s WHERE partitionKey = ?", 1);
 +            flush(forceFlush);
 +            assertEmpty(execute("SELECT * FROM %s WHERE partitionKey = ?", 1));
 +
 +            // test slices on the second clustering column
 +
 +            execute("DELETE FROM %s WHERE partitionKey = ? AND clustering_1 = ? AND clustering_2 < ?", 0, 0, 2);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s WHERE partitionKey = ? AND clustering_1 < ?", 0, 2),
 +                       row(0, 0, 2, 2),
 +                       row(0, 0, 3, 3),
 +                       row(0, 0, 4, 4));
 +
 +            execute("DELETE FROM %s WHERE partitionKey = ? AND clustering_1 = ? AND clustering_2 <= ?", 0, 0, 3);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s WHERE partitionKey = ? AND clustering_1 < ?", 0, 2),
 +                       row(0, 0, 4, 4));
 +
 +            execute("DELETE FROM %s WHERE partitionKey = ? AND  clustering_1 = ? AND clustering_2 > ? ", 0, 2, 2);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s WHERE partitionKey = ? AND  clustering_1 = ?", 0, 2),
 +                       row(0, 2, 0, 10),
 +                       row(0, 2, 1, 11),
 +                       row(0, 2, 2, 12));
 +
 +            execute("DELETE FROM %s WHERE partitionKey = ? AND  clustering_1 = ? AND clustering_2 >= ? ", 0, 2, 1);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s WHERE partitionKey = ? AND  clustering_1 = ?", 0, 2),
 +                       row(0, 2, 0, 10));
 +
 +            execute("DELETE FROM %s WHERE partitionKey = ? AND  clustering_1 = ? AND clustering_2 > ? AND clustering_2 < ? ",
 +                    0, 3, 1, 4);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s WHERE partitionKey = ? AND  clustering_1 = ?", 0, 3),
 +                       row(0, 3, 0, 15),
 +                       row(0, 3, 1, 16),
 +                       row(0, 3, 4, 19));
 +
 +            execute("DELETE FROM %s WHERE partitionKey = ? AND  clustering_1 = ? AND clustering_2 > ? AND clustering_2 < ? ",
 +                    0, 3, 4, 1);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s WHERE partitionKey = ? AND  clustering_1 = ?", 0, 3),
 +                       row(0, 3, 0, 15),
 +                       row(0, 3, 1, 16),
 +                       row(0, 3, 4, 19));
 +
 +            execute("DELETE FROM %s WHERE partitionKey = ? AND  clustering_1 = ? AND clustering_2 >= ? AND clustering_2 <= ? ",
 +                    0, 3, 1, 4);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s WHERE partitionKey = ? AND  clustering_1 = ?", 0, 3),
 +                       row(0, 3, 0, 15));
 +
 +            // test slices on the first clustering column
 +
 +            execute("DELETE FROM %s WHERE partitionKey = ? AND  clustering_1 >= ?", 0, 4);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s WHERE partitionKey = ?", 0),
 +                       row(0, 0, 4, 4),
 +                       row(0, 2, 0, 10),
 +                       row(0, 3, 0, 15));
 +
 +            execute("DELETE FROM %s WHERE partitionKey = ? AND  clustering_1 > ?", 0, 3);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s WHERE partitionKey = ?", 0),
 +                       row(0, 0, 4, 4),
 +                       row(0, 2, 0, 10),
 +                       row(0, 3, 0, 15));
 +
 +            execute("DELETE FROM %s WHERE partitionKey = ? AND clustering_1 < ?", 0, 3);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s WHERE partitionKey = ?", 0),
 +                       row(0, 3, 0, 15));
 +
 +            execute("DELETE FROM %s WHERE partitionKey = ? AND clustering_1 >= ? AND clustering_1 < ?", 2, 0, 3);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s WHERE partitionKey = ?", 2),
 +                       row(2, 3, 0, 65),
 +                       row(2, 3, 1, 66),
 +                       row(2, 3, 2, 67),
 +                       row(2, 3, 3, 68),
 +                       row(2, 3, 4, 69),
 +                       row(2, 4, 0, 70),
 +                       row(2, 4, 1, 71),
 +                       row(2, 4, 2, 72),
 +                       row(2, 4, 3, 73),
 +                       row(2, 4, 4, 74));
 +
 +            execute("DELETE FROM %s WHERE partitionKey = ? AND clustering_1 > ? AND clustering_1 <= ?", 2, 3, 5);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s WHERE partitionKey = ?", 2),
 +                       row(2, 3, 0, 65),
 +                       row(2, 3, 1, 66),
 +                       row(2, 3, 2, 67),
 +                       row(2, 3, 3, 68),
 +                       row(2, 3, 4, 69));
 +
 +            execute("DELETE FROM %s WHERE partitionKey = ? AND clustering_1 < ? AND clustering_1 > ?", 2, 3, 5);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s WHERE partitionKey = ?", 2),
 +                       row(2, 3, 0, 65),
 +                       row(2, 3, 1, 66),
 +                       row(2, 3, 2, 67),
 +                       row(2, 3, 3, 68),
 +                       row(2, 3, 4, 69));
 +
 +            // test multi-column slices
 +            execute("DELETE FROM %s WHERE partitionKey = ? AND (clustering_1, clustering_2) > (?, ?)", 2, 3, 3);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s WHERE partitionKey = ?", 2),
 +                       row(2, 3, 0, 65),
 +                       row(2, 3, 1, 66),
 +                       row(2, 3, 2, 67),
 +                       row(2, 3, 3, 68));
 +
 +            execute("DELETE FROM %s WHERE partitionKey = ? AND (clustering_1, clustering_2) < (?, ?)", 2, 3, 1);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s WHERE partitionKey = ?", 2),
 +                       row(2, 3, 1, 66),
 +                       row(2, 3, 2, 67),
 +                       row(2, 3, 3, 68));
 +
 +            execute("DELETE FROM %s WHERE partitionKey = ? AND (clustering_1, clustering_2) >= (?, ?) AND (clustering_1) <= (?)", 2, 3, 2, 4);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s WHERE partitionKey = ?", 2),
 +                       row(2, 3, 1, 66));
 +
 +            // Test with a mix of single column and multi-column restrictions
 +            execute("DELETE FROM %s WHERE partitionKey = ? AND clustering_1 = ? AND (clustering_2) < (?)", 3, 0, 3);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s WHERE partitionKey = ? AND clustering_1 = ?", 3, 0),
 +                       row(3, 0, 3, 78),
 +                       row(3, 0, 4, 79));
 +
 +            execute("DELETE FROM %s WHERE partitionKey = ? AND clustering_1 IN (?, ?) AND (clustering_2) >= (?)", 3, 0, 1, 3);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s WHERE partitionKey = ? AND clustering_1 IN (?, ?)", 3, 0, 1),
 +                       row(3, 1, 0, 80),
 +                       row(3, 1, 1, 81),
 +                       row(3, 1, 2, 82));
 +
 +            execute("DELETE FROM %s WHERE partitionKey = ? AND (clustering_1) IN ((?), (?)) AND clustering_2 < ?", 3, 0, 1, 1);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s WHERE partitionKey = ? AND clustering_1 IN (?, ?)", 3, 0, 1),
 +                       row(3, 1, 1, 81),
 +                       row(3, 1, 2, 82));
 +
 +            execute("DELETE FROM %s WHERE partitionKey = ? AND (clustering_1) = (?) AND clustering_2 >= ?", 3, 1, 2);
 +            flush(forceFlush);
 +            assertRows(execute("SELECT * FROM %s WHERE partitionKey = ? AND clustering_1 IN (?, ?)", 3, 0, 1),
 +                       row(3, 1, 1, 81));
 +
 +            // Test invalid queries
 +            assertInvalidMessage("Range deletions are not supported for specific columns",
 +                                 "DELETE value FROM %s WHERE partitionKey = ? AND (clustering_1, clustering_2) >= (?, ?)", 2, 3, 1);
 +            assertInvalidMessage("Range deletions are not supported for specific columns",
 +                                 "DELETE value FROM %s WHERE partitionKey = ? AND clustering_1 >= ?", 2, 3);
 +            assertInvalidMessage("Range deletions are not supported for specific columns",
 +                                 "DELETE value FROM %s WHERE partitionKey = ? AND clustering_1 = ?", 2, 3);
 +            assertInvalidMessage("Range deletions are not supported for specific columns",
 +                                 "DELETE value FROM %s WHERE partitionKey = ?", 2);
 +        }
 +    }
 +
 +    @Test
 +    public void testDeleteWithAStaticColumn() throws Throwable
 +    {
 +        testDeleteWithAStaticColumn(false);
 +        testDeleteWithAStaticColumn(true);
 +    }
 +
 +    private void testDeleteWithAStaticColumn(boolean forceFlush) throws Throwable
 +    {
 +        createTable("CREATE TABLE %s (partitionKey int," +
 +                                      "clustering_1 int," +
 +                                      "clustering_2 int," +
 +                                      "value int," +
 +                                      "staticValue text static," +
 +                                      " PRIMARY KEY (partitionKey, clustering_1, clustering_2))");
 +
 +        execute("INSERT INTO %s (partitionKey, clustering_1, clustering_2, value, staticValue) VALUES (0, 0, 0, 0, 'A')");
 +        execute("INSERT INTO %s (partitionKey, clustering_1, clustering_2, value) VALUES (0, 0, 1, 1)");
 +        execute("INSERT INTO %s (partitionKey, clustering_1, clustering_2, value, staticValue) VALUES (1, 0, 0, 6, 'B')");
 +        flush(forceFlush);
 +
 +        execute("DELETE staticValue FROM %s WHERE partitionKey = ?", 0);
 +        flush(forceFlush);
 +        assertRows(execute("SELECT DISTINCT staticValue FROM %s WHERE partitionKey IN (?, ?)", 0, 1),
 +                   row(new Object[1]), row("B"));
 +
 +        execute("DELETE staticValue, value FROM %s WHERE partitionKey = ? AND clustering_1 = ? AND clustering_2 = ?",
 +                1, 0, 0);
 +        flush(forceFlush);
 +        assertRows(execute("SELECT * FROM %s"),
 +                   row(1, 0, 0, null, null),
 +                   row(0, 0, 0, null, 0),
 +                   row(0, 0, 1, null, 1));
 +
 +        assertInvalidMessage("Invalid restrictions on clustering columns since the DELETE statement modifies only static columns",
 +                             "DELETE staticValue FROM %s WHERE partitionKey = ? AND clustering_1 = ? AND clustering_2 = ?",
 +                             0, 0, 1);
 +
 +        assertInvalidMessage("Invalid restrictions on clustering columns since the DELETE statement modifies only static columns",
 +                             "DELETE staticValue FROM %s WHERE partitionKey = ? AND (clustering_1, clustering_2) >= (?, ?)",
 +                             0, 0, 1);
 +    }
 +
 +    @Test
 +    public void testDeleteWithSecondaryIndices() throws Throwable
 +    {
 +        testDeleteWithSecondaryIndices(false);
 +        testDeleteWithSecondaryIndices(true);
 +    }
 +
 +    private void testDeleteWithSecondaryIndices(boolean forceFlush) throws Throwable
 +    {
 +        createTable("CREATE TABLE %s (partitionKey int," +
 +                "clustering_1 int," +
 +                "value int," +
 +                "values set<int>," +
 +                " PRIMARY KEY (partitionKey, clustering_1))");
 +
 +        createIndex("CREATE INDEX ON %s (value)");
 +        createIndex("CREATE INDEX ON %s (clustering_1)");
 +        createIndex("CREATE INDEX ON %s (values)");
 +
 +        execute("INSERT INTO %s (partitionKey, clustering_1, value, values) VALUES (0, 0, 0, {0})");
 +        execute("INSERT INTO %s (partitionKey, clustering_1, value, values) VALUES (0, 1, 1, {0, 1})");
 +        execute("INSERT INTO %s (partitionKey, clustering_1, value, values) VALUES (0, 2, 2, {0, 1, 2})");
 +        execute("INSERT INTO %s (partitionKey, clustering_1, value, values) VALUES (0, 3, 3, {0, 1, 2, 3})");
 +        execute("INSERT INTO %s (partitionKey, clustering_1, value, values) VALUES (1, 0, 4, {0, 1, 2, 3, 4})");
 +
 +        flush(forceFlush);
 +
 +        assertInvalidMessage("Non PRIMARY KEY columns found in where clause: value",
 +                             "DELETE FROM %s WHERE partitionKey = ? AND clustering_1 = ? AND value = ?", 3, 3, 3);
 +        assertInvalidMessage("Non PRIMARY KEY columns found in where clause: values",
 +                             "DELETE FROM %s WHERE partitionKey = ? AND clustering_1 = ? AND values CONTAINS ?", 3, 3, 3);
 +        assertInvalidMessage("Non PRIMARY KEY columns found in where clause: value",
 +                             "DELETE FROM %s WHERE partitionKey = ? AND value = ?", 3, 3);
 +        assertInvalidMessage("Non PRIMARY KEY columns found in where clause: values",
 +                             "DELETE FROM %s WHERE partitionKey = ? AND values CONTAINS ?", 3, 3);
 +        assertInvalidMessage("Some partition key parts are missing: partitionkey",
 +                             "DELETE FROM %s WHERE clustering_1 = ?", 3);
 +        assertInvalidMessage("Some partition key parts are missing: partitionkey",
 +                             "DELETE FROM %s WHERE value = ?", 3);
 +        assertInvalidMessage("Some partition key parts are missing: partitionkey",
 +                             "DELETE FROM %s WHERE values CONTAINS ?", 3);
 +    }
 +
 +    @Test
 +    public void testDeleteWithOnlyPK() throws Throwable
 +    {
 +        // This is a regression test for CASSANDRA-11102
 +
 +        createTable("CREATE TABLE %s (k int, v int, PRIMARY KEY (k, v)) WITH gc_grace_seconds=1");
 +
 +        execute("INSERT INTO %s(k, v) VALUES (?, ?)", 1, 2);
 +
 +        execute("DELETE FROM %s WHERE k = ? AND v = ?", 1, 2);
 +        execute("INSERT INTO %s(k, v) VALUES (?, ?)", 2, 3);
 +
 +        Thread.sleep(500);
 +
 +        execute("DELETE FROM %s WHERE k = ? AND v = ?", 2, 3);
 +        execute("INSERT INTO %s(k, v) VALUES (?, ?)", 1, 2);
 +
 +        Thread.sleep(500);
 +
 +        flush();
 +
 +        assertRows(execute("SELECT * FROM %s"), row(1, 2));
 +
 +        Thread.sleep(1000);
 +        compact();
 +
 +        assertRows(execute("SELECT * FROM %s"), row(1, 2));
 +    }
 +
 +    @Test
 +    public void testDeleteColumnNoClustering() throws Throwable
 +    {
 +        // This is a regression test for CASSANDRA-11068 (and ultimately another test for CASSANDRA-11102)
 +        // Creates a table without clustering, insert a row (with a column) and only remove the column.
 +        // We should still have a row (with a null column value) even post-compaction.
 +
 +        createTable("CREATE TABLE %s (k int PRIMARY KEY, v int) WITH gc_grace_seconds=0");
 +
 +        execute("INSERT INTO %s(k, v) VALUES (?, ?)", 0, 0);
 +        execute("DELETE v FROM %s WHERE k=?", 0);
 +
 +        assertRows(execute("SELECT * FROM %s"), row(0, null));
 +
 +        flush();
 +        assertRows(execute("SELECT * FROM %s"), row(0, null));
 +
 +        compact();
 +        assertRows(execute("SELECT * FROM %s"), row(0, null));
 +    }
  
+     @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"));
++
++                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 IN (textAsBlob(''), textAsBlob('1'));");
++
++                assertEmpty(execute("SELECT * FROM %s"));
++
++                execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?)", bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1"));
++                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"));
++
++                execute("DELETE FROM %s WHERE pk = textAsBlob('foo123') AND c > textAsBlob('')");
++
++                assertRows(execute("SELECT * FROM %s"),
++                           row(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'))");
+             }
++
++            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"));
++
++            execute("DELETE FROM %s WHERE pk = textAsBlob('foo123') AND c > textAsBlob('')");
++
++            assertEmpty(execute("SELECT * FROM %s"));
++
++            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"));
++
++            execute("DELETE FROM %s WHERE pk = textAsBlob('foo123') AND c <= textAsBlob('')");
++            execute("DELETE FROM %s WHERE pk = textAsBlob('foo123') AND c < textAsBlob('')");
++
++            assertRows(execute("SELECT * FROM %s"),
++                       row(bytes("foo123"), bytes("1"), bytes("1")),
++                       row(bytes("foo123"), bytes("2"), bytes("2")));
+         }
+     }
+ 
+     @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"));
++
++            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 IN (textAsBlob(''), textAsBlob('1')) AND c2 = textAsBlob('1');");
++
++            assertEmpty(execute("SELECT * FROM %s"));
++
++            execute("INSERT INTO %s (pk, c1, c2, v) VALUES (?, ?, ?, ?)", bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1"), bytes("0"));
++            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("3"));
++
++            execute("DELETE FROM %s WHERE pk = textAsBlob('foo123') AND c1 > textAsBlob('')");
++
++            assertRows(execute("SELECT * FROM %s"),
++                       row(bytes("foo123"), EMPTY_BYTE_BUFFER, bytes("1"), bytes("0")));
++
++            execute("DELETE FROM %s WHERE pk = textAsBlob('foo123') AND c1 >= textAsBlob('')");
++
++            assertEmpty(execute("SELECT * FROM %s"));
++
++            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("3"));
++
++            execute("DELETE FROM %s WHERE pk = textAsBlob('foo123') AND c1 > textAsBlob('')");
++
++            assertEmpty(execute("SELECT * FROM %s"));
++
++            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("3"));
++
++            execute("DELETE FROM %s WHERE pk = textAsBlob('foo123') AND c1 <= textAsBlob('')");
++            execute("DELETE FROM %s WHERE pk = textAsBlob('foo123') AND c1 < textAsBlob('')");
++
++            assertRows(execute("SELECT * FROM %s"),
++                       row(bytes("foo123"), bytes("1"), bytes("1"), bytes("1")),
++                       row(bytes("foo123"), bytes("1"), bytes("2"), bytes("3")));
+         }
+     }
++
 +    private void flush(boolean forceFlush)
 +    {
 +        if (forceFlush)
 +            flush();
 +    }
 +
 +    @Test
 +    public void testDeleteAndReverseQueries() throws Throwable
 +    {
 +        // This test insert rows in one sstable and a range tombstone covering some of those rows in another, and it
 +        // validates we correctly get only the non-removed rows when doing reverse queries.
 +
 +        createTable("CREATE TABLE %s (k text, i int, PRIMARY KEY (k, i))");
 +
 +        for (int i = 0; i < 10; i++)
 +            execute("INSERT INTO %s(k, i) values (?, ?)", "a", i);
 +
 +        flush();
 +
 +        execute("DELETE FROM %s WHERE k = ? AND i >= ? AND i <= ?", "a", 2, 7);
 +
 +        assertRows(execute("SELECT i FROM %s WHERE k = ? ORDER BY i DESC", "a"),
 +            row(9), row(8), row(1), row(0)
 +        );
 +
 +        flush();
 +
 +        assertRows(execute("SELECT i FROM %s WHERE k = ? ORDER BY i DESC", "a"),
 +            row(9), row(8), row(1), row(0)
 +        );
 +    }
  }


[10/12] cassandra git commit: Merge branch cassandra-2.2 into cassandra-3.0

Posted by bl...@apache.org.
Merge branch cassandra-2.2 into cassandra-3.0


Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/bb56193a
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/bb56193a
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/bb56193a

Branch: refs/heads/trunk
Commit: bb56193a3f2ac865efb18c9d6944f2927f667771
Parents: 6528fbf 9583b6b
Author: Benjamin Lerer <b....@gmail.com>
Authored: Tue Aug 16 15:29:42 2016 +0200
Committer: Benjamin Lerer <b....@gmail.com>
Committed: Tue Aug 16 15:34:35 2016 +0200

----------------------------------------------------------------------
 CHANGES.txt                                     |   1 +
 NEWS.txt                                        |  10 +
 .../cassandra/db/compaction/Scrubber.java       | 169 ++++-
 .../cassandra/db/marshal/ReversedType.java      |  10 -
 .../db/partitions/AbstractBTreePartition.java   |   7 +-
 .../db/partitions/ImmutableBTreePartition.java  |  34 +-
 .../db/rows/UnfilteredRowIterators.java         |  28 +
 .../org/apache/cassandra/cql3/CQLTester.java    |  18 +
 .../validation/entities/SecondaryIndexTest.java | 131 ++++
 .../cql3/validation/operations/DeleteTest.java  | 113 +++
 .../operations/SelectOrderByTest.java           | 719 ++++++++++---------
 .../cql3/validation/operations/SelectTest.java  | 357 ++++++++-
 .../cassandra/db/marshal/ReversedTypeTest.java  |   4 +-
 13 files changed, 1210 insertions(+), 391 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/bb56193a/CHANGES.txt
----------------------------------------------------------------------
diff --cc CHANGES.txt
index a202755,c421398..7605952
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@@ -49,9 -14,12 +49,10 @@@ Merged from 2.2
   * Fixed cqlshlib.test.remove_test_db (CASSANDRA-12214)
   * Synchronize ThriftServer::stop() (CASSANDRA-12105)
   * Use dedicated thread for JMX notifications (CASSANDRA-12146)
 - * NPE when trying to remove purgable tombstones from result (CASSANDRA-12143)
   * Improve streaming synchronization and fault tolerance (CASSANDRA-11414)
   * MemoryUtil.getShort() should return an unsigned short also for architectures not supporting unaligned memory accesses (CASSANDRA-11973)
 - * Don't write shadowed range tombstone (CASSANDRA-12030)
  Merged from 2.1:
+  * 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/bb56193a/NEWS.txt
----------------------------------------------------------------------
diff --cc NEWS.txt
index 6b8dfd3,0a3ab36..0bd3920
--- a/NEWS.txt
+++ b/NEWS.txt
@@@ -13,55 -13,17 +13,65 @@@ restore snapshots created with the prev
  'sstableloader' tool. You can upgrade the file format of your snapshots
  using the provided 'sstableupgrade' tool.
  
 -2.2.8
++3.0.9
+ =====
+ 
+ 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 more details.
++   - 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 more details.
+ 
 -2.2.7
 +3.0.8
 +=====
 +
 +Upgrading
 +---------
 +   - Ec2MultiRegionSnitch will no longer automatically set broadcast_rpc_address
 +     to the public instance IP if this property is defined on cassandra.yaml.
 +
 +3.0.7
 +=====
 +
 +Upgrading
 +---------
 +   - A maximum size for SSTables values has been introduced, to prevent out of memory
 +     exceptions when reading corrupt SSTables. This maximum size can be set via
 +     max_value_size_in_mb in cassandra.yaml. The default is 256MB, which matches the default
 +     value of native_transport_max_frame_size_in_mb. SSTables will be considered corrupt if
 +     they contain values whose size exceeds this limit. See CASSANDRA-9530 for more details.
 +
 +Deprecation
 +-----------
 +   - DateTieredCompactionStrategy has been deprecated - new tables should use
 +     TimeWindowCompactionStrategy. Note that migrating an existing DTCS-table to TWCS might
 +     cause increased compaction load for a while after the migration so make sure you run
 +     tests before migrating. Read CASSANDRA-9666 for background on this.
 +
 +New features
 +------------
 +   - TimeWindowCompactionStrategy has been added. This has proven to be a better approach
 +     to time series compaction and new tables should use this instead of DTCS. See
 +     CASSANDRA-9666 for details.
 +
 +3.0.6
 +=====
 +
 +New features
 +------------
 +   - JSON timestamps are now in UTC and contain the timezone information, see
 +     CASSANDRA-11137 for more details.
 +
 +3.0.5
 +=====
 +
 +Upgrading
 +---------
 +   - Nothing specific to this release, but please see previous versions upgrading section,
 +     especially if you are upgrading from 2.2.
 +
 +3.0.4
  =====
  
  New features

http://git-wip-us.apache.org/repos/asf/cassandra/blob/bb56193a/src/java/org/apache/cassandra/db/compaction/Scrubber.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/db/compaction/Scrubber.java
index 539c4c7,99ee62e..c010891
--- a/src/java/org/apache/cassandra/db/compaction/Scrubber.java
+++ b/src/java/org/apache/cassandra/db/compaction/Scrubber.java
@@@ -23,11 -23,11 +23,12 @@@ import java.util.*
  
  import com.google.common.annotations.VisibleForTesting;
  import com.google.common.base.Throwables;
 -import com.google.common.collect.AbstractIterator;
  
++import org.apache.cassandra.config.CFMetaData;
  import org.apache.cassandra.db.*;
  import org.apache.cassandra.db.lifecycle.LifecycleTransaction;
 -import org.apache.cassandra.db.columniterator.OnDiskAtomIterator;
 +import org.apache.cassandra.db.rows.*;
 +import org.apache.cassandra.db.partitions.*;
  import org.apache.cassandra.io.sstable.*;
  import org.apache.cassandra.io.sstable.format.SSTableReader;
  import org.apache.cassandra.io.sstable.format.SSTableWriter;
@@@ -327,6 -312,32 +302,38 @@@ public class Scrubber implements Closea
          }
      }
  
+     @SuppressWarnings("resource")
+     private boolean tryAppend(DecoratedKey prevKey, DecoratedKey key, 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, checkData),
 -                                                              cfs.metadata.comparator.onDiskAtomComparator());
 -        if (prevKey != null && prevKey.compareTo(key) > 0)
++        // OrderCheckerIterator will check, at iteration time, that the rows are in the proper order. If it detects
++        // that one row is out of order, it will stop returning them. The remaining rows will be sorted and added
++        // to the outOfOrder set that will be later written to a new SSTable.
++        OrderCheckerIterator sstableIterator = new OrderCheckerIterator(new RowMergingSSTableIterator(sstable, dataFile, key),
++                                                                        cfs.metadata.comparator);
++
++        try (UnfilteredRowIterator iterator = withValidation(sstableIterator, dataFile.getPath()))
+         {
 -            saveOutOfOrderRow(prevKey, key, atoms);
 -            return false;
 -        }
++            if (prevKey != null && prevKey.compareTo(key) > 0)
++            {
++                saveOutOfOrderRow(prevKey, key, iterator);
++                return false;
++            }
+ 
 -        AbstractCompactedRow compactedRow = new LazilyCompactedRow(controller, Collections.singletonList(atoms));
 -        if (writer.tryAppend(compactedRow) == null)
 -            emptyRows++;
 -        else
 -            goodRows++;
++            if (writer.tryAppend(iterator) == null)
++                emptyRows++;
++            else
++                goodRows++;
++        }
+ 
 -        if (atoms.hasOutOfOrderCells())
 -            saveOutOfOrderRow(key, atoms);
++        if (sstableIterator.hasRowsOutOfOrder())
++        {
++            outputHandler.warn(String.format("Out of order rows found in partition: %s", key));
++            outOfOrder.add(sstableIterator.getRowsOutOfOrder());
++        }
+ 
+         return true;
+     }
+ 
      private void updateIndexKey()
      {
          currentIndexKey = nextIndexKey;
@@@ -469,44 -523,88 +476,146 @@@
      }
  
      /**
 +     * During 2.x migration, under some circumstances rows might have gotten duplicated.
 +     * Merging iterator merges rows with same clustering.
 +     *
 +     * For more details, refer to CASSANDRA-12144.
 +     */
 +    private static class RowMergingSSTableIterator extends SSTableIdentityIterator
 +    {
 +        RowMergingSSTableIterator(SSTableReader sstable, RandomAccessReader file, DecoratedKey key)
 +        {
 +            super(sstable, file, key);
 +        }
 +
 +        @Override
 +        protected Unfiltered doCompute()
 +        {
 +            if (!iterator.hasNext())
 +                return endOfData();
 +
 +            Unfiltered next = iterator.next();
 +            if (!next.isRow())
 +                return next;
 +
 +            while (iterator.hasNext())
 +            {
 +                Unfiltered peek = iterator.peek();
 +                // If there was a duplicate row, merge it.
 +                if (next.clustering().equals(peek.clustering()) && peek.isRow())
 +                {
 +                    iterator.next(); // Make sure that the peeked item was consumed.
 +                    next = Rows.merge((Row) next, (Row) peek, FBUtilities.nowInSeconds());
 +                }
 +                else
 +                {
 +                    break;
 +                }
 +            }
 +
 +            return next;
 +        }
 +    }
++
++    /**
+      * 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
++    private static final class OrderCheckerIterator extends AbstractIterator<Unfiltered> implements UnfilteredRowIterator
+     {
+         /**
+          * The decorated iterator.
+          */
 -        private final OnDiskAtomIterator iterator;
++        private final UnfilteredRowIterator iterator;
+ 
 -        /**
 -         * The atom comparator.
 -         */
 -        private final Comparator<OnDiskAtom> comparator;
++        private final ClusteringComparator comparator;
+ 
 -        /**
 -         * The Column family containing the cells which are out of order.
 -         */
 -        private ColumnFamily outOfOrderCells;
++        private Unfiltered previous;
+ 
+         /**
 -         * The previous atom returned
++         * The partition containing the rows which are out of order.
+          */
 -        private OnDiskAtom previous;
++        private Partition rowsOutOfOrder;
+ 
 -        public OrderCheckerIterator(OnDiskAtomIterator iterator, Comparator<OnDiskAtom> comparator)
++        public OrderCheckerIterator(UnfilteredRowIterator iterator, ClusteringComparator comparator)
+         {
+             this.iterator = iterator;
+             this.comparator = comparator;
+         }
+ 
 -        public ColumnFamily getColumnFamily()
++        public CFMetaData metadata()
+         {
 -            return iterator.getColumnFamily();
++            return iterator.metadata();
+         }
+ 
 -        public DecoratedKey getKey()
++        public boolean isReverseOrder()
+         {
 -            return iterator.getKey();
++            return iterator.isReverseOrder();
+         }
+ 
 -        public void close() throws IOException
++        public PartitionColumns columns()
+         {
 -            iterator.close();
++            return iterator.columns();
++        }
++
++        public DecoratedKey partitionKey()
++        {
++            return iterator.partitionKey();
++        }
++
++        public Row staticRow()
++        {
++            return iterator.staticRow();
+         }
+ 
+         @Override
 -        protected OnDiskAtom computeNext()
++        public boolean isEmpty()
++        {
++            return iterator.isEmpty();
++        }
++
++        public void close()
++        {
++            iterator.close();
++        }
++
++        public DeletionTime partitionLevelDeletion()
++        {
++            return iterator.partitionLevelDeletion();
++        }
++
++        public EncodingStats stats()
++        {
++            return iterator.stats();
++        }
++
++        public boolean hasRowsOutOfOrder()
++        {
++            return rowsOutOfOrder != null;
++        }
++
++        public Partition getRowsOutOfOrder()
++        {
++            return rowsOutOfOrder;
++        }
++
++        protected Unfiltered computeNext()
+         {
+             if (!iterator.hasNext())
+                 return endOfData();
+ 
 -            OnDiskAtom next = iterator.next();
++            Unfiltered next = iterator.next();
+ 
 -            // If we detect that some cells are out of order we will store and sort the remaining once to insert them
++            // If we detect that some rows are out of order we will store and sort the remaining ones to insert them
+             // in a separate SSTable.
+             if (previous != null && comparator.compare(next, previous) < 0)
+             {
 -                outOfOrderCells = collectOutOfOrderCells(next, iterator);
++                rowsOutOfOrder = ImmutableBTreePartition.create(UnfilteredRowIterators.concat(next, iterator), false);
+                 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/bb56193a/src/java/org/apache/cassandra/db/marshal/ReversedType.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/db/marshal/ReversedType.java
index 02320c7,19bee5f..82a1895
--- a/src/java/org/apache/cassandra/db/marshal/ReversedType.java
+++ b/src/java/org/apache/cassandra/db/marshal/ReversedType.java
@@@ -66,18 -65,8 +66,8 @@@ public class ReversedType<T> extends Ab
          return baseType.isEmptyValueMeaningless();
      }
  
 -    public int compare(ByteBuffer o1, ByteBuffer o2)
 +    public int compareCustom(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/bb56193a/src/java/org/apache/cassandra/db/partitions/AbstractBTreePartition.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/db/partitions/AbstractBTreePartition.java
index 1fa3324,0000000..c63acc2
mode 100644,000000..100644
--- a/src/java/org/apache/cassandra/db/partitions/AbstractBTreePartition.java
+++ b/src/java/org/apache/cassandra/db/partitions/AbstractBTreePartition.java
@@@ -1,408 -1,0 +1,413 @@@
 +/*
 +* 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.db.partitions;
 +
 +import java.util.Iterator;
 +
 +import org.apache.cassandra.config.CFMetaData;
 +import org.apache.cassandra.db.*;
 +import org.apache.cassandra.db.filter.ColumnFilter;
 +import org.apache.cassandra.db.rows.*;
 +import org.apache.cassandra.utils.SearchIterator;
 +import org.apache.cassandra.utils.btree.BTree;
 +import org.apache.cassandra.utils.btree.BTreeSearchIterator;
 +
 +import static org.apache.cassandra.utils.btree.BTree.Dir.desc;
 +
 +public abstract class AbstractBTreePartition implements Partition, Iterable<Row>
 +{
 +    protected static final Holder EMPTY = new Holder(PartitionColumns.NONE, BTree.empty(), DeletionInfo.LIVE, Rows.EMPTY_STATIC_ROW, EncodingStats.NO_STATS);
 +
 +    protected final CFMetaData metadata;
 +    protected final DecoratedKey partitionKey;
 +
 +    protected abstract Holder holder();
 +    protected abstract boolean canHaveShadowedData();
 +
 +    protected AbstractBTreePartition(CFMetaData metadata, DecoratedKey partitionKey)
 +    {
 +        this.metadata = metadata;
 +        this.partitionKey = partitionKey;
 +    }
 +
 +    protected static final class Holder
 +    {
 +        final PartitionColumns columns;
 +        final DeletionInfo deletionInfo;
 +        // the btree of rows
 +        final Object[] tree;
 +        final Row staticRow;
 +        final EncodingStats stats;
 +
 +        Holder(PartitionColumns columns, Object[] tree, DeletionInfo deletionInfo, Row staticRow, EncodingStats stats)
 +        {
 +            this.columns = columns;
 +            this.tree = tree;
 +            this.deletionInfo = deletionInfo;
 +            this.staticRow = staticRow == null ? Rows.EMPTY_STATIC_ROW : staticRow;
 +            this.stats = stats;
 +        }
 +    }
 +
 +    public DeletionInfo deletionInfo()
 +    {
 +        return holder().deletionInfo;
 +    }
 +
 +    public Row staticRow()
 +    {
 +        return holder().staticRow;
 +    }
 +
 +    public boolean isEmpty()
 +    {
 +        Holder holder = holder();
 +        return holder.deletionInfo.isLive() && BTree.isEmpty(holder.tree) && holder.staticRow.isEmpty();
 +    }
 +
 +    public boolean hasRows()
 +    {
 +        Holder holder = holder();
 +        return !BTree.isEmpty(holder.tree);
 +    }
 +
 +    public CFMetaData metadata()
 +    {
 +        return metadata;
 +    }
 +
 +    public DecoratedKey partitionKey()
 +    {
 +        return partitionKey;
 +    }
 +
 +    public DeletionTime partitionLevelDeletion()
 +    {
 +        return holder().deletionInfo.getPartitionDeletion();
 +    }
 +
 +    public PartitionColumns columns()
 +    {
 +        return holder().columns;
 +    }
 +
 +    public EncodingStats stats()
 +    {
 +        return holder().stats;
 +    }
 +
 +    public Row getRow(Clustering clustering)
 +    {
 +        Row row = searchIterator(ColumnFilter.selection(columns()), false).next(clustering);
 +        // Note that for statics, this will never return null, this will return an empty row. However,
 +        // it's more consistent for this method to return null if we don't really have a static row.
 +        return row == null || (clustering == Clustering.STATIC_CLUSTERING && row.isEmpty()) ? null : row;
 +    }
 +
 +    private Row staticRow(Holder current, ColumnFilter columns, boolean setActiveDeletionToRow)
 +    {
 +        DeletionTime partitionDeletion = current.deletionInfo.getPartitionDeletion();
 +        if (columns.fetchedColumns().statics.isEmpty() || (current.staticRow.isEmpty() && partitionDeletion.isLive()))
 +            return Rows.EMPTY_STATIC_ROW;
 +
 +        Row row = current.staticRow.filter(columns, partitionDeletion, setActiveDeletionToRow, metadata);
 +        return row == null ? Rows.EMPTY_STATIC_ROW : row;
 +    }
 +
 +    public SearchIterator<Clustering, Row> searchIterator(final ColumnFilter columns, final boolean reversed)
 +    {
 +        // TODO: we could optimize comparison for "NativeRow" � la #6755
 +        final Holder current = holder();
 +        return new SearchIterator<Clustering, Row>()
 +        {
 +            private final SearchIterator<Clustering, Row> rawIter = new BTreeSearchIterator<>(current.tree, metadata.comparator, desc(reversed));
 +            private final DeletionTime partitionDeletion = current.deletionInfo.getPartitionDeletion();
 +
 +            public boolean hasNext()
 +            {
 +                return rawIter.hasNext();
 +            }
 +
 +            public Row next(Clustering clustering)
 +            {
 +                if (clustering == Clustering.STATIC_CLUSTERING)
 +                    return staticRow(current, columns, true);
 +
 +                Row row = rawIter.next(clustering);
 +                RangeTombstone rt = current.deletionInfo.rangeCovering(clustering);
 +
 +                // A search iterator only return a row, so it doesn't allow to directly account for deletion that should apply to to row
 +                // (the partition deletion or the deletion of a range tombstone that covers it). So if needs be, reuse the row deletion
 +                // to carry the proper deletion on the row.
 +                DeletionTime activeDeletion = partitionDeletion;
 +                if (rt != null && rt.deletionTime().supersedes(activeDeletion))
 +                    activeDeletion = rt.deletionTime();
 +
 +                if (row == null)
 +                    return activeDeletion.isLive() ? null : BTreeRow.emptyDeletedRow(clustering, Row.Deletion.regular(activeDeletion));
 +
 +                return row.filter(columns, activeDeletion, true, metadata);
 +            }
 +        };
 +    }
 +
 +    public UnfilteredRowIterator unfilteredIterator()
 +    {
 +        return unfilteredIterator(ColumnFilter.all(metadata()), Slices.ALL, false);
 +    }
 +
 +    public UnfilteredRowIterator unfilteredIterator(ColumnFilter selection, Slices slices, boolean reversed)
 +    {
 +        return unfilteredIterator(holder(), selection, slices, reversed);
 +    }
 +
 +    public UnfilteredRowIterator unfilteredIterator(Holder current, ColumnFilter selection, Slices slices, boolean reversed)
 +    {
 +        Row staticRow = staticRow(current, selection, false);
 +        if (slices.size() == 0)
 +        {
 +            DeletionTime partitionDeletion = current.deletionInfo.getPartitionDeletion();
 +            return UnfilteredRowIterators.noRowsIterator(metadata, partitionKey, staticRow, partitionDeletion, reversed);
 +        }
 +
 +        return slices.size() == 1
 +               ? sliceIterator(selection, slices.get(0), reversed, current, staticRow)
 +               : new SlicesIterator(selection, slices, reversed, current, staticRow);
 +    }
 +
 +    private UnfilteredRowIterator sliceIterator(ColumnFilter selection, Slice slice, boolean reversed, Holder current, Row staticRow)
 +    {
 +        Slice.Bound start = slice.start() == Slice.Bound.BOTTOM ? null : slice.start();
 +        Slice.Bound end = slice.end() == Slice.Bound.TOP ? null : slice.end();
 +        Iterator<Row> rowIter = BTree.slice(current.tree, metadata.comparator, start, true, end, true, desc(reversed));
 +        Iterator<RangeTombstone> deleteIter = current.deletionInfo.rangeIterator(slice, reversed);
 +
 +        return merge(rowIter, deleteIter, selection, reversed, current, staticRow);
 +    }
 +
 +    private RowAndDeletionMergeIterator merge(Iterator<Row> rowIter, Iterator<RangeTombstone> deleteIter,
 +                                                     ColumnFilter selection, boolean reversed, Holder current, Row staticRow)
 +    {
 +        return new RowAndDeletionMergeIterator(metadata, partitionKey, current.deletionInfo.getPartitionDeletion(),
 +                                               selection, staticRow, reversed, current.stats,
 +                                               rowIter, deleteIter,
 +                                               canHaveShadowedData());
 +    }
 +
 +    private abstract class AbstractIterator extends AbstractUnfilteredRowIterator
 +    {
 +        final Holder current;
 +        final ColumnFilter selection;
 +
 +        private AbstractIterator(ColumnFilter selection, boolean isReversed)
 +        {
 +            this(AbstractBTreePartition.this.holder(), selection, isReversed);
 +        }
 +
 +        private AbstractIterator(Holder current, ColumnFilter selection, boolean isReversed)
 +        {
 +            this(current,
 +                 AbstractBTreePartition.this.staticRow(current, selection, false),
 +                 selection, isReversed);
 +        }
 +
 +        private AbstractIterator(Holder current, Row staticRow, ColumnFilter selection, boolean isReversed)
 +        {
 +            super(AbstractBTreePartition.this.metadata,
 +                  AbstractBTreePartition.this.partitionKey,
 +                  current.deletionInfo.getPartitionDeletion(),
 +                  selection.fetchedColumns(), // non-selected columns will be filtered in subclasses by RowAndDeletionMergeIterator
 +                                              // it would also be more precise to return the intersection of the selection and current.columns,
 +                                              // but its probably not worth spending time on computing that.
 +                  staticRow,
 +                  isReversed,
 +                  current.stats);
 +            this.current = current;
 +            this.selection = selection;
 +        }
 +    }
 +
 +    public class SlicesIterator extends AbstractIterator
 +    {
 +        private final Slices slices;
 +
 +        private int idx;
 +        private Iterator<Unfiltered> currentSlice;
 +
 +        private SlicesIterator(ColumnFilter selection,
 +                               Slices slices,
 +                               boolean isReversed,
 +                               Holder current,
 +                               Row staticRow)
 +        {
 +            super(current, staticRow, selection, isReversed);
 +            this.slices = slices;
 +        }
 +
 +        protected Unfiltered computeNext()
 +        {
 +            while (true)
 +            {
 +                if (currentSlice == null)
 +                {
 +                    if (idx >= slices.size())
 +                        return endOfData();
 +
 +                    int sliceIdx = isReverseOrder ? slices.size() - idx - 1 : idx;
 +                    currentSlice = sliceIterator(selection, slices.get(sliceIdx), isReverseOrder, current, Rows.EMPTY_STATIC_ROW);
 +                    idx++;
 +                }
 +
 +                if (currentSlice.hasNext())
 +                    return currentSlice.next();
 +
 +                currentSlice = null;
 +            }
 +        }
 +    }
 +
 +    public class SliceableIterator extends AbstractIterator implements SliceableUnfilteredRowIterator
 +    {
 +        private Iterator<Unfiltered> iterator;
 +
 +        protected SliceableIterator(ColumnFilter selection, boolean isReversed)
 +        {
 +            super(selection, isReversed);
 +        }
 +
 +        protected Unfiltered computeNext()
 +        {
 +            if (iterator == null)
 +                iterator = unfilteredIterator(selection, Slices.ALL, isReverseOrder);
 +            if (!iterator.hasNext())
 +                return endOfData();
 +            return iterator.next();
 +        }
 +
 +        public Iterator<Unfiltered> slice(Slice slice)
 +        {
 +            return sliceIterator(selection, slice, isReverseOrder, current, staticRow);
 +        }
 +    }
 +
 +    public SliceableUnfilteredRowIterator sliceableUnfilteredIterator(ColumnFilter columns, boolean reversed)
 +    {
 +        return new SliceableIterator(columns, reversed);
 +    }
 +
 +    protected SliceableUnfilteredRowIterator sliceableUnfilteredIterator()
 +    {
 +        return sliceableUnfilteredIterator(ColumnFilter.all(metadata), false);
 +    }
 +
 +    protected static Holder build(UnfilteredRowIterator iterator, int initialRowCapacity)
 +    {
++        return build(iterator, initialRowCapacity, true);
++    }
++
++    protected static Holder build(UnfilteredRowIterator iterator, int initialRowCapacity, boolean ordered)
++    {
 +        CFMetaData metadata = iterator.metadata();
 +        PartitionColumns columns = iterator.columns();
 +        boolean reversed = iterator.isReverseOrder();
 +
 +        BTree.Builder<Row> builder = BTree.builder(metadata.comparator, initialRowCapacity);
-         builder.auto(false);
++        builder.auto(!ordered);
 +        MutableDeletionInfo.Builder deletionBuilder = MutableDeletionInfo.builder(iterator.partitionLevelDeletion(), metadata.comparator, reversed);
 +
 +        while (iterator.hasNext())
 +        {
 +            Unfiltered unfiltered = iterator.next();
 +            if (unfiltered.kind() == Unfiltered.Kind.ROW)
 +                builder.add((Row)unfiltered);
 +            else
 +                deletionBuilder.add((RangeTombstoneMarker)unfiltered);
 +        }
 +
 +        if (reversed)
 +            builder.reverse();
 +
 +        return new Holder(columns, builder.build(), deletionBuilder.build(), iterator.staticRow(), iterator.stats());
 +    }
 +
 +    // Note that when building with a RowIterator, deletion will generally be LIVE, but we allow to pass it nonetheless because PartitionUpdate
 +    // passes a MutableDeletionInfo that it mutates later.
 +    protected static Holder build(RowIterator rows, DeletionInfo deletion, boolean buildEncodingStats, int initialRowCapacity)
 +    {
 +        CFMetaData metadata = rows.metadata();
 +        PartitionColumns columns = rows.columns();
 +        boolean reversed = rows.isReverseOrder();
 +
 +        BTree.Builder<Row> builder = BTree.builder(metadata.comparator, initialRowCapacity);
 +        builder.auto(false);
 +        while (rows.hasNext())
 +        {
 +            Row row = rows.next();
 +            builder.add(row);
 +        }
 +
 +        if (reversed)
 +            builder.reverse();
 +
 +        Row staticRow = rows.staticRow();
 +        Object[] tree = builder.build();
 +        EncodingStats stats = buildEncodingStats ? EncodingStats.Collector.collect(staticRow, BTree.iterator(tree), deletion)
 +                                                 : EncodingStats.NO_STATS;
 +        return new Holder(columns, tree, deletion, staticRow, stats);
 +    }
 +
 +    @Override
 +    public String toString()
 +    {
 +        StringBuilder sb = new StringBuilder();
 +
 +        sb.append(String.format("[%s.%s] key=%s columns=%s",
 +                                metadata.ksName,
 +                                metadata.cfName,
 +                                metadata.getKeyValidator().getString(partitionKey().getKey()),
 +                                columns()));
 +
 +        if (staticRow() != Rows.EMPTY_STATIC_ROW)
 +            sb.append("\n    ").append(staticRow().toString(metadata));
 +
 +        for (Row row : this)
 +            sb.append("\n    ").append(row.toString(metadata));
 +
 +        return sb.toString();
 +    }
 +
 +    public int rowCount()
 +    {
 +        return BTree.size(holder().tree);
 +    }
 +
 +    public Iterator<Row> iterator()
 +    {
 +        return BTree.<Row>iterator(holder().tree);
 +    }
 +
 +    public Row lastRow()
 +    {
 +        Object[] tree = holder().tree;
 +        if (BTree.isEmpty(tree))
 +            return null;
 +
 +        return BTree.findByIndex(tree, BTree.size(tree) - 1);
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/bb56193a/src/java/org/apache/cassandra/db/partitions/ImmutableBTreePartition.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/db/partitions/ImmutableBTreePartition.java
index 9af4bad,0000000..8d96f1e
mode 100644,000000..100644
--- a/src/java/org/apache/cassandra/db/partitions/ImmutableBTreePartition.java
+++ b/src/java/org/apache/cassandra/db/partitions/ImmutableBTreePartition.java
@@@ -1,91 -1,0 +1,123 @@@
 +/*
 +* 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.db.partitions;
 +
 +import org.apache.cassandra.config.CFMetaData;
 +import org.apache.cassandra.db.DecoratedKey;
 +import org.apache.cassandra.db.DeletionInfo;
 +import org.apache.cassandra.db.PartitionColumns;
 +import org.apache.cassandra.db.rows.*;
 +
 +public class ImmutableBTreePartition extends AbstractBTreePartition
 +{
 +
 +    protected final Holder holder;
 +
 +    public ImmutableBTreePartition(CFMetaData metadata,
 +                                      DecoratedKey partitionKey,
 +                                      PartitionColumns columns,
 +                                      Row staticRow,
 +                                      Object[] tree,
 +                                      DeletionInfo deletionInfo,
 +                                      EncodingStats stats)
 +    {
 +        super(metadata, partitionKey);
 +        this.holder = new Holder(columns, tree, deletionInfo, staticRow, stats);
 +    }
 +
 +    protected ImmutableBTreePartition(CFMetaData metadata,
 +                                      DecoratedKey partitionKey,
 +                                      Holder holder)
 +    {
 +        super(metadata, partitionKey);
 +        this.holder = holder;
 +    }
 +
 +    /**
 +     * Creates an {@code ImmutableBTreePartition} holding all the data of the provided iterator.
 +     *
 +     * Warning: Note that this method does not close the provided iterator and it is
 +     * up to the caller to do so.
 +     *
 +     * @param iterator the iterator to gather in memory.
 +     * @return the created partition.
 +     */
 +    public static ImmutableBTreePartition create(UnfilteredRowIterator iterator)
 +    {
 +        return create(iterator, 16);
 +    }
 +
 +    /**
 +     * Creates an {@code ImmutableBTreePartition} holding all the data of the provided iterator.
 +     *
 +     * Warning: Note that this method does not close the provided iterator and it is
 +     * up to the caller to do so.
 +     *
 +     * @param iterator the iterator to gather in memory.
++     * @param ordered {@code true} if the iterator will return the rows in order, {@code false} otherwise.
++     * @return the created partition.
++     */
++    public static ImmutableBTreePartition create(UnfilteredRowIterator iterator, boolean ordered)
++    {
++        return create(iterator, 16, ordered);
++    }
++
++    /**
++     * Creates an {@code ImmutableBTreePartition} holding all the data of the provided iterator.
++     *
++     * Warning: Note that this method does not close the provided iterator and it is
++     * up to the caller to do so.
++     *
++     * @param iterator the iterator to gather in memory.
 +     * @param initialRowCapacity sizing hint (in rows) to use for the created partition. It should ideally
 +     * correspond or be a good estimation of the number or rows in {@code iterator}.
 +     * @return the created partition.
 +     */
 +    public static ImmutableBTreePartition create(UnfilteredRowIterator iterator, int initialRowCapacity)
 +    {
-         return new ImmutableBTreePartition(iterator.metadata(), iterator.partitionKey(), build(iterator, initialRowCapacity));
++        return create(iterator, initialRowCapacity, true);
++    }
++
++    /**
++     * Creates an {@code ImmutableBTreePartition} holding all the data of the provided iterator.
++     *
++     * Warning: Note that this method does not close the provided iterator and it is
++     * up to the caller to do so.
++     *
++     * @param iterator the iterator to gather in memory.
++     * @param initialRowCapacity sizing hint (in rows) to use for the created partition. It should ideally
++     * correspond or be a good estimation of the number or rows in {@code iterator}.
++     * @param ordered {@code true} if the iterator will return the rows in order, {@code false} otherwise.
++     * @return the created partition.
++     */
++    public static ImmutableBTreePartition create(UnfilteredRowIterator iterator, int initialRowCapacity, boolean ordered)
++    {
++        return new ImmutableBTreePartition(iterator.metadata(), iterator.partitionKey(), build(iterator, initialRowCapacity, ordered));
 +    }
 +
 +    protected Holder holder()
 +    {
 +        return holder;
 +    }
 +
 +    protected boolean canHaveShadowedData()
 +    {
 +        return false;
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/bb56193a/src/java/org/apache/cassandra/db/rows/UnfilteredRowIterators.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/db/rows/UnfilteredRowIterators.java
index 3218ff2,0000000..43653a9
mode 100644,000000..100644
--- a/src/java/org/apache/cassandra/db/rows/UnfilteredRowIterators.java
+++ b/src/java/org/apache/cassandra/db/rows/UnfilteredRowIterators.java
@@@ -1,558 -1,0 +1,586 @@@
 +/*
 + * 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.db.rows;
 +
 +import java.util.*;
 +import java.security.MessageDigest;
 +
 +import org.slf4j.Logger;
 +import org.slf4j.LoggerFactory;
 +
 +import org.apache.cassandra.config.CFMetaData;
 +import org.apache.cassandra.db.*;
 +import org.apache.cassandra.db.transform.FilteredRows;
 +import org.apache.cassandra.db.transform.MoreRows;
 +import org.apache.cassandra.db.transform.Transformation;
 +import org.apache.cassandra.io.util.FileUtils;
 +import org.apache.cassandra.io.sstable.CorruptSSTableException;
 +import org.apache.cassandra.serializers.MarshalException;
 +import org.apache.cassandra.net.MessagingService;
 +import org.apache.cassandra.utils.FBUtilities;
 +import org.apache.cassandra.utils.IMergeIterator;
 +import org.apache.cassandra.utils.MergeIterator;
 +import org.apache.cassandra.utils.memory.AbstractAllocator;
 +
 +/**
 + * Static methods to work with atom iterators.
 + */
 +public abstract class UnfilteredRowIterators
 +{
 +    private static final Logger logger = LoggerFactory.getLogger(UnfilteredRowIterators.class);
 +
 +    private UnfilteredRowIterators() {}
 +
 +    /**
 +     * Interface for a listener interested in the result of merging multiple versions of a given row.
 +     * <p>
 +     * Implementors of this interface are given enough information that they can easily reconstruct the difference
 +     * between the merged result and each individual input. This is used when reconciling results on replias for
 +     * instance to figure out what to send as read-repair to each source.
 +     */
 +    public interface MergeListener
 +    {
 +        /**
 +         * Called once for the merged partition.
 +         *
 +         * @param mergedDeletion the partition level deletion for the merged partition. Implementors can test if the
 +         * merged partition actually has a partition level deletion or not by calling {@code mergedDeletion.isLive()}.
 +         * @param versions the partition level deletion for the sources of the merge. Elements of the array will never
 +         * be null, but be "live".
 +         **/
 +        public void onMergedPartitionLevelDeletion(DeletionTime mergedDeletion, DeletionTime[] versions);
 +
 +        /**
 +         * Called once for every row participating in the merge.
 +         * <p>
 +         * Note that this is called for every clustering where at least one of the source merged has a row. In
 +         * particular, this may be called in cases where there is no row in the merged output (if a source has a row
 +         * that is shadowed by another source range tombstone or partition level deletion).
 +         *
 +         * @param merged the result of the merge. This cannot be {@code null} but can be empty, in which case this is a
 +         * placeholder for when at least one source has a row, but that row is shadowed in the merged output.
 +         * @param versions for each source, the row in that source corresponding to {@code merged}. This can be
 +         * {@code null} for some sources if the source has not such row.
 +         */
 +        public void onMergedRows(Row merged, Row[] versions);
 +
 +        /**
 +         * Called once for every range tombstone marker participating in the merge.
 +         * <p>
 +         * Note that this is called for every "clustering position" where at least one of the source merged has a range
 +         * tombstone marker.
 +         *
 +         * @param merged the marker in the merged output. This can be {@code null} if there is no such marker, which
 +         * means that at least one source has a marker in {@code versions} but the merged out has nothing corresponding
 +         * (this basically mean the merged output has a currently open deletion that shadows whatever marker the source
 +         * had).
 +         * @param versions the marker for each source merged. This can be {@code null} for some source if that source
 +         * has not such marker.
 +         */
 +        public void onMergedRangeTombstoneMarkers(RangeTombstoneMarker merged, RangeTombstoneMarker[] versions);
 +
 +        public void close();
 +    }
 +
 +    /**
 +     * Returns a iterator that only returns rows with only live content.
 +     *
 +     * This is mainly used in the CQL layer when we know we don't care about deletion
 +     * infos (and since an UnfilteredRowIterator cannot shadow it's own data, we know everyting
 +     * returned isn't shadowed by a tombstone).
 +     */
 +    public static RowIterator filter(UnfilteredRowIterator iter, int nowInSec)
 +    {
 +        return FilteredRows.filter(iter, nowInSec);
 +    }
 +
 +    /**
 +     * Returns an iterator that is the result of merging other iterators.
 +     */
 +    public static UnfilteredRowIterator merge(List<UnfilteredRowIterator> iterators, int nowInSec)
 +    {
 +        assert !iterators.isEmpty();
 +        if (iterators.size() == 1)
 +            return iterators.get(0);
 +
 +        return UnfilteredRowMergeIterator.create(iterators, nowInSec, null);
 +    }
 +
 +    /**
 +     * Returns an iterator that is the result of merging other iterators, and (optionally) using
 +     * specific MergeListener.
 +     *
 +     * Note that this method assumes that there is at least 2 iterators to merge.
 +     */
 +    public static UnfilteredRowIterator merge(List<UnfilteredRowIterator> iterators, int nowInSec, MergeListener mergeListener)
 +    {
 +        return UnfilteredRowMergeIterator.create(iterators, nowInSec, mergeListener);
 +    }
 +
 +    /**
 +     * Returns an empty unfiltered iterator for a given partition.
 +     */
 +    public static UnfilteredRowIterator noRowsIterator(final CFMetaData cfm, final DecoratedKey partitionKey, final Row staticRow, final DeletionTime partitionDeletion, final boolean isReverseOrder)
 +    {
 +        return EmptyIterators.unfilteredRow(cfm, partitionKey, isReverseOrder, staticRow, partitionDeletion);
 +    }
 +
 +    /**
 +     * Digests the partition represented by the provided iterator.
 +     *
 +     * @param command the command that has yield {@code iterator}. This can be null if {@code version >= MessagingService.VERSION_30}
 +     * as this is only used when producing digest to be sent to legacy nodes.
 +     * @param iterator the iterator to digest.
 +     * @param digest the {@code MessageDigest} to use for the digest.
 +     * @param version the messaging protocol to use when producing the digest.
 +     */
 +    public static void digest(ReadCommand command, UnfilteredRowIterator iterator, MessageDigest digest, int version)
 +    {
 +        if (version < MessagingService.VERSION_30)
 +        {
 +            LegacyLayout.fromUnfilteredRowIterator(command, iterator).digest(iterator.metadata(), digest);
 +            return;
 +        }
 +
 +        digest.update(iterator.partitionKey().getKey().duplicate());
 +        iterator.partitionLevelDeletion().digest(digest);
 +        iterator.columns().regulars.digest(digest);
 +        // When serializing an iterator, we skip the static columns if the iterator has not static row, even if the
 +        // columns() object itself has some (the columns() is a superset of what the iterator actually contains, and
 +        // will correspond to the queried columns pre-serialization). So we must avoid taking the satic column names
 +        // into account if there is no static row or we'd have a digest mismatch between depending on whether the digest
 +        // is computed on an iterator that has been serialized or not (see CASSANDRA-12090)
 +        // TODO: in practice we could completely skip digesting the columns since they are more informative of what the
 +        // iterator may contain, and digesting the actual content is enough. And in fact, that would be more correct
 +        // (since again, the columns could be different without the information represented by the iterator being
 +        // different), but removing them entirely is stricly speaking a breaking change (it would create mismatches on
 +        // upgrade) so we can only do on the next protocol version bump.
 +        if (iterator.staticRow() != Rows.EMPTY_STATIC_ROW)
 +            iterator.columns().statics.digest(digest);
 +        FBUtilities.updateWithBoolean(digest, iterator.isReverseOrder());
 +        iterator.staticRow().digest(digest);
 +
 +        while (iterator.hasNext())
 +        {
 +            Unfiltered unfiltered = iterator.next();
 +            unfiltered.digest(digest);
 +        }
 +    }
 +
 +    /**
 +     * Returns an iterator that concatenate two atom iterators.
 +     * This method assumes that both iterator are from the same partition and that the atom from
 +     * {@code iter2} come after the ones of {@code iter1} (that is, that concatenating the iterator
 +     * make sense).
 +     */
 +    public static UnfilteredRowIterator concat(final UnfilteredRowIterator iter1, final UnfilteredRowIterator iter2)
 +    {
 +        assert iter1.metadata().cfId.equals(iter2.metadata().cfId)
 +            && iter1.partitionKey().equals(iter2.partitionKey())
 +            && iter1.partitionLevelDeletion().equals(iter2.partitionLevelDeletion())
 +            && iter1.isReverseOrder() == iter2.isReverseOrder()
 +            && iter1.columns().equals(iter2.columns())
 +            && iter1.staticRow().equals(iter2.staticRow());
 +
 +        class Extend implements MoreRows<UnfilteredRowIterator>
 +        {
 +            boolean returned = false;
 +            public UnfilteredRowIterator moreContents()
 +            {
 +                if (returned)
 +                    return null;
 +                returned = true;
 +                return iter2;
 +            }
 +        }
 +
 +        return MoreRows.extend(iter1, new Extend());
 +    }
 +
++    /**
++     * Returns an iterator that concatenate the specified atom with the iterator.
++     */
++    public static UnfilteredRowIterator concat(final Unfiltered first, final UnfilteredRowIterator rest)
++    {
++        return new WrappingUnfilteredRowIterator(rest)
++        {
++            private boolean hasReturnedFirst;
++
++            @Override
++            public boolean hasNext()
++            {
++                return hasReturnedFirst ? super.hasNext() : true;
++            }
++
++            @Override
++            public Unfiltered next()
++            {
++                if (!hasReturnedFirst)
++                {
++                    hasReturnedFirst = true;
++                    return first;
++                }
++                return super.next();
++            }
++        };
++    }
++
 +    public static UnfilteredRowIterator cloningIterator(UnfilteredRowIterator iterator, final AbstractAllocator allocator)
 +    {
 +        class Cloner extends Transformation
 +        {
 +            private final Row.Builder builder = allocator.cloningBTreeRowBuilder();
 +
 +            public Row applyToStatic(Row row)
 +            {
 +                return Rows.copy(row, builder).build();
 +            }
 +
 +            @Override
 +            public Row applyToRow(Row row)
 +            {
 +                return Rows.copy(row, builder).build();
 +            }
 +
 +            @Override
 +            public RangeTombstoneMarker applyToMarker(RangeTombstoneMarker marker)
 +            {
 +                return marker.copy(allocator);
 +            }
 +        }
 +        return Transformation.apply(iterator, new Cloner());
 +    }
 +
 +    /**
 +     * Validate that the data of the provided iterator is valid, that is that the values
 +     * it contains are valid for the type they represent, and more generally that the
 +     * infos stored are sensible.
 +     *
 +     * This is mainly used by scrubber to detect problems in sstables.
 +     *
 +     * @param iterator the partition to check.
 +     * @param filename the name of the file the data is comming from.
 +     * @return an iterator that returns the same data than {@code iterator} but that
 +     * checks said data and throws a {@code CorruptedSSTableException} if it detects
 +     * invalid data.
 +     */
 +    public static UnfilteredRowIterator withValidation(UnfilteredRowIterator iterator, final String filename)
 +    {
 +        class Validator extends Transformation
 +        {
 +            @Override
 +            public Row applyToStatic(Row row)
 +            {
 +                validate(row);
 +                return row;
 +            }
 +
 +            @Override
 +            public Row applyToRow(Row row)
 +            {
 +                validate(row);
 +                return row;
 +            }
 +
 +            @Override
 +            public RangeTombstoneMarker applyToMarker(RangeTombstoneMarker marker)
 +            {
 +                validate(marker);
 +                return marker;
 +            }
 +
 +            private void validate(Unfiltered unfiltered)
 +            {
 +                try
 +                {
 +                    unfiltered.validateData(iterator.metadata());
 +                }
 +                catch (MarshalException me)
 +                {
 +                    throw new CorruptSSTableException(me, filename);
 +                }
 +            }
 +        }
 +        return Transformation.apply(iterator, new Validator());
 +    }
 +
 +    /**
 +     * Wraps the provided iterator so it logs the returned atoms for debugging purposes.
 +     * <p>
 +     * Note that this is only meant for debugging as this can log a very large amount of
 +     * logging at INFO.
 +     */
 +    public static UnfilteredRowIterator loggingIterator(UnfilteredRowIterator iterator, final String id, final boolean fullDetails)
 +    {
 +        CFMetaData metadata = iterator.metadata();
 +        logger.info("[{}] Logging iterator on {}.{}, partition key={}, reversed={}, deletion={}",
 +                    id,
 +                    metadata.ksName,
 +                    metadata.cfName,
 +                    metadata.getKeyValidator().getString(iterator.partitionKey().getKey()),
 +                    iterator.isReverseOrder(),
 +                    iterator.partitionLevelDeletion().markedForDeleteAt());
 +
 +        class Logger extends Transformation
 +        {
 +            @Override
 +            public Row applyToStatic(Row row)
 +            {
 +                if (!row.isEmpty())
 +                    logger.info("[{}] {}", id, row.toString(metadata, fullDetails));
 +                return row;
 +            }
 +
 +            @Override
 +            public Row applyToRow(Row row)
 +            {
 +                logger.info("[{}] {}", id, row.toString(metadata, fullDetails));
 +                return row;
 +            }
 +
 +            @Override
 +            public RangeTombstoneMarker applyToMarker(RangeTombstoneMarker marker)
 +            {
 +                logger.info("[{}] {}", id, marker.toString(metadata));
 +                return marker;
 +            }
 +        }
 +        return Transformation.apply(iterator, new Logger());
 +    }
 +
 +    /**
 +     * A wrapper over MergeIterator to implement the UnfilteredRowIterator interface.
 +     */
 +    private static class UnfilteredRowMergeIterator extends AbstractUnfilteredRowIterator
 +    {
 +        private final IMergeIterator<Unfiltered, Unfiltered> mergeIterator;
 +        private final MergeListener listener;
 +
 +        private UnfilteredRowMergeIterator(CFMetaData metadata,
 +                                           List<UnfilteredRowIterator> iterators,
 +                                           PartitionColumns columns,
 +                                           DeletionTime partitionDeletion,
 +                                           int nowInSec,
 +                                           boolean reversed,
 +                                           MergeListener listener)
 +        {
 +            super(metadata,
 +                  iterators.get(0).partitionKey(),
 +                  partitionDeletion,
 +                  columns,
 +                  mergeStaticRows(iterators, columns.statics, nowInSec, listener, partitionDeletion),
 +                  reversed,
 +                  mergeStats(iterators));
 +
 +            this.mergeIterator = MergeIterator.get(iterators,
 +                                                   reversed ? metadata.comparator.reversed() : metadata.comparator,
 +                                                   new MergeReducer(iterators.size(), reversed, nowInSec, listener));
 +            this.listener = listener;
 +        }
 +
 +        private static UnfilteredRowMergeIterator create(List<UnfilteredRowIterator> iterators, int nowInSec, MergeListener listener)
 +        {
 +            try
 +            {
 +                checkForInvalidInput(iterators);
 +                return new UnfilteredRowMergeIterator(iterators.get(0).metadata(),
 +                                                      iterators,
 +                                                      collectColumns(iterators),
 +                                                      collectPartitionLevelDeletion(iterators, listener),
 +                                                      nowInSec,
 +                                                      iterators.get(0).isReverseOrder(),
 +                                                      listener);
 +            }
 +            catch (RuntimeException | Error e)
 +            {
 +                try
 +                {
 +                    FBUtilities.closeAll(iterators);
 +                }
 +                catch (Exception suppressed)
 +                {
 +                    e.addSuppressed(suppressed);
 +                }
 +                throw e;
 +            }
 +        }
 +
 +        @SuppressWarnings("resource") // We're not really creating any resource here
 +        private static void checkForInvalidInput(List<UnfilteredRowIterator> iterators)
 +        {
 +            if (iterators.isEmpty())
 +                return;
 +
 +            UnfilteredRowIterator first = iterators.get(0);
 +            for (int i = 1; i < iterators.size(); i++)
 +            {
 +                UnfilteredRowIterator iter = iterators.get(i);
 +                assert first.metadata().cfId.equals(iter.metadata().cfId);
 +                assert first.partitionKey().equals(iter.partitionKey());
 +                assert first.isReverseOrder() == iter.isReverseOrder();
 +            }
 +        }
 +
 +        @SuppressWarnings("resource") // We're not really creating any resource here
 +        private static DeletionTime collectPartitionLevelDeletion(List<UnfilteredRowIterator> iterators, MergeListener listener)
 +        {
 +            DeletionTime[] versions = listener == null ? null : new DeletionTime[iterators.size()];
 +
 +            DeletionTime delTime = DeletionTime.LIVE;
 +            for (int i = 0; i < iterators.size(); i++)
 +            {
 +                UnfilteredRowIterator iter = iterators.get(i);
 +                DeletionTime iterDeletion = iter.partitionLevelDeletion();
 +                if (listener != null)
 +                    versions[i] = iterDeletion;
 +                if (!delTime.supersedes(iterDeletion))
 +                    delTime = iterDeletion;
 +            }
 +            if (listener != null)
 +                listener.onMergedPartitionLevelDeletion(delTime, versions);
 +            return delTime;
 +        }
 +
 +        private static Row mergeStaticRows(List<UnfilteredRowIterator> iterators,
 +                                           Columns columns,
 +                                           int nowInSec,
 +                                           MergeListener listener,
 +                                           DeletionTime partitionDeletion)
 +        {
 +            if (columns.isEmpty())
 +                return Rows.EMPTY_STATIC_ROW;
 +
 +            if (iterators.stream().allMatch(iter -> iter.staticRow().isEmpty()))
 +                return Rows.EMPTY_STATIC_ROW;
 +
 +            Row.Merger merger = new Row.Merger(iterators.size(), nowInSec, columns.hasComplex());
 +            for (int i = 0; i < iterators.size(); i++)
 +                merger.add(i, iterators.get(i).staticRow());
 +
 +            Row merged = merger.merge(partitionDeletion);
 +            if (merged == null)
 +                merged = Rows.EMPTY_STATIC_ROW;
 +            if (listener != null)
 +                listener.onMergedRows(merged, merger.mergedRows());
 +            return merged;
 +        }
 +
 +        private static PartitionColumns collectColumns(List<UnfilteredRowIterator> iterators)
 +        {
 +            PartitionColumns first = iterators.get(0).columns();
 +            Columns statics = first.statics;
 +            Columns regulars = first.regulars;
 +            for (int i = 1; i < iterators.size(); i++)
 +            {
 +                PartitionColumns cols = iterators.get(i).columns();
 +                statics = statics.mergeTo(cols.statics);
 +                regulars = regulars.mergeTo(cols.regulars);
 +            }
 +            return statics == first.statics && regulars == first.regulars
 +                 ? first
 +                 : new PartitionColumns(statics, regulars);
 +        }
 +
 +        private static EncodingStats mergeStats(List<UnfilteredRowIterator> iterators)
 +        {
 +            EncodingStats stats = EncodingStats.NO_STATS;
 +            for (UnfilteredRowIterator iter : iterators)
 +                stats = stats.mergeWith(iter.stats());
 +            return stats;
 +        }
 +
 +        protected Unfiltered computeNext()
 +        {
 +            while (mergeIterator.hasNext())
 +            {
 +                Unfiltered merged = mergeIterator.next();
 +                if (merged != null)
 +                    return merged;
 +            }
 +            return endOfData();
 +        }
 +
 +        public void close()
 +        {
 +            // This will close the input iterators
 +            FileUtils.closeQuietly(mergeIterator);
 +
 +            if (listener != null)
 +                listener.close();
 +        }
 +
 +        private class MergeReducer extends MergeIterator.Reducer<Unfiltered, Unfiltered>
 +        {
 +            private final MergeListener listener;
 +
 +            private Unfiltered.Kind nextKind;
 +
 +            private final Row.Merger rowMerger;
 +            private final RangeTombstoneMarker.Merger markerMerger;
 +
 +            private MergeReducer(int size, boolean reversed, int nowInSec, MergeListener listener)
 +            {
 +                this.rowMerger = new Row.Merger(size, nowInSec, columns().regulars.hasComplex());
 +                this.markerMerger = new RangeTombstoneMarker.Merger(size, partitionLevelDeletion(), reversed);
 +                this.listener = listener;
 +            }
 +
 +            @Override
 +            public boolean trivialReduceIsTrivial()
 +            {
 +                // If we have a listener, we must signal it even when we have a single version
 +                return listener == null;
 +            }
 +
 +            public void reduce(int idx, Unfiltered current)
 +            {
 +                nextKind = current.kind();
 +                if (nextKind == Unfiltered.Kind.ROW)
 +                    rowMerger.add(idx, (Row)current);
 +                else
 +                    markerMerger.add(idx, (RangeTombstoneMarker)current);
 +            }
 +
 +            protected Unfiltered getReduced()
 +            {
 +                if (nextKind == Unfiltered.Kind.ROW)
 +                {
 +                    Row merged = rowMerger.merge(markerMerger.activeDeletion());
 +                    if (listener != null)
 +                        listener.onMergedRows(merged == null ? BTreeRow.emptyRow(rowMerger.mergedClustering()) : merged, rowMerger.mergedRows());
 +                    return merged;
 +                }
 +                else
 +                {
 +                    RangeTombstoneMarker merged = markerMerger.merge();
 +                    if (listener != null)
 +                        listener.onMergedRangeTombstoneMarkers(merged, markerMerger.mergedMarkers());
 +                    return merged;
 +                }
 +            }
 +
 +            protected void onKeyChange()
 +            {
 +                if (nextKind == Unfiltered.Kind.ROW)
 +                    rowMerger.clear();
 +                else
 +                    markerMerger.clear();
 +            }
 +        }
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/bb56193a/test/unit/org/apache/cassandra/cql3/CQLTester.java
----------------------------------------------------------------------
diff --cc test/unit/org/apache/cassandra/cql3/CQLTester.java
index e3dc220,98b8e23..c9c4631
--- a/test/unit/org/apache/cassandra/cql3/CQLTester.java
+++ b/test/unit/org/apache/cassandra/cql3/CQLTester.java
@@@ -366,21 -278,20 +366,39 @@@ public abstract class CQLTeste
          return list.isEmpty() ? Collections.<String>emptyList() : new ArrayList<>(list);
      }
  
 +    public ColumnFamilyStore getCurrentColumnFamilyStore()
 +    {
 +        String currentTable = currentTable();
 +        return currentTable == null
 +             ? null
 +             : Keyspace.open(KEYSPACE).getColumnFamilyStore(currentTable);
 +    }
 +
      public void flush()
      {
 -        try
 -        {
 -            String currentTable = currentTable();
 -            if (currentTable != null)
 -                Keyspace.open(KEYSPACE).getColumnFamilyStore(currentTable).forceFlush().get();
 -        }
 -        catch (InterruptedException | ExecutionException e)
 -        {
 -            throw new RuntimeException(e);
 -        }
 +        ColumnFamilyStore store = getCurrentColumnFamilyStore();
 +        if (store != null)
 +            store.forceBlockingFlush();
 +    }
 +
++    @FunctionalInterface
++    public interface CheckedFunction {
++        void apply() throws Throwable;
++    }
++
++    /**
++     * Runs the given function before and after a flush of sstables.  This is useful for checking that behavior is
++     * the same whether data is in memtables or sstables.
++     * @param runnable
++     * @throws Throwable
++     */
++    public void beforeAndAfterFlush(CheckedFunction runnable) throws Throwable
++    {
++        runnable.apply();
++        flush();
++        runnable.apply();
+     }
+ 
      public void compact()
      {
          try

http://git-wip-us.apache.org/repos/asf/cassandra/blob/bb56193a/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java
----------------------------------------------------------------------
diff --cc test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java
index f9802d7,11d2462..0cf13bd
--- a/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java
+++ b/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java
@@@ -17,47 -17,38 +17,49 @@@
   */
  package org.apache.cassandra.cql3.validation.entities;
  
 -import static org.junit.Assert.assertTrue;
 -import static org.junit.Assert.fail;
 -
  import java.nio.ByteBuffer;
 -import java.util.HashMap;
 -import java.util.List;
 -import java.util.Locale;
 -import java.util.Map;
 -import java.util.Set;
 -import java.util.UUID;
 +import java.util.*;
 +import java.util.concurrent.Callable;
  import java.util.concurrent.CountDownLatch;
  
 +import org.apache.commons.lang3.StringUtils;
 +import org.junit.Test;
 +
 +import org.apache.cassandra.config.CFMetaData;
 +import org.apache.cassandra.config.ColumnDefinition;
 +import org.apache.cassandra.config.DatabaseDescriptor;
  import org.apache.cassandra.cql3.CQLTester;
 -import org.apache.cassandra.db.ColumnFamily;
 +import org.apache.cassandra.cql3.ColumnIdentifier;
 +import org.apache.cassandra.cql3.QueryProcessor;
 +import org.apache.cassandra.cql3.restrictions.StatementRestrictions;
 +import org.apache.cassandra.cql3.statements.IndexTarget;
  import org.apache.cassandra.db.ColumnFamilyStore;
 -import org.apache.cassandra.db.DecoratedKey;
 -import org.apache.cassandra.db.IndexExpression;
 -import org.apache.cassandra.db.composites.CellName;
 -import org.apache.cassandra.db.index.IndexNotAvailableException;
 -import org.apache.cassandra.db.index.PerRowSecondaryIndex;
 -import org.apache.cassandra.db.index.SecondaryIndexSearcher;
 -import org.apache.cassandra.db.index.composites.CompositesSearcher;
 +import org.apache.cassandra.db.DeletionTime;
 +import org.apache.cassandra.db.marshal.AbstractType;
 +import org.apache.cassandra.db.rows.Cell;
 +import org.apache.cassandra.db.rows.Row;
  import org.apache.cassandra.exceptions.ConfigurationException;
 -import org.apache.cassandra.exceptions.InvalidRequestException;
  import org.apache.cassandra.exceptions.SyntaxException;
 -import org.apache.cassandra.utils.FBUtilities;
 -import org.apache.cassandra.utils.concurrent.OpOrder.Group;
 -import org.apache.commons.lang3.StringUtils;
 -import org.junit.Test;
 -
 +import org.apache.cassandra.index.IndexNotAvailableException;
 +import org.apache.cassandra.index.SecondaryIndexManager;
 +import org.apache.cassandra.index.StubIndex;
 +import org.apache.cassandra.index.internal.CustomCassandraIndex;
 +import org.apache.cassandra.schema.IndexMetadata;
 +import org.apache.cassandra.service.ClientState;
 +import org.apache.cassandra.transport.messages.ResultMessage;
 +import org.apache.cassandra.utils.ByteBufferUtil;
 +import org.apache.cassandra.utils.MD5Digest;
 +import org.apache.cassandra.utils.Pair;
 +
 +import static org.apache.cassandra.Util.throwAssert;
+ import static org.apache.cassandra.utils.ByteBufferUtil.EMPTY_BYTE_BUFFER;
+ import static org.apache.cassandra.utils.ByteBufferUtil.bytes;
 +import static org.junit.Assert.assertEquals;
 +import static org.junit.Assert.assertFalse;
 +import static org.junit.Assert.assertNotNull;
 +import static org.junit.Assert.assertNull;
 +import static org.junit.Assert.assertTrue;
 +import static org.junit.Assert.fail;
  
  public class SecondaryIndexTest extends CQLTester
  {
@@@ -949,77 -684,190 +951,206 @@@
      }
  
      @Test
 +    public void droppingIndexInvalidatesPreparedStatements() throws Throwable
 +    {
 +        createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY ((a), b))");
 +        createIndex("CREATE INDEX c_idx ON %s(c)");
 +        MD5Digest cqlId = prepareStatement("SELECT * FROM %s.%s WHERE c=?", false).statementId;
 +        Integer thriftId = prepareStatement("SELECT * FROM %s.%s WHERE c=?", true).toThriftPreparedResult().getItemId();
 +
 +        assertNotNull(QueryProcessor.instance.getPrepared(cqlId));
 +        assertNotNull(QueryProcessor.instance.getPreparedForThrift(thriftId));
 +
 +        dropIndex("DROP INDEX %s.c_idx");
 +
 +        assertNull(QueryProcessor.instance.getPrepared(cqlId));
 +        assertNull(QueryProcessor.instance.getPreparedForThrift(thriftId));
 +    }
 +
 +    // See CASSANDRA-11021
 +    @Test
 +    public void testIndexesOnNonStaticColumnsWhereSchemaIncludesStaticColumns() throws Throwable
 +    {
 +        createTable("CREATE TABLE %s (a int, b int, c int static, d int, PRIMARY KEY (a, b))");
 +        createIndex("CREATE INDEX b_idx on %s(b)");
 +        createIndex("CREATE INDEX d_idx on %s(d)");
 +
 +        execute("INSERT INTO %s (a, b, c ,d) VALUES (0, 0, 0, 0)");
 +        execute("INSERT INTO %s (a, b, c, d) VALUES (1, 1, 1, 1)");
 +        assertRows(execute("SELECT * FROM %s WHERE b = 0"), row(0, 0, 0, 0));
 +        assertRows(execute("SELECT * FROM %s WHERE d = 1"), row(1, 1, 1, 1));
 +
 +        execute("UPDATE %s SET c = 2 WHERE a = 0");
 +        execute("UPDATE %s SET c = 3, d = 4 WHERE a = 1 AND b = 1");
 +        assertRows(execute("SELECT * FROM %s WHERE b = 0"), row(0, 0, 2, 0));
 +        assertRows(execute("SELECT * FROM %s WHERE d = 4"), row(1, 1, 3, 4));
 +
 +        execute("DELETE FROM %s WHERE a = 0");
 +        execute("DELETE FROM %s WHERE a = 1 AND b = 1");
 +        assertEmpty(execute("SELECT * FROM %s WHERE b = 0"));
 +        assertEmpty(execute("SELECT * FROM %s WHERE d = 3"));
 +    }
 +
++    @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();
 -
++        beforeAndAfterFlush(() -> {
+             // 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();
++        beforeAndAfterFlush(() -> {
+ 
+             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();
++        beforeAndAfterFlush(() -> {
+ 
+             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",
++        assertInvalidMessage("Secondary indexes are not supported on COMPACT STORAGE tables that have clustering columns",
+                             "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));
+     }
+ 
 -    /**
 -     * Custom index used to test the behavior of the system when the index is not ready.
 -     * As Custom indices cannot by <code>PerColumnSecondaryIndex</code> we use a <code>PerRowSecondaryIndex</code>
 -     * to avoid the check but return a <code>CompositesSearcher</code>.
 -     */
 -    public static class IndexBlockingOnInitialization extends PerRowSecondaryIndex
 +    private ResultMessage.Prepared prepareStatement(String cql, boolean forThrift)
      {
 -        private volatile CountDownLatch latch = new CountDownLatch(1);
 -
 -        @Override
 -        public void index(ByteBuffer rowKey, ColumnFamily cf)
 -        {
 -            try
 -            {
 -                latch.await();
 -            }
 -            catch (InterruptedException e)
 -            {
 -                Thread.interrupted();
 -            }
 -        }
 -
 -        @Override
 -        public void delete(DecoratedKey key, Group opGroup)
 -        {
 -        }
 +        return QueryProcessor.prepare(String.format(cql, KEYSPACE, currentTable()),
 +                                      ClientState.forInternalCalls(),
 +                                      forThrift);
 +    }
  
 -        @Override
 -        public void init()
 -        {
 -        }
 +    private void validateCell(Cell cell, ColumnDefinition def, ByteBuffer val, long timestamp)
 +    {
 +        assertNotNull(cell);
 +        assertEquals(0, def.type.compare(cell.value(), val));
 +        assertEquals(timestamp, cell.timestamp());
 +    }
  
 -        @Override
 -        public void reload()
 -        {
 -        }
 +    private static void assertColumnValue(int expected, String name, Row row, CFMetaData cfm)
 +    {
 +        ColumnDefinition col = cfm.getColumnDefinition(new ColumnIdentifier(name, true));
 +        AbstractType<?> type = col.type;
 +        assertEquals(expected, type.compose(row.getCell(col).value()));
 +    }
  
 -        @Override
 -        public void validateOptions() throws ConfigurationException
 -        {
 -        }
 +    /**
 +     * <code>CassandraIndex</code> that blocks during the initialization.
 +     */
 +    public static class IndexBlockingOnInitialization extends CustomCassandraIndex
 +    {
 +        private final CountDownLatch latch = new CountDownLatch(1);
  
 -        @Override
 -        public String getIndexName()
 +        public IndexBlockingOnInitialization(ColumnFamilyStore baseCfs, IndexMetadata indexDef)
          {
 -            return "testIndex";
 +            super(baseCfs, indexDef);
          }
  
          @Override


[02/12] cassandra git commit: Merge branch cassandra-2.1 into cassandra-2.2

Posted by bl...@apache.org.
http://git-wip-us.apache.org/repos/asf/cassandra/blob/9583b6b3/test/unit/org/apache/cassandra/cql3/validation/operations/SelectTest.java
----------------------------------------------------------------------
diff --cc test/unit/org/apache/cassandra/cql3/validation/operations/SelectTest.java
index c8df4c3,cef4635..ac1ba4c
--- a/test/unit/org/apache/cassandra/cql3/validation/operations/SelectTest.java
+++ b/test/unit/org/apache/cassandra/cql3/validation/operations/SelectTest.java
@@@ -23,15 -23,18 +23,18 @@@ import java.util.UUID
  import org.junit.Test;
  
  import junit.framework.Assert;
 -import org.apache.cassandra.config.DatabaseDescriptor;
  import org.apache.cassandra.cql3.UntypedResultSet;
 -import org.apache.cassandra.cql3.CQLTester;
 -import org.apache.cassandra.dht.Murmur3Partitioner;
 +import org.apache.cassandra.cql3.restrictions.StatementRestrictions;
  import org.apache.cassandra.exceptions.InvalidRequestException;
- import org.apache.cassandra.utils.ByteBufferUtil;
+ 
 +import org.apache.cassandra.cql3.CQLTester;
 +
  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
   */
@@@ -1367,891 -1240,6 +1370,891 @@@ public class SelectTest extends CQLTest
      }
  
      @Test
 +    public void testAlias() throws Throwable
 +    {
 +        createTable("CREATE TABLE %s (id int PRIMARY KEY, name text)");
 +
 +        for (int i = 0; i < 5; i++)
 +            execute("INSERT INTO %s (id, name) VALUES (?, ?) USING TTL 10 AND TIMESTAMP 0", i, Integer.toString(i));
 +
 +        assertInvalidMessage("Aliases aren't allowed in the where clause",
 +                             "SELECT id AS user_id, name AS user_name FROM %s WHERE user_id = 0");
 +
 +        // test that select throws a meaningful exception for aliases in order by clause
 +        assertInvalidMessage("Aliases are not allowed in order by clause",
 +                             "SELECT id AS user_id, name AS user_name FROM %s WHERE id IN (0) ORDER BY user_name");
 +
 +    }
 +
 +    @Test
 +    public void testFilteringWithoutIndices() throws Throwable
 +    {
 +        createTable("CREATE TABLE %s (a int, b int, c int, d int, e map<int, int>, PRIMARY KEY (a, b))");
 +
 +        // Checks filtering
 +        assertInvalidMessage("Predicates on non-primary-key columns (c, d) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c = 1 AND d = 2 ALLOW FILTERING");
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE a = 1 AND b = 1 AND c = 2 ALLOW FILTERING");
 +        assertInvalidMessage("IN predicates on non-primary-key columns (c) is not yet supported",
 +                             "SELECT * FROM %s WHERE a IN (1, 2) AND c IN (2, 3) ALLOW FILTERING");
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c > 2 ALLOW FILTERING");
 +        assertInvalidMessage("Predicates on non-primary-key columns (e) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE e CONTAINS 1 ALLOW FILTERING");
 +        assertInvalidMessage("Predicates on non-primary-key columns (e) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE e CONTAINS KEY 1 ALLOW FILTERING");
 +
 +        // Checks filtering with null
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c = null ALLOW FILTERING");
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c > null ALLOW FILTERING");
 +        assertInvalidMessage("Predicates on non-primary-key columns (e) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE e CONTAINS null ALLOW FILTERING");
 +        assertInvalidMessage("Predicates on non-primary-key columns (e) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE e CONTAINS KEY null ALLOW FILTERING");
 +
 +        // Checks filtering with unset
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c = ? ALLOW FILTERING", unset());
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c > ? ALLOW FILTERING", unset());
 +        assertInvalidMessage("Predicates on non-primary-key columns (e) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE e CONTAINS ? ALLOW FILTERING", unset());
 +        assertInvalidMessage("Predicates on non-primary-key columns (e) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE e CONTAINS KEY ? ALLOW FILTERING", unset());
 +
 +        createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY(a)) WITH COMPACT STORAGE");
 +
 +        execute("INSERT INTO %s (a, b, c) VALUES (1, 2, 4)");
 +        execute("INSERT INTO %s (a, b, c) VALUES (2, 2, 8)");
 +        execute("INSERT INTO %s (a, b, c) VALUES (3, 6, 4)");
 +
 +        assertRows(execute("SELECT * FROM %s WHERE c = 4 ALLOW FILTERING"),
 +                   row(1, 2, 4),
 +                   row(3, 6, 4));
 +
 +        // Checks filtering with null
 +        assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE, "SELECT * FROM %s WHERE c = null");
 +        assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE, "SELECT * FROM %s WHERE c > null");
 +        assertInvalidMessage("Unsupported null value for column c", "SELECT * FROM %s WHERE c = null ALLOW FILTERING");
 +        assertInvalidMessage("Unsupported null value for column c", "SELECT * FROM %s WHERE c > null ALLOW FILTERING");
 +
 +        // Checks filtering with unset
 +        assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
 +                             "SELECT * FROM %s WHERE c = ?", unset());
 +        assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
 +                             "SELECT * FROM %s WHERE c > ?", unset());
 +        assertInvalidMessage("Unsupported unset value for column c",
 +                             "SELECT * FROM %s WHERE c = ? ALLOW FILTERING", unset());
 +        assertInvalidMessage("Unsupported unset value for column c",
 +                             "SELECT * FROM %s WHERE c > ? ALLOW FILTERING", unset());
 +    }
 +
 +    @Test
 +    public void testFilteringOnStaticColumnWithoutIndices() throws Throwable
 +    {
 +        createTable("CREATE TABLE %s (a int, b int, s int static, c int, PRIMARY KEY (a, b))");
 +
 +        // Checks filtering
 +        assertInvalidMessage("Predicates on non-primary-key columns (c, s) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c = 1 AND s = 2 ALLOW FILTERING");
 +        assertInvalidMessage("Predicates on non-primary-key columns (s) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE a = 1 AND b = 1 AND s = 2 ALLOW FILTERING");
 +        assertInvalidMessage("Predicates on non-primary-key columns (s) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE s > 2 ALLOW FILTERING");
 +
 +        // Checks filtering with null
 +        assertInvalidMessage("Predicates on non-primary-key columns (s) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE s = null ALLOW FILTERING");
 +        assertInvalidMessage("Predicates on non-primary-key columns (s) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE s > null ALLOW FILTERING");
 +
 +        // Checks filtering with unset
 +        assertInvalidMessage("Predicates on non-primary-key columns (s) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE s = ? ALLOW FILTERING", unset());
 +        assertInvalidMessage("Predicates on non-primary-key columns (s) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE s > ? ALLOW FILTERING", unset());
 +    }
 +
 +    @Test
 +    public void testFilteringOnCompactTablesWithoutIndices() throws Throwable
 +    {
 +        //----------------------------------------------
 +        // Test COMPACT table with clustering columns
 +        //----------------------------------------------
 +        createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY (a, b)) WITH COMPACT STORAGE");
 +
 +        execute("INSERT INTO %s (a, b, c) VALUES (1, 2, 4)");
 +        execute("INSERT INTO %s (a, b, c) VALUES (1, 3, 6)");
 +        execute("INSERT INTO %s (a, b, c) VALUES (1, 4, 4)");
 +        execute("INSERT INTO %s (a, b, c) VALUES (2, 3, 7)");
 +
 +        // Lets add some tombstones to make sure that filtering handle them properly
 +        execute("INSERT INTO %s (a, b, c) VALUES (1, 1, 4)");
 +        execute("INSERT INTO %s (a, b, c) VALUES (2, 2, 7)");
 +        execute("DELETE FROM %s WHERE a = 1 AND b = 1");
 +        execute("DELETE FROM %s WHERE a = 2 AND b = 2");
 +
 +        flush();
 +
 +        // Checks filtering
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE a = 1 AND b = 4 AND c = 4 ALLOW FILTERING");
 +
 +        assertInvalidMessage("IN predicates on non-primary-key columns (c) is not yet supported",
 +                             "SELECT * FROM %s WHERE a IN (1, 2) AND c IN (6, 7) ALLOW FILTERING");
 +
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c > 4 ALLOW FILTERING");
 +
 +        // Checks filtering with null
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c = null");
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c = null ALLOW FILTERING");
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c > null");
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c > null ALLOW FILTERING");
 +
 +        // Checks filtering with unset
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c = ? ALLOW FILTERING",
 +                             unset());
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c > ? ALLOW FILTERING",
 +                             unset());
 +
 +        //----------------------------------------------
 +        // Test COMPACT table without clustering columns
 +        //----------------------------------------------
 +        createTable("CREATE TABLE %s (a int PRIMARY KEY, b int, c int) WITH COMPACT STORAGE");
 +
 +        execute("INSERT INTO %s (a, b, c) VALUES (1, 2, 4)");
 +        execute("INSERT INTO %s (a, b, c) VALUES (2, 1, 6)");
 +        execute("INSERT INTO %s (a, b, c) VALUES (3, 2, 4)");
 +        execute("INSERT INTO %s (a, b, c) VALUES (4, 1, 7)");
 +
 +        // Lets add some tombstones to make sure that filtering handle them properly
 +        execute("INSERT INTO %s (a, b, c) VALUES (0, 1, 4)");
 +        execute("INSERT INTO %s (a, b, c) VALUES (5, 2, 7)");
 +        execute("DELETE FROM %s WHERE a = 0");
 +        execute("DELETE FROM %s WHERE a = 5");
 +
 +        flush();
 +
 +        // Checks filtering
 +        assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
 +                             "SELECT * FROM %s WHERE a = 1 AND b = 4 AND c = 4");
 +
 +        assertRows(execute("SELECT * FROM %s WHERE a = 1 AND b = 2 AND c = 4 ALLOW FILTERING"),
 +                   row(1, 2, 4));
 +
 +        assertInvalidMessage("IN predicates on non-primary-key columns (c) is not yet supported",
 +                             "SELECT * FROM %s WHERE a IN (1, 2) AND c IN (6, 7)");
 +
 +        assertInvalidMessage("IN predicates on non-primary-key columns (c) is not yet supported",
 +                             "SELECT * FROM %s WHERE a IN (1, 2) AND c IN (6, 7) ALLOW FILTERING");
 +
 +        assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
 +                             "SELECT * FROM %s WHERE c > 4");
 +
 +        assertRows(execute("SELECT * FROM %s WHERE c > 4 ALLOW FILTERING"),
 +                   row(2, 1, 6),
 +                   row(4, 1, 7));
 +
 +        assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
 +                             "SELECT * FROM %s WHERE b < 3 AND c <= 4");
 +
 +        assertRows(execute("SELECT * FROM %s WHERE b < 3 AND c <= 4 ALLOW FILTERING"),
 +                   row(1, 2, 4),
 +                   row(3, 2, 4));
 +
 +        assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
 +                             "SELECT * FROM %s WHERE c >= 3 AND c <= 6");
 +
 +        assertRows(execute("SELECT * FROM %s WHERE c >= 3 AND c <= 6 ALLOW FILTERING"),
 +                   row(1, 2, 4),
 +                   row(2, 1, 6),
 +                   row(3, 2, 4));
 +
 +        // Checks filtering with null
 +        assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
 +                             "SELECT * FROM %s WHERE c = null");
 +        assertInvalidMessage("Unsupported null value for column c",
 +                             "SELECT * FROM %s WHERE c = null ALLOW FILTERING");
 +        assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
 +                             "SELECT * FROM %s WHERE c > null");
 +        assertInvalidMessage("Unsupported null value for column c",
 +                             "SELECT * FROM %s WHERE c > null ALLOW FILTERING");
 +
 +        // Checks filtering with unset
 +        assertInvalidMessage("Unsupported unset value for column c",
 +                             "SELECT * FROM %s WHERE c = ? ALLOW FILTERING",
 +                             unset());
 +        assertInvalidMessage("Unsupported unset value for column c",
 +                             "SELECT * FROM %s WHERE c > ? ALLOW FILTERING",
 +                             unset());
 +    }
 +
 +    @Test
 +    public void testFilteringWithoutIndicesWithCollections() throws Throwable
 +    {
 +        createTable("CREATE TABLE %s (a int, b int, c list<int>, d set<int>, e map<int, int>, PRIMARY KEY (a, b))");
 +
 +        execute("INSERT INTO %s (a, b, c, d, e) VALUES (1, 2, [1, 6], {2, 12}, {1: 6})");
 +        execute("INSERT INTO %s (a, b, c, d, e) VALUES (1, 3, [3, 2], {6, 4}, {3: 2})");
 +        execute("INSERT INTO %s (a, b, c, d, e) VALUES (1, 4, [1, 2], {2, 4}, {1: 2})");
 +        execute("INSERT INTO %s (a, b, c, d, e) VALUES (2, 3, [3, 6], {6, 12}, {3: 6})");
 +
 +        flush();
 +
 +        // Checks filtering for lists
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c CONTAINS 2 ALLOW FILTERING");
 +
 +        // Checks filtering for sets
 +        assertInvalidMessage("Predicates on non-primary-key columns (d) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE d CONTAINS 4 ALLOW FILTERING");
 +
 +        // Checks filtering for maps
 +        assertInvalidMessage("Predicates on non-primary-key columns (e) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE e CONTAINS 2 ALLOW FILTERING");
 +
 +        assertInvalidMessage("Predicates on non-primary-key columns (e) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE e CONTAINS KEY 2 ALLOW FILTERING");
 +
 +        // Checks filtering with null
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c CONTAINS null ALLOW FILTERING");
 +        assertInvalidMessage("Predicates on non-primary-key columns (d) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE d CONTAINS null ALLOW FILTERING");
 +        assertInvalidMessage("Predicates on non-primary-key columns (e) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE e CONTAINS null ALLOW FILTERING");
 +        assertInvalidMessage("Predicates on non-primary-key columns (e) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE e CONTAINS KEY null ALLOW FILTERING");
 +        assertInvalidMessage("Predicates on non-primary-key columns (e) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE e[null] = 2 ALLOW FILTERING");
 +        assertInvalidMessage("Predicates on non-primary-key columns (e) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE e[1] = null ALLOW FILTERING");
 +
 +        // Checks filtering with unset
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c CONTAINS ? ALLOW FILTERING",
 +                             unset());
 +        assertInvalidMessage("Predicates on non-primary-key columns (d) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE d CONTAINS ? ALLOW FILTERING",
 +                             unset());
 +        assertInvalidMessage("Predicates on non-primary-key columns (e) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE e CONTAINS ? ALLOW FILTERING",
 +                             unset());
 +        assertInvalidMessage("Predicates on non-primary-key columns (e) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE e CONTAINS KEY ? ALLOW FILTERING",
 +                             unset());
 +        assertInvalidMessage("Predicates on non-primary-key columns (e) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE e[?] = 2 ALLOW FILTERING",
 +                             unset());
 +        assertInvalidMessage("Predicates on non-primary-key columns (e) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE e[1] = ? ALLOW FILTERING",
 +                             unset());
 +    }
 +
 +    @Test
 +    public void testFilteringWithoutIndicesWithFrozenCollections() throws Throwable
 +    {
 +        createTable("CREATE TABLE %s (a int, b int, c frozen<list<int>>, d frozen<set<int>>, e frozen<map<int, int>>, PRIMARY KEY (a, b))");
 +
 +        execute("INSERT INTO %s (a, b, c, d, e) VALUES (1, 2, [1, 6], {2, 12}, {1: 6})");
 +        execute("INSERT INTO %s (a, b, c, d, e) VALUES (1, 3, [3, 2], {6, 4}, {3: 2})");
 +        execute("INSERT INTO %s (a, b, c, d, e) VALUES (1, 4, [1, 2], {2, 4}, {1: 2})");
 +        execute("INSERT INTO %s (a, b, c, d, e) VALUES (2, 3, [3, 6], {6, 12}, {3: 6})");
 +
 +        flush();
 +
 +        // Checks filtering for lists
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c = [3, 2] ALLOW FILTERING");
 +
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c > [1, 5] AND c < [3, 6] ALLOW FILTERING");
 +
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c CONTAINS 2 ALLOW FILTERING");
 +
 +        // Checks filtering for sets
 +        assertInvalidMessage("Predicates on non-primary-key columns (d) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE d = {6, 4} ALLOW FILTERING");
 +
 +        assertInvalidMessage("Predicates on non-primary-key columns (d) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE d > {4, 5} AND d < {6} ALLOW FILTERING");
 +
 +        assertInvalidMessage("Predicates on non-primary-key columns (d) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE d CONTAINS 4 ALLOW FILTERING");
 +
 +        // Checks filtering for maps
 +        assertInvalidMessage("Predicates on non-primary-key columns (e) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE e = {1 : 2} ALLOW FILTERING");
 +
 +        assertInvalidMessage("Predicates on non-primary-key columns (e) are not yet supported for non secondary index queries",
 +                "SELECT * FROM %s WHERE e > {1 : 4} AND e < {3 : 6} ALLOW FILTERING");
 +
 +        assertInvalidMessage("Predicates on non-primary-key columns (e) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE e CONTAINS 2 ALLOW FILTERING");
 +
 +        assertInvalidMessage("Map-entry equality predicates on frozen map column e are not supported",
 +                             "SELECT * FROM %s WHERE e[1] = 6 ALLOW FILTERING");
 +
 +        // Checks filtering with null
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c = null ALLOW FILTERING");
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c CONTAINS null ALLOW FILTERING");
 +        assertInvalidMessage("Predicates on non-primary-key columns (d) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE d = null ALLOW FILTERING");
 +        assertInvalidMessage("Predicates on non-primary-key columns (d) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE d CONTAINS null ALLOW FILTERING");
 +        assertInvalidMessage("Predicates on non-primary-key columns (e) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE e = null ALLOW FILTERING");
 +        assertInvalidMessage("Predicates on non-primary-key columns (e) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE e CONTAINS null ALLOW FILTERING");
 +        assertInvalidMessage("Predicates on non-primary-key columns (e) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE e CONTAINS KEY null ALLOW FILTERING");
 +        assertInvalidMessage("Map-entry equality predicates on frozen map column e are not supported",
 +                             "SELECT * FROM %s WHERE e[null] = 2 ALLOW FILTERING");
 +        assertInvalidMessage("Map-entry equality predicates on frozen map column e are not supported",
 +                             "SELECT * FROM %s WHERE e[1] = null ALLOW FILTERING");
 +
 +        // Checks filtering with unset
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c = ? ALLOW FILTERING",
 +                             unset());
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c CONTAINS ? ALLOW FILTERING",
 +                             unset());
 +        assertInvalidMessage("Predicates on non-primary-key columns (d) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE d = ? ALLOW FILTERING",
 +                             unset());
 +        assertInvalidMessage("Predicates on non-primary-key columns (d) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE d CONTAINS ? ALLOW FILTERING",
 +                             unset());
 +        assertInvalidMessage("Predicates on non-primary-key columns (e) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE e = ? ALLOW FILTERING",
 +                             unset());
 +        assertInvalidMessage("Predicates on non-primary-key columns (e) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE e CONTAINS ? ALLOW FILTERING",
 +                             unset());
 +        assertInvalidMessage("Predicates on non-primary-key columns (e) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE e CONTAINS KEY ? ALLOW FILTERING",
 +                             unset());
 +        assertInvalidMessage("Map-entry equality predicates on frozen map column e are not supported",
 +                             "SELECT * FROM %s WHERE e[?] = 2 ALLOW FILTERING",
 +                             unset());
 +        assertInvalidMessage("Map-entry equality predicates on frozen map column e are not supported",
 +                             "SELECT * FROM %s WHERE e[1] = ? ALLOW FILTERING",
 +                             unset());
 +    }
 +
 +    @Test
 +    public void testFilteringOnCompactTablesWithoutIndicesAndWithLists() throws Throwable
 +    {
 +        //----------------------------------------------
 +        // Test COMPACT table with clustering columns
 +        //----------------------------------------------
 +        createTable("CREATE TABLE %s (a int, b int, c frozen<list<int>>, PRIMARY KEY (a, b)) WITH COMPACT STORAGE");
 +
 +        execute("INSERT INTO %s (a, b, c) VALUES (1, 2, [4, 2])");
 +        execute("INSERT INTO %s (a, b, c) VALUES (1, 3, [6, 2])");
 +        execute("INSERT INTO %s (a, b, c) VALUES (1, 4, [4, 1])");
 +        execute("INSERT INTO %s (a, b, c) VALUES (2, 3, [7, 1])");
 +
 +        flush();
 +
 +        // Checks filtering
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE a = 1 AND b = 4 AND c = [4, 1] ALLOW FILTERING");
 +
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c > [4, 2] ALLOW FILTERING");
 +
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE b <= 3 AND c < [6, 2] ALLOW FILTERING");
 +
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c CONTAINS 2 ALLOW FILTERING");
 +
 +        assertInvalidMessage("Cannot use CONTAINS KEY on non-map column c",
 +                             "SELECT * FROM %s WHERE c CONTAINS KEY 2 ALLOW FILTERING");
 +
 +        // Checks filtering with null
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c = null");
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c = null ALLOW FILTERING");
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c > null");
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c > null ALLOW FILTERING");
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c CONTAINS null");
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c CONTAINS null ALLOW FILTERING");
 +
 +        // Checks filtering with unset
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c = ? ALLOW FILTERING",
 +                             unset());
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c > ? ALLOW FILTERING",
 +                             unset());
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c CONTAINS ? ALLOW FILTERING",
 +                             unset());
 +
 +        //----------------------------------------------
 +        // Test COMPACT table without clustering columns
 +        //----------------------------------------------
 +        createTable("CREATE TABLE %s (a int PRIMARY KEY, b int, c frozen<list<int>>) WITH COMPACT STORAGE");
 +
 +        execute("INSERT INTO %s (a, b, c) VALUES (1, 2, [4, 2])");
 +        execute("INSERT INTO %s (a, b, c) VALUES (2, 1, [6, 2])");
 +        execute("INSERT INTO %s (a, b, c) VALUES (3, 2, [4, 1])");
 +        execute("INSERT INTO %s (a, b, c) VALUES (4, 1, [7, 1])");
 +
 +        flush();
 +
 +        // Checks filtering
 +        assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
 +                             "SELECT * FROM %s WHERE a = 1 AND b = 2 AND c = [4, 2]");
 +
 +        assertRows(execute("SELECT * FROM %s WHERE a = 1 AND b = 2 AND c = [4, 2] ALLOW FILTERING"),
 +                   row(1, 2, list(4, 2)));
 +
 +        assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
 +                             "SELECT * FROM %s WHERE c > [4, 2]");
 +
 +        assertRows(execute("SELECT * FROM %s WHERE c > [4, 2] ALLOW FILTERING"),
 +                   row(2, 1, list(6, 2)),
 +                   row(4, 1, list(7, 1)));
 +
 +        assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
 +                             "SELECT * FROM %s WHERE b < 3 AND c <= [4, 2]");
 +
 +        assertRows(execute("SELECT * FROM %s WHERE b < 3 AND c <= [4, 2] ALLOW FILTERING"),
 +                   row(1, 2, list(4, 2)),
 +                   row(3, 2, list(4, 1)));
 +
 +        assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
 +                             "SELECT * FROM %s WHERE c >= [4, 3] AND c <= [7]");
 +
 +        assertRows(execute("SELECT * FROM %s WHERE c >= [4, 3] AND c <= [7] ALLOW FILTERING"),
 +                   row(2, 1, list(6, 2)));
 +
 +        assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
 +                "SELECT * FROM %s WHERE c CONTAINS 2");
 +
 +        assertRows(execute("SELECT * FROM %s WHERE c CONTAINS 2 ALLOW FILTERING"),
 +                   row(1, 2, list(4, 2)),
 +                   row(2, 1, list(6, 2)));
 +
 +        assertInvalidMessage("Cannot use CONTAINS KEY on non-map column c",
 +                             "SELECT * FROM %s WHERE c CONTAINS KEY 2 ALLOW FILTERING");
 +
 +        assertRows(execute("SELECT * FROM %s WHERE c CONTAINS 2 AND c CONTAINS 6 ALLOW FILTERING"),
 +                   row(2, 1, list(6, 2)));
 +
 +        // Checks filtering with null
 +        assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
 +                             "SELECT * FROM %s WHERE c = null");
 +        assertInvalidMessage("Unsupported null value for column c",
 +                             "SELECT * FROM %s WHERE c = null ALLOW FILTERING");
 +        assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
 +                             "SELECT * FROM %s WHERE c > null");
 +        assertInvalidMessage("Unsupported null value for column c",
 +                             "SELECT * FROM %s WHERE c > null ALLOW FILTERING");
 +        assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
 +                             "SELECT * FROM %s WHERE c CONTAINS null");
 +        assertInvalidMessage("Unsupported null value for column c",
 +                             "SELECT * FROM %s WHERE c CONTAINS null ALLOW FILTERING");
 +
 +        // Checks filtering with unset
 +        assertInvalidMessage("Unsupported unset value for column c",
 +                             "SELECT * FROM %s WHERE c = ? ALLOW FILTERING",
 +                             unset());
 +        assertInvalidMessage("Unsupported unset value for column c",
 +                             "SELECT * FROM %s WHERE c > ? ALLOW FILTERING",
 +                             unset());
 +        assertInvalidMessage("Unsupported unset value for column c",
 +                             "SELECT * FROM %s WHERE c CONTAINS ? ALLOW FILTERING",
 +                             unset());
 +    }
 +
 +    @Test
 +    public void testFilteringOnCompactTablesWithoutIndicesAndWithSets() throws Throwable
 +    {
 +        //----------------------------------------------
 +        // Test COMPACT table with clustering columns
 +        //----------------------------------------------
 +        createTable("CREATE TABLE %s (a int, b int, c frozen<set<int>>, PRIMARY KEY (a, b)) WITH COMPACT STORAGE");
 +
 +        execute("INSERT INTO %s (a, b, c) VALUES (1, 2, {4, 2})");
 +        execute("INSERT INTO %s (a, b, c) VALUES (1, 3, {6, 2})");
 +        execute("INSERT INTO %s (a, b, c) VALUES (1, 4, {4, 1})");
 +        execute("INSERT INTO %s (a, b, c) VALUES (2, 3, {7, 1})");
 +
 +        flush();
 +
 +        // Checks filtering
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE a = 1 AND b = 4 AND c = {4, 1} ALLOW FILTERING");
 +
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c > {4, 2} ALLOW FILTERING");
 +
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c >= {4, 2} AND c <= {6, 4} ALLOW FILTERING");
 +
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c CONTAINS 2 ALLOW FILTERING");
 +
 +        assertInvalidMessage("Cannot use CONTAINS KEY on non-map column c",
 +                             "SELECT * FROM %s WHERE c CONTAINS KEY 2 ALLOW FILTERING");
 +
 +        // Checks filtering with null
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c = null");
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c = null ALLOW FILTERING");
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c > null");
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c > null ALLOW FILTERING");
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c CONTAINS null");
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c CONTAINS null ALLOW FILTERING");
 +
 +        // Checks filtering with unset
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c = ? ALLOW FILTERING",
 +                             unset());
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c > ? ALLOW FILTERING",
 +                             unset());
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c CONTAINS ? ALLOW FILTERING",
 +                             unset());
 +
 +        //----------------------------------------------
 +        // Test COMPACT table without clustering columns
 +        //----------------------------------------------
 +        createTable("CREATE TABLE %s (a int PRIMARY KEY, b int, c frozen<set<int>>) WITH COMPACT STORAGE");
 +
 +        execute("INSERT INTO %s (a, b, c) VALUES (1, 2, {4, 2})");
 +        execute("INSERT INTO %s (a, b, c) VALUES (2, 1, {6, 2})");
 +        execute("INSERT INTO %s (a, b, c) VALUES (3, 2, {4, 1})");
 +        execute("INSERT INTO %s (a, b, c) VALUES (4, 1, {7, 1})");
 +
 +        flush();
 +
 +        // Checks filtering
 +        assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
 +                             "SELECT * FROM %s WHERE a = 1 AND b = 2 AND c = {4, 2}");
 +
 +        assertRows(execute("SELECT * FROM %s WHERE a = 1 AND b = 2 AND c = {4, 2} ALLOW FILTERING"),
 +                   row(1, 2, set(4, 2)));
 +
 +        assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
 +                             "SELECT * FROM %s WHERE c > {4, 2}");
 +
 +        assertRows(execute("SELECT * FROM %s WHERE c > {4, 2} ALLOW FILTERING"),
 +                   row(2, 1, set(6, 2)));
 +
 +        assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
 +                             "SELECT * FROM %s WHERE b < 3 AND c <= {4, 2}");
 +
 +        assertRows(execute("SELECT * FROM %s WHERE b < 3 AND c <= {4, 2} ALLOW FILTERING"),
 +                   row(1, 2, set(4, 2)),
 +                   row(4, 1, set(1, 7)),
 +                   row(3, 2, set(4, 1)));
 +
 +        assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
 +                             "SELECT * FROM %s WHERE c >= {4, 3} AND c <= {7}");
 +
 +        assertRows(execute("SELECT * FROM %s WHERE c >= {5, 2} AND c <= {7} ALLOW FILTERING"),
 +                   row(2, 1, set(6, 2)));
 +
 +        assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
 +                "SELECT * FROM %s WHERE c CONTAINS 2");
 +
 +        assertRows(execute("SELECT * FROM %s WHERE c CONTAINS 2 ALLOW FILTERING"),
 +                   row(1, 2, set(4, 2)),
 +                   row(2, 1, set(6, 2)));
 +
 +        assertInvalidMessage("Cannot use CONTAINS KEY on non-map column c",
 +                             "SELECT * FROM %s WHERE c CONTAINS KEY 2 ALLOW FILTERING");
 +
 +        assertRows(execute("SELECT * FROM %s WHERE c CONTAINS 2 AND c CONTAINS 6 ALLOW FILTERING"),
 +                   row(2, 1, set(6, 2)));
 +
 +        // Checks filtering with null
 +        assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
 +                             "SELECT * FROM %s WHERE c = null");
 +        assertInvalidMessage("Unsupported null value for column c",
 +                             "SELECT * FROM %s WHERE c = null ALLOW FILTERING");
 +        assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
 +                             "SELECT * FROM %s WHERE c > null");
 +        assertInvalidMessage("Unsupported null value for column c",
 +                             "SELECT * FROM %s WHERE c > null ALLOW FILTERING");
 +        assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
 +                             "SELECT * FROM %s WHERE c CONTAINS null");
 +        assertInvalidMessage("Unsupported null value for column c",
 +                             "SELECT * FROM %s WHERE c CONTAINS null ALLOW FILTERING");
 +
 +        // Checks filtering with unset
 +        assertInvalidMessage("Unsupported unset value for column c",
 +                             "SELECT * FROM %s WHERE c = ? ALLOW FILTERING",
 +                             unset());
 +        assertInvalidMessage("Unsupported unset value for column c",
 +                             "SELECT * FROM %s WHERE c > ? ALLOW FILTERING",
 +                             unset());
 +        assertInvalidMessage("Unsupported unset value for column c",
 +                             "SELECT * FROM %s WHERE c CONTAINS ? ALLOW FILTERING",
 +                             unset());
 +    }
 +
 +    @Test
 +    public void testIndexQueryWithValueOver64K() throws Throwable
 +    {
 +        createTable("CREATE TABLE %s (a int, b int, c blob, PRIMARY KEY (a, b))");
 +        createIndex("CREATE INDEX test ON %s (c)");
 +
-         execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, 0, ByteBufferUtil.bytes(1));
-         execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, 1, ByteBufferUtil.bytes(2));
++        execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, 0, bytes(1));
++        execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, 1, bytes(2));
 +
 +        assertInvalidMessage("Index expression values may not be larger than 64K",
 +                             "SELECT * FROM %s WHERE c = ?  ALLOW FILTERING", TOO_BIG);
 +    }
 +
 +    @Test
 +    public void testFilteringOnCompactTablesWithoutIndicesAndWithMaps() throws Throwable
 +    {
 +        //----------------------------------------------
 +        // Test COMPACT table with clustering columns
 +        //----------------------------------------------
 +        createTable("CREATE TABLE %s (a int, b int, c frozen<map<int, int>>, PRIMARY KEY (a, b)) WITH COMPACT STORAGE");
 +
 +        execute("INSERT INTO %s (a, b, c) VALUES (1, 2, {4 : 2})");
 +        execute("INSERT INTO %s (a, b, c) VALUES (1, 3, {6 : 2})");
 +        execute("INSERT INTO %s (a, b, c) VALUES (1, 4, {4 : 1})");
 +        execute("INSERT INTO %s (a, b, c) VALUES (2, 3, {7 : 1})");
 +
 +        flush();
 +
 +        // Checks filtering
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE a = 1 AND b = 4 AND c = {4 : 1} ALLOW FILTERING");
 +
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c > {4 : 2} ALLOW FILTERING");
 +
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE b <= 3 AND c < {6 : 2} ALLOW FILTERING");
 +
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c CONTAINS 2 ALLOW FILTERING");
 +
 +        // Checks filtering with null
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c = null");
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c = null ALLOW FILTERING");
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c > null");
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c > null ALLOW FILTERING");
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c CONTAINS null");
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c CONTAINS null ALLOW FILTERING");
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c CONTAINS KEY null ALLOW FILTERING");
 +
 +        // Checks filtering with unset
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c = ? ALLOW FILTERING",
 +                             unset());
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c > ? ALLOW FILTERING",
 +                             unset());
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c CONTAINS ? ALLOW FILTERING",
 +                             unset());
 +        assertInvalidMessage("Predicates on non-primary-key columns (c) are not yet supported for non secondary index queries",
 +                             "SELECT * FROM %s WHERE c CONTAINS KEY ? ALLOW FILTERING",
 +                             unset());
 +
 +        //----------------------------------------------
 +        // Test COMPACT table without clustering columns
 +        //----------------------------------------------
 +        createTable("CREATE TABLE %s (a int PRIMARY KEY, b int, c frozen<map<int, int>>) WITH COMPACT STORAGE");
 +
 +        execute("INSERT INTO %s (a, b, c) VALUES (1, 2, {4 : 2})");
 +        execute("INSERT INTO %s (a, b, c) VALUES (2, 1, {6 : 2})");
 +        execute("INSERT INTO %s (a, b, c) VALUES (3, 2, {4 : 1})");
 +        execute("INSERT INTO %s (a, b, c) VALUES (4, 1, {7 : 1})");
 +
 +        flush();
 +
 +        // Checks filtering
 +        assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
 +                             "SELECT * FROM %s WHERE a = 1 AND b = 2 AND c = {4 : 2}");
 +
 +        assertRows(execute("SELECT * FROM %s WHERE a = 1 AND b = 2 AND c = {4 : 2} ALLOW FILTERING"),
 +                   row(1, 2, map(4, 2)));
 +
 +        assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
 +                             "SELECT * FROM %s WHERE c > {4 : 2}");
 +
 +        assertRows(execute("SELECT * FROM %s WHERE c > {4 : 2} ALLOW FILTERING"),
 +                   row(2, 1, map(6, 2)),
 +                   row(4, 1, map(7, 1)));
 +
 +        assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
 +                             "SELECT * FROM %s WHERE b < 3 AND c <= {4 : 2}");
 +
 +        assertRows(execute("SELECT * FROM %s WHERE b < 3 AND c <= {4 : 2} ALLOW FILTERING"),
 +                   row(1, 2, map(4, 2)),
 +                   row(3, 2, map(4, 1)));
 +
 +        assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
 +                             "SELECT * FROM %s WHERE c >= {4 : 3} AND c <= {7 : 1}");
 +
 +        assertRows(execute("SELECT * FROM %s WHERE c >= {5 : 2} AND c <= {7 : 0} ALLOW FILTERING"),
 +                   row(2, 1, map(6, 2)));
 +
 +        assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
 +                "SELECT * FROM %s WHERE c CONTAINS 2");
 +
 +        assertRows(execute("SELECT * FROM %s WHERE c CONTAINS 2 ALLOW FILTERING"),
 +                   row(1, 2, map(4, 2)),
 +                   row(2, 1, map(6, 2)));
 +
 +        assertRows(execute("SELECT * FROM %s WHERE c CONTAINS KEY 4 ALLOW FILTERING"),
 +                   row(1, 2, map(4, 2)),
 +                   row(3, 2, map(4, 1)));
 +
 +        assertRows(execute("SELECT * FROM %s WHERE c CONTAINS 2 AND c CONTAINS KEY 6 ALLOW FILTERING"),
 +                   row(2, 1, map(6, 2)));
 +
 +        // Checks filtering with null
 +        assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
 +                             "SELECT * FROM %s WHERE c = null");
 +        assertInvalidMessage("Unsupported null value for column c",
 +                             "SELECT * FROM %s WHERE c = null ALLOW FILTERING");
 +        assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
 +                             "SELECT * FROM %s WHERE c > null");
 +        assertInvalidMessage("Unsupported null value for column c",
 +                             "SELECT * FROM %s WHERE c > null ALLOW FILTERING");
 +        assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
 +                             "SELECT * FROM %s WHERE c CONTAINS null");
 +        assertInvalidMessage("Unsupported null value for column c",
 +                             "SELECT * FROM %s WHERE c CONTAINS null ALLOW FILTERING");
 +        assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
 +                             "SELECT * FROM %s WHERE c CONTAINS KEY null");
 +        assertInvalidMessage("Unsupported null value for column c",
 +                             "SELECT * FROM %s WHERE c CONTAINS KEY null ALLOW FILTERING");
 +
 +        // Checks filtering with unset
 +        assertInvalidMessage("Unsupported unset value for column c",
 +                             "SELECT * FROM %s WHERE c = ? ALLOW FILTERING",
 +                             unset());
 +        assertInvalidMessage("Unsupported unset value for column c",
 +                             "SELECT * FROM %s WHERE c > ? ALLOW FILTERING",
 +                             unset());
 +        assertInvalidMessage("Unsupported unset value for column c",
 +                             "SELECT * FROM %s WHERE c CONTAINS ? ALLOW FILTERING",
 +                             unset());
 +        assertInvalidMessage("Unsupported unset value for column c",
 +                             "SELECT * FROM %s WHERE c CONTAINS KEY ? ALLOW FILTERING",
 +                             unset());
 +    }
 +
 +    /**
 +     * Check select with and without compact storage, with different column
 +     * order. See CASSANDRA-10988
 +     */
 +    @Test
 +    public void testClusteringOrderWithSlice() throws Throwable
 +    {
 +        for (String compactOption : new String[] { "", " COMPACT STORAGE AND" })
 +        {
 +            // non-compound, ASC order
 +            createTable("CREATE TABLE %s (a text, b int, PRIMARY KEY (a, b)) WITH" +
 +                        compactOption +
 +                        " CLUSTERING ORDER BY (b ASC)");
 +
 +            execute("INSERT INTO %s (a, b) VALUES ('a', 2)");
 +            execute("INSERT INTO %s (a, b) VALUES ('a', 3)");
 +            assertRows(execute("SELECT * FROM %s WHERE a = 'a' AND b > 0"),
 +                       row("a", 2),
 +                       row("a", 3));
 +
 +            assertRows(execute("SELECT * FROM %s WHERE a = 'a' AND b > 0 ORDER BY b DESC"),
 +                       row("a", 3),
 +                       row("a", 2));
 +
 +            // non-compound, DESC order
 +            createTable("CREATE TABLE %s (a text, b int, PRIMARY KEY (a, b)) WITH" +
 +                        compactOption +
 +                        " CLUSTERING ORDER BY (b DESC)");
 +
 +            execute("INSERT INTO %s (a, b) VALUES ('a', 2)");
 +            execute("INSERT INTO %s (a, b) VALUES ('a', 3)");
 +            assertRows(execute("SELECT * FROM %s WHERE a = 'a' AND b > 0"),
 +                       row("a", 3),
 +                       row("a", 2));
 +
 +            assertRows(execute("SELECT * FROM %s WHERE a = 'a' AND b > 0 ORDER BY b ASC"),
 +                       row("a", 2),
 +                       row("a", 3));
 +
 +            // compound, first column DESC order
 +            createTable("CREATE TABLE %s (a text, b int, c int, PRIMARY KEY (a, b, c)) WITH" +
 +                        compactOption +
 +                        " CLUSTERING ORDER BY (b DESC)"
 +            );
 +
 +            execute("INSERT INTO %s (a, b, c) VALUES ('a', 2, 4)");
 +            execute("INSERT INTO %s (a, b, c) VALUES ('a', 3, 5)");
 +            assertRows(execute("SELECT * FROM %s WHERE a = 'a' AND b > 0"),
 +                       row("a", 3, 5),
 +                       row("a", 2, 4));
 +
 +            assertRows(execute("SELECT * FROM %s WHERE a = 'a' AND b > 0 ORDER BY b ASC"),
 +                       row("a", 2, 4),
 +                       row("a", 3, 5));
 +
 +            // compound, mixed order
 +            createTable("CREATE TABLE %s (a text, b int, c int, PRIMARY KEY (a, b, c)) WITH" +
 +                        compactOption +
 +                        " CLUSTERING ORDER BY (b ASC, c DESC)"
 +            );
 +
 +            execute("INSERT INTO %s (a, b, c) VALUES ('a', 2, 4)");
 +            execute("INSERT INTO %s (a, b, c) VALUES ('a', 3, 5)");
 +            assertRows(execute("SELECT * FROM %s WHERE a = 'a' AND b > 0"),
 +                       row("a", 2, 4),
 +                       row("a", 3, 5));
 +
 +            assertRows(execute("SELECT * FROM %s WHERE a = 'a' AND b > 0 ORDER BY b ASC"),
 +                       row("a", 2, 4),
 +                       row("a", 3, 5));
 +        }
 +    }
 +
 +    @Test
      public void testOverlyLargeSelectPK() throws Throwable
      {
          createTable("CREATE TABLE %s (a text, b text, PRIMARY KEY ((a), b))");
@@@ -2339,4 -1330,375 +2342,367 @@@
                     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 restrictions on non-primary key value
++            assertInvalidMessage("Predicates on non-primary-key columns (v) are not yet supported for non secondary index queries",
++                                 "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'));"),
++                assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c1 IN (textAsBlob(''), textAsBlob('1')) AND c2 = 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 = 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"), 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'));"),
++                assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c1 IN (textAsBlob(''), textAsBlob('1')) AND c2 = 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) 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),
++                assertRows(execute("SELECT * FROM %s WHERE pk = textAsBlob('foo123') AND c1 IN (textAsBlob(''), textAsBlob('1')) AND c2 = 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) 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")));
+             }
+         }
+     }
  }


[05/12] cassandra git commit: Merge branch cassandra-2.1 into cassandra-2.2

Posted by bl...@apache.org.
http://git-wip-us.apache.org/repos/asf/cassandra/blob/9583b6b3/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
index 8820ff7,40f3f33..edf8e47
--- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
@@@ -181,8 -247,11 +181,11 @@@ public class SelectStatement implement
  
      private Pageable getPageableCommand(QueryOptions options, int limit, long now) throws RequestValidationException
      {
 -        if (isNotReturningAnyRows(options))
++        if (restrictions.isNotReturningAnyRows(options))
+             return null;
+ 
          int limitForQuery = updateLimitForQuery(limit);
 -        if (isKeyRange || usesSecondaryIndexing)
 +        if (restrictions.isKeyRange() || restrictions.usesSecondaryIndexing())
              return getRangeCommand(options, limitForQuery, now);
  
          List<ReadCommand> commands = getSliceCommands(options, limitForQuery, now);
@@@ -596,52 -961,338 +599,58 @@@
          }
      }
  
 -    /** Returns true if a non-frozen collection is selected, false otherwise. */
 -    private boolean selectACollection()
 +    /**
 +     * May be used by custom QueryHandler implementations
 +     */
 +    public List<IndexExpression> getValidatedIndexExpressions(QueryOptions options) throws InvalidRequestException
      {
 -        if (!cfm.comparator.hasCollections())
 -            return false;
 +        if (!restrictions.usesSecondaryIndexing())
 +            return Collections.emptyList();
  
 -        for (ColumnDefinition def : selection.getColumns())
 -        {
 -            if (def.type.isCollection() && def.type.isMultiCell())
 -                return true;
 -        }
 +        ColumnFamilyStore cfs = Keyspace.open(keyspace()).getColumnFamilyStore(columnFamily());
 +        SecondaryIndexManager secondaryIndexManager = cfs.indexManager;
  
 -        return false;
 +        List<IndexExpression> expressions = restrictions.getIndexExpressions(secondaryIndexManager, options);
 +        secondaryIndexManager.validateIndexSearchersForQuery(expressions);
 +
 +        return expressions;
      }
  
 -    @VisibleForTesting
 -    static List<Composite> buildBound(Bound bound,
 -                                      List<ColumnDefinition> defs,
 -                                      Restriction[] restrictions,
 -                                      boolean isReversed,
 -                                      CType type,
 -                                      QueryOptions options) throws InvalidRequestException
 +    private CellName makeExclusiveSliceBound(Bound bound, CellNameType type, QueryOptions options) throws InvalidRequestException
      {
 -        CBuilder builder = type.builder();
 -
 -        // The end-of-component of composite doesn't depend on whether the
 -        // component type is reversed or not (i.e. the ReversedType is applied
 -        // to the component comparator but not to the end-of-component itself),
 -        // it only depends on whether the slice is reversed
 -        Bound eocBound = isReversed ? Bound.reverse(bound) : bound;
 -        for (int i = 0, m = defs.size(); i < m; i++)
 -        {
 -            ColumnDefinition def = defs.get(i);
 -
 -            // In a restriction, we always have Bound.START < Bound.END for the "base" comparator.
 -            // So if we're doing a reverse slice, we must inverse the bounds when giving them as start and end of the slice filter.
 -            // But if the actual comparator itself is reversed, we must inversed the bounds too.
 -            Bound b = isReversed == isReversedType(def) ? bound : Bound.reverse(bound);
 -            Restriction r = restrictions[def.position()];
 -            if (isNullRestriction(r, b) || !r.canEvaluateWithSlices())
 -            {
 -                // There wasn't any non EQ relation on that key, we select all records having the preceding component as prefix.
 -                // For composites, if there was preceding component and we're computing the end, we must change the last component
 -                // End-Of-Component, otherwise we would be selecting only one record.
 -                Composite prefix = builder.build();
 -                return Collections.singletonList(eocBound == Bound.END ? prefix.end() : prefix.start());
 -            }
 -            if (r.isSlice())
 -            {
 -                if (r.isMultiColumn())
 -                {
 -                    MultiColumnRestriction.Slice slice = (MultiColumnRestriction.Slice) r;
 +        // clusteringColumnBounds may reverse bound if clustering order is reversed
 +        // but areRequestedBoundsInclusive checks for Restriction::isInclusive and never
 +        // reverses the order. In order to avoid inconsistencies and check inclusive
 +        // bounds correctly, we need to check for column order and reverse it. See CASSANDRA-10988
 +        if (restrictions.areRequestedBoundsInclusive(reverseBoundIfNeeded(bound)))
 +            return null;
  
-        return type.makeCellName(restrictions.getClusteringColumnsBounds(bound, options).get(0));
 -                    if (!slice.hasBound(b))
 -                    {
 -                        Composite prefix = builder.build();
 -                        return Collections.singletonList(builder.remainingCount() > 0 && eocBound == Bound.END
 -                                ? prefix.end()
 -                                : prefix);
 -                    }
++        // We can only reach that if the table is dense non-compound tables.
++        // By consequence, we know that the table is a COMPACT table with only one clustering column.
++        ByteBuffer value = restrictions.getClusteringColumnsBounds(bound, options).get(0);
+ 
 -                    List<ByteBuffer> vals = slice.componentBounds(b, options);
 -
 -                    for (int j = 0, n = vals.size(); j < n; j++)
 -                        addValue(builder, defs.get(i + j), vals.get(j)) ;
 -                }
 -                else
 -                {
 -                    builder.add(getSliceValue(r, b, options));
 -                }
 -                Operator relType = ((Restriction.Slice)r).getRelation(eocBound, b);
 -                return Collections.singletonList(builder.build().withEOC(eocForRelation(relType)));
 -            }
 -
 -            if (r.isIN())
 -            {
 -                // The IN query might not have listed the values in comparator order, so we need to re-sort
 -                // the bounds lists to make sure the slices works correctly (also, to avoid duplicates).
 -                TreeSet<Composite> inValues = new TreeSet<>(isReversed ? type.reverseComparator() : type);
 -
 -                if (r.isMultiColumn())
 -                {
 -                    List<List<ByteBuffer>> splitInValues = ((MultiColumnRestriction.IN) r).splitValues(options);
 -
 -                    for (List<ByteBuffer> components : splitInValues)
 -                    {
 -                        for (int j = 0; j < components.size(); j++)
 -                            if (components.get(j) == null)
 -                                throw new InvalidRequestException("Invalid null value in condition for column " + defs.get(i + j).name);
 -
 -                        Composite prefix = builder.buildWith(components);
 -                        inValues.add(builder.remainingCount() == 0 ? prefix : addEOC(prefix, eocBound));
 -                    }
 -                    return new ArrayList<>(inValues);
 -                }
 -
 -                List<ByteBuffer> values = r.values(options);
 -                if (values.size() != 1)
 -                {
 -                    // IN query, we only support it on the clustering columns
 -                    assert def.position() == defs.size() - 1;
 -                    for (ByteBuffer val : values)
 -                    {
 -                        if (val == null)
 -                            throw new InvalidRequestException(String.format("Invalid null value in condition for column %s",
 -                                                                            def.name));
 -                        Composite prefix = builder.buildWith(val);
 -                        // See below for why this
 -                        inValues.add(builder.remainingCount() == 0 ? prefix : addEOC(prefix, eocBound));
 -                    }
 -                    return new ArrayList<>(inValues);
 -                }
 -            }
 -
 -            List<ByteBuffer> values = r.values(options);
 -
 -            if (r.isMultiColumn())
 -            {
 -                for (int j = 0; j < values.size(); j++)
 -                    addValue(builder, defs.get(i + j), values.get(j));
 -                i += values.size() - 1; // skips the processed columns
 -            }
 -            else
 -            {
 -                addValue(builder, def, values.get(0));
 -            }
 -        }
 -        // Means no relation at all or everything was an equal
 -        // Note: if the builder is "full", there is no need to use the end-of-component bit. For columns selection,
 -        // it would be harmless to do it. However, we use this method got the partition key too. And when a query
 -        // with 2ndary index is done, and with the the partition provided with an EQ, we'll end up here, and in that
 -        // case using the eoc would be bad, since for the random partitioner we have no guarantee that
 -        // prefix.end() will sort after prefix (see #5240).
 -        Composite prefix = builder.build();
 -        return Collections.singletonList(builder.remainingCount() == 0 ? prefix : addEOC(prefix, eocBound));
 -    }
++        // 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;
 +    }
  
      /**
 -     * Adds an EOC to the specified Composite.
 -     *
 -     * @param composite the composite
 -     * @param eocBound the EOC bound
 -     * @return a new <code>Composite</code> with the EOC corresponding to the eocBound
 +     * Reverses the specified bound if the non-compound clustering column is a reversed one.
 +     * @param bound bound to reverse
 +     * @return the bound reversed if the column type was a reversed one or the original bound
       */
 -    private static Composite addEOC(Composite composite, Bound eocBound)
 +    private Bound reverseBoundIfNeeded(Bound bound)
      {
 -        return eocBound == Bound.END ? composite.end() : composite.start();
 -    }
 +        assert !cfm.comparator.isCompound();
  
 -    /**
 -     * Adds the specified value to the specified builder
 -     *
 -     * @param builder the CBuilder to which the value must be added
 -     * @param def the column associated to the value
 -     * @param value the value to add
 -     * @throws InvalidRequestException if the value is null
 -     */
 -    private static void addValue(CBuilder builder, ColumnDefinition def, ByteBuffer value) throws InvalidRequestException
 -    {
 -        if (value == null)
 -            throw new InvalidRequestException(String.format("Invalid null value in condition for column %s", def.name));
 -        builder.add(value);
 -    }
 -
 -    private static Composite.EOC eocForRelation(Operator op)
 -    {
 -        switch (op)
 -        {
 -            case LT:
 -                // < X => using startOf(X) as finish bound
 -                return Composite.EOC.START;
 -            case GT:
 -            case LTE:
 -                // > X => using endOf(X) as start bound
 -                // <= X => using endOf(X) as finish bound
 -                return Composite.EOC.END;
 -            default:
 -                // >= X => using X as start bound (could use START_OF too)
 -                // = X => using X
 -                return Composite.EOC.NONE;
 -        }
 -    }
 -
 -    private static boolean isNullRestriction(Restriction r, Bound b)
 -    {
 -        return r == null || (r.isSlice() && !((Restriction.Slice)r).hasBound(b));
 -    }
 -
 -    private static ByteBuffer getSliceValue(Restriction r, Bound b, QueryOptions options) throws InvalidRequestException
 -    {
 -        Restriction.Slice slice = (Restriction.Slice)r;
 -        assert slice.hasBound(b);
 -        ByteBuffer val = slice.bound(b, options);
 -        if (val == null)
 -            throw new InvalidRequestException(String.format("Invalid null clustering key part %s", r));
 -        return val;
 -    }
 -
 -    private List<Composite> getRequestedBound(Bound b, QueryOptions options) throws InvalidRequestException
 -    {
 -        assert isColumnRange();
 -        List<Composite> bound = buildBound(b, cfm.clusteringColumns(), columnRestrictions, isReversed, cfm.comparator, options);
 -        for (Composite c : bound) {
 -            if (!c.isEmpty())
 -                QueryProcessor.validateComposite(c, cfm.comparator);
 -        }
 -        return bound;
 +        List<ColumnDefinition> columnDefs = cfm.clusteringColumns();
 +        return columnDefs.get(columnDefs.size() - 1).isReversedType() ? bound.reverse() : bound;
      }
  
 -    /**
 -     * May be used by custom QueryHandler implementations
 -     */
 -    public List<IndexExpression> getValidatedIndexExpressions(QueryOptions options) throws InvalidRequestException
 -    {
 -        if (!usesSecondaryIndexing || restrictedColumns.isEmpty())
 -            return Collections.emptyList();
 -
 -        List<IndexExpression> expressions = new ArrayList<IndexExpression>();
 -        for (ColumnDefinition def : restrictedColumns.keySet())
 -        {
 -            Restriction restriction;
 -            switch (def.kind)
 -            {
 -                case PARTITION_KEY:
 -                    restriction = keyRestrictions[def.position()];
 -                    break;
 -                case CLUSTERING_COLUMN:
 -                    restriction = columnRestrictions[def.position()];
 -                    break;
 -                case REGULAR:
 -                case STATIC:
 -                    restriction = metadataRestrictions.get(def.name);
 -                    break;
 -                default:
 -                    // We don't allow restricting a COMPACT_VALUE for now in prepare.
 -                    throw new AssertionError();
 -            }
 -
 -            if (restriction.isSlice())
 -            {
 -                Restriction.Slice slice = (Restriction.Slice)restriction;
 -                for (Bound b : Bound.values())
 -                {
 -                    if (slice.hasBound(b))
 -                    {
 -                        ByteBuffer value = validateIndexedValue(def, slice.bound(b, options));
 -                        Operator op = slice.getIndexOperator(b);
 -                        // If the underlying comparator for name is reversed, we need to reverse the IndexOperator: user operation
 -                        // always refer to the "forward" sorting even if the clustering order is reversed, but the 2ndary code does
 -                        // use the underlying comparator as is.
 -                        if (def.type instanceof ReversedType)
 -                            op = reverse(op);
 -                        expressions.add(new IndexExpression(def.name.bytes, op, value));
 -                    }
 -                }
 -            }
 -            else if (restriction.isContains())
 -            {
 -                SingleColumnRestriction.Contains contains = (SingleColumnRestriction.Contains)restriction;
 -                for (ByteBuffer value : contains.values(options))
 -                {
 -                    validateIndexedValue(def, value);
 -                    expressions.add(new IndexExpression(def.name.bytes, Operator.CONTAINS, value));
 -                }
 -                for (ByteBuffer key : contains.keys(options))
 -                {
 -                    validateIndexedValue(def, key);
 -                    expressions.add(new IndexExpression(def.name.bytes, Operator.CONTAINS_KEY, key));
 -                }
 -            }
 -            else
 -            {
 -                ByteBuffer value;
 -                if (restriction.isMultiColumn())
 -                {
 -                    List<ByteBuffer> values = restriction.values(options);
 -                    value = values.get(def.position());
 -                }
 -                else
 -                {
 -                    List<ByteBuffer> values = restriction.values(options);
 -                    if (values.size() != 1)
 -                        throw new InvalidRequestException("IN restrictions are not supported on indexed columns");
 -
 -                    value = values.get(0);
 -                }
 -
 -                validateIndexedValue(def, value);
 -                expressions.add(new IndexExpression(def.name.bytes, Operator.EQ, value));
 -            }
 -        }
 -
 -        if (usesSecondaryIndexing)
 -        {
 -            ColumnFamilyStore cfs = Keyspace.open(keyspace()).getColumnFamilyStore(columnFamily());
 -            SecondaryIndexManager secondaryIndexManager = cfs.indexManager;
 -            secondaryIndexManager.validateIndexSearchersForQuery(expressions);
 -        }
 -        
 -        return expressions;
 -    }
 -
 -    private static ByteBuffer validateIndexedValue(ColumnDefinition def, ByteBuffer value) throws InvalidRequestException
 -    {
 -        if (value == null)
 -            throw new InvalidRequestException(String.format("Unsupported null value for indexed column %s", def.name));
 -        if (value.remaining() > 0xFFFF)
 -            throw new InvalidRequestException("Index expression values may not be larger than 64K");
 -        return value;
 -    }
 -
 -    private CellName makeExclusiveSliceBound(Bound bound, CellNameType type, QueryOptions options) throws InvalidRequestException
 -    {
 -        if (sliceRestriction.isInclusive(bound))
 -            return null;
 -
 -        // 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;
 -
          final CellNameType type = cfm.comparator;
 +
          final CellName excludedStart = makeExclusiveSliceBound(Bound.START, type, options);
          final CellName excludedEnd = makeExclusiveSliceBound(Bound.END, type, options);
  

http://git-wip-us.apache.org/repos/asf/cassandra/blob/9583b6b3/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java
index 91a059f,39e632a..517d842
--- a/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java
@@@ -86,7 -84,7 +86,7 @@@ public class UpdateStatement extends Mo
          if (cfm.comparator.isDense())
          {
              if (prefix.isEmpty())
-                 throw new InvalidRequestException(String.format("Missing PRIMARY KEY part %s", cfm.clusteringColumns().get(0)));
 -                throw new InvalidRequestException(String.format("Missing PRIMARY KEY part %s", cfm.clusteringColumns().iterator().next().name));
++                throw new InvalidRequestException(String.format("Missing PRIMARY KEY part %s", cfm.clusteringColumns().get(0).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/9583b6b3/src/java/org/apache/cassandra/db/compaction/Scrubber.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/db/compaction/Scrubber.java
index d1fe702,2df3665..99ee62e
--- a/src/java/org/apache/cassandra/db/compaction/Scrubber.java
+++ b/src/java/org/apache/cassandra/db/compaction/Scrubber.java
@@@ -23,12 -23,12 +23,14 @@@ 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.lifecycle.LifecycleTransaction;
+ import org.apache.cassandra.db.columniterator.OnDiskAtomIterator;
  import org.apache.cassandra.io.sstable.*;
 +import org.apache.cassandra.io.sstable.format.SSTableReader;
 +import org.apache.cassandra.io.sstable.format.SSTableWriter;
  import org.apache.cassandra.io.util.FileUtils;
  import org.apache.cassandra.io.util.RandomAccessReader;
  import org.apache.cassandra.service.ActiveRepairService;
@@@ -220,22 -222,11 +222,8 @@@ public class Scrubber implements Closea
                      if (indexFile != null && dataStart != dataStartFromIndex)
                          outputHandler.warn(String.format("Data file row position %d differs from index file row position %d", dataStart, dataStartFromIndex));
  
-                     SSTableIdentityIterator atoms = new SSTableIdentityIterator(sstable, dataFile, key, checkData);
- 
-                     if (prevKey != null && prevKey.compareTo(key) > 0)
-                     {
-                         saveOutOfOrderRow(prevKey, key, atoms);
-                         continue;
-                     }
- 
-                     @SuppressWarnings("resource")
-                     AbstractCompactedRow compactedRow = new LazilyCompactedRow(controller, Collections.singletonList(atoms));
-                     if (writer.tryAppend(compactedRow) == null)
-                         emptyRows++;
-                     else
-                         goodRows++;
 -                    if (indexFile != null && dataSize != dataSizeFromIndex)
 -                        outputHandler.warn(String.format("Data file row size %d different from index file row size %d", dataSize, dataSizeFromIndex));
--
-                     prevKey = key;
 -                    if (tryAppend(prevKey, key, dataSize, writer))
++                    if (tryAppend(prevKey, key, writer))
+                         prevKey = key;
                  }
                  catch (Throwable th)
                  {
@@@ -251,22 -242,8 +239,9 @@@
                          try
                          {
                              dataFile.seek(dataStartFromIndex);
 -                            if (tryAppend(prevKey, key, dataSize, writer))
 +
-                             SSTableIdentityIterator atoms = new SSTableIdentityIterator(sstable, dataFile, key, checkData);
-                             if (prevKey != null && prevKey.compareTo(key) > 0)
-                             {
-                                 saveOutOfOrderRow(prevKey, key, atoms);
-                                 continue;
-                             }
- 
-                             @SuppressWarnings("resource")
-                             AbstractCompactedRow compactedRow = new LazilyCompactedRow(controller, Collections.singletonList(atoms));
-                             if (writer.tryAppend(compactedRow) == null)
-                                 emptyRows++;
-                             else
-                                 goodRows++;
- 
-                             prevKey = key;
++                            if (tryAppend(prevKey, key, writer))
+                                 prevKey = key;
                          }
                          catch (Throwable th2)
                          {
@@@ -337,6 -316,32 +312,32 @@@
          }
      }
  
+     @SuppressWarnings("resource")
 -    private boolean tryAppend(DecoratedKey prevKey, DecoratedKey key, long dataSize, SSTableRewriter writer)
++    private boolean tryAppend(DecoratedKey prevKey, DecoratedKey key, 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),
++        OrderCheckerIterator atoms = new OrderCheckerIterator(new SSTableIdentityIterator(sstable, dataFile, key, checkData),
+                                                               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;

http://git-wip-us.apache.org/repos/asf/cassandra/blob/9583b6b3/src/java/org/apache/cassandra/db/marshal/ReversedType.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/db/marshal/ReversedType.java
index 68a7e02,53798f8..19bee5f
--- a/src/java/org/apache/cassandra/db/marshal/ReversedType.java
+++ b/src/java/org/apache/cassandra/db/marshal/ReversedType.java
@@@ -60,23 -58,8 +60,13 @@@ public class ReversedType<T> extends Ab
          this.baseType = baseType;
      }
  
 +    public boolean isEmptyValueMeaningless()
 +    {
 +        return baseType.isEmptyValueMeaningless();
 +    }
 +
      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);
      }