blob: e96b8dbbbda6cb01b5244b466d1b8338f1d1400d [file] [log] [blame]
/**
* 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellComparator;
import org.apache.hadoop.hbase.CompareOperator;
import org.apache.hadoop.hbase.HBaseClassTestRule;
import org.apache.hadoop.hbase.KeyValue;
import org.apache.hadoop.hbase.KeyValueUtil;
import org.apache.hadoop.hbase.exceptions.DeserializationException;
import org.apache.hadoop.hbase.filter.Filter.ReturnCode;
import org.apache.hadoop.hbase.filter.FilterList.Operator;
import org.apache.hadoop.hbase.testclassification.FilterTests;
import org.apache.hadoop.hbase.testclassification.MediumTests;
import org.apache.hadoop.hbase.util.Bytes;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.mockito.Mockito;
import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
@Category({FilterTests.class, MediumTests.class})
public class TestFilterList {
@ClassRule
public static final HBaseClassTestRule CLASS_RULE =
HBaseClassTestRule.forClass(TestFilterList.class);
static final int MAX_PAGES = 2;
@Test
public void testAddFilter() throws Exception {
Filter filter1 = new FirstKeyOnlyFilter();
Filter filter2 = new FirstKeyOnlyFilter();
FilterList filterList = new FilterList(filter1, filter2);
filterList.addFilter(new FirstKeyOnlyFilter());
filterList = new FilterList(Arrays.asList(filter1, filter2));
filterList.addFilter(new FirstKeyOnlyFilter());
filterList = new FilterList(Operator.MUST_PASS_ALL, filter1, filter2);
filterList.addFilter(new FirstKeyOnlyFilter());
filterList = new FilterList(Operator.MUST_PASS_ALL, Arrays.asList(filter1, filter2));
filterList.addFilter(new FirstKeyOnlyFilter());
filterList.setReversed(false);
FirstKeyOnlyFilter f = new FirstKeyOnlyFilter();
f.setReversed(true);
try {
filterList.addFilter(f);
fail("The IllegalArgumentException should be thrown because the added filter is reversed");
} catch (IllegalArgumentException e) {
}
}
@Test
public void testConstruction() {
FirstKeyOnlyFilter f1 = new FirstKeyOnlyFilter();
FirstKeyOnlyFilter f2 = new FirstKeyOnlyFilter();
f1.setReversed(true);
f2.setReversed(false);
try {
FilterList ff = new FilterList(f1, f2);
fail("The IllegalArgumentException should be thrown");
} catch (IllegalArgumentException e) {
}
try {
FilterList ff = new FilterList(Arrays.asList(f1, f2));
fail("The IllegalArgumentException should be thrown because the added filter is reversed");
} catch (IllegalArgumentException e) {
}
try {
FilterList ff = new FilterList(FilterList.Operator.MUST_PASS_ALL, Arrays.asList(f1, f2));
fail("The IllegalArgumentException should be thrown because the added filter is reversed");
} catch (IllegalArgumentException e) {
}
try {
FilterList ff = new FilterList(FilterList.Operator.MUST_PASS_ALL, f1, f2);
fail("The IllegalArgumentException should be thrown because the added filter is reversed");
} catch (IllegalArgumentException e) {
}
}
/**
* Test "must pass one"
* @throws Exception
*/
@Test
public void testMPONE() throws Exception {
mpOneTest(getFilterMPONE());
}
private Filter getFilterMPONE() {
List<Filter> filters = new ArrayList<>();
filters.add(new PageFilter(MAX_PAGES));
filters.add(new WhileMatchFilter(new PrefixFilter(Bytes.toBytes("yyy"))));
Filter filterMPONE =
new FilterList(FilterList.Operator.MUST_PASS_ONE, filters);
return filterMPONE;
}
private void mpOneTest(Filter filterMPONE) throws Exception {
/* Filter must do all below steps:
* <ul>
* <li>{@link #reset()}</li>
* <li>{@link #filterAllRemaining()} -> true indicates scan is over, false, keep going on.</li>
* <li>{@link #filterRowKey(byte[],int,int)} -> true to drop this row,
* if false, we will also call</li>
* <li>{@link #filterCell(org.apache.hadoop.hbase.KeyValue)} -> true to drop this cell</li>
* <li>{@link #filterRow()} -> last chance to drop entire row based on the sequence of
* filterValue() calls. Eg: filter a row if it doesn't contain a specified column.
* </li>
* </ul>
*/
filterMPONE.reset();
assertFalse(filterMPONE.filterAllRemaining());
/* Will pass both */
byte [] rowkey = Bytes.toBytes("yyyyyyyyy");
for (int i = 0; i < MAX_PAGES - 1; i++) {
assertFalse(filterMPONE.filterRowKey(KeyValueUtil.createFirstOnRow(rowkey)));
KeyValue kv = new KeyValue(rowkey, rowkey, Bytes.toBytes(i),
Bytes.toBytes(i));
assertTrue(Filter.ReturnCode.INCLUDE == filterMPONE.filterCell(kv));
assertFalse(filterMPONE.filterRow());
}
/* Only pass PageFilter */
rowkey = Bytes.toBytes("z");
assertFalse(filterMPONE.filterRowKey(KeyValueUtil.createFirstOnRow(rowkey)));
KeyValue kv = new KeyValue(rowkey, rowkey, Bytes.toBytes(0),
Bytes.toBytes(0));
assertTrue(Filter.ReturnCode.INCLUDE == filterMPONE.filterCell(kv));
assertFalse(filterMPONE.filterRow());
/* reach MAX_PAGES already, should filter any rows */
rowkey = Bytes.toBytes("yyy");
assertTrue(filterMPONE.filterRowKey(KeyValueUtil.createFirstOnRow(rowkey)));
kv = new KeyValue(rowkey, rowkey, Bytes.toBytes(0),
Bytes.toBytes(0));
assertFalse(Filter.ReturnCode.INCLUDE == filterMPONE.filterCell(kv));
assertFalse(filterMPONE.filterRow());
/* We should filter any row */
rowkey = Bytes.toBytes("z");
assertTrue(filterMPONE.filterRowKey(KeyValueUtil.createFirstOnRow(rowkey)));
assertTrue(filterMPONE.filterAllRemaining());
}
/**
* Test "must pass all"
* @throws Exception
*/
@Test
public void testMPALL() throws Exception {
mpAllTest(getMPALLFilter());
}
private Filter getMPALLFilter() {
List<Filter> filters = new ArrayList<>();
filters.add(new PageFilter(MAX_PAGES));
filters.add(new WhileMatchFilter(new PrefixFilter(Bytes.toBytes("yyy"))));
Filter filterMPALL =
new FilterList(FilterList.Operator.MUST_PASS_ALL, filters);
return filterMPALL;
}
private void mpAllTest(Filter filterMPALL) throws Exception {
/* Filter must do all below steps:
* <ul>
* <li>{@link #reset()}</li>
* <li>{@link #filterAllRemaining()} -> true indicates scan is over, false, keep going on.</li>
* <li>{@link #filterRowKey(byte[],int,int)} -> true to drop this row,
* if false, we will also call</li>
* <li>{@link #filterCell(org.apache.hadoop.hbase.KeyValue)} -> true to drop this cell</li>
* <li>{@link #filterRow()} -> last chance to drop entire row based on the sequence of
* filterValue() calls. Eg: filter a row if it doesn't contain a specified column.
* </li>
* </ul>
*/
filterMPALL.reset();
assertFalse(filterMPALL.filterAllRemaining());
byte [] rowkey = Bytes.toBytes("yyyyyyyyy");
for (int i = 0; i < MAX_PAGES - 1; i++) {
assertFalse(filterMPALL.filterRowKey(KeyValueUtil.createFirstOnRow(rowkey)));
KeyValue kv = new KeyValue(rowkey, rowkey, Bytes.toBytes(i),
Bytes.toBytes(i));
assertTrue(Filter.ReturnCode.INCLUDE == filterMPALL.filterCell(kv));
}
filterMPALL.reset();
rowkey = Bytes.toBytes("z");
assertTrue(filterMPALL.filterRowKey(KeyValueUtil.createFirstOnRow(rowkey)));
// Should fail here; row should be filtered out.
KeyValue kv = new KeyValue(rowkey, rowkey, rowkey, rowkey);
assertTrue(Filter.ReturnCode.NEXT_ROW == filterMPALL.filterCell(kv));
}
/**
* Test list ordering
* @throws Exception
*/
@Test
public void testOrdering() throws Exception {
orderingTest(getOrderingFilter());
}
public Filter getOrderingFilter() {
List<Filter> filters = new ArrayList<>();
filters.add(new PrefixFilter(Bytes.toBytes("yyy")));
filters.add(new PageFilter(MAX_PAGES));
Filter filterMPONE =
new FilterList(FilterList.Operator.MUST_PASS_ONE, filters);
return filterMPONE;
}
public void orderingTest(Filter filterMPONE) throws Exception {
/* Filter must do all below steps:
* <ul>
* <li>{@link #reset()}</li>
* <li>{@link #filterAllRemaining()} -> true indicates scan is over, false, keep going on.</li>
* <li>{@link #filterRowKey(byte[],int,int)} -> true to drop this row,
* if false, we will also call</li>
* <li>{@link #filterCell(org.apache.hadoop.hbase.KeyValue)} -> true to drop this key/value</li>
* <li>{@link #filterRow()} -> last chance to drop entire row based on the sequence of
* filterValue() calls. Eg: filter a row if it doesn't contain a specified column.
* </li>
* </ul>
*/
filterMPONE.reset();
assertFalse(filterMPONE.filterAllRemaining());
/* We should be able to fill MAX_PAGES without incrementing page counter */
byte [] rowkey = Bytes.toBytes("yyyyyyyy");
for (int i = 0; i < MAX_PAGES; i++) {
assertFalse(filterMPONE.filterRowKey(KeyValueUtil.createFirstOnRow(rowkey)));
KeyValue kv = new KeyValue(rowkey, rowkey, Bytes.toBytes(i),
Bytes.toBytes(i));
assertTrue(Filter.ReturnCode.INCLUDE == filterMPONE.filterCell(kv));
assertFalse(filterMPONE.filterRow());
}
/* Now let's fill the page filter */
rowkey = Bytes.toBytes("xxxxxxx");
for (int i = 0; i < MAX_PAGES; i++) {
assertFalse(filterMPONE.filterRowKey(KeyValueUtil.createFirstOnRow(rowkey)));
KeyValue kv = new KeyValue(rowkey, rowkey, Bytes.toBytes(i),
Bytes.toBytes(i));
assertTrue(Filter.ReturnCode.INCLUDE == filterMPONE.filterCell(kv));
assertFalse(filterMPONE.filterRow());
}
/* We should still be able to include even though page filter is at max */
rowkey = Bytes.toBytes("yyy");
for (int i = 0; i < MAX_PAGES; i++) {
assertFalse(filterMPONE.filterRowKey(KeyValueUtil.createFirstOnRow(rowkey)));
KeyValue kv = new KeyValue(rowkey, rowkey, Bytes.toBytes(i),
Bytes.toBytes(i));
assertTrue(Filter.ReturnCode.INCLUDE == filterMPONE.filterCell(kv));
assertFalse(filterMPONE.filterRow());
}
}
/**
* When we do a "MUST_PASS_ONE" (a logical 'OR') of the above two filters
* we expect to get the same result as the 'prefix' only result.
* @throws Exception
*/
@Test
public void testFilterListTwoFiltersMustPassOne() throws Exception {
byte[] r1 = Bytes.toBytes("Row1");
byte[] r11 = Bytes.toBytes("Row11");
byte[] r2 = Bytes.toBytes("Row2");
FilterList flist = new FilterList(FilterList.Operator.MUST_PASS_ONE);
flist.addFilter(new PrefixFilter(r1));
flist.filterRowKey(KeyValueUtil.createFirstOnRow(r1));
assertEquals(ReturnCode.INCLUDE, flist.filterCell(new KeyValue(r1, r1, r1)));
assertEquals(ReturnCode.INCLUDE, flist.filterCell(new KeyValue(r11, r11, r11)));
flist.reset();
flist.filterRowKey(KeyValueUtil.createFirstOnRow(r2));
assertEquals(ReturnCode.SKIP, flist.filterCell(new KeyValue(r2, r2, r2)));
flist = new FilterList(FilterList.Operator.MUST_PASS_ONE);
flist.addFilter(new AlwaysNextColFilter());
flist.addFilter(new PrefixFilter(r1));
flist.filterRowKey(KeyValueUtil.createFirstOnRow(r1));
assertEquals(ReturnCode.INCLUDE, flist.filterCell(new KeyValue(r1, r1, r1)));
assertEquals(ReturnCode.INCLUDE, flist.filterCell(new KeyValue(r11, r11, r11)));
flist.reset();
flist.filterRowKey(KeyValueUtil.createFirstOnRow(r2));
assertEquals(ReturnCode.NEXT_COL, flist.filterCell(new KeyValue(r2, r2, r2)));
}
/**
* When we do a "MUST_PASS_ONE" (a logical 'OR') of the two filters
* we expect to get the same result as the inclusive stop result.
* @throws Exception
*/
@Test
public void testFilterListWithInclusiveStopFilterMustPassOne() throws Exception {
byte[] r1 = Bytes.toBytes("Row1");
byte[] r11 = Bytes.toBytes("Row11");
byte[] r2 = Bytes.toBytes("Row2");
FilterList flist = new FilterList(FilterList.Operator.MUST_PASS_ONE);
flist.addFilter(new AlwaysNextColFilter());
flist.addFilter(new InclusiveStopFilter(r1));
flist.filterRowKey(KeyValueUtil.createFirstOnRow(r1));
assertEquals(ReturnCode.INCLUDE, flist.filterCell(new KeyValue(r1, r1, r1)));
assertEquals(ReturnCode.INCLUDE, flist.filterCell(new KeyValue(r11, r11, r11)));
flist.reset();
flist.filterRowKey(KeyValueUtil.createFirstOnRow(r2));
assertEquals(ReturnCode.NEXT_COL, flist.filterCell(new KeyValue(r2, r2, r2)));
}
public static class AlwaysNextColFilter extends FilterBase {
public AlwaysNextColFilter() {
super();
}
@Override
public ReturnCode filterCell(final Cell v) {
return ReturnCode.NEXT_COL;
}
public static AlwaysNextColFilter parseFrom(final byte[] pbBytes)
throws DeserializationException {
return new AlwaysNextColFilter();
}
}
/**
* Test serialization
* @throws Exception
*/
@Test
public void testSerialization() throws Exception {
List<Filter> filters = new ArrayList<>();
filters.add(new PageFilter(MAX_PAGES));
filters.add(new WhileMatchFilter(new PrefixFilter(Bytes.toBytes("yyy"))));
Filter filterMPALL =
new FilterList(FilterList.Operator.MUST_PASS_ALL, filters);
// Decompose filterMPALL to bytes.
byte[] buffer = filterMPALL.toByteArray();
// Recompose filterMPALL.
FilterList newFilter = FilterList.parseFrom(buffer);
// Run tests
mpOneTest(ProtobufUtil.toFilter(ProtobufUtil.toFilter(getFilterMPONE())));
mpAllTest(ProtobufUtil.toFilter(ProtobufUtil.toFilter(getMPALLFilter())));
orderingTest(ProtobufUtil.toFilter(ProtobufUtil.toFilter(getOrderingFilter())));
}
/**
* Test filterCell logic.
* @throws Exception
*/
@Test
public void testFilterCell() throws Exception {
Filter includeFilter = new FilterBase() {
@Override
public Filter.ReturnCode filterCell(final Cell v) {
return Filter.ReturnCode.INCLUDE;
}
};
Filter alternateFilter = new FilterBase() {
boolean returnInclude = true;
@Override
public Filter.ReturnCode filterCell(final Cell v) {
Filter.ReturnCode returnCode = returnInclude ? Filter.ReturnCode.INCLUDE :
Filter.ReturnCode.SKIP;
returnInclude = !returnInclude;
return returnCode;
}
};
Filter alternateIncludeFilter = new FilterBase() {
boolean returnIncludeOnly = false;
@Override
public Filter.ReturnCode filterCell(final Cell v) {
Filter.ReturnCode returnCode = returnIncludeOnly ? Filter.ReturnCode.INCLUDE :
Filter.ReturnCode.INCLUDE_AND_NEXT_COL;
returnIncludeOnly = !returnIncludeOnly;
return returnCode;
}
};
// Check must pass one filter.
FilterList mpOnefilterList = new FilterList(Operator.MUST_PASS_ONE,
Arrays.asList(new Filter[] { includeFilter, alternateIncludeFilter, alternateFilter }));
// INCLUDE, INCLUDE, INCLUDE_AND_NEXT_COL.
assertEquals(ReturnCode.INCLUDE, mpOnefilterList.filterCell(null));
// INCLUDE, SKIP, INCLUDE.
assertEquals(Filter.ReturnCode.INCLUDE, mpOnefilterList.filterCell(null));
// Check must pass all filter.
FilterList mpAllfilterList = new FilterList(Operator.MUST_PASS_ALL,
Arrays.asList(new Filter[] { includeFilter, alternateIncludeFilter, alternateFilter }));
// INCLUDE, INCLUDE, INCLUDE_AND_NEXT_COL.
assertEquals(Filter.ReturnCode.INCLUDE_AND_NEXT_COL, mpAllfilterList.filterCell(null));
// INCLUDE, SKIP, INCLUDE.
assertEquals(Filter.ReturnCode.SKIP, mpAllfilterList.filterCell(null));
}
/**
* Test pass-thru of hints.
*/
@Test
public void testHintPassThru() throws Exception {
final KeyValue minKeyValue = new KeyValue(Bytes.toBytes(0L), null, null);
final KeyValue maxKeyValue = new KeyValue(Bytes.toBytes(Long.MAX_VALUE),
null, null);
Filter filterNoHint = new FilterBase() {
@Override
public byte [] toByteArray() {
return null;
}
@Override
public ReturnCode filterCell(final Cell ignored) throws IOException {
return ReturnCode.INCLUDE;
}
};
Filter filterMinHint = new FilterBase() {
@Override
public ReturnCode filterCell(final Cell ignored) {
return ReturnCode.SEEK_NEXT_USING_HINT;
}
@Override
public Cell getNextCellHint(Cell currentKV) {
return minKeyValue;
}
@Override
public byte [] toByteArray() {return null;}
};
Filter filterMaxHint = new FilterBase() {
@Override
public ReturnCode filterCell(final Cell ignored) {
return ReturnCode.SEEK_NEXT_USING_HINT;
}
@Override
public Cell getNextCellHint(Cell cell) {
return new KeyValue(Bytes.toBytes(Long.MAX_VALUE), null, null);
}
@Override
public byte [] toByteArray() {return null;}
};
CellComparator comparator = CellComparator.getInstance();
// MUST PASS ONE
// Should take the min if given two hints
FilterList filterList = new FilterList(Operator.MUST_PASS_ONE,
Arrays.asList(new Filter [] { filterMinHint, filterMaxHint } ));
assertEquals(0, comparator.compare(filterList.getNextCellHint(null), minKeyValue));
// Should have no hint if any filter has no hint
filterList = new FilterList(Operator.MUST_PASS_ONE,
Arrays.asList(
new Filter [] { filterMinHint, filterMaxHint, filterNoHint } ));
assertNull(filterList.getNextCellHint(null));
filterList = new FilterList(Operator.MUST_PASS_ONE,
Arrays.asList(new Filter [] { filterNoHint, filterMaxHint } ));
assertNull(filterList.getNextCellHint(null));
// Should give max hint if its the only one
filterList = new FilterList(Operator.MUST_PASS_ONE,
Arrays.asList(new Filter[] { filterMaxHint, filterMaxHint }));
assertEquals(0, comparator.compare(filterList.getNextCellHint(null), maxKeyValue));
// MUST PASS ALL
// Should take the first hint
filterList = new FilterList(Operator.MUST_PASS_ALL,
Arrays.asList(new Filter [] { filterMinHint, filterMaxHint } ));
filterList.filterCell(null);
assertEquals(0, comparator.compare(filterList.getNextCellHint(null), minKeyValue));
filterList = new FilterList(Operator.MUST_PASS_ALL,
Arrays.asList(new Filter [] { filterMaxHint, filterMinHint } ));
filterList.filterCell(null);
assertEquals(0, comparator.compare(filterList.getNextCellHint(null), maxKeyValue));
// Should have first hint even if a filter has no hint
filterList = new FilterList(Operator.MUST_PASS_ALL,
Arrays.asList(new Filter[] { filterNoHint, filterMinHint, filterMaxHint }));
filterList.filterCell(null);
assertEquals(0, comparator.compare(filterList.getNextCellHint(null), minKeyValue));
filterList = new FilterList(Operator.MUST_PASS_ALL,
Arrays.asList(new Filter[] { filterNoHint, filterMaxHint }));
filterList.filterCell(null);
assertEquals(0, comparator.compare(filterList.getNextCellHint(null), maxKeyValue));
filterList = new FilterList(Operator.MUST_PASS_ALL,
Arrays.asList(new Filter[] { filterNoHint, filterMinHint }));
filterList.filterCell(null);
assertEquals(0, comparator.compare(filterList.getNextCellHint(null), minKeyValue));
}
/**
* Tests the behavior of transform() in a hierarchical filter.
*
* transform() only applies after a filterCell() whose return-code includes the KeyValue.
* Lazy evaluation of AND
*/
@Test
public void testTransformMPO() throws Exception {
// Apply the following filter:
// (family=fam AND qualifier=qual1 AND KeyOnlyFilter)
// OR (family=fam AND qualifier=qual2)
final FilterList flist = new FilterList(Operator.MUST_PASS_ONE, Lists.<Filter>newArrayList(
new FilterList(Operator.MUST_PASS_ALL, Lists.<Filter>newArrayList(
new FamilyFilter(CompareOperator.EQUAL, new BinaryComparator(Bytes.toBytes("fam"))),
new QualifierFilter(CompareOperator.EQUAL, new BinaryComparator(Bytes.toBytes("qual1"))),
new KeyOnlyFilter())),
new FilterList(Operator.MUST_PASS_ALL, Lists.<Filter>newArrayList(
new FamilyFilter(CompareOperator.EQUAL, new BinaryComparator(Bytes.toBytes("fam"))),
new QualifierFilter(CompareOperator.EQUAL, new BinaryComparator(Bytes.toBytes("qual2")))))));
final KeyValue kvQual1 = new KeyValue(
Bytes.toBytes("row"), Bytes.toBytes("fam"), Bytes.toBytes("qual1"), Bytes.toBytes("value"));
final KeyValue kvQual2 = new KeyValue(
Bytes.toBytes("row"), Bytes.toBytes("fam"), Bytes.toBytes("qual2"), Bytes.toBytes("value"));
final KeyValue kvQual3 = new KeyValue(
Bytes.toBytes("row"), Bytes.toBytes("fam"), Bytes.toBytes("qual3"), Bytes.toBytes("value"));
// Value for fam:qual1 should be stripped:
assertEquals(Filter.ReturnCode.INCLUDE, flist.filterCell(kvQual1));
final KeyValue transformedQual1 = KeyValueUtil.ensureKeyValue(flist.transformCell(kvQual1));
assertEquals(0, transformedQual1.getValueLength());
// Value for fam:qual2 should not be stripped:
assertEquals(Filter.ReturnCode.INCLUDE, flist.filterCell(kvQual2));
final KeyValue transformedQual2 = KeyValueUtil.ensureKeyValue(flist.transformCell(kvQual2));
assertEquals("value", Bytes.toString(transformedQual2.getValueArray(),
transformedQual2.getValueOffset(), transformedQual2.getValueLength()));
// Other keys should be skipped:
assertEquals(Filter.ReturnCode.SKIP, flist.filterCell(kvQual3));
}
@Test
public void testWithMultiVersionsInSameRow() throws Exception {
FilterList filterList01 =
new FilterList(Operator.MUST_PASS_ONE, new ColumnPaginationFilter(1, 0));
KeyValue kv1 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam"), Bytes.toBytes("qual"),
1, Bytes.toBytes("value"));
KeyValue kv2 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam"), Bytes.toBytes("qual"),
2, Bytes.toBytes("value"));
KeyValue kv3 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam"), Bytes.toBytes("qual"),
3, Bytes.toBytes("value"));
assertEquals(ReturnCode.INCLUDE_AND_NEXT_COL, filterList01.filterCell(kv1));
assertEquals(ReturnCode.NEXT_COL, filterList01.filterCell(kv2));
assertEquals(ReturnCode.NEXT_COL, filterList01.filterCell(kv3));
FilterList filterList11 =
new FilterList(Operator.MUST_PASS_ONE, new ColumnPaginationFilter(1, 1));
assertEquals(ReturnCode.NEXT_COL, filterList11.filterCell(kv1));
assertEquals(ReturnCode.NEXT_COL, filterList11.filterCell(kv2));
assertEquals(ReturnCode.NEXT_COL, filterList11.filterCell(kv3));
}
@Test
public void testMPONEWithSeekNextUsingHint() throws Exception {
byte[] col = Bytes.toBytes("c");
FilterList filterList =
new FilterList(Operator.MUST_PASS_ONE, new ColumnPaginationFilter(1, col));
KeyValue kv1 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam"), Bytes.toBytes("a"), 1,
Bytes.toBytes("value"));
KeyValue kv2 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam"), Bytes.toBytes("b"), 2,
Bytes.toBytes("value"));
KeyValue kv3 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam"), Bytes.toBytes("c"), 3,
Bytes.toBytes("value"));
KeyValue kv4 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam"), Bytes.toBytes("c"), 4,
Bytes.toBytes("value"));
assertEquals(ReturnCode.SEEK_NEXT_USING_HINT, filterList.filterCell(kv1));
assertEquals(ReturnCode.SEEK_NEXT_USING_HINT, filterList.filterCell(kv2));
assertEquals(ReturnCode.INCLUDE_AND_NEXT_COL, filterList.filterCell(kv3));
assertEquals(ReturnCode.NEXT_COL, filterList.filterCell(kv4));
}
private static class MockFilter extends FilterBase {
private ReturnCode targetRetCode;
public boolean didCellPassToTheFilter = false;
public MockFilter(ReturnCode targetRetCode) {
this.targetRetCode = targetRetCode;
}
@Override
public ReturnCode filterCell(final Cell v) throws IOException {
this.didCellPassToTheFilter = true;
return targetRetCode;
}
@Override
public boolean equals(Object obj) {
if(obj == null || !(obj instanceof MockFilter)){
return false;
}
if(obj == this){
return true;
}
MockFilter f = (MockFilter)obj;
return this.targetRetCode.equals(f.targetRetCode);
}
@Override
public int hashCode() {
return Objects.hash(this.targetRetCode);
}
}
@Test
public void testShouldPassCurrentCellToFilter() throws IOException {
KeyValue kv1 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam"), Bytes.toBytes("a"), 1,
Bytes.toBytes("value"));
KeyValue kv2 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam"), Bytes.toBytes("a"), 2,
Bytes.toBytes("value"));
KeyValue kv3 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam"), Bytes.toBytes("b"), 3,
Bytes.toBytes("value"));
KeyValue kv4 = new KeyValue(Bytes.toBytes("row1"), Bytes.toBytes("fam"), Bytes.toBytes("c"), 4,
Bytes.toBytes("value"));
MockFilter mockFilter = new MockFilter(ReturnCode.NEXT_COL);
FilterList filter = new FilterList(Operator.MUST_PASS_ONE, mockFilter);
filter.filterCell(kv1);
assertTrue(mockFilter.didCellPassToTheFilter);
mockFilter.didCellPassToTheFilter = false;
filter.filterCell(kv2);
assertFalse(mockFilter.didCellPassToTheFilter);
mockFilter.didCellPassToTheFilter = false;
filter.filterCell(kv3);
assertTrue(mockFilter.didCellPassToTheFilter);
mockFilter = new MockFilter(ReturnCode.INCLUDE_AND_NEXT_COL);
filter = new FilterList(Operator.MUST_PASS_ONE, mockFilter);
filter.filterCell(kv1);
assertTrue(mockFilter.didCellPassToTheFilter);
mockFilter.didCellPassToTheFilter = false;
filter.filterCell(kv2);
assertFalse(mockFilter.didCellPassToTheFilter);
mockFilter.didCellPassToTheFilter = false;
filter.filterCell(kv3);
assertTrue(mockFilter.didCellPassToTheFilter);
mockFilter = new MockFilter(ReturnCode.NEXT_ROW);
filter = new FilterList(Operator.MUST_PASS_ONE, mockFilter);
filter.filterCell(kv1);
assertTrue(mockFilter.didCellPassToTheFilter);
mockFilter.didCellPassToTheFilter = false;
filter.filterCell(kv2);
assertFalse(mockFilter.didCellPassToTheFilter);
mockFilter.didCellPassToTheFilter = false;
filter.filterCell(kv3);
assertFalse(mockFilter.didCellPassToTheFilter);
filter.reset();
mockFilter.didCellPassToTheFilter = false;
filter.filterCell(kv4);
assertTrue(mockFilter.didCellPassToTheFilter);
mockFilter = new MockFilter(ReturnCode.INCLUDE_AND_SEEK_NEXT_ROW);
filter = new FilterList(Operator.MUST_PASS_ONE, mockFilter);
filter.filterCell(kv1);
assertTrue(mockFilter.didCellPassToTheFilter);
mockFilter.didCellPassToTheFilter = false;
filter.filterCell(kv2);
assertFalse(mockFilter.didCellPassToTheFilter);
mockFilter.didCellPassToTheFilter = false;
filter.filterCell(kv3);
assertFalse(mockFilter.didCellPassToTheFilter);
filter.reset();
mockFilter.didCellPassToTheFilter = false;
filter.filterCell(kv4);
assertTrue(mockFilter.didCellPassToTheFilter);
}
@Test
public void testTheMaximalRule() throws IOException {
KeyValue kv1 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam"), Bytes.toBytes("a"), 1,
Bytes.toBytes("value"));
MockFilter filter1 = new MockFilter(ReturnCode.INCLUDE);
MockFilter filter2 = new MockFilter(ReturnCode.INCLUDE_AND_NEXT_COL);
MockFilter filter3 = new MockFilter(ReturnCode.INCLUDE_AND_SEEK_NEXT_ROW);
MockFilter filter4 = new MockFilter(ReturnCode.NEXT_COL);
MockFilter filter5 = new MockFilter(ReturnCode.SKIP);
MockFilter filter6 = new MockFilter(ReturnCode.SEEK_NEXT_USING_HINT);
MockFilter filter7 = new MockFilter(ReturnCode.NEXT_ROW);
FilterList filterList = new FilterList(Operator.MUST_PASS_ALL, filter1, filter2);
assertEquals(ReturnCode.INCLUDE_AND_NEXT_COL, filterList.filterCell(kv1));
filterList = new FilterList(Operator.MUST_PASS_ALL, filter2, filter3);
assertEquals(ReturnCode.INCLUDE_AND_SEEK_NEXT_ROW, filterList.filterCell(kv1));
filterList = new FilterList(Operator.MUST_PASS_ALL, filter4, filter5, filter6);
assertEquals(ReturnCode.NEXT_COL, filterList.filterCell(kv1));
filterList = new FilterList(Operator.MUST_PASS_ALL, filter4, filter6);
assertEquals(ReturnCode.NEXT_COL, filterList.filterCell(kv1));
filterList = new FilterList(Operator.MUST_PASS_ALL, filter3, filter1);
assertEquals(ReturnCode.INCLUDE_AND_SEEK_NEXT_ROW, filterList.filterCell(kv1));
filterList = new FilterList(Operator.MUST_PASS_ALL, filter3, filter2, filter1, filter5);
assertEquals(ReturnCode.NEXT_ROW, filterList.filterCell(kv1));
filterList = new FilterList(Operator.MUST_PASS_ALL, filter2,
new FilterList(Operator.MUST_PASS_ALL, filter3, filter4));
assertEquals(ReturnCode.NEXT_ROW, filterList.filterCell(kv1));
filterList = new FilterList(Operator.MUST_PASS_ALL, filter3, filter7);
assertEquals(ReturnCode.NEXT_ROW, filterList.filterCell(kv1));
}
@Test
public void testTheMinimalRule() throws IOException {
KeyValue kv1 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam"), Bytes.toBytes("a"), 1,
Bytes.toBytes("value"));
MockFilter filter1 = new MockFilter(ReturnCode.INCLUDE);
MockFilter filter2 = new MockFilter(ReturnCode.INCLUDE_AND_NEXT_COL);
MockFilter filter3 = new MockFilter(ReturnCode.INCLUDE_AND_SEEK_NEXT_ROW);
MockFilter filter4 = new MockFilter(ReturnCode.NEXT_COL);
MockFilter filter5 = new MockFilter(ReturnCode.SKIP);
MockFilter filter6 = new MockFilter(ReturnCode.SEEK_NEXT_USING_HINT);
FilterList filterList = new FilterList(Operator.MUST_PASS_ONE, filter1, filter2);
assertEquals(ReturnCode.INCLUDE, filterList.filterCell(kv1));
filterList = new FilterList(Operator.MUST_PASS_ONE, filter2, filter3);
assertEquals(ReturnCode.INCLUDE_AND_NEXT_COL, filterList.filterCell(kv1));
filterList = new FilterList(Operator.MUST_PASS_ONE, filter4, filter5, filter6);
assertEquals(ReturnCode.SKIP, filterList.filterCell(kv1));
filterList = new FilterList(Operator.MUST_PASS_ONE, filter4, filter6);
assertEquals(ReturnCode.SKIP, filterList.filterCell(kv1));
filterList = new FilterList(Operator.MUST_PASS_ONE, filter3, filter1);
assertEquals(ReturnCode.INCLUDE, filterList.filterCell(kv1));
filterList = new FilterList(Operator.MUST_PASS_ONE, filter3, filter2, filter1, filter5);
assertEquals(ReturnCode.INCLUDE, filterList.filterCell(kv1));
filterList = new FilterList(Operator.MUST_PASS_ONE, filter2,
new FilterList(Operator.MUST_PASS_ONE, filter3, filter4));
assertEquals(ReturnCode.INCLUDE_AND_NEXT_COL, filterList.filterCell(kv1));
filterList = new FilterList(Operator.MUST_PASS_ONE, filter2,
new FilterList(Operator.MUST_PASS_ONE, filter3, filter4));
assertEquals(ReturnCode.INCLUDE_AND_NEXT_COL, filterList.filterCell(kv1));
filterList = new FilterList(Operator.MUST_PASS_ONE, filter6, filter6);
assertEquals(ReturnCode.SEEK_NEXT_USING_HINT, filterList.filterCell(kv1));
}
static class MockSeekHintFilter extends FilterBase {
private Cell returnCell;
public MockSeekHintFilter(Cell returnCell) {
this.returnCell = returnCell;
}
@Override
public ReturnCode filterCell(final Cell v) throws IOException {
return ReturnCode.SEEK_NEXT_USING_HINT;
}
@Override
public Cell getNextCellHint(Cell currentCell) throws IOException {
return this.returnCell;
}
@Override
public boolean equals(Object obj) {
if(obj == null || !(obj instanceof MockSeekHintFilter)){
return false;
}
if(obj == this){
return true;
}
MockSeekHintFilter f = (MockSeekHintFilter)obj;
return this.returnCell.equals(f.returnCell);
}
@Override
public int hashCode() {
return Objects.hash(this.returnCell);
}
}
@Test
public void testReversedFilterListWithMockSeekHintFilter() throws IOException {
KeyValue kv1 = new KeyValue(Bytes.toBytes("row1"), Bytes.toBytes("fam"), Bytes.toBytes("a"), 1,
Bytes.toBytes("value"));
KeyValue kv2 = new KeyValue(Bytes.toBytes("row2"), Bytes.toBytes("fam"), Bytes.toBytes("a"), 1,
Bytes.toBytes("value"));
KeyValue kv3 = new KeyValue(Bytes.toBytes("row3"), Bytes.toBytes("fam"), Bytes.toBytes("a"), 1,
Bytes.toBytes("value"));
Filter filter1 = new MockSeekHintFilter(kv1);
filter1.setReversed(true);
Filter filter2 = new MockSeekHintFilter(kv2);
filter2.setReversed(true);
Filter filter3 = new MockSeekHintFilter(kv3);
filter3.setReversed(true);
FilterList filterList = new FilterList(Operator.MUST_PASS_ONE);
filterList.setReversed(true);
filterList.addFilter(filter1);
filterList.addFilter(filter2);
filterList.addFilter(filter3);
Assert.assertEquals(ReturnCode.SEEK_NEXT_USING_HINT, filterList.filterCell(kv1));
Assert.assertEquals(kv3, filterList.getNextCellHint(kv1));
filterList = new FilterList(Operator.MUST_PASS_ALL);
filterList.setReversed(true);
filterList.addFilter(filter1);
filterList.addFilter(filter2);
filterList.addFilter(filter3);
Assert.assertEquals(ReturnCode.SEEK_NEXT_USING_HINT, filterList.filterCell(kv1));
Assert.assertEquals(kv1, filterList.getNextCellHint(kv1));
}
@Test
public void testReversedFilterListWithOR() throws IOException {
byte[] r22 = Bytes.toBytes("Row22");
byte[] r2 = Bytes.toBytes("Row2");
byte[] r1 = Bytes.toBytes("Row1");
FilterList filterList = new FilterList(FilterList.Operator.MUST_PASS_ONE);
filterList.setReversed(true);
PrefixFilter prefixFilter = new PrefixFilter(r2);
prefixFilter.setReversed(true);
filterList.addFilter(prefixFilter);
filterList.filterRowKey(KeyValueUtil.createFirstOnRow(r22));
assertEquals(ReturnCode.INCLUDE, filterList.filterCell(new KeyValue(r22, r22, r22)));
assertEquals(ReturnCode.INCLUDE, filterList.filterCell(new KeyValue(r2, r2, r2)));
filterList.reset();
filterList.filterRowKey(KeyValueUtil.createFirstOnRow(r1));
assertEquals(ReturnCode.SKIP, filterList.filterCell(new KeyValue(r1, r1, r1)));
filterList = new FilterList(FilterList.Operator.MUST_PASS_ONE);
filterList.setReversed(true);
AlwaysNextColFilter alwaysNextColFilter = new AlwaysNextColFilter();
alwaysNextColFilter.setReversed(true);
prefixFilter = new PrefixFilter(r2);
prefixFilter.setReversed(true);
filterList.addFilter(alwaysNextColFilter);
filterList.addFilter(prefixFilter);
filterList.filterRowKey(KeyValueUtil.createFirstOnRow(r22));
assertEquals(ReturnCode.INCLUDE, filterList.filterCell(new KeyValue(r22, r22, r22)));
assertEquals(ReturnCode.INCLUDE, filterList.filterCell(new KeyValue(r2, r2, r2)));
filterList.reset();
filterList.filterRowKey(KeyValueUtil.createFirstOnRow(r1));
assertEquals(ReturnCode.NEXT_COL, filterList.filterCell(new KeyValue(r1, r1, r1)));
}
@Test
public void testKeyOnlyFilterTransformCell() throws IOException {
Cell c;
KeyValue kv1 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("cf"), Bytes.toBytes("column1"),
1, Bytes.toBytes("value1"));
KeyValue kv2 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("cf"), Bytes.toBytes("column1"),
2, Bytes.toBytes("value2"));
Filter filter1 = new SingleColumnValueFilter(Bytes.toBytes("cf"), Bytes.toBytes("column1"),
CompareOperator.EQUAL, Bytes.toBytes("value1"));
Filter filter2 = new SingleColumnValueFilter(Bytes.toBytes("cf"), Bytes.toBytes("column1"),
CompareOperator.EQUAL, Bytes.toBytes("value2"));
FilterList internalFilterList = new FilterList(Operator.MUST_PASS_ONE, filter1, filter2);
FilterList keyOnlyFilterFirst =
new FilterList(Operator.MUST_PASS_ALL, new KeyOnlyFilter(), internalFilterList);
assertEquals(ReturnCode.INCLUDE, keyOnlyFilterFirst.filterCell(kv1));
c = keyOnlyFilterFirst.transformCell(kv1);
assertEquals(0, c.getValueLength());
assertEquals(ReturnCode.INCLUDE, keyOnlyFilterFirst.filterCell(kv2));
c = keyOnlyFilterFirst.transformCell(kv2);
assertEquals(0, c.getValueLength());
internalFilterList.reset();
FilterList keyOnlyFilterLast =
new FilterList(Operator.MUST_PASS_ALL, new KeyOnlyFilter(), internalFilterList);
assertEquals(ReturnCode.INCLUDE, keyOnlyFilterLast.filterCell(kv1));
c = keyOnlyFilterLast.transformCell(kv1);
assertEquals(0, c.getValueLength());
assertEquals(ReturnCode.INCLUDE, keyOnlyFilterLast.filterCell(kv2));
c = keyOnlyFilterLast.transformCell(kv2);
assertEquals(0, c.getValueLength());
}
@Test
public void testEmptyFilterListTransformCell() throws IOException {
KeyValue kv = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("cf"), Bytes.toBytes("column1"),
1, Bytes.toBytes("value"));
FilterList filterList = new FilterList(Operator.MUST_PASS_ALL);
assertEquals(ReturnCode.INCLUDE, filterList.filterCell(kv));
assertEquals(kv, filterList.transformCell(kv));
filterList = new FilterList(Operator.MUST_PASS_ONE);
assertEquals(ReturnCode.INCLUDE, filterList.filterCell(kv));
assertEquals(kv, filterList.transformCell(kv));
}
private static class MockNextRowFilter extends FilterBase {
private int hitCount = 0;
@Override
public ReturnCode filterCell(final Cell v) throws IOException {
hitCount++;
return ReturnCode.NEXT_ROW;
}
public int getHitCount() {
return hitCount;
}
}
@Test
public void testRowCountFilter() throws IOException {
KeyValue kv1 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam1"), Bytes.toBytes("a"), 1,
Bytes.toBytes("value"));
KeyValue kv2 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam2"), Bytes.toBytes("a"), 2,
Bytes.toBytes("value"));
MockNextRowFilter mockNextRowFilter = new MockNextRowFilter();
FilterList filter = new FilterList(Operator.MUST_PASS_ONE, mockNextRowFilter);
filter.filterCell(kv1);
filter.filterCell(kv2);
assertEquals(2, mockNextRowFilter.getHitCount());
}
private static class TransformFilter extends FilterBase {
private ReturnCode targetRetCode;
private boolean transformed = false;
public TransformFilter(ReturnCode targetRetCode) {
this.targetRetCode = targetRetCode;
}
@Override
public ReturnCode filterCell(final Cell v) throws IOException {
return targetRetCode;
}
@Override
public Cell transformCell(Cell c) throws IOException {
transformed = true;
return super.transformCell(c);
}
public boolean getTransformed() {
return this.transformed;
}
@Override
public boolean equals(Object obj) {
if(!(obj instanceof TransformFilter)){
return false;
}
if (obj == this) {
return true;
}
TransformFilter f = (TransformFilter)obj;
return this.targetRetCode.equals(f.targetRetCode);
}
@Override
public int hashCode() {
return Objects.hash(this.targetRetCode);
}
}
@Test
public void testTransformCell() throws IOException {
KeyValue kv =
new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("cf"), Bytes.toBytes("column1"), 1,
Bytes.toBytes("value"));
// case MUST_PASS_ONE
TransformFilter filter1 = new TransformFilter(ReturnCode.INCLUDE);
TransformFilter filter2 = new TransformFilter(ReturnCode.NEXT_ROW);
TransformFilter filter3 = new TransformFilter(ReturnCode.SEEK_NEXT_USING_HINT);
FilterList filterList = new FilterList(Operator.MUST_PASS_ONE, filter1, filter2, filter3);
Assert.assertEquals(ReturnCode.INCLUDE, filterList.filterCell(kv));
Assert.assertEquals(kv, filterList.transformCell(kv));
Assert.assertEquals(true, filter1.getTransformed());
Assert.assertEquals(false, filter2.getTransformed());
Assert.assertEquals(false, filter3.getTransformed());
// case MUST_PASS_ALL
filter1 = new TransformFilter(ReturnCode.INCLUDE);
filter2 = new TransformFilter(ReturnCode.INCLUDE_AND_SEEK_NEXT_ROW);
filter3 = new TransformFilter(ReturnCode.INCLUDE_AND_NEXT_COL);
filterList = new FilterList(Operator.MUST_PASS_ALL, filter1, filter2, filter3);
Assert.assertEquals(ReturnCode.INCLUDE_AND_SEEK_NEXT_ROW, filterList.filterCell(kv));
Assert.assertEquals(kv, filterList.transformCell(kv));
Assert.assertEquals(true, filter1.getTransformed());
Assert.assertEquals(true, filter2.getTransformed());
Assert.assertEquals(true, filter3.getTransformed());
}
@Test
public void testFilterListWithORWhenPassingCellMismatchPreviousRC() throws IOException {
// Mainly test FilterListWithOR#calculateReturnCodeByPrevCellAndRC method with two sub-filters.
KeyValue kv1 = new KeyValue(Bytes.toBytes("row1"), Bytes.toBytes("fam"), Bytes.toBytes("a"),
100, Bytes.toBytes("value"));
KeyValue kv2 = new KeyValue(Bytes.toBytes("row1"), Bytes.toBytes("fam"), Bytes.toBytes("a"), 99,
Bytes.toBytes("value"));
KeyValue kv3 = new KeyValue(Bytes.toBytes("row1"), Bytes.toBytes("fam"), Bytes.toBytes("b"), 1,
Bytes.toBytes("value"));
KeyValue kv4 = new KeyValue(Bytes.toBytes("row1"), Bytes.toBytes("fan"), Bytes.toBytes("a"), 1,
Bytes.toBytes("value"));
Filter subFilter1 = Mockito.mock(FilterBase.class);
Mockito.when(subFilter1.filterCell(kv1)).thenReturn(ReturnCode.INCLUDE_AND_NEXT_COL);
Mockito.when(subFilter1.filterCell(kv2)).thenReturn(ReturnCode.NEXT_COL);
Mockito.when(subFilter1.filterCell(kv3)).thenReturn(ReturnCode.INCLUDE_AND_NEXT_COL);
Mockito.when(subFilter1.filterCell(kv4)).thenReturn(ReturnCode.INCLUDE_AND_NEXT_COL);
Filter subFilter2 = Mockito.mock(FilterBase.class);
Mockito.when(subFilter2.filterCell(kv1)).thenReturn(ReturnCode.SKIP);
Mockito.when(subFilter2.filterCell(kv2)).thenReturn(ReturnCode.NEXT_ROW);
Mockito.when(subFilter2.filterCell(kv3)).thenReturn(ReturnCode.NEXT_ROW);
Mockito.when(subFilter2.filterCell(kv4)).thenReturn(ReturnCode.INCLUDE_AND_SEEK_NEXT_ROW);
Filter filterList = new FilterList(Operator.MUST_PASS_ONE, subFilter1, subFilter2);
Assert.assertEquals(ReturnCode.INCLUDE, filterList.filterCell(kv1));
Assert.assertEquals(ReturnCode.NEXT_COL, filterList.filterCell(kv2));
Assert.assertEquals(ReturnCode.INCLUDE_AND_NEXT_COL, filterList.filterCell(kv3));
Assert.assertEquals(ReturnCode.INCLUDE_AND_NEXT_COL, filterList.filterCell(kv4));
// One sub-filter will filterAllRemaining but other sub-filter will return SEEK_HINT
subFilter1 = Mockito.mock(FilterBase.class);
Mockito.when(subFilter1.filterAllRemaining()).thenReturn(true);
Mockito.when(subFilter1.filterCell(kv1)).thenReturn(ReturnCode.NEXT_ROW);
subFilter2 = Mockito.mock(FilterBase.class);
Mockito.when(subFilter2.filterCell(kv1)).thenReturn(ReturnCode.SEEK_NEXT_USING_HINT);
filterList = new FilterList(Operator.MUST_PASS_ONE, subFilter1, subFilter2);
Assert.assertEquals(ReturnCode.SEEK_NEXT_USING_HINT, filterList.filterCell(kv1));
// Two sub-filter returns SEEK_NEXT_USING_HINT, then we should return SEEK_NEXT_USING_HINT.
subFilter1 = Mockito.mock(FilterBase.class);
Mockito.when(subFilter1.filterCell(kv1)).thenReturn(ReturnCode.SEEK_NEXT_USING_HINT);
subFilter2 = Mockito.mock(FilterBase.class);
Mockito.when(subFilter2.filterCell(kv1)).thenReturn(ReturnCode.SEEK_NEXT_USING_HINT);
filterList = new FilterList(Operator.MUST_PASS_ONE, subFilter1, subFilter2);
Assert.assertEquals(ReturnCode.SEEK_NEXT_USING_HINT, filterList.filterCell(kv1));
}
}