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 <sylv...@datastax.com> Authored: Wed Apr 11 16:31:36 2012 +0200 Committer: Sylvain Lebresne <sylv...@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)