You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by jb...@apache.org on 2012/04/11 20:26:43 UTC

[21/21] git commit: Fix get_paged_slice

Fix get_paged_slice

patch by slebresne; reviewed by jbellis for CASSANDRA-4136


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

Branch: refs/heads/trunk
Commit: fc7e86404a27963071e416ff4deb0c7143e68bfc
Parents: c14e266
Author: Sylvain Lebresne <sy...@datastax.com>
Authored: Wed Apr 11 16:31:36 2012 +0200
Committer: Sylvain Lebresne <sy...@datastax.com>
Committed: Wed Apr 11 16:31:36 2012 +0200

----------------------------------------------------------------------
 CHANGES.txt                                        |    1 +
 .../cassandra/cql3/statements/SelectStatement.java |    3 +-
 .../org/apache/cassandra/db/ColumnFamilyStore.java |   10 +-
 .../org/apache/cassandra/db/RangeSliceCommand.java |   23 +++--
 .../apache/cassandra/db/filter/ExtendedFilter.java |   30 ++++--
 .../cassandra/db/filter/SliceQueryFilter.java      |    3 +-
 .../cassandra/db/index/keys/KeysSearcher.java      |    2 +-
 .../cassandra/service/RangeSliceVerbHandler.java   |    2 +-
 .../org/apache/cassandra/service/StorageProxy.java |    3 +-
 .../apache/cassandra/thrift/CassandraServer.java   |    2 +-
 .../apache/cassandra/db/ColumnFamilyStoreTest.java |   92 +++++++++++++--
 11 files changed, 133 insertions(+), 38 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/fc7e8640/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index 26315be..df030b9 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -12,6 +12,7 @@
  * fix terminination of the stress.java when errors were encountered
    (CASSANDRA-4128)
  * Move CfDef and KsDef validation out of thrift (CASSANDRA-4037)
+ * Fix get_paged_slice (CASSANDRA-4136)
 Merged from 1.0:
  * add auto_snapshot option allowing disabling snapshot before drop/truncate
    (CASSANDRA-3710)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fc7e8640/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 b95d6ba..5bcd37a 100644
--- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
@@ -285,7 +285,8 @@ public class SelectStatement implements CQLStatement
                                                                     bounds,
                                                                     expressions,
                                                                     getLimit(),
-                                                                    true), // limit by columns, not keys
+                                                                    true, // limit by columns, not keys
+                                                                    false),
                                               parameters.consistencyLevel);
         }
         catch (IOException e)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fc7e8640/src/java/org/apache/cassandra/db/ColumnFamilyStore.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/ColumnFamilyStore.java b/src/java/org/apache/cassandra/db/ColumnFamilyStore.java
index cea2fee..a4e2e51 100644
--- a/src/java/org/apache/cassandra/db/ColumnFamilyStore.java
+++ b/src/java/org/apache/cassandra/db/ColumnFamilyStore.java
@@ -1353,12 +1353,12 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
 
     public List<Row> getRangeSlice(ByteBuffer superColumn, final AbstractBounds<RowPosition> range, int maxResults, IFilter columnFilter, List<IndexExpression> rowFilter)
     {
-        return getRangeSlice(superColumn, range, maxResults, columnFilter, rowFilter, false);
+        return getRangeSlice(superColumn, range, maxResults, columnFilter, rowFilter, false, false);
     }
 
-    public List<Row> getRangeSlice(ByteBuffer superColumn, final AbstractBounds<RowPosition> range, int maxResults, IFilter columnFilter, List<IndexExpression> rowFilter, boolean maxIsColumns)
+    public List<Row> getRangeSlice(ByteBuffer superColumn, final AbstractBounds<RowPosition> range, int maxResults, IFilter columnFilter, List<IndexExpression> rowFilter, boolean maxIsColumns, boolean isPaging)
     {
-        return filter(getSequentialIterator(superColumn, range, columnFilter), ExtendedFilter.create(this, columnFilter, rowFilter, maxResults, maxIsColumns));
+        return filter(getSequentialIterator(superColumn, range, columnFilter), ExtendedFilter.create(this, columnFilter, rowFilter, maxResults, maxIsColumns, isPaging));
     }
 
     public List<Row> search(List<IndexExpression> clause, AbstractBounds<RowPosition> range, int maxResults, IFilter dataFilter)
@@ -1404,8 +1404,8 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
                 rows.add(new Row(rawRow.key, data));
                 if (data != null)
                     columnsCount += data.getLiveColumnCount();
-                // Update the underlying filter to avoid querying more columns per slice than necessary
-                filter.updateColumnsLimit(columnsCount);
+                // Update the underlying filter to avoid querying more columns per slice than necessary and to handle paging
+                filter.updateFilter(columnsCount);
             }
             return rows;
         }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fc7e8640/src/java/org/apache/cassandra/db/RangeSliceCommand.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/RangeSliceCommand.java b/src/java/org/apache/cassandra/db/RangeSliceCommand.java
index 80dc719..013dfc5 100644
--- a/src/java/org/apache/cassandra/db/RangeSliceCommand.java
+++ b/src/java/org/apache/cassandra/db/RangeSliceCommand.java
@@ -75,33 +75,34 @@ public class RangeSliceCommand implements MessageProducer, IReadCommand
     public final AbstractBounds<RowPosition> range;
     public final int maxResults;
     public final boolean maxIsColumns;
+    public final boolean isPaging;
 
     public RangeSliceCommand(String keyspace, String column_family, ByteBuffer super_column, SlicePredicate predicate, AbstractBounds<RowPosition> range, int maxResults)
     {
-        this(keyspace, column_family, super_column, predicate, range, null, maxResults, false);
+        this(keyspace, column_family, super_column, predicate, range, null, maxResults, false, false);
     }
 
-    public RangeSliceCommand(String keyspace, String column_family, ByteBuffer super_column, SlicePredicate predicate, AbstractBounds<RowPosition> range, int maxResults, boolean maxIsColumns)
+    public RangeSliceCommand(String keyspace, String column_family, ByteBuffer super_column, SlicePredicate predicate, AbstractBounds<RowPosition> range, int maxResults, boolean maxIsColumns, boolean isPaging)
     {
-        this(keyspace, column_family, super_column, predicate, range, null, maxResults, maxIsColumns);
+        this(keyspace, column_family, super_column, predicate, range, null, maxResults, maxIsColumns, false);
     }
 
     public RangeSliceCommand(String keyspace, ColumnParent column_parent, SlicePredicate predicate, AbstractBounds<RowPosition> range, List<IndexExpression> row_filter, int maxResults)
     {
-        this(keyspace, column_parent.getColumn_family(), column_parent.super_column, predicate, range, row_filter, maxResults, false);
+        this(keyspace, column_parent.getColumn_family(), column_parent.super_column, predicate, range, row_filter, maxResults, false, false);
     }
 
-    public RangeSliceCommand(String keyspace, ColumnParent column_parent, SlicePredicate predicate, AbstractBounds<RowPosition> range, List<IndexExpression> row_filter, int maxResults, boolean maxIsColumns)
+    public RangeSliceCommand(String keyspace, ColumnParent column_parent, SlicePredicate predicate, AbstractBounds<RowPosition> range, List<IndexExpression> row_filter, int maxResults, boolean maxIsColumns, boolean isPaging)
     {
-        this(keyspace, column_parent.getColumn_family(), column_parent.super_column, predicate, range, row_filter, maxResults, maxIsColumns);
+        this(keyspace, column_parent.getColumn_family(), column_parent.super_column, predicate, range, row_filter, maxResults, maxIsColumns, isPaging);
     }
 
     public RangeSliceCommand(String keyspace, String column_family, ByteBuffer super_column, SlicePredicate predicate, AbstractBounds<RowPosition> range, List<IndexExpression> row_filter, int maxResults)
     {
-        this(keyspace, column_family, super_column, predicate, range, row_filter, maxResults, false);
+        this(keyspace, column_family, super_column, predicate, range, row_filter, maxResults, false, false);
     }
 
-    public RangeSliceCommand(String keyspace, String column_family, ByteBuffer super_column, SlicePredicate predicate, AbstractBounds<RowPosition> range, List<IndexExpression> row_filter, int maxResults, boolean maxIsColumns)
+    public RangeSliceCommand(String keyspace, String column_family, ByteBuffer super_column, SlicePredicate predicate, AbstractBounds<RowPosition> range, List<IndexExpression> row_filter, int maxResults, boolean maxIsColumns, boolean isPaging)
     {
         this.keyspace = keyspace;
         this.column_family = column_family;
@@ -111,6 +112,7 @@ public class RangeSliceCommand implements MessageProducer, IReadCommand
         this.row_filter = row_filter;
         this.maxResults = maxResults;
         this.maxIsColumns = maxIsColumns;
+        this.isPaging = isPaging;
     }
 
     public Message getMessage(Integer version) throws IOException
@@ -182,6 +184,7 @@ class RangeSliceCommandSerializer implements IVersionedSerializer<RangeSliceComm
         if (version >= MessagingService.VERSION_11)
         {
             dos.writeBoolean(sliceCommand.maxIsColumns);
+            dos.writeBoolean(sliceCommand.isPaging);
         }
     }
 
@@ -219,11 +222,13 @@ class RangeSliceCommandSerializer implements IVersionedSerializer<RangeSliceComm
 
         int maxResults = dis.readInt();
         boolean maxIsColumns = false;
+        boolean isPaging = false;
         if (version >= MessagingService.VERSION_11)
         {
             maxIsColumns = dis.readBoolean();
+            isPaging = dis.readBoolean();
         }
-        return new RangeSliceCommand(keyspace, columnFamily, superColumn, pred, range, rowFilter, maxResults, maxIsColumns);
+        return new RangeSliceCommand(keyspace, columnFamily, superColumn, pred, range, rowFilter, maxResults, maxIsColumns, isPaging);
     }
 
     public long serializedSize(RangeSliceCommand rangeSliceCommand, int version)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fc7e8640/src/java/org/apache/cassandra/db/filter/ExtendedFilter.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/filter/ExtendedFilter.java b/src/java/org/apache/cassandra/db/filter/ExtendedFilter.java
index df55c25..4d620f8 100644
--- a/src/java/org/apache/cassandra/db/filter/ExtendedFilter.java
+++ b/src/java/org/apache/cassandra/db/filter/ExtendedFilter.java
@@ -47,16 +47,23 @@ public abstract class ExtendedFilter
     protected final IFilter originalFilter;
     private final int maxResults;
     private final boolean maxIsColumns;
+    private final boolean isPaging;
 
-    public static ExtendedFilter create(ColumnFamilyStore cfs, IFilter filter, List<IndexExpression> clause, int maxResults, boolean maxIsColumns)
+    public static ExtendedFilter create(ColumnFamilyStore cfs, IFilter filter, List<IndexExpression> clause, int maxResults, boolean maxIsColumns, boolean isPaging)
     {
         if (clause == null || clause.isEmpty())
-            return new EmptyClauseFilter(cfs, filter, maxResults, maxIsColumns);
+        {
+            return new EmptyClauseFilter(cfs, filter, maxResults, maxIsColumns, isPaging);
+        }
         else
+        {
+            if (isPaging)
+                throw new IllegalArgumentException("Cross-row paging is not supported along with index clauses");
             return new FilterWithClauses(cfs, filter, clause, maxResults, maxIsColumns);
+        }
     }
 
-    protected ExtendedFilter(ColumnFamilyStore cfs, IFilter filter, int maxResults, boolean maxIsColumns)
+    protected ExtendedFilter(ColumnFamilyStore cfs, IFilter filter, int maxResults, boolean maxIsColumns, boolean isPaging)
     {
         assert cfs != null;
         assert filter != null;
@@ -64,8 +71,11 @@ public abstract class ExtendedFilter
         this.originalFilter = filter;
         this.maxResults = maxResults;
         this.maxIsColumns = maxIsColumns;
+        this.isPaging = isPaging;
         if (maxIsColumns)
             originalFilter.updateColumnsLimit(maxResults);
+        if (isPaging && (!(originalFilter instanceof SliceQueryFilter) || ((SliceQueryFilter)originalFilter).finish.remaining() != 0))
+            throw new IllegalArgumentException("Cross-row paging is only supported for SliceQueryFilter having an empty finish column");
     }
 
     public int maxRows()
@@ -82,12 +92,16 @@ public abstract class ExtendedFilter
      * Update the filter if necessary given the number of column already
      * fetched.
      */
-    public void updateColumnsLimit(int columnsCount)
+    public void updateFilter(int currentColumnsCount)
     {
+        // As soon as we'd done our first call, we want to reset the start column if we're paging
+        if (isPaging)
+            ((SliceQueryFilter)initialFilter()).start = ByteBufferUtil.EMPTY_BYTE_BUFFER;
+
         if (!maxIsColumns)
             return;
 
-        int remaining = maxResults - columnsCount;
+        int remaining = maxResults - currentColumnsCount;
         initialFilter().updateColumnsLimit(remaining);
     }
 
@@ -140,7 +154,7 @@ public abstract class ExtendedFilter
 
         public FilterWithClauses(ColumnFamilyStore cfs, IFilter filter, List<IndexExpression> clause, int maxResults, boolean maxIsColumns)
         {
-            super(cfs, filter, maxResults, maxIsColumns);
+            super(cfs, filter, maxResults, maxIsColumns, false);
             assert clause != null;
             this.clause = clause;
             this.initialFilter = computeInitialFilter();
@@ -265,9 +279,9 @@ public abstract class ExtendedFilter
 
     private static class EmptyClauseFilter extends ExtendedFilter
     {
-        public EmptyClauseFilter(ColumnFamilyStore cfs, IFilter filter, int maxResults, boolean maxIsColumns)
+        public EmptyClauseFilter(ColumnFamilyStore cfs, IFilter filter, int maxResults, boolean maxIsColumns, boolean isPaging)
         {
-            super(cfs, filter, maxResults, maxIsColumns);
+            super(cfs, filter, maxResults, maxIsColumns, isPaging);
         }
 
         public IFilter initialFilter()

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fc7e8640/src/java/org/apache/cassandra/db/filter/SliceQueryFilter.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/filter/SliceQueryFilter.java b/src/java/org/apache/cassandra/db/filter/SliceQueryFilter.java
index e6372c2..1a4a912 100644
--- a/src/java/org/apache/cassandra/db/filter/SliceQueryFilter.java
+++ b/src/java/org/apache/cassandra/db/filter/SliceQueryFilter.java
@@ -43,7 +43,8 @@ public class SliceQueryFilter implements IFilter
 {
     private static Logger logger = LoggerFactory.getLogger(SliceQueryFilter.class);
 
-    public final ByteBuffer start; public final ByteBuffer finish;
+    public volatile ByteBuffer start;
+    public volatile ByteBuffer finish;
     public final boolean reversed;
     public volatile int count;
 

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fc7e8640/src/java/org/apache/cassandra/db/index/keys/KeysSearcher.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/index/keys/KeysSearcher.java b/src/java/org/apache/cassandra/db/index/keys/KeysSearcher.java
index 686f810..a66d040 100644
--- a/src/java/org/apache/cassandra/db/index/keys/KeysSearcher.java
+++ b/src/java/org/apache/cassandra/db/index/keys/KeysSearcher.java
@@ -84,7 +84,7 @@ public class KeysSearcher extends SecondaryIndexSearcher
     public List<Row> search(List<IndexExpression> clause, AbstractBounds<RowPosition> range, int maxResults, IFilter dataFilter, boolean maxIsColumns)
     {
         assert clause != null && !clause.isEmpty();
-        ExtendedFilter filter = ExtendedFilter.create(baseCfs, dataFilter, clause, maxResults, maxIsColumns);
+        ExtendedFilter filter = ExtendedFilter.create(baseCfs, dataFilter, clause, maxResults, maxIsColumns, false);
         return baseCfs.filter(getIndexedIterator(range, filter), filter);
     }
 

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fc7e8640/src/java/org/apache/cassandra/service/RangeSliceVerbHandler.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/service/RangeSliceVerbHandler.java b/src/java/org/apache/cassandra/service/RangeSliceVerbHandler.java
index 76823de..ad085ba 100644
--- a/src/java/org/apache/cassandra/service/RangeSliceVerbHandler.java
+++ b/src/java/org/apache/cassandra/service/RangeSliceVerbHandler.java
@@ -47,7 +47,7 @@ public class RangeSliceVerbHandler implements IVerbHandler
         if (cfs.indexManager.hasIndexFor(command.row_filter))
             return cfs.search(command.row_filter, command.range, command.maxResults, columnFilter, command.maxIsColumns);
         else
-            return cfs.getRangeSlice(command.super_column, command.range, command.maxResults, columnFilter, command.row_filter, command.maxIsColumns);
+            return cfs.getRangeSlice(command.super_column, command.range, command.maxResults, columnFilter, command.row_filter, command.maxIsColumns, command.isPaging);
     }
 
     public void doVerb(Message message, String id)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fc7e8640/src/java/org/apache/cassandra/service/StorageProxy.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/service/StorageProxy.java b/src/java/org/apache/cassandra/service/StorageProxy.java
index 802a477..cd1a1eb 100644
--- a/src/java/org/apache/cassandra/service/StorageProxy.java
+++ b/src/java/org/apache/cassandra/service/StorageProxy.java
@@ -851,7 +851,8 @@ public class StorageProxy implements StorageProxyMBean
                                                                   range,
                                                                   command.row_filter,
                                                                   command.maxResults,
-                                                                  command.maxIsColumns);
+                                                                  command.maxIsColumns,
+                                                                  command.isPaging);
 
                 List<InetAddress> liveEndpoints = StorageService.instance.getLiveNaturalEndpoints(nodeCmd.keyspace, range.right);
                 DatabaseDescriptor.getEndpointSnitch().sortByProximity(FBUtilities.getBroadcastAddress(), liveEndpoints);

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fc7e8640/src/java/org/apache/cassandra/thrift/CassandraServer.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/thrift/CassandraServer.java b/src/java/org/apache/cassandra/thrift/CassandraServer.java
index 61a3233..7aceb0e 100644
--- a/src/java/org/apache/cassandra/thrift/CassandraServer.java
+++ b/src/java/org/apache/cassandra/thrift/CassandraServer.java
@@ -749,7 +749,7 @@ public class CassandraServer implements Cassandra.Iface
             schedule(DatabaseDescriptor.getRpcTimeout());
             try
             {
-                rows = StorageProxy.getRangeSlice(new RangeSliceCommand(keyspace, column_family, null, predicate, bounds, range.row_filter, range.count, true), consistency_level);
+                rows = StorageProxy.getRangeSlice(new RangeSliceCommand(keyspace, column_family, null, predicate, bounds, range.row_filter, range.count, true, true), consistency_level);
             }
             finally
             {

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fc7e8640/test/unit/org/apache/cassandra/db/ColumnFamilyStoreTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/db/ColumnFamilyStoreTest.java b/test/unit/org/apache/cassandra/db/ColumnFamilyStoreTest.java
index e3fed41..3f40464 100644
--- a/test/unit/org/apache/cassandra/db/ColumnFamilyStoreTest.java
+++ b/test/unit/org/apache/cassandra/db/ColumnFamilyStoreTest.java
@@ -820,11 +820,11 @@ public class ColumnFamilyStoreTest extends SchemaLoader
         sp.getSlice_range().setStart(ArrayUtils.EMPTY_BYTE_ARRAY);
         sp.getSlice_range().setFinish(ArrayUtils.EMPTY_BYTE_ARRAY);
 
-        assertTotalColCount(cfs.getRangeSlice(null, Util.range("", ""), 3, QueryFilter.getFilter(sp, cfs.getComparator()), null, true), 3);
-        assertTotalColCount(cfs.getRangeSlice(null, Util.range("", ""), 5, QueryFilter.getFilter(sp, cfs.getComparator()), null, true), 5);
-        assertTotalColCount(cfs.getRangeSlice(null, Util.range("", ""), 8, QueryFilter.getFilter(sp, cfs.getComparator()), null, true), 8);
-        assertTotalColCount(cfs.getRangeSlice(null, Util.range("", ""), 10, QueryFilter.getFilter(sp, cfs.getComparator()), null, true), 10);
-        assertTotalColCount(cfs.getRangeSlice(null, Util.range("", ""), 100, QueryFilter.getFilter(sp, cfs.getComparator()), null, true), 11);
+        assertTotalColCount(cfs.getRangeSlice(null, Util.range("", ""), 3, QueryFilter.getFilter(sp, cfs.getComparator()), null, true, false), 3);
+        assertTotalColCount(cfs.getRangeSlice(null, Util.range("", ""), 5, QueryFilter.getFilter(sp, cfs.getComparator()), null, true, false), 5);
+        assertTotalColCount(cfs.getRangeSlice(null, Util.range("", ""), 8, QueryFilter.getFilter(sp, cfs.getComparator()), null, true, false), 8);
+        assertTotalColCount(cfs.getRangeSlice(null, Util.range("", ""), 10, QueryFilter.getFilter(sp, cfs.getComparator()), null, true, false), 10);
+        assertTotalColCount(cfs.getRangeSlice(null, Util.range("", ""), 100, QueryFilter.getFilter(sp, cfs.getComparator()), null, true, false), 11);
 
         // Check that when querying by name, we always include all names for a
         // gien row even if it means returning more columns than requested (this is necesseray for CQL)
@@ -835,11 +835,83 @@ public class ColumnFamilyStoreTest extends SchemaLoader
             ByteBufferUtil.bytes("c2")
         ));
 
-        assertTotalColCount(cfs.getRangeSlice(null, Util.range("", ""), 1, QueryFilter.getFilter(sp, cfs.getComparator()), null, true), 3);
-        assertTotalColCount(cfs.getRangeSlice(null, Util.range("", ""), 4, QueryFilter.getFilter(sp, cfs.getComparator()), null, true), 5);
-        assertTotalColCount(cfs.getRangeSlice(null, Util.range("", ""), 5, QueryFilter.getFilter(sp, cfs.getComparator()), null, true), 5);
-        assertTotalColCount(cfs.getRangeSlice(null, Util.range("", ""), 6, QueryFilter.getFilter(sp, cfs.getComparator()), null, true), 8);
-        assertTotalColCount(cfs.getRangeSlice(null, Util.range("", ""), 100, QueryFilter.getFilter(sp, cfs.getComparator()), null, true), 8);
+        assertTotalColCount(cfs.getRangeSlice(null, Util.range("", ""), 1, QueryFilter.getFilter(sp, cfs.getComparator()), null, true, false), 3);
+        assertTotalColCount(cfs.getRangeSlice(null, Util.range("", ""), 4, QueryFilter.getFilter(sp, cfs.getComparator()), null, true, false), 5);
+        assertTotalColCount(cfs.getRangeSlice(null, Util.range("", ""), 5, QueryFilter.getFilter(sp, cfs.getComparator()), null, true, false), 5);
+        assertTotalColCount(cfs.getRangeSlice(null, Util.range("", ""), 6, QueryFilter.getFilter(sp, cfs.getComparator()), null, true, false), 8);
+        assertTotalColCount(cfs.getRangeSlice(null, Util.range("", ""), 100, QueryFilter.getFilter(sp, cfs.getComparator()), null, true, false), 8);
+    }
+
+    @Test
+    public void testRangeSlicePaging() throws Throwable
+    {
+        String tableName = "Keyspace1";
+        String cfName = "Standard1";
+        Table table = Table.open(tableName);
+        ColumnFamilyStore cfs = table.getColumnFamilyStore(cfName);
+        cfs.clearUnsafe();
+
+        Column[] cols = new Column[4];
+        for (int i = 0; i < 4; i++)
+            cols[i] = column("c" + i, "value", 1);
+
+        putColsStandard(cfs, Util.dk("a"), cols[0], cols[1], cols[2], cols[3]);
+        putColsStandard(cfs, Util.dk("b"), cols[0], cols[1], cols[2]);
+        putColsStandard(cfs, Util.dk("c"), cols[0], cols[1], cols[2], cols[3]);
+        cfs.forceBlockingFlush();
+
+        SlicePredicate sp = new SlicePredicate();
+        sp.setSlice_range(new SliceRange());
+        sp.getSlice_range().setCount(1);
+        sp.getSlice_range().setStart(ArrayUtils.EMPTY_BYTE_ARRAY);
+        sp.getSlice_range().setFinish(ArrayUtils.EMPTY_BYTE_ARRAY);
+
+        Collection<Row> rows = cfs.getRangeSlice(null, Util.range("", ""), 3, QueryFilter.getFilter(sp, cfs.getComparator()), null, true, true);
+        assert rows.size() == 1 : "Expected 1 row, got " + rows;
+        Row row = rows.iterator().next();
+        assertColumnNames(row, "c0", "c1", "c2");
+
+        sp.getSlice_range().setStart(ByteBufferUtil.getArray(ByteBufferUtil.bytes("c2")));
+        rows = cfs.getRangeSlice(null, Util.range("", ""), 3, QueryFilter.getFilter(sp, cfs.getComparator()), null, true, true);
+        assert rows.size() == 2 : "Expected 2 rows, got " + rows;
+        Iterator<Row> iter = rows.iterator();
+        Row row1 = iter.next();
+        Row row2 = iter.next();
+        assertColumnNames(row1, "c2", "c3");
+        assertColumnNames(row2, "c0");
+
+        sp.getSlice_range().setStart(ByteBufferUtil.getArray(ByteBufferUtil.bytes("c0")));
+        rows = cfs.getRangeSlice(null, new Bounds<RowPosition>(row2.key, Util.rp("")), 3, QueryFilter.getFilter(sp, cfs.getComparator()), null, true, true);
+        assert rows.size() == 1 : "Expected 1 row, got " + rows;
+        row = rows.iterator().next();
+        assertColumnNames(row, "c0", "c1", "c2");
+
+        sp.getSlice_range().setStart(ByteBufferUtil.getArray(ByteBufferUtil.bytes("c2")));
+        rows = cfs.getRangeSlice(null, new Bounds<RowPosition>(row.key, Util.rp("")), 3, QueryFilter.getFilter(sp, cfs.getComparator()), null, true, true);
+        assert rows.size() == 2 : "Expected 2 rows, got " + rows;
+        iter = rows.iterator();
+        row1 = iter.next();
+        row2 = iter.next();
+        assertColumnNames(row1, "c2");
+        assertColumnNames(row2, "c0", "c1");
+    }
+
+    private static void assertColumnNames(Row row, String ... columnNames) throws Exception
+    {
+        if (row == null || row.cf == null)
+            throw new AssertionError("The row should not be empty");
+
+        Iterator<IColumn> columns = row.cf.getSortedColumns().iterator();
+        Iterator<String> names = Arrays.asList(columnNames).iterator();
+
+        while (columns.hasNext())
+        {
+            IColumn c = columns.next();
+            assert names.hasNext() : "Got more columns that expected (first unexpected column: " + ByteBufferUtil.string(c.name()) + ")";
+            String n = names.next();
+            assert c.name().equals(ByteBufferUtil.bytes(n)) : "Expected " + n + ", got " + ByteBufferUtil.string(c.name());
+        }
+        assert !names.hasNext() : "Missing expected column " + names.next();
     }
 
     private static DecoratedKey idk(int i)