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

stoty 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 20e510fded8 HBASE-28634 Fix FuzzyRowFilter may not return data on 
reverse scans (#6482)
20e510fded8 is described below

commit 20e510fded8a729f69a0f99097540e6b738fd100
Author: Dávid Paksy <[email protected]>
AuthorDate: Mon Nov 25 09:54:46 2024 +0100

    HBASE-28634 Fix FuzzyRowFilter may not return data on reverse scans (#6482)
    
    Signed-off-by: Istvan Toth <[email protected]>
---
 .../apache/hadoop/hbase/filter/FuzzyRowFilter.java |  41 +++--
 .../apache/hadoop/hbase/filter/PrefixFilter.java   |  15 +-
 .../org/apache/hadoop/hbase/PrivateCellUtil.java   |  13 ++
 .../hadoop/hbase/filter/TestFuzzyRowFilter.java    | 182 ++++++++++++---------
 .../hbase/filter/TestFuzzyRowFilterEndToEnd.java   |  64 ++++++++
 5 files changed, 207 insertions(+), 108 deletions(-)

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 ab0513260c6..86b4f7de9d2 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
@@ -77,7 +77,7 @@ public class FuzzyRowFilter extends FilterBase implements 
HintingFilter {
   static final byte V2_PROCESSED_WILDCARD_MASK = 2;
 
   private final byte processedWildcardMask;
-  private List<Pair<byte[], byte[]>> fuzzyKeysData;
+  private final List<Pair<byte[], byte[]>> fuzzyKeysData;
   // Used to record whether we want to skip the current row.
   // Usually we should use filterRowKey here but in the current scan 
implementation, if filterRowKey
   // returns true, we will just skip to next row, instead of calling 
getNextCellHint to determine
@@ -99,7 +99,7 @@ public class FuzzyRowFilter extends FilterBase implements 
HintingFilter {
   /**
    * Row tracker (keeps all next rows after SEEK_NEXT_USING_HINT was returned)
    */
-  private RowTracker tracker;
+  private final RowTracker tracker;
 
   // this client side constructor ensures that all client-constructed
   // FuzzyRowFilters use the new v2 mask.
@@ -227,7 +227,7 @@ public class FuzzyRowFilter extends FilterBase implements 
HintingFilter {
 
   @Override
   public ReturnCode filterCell(final Cell c) {
-    final int startIndex = lastFoundIndex >= 0 ? lastFoundIndex : 0;
+    final int startIndex = Math.max(lastFoundIndex, 0);
     final int size = fuzzyKeysData.size();
     for (int i = startIndex; i < size + startIndex; i++) {
       final int index = i % size;
@@ -258,7 +258,7 @@ public class FuzzyRowFilter extends FilterBase implements 
HintingFilter {
   @Override
   public Cell getNextCellHint(Cell currentCell) {
     boolean result = tracker.updateTracker(currentCell);
-    if (result == false) {
+    if (!result) {
       done = true;
       return null;
     }
@@ -614,26 +614,30 @@ public class FuzzyRowFilter extends FilterBase implements 
HintingFilter {
   }
 
   /**
-   * @return greater byte array than given (row) which satisfies the fuzzy 
rule if it exists, null
-   *         otherwise
+   * Find out the closes next byte array that satisfies fuzzy rule and is 
after the given one. In
+   * the reverse case it returns increased byte array to make sure that the 
proper row is selected
+   * next.
+   * @return byte array which is after the given row and which satisfies the 
fuzzy rule if it
+   *         exists, null otherwise
    */
   @InterfaceAudience.Private
   static byte[] getNextForFuzzyRule(boolean reverse, byte[] row, int offset, 
int length,
     byte[] fuzzyKeyBytes, byte[] fuzzyKeyMeta) {
-    // To find out the next "smallest" byte array that satisfies fuzzy rule 
and "greater" than
-    // the given one we do the following:
+    // To find out the closest next byte array that satisfies fuzzy rule and 
is after the given one
+    // we do the following:
     // 1. setting values on all "fixed" positions to the values from 
fuzzyKeyBytes
     // 2. if during the first step given row did not increase, then we 
increase the value at
     // the first "non-fixed" position (where it is not maximum already)
 
     // It is easier to perform this by using fuzzyKeyBytes copy and setting 
"non-fixed" position
     // values than otherwise.
-    byte[] result =
-      Arrays.copyOf(fuzzyKeyBytes, length > fuzzyKeyBytes.length ? length : 
fuzzyKeyBytes.length);
-    if (reverse && length > fuzzyKeyBytes.length) {
-      // we need trailing 0xff's instead of trailing 0x00's
-      for (int i = fuzzyKeyBytes.length; i < result.length; i++) {
-        result[i] = (byte) 0xFF;
+    byte[] result = Arrays.copyOf(fuzzyKeyBytes, Math.max(length, 
fuzzyKeyBytes.length));
+    if (reverse) {
+      // we need 0xff's instead of 0x00's
+      for (int i = 0; i < result.length; i++) {
+        if (result[i] == 0) {
+          result[i] = (byte) 0xFF;
+        }
       }
     }
     int toInc = -1;
@@ -679,7 +683,14 @@ public class FuzzyRowFilter extends FilterBase implements 
HintingFilter {
       }
     }
 
-    return reverse ? result : trimTrailingZeroes(result, fuzzyKeyMeta, toInc);
+    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);
+    } else {
+      return trailingZerosTrimmed;
+    }
   }
 
   /**
diff --git 
a/hbase-client/src/main/java/org/apache/hadoop/hbase/filter/PrefixFilter.java 
b/hbase-client/src/main/java/org/apache/hadoop/hbase/filter/PrefixFilter.java
index 07ed8d630eb..0dbbe93c702 100644
--- 
a/hbase-client/src/main/java/org/apache/hadoop/hbase/filter/PrefixFilter.java
+++ 
b/hbase-client/src/main/java/org/apache/hadoop/hbase/filter/PrefixFilter.java
@@ -18,7 +18,6 @@
 package org.apache.hadoop.hbase.filter;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import org.apache.hadoop.hbase.ByteBufferExtendedCell;
 import org.apache.hadoop.hbase.Cell;
 import org.apache.hadoop.hbase.PrivateCellUtil;
@@ -57,7 +56,7 @@ public class PrefixFilter extends FilterBase implements 
HintingFilter {
       return;
     }
     // On reversed scan hint should be the prefix with last byte incremented
-    byte[] reversedHintBytes = increaseLastNonMaxByte(this.prefix);
+    byte[] reversedHintBytes = 
PrivateCellUtil.increaseLastNonMaxByte(this.prefix);
     this.reversedNextCellHint =
       PrivateCellUtil.createFirstOnRow(reversedHintBytes, 0, (short) 
reversedHintBytes.length);
     // On forward scan hint should be the prefix
@@ -138,18 +137,6 @@ public class PrefixFilter extends FilterBase implements 
HintingFilter {
     }
   }
 
-  private byte[] increaseLastNonMaxByte(byte[] bytes) {
-    byte[] result = Arrays.copyOf(bytes, bytes.length);
-    for (int i = bytes.length - 1; i >= 0; i--) {
-      byte b = bytes[i];
-      if (b < Byte.MAX_VALUE) {
-        result[i] = (byte) (b + 1);
-        break;
-      }
-    }
-    return result;
-  }
-
   public static Filter createFilterFromArguments(ArrayList<byte[]> 
filterArguments) {
     Preconditions.checkArgument(filterArguments.size() == 1, "Expected 1 but 
got: %s",
       filterArguments.size());
diff --git 
a/hbase-common/src/main/java/org/apache/hadoop/hbase/PrivateCellUtil.java 
b/hbase-common/src/main/java/org/apache/hadoop/hbase/PrivateCellUtil.java
index 742c091e61c..d08634001a6 100644
--- a/hbase-common/src/main/java/org/apache/hadoop/hbase/PrivateCellUtil.java
+++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/PrivateCellUtil.java
@@ -27,6 +27,7 @@ import java.io.OutputStream;
 import java.math.BigDecimal;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Optional;
@@ -2927,4 +2928,16 @@ public final class PrivateCellUtil {
   public static ExtendedCell createFirstDeleteFamilyCellOnRow(final byte[] 
row, final byte[] fam) {
     return new FirstOnRowDeleteFamilyCell(row, fam);
   }
+
+  public static byte[] increaseLastNonMaxByte(byte[] bytes) {
+    byte[] result = Arrays.copyOf(bytes, bytes.length);
+    for (int i = bytes.length - 1; i >= 0; i--) {
+      byte b = bytes[i];
+      if (b < Byte.MAX_VALUE) {
+        result[i] = (byte) (b + 1);
+        break;
+      }
+    }
+    return result;
+  }
 }
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 0360bed7544..797e5370acd 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
@@ -197,14 +197,20 @@ public class TestFuzzyRowFilter {
       new byte[] { 1, 0, 2, 0, 1 }, // current
       new byte[] { 1, 1, 0, 2 }); // expected next
 
-    assertNext(false, new byte[] { 1, 0, 1 }, new byte[] { -1, 0, -1 },
-      new byte[] { 1, (byte) 128, 2, 0, 1 }, new byte[] { 1, (byte) 129, 1 });
+    assertNext(false, new byte[] { 1, 0, 1 }, // fuzzy row
+      new byte[] { -1, 0, -1 }, // mask
+      new byte[] { 1, (byte) 128, 2, 0, 1 }, // current
+      new byte[] { 1, (byte) 129, 1 }); // expected next
 
-    assertNext(false, new byte[] { 0, 1, 0, 1 }, new byte[] { 0, -1, 0, -1 },
-      new byte[] { 5, 1, 0, 1 }, new byte[] { 5, 1, 1, 1 });
+    assertNext(false, new byte[] { 0, 1, 0, 1 }, // fuzzy row
+      new byte[] { 0, -1, 0, -1 }, // mask
+      new byte[] { 5, 1, 0, 1 }, // current
+      new byte[] { 5, 1, 1, 1 }); // expected next
 
-    assertNext(false, new byte[] { 0, 1, 0, 1 }, new byte[] { 0, -1, 0, -1 },
-      new byte[] { 5, 1, 0, 1, 1 }, new byte[] { 5, 1, 0, 1, 2 });
+    assertNext(false, new byte[] { 0, 1, 0, 1 }, // fuzzy row
+      new byte[] { 0, -1, 0, -1 }, // mask
+      new byte[] { 5, 1, 0, 1, 1 }, // current
+      new byte[] { 5, 1, 0, 1, 2 }); // expected next
 
     assertNext(false, new byte[] { 0, 1, 0, 0 }, // fuzzy row
       new byte[] { 0, -1, 0, 0 }, // mask
@@ -221,23 +227,35 @@ public class TestFuzzyRowFilter {
       new byte[] { 5, 1, (byte) 255, 0 }, // current
       new byte[] { 5, 1, (byte) 255, 1 }); // expected next
 
-    assertNext(false, new byte[] { 5, 1, 1, 0 }, new byte[] { -1, -1, 0, 0 },
-      new byte[] { 5, 1, (byte) 255, 1 }, new byte[] { 5, 1, (byte) 255, 2 });
+    assertNext(false, new byte[] { 5, 1, 1, 0 }, // fuzzy row
+      new byte[] { -1, -1, 0, 0 }, // mask
+      new byte[] { 5, 1, (byte) 255, 1 }, // current
+      new byte[] { 5, 1, (byte) 255, 2 }); // expected next
 
-    assertNext(false, new byte[] { 1, 1, 1, 1 }, new byte[] { -1, -1, 0, 0 },
-      new byte[] { 1, 1, 2, 2 }, new byte[] { 1, 1, 2, 3 });
+    assertNext(false, new byte[] { 1, 1, 1, 1 }, // fuzzy row
+      new byte[] { -1, -1, 0, 0 }, // mask
+      new byte[] { 1, 1, 2, 2 }, // current
+      new byte[] { 1, 1, 2, 3 }); // expected next
 
-    assertNext(false, new byte[] { 1, 1, 1, 1 }, new byte[] { -1, -1, 0, 0 },
-      new byte[] { 1, 1, 3, 2 }, new byte[] { 1, 1, 3, 3 });
+    assertNext(false, new byte[] { 1, 1, 1, 1 }, // fuzzy row
+      new byte[] { -1, -1, 0, 0 }, // mask
+      new byte[] { 1, 1, 3, 2 }, // current
+      new byte[] { 1, 1, 3, 3 }); // expected next
 
-    assertNext(false, new byte[] { 1, 1, 1, 1 }, new byte[] { 0, 0, 0, 0 },
-      new byte[] { 1, 1, 2, 3 }, new byte[] { 1, 1, 2, 4 });
+    assertNext(false, new byte[] { 1, 1, 1, 1 }, // fuzzy row
+      new byte[] { 0, 0, 0, 0 }, // mask
+      new byte[] { 1, 1, 2, 3 }, // current
+      new byte[] { 1, 1, 2, 4 }); // expected next
 
-    assertNext(false, new byte[] { 1, 1, 1, 1 }, new byte[] { 0, 0, 0, 0 },
-      new byte[] { 1, 1, 3, 2 }, new byte[] { 1, 1, 3, 3 });
+    assertNext(false, new byte[] { 1, 1, 1, 1 }, // fuzzy row
+      new byte[] { 0, 0, 0, 0 }, // mask
+      new byte[] { 1, 1, 3, 2 }, // current
+      new byte[] { 1, 1, 3, 3 }); // expected next
 
-    assertNext(false, new byte[] { 1, 1, 0, 0 }, new byte[] { -1, -1, 0, 0 },
-      new byte[] { 0, 1, 3, 2 }, new byte[] { 1, 1 });
+    assertNext(false, new byte[] { 1, 1, 0, 0 }, // fuzzy row
+      new byte[] { -1, -1, 0, 0 }, // mask
+      new byte[] { 0, 1, 3, 2 }, // current
+      new byte[] { 1, 1 }); // expected next
 
     // No next for this one
     Assert.assertNull(FuzzyRowFilter.getNextForFuzzyRule(new byte[] { 2, 3, 1, 
1, 1 }, // row to
@@ -254,94 +272,100 @@ public class TestFuzzyRowFilter {
 
   @Test
   public void testGetNextForFuzzyRuleReverse() {
+    // In these reverse cases for the next row key the last non-max byte 
should be increased
+    // to make sure that the proper row is selected next by the scanner.
+    // For example:
+    // fuzzy row: 0,1,2
+    // mask: 0,-1,-1
+    // current: 1,2,1,0,1
+    // next would be: 1,1,2
+    // this has to be increased to 1,1,3 to make sure that the proper row is 
selected next.
     assertNext(true, new byte[] { 0, 1, 2 }, // fuzzy row
       new byte[] { 0, -1, -1 }, // mask
       new byte[] { 1, 2, 1, 0, 1 }, // current
-      // TODO: should be {1, 1, 3} ?
-      new byte[] { 1, 1, 2, (byte) 0xFF, (byte) 0xFF }); // expected next
+      new byte[] { 1, 1, 3 }); // expected next
 
     assertNext(true, new byte[] { 0, 1, 0, 2, 0 }, // fuzzy row
       new byte[] { 0, -1, 0, -1, 0 }, // mask
       new byte[] { 1, 2, 1, 3, 1 }, // current
-      // TODO: should be {1, 1, 1, 3} ?
-      new byte[] { 1, 1, 0, 2, 0 }); // expected next
+      new byte[] { 1, 1, (byte) 255, 3 }); // expected next
 
-    assertNext(true, new byte[] { 1, 0, 1 }, new byte[] { -1, 0, -1 },
-      new byte[] { 1, (byte) 128, 2, 0, 1 },
-      // TODO: should be {1, (byte) 128, 2} ?
-      new byte[] { 1, (byte) 128, 1, (byte) 0xFF, (byte) 0xFF });
+    assertNext(true, new byte[] { 1, 0, 1 }, // fuzzy row
+      new byte[] { -1, 0, -1 }, // mask
+      new byte[] { 1, (byte) 128, 2, 0, 1 }, // current
+      new byte[] { 1, (byte) 128, 2 }); // expected next
 
-    assertNext(true, new byte[] { 0, 1, 0, 1 }, new byte[] { 0, -1, 0, -1 },
-      new byte[] { 5, 1, 0, 2, 1 },
-      // TODO: should be {5, 1, 0, 2} ?
-      new byte[] { 5, 1, 0, 1, (byte) 0xFF });
+    assertNext(true, new byte[] { 0, 1, 0, 1 }, // fuzzy row
+      new byte[] { 0, -1, 0, -1 }, // mask
+      new byte[] { 5, 1, 0, 2, 1 }, // current
+      new byte[] { 5, 1, 0, 2 }); // expected next
 
     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, 0 }); // expected next
+      new byte[] { 5, 1, (byte) 255, 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, 1 }, // current
-      new byte[] { 4, 1, (byte) 255, 1 }); // expected next
+      new byte[] { 4, 1, (byte) 255, 2 }); // expected next
 
     assertNext(true, new byte[] { 0, 1, 0, 1 }, // fuzzy row
       new byte[] { 0, -1, 0, -1 }, // mask
       new byte[] { 5, 1, (byte) 255, 0 }, // current
-      new byte[] { 5, 1, (byte) 254, 1 }); // expected next
+      new byte[] { 5, 1, (byte) 254, 2 }); // expected next
 
-    assertNext(true, new byte[] { 1, 1, 0, 0 }, new byte[] { -1, -1, 0, 0 },
-      new byte[] { 2, 1, 3, 2 },
-      // TODO: should be {1, 0} ?
-      new byte[] { 1, 1, 0, 0 });
+    assertNext(true, new byte[] { 1, 1, 0, 0 }, // fuzzy row
+      new byte[] { -1, -1, 0, 0 }, // mask
+      new byte[] { 2, 1, 3, 2 }, // current
+      new byte[] { 1, 2 }); // expected next
 
     assertNext(true, new byte[] { 1, 0, 1 }, // fuzzy row
       new byte[] { -1, 0, -1 }, // mask
       new byte[] { 2, 3, 1, 1, 1 }, // row to check
-      // TODO: should be {1, (byte) 0xFF, 2} ?
-      new byte[] { 1, 0, 1, (byte) 0xFF, (byte) 0xFF });
-
-    assertNext(true, new byte[] { 1, 1, 0, 3 }, new byte[] { -1, -1, 0, -1 },
-      new byte[] { 1, (byte) 245, 1, 3, 0 },
-      // TODO: should be {1, 1, (byte) 255, 4} ?
-      new byte[] { 1, 1, 0, 3, (byte) 0xFF });
-
-    assertNext(true, new byte[] { 1, 2, 0, 3 }, new byte[] { -1, -1, 0, -1 },
-      new byte[] { 1, 3, 1, 3, 0 },
-      // TODO: should be 1, 2, (byte) 255, 4 ?
-      new byte[] { 1, 2, 0, 3, (byte) 0xFF });
-
-    assertNext(true, new byte[] { 1, 2, 0, 3 }, new byte[] { -1, -1, 0, -1 },
-      new byte[] { 2, 1, 1, 1, 0 },
-      // TODO: should be {1, 2, (byte) 255, 4} ?
-      new byte[] { 1, 2, 0, 3, (byte) 0xFF });
-
-    assertNext(true,
-      // TODO: should be null?
-      new byte[] { 1, 0, 1 }, new byte[] { -1, 0, -1 }, new byte[] { 1, (byte) 
128, 2 },
-      new byte[] { 1, (byte) 128, 1 });
-
-    assertNext(true,
-      // TODO: should be null?
-      new byte[] { 0, 1, 0, 1 }, new byte[] { 0, -1, 0, -1 }, new byte[] { 5, 
1, 0, 2 },
-      new byte[] { 5, 1, 0, 1 });
-
-    assertNext(true,
-      // TODO: should be null?
-      new byte[] { 5, 1, 1, 0 }, new byte[] { -1, -1, 0, 0 }, new byte[] { 5, 
1, (byte) 0xFF, 1 },
-      new byte[] { 5, 1, (byte) 0xFF, 0 });
-
-    assertNext(true,
-      // TODO: should be null?
-      new byte[] { 1, 1, 1, 1 }, new byte[] { -1, -1, 0, 0 }, new byte[] { 1, 
1, 2, 2 },
-      new byte[] { 1, 1, 2, 1 });
-
-    assertNext(true,
-      // TODO: should be null?
-      new byte[] { 1, 1, 1, 1 }, new byte[] { 0, 0, 0, 0 }, new byte[] { 1, 1, 
2, 3 },
-      new byte[] { 1, 1, 2, 2 });
+      new byte[] { 1, (byte) 255, 2 }); // expected next
+
+    assertNext(true, new byte[] { 1, 1, 0, 3 }, // fuzzy row
+      new byte[] { -1, -1, 0, -1 }, // mask
+      new byte[] { 1, (byte) 245, 1, 3, 0 }, // row to check
+      new byte[] { 1, 1, (byte) 255, 4 }); // expected next
+
+    assertNext(true, new byte[] { 1, 2, 0, 3 }, // fuzzy row
+      new byte[] { -1, -1, 0, -1 }, // mask
+      new byte[] { 1, 3, 1, 3, 0 }, // row to check
+      new byte[] { 1, 2, (byte) 255, 4 }); // expected next
+
+    assertNext(true, new byte[] { 1, 2, 0, 3 }, // fuzzy row
+      new byte[] { -1, -1, 0, -1 }, // mask
+      new byte[] { 2, 1, 1, 1, 0 }, // row to check
+      new byte[] { 1, 2, (byte) 255, 4 }); // expected next
+
+    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
+
+    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
+
+    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
+
+    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
+
+    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
 
+    // no before cell than current which satisfies the fuzzy row -> null
     Assert.assertNull(FuzzyRowFilter.getNextForFuzzyRule(true, new byte[] { 1, 
1, 1, 3, 0 },
       new byte[] { 1, 2, 0, 3 }, new byte[] { -1, -1, 0, -1 }));
   }
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 7cf76433a0d..69523778712 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
@@ -24,6 +24,7 @@ import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.LinkedList;
 import java.util.List;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hbase.Cell;
@@ -352,4 +353,67 @@ public class TestFuzzyRowFilterEndToEnd {
 
     TEST_UTIL.deleteTable(TableName.valueOf(name.getMethodName()));
   }
+
+  @Test
+  public void testHBASE28634() throws IOException {
+    final String CF = "f";
+    final String CQ = "name";
+
+    Table ht = TEST_UTIL.createTable(TableName.valueOf(name.getMethodName()), 
Bytes.toBytes(CF));
+
+    // Put data
+    List<Put> puts = Lists.newArrayList();
+    puts.add(new Put(Bytes.toBytes("111311")).addColumn(Bytes.toBytes(CF), 
Bytes.toBytes(CQ),
+      Bytes.toBytes("a1")));
+    puts.add(new Put(Bytes.toBytes("111444")).addColumn(Bytes.toBytes(CF), 
Bytes.toBytes(CQ),
+      Bytes.toBytes("a2")));
+    puts.add(new Put(Bytes.toBytes("111511")).addColumn(Bytes.toBytes(CF), 
Bytes.toBytes(CQ),
+      Bytes.toBytes("a3")));
+    puts.add(new Put(Bytes.toBytes("111611")).addColumn(Bytes.toBytes(CF), 
Bytes.toBytes(CQ),
+      Bytes.toBytes("a4")));
+    puts.add(new Put(Bytes.toBytes("111446")).addColumn(Bytes.toBytes(CF), 
Bytes.toBytes(CQ),
+      Bytes.toBytes("a5")));
+    puts.add(new Put(Bytes.toBytes("111777")).addColumn(Bytes.toBytes(CF), 
Bytes.toBytes(CQ),
+      Bytes.toBytes("a6")));
+    puts.add(new Put(Bytes.toBytes("111777")).addColumn(Bytes.toBytes(CF), 
Bytes.toBytes(CQ),
+      Bytes.toBytes("a")));
+    ht.put(puts);
+
+    TEST_UTIL.flush();
+
+    // Forward scan
+    LinkedList<Pair<byte[], byte[]>> fuzzyList = new LinkedList<>();
+    byte[] fuzzyRowKey = Bytes.toBytes("111433");
+    byte[] mask = Bytes.toBytesBinary("\\xFF\\xFF\\xFF\\xFF\\x02\\x02");
+    fuzzyList.add(new Pair<>(fuzzyRowKey, mask));
+    FuzzyRowFilter fuzzyRowFilter = new FuzzyRowFilter(fuzzyList);
+
+    Scan scan = new Scan();
+    scan.setFilter(fuzzyRowFilter);
+
+    ResultScanner scanner = ht.getScanner(scan);
+    List<byte[]> actualRowsList = new ArrayList<>();
+    for (Result result : scanner) {
+      byte[] row = result.getRow();
+      actualRowsList.add(row);
+    }
+
+    assertEquals(2, actualRowsList.size());
+
+    // Reverse scan
+    scan = new Scan();
+    scan.setFilter(fuzzyRowFilter);
+    scan.setReversed(true);
+
+    scanner = ht.getScanner(scan);
+    actualRowsList = new ArrayList<>();
+    for (Result result : scanner) {
+      byte[] row = result.getRow();
+      actualRowsList.add(row);
+    }
+
+    assertEquals(2, actualRowsList.size());
+
+    TEST_UTIL.deleteTable(TableName.valueOf(name.getMethodName()));
+  }
 }

Reply via email to