This is an automated email from the ASF dual-hosted git repository.
junegunn pushed a commit to branch branch-2
in repository https://gitbox.apache.org/repos/asf/hbase.git
The following commit(s) were added to refs/heads/branch-2 by this push:
new 0f81af35ae5 HBASE-29896 Raw scan incorrectly skips cells expired by
cell-level TTL (#7749)
0f81af35ae5 is described below
commit 0f81af35ae5dc23fef418e417dcfdc88d383691c
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 dcffbb140ed..c9884b70701 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 7e38ea29334..f90feda2957 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
@@ -169,6 +169,24 @@ public abstract class ScanQueryMatcher implements
ShipperListener {
* @return null means continue.
*/
protected final MatchCode preCheck(ExtendedCell 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(ExtendedCell cell) {
if (currentRow == null) {
// Since the curCell is null it means we are already sure that we have
moved over to the next
// row
@@ -190,10 +208,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 c55d1d9334f..2d392317727 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
@@ -660,6 +660,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();