From 4ef6319af018c091f72e50383b432ebbcd1d0cbf Mon Sep 17 00:00:00 2001 From: Reid Chan Date: Tue, 13 Feb 2018 16:01:28 +0800 Subject: [PATCH] HBASE-19950 Introduce a ColumnValueFilter Signed-off-by: Chia-Ping Tsai --- .../hbase/filter/ColumnValueFilter.java | 241 ++++++++++++++++++ .../hadoop/hbase/filter/CompareFilter.java | 10 +- .../hadoop/hbase/filter/ParseFilter.java | 4 +- .../src/main/protobuf/Filter.proto | 7 + hbase-protocol/src/main/protobuf/Filter.proto | 9 +- .../hadoop/hbase/filter/TestFilter.java | 155 +++++++++++ .../hbase/filter/TestFilterSerialization.java | 9 + .../hadoop/hbase/filter/TestParseFilter.java | 11 + 8 files changed, 440 insertions(+), 6 deletions(-) create mode 100644 hbase-client/src/main/java/org/apache/hadoop/hbase/filter/ColumnValueFilter.java 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 filterArguments) { + Preconditions.checkArgument(filterArguments.size() == 4, + "Expect 4 arguments: %s", filterArguments.size()); + byte[] family = ParseFilter.removeQuotesFromByteArray(filterArguments.get(0)); + byte[] qualifier = ParseFilter.removeQuotesFromByteArray(filterArguments.get(1)); + CompareOperator operator = ParseFilter.createCompareOperator(filterArguments.get(2)); + ByteArrayComparable comparator = + ParseFilter.createComparator(ParseFilter.removeQuotesFromByteArray(filterArguments.get(3))); + + if (comparator instanceof RegexStringComparator || + comparator instanceof SubstringComparator) { + if (operator != CompareOperator.EQUAL && + operator != CompareOperator.NOT_EQUAL) { + throw new IllegalArgumentException("A regexstring comparator and substring comparator " + + "can only be used with EQUAL and NOT_EQUAL"); + } + } + + return new ColumnValueFilter(family, qualifier, operator, comparator); + } + + /** + * @return A pb instance to represent this instance. + */ + FilterProtos.ColumnValueFilter convert() { + FilterProtos.ColumnValueFilter.Builder builder = + FilterProtos.ColumnValueFilter.newBuilder(); + + builder.setFamily(UnsafeByteOperations.unsafeWrap(this.family)); + builder.setQualifier(UnsafeByteOperations.unsafeWrap(this.qualifier)); + builder.setCompareOp(HBaseProtos.CompareType.valueOf(this.op.name())); + builder.setComparator(ProtobufUtil.toComparator(this.comparator)); + + return builder.build(); + } + + /** + * Parse protobuf bytes to a ColumnValueFilter + * @param pbBytes pbBytes + * @return a ColumnValueFilter + * @throws DeserializationException deserialization exception + */ + public static ColumnValueFilter parseFrom(final byte[] pbBytes) throws DeserializationException { + FilterProtos.ColumnValueFilter proto; + try { + proto = FilterProtos.ColumnValueFilter.parseFrom(pbBytes); + } catch (InvalidProtocolBufferException e) { + throw new DeserializationException(e); + } + + final CompareOperator compareOp = CompareOperator.valueOf(proto.getCompareOp().name()); + final ByteArrayComparable comparator; + try { + comparator = ProtobufUtil.toComparator(proto.getComparator()); + } catch (IOException ioe) { + throw new DeserializationException(ioe); + } + + return new ColumnValueFilter(proto.getFamily().toByteArray(), + proto.getQualifier().toByteArray(), compareOp, comparator); + } + + @Override + public byte[] toByteArray() throws IOException { + return convert().toByteArray(); + } + + @Override + boolean areSerializedFieldsEqual(Filter o) { + if (o == this) { + return true; + } else if (!(o instanceof ColumnValueFilter)) { + return false; + } + + ColumnValueFilter other = (ColumnValueFilter) o; + return Bytes.equals(this.getFamily(), other.getFamily()) && + Bytes.equals(this.getQualifier(), other.getQualifier()) && + this.getCompareOperator().equals(other.getCompareOperator()) && + this.getComparator().areSerializedFieldsEqual(other.getComparator()); + } + + @Override + public boolean isFamilyEssential(byte[] name) throws IOException { + return Bytes.equals(name, this.family); + } + + @Override + public String toString() { + return String.format("%s (%s, %s, %s, %s)", + getClass().getSimpleName(), Bytes.toStringBinary(this.family), + Bytes.toStringBinary(this.qualifier), this.op.name(), + Bytes.toStringBinary(this.comparator.getValue())); + } +} diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/filter/CompareFilter.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/filter/CompareFilter.java index 6e6ca6079cd..50924ec631a 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/filter/CompareFilter.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/filter/CompareFilter.java @@ -25,23 +25,27 @@ import java.util.ArrayList; import org.apache.hadoop.hbase.Cell; import org.apache.hadoop.hbase.CompareOperator; import org.apache.hadoop.hbase.PrivateCellUtil; +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.hadoop.hbase.shaded.protobuf.ProtobufUtil; import org.apache.hadoop.hbase.shaded.protobuf.generated.FilterProtos; import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos; import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos.CompareType; -import org.apache.hadoop.hbase.util.Bytes; -import org.apache.hbase.thirdparty.com.google.common.base.Preconditions; /** * This is a generic filter to be used to filter by comparison. It takes an * operator (equal, greater, not equal, etc) and a byte [] comparator. *

* 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 orFilters = new ArrayList<>(2); + orFilters.add( + new ColumnValueFilter(FAMILIES[1], QUALIFIERS_FIVE[1], CompareOperator.NOT_EQUAL, VALUES[1])); + orFilters.add( + new QualifierFilter(CompareOperator.EQUAL, new BinaryComparator(QUALIFIERS_FIVE[0]))); + scan.setFilter(new FilterList(Operator.MUST_PASS_ONE, orFilters)); + KeyValue[] expectedMustPassOne = + { new KeyValue(ROWS_ONE[0], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[1]), + new KeyValue(ROWS_ONE[0], FAMILIES[1], QUALIFIERS_FIVE[0], VALUES[1]), + new KeyValue(ROWS_ONE[1], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[1]), + new KeyValue(ROWS_ONE[1], FAMILIES[1], QUALIFIERS_FIVE[0], VALUES[1]), + new KeyValue(ROWS_ONE[2], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[1]), + new KeyValue(ROWS_ONE[2], FAMILIES[1], QUALIFIERS_FIVE[0], VALUES[1]), + new KeyValue(ROWS_ONE[3], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[1]), + new KeyValue(ROWS_ONE[3], FAMILIES[1], QUALIFIERS_FIVE[0], VALUES[1]), + new KeyValue(ROWS_TWO[0], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[0]), + new KeyValue(ROWS_TWO[0], FAMILIES[1], QUALIFIERS_FIVE[0], VALUES[0]), + new KeyValue(ROWS_TWO[0], FAMILIES[1], QUALIFIERS_FIVE[1], VALUES[0]), // this pass scvf + new KeyValue(ROWS_TWO[1], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[0]), + new KeyValue(ROWS_TWO[1], FAMILIES[1], QUALIFIERS_FIVE[0], VALUES[0]), + new KeyValue(ROWS_TWO[1], FAMILIES[1], QUALIFIERS_FIVE[1], VALUES[0]), // this pass scvf + new KeyValue(ROWS_TWO[2], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[0]), + new KeyValue(ROWS_TWO[2], FAMILIES[1], QUALIFIERS_FIVE[0], VALUES[0]), + new KeyValue(ROWS_TWO[2], FAMILIES[1], QUALIFIERS_FIVE[1], VALUES[0]), // this pass scvf + new KeyValue(ROWS_TWO[3], FAMILIES[0], QUALIFIERS_FIVE[0], VALUES[0]), + new KeyValue(ROWS_TWO[3], FAMILIES[1], QUALIFIERS_FIVE[0], VALUES[0]), + new KeyValue(ROWS_TWO[3], FAMILIES[1], QUALIFIERS_FIVE[1], VALUES[0]) }; // this pass scvf + verifyScanFull(scan, expectedMustPassOne); + // 8. Test FilterList(MUST_PASS_ALL) combining ColumnValueFilter and RowFilter + // (ColumnValueFilter, != f[1]:q5[1]:v[1]) && (RowFilter, = prefix:"testRow") + List andFilters = new ArrayList<>(2); + andFilters.add( + new ColumnValueFilter(FAMILIES[1], QUALIFIERS_FIVE[1], CompareOperator.NOT_EQUAL, VALUES[1])); + andFilters.add(new RowFilter(CompareOperator.EQUAL, + new BinaryPrefixComparator(Bytes.toBytes("testRow")))); + scan.setFilter(new FilterList(Operator.MUST_PASS_ALL, andFilters)); + KeyValue[] expectedMustPassAll = + { 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, expectedMustPassAll); + // 9. Test specified columns with FilterList(MUST_PASS_ONE) which sused in case 7. + // Result is different from case 7, because column is strongly constrained by specified columns + Scan anotherScan = new Scan().addColumn(FAMILIES[1], QUALIFIERS_FIVE[1]) + .setFilter(new FilterList(Operator.MUST_PASS_ONE, orFilters)); + KeyValue[] expectedValues = + { 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(anotherScan, expectedValues); + } + private void verifyScan(Scan s, long expectedRows, long expectedKeys) throws IOException { InternalScanner scanner = this.region.getScanner(s); diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/filter/TestFilterSerialization.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/filter/TestFilterSerialization.java index 2a0d1f4b96b..b0d876b78da 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/filter/TestFilterSerialization.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/filter/TestFilterSerialization.java @@ -341,4 +341,13 @@ public class TestFilterSerialization { assertTrue(multiRowRangeFilter.areSerializedFieldsEqual( ProtobufUtil.toFilter(ProtobufUtil.toFilter(multiRowRangeFilter)))); } + + @Test + public void testColumnValueFilter() throws Exception { + ColumnValueFilter columnValueFilter = + new ColumnValueFilter(Bytes.toBytes("family"), Bytes.toBytes("qualifier"), + CompareOperator.EQUAL, Bytes.toBytes("value")); + assertTrue(columnValueFilter.areSerializedFieldsEqual( + ProtobufUtil.toFilter(ProtobufUtil.toFilter(columnValueFilter)))); + } } diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/filter/TestParseFilter.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/filter/TestParseFilter.java index d30958ccb55..cee0fb35025 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/filter/TestParseFilter.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/filter/TestParseFilter.java @@ -685,4 +685,15 @@ public class TestParseFilter { assertEquals(clazz, filter.getClass()); return clazz.cast(filter); } + + @Test + public void testColumnValueFilter() throws IOException { + String filterString = "ColumnValueFilter ('family', 'qualifier', <, 'binaryprefix:value')"; + ColumnValueFilter cvf = doTestFilter(filterString, ColumnValueFilter.class); + assertEquals("family", new String(cvf.getFamily(), StandardCharsets.UTF_8)); + assertEquals("qualifier", new String(cvf.getQualifier(), StandardCharsets.UTF_8)); + assertEquals(CompareOperator.LESS, cvf.getCompareOperator()); + assertTrue(cvf.getComparator() instanceof BinaryPrefixComparator); + assertEquals("value", new String(cvf.getComparator().getValue(), StandardCharsets.UTF_8)); + } }