diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ClientScanner.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ClientScanner.java index b11a841..dffa536 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ClientScanner.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ClientScanner.java @@ -17,6 +17,7 @@ */ package org.apache.hadoop.hbase.client; +import static org.apache.hadoop.hbase.HConstants.EMPTY_START_ROW; import static org.apache.hadoop.hbase.client.ConnectionUtils.calcEstimatedSize; import static org.apache.hadoop.hbase.client.ConnectionUtils.numberOfIndividualRows; @@ -433,6 +434,10 @@ public abstract class ClientScanner extends AbstractClientScanner { if (closed) { return; } + // corner case handling: reverse scan with empty start row + if (scan.isReversed() && Bytes.equals(scan.getStartRow(), EMPTY_START_ROW)) { + return; + } long remainingResultSize = maxScannerResultSize; int countdown = this.caching; // This is possible if we just stopped at the boundary of a region in the previous call. diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ClientSimpleScanner.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ClientSimpleScanner.java index b219531..303206b 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ClientSimpleScanner.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ClientSimpleScanner.java @@ -30,7 +30,7 @@ import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.ipc.RpcControllerFactory; /** - * ClientSimpleScanner implements a sync scanner behaviour. + * ClientSimpleScanner implements a sync scanner behavior. * The cache is a simple list. * The prefetch is invoked only when the application finished processing the entire cache. */ diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ConnectionUtils.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ConnectionUtils.java index e010e9a..ba93351 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ConnectionUtils.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ConnectionUtils.java @@ -366,6 +366,9 @@ public final class ConnectionUtils { } static boolean noMoreResultsForReverseScan(Scan scan, HRegionInfo info) { + if (isEmptyStartRow(scan.getStartRow())) { + return true; + } if (isEmptyStartRow(info.getStartKey())) { return true; } diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestFromClientSide3.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestFromClientSide3.java index 3680822..024f2fb 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestFromClientSide3.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestFromClientSide3.java @@ -700,7 +700,7 @@ public class TestFromClientSide3 { /** * A test case for issue HBASE-17482 - * After combile seqid with mvcc readpoint, seqid/mvcc is acquired and stamped + * After combining seqid with mvcc readpoint, seqid/mvcc is acquired and stamped * onto cells in the append thread, a countdown latch is used to ensure that happened * before cells can be put into memstore. But the MVCCPreAssign patch(HBASE-16698) * make the seqid/mvcc acquirement in handler thread and stamping in append thread @@ -805,6 +805,36 @@ public class TestFromClientSide3 { } } + @Test + public void testReversedScanWithEmptyStartRow() throws Exception { + // preparation: create table and put some data + final TableName tableName = TableName.valueOf(name.getMethodName()); + HTableDescriptor htd = new HTableDescriptor(tableName); + HColumnDescriptor fam = new HColumnDescriptor(FAMILY); + htd.addFamily(fam); + Admin admin = TEST_UTIL.getAdmin(); + admin.createTable(htd); + Table table = admin.getConnection().getTable(TableName.valueOf(name.getMethodName())); + byte[] row = Bytes.toBytes("ROW"); + byte[] value = Bytes.toBytes("VALUE"); + Put put = new Put(row); + put.addColumn(FAMILY, QUALIFIER, value); + table.put(put); + // do reverse scan + Scan emptyRowScan = new Scan(); + emptyRowScan.withStartRow(HConstants.EMPTY_START_ROW); + emptyRowScan.setReversed(true); + ResultScanner scanner = table.getScanner(emptyRowScan); + Result result; + int count = 0; + while ((result = scanner.next()) != null) { + LOG.debug("Result: " + result); + count++; + } + + assertEquals(0, count); + } + private static void assertNoLocks(final TableName tableName) throws IOException, InterruptedException { HRegion region = (HRegion) find(tableName); assertEquals(0, region.getLockedRows().size());