diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/filter/FuzzyRowFilter.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/filter/FuzzyRowFilter.java index 006ddc8f1c30..4c4923c6cd9f 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/filter/FuzzyRowFilter.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/filter/FuzzyRowFilter.java @@ -644,9 +644,13 @@ static byte[] getNextForFuzzyRule(boolean reverse, byte[] row, int offset, int l byte[] trailingZerosTrimmed = trimTrailingZeroes(result, fuzzyKeyMeta, toInc); if (reverse) { - // In the reverse case we increase last non-max byte to make sure that the proper row is - // selected next. - return PrivateCellUtil.increaseLastNonMaxByte(trailingZerosTrimmed); + // In the reverse case we usually increase last non-max byte to make sure that the proper row + // is selected next. + byte[] nextRowKeyCandidate = PrivateCellUtil.increaseLastNonMaxByte(trailingZerosTrimmed); + // If the adjusted hint is the current row, return the unadjusted candidate so the hint moves. + return Bytes.equals(row, offset, length, nextRowKeyCandidate, 0, nextRowKeyCandidate.length) + ? trailingZerosTrimmed + : nextRowKeyCandidate; } else { return trailingZerosTrimmed; } diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/filter/TestFuzzyRowFilter.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/filter/TestFuzzyRowFilter.java index 3a280d843046..0c5dc41de6be 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/filter/TestFuzzyRowFilter.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/filter/TestFuzzyRowFilter.java @@ -265,7 +265,7 @@ public void testGetNextForFuzzyRuleReverse() { assertNext(true, new byte[] { 0, 1, 0, 0 }, // fuzzy row new byte[] { 0, -1, 0, 0 }, // mask new byte[] { 5, 1, (byte) 255, 1 }, // current - new byte[] { 5, 1, (byte) 255, 1 }); // expected next + new byte[] { 5, 1, (byte) 255, 0 }); // expected next assertNext(true, new byte[] { 0, 1, 0, 1 }, // fuzzy row new byte[] { 0, -1, 0, -1 }, // mask @@ -305,27 +305,27 @@ public void testGetNextForFuzzyRuleReverse() { assertNext(true, new byte[] { 1, 0, 1 }, // fuzzy row new byte[] { -1, 0, -1 }, // mask new byte[] { 1, (byte) 128, 2 }, // row to check - new byte[] { 1, (byte) 128, 2 }); // expected next + new byte[] { 1, (byte) 128, 1 }); // expected next assertNext(true, new byte[] { 0, 1, 0, 1 }, // fuzzy row new byte[] { 0, -1, 0, -1 }, // mask new byte[] { 5, 1, 0, 2 }, // row to check - new byte[] { 5, 1, 0, 2 }); // expected next + new byte[] { 5, 1, 0, 1 }); // expected next assertNext(true, new byte[] { 5, 1, 1, 0 }, // fuzzy row new byte[] { -1, -1, 0, 0 }, // mask new byte[] { 5, 1, (byte) 0xFF, 1 }, // row to check - new byte[] { 5, 1, (byte) 0xFF, 1 }); // expected next + new byte[] { 5, 1, (byte) 0xFF, 0 }); // expected next assertNext(true, new byte[] { 1, 1, 1, 1 }, // fuzzy row new byte[] { -1, -1, 0, 0 }, // mask new byte[] { 1, 1, 2, 2 }, // row to check - new byte[] { 1, 1, 2, 2 }); // expected next + new byte[] { 1, 1, 2, 1 }); // expected next assertNext(true, new byte[] { 1, 1, 1, 1 }, // fuzzy row new byte[] { 0, 0, 0, 0 }, // mask new byte[] { 1, 1, 2, 3 }, // row to check - new byte[] { 1, 1, 2, 3 }); // expected next + new byte[] { 1, 1, 2, 2 }); // expected next // no before cell than current which satisfies the fuzzy row -> null assertNull(FuzzyRowFilter.getNextForFuzzyRule(true, new byte[] { 1, 1, 1, 3, 0 }, diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/filter/TestFuzzyRowFilterEndToEnd.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/filter/TestFuzzyRowFilterEndToEnd.java index a3b99a6db65f..a0aa7fa4f5c7 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/filter/TestFuzzyRowFilterEndToEnd.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/filter/TestFuzzyRowFilterEndToEnd.java @@ -18,6 +18,7 @@ package org.apache.hadoop.hbase.filter; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import java.io.IOException; @@ -330,6 +331,47 @@ public void testHBASE26967(TestInfo testInfo) throws IOException { TEST_UTIL.deleteTable(TableName.valueOf(name)); } + @Test + public void testReverseScanMovesPastSameRowFuzzyHint(TestInfo testInfo) throws IOException { + final String cf = "f"; + final String cq = "q"; + + String name = testInfo.getTestMethod().orElseThrow(AssertionError::new).getName(); + try (Table ht = TEST_UTIL.createTable(TableName.valueOf(name), Bytes.toBytes(cf))) { + List puts = Lists.newArrayList(); + puts.add(new Put(Bytes.toBytes("aaa")).addColumn(Bytes.toBytes(cf), Bytes.toBytes(cq), + Bytes.toBytes("v"))); + puts.add(new Put(Bytes.toBytes("aba")).addColumn(Bytes.toBytes(cf), Bytes.toBytes(cq), + Bytes.toBytes("v"))); + puts.add(new Put(Bytes.toBytes("abb")).addColumn(Bytes.toBytes(cf), Bytes.toBytes(cq), + Bytes.toBytes("v"))); + puts.add(new Put(Bytes.toBytes("abc")).addColumn(Bytes.toBytes(cf), Bytes.toBytes(cq), + Bytes.toBytes("v"))); + ht.put(puts); + + TEST_UTIL.flush(); + + List> fuzzyList = new LinkedList<>(); + fuzzyList.add(new Pair<>(Bytes.toBytes("aaa"), new byte[] { 0, 1, 0 })); + + Scan scan = new Scan(); + scan.setReversed(true); + scan.setFilter(new FuzzyRowFilter(fuzzyList)); + + try (ResultScanner scanner = ht.getScanner(scan)) { + Result result = scanner.next(); + assertNotNull(result); + assertEquals("aba", Bytes.toString(result.getRow())); + result = scanner.next(); + assertNotNull(result); + assertEquals("aaa", Bytes.toString(result.getRow())); + assertNull(scanner.next()); + } + } + + TEST_UTIL.deleteTable(TableName.valueOf(name)); + } + @Test public void testHBASE28634(TestInfo testInfo) throws IOException { final String CF = "f";