This is an automated email from the ASF dual-hosted git repository.

junegunn pushed a commit to branch branch-2.5
in repository https://gitbox.apache.org/repos/asf/hbase.git


The following commit(s) were added to refs/heads/branch-2.5 by this push:
     new 0a32fbcd9b5 HBASE-29896 Raw scan incorrectly skips cells expired by 
cell-level TTL (#7749)
0a32fbcd9b5 is described below

commit 0a32fbcd9b51a1e358b02b7354530bc33074c7de
Author: Junegunn Choi <[email protected]>
AuthorDate: Sat Feb 14 20:40:52 2026 +0900

    HBASE-29896 Raw scan incorrectly skips cells expired by cell-level TTL 
(#7749)
    
    Signed-off-by: Duo Zhang <[email protected]>
    Reviewed-by: Liu Xiao <[email protected]>
---
 .../querymatcher/RawScanQueryMatcher.java          |  2 +-
 .../querymatcher/ScanQueryMatcher.java             | 22 ++++++++++++++++++----
 .../hbase/client/TestScannersFromClientSide.java   | 19 +++++++++++++++++++
 3 files changed, 38 insertions(+), 5 deletions(-)

diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/querymatcher/RawScanQueryMatcher.java
 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/querymatcher/RawScanQueryMatcher.java
index 180d2dd2ed3..b2a3e0368e1 100644
--- 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/querymatcher/RawScanQueryMatcher.java
+++ 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/querymatcher/RawScanQueryMatcher.java
@@ -39,7 +39,7 @@ public abstract class RawScanQueryMatcher extends 
UserScanQueryMatcher {
     if (filter != null && filter.filterAllRemaining()) {
       return MatchCode.DONE_SCAN;
     }
-    MatchCode returnCode = preCheck(cell);
+    MatchCode returnCode = preCheckRaw(cell);
     if (returnCode != null) {
       return returnCode;
     }
diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/querymatcher/ScanQueryMatcher.java
 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/querymatcher/ScanQueryMatcher.java
index 2ab3d68fca1..a44baf2832e 100644
--- 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/querymatcher/ScanQueryMatcher.java
+++ 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/querymatcher/ScanQueryMatcher.java
@@ -168,6 +168,24 @@ public abstract class ScanQueryMatcher implements 
ShipperListener {
    * @return null means continue.
    */
   protected final MatchCode preCheck(Cell cell) {
+    final MatchCode code = preCheckRaw(cell);
+    if (code != null) {
+      return code;
+    }
+
+    // check if the cell is expired by cell TTL
+    if (isCellTTLExpired(cell, this.oldestUnexpiredTS, this.now)) {
+      return MatchCode.SKIP;
+    }
+
+    return null;
+  }
+
+  /**
+   * preCheck for raw scan. This should not skip expired cells.
+   * @return null means continue.
+   */
+  protected final MatchCode preCheckRaw(Cell cell) {
     if (currentRow == null) {
       // Since the curCell is null it means we are already sure that we have 
moved over to the next
       // row
@@ -189,10 +207,6 @@ public abstract class ScanQueryMatcher implements 
ShipperListener {
     if (timestamp == HConstants.OLDEST_TIMESTAMP || columns.isDone(timestamp)) 
{
       return columns.getNextRowOrNextColumn(cell);
     }
-    // check if the cell is expired by cell TTL
-    if (isCellTTLExpired(cell, this.oldestUnexpiredTS, this.now)) {
-      return MatchCode.SKIP;
-    }
     return null;
   }
 
diff --git 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestScannersFromClientSide.java
 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestScannersFromClientSide.java
index ba7734c24d0..c151020bf63 100644
--- 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestScannersFromClientSide.java
+++ 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestScannersFromClientSide.java
@@ -641,6 +641,25 @@ public class TestScannersFromClientSide {
     verifyResult(result, kvListExp, toLog, "Testing offset + multiple CFs + 
maxResults");
   }
 
+  @Test
+  public void testRawScanExpiredCell() throws Exception {
+    final TableName tableName = name.getTableName();
+    try (final Table table = TEST_UTIL.createTable(tableName, FAMILY)) {
+      final Put put = new Put(ROW);
+      put.addColumn(FAMILY, QUALIFIER, VALUE);
+      put.setTTL(0);
+      table.put(put);
+      final Scan scan = new Scan().setRaw(true);
+      try (final ResultScanner scanner = table.getScanner(scan)) {
+        final Result result = scanner.next();
+        assertArrayEquals(VALUE, result.getValue(FAMILY, QUALIFIER));
+        assertNull(scanner.next());
+      }
+    } finally {
+      TEST_UTIL.deleteTable(tableName);
+    }
+  }
+
   @Test
   public void testScanRawDeleteFamilyVersion() throws Exception {
     TableName tableName = name.getTableName();

Reply via email to