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 d565f31..e895b10 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 @@ -17,6 +17,7 @@ */ package org.apache.hadoop.hbase.filter; +import com.google.common.annotations.VisibleForTesting; import org.apache.hadoop.hbase.util.ByteStringer; import com.google.protobuf.InvalidProtocolBufferException; @@ -84,7 +85,7 @@ public class FuzzyRowFilter extends FilterBase { SatisfiesCode bestOption = SatisfiesCode.NO_NEXT; for (Pair fuzzyData : fuzzyKeysData) { SatisfiesCode satisfiesCode = - satisfies(rowKey, fuzzyData.getFirst(), fuzzyData.getSecond()); + satisfies(isReversed(), rowKey, fuzzyData.getFirst(), fuzzyData.getSecond()); if (satisfiesCode == SatisfiesCode.YES) { return ReturnCode.INCLUDE; } @@ -112,7 +113,7 @@ public class FuzzyRowFilter extends FilterBase { byte[] nextRowKey = null; // Searching for the "smallest" row key that satisfies at least one fuzzy row key for (Pair fuzzyData : fuzzyKeysData) { - byte[] nextRowKeyCandidate = getNextForFuzzyRule(rowKey, + byte[] nextRowKeyCandidate = getNextForFuzzyRule(isReversed(), rowKey, fuzzyData.getFirst(), fuzzyData.getSecond()); if (nextRowKeyCandidate == null) { continue; @@ -195,20 +196,26 @@ public class FuzzyRowFilter extends FilterBase { // Utility methods static enum SatisfiesCode { - // row satisfies fuzzy rule + /** row satisfies fuzzy rule */ YES, - // row doesn't satisfy fuzzy rule, but there's possible greater row that does + /** row doesn't satisfy fuzzy rule, but there's possible greater row that does */ NEXT_EXISTS, - // row doesn't satisfy fuzzy rule and there's no greater row that does + /** row doesn't satisfy fuzzy rule and there's no greater row that does */ NO_NEXT } - static SatisfiesCode satisfies(byte[] row, - byte[] fuzzyKeyBytes, byte[] fuzzyKeyMeta) { - return satisfies(row, 0, row.length, fuzzyKeyBytes, fuzzyKeyMeta); + @VisibleForTesting + static SatisfiesCode satisfies(byte[] row, byte[] fuzzyKeyBytes, byte[] fuzzyKeyMeta) { + return satisfies(false, row, 0, row.length, fuzzyKeyBytes, fuzzyKeyMeta); + } + + @VisibleForTesting + static SatisfiesCode satisfies(boolean reverse, byte[] row, byte[] fuzzyKeyBytes, + byte[] fuzzyKeyMeta) { + return satisfies(reverse, row, 0, row.length, fuzzyKeyBytes, fuzzyKeyMeta); } - private static SatisfiesCode satisfies(byte[] row, int offset, int length, + private static SatisfiesCode satisfies(boolean reverse, byte[] row, int offset, int length, byte[] fuzzyKeyBytes, byte[] fuzzyKeyMeta) { if (row == null) { // do nothing, let scan to proceed @@ -231,7 +238,13 @@ public class FuzzyRowFilter extends FilterBase { // this row and which satisfies the fuzzy rule. Otherwise there's no such byte array: // this row is simply bigger than any byte array that satisfies the fuzzy rule boolean rowByteLessThanFixed = (row[i + offset] & 0xFF) < (fuzzyKeyBytes[i] & 0xFF); - return rowByteLessThanFixed ? SatisfiesCode.NEXT_EXISTS : SatisfiesCode.NO_NEXT; + if (rowByteLessThanFixed && !reverse) { + return SatisfiesCode.NEXT_EXISTS; + } else if (!rowByteLessThanFixed && reverse) { + return SatisfiesCode.NEXT_EXISTS; + } else { + return SatisfiesCode.NO_NEXT; + } } // Second, checking if this position is not fixed and byte value is not the biggest. In this @@ -240,7 +253,7 @@ public class FuzzyRowFilter extends FilterBase { // (see the code of getNextForFuzzyRule below) by one. // Note: if non-fixed byte is already at biggest value, this doesn't allow us to say there's // bigger one that satisfies the rule as it can't be increased. - if (fuzzyKeyMeta[i] == 1 && !isMax(fuzzyKeyBytes[i])) { + if (fuzzyKeyMeta[i] == 1 && !isMax(reverse, fuzzyKeyBytes[i])) { nextRowKeyCandidateExists = true; } } @@ -248,19 +261,26 @@ public class FuzzyRowFilter extends FilterBase { return SatisfiesCode.YES; } - private static boolean isMax(byte fuzzyKeyByte) { - return (fuzzyKeyByte & 0xFF) == 255; + private static boolean isMax(boolean reverse, byte fuzzyKeyByte) { + return fuzzyKeyByte == (reverse ? Byte.MIN_VALUE : Byte.MAX_VALUE); } + @VisibleForTesting static byte[] getNextForFuzzyRule(byte[] row, byte[] fuzzyKeyBytes, byte[] fuzzyKeyMeta) { - return getNextForFuzzyRule(row, 0, row.length, fuzzyKeyBytes, fuzzyKeyMeta); + return getNextForFuzzyRule(false, row, 0, row.length, fuzzyKeyBytes, fuzzyKeyMeta); + } + + @VisibleForTesting + static byte[] getNextForFuzzyRule(boolean reverse, byte[] row, byte[] fuzzyKeyBytes, + byte[] fuzzyKeyMeta) { + return getNextForFuzzyRule(reverse, row, 0, row.length, fuzzyKeyBytes, fuzzyKeyMeta); } /** * @return greater byte array than given (row) which satisfies the fuzzy rule if it exists, * null otherwise */ - private static byte[] getNextForFuzzyRule(byte[] row, int offset, int length, + 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: @@ -272,28 +292,53 @@ public class FuzzyRowFilter extends FilterBase { // 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; + } + } int toInc = -1; boolean increased = false; for (int i = 0; i < result.length; i++) { if (i >= fuzzyKeyMeta.length || fuzzyKeyMeta[i] == 1) { result[i] = row[offset + i]; - if (!isMax(row[i])) { + if (!isMax(reverse, row[i])) { // this is "non-fixed" position and is not at max value, hence we can increase it toInc = i; } } else if (i < fuzzyKeyMeta.length && fuzzyKeyMeta[i] == 0) { - if ((row[i + offset] & 0xFF) < (fuzzyKeyBytes[i] & 0xFF)) { - // if setting value for any fixed position increased the original array, - // we are OK - increased = true; - break; + if (reverse) { + if ((row[i + offset] & 0xFF) > (fuzzyKeyBytes[i] & 0xFF)) { + // if setting value for any fixed position increased the original array, + // we are OK + increased = true; + break; + } + } else { + if ((row[i + offset] & 0xFF) < (fuzzyKeyBytes[i] & 0xFF)) { + // if setting value for any fixed position increased the original array, + // we are OK + increased = true; + break; + } } - if ((row[i + offset] & 0xFF) > (fuzzyKeyBytes[i] & 0xFF)) { - // if setting value for any fixed position makes array "smaller", then just stop: - // in case we found some non-fixed position to increase we will do it, otherwise - // there's no "next" row key that satisfies fuzzy rule and "greater" than given row - break; + + if (reverse) { + if ((row[i + offset] & 0xFF) < (fuzzyKeyBytes[i] & 0xFF)) { + // if setting value for any fixed position makes array "smaller", then just stop: + // in case we found some non-fixed position to increase we will do it, otherwise + // there's no "next" row key that satisfies fuzzy rule and "greater" than given row + break; + } + } else { + if ((row[i + offset] & 0xFF) > (fuzzyKeyBytes[i] & 0xFF)) { + // if setting value for any fixed position makes array "smaller", then just stop: + // in case we found some non-fixed position to increase we will do it, otherwise + // there's no "next" row key that satisfies fuzzy rule and "greater" than given row + break; + } } } } @@ -302,13 +347,17 @@ public class FuzzyRowFilter extends FilterBase { if (toInc < 0) { return null; } - result[toInc]++; + if (reverse) { + result[toInc]--; + } else { + result[toInc]++; + } // Setting all "non-fixed" positions to zeroes to the right of the one we increased so // that found "next" row key is the smallest possible for (int i = toInc + 1; i < result.length; i++) { if (i >= fuzzyKeyMeta.length || fuzzyKeyMeta[i] == 1) { - result[i] = 0; + result[i] = reverse ? (byte) 0xFF : 0; } } } @@ -317,7 +366,6 @@ public class FuzzyRowFilter extends FilterBase { } /** - * @param other * @return true if and only if the fields of the filter that are serialized * are equal to the corresponding fields in other. Used for testing. */