This is an automated email from the ASF dual-hosted git repository.
stoty pushed a commit to branch branch-3
in repository https://gitbox.apache.org/repos/asf/hbase.git
The following commit(s) were added to refs/heads/branch-3 by this push:
new abe3e679933 HBASE-28634 Fix FuzzyRowFilter may not return data on
reverse scans (#6457)
abe3e679933 is described below
commit abe3e6799337a1e1652b414b67bd6d71c3e1876f
Author: Dávid Paksy <[email protected]>
AuthorDate: Mon Nov 25 09:44:50 2024 +0100
HBASE-28634 Fix FuzzyRowFilter may not return data on reverse scans (#6457)
Signed-off-by: Istvan Toth <[email protected]>
(cherry picked from commit a735effa7a34ef1dff08ed5ad33d833f2b68aa66)
---
.../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 f8c35b46528..006ddc8f1c3 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
@@ -67,7 +67,7 @@ import
org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos.BytesBytesP
@InterfaceAudience.Public
public class FuzzyRowFilter extends FilterBase implements HintingFilter {
private static final boolean UNSAFE_UNALIGNED =
HBasePlatformDependent.unaligned();
- 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
@@ -89,7 +89,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;
public FuzzyRowFilter(List<Pair<byte[], byte[]>> fuzzyKeysData) {
List<Pair<byte[], byte[]>> fuzzyKeyDataCopy = new
ArrayList<>(fuzzyKeysData.size());
@@ -200,7 +200,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;
@@ -226,7 +226,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;
}
@@ -574,25 +574,29 @@ 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
*/
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;
@@ -638,7 +642,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 5ef48c62fc6..f6bdf4358e2 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
@@ -132,18 +131,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 08160145455..33a821d75db 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.Map.Entry;
@@ -3095,4 +3096,16 @@ public final class PrivateCellUtil {
return HConstants.NO_SEQNUM;
}
}
+
+ 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 8079f3b54cd..d184bf2a318 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
@@ -164,14 +164,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
@@ -188,23 +194,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
@@ -221,94 +239,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 7ab741b7034..8337009fbad 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;
@@ -337,4 +338,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()));
+ }
}