diff --git a/src/java/org/apache/hadoop/hbase/regionserver/QueryMatcher.java b/src/java/org/apache/hadoop/hbase/regionserver/QueryMatcher.java index a0ba336..66bd6f1 100644 --- a/src/java/org/apache/hadoop/hbase/regionserver/QueryMatcher.java +++ b/src/java/org/apache/hadoop/hbase/regionserver/QueryMatcher.java @@ -231,8 +231,10 @@ public class QueryMatcher { */ long timestamp = Bytes.toLong(bytes, offset); if(isExpired(timestamp)) { - // reached the expired part, for scans, this indicates we're done. - return MatchCode.NEXT; // done_row + /* KeyValue is expired, skip but don't early out since a non-expired + * kv could come next. + */ + return MatchCode.SKIP; // go to next kv } offset += Bytes.SIZEOF_LONG; diff --git a/src/java/org/apache/hadoop/hbase/regionserver/ScanQueryMatcher.java b/src/java/org/apache/hadoop/hbase/regionserver/ScanQueryMatcher.java index 92a00fc..0ea290f 100644 --- a/src/java/org/apache/hadoop/hbase/regionserver/ScanQueryMatcher.java +++ b/src/java/org/apache/hadoop/hbase/regionserver/ScanQueryMatcher.java @@ -122,9 +122,8 @@ public class ScanQueryMatcher extends QueryMatcher { long timestamp = kv.getTimestamp(); if (isExpired(timestamp)) { - // done, the rest wil also be expired as well. - stickyNextRow = true; - return MatchCode.SEEK_NEXT_ROW; + // done, the rest of this column will also be expired as well. + return MatchCode.SEEK_NEXT_COL; } byte type = kv.getType(); diff --git a/src/test/org/apache/hadoop/hbase/regionserver/TestQueryMatcher.java b/src/test/org/apache/hadoop/hbase/regionserver/TestQueryMatcher.java index 4cce3bf..564ea2d 100644 --- a/src/test/org/apache/hadoop/hbase/regionserver/TestQueryMatcher.java +++ b/src/test/org/apache/hadoop/hbase/regionserver/TestQueryMatcher.java @@ -27,6 +27,7 @@ import java.util.List; import org.apache.hadoop.hbase.HBaseTestCase; import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.KeyValue; +import org.apache.hadoop.hbase.KeyValueTestUtil; import org.apache.hadoop.hbase.KeyValue.KeyComparator; import org.apache.hadoop.hbase.client.Get; import org.apache.hadoop.hbase.regionserver.QueryMatcher.MatchCode; @@ -162,4 +163,103 @@ implements HConstants { } } + + /** + * Verify that {@link QueryMatcher} only skips expired KeyValue + * instances and does not exit early from the row (skipping + * later non-expired KeyValues). This version mimics a Get with + * explicitly specified column qualifiers. + * + * @throws IOException + */ + public void testMatch_ExpiredExplicit() + throws IOException { + + long testTTL = 1000; + MatchCode [] expected = new MatchCode[] { + MatchCode.SKIP, + MatchCode.INCLUDE, + MatchCode.SKIP, + MatchCode.INCLUDE, + MatchCode.SKIP, + MatchCode.NEXT + }; + + QueryMatcher qm = new QueryMatcher(get, fam2, + get.getFamilyMap().get(fam2), testTTL, rowComparator, 1); + + long now = System.currentTimeMillis(); + KeyValue [] kvs = new KeyValue[] { + new KeyValue(row1, fam2, col1, now-100, data), + new KeyValue(row1, fam2, col2, now-50, data), + new KeyValue(row1, fam2, col3, now-5000, data), + new KeyValue(row1, fam2, col4, now-500, data), + new KeyValue(row1, fam2, col5, now-10000, data), + new KeyValue(row2, fam1, col1, now-10, data) + }; + + List actual = new ArrayList(kvs.length); + for (KeyValue kv : kvs) { + actual.add( qm.match(kv) ); + } + + assertEquals(expected.length, actual.size()); + for (int i=0; i actual = new ArrayList(kvs.length); + for (KeyValue kv : kvs) { + actual.add( qm.match(kv) ); + } + + assertEquals(expected.length, actual.size()); + for (int i=0; i results = new ArrayList(); + assertEquals(true, scanner.next(results)); + assertEquals(2, results.size()); + assertEquals(kvs[1], results.get(0)); + assertEquals(kvs[2], results.get(1)); + results.clear(); + + assertEquals(true, scanner.next(results)); + assertEquals(3, results.size()); + assertEquals(kvs[4], results.get(0)); + assertEquals(kvs[5], results.get(1)); + assertEquals(kvs[6], results.get(2)); + results.clear(); + + assertEquals(false, scanner.next(results)); + } }