001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.filter;
019
020import static org.junit.jupiter.api.Assertions.assertEquals;
021import static org.junit.jupiter.api.Assertions.assertFalse;
022import static org.junit.jupiter.api.Assertions.assertNull;
023import static org.junit.jupiter.api.Assertions.assertTrue;
024import static org.junit.jupiter.api.Assertions.fail;
025
026import java.io.IOException;
027import java.util.ArrayList;
028import java.util.Arrays;
029import java.util.List;
030import java.util.Objects;
031import org.apache.hadoop.hbase.Cell;
032import org.apache.hadoop.hbase.CellComparator;
033import org.apache.hadoop.hbase.CompareOperator;
034import org.apache.hadoop.hbase.ExtendedCell;
035import org.apache.hadoop.hbase.KeyValue;
036import org.apache.hadoop.hbase.KeyValueUtil;
037import org.apache.hadoop.hbase.exceptions.DeserializationException;
038import org.apache.hadoop.hbase.filter.Filter.ReturnCode;
039import org.apache.hadoop.hbase.filter.FilterList.Operator;
040import org.apache.hadoop.hbase.testclassification.FilterTests;
041import org.apache.hadoop.hbase.testclassification.MediumTests;
042import org.apache.hadoop.hbase.util.Bytes;
043import org.junit.jupiter.api.Tag;
044import org.junit.jupiter.api.Test;
045import org.mockito.Mockito;
046
047import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
048
049import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
050
051@Tag(FilterTests.TAG)
052@Tag(MediumTests.TAG)
053public class TestFilterList {
054
055  static final int MAX_PAGES = 2;
056
057  @Test
058  public void testAddFilter() throws Exception {
059    Filter filter1 = new FirstKeyOnlyFilter();
060    Filter filter2 = new FirstKeyOnlyFilter();
061
062    FilterList filterList = new FilterList(filter1, filter2);
063    filterList.addFilter(new FirstKeyOnlyFilter());
064
065    filterList = new FilterList(Arrays.asList(filter1, filter2));
066    filterList.addFilter(new FirstKeyOnlyFilter());
067
068    filterList = new FilterList(Operator.MUST_PASS_ALL, filter1, filter2);
069    filterList.addFilter(new FirstKeyOnlyFilter());
070
071    filterList = new FilterList(Operator.MUST_PASS_ALL, Arrays.asList(filter1, filter2));
072    filterList.addFilter(new FirstKeyOnlyFilter());
073
074    filterList.setReversed(false);
075    FirstKeyOnlyFilter f = new FirstKeyOnlyFilter();
076    f.setReversed(true);
077    try {
078      filterList.addFilter(f);
079      fail("The IllegalArgumentException should be thrown because the added filter is reversed");
080    } catch (IllegalArgumentException e) {
081    }
082
083  }
084
085  @Test
086  public void testConstruction() {
087    FirstKeyOnlyFilter f1 = new FirstKeyOnlyFilter();
088    FirstKeyOnlyFilter f2 = new FirstKeyOnlyFilter();
089    f1.setReversed(true);
090    f2.setReversed(false);
091
092    try {
093      FilterList ff = new FilterList(f1, f2);
094      fail("The IllegalArgumentException should be thrown");
095    } catch (IllegalArgumentException e) {
096    }
097
098    try {
099      FilterList ff = new FilterList(Arrays.asList(f1, f2));
100      fail("The IllegalArgumentException should be thrown because the added filter is reversed");
101    } catch (IllegalArgumentException e) {
102    }
103
104    try {
105      FilterList ff = new FilterList(FilterList.Operator.MUST_PASS_ALL, Arrays.asList(f1, f2));
106      fail("The IllegalArgumentException should be thrown because the added filter is reversed");
107    } catch (IllegalArgumentException e) {
108    }
109
110    try {
111      FilterList ff = new FilterList(FilterList.Operator.MUST_PASS_ALL, f1, f2);
112      fail("The IllegalArgumentException should be thrown because the added filter is reversed");
113    } catch (IllegalArgumentException e) {
114    }
115  }
116
117  /**
118   * Test "must pass one"
119   */
120  @Test
121  public void testMPONE() throws Exception {
122    mpOneTest(getFilterMPONE());
123  }
124
125  private Filter getFilterMPONE() {
126    List<Filter> filters = new ArrayList<>();
127    filters.add(new PageFilter(MAX_PAGES));
128    filters.add(new WhileMatchFilter(new PrefixFilter(Bytes.toBytes("yyy"))));
129    Filter filterMPONE = new FilterList(FilterList.Operator.MUST_PASS_ONE, filters);
130    return filterMPONE;
131  }
132
133  private void mpOneTest(Filter filterMPONE) throws Exception {
134    /*
135     * Filter must do all below steps: <ul> <li>{@link #reset()}</li> <li>{@link
136     * #filterAllRemaining()} -> true indicates scan is over, false, keep going on.</li> <li>{@link
137     * #filterRowKey(byte[],int,int)} -> true to drop this row, if false, we will also call</li>
138     * <li>{@link #filterCell(org.apache.hadoop.hbase.KeyValue)} -> true to drop this cell</li>
139     * <li>{@link #filterRow()} -> last chance to drop entire row based on the sequence of
140     * filterValue() calls. Eg: filter a row if it doesn't contain a specified column. </li> </ul>
141     */
142    filterMPONE.reset();
143    assertFalse(filterMPONE.filterAllRemaining());
144
145    /* Will pass both */
146    byte[] rowkey = Bytes.toBytes("yyyyyyyyy");
147    for (int i = 0; i < MAX_PAGES - 1; i++) {
148      assertFalse(filterMPONE.filterRowKey(KeyValueUtil.createFirstOnRow(rowkey)));
149      KeyValue kv = new KeyValue(rowkey, rowkey, Bytes.toBytes(i), Bytes.toBytes(i));
150      assertTrue(Filter.ReturnCode.INCLUDE == filterMPONE.filterCell(kv));
151      assertFalse(filterMPONE.filterRow());
152    }
153
154    /* Only pass PageFilter */
155    rowkey = Bytes.toBytes("z");
156    assertFalse(filterMPONE.filterRowKey(KeyValueUtil.createFirstOnRow(rowkey)));
157    KeyValue kv = new KeyValue(rowkey, rowkey, Bytes.toBytes(0), Bytes.toBytes(0));
158    assertTrue(Filter.ReturnCode.INCLUDE == filterMPONE.filterCell(kv));
159    assertFalse(filterMPONE.filterRow());
160
161    /* reach MAX_PAGES already, should filter any rows */
162    rowkey = Bytes.toBytes("yyy");
163    assertTrue(filterMPONE.filterRowKey(KeyValueUtil.createFirstOnRow(rowkey)));
164    kv = new KeyValue(rowkey, rowkey, Bytes.toBytes(0), Bytes.toBytes(0));
165    assertFalse(Filter.ReturnCode.INCLUDE == filterMPONE.filterCell(kv));
166    // FilterList.filterRow() returns true because previously "z" was filtered out (return true) by
167    // PrefixFilter.filterRowKey()
168    assertTrue(filterMPONE.filterRow());
169
170    /* We should filter any row */
171    rowkey = Bytes.toBytes("z");
172    assertTrue(filterMPONE.filterRowKey(KeyValueUtil.createFirstOnRow(rowkey)));
173    assertTrue(filterMPONE.filterAllRemaining());
174  }
175
176  /**
177   * Test "must pass all"
178   */
179  @Test
180  public void testMPALL() throws Exception {
181    mpAllTest(getMPALLFilter());
182  }
183
184  private Filter getMPALLFilter() {
185    List<Filter> filters = new ArrayList<>();
186    filters.add(new PageFilter(MAX_PAGES));
187    filters.add(new WhileMatchFilter(new PrefixFilter(Bytes.toBytes("yyy"))));
188    Filter filterMPALL = new FilterList(FilterList.Operator.MUST_PASS_ALL, filters);
189    return filterMPALL;
190  }
191
192  private void mpAllTest(Filter filterMPALL) throws Exception {
193    /*
194     * Filter must do all below steps: <ul> <li>{@link #reset()}</li> <li>{@link
195     * #filterAllRemaining()} -> true indicates scan is over, false, keep going on.</li> <li>{@link
196     * #filterRowKey(byte[],int,int)} -> true to drop this row, if false, we will also call</li>
197     * <li>{@link #filterCell(org.apache.hadoop.hbase.KeyValue)} -> true to drop this cell</li>
198     * <li>{@link #filterRow()} -> last chance to drop entire row based on the sequence of
199     * filterValue() calls. Eg: filter a row if it doesn't contain a specified column. </li> </ul>
200     */
201    filterMPALL.reset();
202    assertFalse(filterMPALL.filterAllRemaining());
203    byte[] rowkey = Bytes.toBytes("yyyyyyyyy");
204    for (int i = 0; i < MAX_PAGES - 1; i++) {
205      assertFalse(filterMPALL.filterRowKey(KeyValueUtil.createFirstOnRow(rowkey)));
206      KeyValue kv = new KeyValue(rowkey, rowkey, Bytes.toBytes(i), Bytes.toBytes(i));
207      assertTrue(Filter.ReturnCode.INCLUDE == filterMPALL.filterCell(kv));
208    }
209    filterMPALL.reset();
210    rowkey = Bytes.toBytes("z");
211    assertTrue(filterMPALL.filterRowKey(KeyValueUtil.createFirstOnRow(rowkey)));
212    // Should fail here; row should be filtered out.
213    KeyValue kv = new KeyValue(rowkey, rowkey, rowkey, rowkey);
214    assertTrue(Filter.ReturnCode.NEXT_ROW == filterMPALL.filterCell(kv));
215  }
216
217  /**
218   * Test list ordering
219   */
220  @Test
221  public void testOrdering() throws Exception {
222    orderingTest(getOrderingFilter());
223  }
224
225  public Filter getOrderingFilter() {
226    List<Filter> filters = new ArrayList<>();
227    filters.add(new PrefixFilter(Bytes.toBytes("yyy")));
228    filters.add(new PageFilter(MAX_PAGES));
229    Filter filterMPONE = new FilterList(FilterList.Operator.MUST_PASS_ONE, filters);
230    return filterMPONE;
231  }
232
233  public void orderingTest(Filter filterMPONE) throws Exception {
234    /*
235     * Filter must do all below steps: <ul> <li>{@link #reset()}</li> <li>{@link
236     * #filterAllRemaining()} -> true indicates scan is over, false, keep going on.</li> <li>{@link
237     * #filterRowKey(byte[],int,int)} -> true to drop this row, if false, we will also call</li>
238     * <li>{@link #filterCell(org.apache.hadoop.hbase.KeyValue)} -> true to drop this key/value</li>
239     * <li>{@link #filterRow()} -> last chance to drop entire row based on the sequence of
240     * filterValue() calls. Eg: filter a row if it doesn't contain a specified column. </li> </ul>
241     */
242    filterMPONE.reset();
243    assertFalse(filterMPONE.filterAllRemaining());
244
245    /* We should be able to fill MAX_PAGES without incrementing page counter */
246    byte[] rowkey = Bytes.toBytes("yyyyyyyy");
247    for (int i = 0; i < MAX_PAGES; i++) {
248      assertFalse(filterMPONE.filterRowKey(KeyValueUtil.createFirstOnRow(rowkey)));
249      KeyValue kv = new KeyValue(rowkey, rowkey, Bytes.toBytes(i), Bytes.toBytes(i));
250      assertTrue(Filter.ReturnCode.INCLUDE == filterMPONE.filterCell(kv));
251      assertFalse(filterMPONE.filterRow());
252    }
253
254    /* Now let's fill the page filter */
255    rowkey = Bytes.toBytes("xxxxxxx");
256    for (int i = 0; i < MAX_PAGES; i++) {
257      assertFalse(filterMPONE.filterRowKey(KeyValueUtil.createFirstOnRow(rowkey)));
258      KeyValue kv = new KeyValue(rowkey, rowkey, Bytes.toBytes(i), Bytes.toBytes(i));
259      assertTrue(Filter.ReturnCode.INCLUDE == filterMPONE.filterCell(kv));
260      assertFalse(filterMPONE.filterRow());
261    }
262
263    /* We should still be able to include even though page filter is at max */
264    rowkey = Bytes.toBytes("yyy");
265    for (int i = 0; i < MAX_PAGES; i++) {
266      assertFalse(filterMPONE.filterRowKey(KeyValueUtil.createFirstOnRow(rowkey)));
267      KeyValue kv = new KeyValue(rowkey, rowkey, Bytes.toBytes(i), Bytes.toBytes(i));
268      assertTrue(Filter.ReturnCode.INCLUDE == filterMPONE.filterCell(kv));
269      assertFalse(filterMPONE.filterRow());
270    }
271  }
272
273  /**
274   * When we do a "MUST_PASS_ONE" (a logical 'OR') of the above two filters we expect to get the
275   * same result as the 'prefix' only result.
276   */
277  @Test
278  public void testFilterListTwoFiltersMustPassOne() throws Exception {
279    byte[] r1 = Bytes.toBytes("Row1");
280    byte[] r11 = Bytes.toBytes("Row11");
281    byte[] r2 = Bytes.toBytes("Row2");
282
283    FilterList flist = new FilterList(FilterList.Operator.MUST_PASS_ONE);
284    flist.addFilter(new PrefixFilter(r1));
285    flist.filterRowKey(KeyValueUtil.createFirstOnRow(r1));
286    assertEquals(ReturnCode.INCLUDE, flist.filterCell(new KeyValue(r1, r1, r1)));
287    assertEquals(ReturnCode.INCLUDE, flist.filterCell(new KeyValue(r11, r11, r11)));
288
289    flist.reset();
290    flist.filterRowKey(KeyValueUtil.createFirstOnRow(r2));
291    assertEquals(ReturnCode.SKIP, flist.filterCell(new KeyValue(r2, r2, r2)));
292
293    flist = new FilterList(FilterList.Operator.MUST_PASS_ONE);
294    flist.addFilter(new AlwaysNextColFilter());
295    flist.addFilter(new PrefixFilter(r1));
296    flist.filterRowKey(KeyValueUtil.createFirstOnRow(r1));
297    assertEquals(ReturnCode.INCLUDE, flist.filterCell(new KeyValue(r1, r1, r1)));
298    assertEquals(ReturnCode.INCLUDE, flist.filterCell(new KeyValue(r11, r11, r11)));
299
300    flist.reset();
301    flist.filterRowKey(KeyValueUtil.createFirstOnRow(r2));
302    assertEquals(ReturnCode.NEXT_COL, flist.filterCell(new KeyValue(r2, r2, r2)));
303  }
304
305  /**
306   * When we do a "MUST_PASS_ONE" (a logical 'OR') of the two filters we expect to get the same
307   * result as the inclusive stop result.
308   */
309  @Test
310  public void testFilterListWithInclusiveStopFilterMustPassOne() throws Exception {
311    byte[] r1 = Bytes.toBytes("Row1");
312    byte[] r11 = Bytes.toBytes("Row11");
313    byte[] r2 = Bytes.toBytes("Row2");
314
315    FilterList flist = new FilterList(FilterList.Operator.MUST_PASS_ONE);
316    flist.addFilter(new AlwaysNextColFilter());
317    flist.addFilter(new InclusiveStopFilter(r1));
318    flist.filterRowKey(KeyValueUtil.createFirstOnRow(r1));
319    assertEquals(ReturnCode.INCLUDE, flist.filterCell(new KeyValue(r1, r1, r1)));
320    assertEquals(ReturnCode.INCLUDE, flist.filterCell(new KeyValue(r11, r11, r11)));
321
322    flist.reset();
323    flist.filterRowKey(KeyValueUtil.createFirstOnRow(r2));
324    assertEquals(ReturnCode.NEXT_COL, flist.filterCell(new KeyValue(r2, r2, r2)));
325  }
326
327  public static class AlwaysNextColFilter extends FilterBase {
328    public AlwaysNextColFilter() {
329      super();
330    }
331
332    @Override
333    public ReturnCode filterCell(final Cell v) {
334      return ReturnCode.NEXT_COL;
335    }
336
337    public static AlwaysNextColFilter parseFrom(final byte[] pbBytes)
338      throws DeserializationException {
339      return new AlwaysNextColFilter();
340    }
341  }
342
343  /**
344   * Test serialization
345   */
346  @Test
347  public void testSerialization() throws Exception {
348    List<Filter> filters = new ArrayList<>();
349    filters.add(new PageFilter(MAX_PAGES));
350    filters.add(new WhileMatchFilter(new PrefixFilter(Bytes.toBytes("yyy"))));
351    Filter filterMPALL = new FilterList(FilterList.Operator.MUST_PASS_ALL, filters);
352
353    // Decompose filterMPALL to bytes.
354    byte[] buffer = filterMPALL.toByteArray();
355
356    // Recompose filterMPALL.
357    FilterList newFilter = FilterList.parseFrom(buffer);
358
359    // Run tests
360    mpOneTest(ProtobufUtil.toFilter(ProtobufUtil.toFilter(getFilterMPONE())));
361    mpAllTest(ProtobufUtil.toFilter(ProtobufUtil.toFilter(getMPALLFilter())));
362    orderingTest(ProtobufUtil.toFilter(ProtobufUtil.toFilter(getOrderingFilter())));
363  }
364
365  /**
366   * Test filterCell logic.
367   */
368  @Test
369  public void testFilterCell() throws Exception {
370    Filter includeFilter = new FilterBase() {
371      @Override
372      public Filter.ReturnCode filterCell(final Cell v) {
373        return Filter.ReturnCode.INCLUDE;
374      }
375    };
376
377    Filter alternateFilter = new FilterBase() {
378      boolean returnInclude = true;
379
380      @Override
381      public Filter.ReturnCode filterCell(final Cell v) {
382        Filter.ReturnCode returnCode =
383          returnInclude ? Filter.ReturnCode.INCLUDE : Filter.ReturnCode.SKIP;
384        returnInclude = !returnInclude;
385        return returnCode;
386      }
387    };
388
389    Filter alternateIncludeFilter = new FilterBase() {
390      boolean returnIncludeOnly = false;
391
392      @Override
393      public Filter.ReturnCode filterCell(final Cell v) {
394        Filter.ReturnCode returnCode =
395          returnIncludeOnly ? Filter.ReturnCode.INCLUDE : Filter.ReturnCode.INCLUDE_AND_NEXT_COL;
396        returnIncludeOnly = !returnIncludeOnly;
397        return returnCode;
398      }
399    };
400
401    // Check must pass one filter.
402    FilterList mpOnefilterList = new FilterList(Operator.MUST_PASS_ONE,
403      Arrays.asList(new Filter[] { includeFilter, alternateIncludeFilter, alternateFilter }));
404    // INCLUDE, INCLUDE, INCLUDE_AND_NEXT_COL.
405    assertEquals(ReturnCode.INCLUDE, mpOnefilterList.filterCell(null));
406    // INCLUDE, SKIP, INCLUDE.
407    assertEquals(Filter.ReturnCode.INCLUDE, mpOnefilterList.filterCell(null));
408
409    // Check must pass all filter.
410    FilterList mpAllfilterList = new FilterList(Operator.MUST_PASS_ALL,
411      Arrays.asList(new Filter[] { includeFilter, alternateIncludeFilter, alternateFilter }));
412    // INCLUDE, INCLUDE, INCLUDE_AND_NEXT_COL.
413    assertEquals(Filter.ReturnCode.INCLUDE_AND_NEXT_COL, mpAllfilterList.filterCell(null));
414    // INCLUDE, SKIP, INCLUDE.
415    assertEquals(Filter.ReturnCode.SKIP, mpAllfilterList.filterCell(null));
416  }
417
418  /**
419   * Test pass-thru of hints.
420   */
421  @Test
422  public void testHintPassThru() throws Exception {
423
424    final KeyValue minKeyValue = new KeyValue(Bytes.toBytes(0L), null, null);
425    final KeyValue maxKeyValue = new KeyValue(Bytes.toBytes(Long.MAX_VALUE), null, null);
426
427    Filter filterNoHint = new FilterBase() {
428      @Override
429      public byte[] toByteArray() {
430        return null;
431      }
432
433      @Override
434      public ReturnCode filterCell(final Cell ignored) throws IOException {
435        return ReturnCode.INCLUDE;
436      }
437    };
438
439    Filter filterMinHint = new FilterBase() {
440      @Override
441      public ReturnCode filterCell(final Cell ignored) {
442        return ReturnCode.SEEK_NEXT_USING_HINT;
443      }
444
445      @Override
446      public Cell getNextCellHint(Cell currentKV) {
447        return minKeyValue;
448      }
449
450      @Override
451      public byte[] toByteArray() {
452        return null;
453      }
454    };
455
456    Filter filterMaxHint = new FilterBase() {
457      @Override
458      public ReturnCode filterCell(final Cell ignored) {
459        return ReturnCode.SEEK_NEXT_USING_HINT;
460      }
461
462      @Override
463      public Cell getNextCellHint(Cell cell) {
464        return new KeyValue(Bytes.toBytes(Long.MAX_VALUE), null, null);
465      }
466
467      @Override
468      public byte[] toByteArray() {
469        return null;
470      }
471    };
472
473    CellComparator comparator = CellComparator.getInstance();
474    // MUST PASS ONE
475
476    // Should take the min if given two hints
477    FilterList filterList = new FilterList(Operator.MUST_PASS_ONE,
478      Arrays.asList(new Filter[] { filterMinHint, filterMaxHint }));
479    assertEquals(0, comparator.compare(filterList.getNextCellHint(null), minKeyValue));
480
481    // Should have no hint if any filter has no hint
482    filterList = new FilterList(Operator.MUST_PASS_ONE,
483      Arrays.asList(new Filter[] { filterMinHint, filterMaxHint, filterNoHint }));
484    assertNull(filterList.getNextCellHint(null));
485    filterList = new FilterList(Operator.MUST_PASS_ONE,
486      Arrays.asList(new Filter[] { filterNoHint, filterMaxHint }));
487    assertNull(filterList.getNextCellHint(null));
488
489    // Should give max hint if its the only one
490    filterList = new FilterList(Operator.MUST_PASS_ONE,
491      Arrays.asList(new Filter[] { filterMaxHint, filterMaxHint }));
492    assertEquals(0, comparator.compare(filterList.getNextCellHint(null), maxKeyValue));
493
494    // MUST PASS ALL
495
496    // Should take the first hint
497    filterList = new FilterList(Operator.MUST_PASS_ALL,
498      Arrays.asList(new Filter[] { filterMinHint, filterMaxHint }));
499    filterList.filterCell(null);
500    assertEquals(0, comparator.compare(filterList.getNextCellHint(null), minKeyValue));
501
502    filterList = new FilterList(Operator.MUST_PASS_ALL,
503      Arrays.asList(new Filter[] { filterMaxHint, filterMinHint }));
504    filterList.filterCell(null);
505    assertEquals(0, comparator.compare(filterList.getNextCellHint(null), maxKeyValue));
506
507    // Should have first hint even if a filter has no hint
508    filterList = new FilterList(Operator.MUST_PASS_ALL,
509      Arrays.asList(new Filter[] { filterNoHint, filterMinHint, filterMaxHint }));
510    filterList.filterCell(null);
511    assertEquals(0, comparator.compare(filterList.getNextCellHint(null), minKeyValue));
512    filterList = new FilterList(Operator.MUST_PASS_ALL,
513      Arrays.asList(new Filter[] { filterNoHint, filterMaxHint }));
514    filterList.filterCell(null);
515    assertEquals(0, comparator.compare(filterList.getNextCellHint(null), maxKeyValue));
516    filterList = new FilterList(Operator.MUST_PASS_ALL,
517      Arrays.asList(new Filter[] { filterNoHint, filterMinHint }));
518    filterList.filterCell(null);
519    assertEquals(0, comparator.compare(filterList.getNextCellHint(null), minKeyValue));
520  }
521
522  /**
523   * Tests the behavior of transform() in a hierarchical filter. transform() only applies after a
524   * filterCell() whose return-code includes the KeyValue. Lazy evaluation of AND
525   */
526  @Test
527  public void testTransformMPO() throws Exception {
528    // Apply the following filter:
529    // (family=fam AND qualifier=qual1 AND KeyOnlyFilter)
530    // OR (family=fam AND qualifier=qual2)
531    final FilterList flist = new FilterList(Operator.MUST_PASS_ONE,
532      Lists.<Filter> newArrayList(
533        new FilterList(Operator.MUST_PASS_ALL,
534          Lists.<Filter> newArrayList(
535            new FamilyFilter(CompareOperator.EQUAL, new BinaryComparator(Bytes.toBytes("fam"))),
536            new QualifierFilter(CompareOperator.EQUAL,
537              new BinaryComparator(Bytes.toBytes("qual1"))),
538            new KeyOnlyFilter())),
539        new FilterList(Operator.MUST_PASS_ALL,
540          Lists.<Filter> newArrayList(
541            new FamilyFilter(CompareOperator.EQUAL, new BinaryComparator(Bytes.toBytes("fam"))),
542            new QualifierFilter(CompareOperator.EQUAL,
543              new BinaryComparator(Bytes.toBytes("qual2")))))));
544
545    final KeyValue kvQual1 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam"),
546      Bytes.toBytes("qual1"), Bytes.toBytes("value"));
547    final KeyValue kvQual2 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam"),
548      Bytes.toBytes("qual2"), Bytes.toBytes("value"));
549    final KeyValue kvQual3 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam"),
550      Bytes.toBytes("qual3"), Bytes.toBytes("value"));
551
552    // Value for fam:qual1 should be stripped:
553    assertEquals(Filter.ReturnCode.INCLUDE, flist.filterCell(kvQual1));
554    final KeyValue transformedQual1 =
555      KeyValueUtil.ensureKeyValue((ExtendedCell) flist.transformCell(kvQual1));
556    assertEquals(0, transformedQual1.getValueLength());
557
558    // Value for fam:qual2 should not be stripped:
559    assertEquals(Filter.ReturnCode.INCLUDE, flist.filterCell(kvQual2));
560    final KeyValue transformedQual2 =
561      KeyValueUtil.ensureKeyValue((ExtendedCell) flist.transformCell(kvQual2));
562    assertEquals("value", Bytes.toString(transformedQual2.getValueArray(),
563      transformedQual2.getValueOffset(), transformedQual2.getValueLength()));
564
565    // Other keys should be skipped:
566    assertEquals(Filter.ReturnCode.SKIP, flist.filterCell(kvQual3));
567  }
568
569  @Test
570  public void testWithMultiVersionsInSameRow() throws Exception {
571    FilterList filterList01 =
572      new FilterList(Operator.MUST_PASS_ONE, new ColumnPaginationFilter(1, 0));
573
574    KeyValue kv1 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam"), Bytes.toBytes("qual"),
575      1, Bytes.toBytes("value"));
576    KeyValue kv2 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam"), Bytes.toBytes("qual"),
577      2, Bytes.toBytes("value"));
578    KeyValue kv3 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam"), Bytes.toBytes("qual"),
579      3, Bytes.toBytes("value"));
580
581    assertEquals(ReturnCode.INCLUDE_AND_NEXT_COL, filterList01.filterCell(kv1));
582    assertEquals(ReturnCode.NEXT_COL, filterList01.filterCell(kv2));
583    assertEquals(ReturnCode.NEXT_COL, filterList01.filterCell(kv3));
584
585    FilterList filterList11 =
586      new FilterList(Operator.MUST_PASS_ONE, new ColumnPaginationFilter(1, 1));
587
588    assertEquals(ReturnCode.NEXT_COL, filterList11.filterCell(kv1));
589    assertEquals(ReturnCode.NEXT_COL, filterList11.filterCell(kv2));
590    assertEquals(ReturnCode.NEXT_COL, filterList11.filterCell(kv3));
591  }
592
593  @Test
594  public void testMPONEWithSeekNextUsingHint() throws Exception {
595    byte[] col = Bytes.toBytes("c");
596    FilterList filterList =
597      new FilterList(Operator.MUST_PASS_ONE, new ColumnPaginationFilter(1, col));
598
599    KeyValue kv1 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam"), Bytes.toBytes("a"), 1,
600      Bytes.toBytes("value"));
601    KeyValue kv2 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam"), Bytes.toBytes("b"), 2,
602      Bytes.toBytes("value"));
603    KeyValue kv3 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam"), Bytes.toBytes("c"), 3,
604      Bytes.toBytes("value"));
605    KeyValue kv4 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam"), Bytes.toBytes("c"), 4,
606      Bytes.toBytes("value"));
607
608    assertEquals(ReturnCode.SEEK_NEXT_USING_HINT, filterList.filterCell(kv1));
609    assertEquals(ReturnCode.SEEK_NEXT_USING_HINT, filterList.filterCell(kv2));
610    assertEquals(ReturnCode.INCLUDE_AND_NEXT_COL, filterList.filterCell(kv3));
611    assertEquals(ReturnCode.NEXT_COL, filterList.filterCell(kv4));
612  }
613
614  private static class MockFilter extends FilterBase {
615    private ReturnCode targetRetCode;
616    public boolean didCellPassToTheFilter = false;
617
618    public MockFilter(ReturnCode targetRetCode) {
619      this.targetRetCode = targetRetCode;
620    }
621
622    @Override
623    public ReturnCode filterCell(final Cell v) throws IOException {
624      this.didCellPassToTheFilter = true;
625      return targetRetCode;
626    }
627
628    @Override
629    public boolean equals(Object obj) {
630      if (obj == null || !(obj instanceof MockFilter)) {
631        return false;
632      }
633      if (obj == this) {
634        return true;
635      }
636      MockFilter f = (MockFilter) obj;
637      return this.targetRetCode.equals(f.targetRetCode);
638    }
639
640    @Override
641    public int hashCode() {
642      return Objects.hash(this.targetRetCode);
643    }
644  }
645
646  private static class HintingMockFilter extends MockFilter implements HintingFilter {
647    public HintingMockFilter(ReturnCode targetRetCode) {
648      super(targetRetCode);
649    }
650  }
651
652  @Test
653  public void testShouldPassCurrentCellToFilter() throws IOException {
654    KeyValue kv1 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam"), Bytes.toBytes("a"), 1,
655      Bytes.toBytes("value"));
656    KeyValue kv2 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam"), Bytes.toBytes("a"), 2,
657      Bytes.toBytes("value"));
658    KeyValue kv3 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam"), Bytes.toBytes("b"), 3,
659      Bytes.toBytes("value"));
660    KeyValue kv4 = new KeyValue(Bytes.toBytes("row1"), Bytes.toBytes("fam"), Bytes.toBytes("c"), 4,
661      Bytes.toBytes("value"));
662
663    MockFilter mockFilter = new MockFilter(ReturnCode.NEXT_COL);
664    FilterList filter = new FilterList(Operator.MUST_PASS_ONE, mockFilter);
665
666    filter.filterCell(kv1);
667    assertTrue(mockFilter.didCellPassToTheFilter);
668
669    mockFilter.didCellPassToTheFilter = false;
670    filter.filterCell(kv2);
671    assertFalse(mockFilter.didCellPassToTheFilter);
672
673    mockFilter.didCellPassToTheFilter = false;
674    filter.filterCell(kv3);
675    assertTrue(mockFilter.didCellPassToTheFilter);
676
677    mockFilter = new MockFilter(ReturnCode.INCLUDE_AND_NEXT_COL);
678    filter = new FilterList(Operator.MUST_PASS_ONE, mockFilter);
679
680    filter.filterCell(kv1);
681    assertTrue(mockFilter.didCellPassToTheFilter);
682
683    mockFilter.didCellPassToTheFilter = false;
684    filter.filterCell(kv2);
685    assertFalse(mockFilter.didCellPassToTheFilter);
686
687    mockFilter.didCellPassToTheFilter = false;
688    filter.filterCell(kv3);
689    assertTrue(mockFilter.didCellPassToTheFilter);
690
691    mockFilter = new MockFilter(ReturnCode.NEXT_ROW);
692    filter = new FilterList(Operator.MUST_PASS_ONE, mockFilter);
693    filter.filterCell(kv1);
694    assertTrue(mockFilter.didCellPassToTheFilter);
695
696    mockFilter.didCellPassToTheFilter = false;
697    filter.filterCell(kv2);
698    assertFalse(mockFilter.didCellPassToTheFilter);
699
700    mockFilter.didCellPassToTheFilter = false;
701    filter.filterCell(kv3);
702    assertFalse(mockFilter.didCellPassToTheFilter);
703
704    filter.reset();
705    mockFilter.didCellPassToTheFilter = false;
706    filter.filterCell(kv4);
707    assertTrue(mockFilter.didCellPassToTheFilter);
708
709    mockFilter = new MockFilter(ReturnCode.INCLUDE_AND_SEEK_NEXT_ROW);
710    filter = new FilterList(Operator.MUST_PASS_ONE, mockFilter);
711    filter.filterCell(kv1);
712    assertTrue(mockFilter.didCellPassToTheFilter);
713
714    mockFilter.didCellPassToTheFilter = false;
715    filter.filterCell(kv2);
716    assertFalse(mockFilter.didCellPassToTheFilter);
717
718    mockFilter.didCellPassToTheFilter = false;
719    filter.filterCell(kv3);
720    assertFalse(mockFilter.didCellPassToTheFilter);
721
722    filter.reset();
723    mockFilter.didCellPassToTheFilter = false;
724    filter.filterCell(kv4);
725    assertTrue(mockFilter.didCellPassToTheFilter);
726  }
727
728  @Test
729  public void testTheMaximalRule() throws IOException {
730    KeyValue kv1 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam"), Bytes.toBytes("a"), 1,
731      Bytes.toBytes("value"));
732    MockFilter filter1 = new MockFilter(ReturnCode.INCLUDE);
733    MockFilter filter2 = new MockFilter(ReturnCode.INCLUDE_AND_NEXT_COL);
734    MockFilter filter3 = new MockFilter(ReturnCode.INCLUDE_AND_SEEK_NEXT_ROW);
735    MockFilter filter4 = new MockFilter(ReturnCode.NEXT_COL);
736    MockFilter filter5 = new MockFilter(ReturnCode.SKIP);
737    MockFilter filter6 = new HintingMockFilter(ReturnCode.SEEK_NEXT_USING_HINT);
738    MockFilter filter7 = new MockFilter(ReturnCode.NEXT_ROW);
739
740    FilterList filterList = new FilterList(Operator.MUST_PASS_ALL, filter1, filter2);
741    assertEquals(ReturnCode.INCLUDE_AND_NEXT_COL, filterList.filterCell(kv1));
742
743    filterList = new FilterList(Operator.MUST_PASS_ALL, filter2, filter3);
744    assertEquals(ReturnCode.INCLUDE_AND_SEEK_NEXT_ROW, filterList.filterCell(kv1));
745
746    filterList = new FilterList(Operator.MUST_PASS_ALL, filter4, filter5, filter6);
747    assertEquals(ReturnCode.SEEK_NEXT_USING_HINT, filterList.filterCell(kv1));
748
749    filterList = new FilterList(Operator.MUST_PASS_ALL, filter4, filter6);
750    assertEquals(ReturnCode.SEEK_NEXT_USING_HINT, filterList.filterCell(kv1));
751
752    filterList = new FilterList(Operator.MUST_PASS_ALL, filter3, filter1);
753    assertEquals(ReturnCode.INCLUDE_AND_SEEK_NEXT_ROW, filterList.filterCell(kv1));
754
755    filterList = new FilterList(Operator.MUST_PASS_ALL, filter3, filter2, filter1, filter5);
756    assertEquals(ReturnCode.NEXT_ROW, filterList.filterCell(kv1));
757
758    filterList = new FilterList(Operator.MUST_PASS_ALL, filter2,
759      new FilterList(Operator.MUST_PASS_ALL, filter3, filter4));
760    assertEquals(ReturnCode.NEXT_ROW, filterList.filterCell(kv1));
761
762    filterList = new FilterList(Operator.MUST_PASS_ALL, filter3, filter7);
763    assertEquals(ReturnCode.NEXT_ROW, filterList.filterCell(kv1));
764  }
765
766  @Test
767  public void testTheMinimalRule() throws IOException {
768    KeyValue kv1 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam"), Bytes.toBytes("a"), 1,
769      Bytes.toBytes("value"));
770    MockFilter filter1 = new MockFilter(ReturnCode.INCLUDE);
771    MockFilter filter2 = new MockFilter(ReturnCode.INCLUDE_AND_NEXT_COL);
772    MockFilter filter3 = new MockFilter(ReturnCode.INCLUDE_AND_SEEK_NEXT_ROW);
773    MockFilter filter4 = new MockFilter(ReturnCode.NEXT_COL);
774    MockFilter filter5 = new MockFilter(ReturnCode.SKIP);
775    MockFilter filter6 = new HintingMockFilter(ReturnCode.SEEK_NEXT_USING_HINT);
776    FilterList filterList = new FilterList(Operator.MUST_PASS_ONE, filter1, filter2);
777    assertEquals(ReturnCode.INCLUDE, filterList.filterCell(kv1));
778
779    filterList = new FilterList(Operator.MUST_PASS_ONE, filter2, filter3);
780    assertEquals(ReturnCode.INCLUDE_AND_NEXT_COL, filterList.filterCell(kv1));
781
782    filterList = new FilterList(Operator.MUST_PASS_ONE, filter4, filter5, filter6);
783    assertEquals(ReturnCode.SKIP, filterList.filterCell(kv1));
784
785    filterList = new FilterList(Operator.MUST_PASS_ONE, filter4, filter6);
786    assertEquals(ReturnCode.SKIP, filterList.filterCell(kv1));
787
788    filterList = new FilterList(Operator.MUST_PASS_ONE, filter3, filter1);
789    assertEquals(ReturnCode.INCLUDE, filterList.filterCell(kv1));
790
791    filterList = new FilterList(Operator.MUST_PASS_ONE, filter3, filter2, filter1, filter5);
792    assertEquals(ReturnCode.INCLUDE, filterList.filterCell(kv1));
793
794    filterList = new FilterList(Operator.MUST_PASS_ONE, filter2,
795      new FilterList(Operator.MUST_PASS_ONE, filter3, filter4));
796    assertEquals(ReturnCode.INCLUDE_AND_NEXT_COL, filterList.filterCell(kv1));
797
798    filterList = new FilterList(Operator.MUST_PASS_ONE, filter2,
799      new FilterList(Operator.MUST_PASS_ONE, filter3, filter4));
800    assertEquals(ReturnCode.INCLUDE_AND_NEXT_COL, filterList.filterCell(kv1));
801
802    filterList = new FilterList(Operator.MUST_PASS_ONE, filter6, filter6);
803    assertEquals(ReturnCode.SEEK_NEXT_USING_HINT, filterList.filterCell(kv1));
804  }
805
806  static class MockSeekHintFilter extends FilterBase {
807    private Cell returnCell;
808
809    public MockSeekHintFilter(Cell returnCell) {
810      this.returnCell = returnCell;
811    }
812
813    @Override
814    public ReturnCode filterCell(final Cell v) throws IOException {
815      return ReturnCode.SEEK_NEXT_USING_HINT;
816    }
817
818    @Override
819    public Cell getNextCellHint(Cell currentCell) throws IOException {
820      return this.returnCell;
821    }
822
823    @Override
824    public boolean equals(Object obj) {
825      if (obj == null || !(obj instanceof MockSeekHintFilter)) {
826        return false;
827      }
828      if (obj == this) {
829        return true;
830      }
831      MockSeekHintFilter f = (MockSeekHintFilter) obj;
832      return this.returnCell.equals(f.returnCell);
833    }
834
835    @Override
836    public int hashCode() {
837      return Objects.hash(this.returnCell);
838    }
839  }
840
841  @Test
842  public void testReversedFilterListWithMockSeekHintFilter() throws IOException {
843    KeyValue kv1 = new KeyValue(Bytes.toBytes("row1"), Bytes.toBytes("fam"), Bytes.toBytes("a"), 1,
844      Bytes.toBytes("value"));
845    KeyValue kv2 = new KeyValue(Bytes.toBytes("row2"), Bytes.toBytes("fam"), Bytes.toBytes("a"), 1,
846      Bytes.toBytes("value"));
847    KeyValue kv3 = new KeyValue(Bytes.toBytes("row3"), Bytes.toBytes("fam"), Bytes.toBytes("a"), 1,
848      Bytes.toBytes("value"));
849    Filter filter1 = new MockSeekHintFilter(kv1);
850    filter1.setReversed(true);
851    Filter filter2 = new MockSeekHintFilter(kv2);
852    filter2.setReversed(true);
853    Filter filter3 = new MockSeekHintFilter(kv3);
854    filter3.setReversed(true);
855
856    FilterList filterList = new FilterList(Operator.MUST_PASS_ONE);
857    filterList.setReversed(true);
858    filterList.addFilter(filter1);
859    filterList.addFilter(filter2);
860    filterList.addFilter(filter3);
861
862    assertEquals(ReturnCode.SEEK_NEXT_USING_HINT, filterList.filterCell(kv1));
863    assertEquals(kv3, filterList.getNextCellHint(kv1));
864
865    filterList = new FilterList(Operator.MUST_PASS_ALL);
866    filterList.setReversed(true);
867    filterList.addFilter(filter1);
868    filterList.addFilter(filter2);
869    filterList.addFilter(filter3);
870
871    assertEquals(ReturnCode.SEEK_NEXT_USING_HINT, filterList.filterCell(kv1));
872    assertEquals(kv1, filterList.getNextCellHint(kv1));
873  }
874
875  @Test
876  public void testReversedFilterListWithOR() throws IOException {
877    byte[] r22 = Bytes.toBytes("Row22");
878    byte[] r2 = Bytes.toBytes("Row2");
879    byte[] r1 = Bytes.toBytes("Row1");
880
881    FilterList filterList = new FilterList(FilterList.Operator.MUST_PASS_ONE);
882    filterList.setReversed(true);
883    PrefixFilter prefixFilter = new PrefixFilter(r2);
884    prefixFilter.setReversed(true);
885    filterList.addFilter(prefixFilter);
886    filterList.filterRowKey(KeyValueUtil.createFirstOnRow(r22));
887    assertEquals(ReturnCode.INCLUDE, filterList.filterCell(new KeyValue(r22, r22, r22)));
888    assertEquals(ReturnCode.INCLUDE, filterList.filterCell(new KeyValue(r2, r2, r2)));
889
890    filterList.reset();
891    filterList.filterRowKey(KeyValueUtil.createFirstOnRow(r1));
892    assertEquals(ReturnCode.SKIP, filterList.filterCell(new KeyValue(r1, r1, r1)));
893
894    filterList = new FilterList(FilterList.Operator.MUST_PASS_ONE);
895    filterList.setReversed(true);
896    AlwaysNextColFilter alwaysNextColFilter = new AlwaysNextColFilter();
897    alwaysNextColFilter.setReversed(true);
898    prefixFilter = new PrefixFilter(r2);
899    prefixFilter.setReversed(true);
900    filterList.addFilter(alwaysNextColFilter);
901    filterList.addFilter(prefixFilter);
902    filterList.filterRowKey(KeyValueUtil.createFirstOnRow(r22));
903    assertEquals(ReturnCode.INCLUDE, filterList.filterCell(new KeyValue(r22, r22, r22)));
904    assertEquals(ReturnCode.INCLUDE, filterList.filterCell(new KeyValue(r2, r2, r2)));
905
906    filterList.reset();
907    filterList.filterRowKey(KeyValueUtil.createFirstOnRow(r1));
908    assertEquals(ReturnCode.NEXT_COL, filterList.filterCell(new KeyValue(r1, r1, r1)));
909  }
910
911  @Test
912  public void testKeyOnlyFilterTransformCell() throws IOException {
913    Cell c;
914    KeyValue kv1 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("cf"), Bytes.toBytes("column1"),
915      1, Bytes.toBytes("value1"));
916    KeyValue kv2 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("cf"), Bytes.toBytes("column1"),
917      2, Bytes.toBytes("value2"));
918
919    Filter filter1 = new SingleColumnValueFilter(Bytes.toBytes("cf"), Bytes.toBytes("column1"),
920      CompareOperator.EQUAL, Bytes.toBytes("value1"));
921    Filter filter2 = new SingleColumnValueFilter(Bytes.toBytes("cf"), Bytes.toBytes("column1"),
922      CompareOperator.EQUAL, Bytes.toBytes("value2"));
923    FilterList internalFilterList = new FilterList(Operator.MUST_PASS_ONE, filter1, filter2);
924
925    FilterList keyOnlyFilterFirst =
926      new FilterList(Operator.MUST_PASS_ALL, new KeyOnlyFilter(), internalFilterList);
927
928    assertEquals(ReturnCode.INCLUDE, keyOnlyFilterFirst.filterCell(kv1));
929    c = keyOnlyFilterFirst.transformCell(kv1);
930    assertEquals(0, c.getValueLength());
931    assertEquals(ReturnCode.INCLUDE, keyOnlyFilterFirst.filterCell(kv2));
932    c = keyOnlyFilterFirst.transformCell(kv2);
933    assertEquals(0, c.getValueLength());
934
935    internalFilterList.reset();
936    FilterList keyOnlyFilterLast =
937      new FilterList(Operator.MUST_PASS_ALL, new KeyOnlyFilter(), internalFilterList);
938    assertEquals(ReturnCode.INCLUDE, keyOnlyFilterLast.filterCell(kv1));
939    c = keyOnlyFilterLast.transformCell(kv1);
940    assertEquals(0, c.getValueLength());
941    assertEquals(ReturnCode.INCLUDE, keyOnlyFilterLast.filterCell(kv2));
942    c = keyOnlyFilterLast.transformCell(kv2);
943    assertEquals(0, c.getValueLength());
944  }
945
946  @Test
947  public void testEmptyFilterListTransformCell() throws IOException {
948    KeyValue kv = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("cf"), Bytes.toBytes("column1"),
949      1, Bytes.toBytes("value"));
950    FilterList filterList = new FilterList(Operator.MUST_PASS_ALL);
951    assertEquals(ReturnCode.INCLUDE, filterList.filterCell(kv));
952    assertEquals(kv, filterList.transformCell(kv));
953
954    filterList = new FilterList(Operator.MUST_PASS_ONE);
955    assertEquals(ReturnCode.INCLUDE, filterList.filterCell(kv));
956    assertEquals(kv, filterList.transformCell(kv));
957  }
958
959  private static class MockNextRowFilter extends FilterBase {
960    private int hitCount = 0;
961
962    @Override
963    public ReturnCode filterCell(final Cell v) throws IOException {
964      hitCount++;
965      return ReturnCode.NEXT_ROW;
966    }
967
968    public int getHitCount() {
969      return hitCount;
970    }
971  }
972
973  @Test
974  public void testRowCountFilter() throws IOException {
975    KeyValue kv1 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam1"), Bytes.toBytes("a"), 1,
976      Bytes.toBytes("value"));
977    KeyValue kv2 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam2"), Bytes.toBytes("a"), 2,
978      Bytes.toBytes("value"));
979    MockNextRowFilter mockNextRowFilter = new MockNextRowFilter();
980    FilterList filter = new FilterList(Operator.MUST_PASS_ONE, mockNextRowFilter);
981    filter.filterCell(kv1);
982    filter.filterCell(kv2);
983    assertEquals(2, mockNextRowFilter.getHitCount());
984  }
985
986  private static class TransformFilter extends FilterBase {
987    private ReturnCode targetRetCode;
988    private boolean transformed = false;
989
990    public TransformFilter(ReturnCode targetRetCode) {
991      this.targetRetCode = targetRetCode;
992    }
993
994    @Override
995    public ReturnCode filterCell(final Cell v) throws IOException {
996      return targetRetCode;
997    }
998
999    @Override
1000    public Cell transformCell(Cell c) throws IOException {
1001      transformed = true;
1002      return super.transformCell(c);
1003    }
1004
1005    public boolean getTransformed() {
1006      return this.transformed;
1007    }
1008
1009    @Override
1010    public boolean equals(Object obj) {
1011      if (!(obj instanceof TransformFilter)) {
1012        return false;
1013      }
1014      if (obj == this) {
1015        return true;
1016      }
1017      TransformFilter f = (TransformFilter) obj;
1018      return this.targetRetCode.equals(f.targetRetCode);
1019    }
1020
1021    @Override
1022    public int hashCode() {
1023      return Objects.hash(this.targetRetCode);
1024    }
1025  }
1026
1027  @Test
1028  public void testTransformCell() throws IOException {
1029    KeyValue kv = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("cf"), Bytes.toBytes("column1"),
1030      1, Bytes.toBytes("value"));
1031
1032    // case MUST_PASS_ONE
1033    TransformFilter filter1 = new TransformFilter(ReturnCode.INCLUDE);
1034    TransformFilter filter2 = new TransformFilter(ReturnCode.NEXT_ROW);
1035    TransformFilter filter3 = new TransformFilter(ReturnCode.SEEK_NEXT_USING_HINT);
1036    FilterList filterList = new FilterList(Operator.MUST_PASS_ONE, filter1, filter2, filter3);
1037    assertEquals(ReturnCode.INCLUDE, filterList.filterCell(kv));
1038    assertEquals(kv, filterList.transformCell(kv));
1039    assertEquals(true, filter1.getTransformed());
1040    assertEquals(false, filter2.getTransformed());
1041    assertEquals(false, filter3.getTransformed());
1042
1043    // case MUST_PASS_ALL
1044    filter1 = new TransformFilter(ReturnCode.INCLUDE);
1045    filter2 = new TransformFilter(ReturnCode.INCLUDE_AND_SEEK_NEXT_ROW);
1046    filter3 = new TransformFilter(ReturnCode.INCLUDE_AND_NEXT_COL);
1047    filterList = new FilterList(Operator.MUST_PASS_ALL, filter1, filter2, filter3);
1048
1049    assertEquals(ReturnCode.INCLUDE_AND_SEEK_NEXT_ROW, filterList.filterCell(kv));
1050    assertEquals(kv, filterList.transformCell(kv));
1051    assertEquals(true, filter1.getTransformed());
1052    assertEquals(true, filter2.getTransformed());
1053    assertEquals(true, filter3.getTransformed());
1054  }
1055
1056  @Test
1057  public void testFilterListWithORWhenPassingCellMismatchPreviousRC() throws IOException {
1058    // Mainly test FilterListWithOR#calculateReturnCodeByPrevCellAndRC method with two sub-filters.
1059    KeyValue kv1 = new KeyValue(Bytes.toBytes("row1"), Bytes.toBytes("fam"), Bytes.toBytes("a"),
1060      100, Bytes.toBytes("value"));
1061    KeyValue kv2 = new KeyValue(Bytes.toBytes("row1"), Bytes.toBytes("fam"), Bytes.toBytes("a"), 99,
1062      Bytes.toBytes("value"));
1063    KeyValue kv3 = new KeyValue(Bytes.toBytes("row1"), Bytes.toBytes("fam"), Bytes.toBytes("b"), 1,
1064      Bytes.toBytes("value"));
1065    KeyValue kv4 = new KeyValue(Bytes.toBytes("row1"), Bytes.toBytes("fan"), Bytes.toBytes("a"), 1,
1066      Bytes.toBytes("value"));
1067    Filter subFilter1 = Mockito.mock(FilterBase.class);
1068    Mockito.when(subFilter1.filterCell(kv1)).thenReturn(ReturnCode.INCLUDE_AND_NEXT_COL);
1069    Mockito.when(subFilter1.filterCell(kv2)).thenReturn(ReturnCode.NEXT_COL);
1070    Mockito.when(subFilter1.filterCell(kv3)).thenReturn(ReturnCode.INCLUDE_AND_NEXT_COL);
1071    Mockito.when(subFilter1.filterCell(kv4)).thenReturn(ReturnCode.INCLUDE_AND_NEXT_COL);
1072
1073    Filter subFilter2 = Mockito.mock(FilterBase.class);
1074    Mockito.when(subFilter2.filterCell(kv1)).thenReturn(ReturnCode.SKIP);
1075    Mockito.when(subFilter2.filterCell(kv2)).thenReturn(ReturnCode.NEXT_ROW);
1076    Mockito.when(subFilter2.filterCell(kv3)).thenReturn(ReturnCode.NEXT_ROW);
1077    Mockito.when(subFilter2.filterCell(kv4)).thenReturn(ReturnCode.INCLUDE_AND_SEEK_NEXT_ROW);
1078
1079    Filter filterList = new FilterList(Operator.MUST_PASS_ONE, subFilter1, subFilter2);
1080    assertEquals(ReturnCode.INCLUDE, filterList.filterCell(kv1));
1081    assertEquals(ReturnCode.NEXT_COL, filterList.filterCell(kv2));
1082    assertEquals(ReturnCode.INCLUDE_AND_NEXT_COL, filterList.filterCell(kv3));
1083    assertEquals(ReturnCode.INCLUDE_AND_NEXT_COL, filterList.filterCell(kv4));
1084
1085    // One sub-filter will filterAllRemaining but other sub-filter will return SEEK_HINT
1086    subFilter1 = Mockito.mock(FilterBase.class);
1087    Mockito.when(subFilter1.filterAllRemaining()).thenReturn(true);
1088    Mockito.when(subFilter1.filterCell(kv1)).thenReturn(ReturnCode.NEXT_ROW);
1089
1090    subFilter2 = Mockito.mock(FilterBase.class);
1091    Mockito.when(subFilter2.filterCell(kv1)).thenReturn(ReturnCode.SEEK_NEXT_USING_HINT);
1092    filterList = new FilterList(Operator.MUST_PASS_ONE, subFilter1, subFilter2);
1093    assertEquals(ReturnCode.SEEK_NEXT_USING_HINT, filterList.filterCell(kv1));
1094
1095    // Two sub-filter returns SEEK_NEXT_USING_HINT, then we should return SEEK_NEXT_USING_HINT.
1096    subFilter1 = Mockito.mock(FilterBase.class);
1097    Mockito.when(subFilter1.filterCell(kv1)).thenReturn(ReturnCode.SEEK_NEXT_USING_HINT);
1098
1099    subFilter2 = Mockito.mock(FilterBase.class);
1100    Mockito.when(subFilter2.filterCell(kv1)).thenReturn(ReturnCode.SEEK_NEXT_USING_HINT);
1101    filterList = new FilterList(Operator.MUST_PASS_ONE, subFilter1, subFilter2);
1102    assertEquals(ReturnCode.SEEK_NEXT_USING_HINT, filterList.filterCell(kv1));
1103  }
1104}