diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/filter/ColumnValueFilter.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/filter/ColumnValueFilter.java new file mode 100644 index 00000000000..07951658f88 --- /dev/null +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/filter/ColumnValueFilter.java @@ -0,0 +1,241 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hadoop.hbase.filter; + +import java.io.IOException; +import java.util.ArrayList; + +import org.apache.hadoop.hbase.Cell; +import org.apache.hadoop.hbase.CellUtil; +import org.apache.hadoop.hbase.CompareOperator; +import org.apache.hadoop.hbase.PrivateCellUtil; +import org.apache.hadoop.hbase.exceptions.DeserializationException; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.yetus.audience.InterfaceAudience; + +import org.apache.hbase.thirdparty.com.google.common.base.Preconditions; +import org.apache.hbase.thirdparty.com.google.protobuf.InvalidProtocolBufferException; +import org.apache.hbase.thirdparty.com.google.protobuf.UnsafeByteOperations; + +import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; +import org.apache.hadoop.hbase.shaded.protobuf.generated.FilterProtos; +import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos; + +/** + * Different from {@link SingleColumnValueFilter} which returns an entire row + * when specified condition is matched, {@link ColumnValueFilter} return the matched cell only. + *
+ * This filter is used to filter cells based on column and value.
+ * It takes a {@link org.apache.hadoop.hbase.CompareOperator} operator (<, <=, =, !=, >, >=), and
+ * and a {@link ByteArrayComparable} comparator.
+ */
+@InterfaceAudience.Public
+public class ColumnValueFilter extends FilterBase {
+ private final byte[] family;
+ private final byte[] qualifier;
+ private final CompareOperator op;
+ private final ByteArrayComparable comparator;
+
+ // This flag is used to speed up seeking cells when matched column is found, such that following
+ // columns in the same row can be skipped faster by NEXT_ROW instead of NEXT_COL.
+ private boolean columnFound = false;
+
+ public ColumnValueFilter(final byte[] family, final byte[] qualifier,
+ final CompareOperator op, final byte[] value) {
+ this(family, qualifier, op, new BinaryComparator(value));
+ }
+
+ public ColumnValueFilter(final byte[] family, final byte[] qualifier,
+ final CompareOperator op,
+ final ByteArrayComparable comparator) {
+ this.family = Preconditions.checkNotNull(family, "family should not be null.");
+ this.qualifier = qualifier == null ? new byte[0] : qualifier;
+ this.op = Preconditions.checkNotNull(op, "CompareOperator should not be null");
+ this.comparator = Preconditions.checkNotNull(comparator, "Comparator should not be null");
+ }
+
+ /**
+ * @return operator
+ */
+ public CompareOperator getCompareOperator() {
+ return op;
+ }
+
+ /**
+ * @return the comparator
+ */
+ public ByteArrayComparable getComparator() {
+ return comparator;
+ }
+
+ /**
+ * @return the column family
+ */
+ public byte[] getFamily() {
+ return family;
+ }
+
+ /**
+ * @return the qualifier
+ */
+ public byte[] getQualifier() {
+ return qualifier;
+ }
+
+ @Override
+ public void reset() throws IOException {
+ columnFound = false;
+ }
+
+ @Override
+ public boolean filterRowKey(Cell cell) throws IOException {
+ return false;
+ }
+
+ @Override
+ public ReturnCode filterCell(Cell c) throws IOException {
+ // 1. Check column match
+ if (!CellUtil.matchingColumn(c, this.family, this.qualifier)) {
+ return columnFound ? ReturnCode.NEXT_ROW : ReturnCode.NEXT_COL;
+ }
+ // Column found
+ columnFound = true;
+ // 2. Check value match:
+ // True means filter out, just skip this cell, else include it.
+ return compareValue(getCompareOperator(), getComparator(), c) ?
+ ReturnCode.SKIP : ReturnCode.INCLUDE;
+ }
+
+ /**
+ * This method is used to determine a cell should be included or filtered out.
+ * @param op one of operators {@link CompareOperator}
+ * @param comparator comparator used to compare cells.
+ * @param cell cell to be compared.
+ * @return true means cell should be filtered out, included otherwise.
+ */
+ private boolean compareValue(final CompareOperator op, final ByteArrayComparable comparator,
+ final Cell cell) {
+ if (op == CompareOperator.NO_OP) {
+ return true;
+ }
+ int compareResult = PrivateCellUtil.compareValue(cell, comparator);
+ return CompareFilter.compare(op, compareResult);
+ }
+
+ /**
+ * Creating this filter by reflection, it is used by {@link ParseFilter},
+ * @param filterArguments arguments for creating a ColumnValueFilter
+ * @return a ColumnValueFilter
+ */
+ public static Filter createFilterFromArguments(ArrayList
* To filter by row key, use {@link RowFilter}.
*
+ * To filter by column family, use {@link FamilyFilter}.
+ *
* To filter by column qualifier, use {@link QualifierFilter}.
*
- * To filter by value, use {@link SingleColumnValueFilter}.
+ * To filter by value, use {@link ValueFilter}.
*
* These filters can be wrapped with {@link SkipFilter} and {@link WhileMatchFilter}
* to add more control.
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/filter/ParseFilter.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/filter/ParseFilter.java
index e984212985e..716322cff9c 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/filter/ParseFilter.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/filter/ParseFilter.java
@@ -93,6 +93,8 @@ public class ParseFilter {
"SingleColumnValueExcludeFilter");
filterHashMap.put("DependentColumnFilter", ParseConstants.FILTER_PACKAGE + "." +
"DependentColumnFilter");
+ filterHashMap.put("ColumnValueFilter", ParseConstants.FILTER_PACKAGE + "." +
+ "ColumnValueFilter");
// Creates the operatorPrecedenceHashMap
operatorPrecedenceHashMap = new HashMap<>();
@@ -769,8 +771,6 @@ public class ParseFilter {
/**
* Takes a compareOperator symbol as a byte array and returns the corresponding CompareOperator
- * @deprecated Since 2.0
- *
* @param compareOpAsByteArray the comparatorOperator symbol as a byte array
* @return the Compare Operator
*/
diff --git a/hbase-protocol-shaded/src/main/protobuf/Filter.proto b/hbase-protocol-shaded/src/main/protobuf/Filter.proto
index 743498532b3..b0d6da68c3b 100644
--- a/hbase-protocol-shaded/src/main/protobuf/Filter.proto
+++ b/hbase-protocol-shaded/src/main/protobuf/Filter.proto
@@ -170,3 +170,10 @@ message RowRange {
message MultiRowRangeFilter {
repeated RowRange row_range_list = 1;
}
+
+message ColumnValueFilter {
+ required bytes family = 1;
+ required bytes qualifier = 2;
+ required CompareType compare_op = 3;
+ required Comparator comparator = 4;
+}
diff --git a/hbase-protocol/src/main/protobuf/Filter.proto b/hbase-protocol/src/main/protobuf/Filter.proto
index 1fa66978234..8a0a9bf5029 100644
--- a/hbase-protocol/src/main/protobuf/Filter.proto
+++ b/hbase-protocol/src/main/protobuf/Filter.proto
@@ -168,4 +168,11 @@ message RowRange {
message MultiRowRangeFilter {
repeated RowRange row_range_list = 1;
-}
\ No newline at end of file
+}
+
+message ColumnValueFilter {
+ required bytes family = 1;
+ required bytes qualifier = 2;
+ required CompareType compare_op = 3;
+ required Comparator comparator = 4;
+}
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/filter/TestFilter.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/filter/TestFilter.java
index 3c5be6303a1..a3e3359c1da 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/filter/TestFilter.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/filter/TestFilter.java
@@ -132,6 +132,10 @@ public class TestFilter {
Bytes.toBytes("testQualifierFour-2"), Bytes.toBytes("testQualifierFour-3")
};
+ private static final byte [][] QUALIFIERS_FIVE = {
+ Bytes.toBytes("testQualifierFive-0"), Bytes.toBytes("testQualifierFive-1")
+ };
+
private static final byte [][] VALUES = {
Bytes.toBytes("testValueOne"), Bytes.toBytes("testValueTwo")
};
@@ -1663,6 +1667,157 @@ public class TestFilter {
}
+ @Test
+ public void testColumnValueFilter() throws Exception {
+ // Prepare test rows:
+ for (int i = 0; i < 2; i++) {
+ for (int j = 0; j < ROWS_ONE.length; j++) {
+ Put p1 = new Put(ROWS_ONE[j]).setDurability(Durability.SKIP_WAL);
+ Put p2 = new Put(ROWS_TWO[j]).setDurability(Durability.SKIP_WAL);
+ for (byte[] q5 : QUALIFIERS_FIVE) {
+ p1.addColumn(FAMILIES[0], q5, VALUES[0 + i]).addColumn(FAMILIES[1], q5, VALUES[0 + i]);
+ p2.addColumn(FAMILIES[0], q5, VALUES[1 - i]).addColumn(FAMILIES[1], q5, VALUES[1 - i]);
+ }
+ this.region.put(p1);
+ this.region.put(p2);
+ }
+ this.region.flush(true);
+ }
+ // 1. Test = f[0]:q5[0]:v[1]
+ Scan scan = new Scan().setFilter(
+ new ColumnValueFilter(FAMILIES[0], QUALIFIERS_FIVE[0], CompareOperator.EQUAL, VALUES[1]));
+ KeyValue[] expectedEquals =
+ { new KeyValue(ROWS_ONE[0], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[1]),
+ new KeyValue(ROWS_ONE[1], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[1]),
+ new KeyValue(ROWS_ONE[2], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[1]),
+ new KeyValue(ROWS_ONE[3], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[1]),
+ new KeyValue(ROWS_TWO[0], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[1]),
+ new KeyValue(ROWS_TWO[1], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[1]),
+ new KeyValue(ROWS_TWO[2], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[1]),
+ new KeyValue(ROWS_TWO[3], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[1]) };
+ verifyScanFull(scan, expectedEquals);
+ // 2. Test > f[0]:q5[0]:v[0]
+ scan.setFilter(
+ new ColumnValueFilter(FAMILIES[0], QUALIFIERS_FIVE[0], CompareOperator.GREATER, VALUES[0]));
+ KeyValue[] expectedGreater =
+ { new KeyValue(ROWS_ONE[0], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[1]),
+ new KeyValue(ROWS_ONE[1], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[1]),
+ new KeyValue(ROWS_ONE[2], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[1]),
+ new KeyValue(ROWS_ONE[3], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[1]),
+ new KeyValue(ROWS_TWO[0], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[1]),
+ new KeyValue(ROWS_TWO[1], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[1]),
+ new KeyValue(ROWS_TWO[2], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[1]),
+ new KeyValue(ROWS_TWO[3], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[1]) };
+ verifyScanFull(scan, expectedGreater);
+ // 3. Test >= f[0]:q5[0]:v[0]
+ // also test readAllVersions(), since FAMILIES[0] allow multiple versions.
+ scan.readAllVersions().setFilter(new ColumnValueFilter(FAMILIES[0], QUALIFIERS_FIVE[0],
+ CompareOperator.GREATER_OR_EQUAL, VALUES[0]));
+ KeyValue[] expectedGreaterOrEqual =
+ { new KeyValue(ROWS_ONE[0], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[1]),
+ new KeyValue(ROWS_ONE[0], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[0]),
+ new KeyValue(ROWS_ONE[1], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[1]),
+ new KeyValue(ROWS_ONE[1], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[0]),
+ new KeyValue(ROWS_ONE[2], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[1]),
+ new KeyValue(ROWS_ONE[2], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[0]),
+ new KeyValue(ROWS_ONE[3], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[1]),
+ new KeyValue(ROWS_ONE[3], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[0]),
+ new KeyValue(ROWS_TWO[0], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[0]),
+ new KeyValue(ROWS_TWO[0], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[1]),
+ new KeyValue(ROWS_TWO[1], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[0]),
+ new KeyValue(ROWS_TWO[1], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[1]),
+ new KeyValue(ROWS_TWO[2], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[0]),
+ new KeyValue(ROWS_TWO[2], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[1]),
+ new KeyValue(ROWS_TWO[3], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[0]),
+ new KeyValue(ROWS_TWO[3], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[1]) };
+ verifyScanFull(scan, expectedGreaterOrEqual);
+ // 4. Test < f[1]:q5[1]:v[1], FAMILIES[1] doesn't support multiple versions
+ scan.readVersions(1).setFilter(new ColumnValueFilter(FAMILIES[1], QUALIFIERS_FIVE[1],
+ CompareOperator.LESS, VALUES[1]));
+ KeyValue[] expectedLess =
+ { new KeyValue(ROWS_TWO[0], FAMILIES[1], QUALIFIERS_FIVE[1], VALUES[0]),
+ new KeyValue(ROWS_TWO[1], FAMILIES[1], QUALIFIERS_FIVE[1], VALUES[0]),
+ new KeyValue(ROWS_TWO[2], FAMILIES[1], QUALIFIERS_FIVE[1], VALUES[0]),
+ new KeyValue(ROWS_TWO[3], FAMILIES[1], QUALIFIERS_FIVE[1], VALUES[0]) };
+ verifyScanFull(scan, expectedLess);
+ // 5. Test <= f[1]:q5[0]:v[1]
+ scan.setFilter(new ColumnValueFilter(FAMILIES[1], QUALIFIERS_FIVE[1],
+ CompareOperator.LESS_OR_EQUAL, VALUES[1]));
+ KeyValue[] expectedLessOrEqual =
+ { new KeyValue(ROWS_ONE[0], FAMILIES[1], QUALIFIERS_FIVE[1], VALUES[1]),
+ new KeyValue(ROWS_ONE[1], FAMILIES[1], QUALIFIERS_FIVE[1], VALUES[1]),
+ new KeyValue(ROWS_ONE[2], FAMILIES[1], QUALIFIERS_FIVE[1], VALUES[1]),
+ new KeyValue(ROWS_ONE[3], FAMILIES[1], QUALIFIERS_FIVE[1], VALUES[1]),
+ new KeyValue(ROWS_TWO[0], FAMILIES[1], QUALIFIERS_FIVE[1], VALUES[0]),
+ new KeyValue(ROWS_TWO[1], FAMILIES[1], QUALIFIERS_FIVE[1], VALUES[0]),
+ new KeyValue(ROWS_TWO[2], FAMILIES[1], QUALIFIERS_FIVE[1], VALUES[0]),
+ new KeyValue(ROWS_TWO[3], FAMILIES[1], QUALIFIERS_FIVE[1], VALUES[0]) };
+ verifyScanFull(scan, expectedLessOrEqual);
+ // 6. Test != f[1]:q5[1]:v[1]
+ scan.setFilter(
+ new ColumnValueFilter(FAMILIES[1], QUALIFIERS_FIVE[1], CompareOperator.NOT_EQUAL, VALUES[1]));
+ KeyValue[] expectedNotEqual =
+ { new KeyValue(ROWS_TWO[0], FAMILIES[1], QUALIFIERS_FIVE[1], VALUES[0]),
+ new KeyValue(ROWS_TWO[1], FAMILIES[1], QUALIFIERS_FIVE[1], VALUES[0]),
+ new KeyValue(ROWS_TWO[2], FAMILIES[1], QUALIFIERS_FIVE[1], VALUES[0]),
+ new KeyValue(ROWS_TWO[3], FAMILIES[1], QUALIFIERS_FIVE[1], VALUES[0]) };
+ verifyScanFull(scan, expectedNotEqual);
+ // 7. Test FilterList(MUST_PASS_ONE) combining ColumnValueFilter and QualifierFilter
+ // (ColumnValueFilter, != f[1]:q5[1]:v[1]) || (QualifierFilter, = q5[0])
+ List