Copilot commented on code in PR #8360:
URL: https://github.com/apache/hbase/pull/8360#discussion_r3426743288


##########
hbase-client/src/main/java/org/apache/hadoop/hbase/filter/FuzzyRowFilter.java:
##########
@@ -216,9 +216,14 @@ public ReturnCode filterCell(final Cell c) {
         return ReturnCode.INCLUDE;
       }
     }
-    // NOT FOUND -> seek next using hint
+    // NOT FOUND -> seek next using hint or skip the current row.
     lastFoundIndex = -1;
     filterRow = true;
+    // For reverse scans, a non-matching row can recreate itself as the next 
hint. Since fuzzy
+    // matching is row-key based, skip the whole non-matching row instead of 
seeking to it again.
+    if (isReversed() && tracker.updateTracker(c) && 
tracker.isNextRowSameAs(c)) {
+      return ReturnCode.NEXT_ROW;
+    }
     return ReturnCode.SEEK_NEXT_USING_HINT;

Review Comment:
   `tracker.updateTracker(...)` is now invoked in `filterCell` (reverse 
non-match path) and will be invoked again in `getNextCellHint` for the common 
`SEEK_NEXT_USING_HINT` flow. Since `updateTracker` has side effects and can be 
non-trivial, consider avoiding double-updating per cell by caching/memoizing 
the tracker update result for the current row/cell (e.g., track the last row 
processed and whether the queue is already updated) or by refactoring so only 
`getNextCellHint` performs the update.



##########
hbase-client/src/main/java/org/apache/hadoop/hbase/filter/FuzzyRowFilter.java:
##########
@@ -231,6 +236,12 @@ public Cell getNextCellHint(Cell currentCell) {
       return null;
     }
     byte[] nextRowKey = tracker.nextRow();
+    if (isReversed() && !tracker.lessThan(currentCell, nextRowKey)) {
+      // filterCell normally handles same-row reverse hints with NEXT_ROW. If 
a non-progressing
+      // hint still reaches here, keep the current-row boundary to avoid 
skipping matching rows
+      // under it, but return a non-seeking hint so StoreScanner advances 
normally.
+      return PrivateCellUtil.createLastOnRow(currentCell);

Review Comment:
   The comment says “return a non-seeking hint”, but `SEEK_NEXT_USING_HINT` 
still causes the scanner to reseek—this hint is just constrained to the current 
row (last cell) to force progress. Suggest rewording to avoid ambiguity (e.g., 
“return a hint within the current row (last cell) so the scanner can advance 
past the row”).



##########
hbase-server/src/test/java/org/apache/hadoop/hbase/filter/TestFuzzyRowFilterEndToEnd.java:
##########
@@ -330,6 +331,103 @@ 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<Put> 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("abaa")).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<Pair<byte[], byte[]>> 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("abaa", Bytes.toString(result.getRow()));
+        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));

Review Comment:
   `deleteTable(...)` is executed after the try-with-resources block, so it 
won’t run if an assertion or scanner operation throws inside the test. To 
ensure cleanup even on failure (and reduce potential cascading 
failures/resource buildup in the mini-cluster), wrap `deleteTable` in a 
`finally` or use a test rule/extension that guarantees table cleanup.



##########
hbase-server/src/test/java/org/apache/hadoop/hbase/filter/TestFuzzyRowFilterEndToEnd.java:
##########
@@ -330,6 +331,103 @@ 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))) {

Review Comment:
   `deleteTable(...)` is executed after the try-with-resources block, so it 
won’t run if an assertion or scanner operation throws inside the test. To 
ensure cleanup even on failure (and reduce potential cascading 
failures/resource buildup in the mini-cluster), wrap `deleteTable` in a 
`finally` or use a test rule/extension that guarantees table cleanup.



##########
hbase-server/src/test/java/org/apache/hadoop/hbase/filter/TestFuzzyRowFilterEndToEnd.java:
##########
@@ -330,6 +331,103 @@ 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<Put> 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("abaa")).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<Pair<byte[], byte[]>> 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("abaa", Bytes.toString(result.getRow()));
+        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 
testReverseScanMovesPastSameRowFuzzyHintAcrossMultipleCells(TestInfo testInfo)
+    throws IOException {
+    final String cf = "f";
+
+    String name = 
testInfo.getTestMethod().orElseThrow(AssertionError::new).getName();
+    try (Table ht = TEST_UTIL.createTable(TableName.valueOf(name), 
Bytes.toBytes(cf))) {
+      List<Put> puts = Lists.newArrayList();
+      puts.add(new Put(Bytes.toBytes("aaa")).addColumn(Bytes.toBytes(cf), 
Bytes.toBytes("q1"),
+        Bytes.toBytes("v")));
+      puts.add(new Put(Bytes.toBytes("aba")).addColumn(Bytes.toBytes(cf), 
Bytes.toBytes("q1"),
+        Bytes.toBytes("v")));
+      puts.add(new Put(Bytes.toBytes("abaa")).addColumn(Bytes.toBytes(cf), 
Bytes.toBytes("q1"),
+        Bytes.toBytes("v")));
+      // The same-row reverse hint can be recreated for each cell on this 
non-matching row.
+      // Before the fix, the first cell could loop inside RowTracker and never 
return to
+      // StoreScanner. After the fix, NEXT_ROW skips the whole row without 
skipping abaa.
+      puts.add(new Put(Bytes.toBytes("abb"))
+        .addColumn(Bytes.toBytes(cf), Bytes.toBytes("q1"), Bytes.toBytes("v1"))
+        .addColumn(Bytes.toBytes(cf), Bytes.toBytes("q2"), Bytes.toBytes("v2"))
+        .addColumn(Bytes.toBytes(cf), Bytes.toBytes("q3"), 
Bytes.toBytes("v3")));
+      puts.add(new Put(Bytes.toBytes("abc")).addColumn(Bytes.toBytes(cf), 
Bytes.toBytes("q1"),
+        Bytes.toBytes("v")));
+      ht.put(puts);
+
+      TEST_UTIL.flush();
+
+      List<Pair<byte[], byte[]>> 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("abaa", Bytes.toString(result.getRow()));
+        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));

Review Comment:
   `deleteTable(...)` is executed after the try-with-resources block, so it 
won’t run if an assertion or scanner operation throws inside the test. To 
ensure cleanup even on failure (and reduce potential cascading 
failures/resource buildup in the mini-cluster), wrap `deleteTable` in a 
`finally` or use a test rule/extension that guarantees table cleanup.



##########
hbase-server/src/test/java/org/apache/hadoop/hbase/filter/TestFuzzyRowFilterEndToEnd.java:
##########
@@ -330,6 +331,103 @@ 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<Put> 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("abaa")).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<Pair<byte[], byte[]>> 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("abaa", Bytes.toString(result.getRow()));
+        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 
testReverseScanMovesPastSameRowFuzzyHintAcrossMultipleCells(TestInfo testInfo)
+    throws IOException {
+    final String cf = "f";
+
+    String name = 
testInfo.getTestMethod().orElseThrow(AssertionError::new).getName();
+    try (Table ht = TEST_UTIL.createTable(TableName.valueOf(name), 
Bytes.toBytes(cf))) {

Review Comment:
   `deleteTable(...)` is executed after the try-with-resources block, so it 
won’t run if an assertion or scanner operation throws inside the test. To 
ensure cleanup even on failure (and reduce potential cascading 
failures/resource buildup in the mini-cluster), wrap `deleteTable` in a 
`finally` or use a test rule/extension that guarantees table cleanup.



##########
hbase-server/src/test/java/org/apache/hadoop/hbase/filter/TestFuzzyRowFilter.java:
##########
@@ -302,6 +307,24 @@ public void testGetNextForFuzzyRuleReverse() {
       new byte[] { 2, 1, 1, 1, 0 }, // row to check
       new byte[] { 1, 2, (byte) 255, 4 }); // expected next
 
+    // no before cell than current which satisfies the fuzzy row -> null
+    assertNull(FuzzyRowFilter.getNextForFuzzyRule(true, new byte[] { 1, 1, 1, 
3, 0 },
+      new byte[] { 1, 2, 0, 3 }, new byte[] { -1, -1, 0, -1 }));

Review Comment:
   Grammar in the comment is off; consider adjusting for clarity (e.g., “no 
cell before the current one satisfies the fuzzy row -> null”).



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to