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.Assert.assertEquals;
021import static org.junit.Assert.assertFalse;
022import static org.junit.Assert.assertNull;
023import static org.junit.Assert.assertTrue;
024import static org.junit.Assert.fail;
025
026import java.io.IOException;
027import java.util.ArrayList;
028import java.util.Arrays;
029import java.util.List;
030import org.apache.hadoop.hbase.Cell;
031import org.apache.hadoop.hbase.CellComparator;
032import org.apache.hadoop.hbase.CompareOperator;
033import org.apache.hadoop.hbase.HBaseClassTestRule;
034import org.apache.hadoop.hbase.KeyValue;
035import org.apache.hadoop.hbase.KeyValueUtil;
036import org.apache.hadoop.hbase.exceptions.DeserializationException;
037import org.apache.hadoop.hbase.filter.Filter.ReturnCode;
038import org.apache.hadoop.hbase.filter.FilterList.Operator;
039import org.apache.hadoop.hbase.testclassification.FilterTests;
040import org.apache.hadoop.hbase.testclassification.SmallTests;
041import org.apache.hadoop.hbase.util.Bytes;
042import org.junit.Assert;
043import org.junit.ClassRule;
044import org.junit.Test;
045import org.junit.experimental.categories.Category;
046
047import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
048
049import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
050
051@Category({FilterTests.class, SmallTests.class})
052public class TestFilterList {
053
054  @ClassRule
055  public static final HBaseClassTestRule CLASS_RULE =
056      HBaseClassTestRule.forClass(TestFilterList.class);
057
058  static final int MAX_PAGES = 2;
059
060  @Test
061  public void testAddFilter() throws Exception {
062    Filter filter1 = new FirstKeyOnlyFilter();
063    Filter filter2 = new FirstKeyOnlyFilter();
064
065    FilterList filterList = new FilterList(filter1, filter2);
066    filterList.addFilter(new FirstKeyOnlyFilter());
067
068    filterList = new FilterList(Arrays.asList(filter1, filter2));
069    filterList.addFilter(new FirstKeyOnlyFilter());
070
071    filterList = new FilterList(Operator.MUST_PASS_ALL, filter1, filter2);
072    filterList.addFilter(new FirstKeyOnlyFilter());
073
074    filterList = new FilterList(Operator.MUST_PASS_ALL, Arrays.asList(filter1, filter2));
075    filterList.addFilter(new FirstKeyOnlyFilter());
076
077    filterList.setReversed(false);
078    FirstKeyOnlyFilter f = new FirstKeyOnlyFilter();
079    f.setReversed(true);
080    try {
081      filterList.addFilter(f);
082      fail("The IllegalArgumentException should be thrown because the added filter is reversed");
083    } catch (IllegalArgumentException e) {
084    }
085
086  }
087
088  @Test
089  public void testConstruction() {
090    FirstKeyOnlyFilter f1 = new FirstKeyOnlyFilter();
091    FirstKeyOnlyFilter f2 = new FirstKeyOnlyFilter();
092    f1.setReversed(true);
093    f2.setReversed(false);
094
095    try {
096      FilterList ff = new FilterList(f1, f2);
097      fail("The IllegalArgumentException should be thrown");
098    } catch (IllegalArgumentException e) {
099    }
100
101    try {
102      FilterList ff = new FilterList(Arrays.asList(f1, f2));
103      fail("The IllegalArgumentException should be thrown because the added filter is reversed");
104    } catch (IllegalArgumentException e) {
105    }
106
107    try {
108      FilterList ff = new FilterList(FilterList.Operator.MUST_PASS_ALL, Arrays.asList(f1, f2));
109      fail("The IllegalArgumentException should be thrown because the added filter is reversed");
110    } catch (IllegalArgumentException e) {
111    }
112
113    try {
114      FilterList ff = new FilterList(FilterList.Operator.MUST_PASS_ALL, f1, f2);
115      fail("The IllegalArgumentException should be thrown because the added filter is reversed");
116    } catch (IllegalArgumentException e) {
117    }
118  }
119  /**
120   * Test "must pass one"
121   * @throws Exception
122   */
123  @Test
124  public void testMPONE() throws Exception {
125    mpOneTest(getFilterMPONE());
126  }
127
128  private Filter getFilterMPONE() {
129    List<Filter> filters = new ArrayList<>();
130    filters.add(new PageFilter(MAX_PAGES));
131    filters.add(new WhileMatchFilter(new PrefixFilter(Bytes.toBytes("yyy"))));
132    Filter filterMPONE =
133      new FilterList(FilterList.Operator.MUST_PASS_ONE, filters);
134    return filterMPONE;
135  }
136
137  private void mpOneTest(Filter filterMPONE) throws Exception {
138    /* Filter must do all below steps:
139     * <ul>
140     * <li>{@link #reset()}</li>
141     * <li>{@link #filterAllRemaining()} -> true indicates scan is over, false, keep going on.</li>
142     * <li>{@link #filterRowKey(byte[],int,int)} -> true to drop this row,
143     * if false, we will also call</li>
144     * <li>{@link #filterCell(org.apache.hadoop.hbase.KeyValue)} -> true to drop this cell</li>
145     * <li>{@link #filterRow()} -> last chance to drop entire row based on the sequence of
146     * filterValue() calls. Eg: filter a row if it doesn't contain a specified column.
147     * </li>
148     * </ul>
149    */
150    filterMPONE.reset();
151    assertFalse(filterMPONE.filterAllRemaining());
152
153    /* Will pass both */
154    byte [] rowkey = Bytes.toBytes("yyyyyyyyy");
155    for (int i = 0; i < MAX_PAGES - 1; i++) {
156      assertFalse(filterMPONE.filterRowKey(KeyValueUtil.createFirstOnRow(rowkey)));
157      KeyValue kv = new KeyValue(rowkey, rowkey, Bytes.toBytes(i),
158        Bytes.toBytes(i));
159      assertTrue(Filter.ReturnCode.INCLUDE == filterMPONE.filterCell(kv));
160      assertFalse(filterMPONE.filterRow());
161    }
162
163    /* Only pass PageFilter */
164    rowkey = Bytes.toBytes("z");
165    assertFalse(filterMPONE.filterRowKey(KeyValueUtil.createFirstOnRow(rowkey)));
166    KeyValue kv = new KeyValue(rowkey, rowkey, Bytes.toBytes(0),
167        Bytes.toBytes(0));
168    assertTrue(Filter.ReturnCode.INCLUDE == filterMPONE.filterCell(kv));
169    assertFalse(filterMPONE.filterRow());
170
171    /* reach MAX_PAGES already, should filter any rows */
172    rowkey = Bytes.toBytes("yyy");
173    assertTrue(filterMPONE.filterRowKey(KeyValueUtil.createFirstOnRow(rowkey)));
174    kv = new KeyValue(rowkey, rowkey, Bytes.toBytes(0),
175        Bytes.toBytes(0));
176    assertFalse(Filter.ReturnCode.INCLUDE == filterMPONE.filterCell(kv));
177    assertFalse(filterMPONE.filterRow());
178
179    /* We should filter any row */
180    rowkey = Bytes.toBytes("z");
181    assertTrue(filterMPONE.filterRowKey(KeyValueUtil.createFirstOnRow(rowkey)));
182    assertTrue(filterMPONE.filterAllRemaining());
183  }
184
185  /**
186   * Test "must pass all"
187   * @throws Exception
188   */
189  @Test
190  public void testMPALL() throws Exception {
191    mpAllTest(getMPALLFilter());
192  }
193
194  private Filter getMPALLFilter() {
195    List<Filter> filters = new ArrayList<>();
196    filters.add(new PageFilter(MAX_PAGES));
197    filters.add(new WhileMatchFilter(new PrefixFilter(Bytes.toBytes("yyy"))));
198    Filter filterMPALL =
199      new FilterList(FilterList.Operator.MUST_PASS_ALL, filters);
200    return filterMPALL;
201  }
202
203  private void mpAllTest(Filter filterMPALL) throws Exception {
204    /* Filter must do all below steps:
205     * <ul>
206     * <li>{@link #reset()}</li>
207     * <li>{@link #filterAllRemaining()} -> true indicates scan is over, false, keep going on.</li>
208     * <li>{@link #filterRowKey(byte[],int,int)} -> true to drop this row,
209     * if false, we will also call</li>
210     * <li>{@link #filterCell(org.apache.hadoop.hbase.KeyValue)} -> true to drop this cell</li>
211     * <li>{@link #filterRow()} -> last chance to drop entire row based on the sequence of
212     * filterValue() calls. Eg: filter a row if it doesn't contain a specified column.
213     * </li>
214     * </ul>
215    */
216    filterMPALL.reset();
217    assertFalse(filterMPALL.filterAllRemaining());
218    byte [] rowkey = Bytes.toBytes("yyyyyyyyy");
219    for (int i = 0; i < MAX_PAGES - 1; i++) {
220      assertFalse(filterMPALL.filterRowKey(KeyValueUtil.createFirstOnRow(rowkey)));
221      KeyValue kv = new KeyValue(rowkey, rowkey, Bytes.toBytes(i),
222        Bytes.toBytes(i));
223      assertTrue(Filter.ReturnCode.INCLUDE == filterMPALL.filterCell(kv));
224    }
225    filterMPALL.reset();
226    rowkey = Bytes.toBytes("z");
227    assertTrue(filterMPALL.filterRowKey(KeyValueUtil.createFirstOnRow(rowkey)));
228    // Should fail here; row should be filtered out.
229    KeyValue kv = new KeyValue(rowkey, rowkey, rowkey, rowkey);
230    assertTrue(Filter.ReturnCode.NEXT_ROW == filterMPALL.filterCell(kv));
231  }
232
233  /**
234   * Test list ordering
235   * @throws Exception
236   */
237  @Test
238  public void testOrdering() throws Exception {
239    orderingTest(getOrderingFilter());
240  }
241
242  public Filter getOrderingFilter() {
243    List<Filter> filters = new ArrayList<>();
244    filters.add(new PrefixFilter(Bytes.toBytes("yyy")));
245    filters.add(new PageFilter(MAX_PAGES));
246    Filter filterMPONE =
247      new FilterList(FilterList.Operator.MUST_PASS_ONE, filters);
248    return filterMPONE;
249  }
250
251  public void orderingTest(Filter filterMPONE) throws Exception {
252    /* Filter must do all below steps:
253     * <ul>
254     * <li>{@link #reset()}</li>
255     * <li>{@link #filterAllRemaining()} -> true indicates scan is over, false, keep going on.</li>
256     * <li>{@link #filterRowKey(byte[],int,int)} -> true to drop this row,
257     * if false, we will also call</li>
258     * <li>{@link #filterCell(org.apache.hadoop.hbase.KeyValue)} -> true to drop this key/value</li>
259     * <li>{@link #filterRow()} -> last chance to drop entire row based on the sequence of
260     * filterValue() calls. Eg: filter a row if it doesn't contain a specified column.
261     * </li>
262     * </ul>
263    */
264    filterMPONE.reset();
265    assertFalse(filterMPONE.filterAllRemaining());
266
267    /* We should be able to fill MAX_PAGES without incrementing page counter */
268    byte [] rowkey = Bytes.toBytes("yyyyyyyy");
269    for (int i = 0; i < MAX_PAGES; i++) {
270      assertFalse(filterMPONE.filterRowKey(KeyValueUtil.createFirstOnRow(rowkey)));
271      KeyValue kv = new KeyValue(rowkey, rowkey, Bytes.toBytes(i),
272          Bytes.toBytes(i));
273        assertTrue(Filter.ReturnCode.INCLUDE == filterMPONE.filterCell(kv));
274      assertFalse(filterMPONE.filterRow());
275    }
276
277    /* Now let's fill the page filter */
278    rowkey = Bytes.toBytes("xxxxxxx");
279    for (int i = 0; i < MAX_PAGES; i++) {
280      assertFalse(filterMPONE.filterRowKey(KeyValueUtil.createFirstOnRow(rowkey)));
281      KeyValue kv = new KeyValue(rowkey, rowkey, Bytes.toBytes(i),
282          Bytes.toBytes(i));
283        assertTrue(Filter.ReturnCode.INCLUDE == filterMPONE.filterCell(kv));
284      assertFalse(filterMPONE.filterRow());
285    }
286
287    /* We should still be able to include even though page filter is at max */
288    rowkey = Bytes.toBytes("yyy");
289    for (int i = 0; i < MAX_PAGES; i++) {
290      assertFalse(filterMPONE.filterRowKey(KeyValueUtil.createFirstOnRow(rowkey)));
291      KeyValue kv = new KeyValue(rowkey, rowkey, Bytes.toBytes(i),
292          Bytes.toBytes(i));
293        assertTrue(Filter.ReturnCode.INCLUDE == filterMPONE.filterCell(kv));
294      assertFalse(filterMPONE.filterRow());
295    }
296  }
297
298  /**
299   * When we do a "MUST_PASS_ONE" (a logical 'OR') of the above two filters
300   * we expect to get the same result as the 'prefix' only result.
301   * @throws Exception
302   */
303  @Test
304  public void testFilterListTwoFiltersMustPassOne() throws Exception {
305    byte[] r1 = Bytes.toBytes("Row1");
306    byte[] r11 = Bytes.toBytes("Row11");
307    byte[] r2 = Bytes.toBytes("Row2");
308
309    FilterList flist = new FilterList(FilterList.Operator.MUST_PASS_ONE);
310    flist.addFilter(new PrefixFilter(r1));
311    flist.filterRowKey(KeyValueUtil.createFirstOnRow(r1));
312    assertEquals(ReturnCode.INCLUDE, flist.filterCell(new KeyValue(r1, r1, r1)));
313    assertEquals(ReturnCode.INCLUDE, flist.filterCell(new KeyValue(r11, r11, r11)));
314
315    flist.reset();
316    flist.filterRowKey(KeyValueUtil.createFirstOnRow(r2));
317    assertEquals(ReturnCode.SKIP, flist.filterCell(new KeyValue(r2, r2, r2)));
318
319    flist = new FilterList(FilterList.Operator.MUST_PASS_ONE);
320    flist.addFilter(new AlwaysNextColFilter());
321    flist.addFilter(new PrefixFilter(r1));
322    flist.filterRowKey(KeyValueUtil.createFirstOnRow(r1));
323    assertEquals(ReturnCode.INCLUDE, flist.filterCell(new KeyValue(r1, r1, r1)));
324    assertEquals(ReturnCode.INCLUDE, flist.filterCell(new KeyValue(r11, r11, r11)));
325
326    flist.reset();
327    flist.filterRowKey(KeyValueUtil.createFirstOnRow(r2));
328    assertEquals(ReturnCode.NEXT_COL, flist.filterCell(new KeyValue(r2, r2, r2)));
329  }
330
331  /**
332   * When we do a "MUST_PASS_ONE" (a logical 'OR') of the two filters
333   * we expect to get the same result as the inclusive stop result.
334   * @throws Exception
335   */
336  @Test
337  public void testFilterListWithInclusiveStopFilterMustPassOne() throws Exception {
338    byte[] r1 = Bytes.toBytes("Row1");
339    byte[] r11 = Bytes.toBytes("Row11");
340    byte[] r2 = Bytes.toBytes("Row2");
341
342    FilterList flist = new FilterList(FilterList.Operator.MUST_PASS_ONE);
343    flist.addFilter(new AlwaysNextColFilter());
344    flist.addFilter(new InclusiveStopFilter(r1));
345    flist.filterRowKey(KeyValueUtil.createFirstOnRow(r1));
346    assertEquals(ReturnCode.INCLUDE, flist.filterCell(new KeyValue(r1, r1, r1)));
347    assertEquals(ReturnCode.INCLUDE, flist.filterCell(new KeyValue(r11, r11, r11)));
348
349    flist.reset();
350    flist.filterRowKey(KeyValueUtil.createFirstOnRow(r2));
351    assertEquals(ReturnCode.NEXT_COL, flist.filterCell(new KeyValue(r2, r2, r2)));
352  }
353
354  public static class AlwaysNextColFilter extends FilterBase {
355    public AlwaysNextColFilter() {
356      super();
357    }
358
359    @Override
360    public ReturnCode filterCell(final Cell v) {
361      return ReturnCode.NEXT_COL;
362    }
363
364    public static AlwaysNextColFilter parseFrom(final byte[] pbBytes)
365        throws DeserializationException {
366      return new AlwaysNextColFilter();
367    }
368  }
369
370  /**
371   * Test serialization
372   * @throws Exception
373   */
374  @Test
375  public void testSerialization() throws Exception {
376    List<Filter> filters = new ArrayList<>();
377    filters.add(new PageFilter(MAX_PAGES));
378    filters.add(new WhileMatchFilter(new PrefixFilter(Bytes.toBytes("yyy"))));
379    Filter filterMPALL =
380      new FilterList(FilterList.Operator.MUST_PASS_ALL, filters);
381
382    // Decompose filterMPALL to bytes.
383    byte[] buffer = filterMPALL.toByteArray();
384
385    // Recompose filterMPALL.
386    FilterList newFilter = FilterList.parseFrom(buffer);
387
388    // Run tests
389    mpOneTest(ProtobufUtil.toFilter(ProtobufUtil.toFilter(getFilterMPONE())));
390    mpAllTest(ProtobufUtil.toFilter(ProtobufUtil.toFilter(getMPALLFilter())));
391    orderingTest(ProtobufUtil.toFilter(ProtobufUtil.toFilter(getOrderingFilter())));
392  }
393
394  /**
395   * Test filterCell logic.
396   * @throws Exception
397   */
398  @Test
399  public void testFilterCell() throws Exception {
400    Filter includeFilter = new FilterBase() {
401      @Override
402      public Filter.ReturnCode filterCell(final Cell v) {
403        return Filter.ReturnCode.INCLUDE;
404      }
405    };
406
407    Filter alternateFilter = new FilterBase() {
408      boolean returnInclude = true;
409
410      @Override
411      public Filter.ReturnCode filterCell(final Cell v) {
412        Filter.ReturnCode returnCode = returnInclude ? Filter.ReturnCode.INCLUDE :
413                                                       Filter.ReturnCode.SKIP;
414        returnInclude = !returnInclude;
415        return returnCode;
416      }
417    };
418
419    Filter alternateIncludeFilter = new FilterBase() {
420      boolean returnIncludeOnly = false;
421
422      @Override
423      public Filter.ReturnCode filterCell(final Cell v) {
424        Filter.ReturnCode returnCode = returnIncludeOnly ? Filter.ReturnCode.INCLUDE :
425                                                           Filter.ReturnCode.INCLUDE_AND_NEXT_COL;
426        returnIncludeOnly = !returnIncludeOnly;
427        return returnCode;
428      }
429    };
430
431    // Check must pass one filter.
432    FilterList mpOnefilterList = new FilterList(Operator.MUST_PASS_ONE,
433        Arrays.asList(new Filter[] { includeFilter, alternateIncludeFilter, alternateFilter }));
434    // INCLUDE, INCLUDE, INCLUDE_AND_NEXT_COL.
435    assertEquals(ReturnCode.INCLUDE, mpOnefilterList.filterCell(null));
436    // INCLUDE, SKIP, INCLUDE.
437    assertEquals(Filter.ReturnCode.INCLUDE, mpOnefilterList.filterCell(null));
438
439    // Check must pass all filter.
440    FilterList mpAllfilterList = new FilterList(Operator.MUST_PASS_ALL,
441        Arrays.asList(new Filter[] { includeFilter, alternateIncludeFilter, alternateFilter }));
442    // INCLUDE, INCLUDE, INCLUDE_AND_NEXT_COL.
443    assertEquals(Filter.ReturnCode.INCLUDE_AND_NEXT_COL, mpAllfilterList.filterCell(null));
444    // INCLUDE, SKIP, INCLUDE.
445    assertEquals(Filter.ReturnCode.SKIP, mpAllfilterList.filterCell(null));
446  }
447
448  /**
449   * Test pass-thru of hints.
450   */
451  @Test
452  public void testHintPassThru() throws Exception {
453
454    final KeyValue minKeyValue = new KeyValue(Bytes.toBytes(0L), null, null);
455    final KeyValue maxKeyValue = new KeyValue(Bytes.toBytes(Long.MAX_VALUE),
456        null, null);
457
458    Filter filterNoHint = new FilterBase() {
459      @Override
460      public byte [] toByteArray() {
461        return null;
462      }
463
464      @Override
465      public ReturnCode filterCell(final Cell ignored) throws IOException {
466        return ReturnCode.INCLUDE;
467      }
468    };
469
470    Filter filterMinHint = new FilterBase() {
471      @Override
472      public ReturnCode filterCell(final Cell ignored) {
473        return ReturnCode.SEEK_NEXT_USING_HINT;
474      }
475
476      @Override
477      public Cell getNextCellHint(Cell currentKV) {
478        return minKeyValue;
479      }
480
481      @Override
482      public byte [] toByteArray() {return null;}
483    };
484
485    Filter filterMaxHint = new FilterBase() {
486      @Override
487      public ReturnCode filterCell(final Cell ignored) {
488        return ReturnCode.SEEK_NEXT_USING_HINT;
489      }
490
491      @Override
492      public Cell getNextCellHint(Cell cell) {
493        return new KeyValue(Bytes.toBytes(Long.MAX_VALUE), null, null);
494      }
495
496      @Override
497      public byte [] toByteArray() {return null;}
498    };
499
500    CellComparator comparator = CellComparator.getInstance();
501    // MUST PASS ONE
502
503    // Should take the min if given two hints
504    FilterList filterList = new FilterList(Operator.MUST_PASS_ONE,
505        Arrays.asList(new Filter [] { filterMinHint, filterMaxHint } ));
506    assertEquals(0, comparator.compare(filterList.getNextCellHint(null), minKeyValue));
507
508    // Should have no hint if any filter has no hint
509    filterList = new FilterList(Operator.MUST_PASS_ONE,
510        Arrays.asList(
511            new Filter [] { filterMinHint, filterMaxHint, filterNoHint } ));
512    assertNull(filterList.getNextCellHint(null));
513    filterList = new FilterList(Operator.MUST_PASS_ONE,
514        Arrays.asList(new Filter [] { filterNoHint, filterMaxHint } ));
515    assertNull(filterList.getNextCellHint(null));
516
517    // Should give max hint if its the only one
518    filterList = new FilterList(Operator.MUST_PASS_ONE,
519        Arrays.asList(new Filter[] { filterMaxHint, filterMaxHint }));
520    assertEquals(0, comparator.compare(filterList.getNextCellHint(null), maxKeyValue));
521
522    // MUST PASS ALL
523
524    // Should take the first hint
525    filterList = new FilterList(Operator.MUST_PASS_ALL,
526        Arrays.asList(new Filter [] { filterMinHint, filterMaxHint } ));
527    filterList.filterCell(null);
528    assertEquals(0, comparator.compare(filterList.getNextCellHint(null), minKeyValue));
529
530    filterList = new FilterList(Operator.MUST_PASS_ALL,
531        Arrays.asList(new Filter [] { filterMaxHint, filterMinHint } ));
532    filterList.filterCell(null);
533    assertEquals(0, comparator.compare(filterList.getNextCellHint(null), maxKeyValue));
534
535    // Should have first hint even if a filter has no hint
536    filterList = new FilterList(Operator.MUST_PASS_ALL,
537        Arrays.asList(new Filter[] { filterNoHint, filterMinHint, filterMaxHint }));
538    filterList.filterCell(null);
539    assertEquals(0, comparator.compare(filterList.getNextCellHint(null), minKeyValue));
540    filterList = new FilterList(Operator.MUST_PASS_ALL,
541        Arrays.asList(new Filter[] { filterNoHint, filterMaxHint }));
542    filterList.filterCell(null);
543    assertEquals(0, comparator.compare(filterList.getNextCellHint(null), maxKeyValue));
544    filterList = new FilterList(Operator.MUST_PASS_ALL,
545        Arrays.asList(new Filter[] { filterNoHint, filterMinHint }));
546    filterList.filterCell(null);
547    assertEquals(0, comparator.compare(filterList.getNextCellHint(null), minKeyValue));
548  }
549
550  /**
551   * Tests the behavior of transform() in a hierarchical filter.
552   *
553   * transform() only applies after a filterCell() whose return-code includes the KeyValue.
554   * Lazy evaluation of AND
555   */
556  @Test
557  public void testTransformMPO() throws Exception {
558    // Apply the following filter:
559    //     (family=fam AND qualifier=qual1 AND KeyOnlyFilter)
560    //  OR (family=fam AND qualifier=qual2)
561    final FilterList flist = new FilterList(Operator.MUST_PASS_ONE, Lists.<Filter>newArrayList(
562        new FilterList(Operator.MUST_PASS_ALL, Lists.<Filter>newArrayList(
563            new FamilyFilter(CompareOperator.EQUAL, new BinaryComparator(Bytes.toBytes("fam"))),
564            new QualifierFilter(CompareOperator.EQUAL, new BinaryComparator(Bytes.toBytes("qual1"))),
565            new KeyOnlyFilter())),
566        new FilterList(Operator.MUST_PASS_ALL, Lists.<Filter>newArrayList(
567            new FamilyFilter(CompareOperator.EQUAL, new BinaryComparator(Bytes.toBytes("fam"))),
568            new QualifierFilter(CompareOperator.EQUAL, new BinaryComparator(Bytes.toBytes("qual2")))))));
569
570    final KeyValue kvQual1 = new KeyValue(
571        Bytes.toBytes("row"), Bytes.toBytes("fam"), Bytes.toBytes("qual1"), Bytes.toBytes("value"));
572    final KeyValue kvQual2 = new KeyValue(
573        Bytes.toBytes("row"), Bytes.toBytes("fam"), Bytes.toBytes("qual2"), Bytes.toBytes("value"));
574    final KeyValue kvQual3 = new KeyValue(
575        Bytes.toBytes("row"), Bytes.toBytes("fam"), Bytes.toBytes("qual3"), Bytes.toBytes("value"));
576
577    // Value for fam:qual1 should be stripped:
578    assertEquals(Filter.ReturnCode.INCLUDE, flist.filterCell(kvQual1));
579    final KeyValue transformedQual1 = KeyValueUtil.ensureKeyValue(flist.transformCell(kvQual1));
580    assertEquals(0, transformedQual1.getValueLength());
581
582    // Value for fam:qual2 should not be stripped:
583    assertEquals(Filter.ReturnCode.INCLUDE, flist.filterCell(kvQual2));
584    final KeyValue transformedQual2 = KeyValueUtil.ensureKeyValue(flist.transformCell(kvQual2));
585    assertEquals("value", Bytes.toString(transformedQual2.getValueArray(),
586      transformedQual2.getValueOffset(), transformedQual2.getValueLength()));
587
588    // Other keys should be skipped:
589    assertEquals(Filter.ReturnCode.SKIP, flist.filterCell(kvQual3));
590  }
591
592  @Test
593  public void testWithMultiVersionsInSameRow() throws Exception {
594    FilterList filterList01 =
595        new FilterList(Operator.MUST_PASS_ONE, new ColumnPaginationFilter(1, 0));
596
597    KeyValue kv1 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam"), Bytes.toBytes("qual"),
598        1, Bytes.toBytes("value"));
599    KeyValue kv2 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam"), Bytes.toBytes("qual"),
600        2, Bytes.toBytes("value"));
601    KeyValue kv3 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam"), Bytes.toBytes("qual"),
602        3, Bytes.toBytes("value"));
603
604    assertEquals(ReturnCode.INCLUDE_AND_NEXT_COL, filterList01.filterCell(kv1));
605    assertEquals(ReturnCode.SKIP, filterList01.filterCell(kv2));
606    assertEquals(ReturnCode.SKIP, filterList01.filterCell(kv3));
607
608    FilterList filterList11 =
609        new FilterList(Operator.MUST_PASS_ONE, new ColumnPaginationFilter(1, 1));
610
611    assertEquals(ReturnCode.NEXT_COL, filterList11.filterCell(kv1));
612    assertEquals(ReturnCode.SKIP, filterList11.filterCell(kv2));
613    assertEquals(ReturnCode.SKIP, filterList11.filterCell(kv3));
614  }
615
616  @Test
617  public void testMPONEWithSeekNextUsingHint() throws Exception {
618    byte[] col = Bytes.toBytes("c");
619    FilterList filterList =
620        new FilterList(Operator.MUST_PASS_ONE, new ColumnPaginationFilter(1, col));
621
622    KeyValue kv1 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam"), Bytes.toBytes("a"), 1,
623        Bytes.toBytes("value"));
624    KeyValue kv2 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam"), Bytes.toBytes("b"), 2,
625        Bytes.toBytes("value"));
626    KeyValue kv3 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam"), Bytes.toBytes("c"), 3,
627        Bytes.toBytes("value"));
628    KeyValue kv4 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam"), Bytes.toBytes("c"), 4,
629        Bytes.toBytes("value"));
630
631    assertEquals(ReturnCode.SEEK_NEXT_USING_HINT, filterList.filterCell(kv1));
632    assertEquals(ReturnCode.SKIP, filterList.filterCell(kv2));
633    assertEquals(ReturnCode.INCLUDE_AND_NEXT_COL, filterList.filterCell(kv3));
634    assertEquals(ReturnCode.SKIP, filterList.filterCell(kv4));
635  }
636
637  private static class MockFilter extends FilterBase {
638    private ReturnCode targetRetCode;
639    public boolean didCellPassToTheFilter = false;
640
641    public MockFilter(ReturnCode targetRetCode) {
642      this.targetRetCode = targetRetCode;
643    }
644
645    @Override
646    public ReturnCode filterCell(final Cell v) throws IOException {
647      this.didCellPassToTheFilter = true;
648      return 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 MockFilter(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.NEXT_COL, filterList.filterCell(kv1));
748
749    filterList = new FilterList(Operator.MUST_PASS_ALL, filter4, filter6);
750    assertEquals(ReturnCode.NEXT_COL, 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 MockFilter(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
824  @Test
825  public void testReversedFilterListWithMockSeekHintFilter() throws IOException {
826    KeyValue kv1 = new KeyValue(Bytes.toBytes("row1"), Bytes.toBytes("fam"), Bytes.toBytes("a"), 1,
827        Bytes.toBytes("value"));
828    KeyValue kv2 = new KeyValue(Bytes.toBytes("row2"), Bytes.toBytes("fam"), Bytes.toBytes("a"), 1,
829        Bytes.toBytes("value"));
830    KeyValue kv3 = new KeyValue(Bytes.toBytes("row3"), Bytes.toBytes("fam"), Bytes.toBytes("a"), 1,
831        Bytes.toBytes("value"));
832    Filter filter1 = new MockSeekHintFilter(kv1);
833    filter1.setReversed(true);
834    Filter filter2 = new MockSeekHintFilter(kv2);
835    filter2.setReversed(true);
836    Filter filter3 = new MockSeekHintFilter(kv3);
837    filter3.setReversed(true);
838
839    FilterList filterList = new FilterList(Operator.MUST_PASS_ONE);
840    filterList.setReversed(true);
841    filterList.addFilter(filter1);
842    filterList.addFilter(filter2);
843    filterList.addFilter(filter3);
844
845    Assert.assertEquals(ReturnCode.SEEK_NEXT_USING_HINT, filterList.filterCell(kv1));
846    Assert.assertEquals(kv3, filterList.getNextCellHint(kv1));
847
848    filterList = new FilterList(Operator.MUST_PASS_ALL);
849    filterList.setReversed(true);
850    filterList.addFilter(filter1);
851    filterList.addFilter(filter2);
852    filterList.addFilter(filter3);
853
854    Assert.assertEquals(ReturnCode.SEEK_NEXT_USING_HINT, filterList.filterCell(kv1));
855    Assert.assertEquals(kv1, filterList.getNextCellHint(kv1));
856  }
857
858  @Test
859  public void testReversedFilterListWithOR() throws IOException {
860    byte[] r22 = Bytes.toBytes("Row22");
861    byte[] r2 = Bytes.toBytes("Row2");
862    byte[] r1 = Bytes.toBytes("Row1");
863
864    FilterList filterList = new FilterList(FilterList.Operator.MUST_PASS_ONE);
865    filterList.setReversed(true);
866    PrefixFilter prefixFilter = new PrefixFilter(r2);
867    prefixFilter.setReversed(true);
868    filterList.addFilter(prefixFilter);
869    filterList.filterRowKey(KeyValueUtil.createFirstOnRow(r22));
870    assertEquals(ReturnCode.INCLUDE, filterList.filterCell(new KeyValue(r22, r22, r22)));
871    assertEquals(ReturnCode.INCLUDE, filterList.filterCell(new KeyValue(r2, r2, r2)));
872
873    filterList.reset();
874    filterList.filterRowKey(KeyValueUtil.createFirstOnRow(r1));
875    assertEquals(ReturnCode.SKIP, filterList.filterCell(new KeyValue(r1, r1, r1)));
876
877    filterList = new FilterList(FilterList.Operator.MUST_PASS_ONE);
878    filterList.setReversed(true);
879    AlwaysNextColFilter alwaysNextColFilter = new AlwaysNextColFilter();
880    alwaysNextColFilter.setReversed(true);
881    prefixFilter = new PrefixFilter(r2);
882    prefixFilter.setReversed(true);
883    filterList.addFilter(alwaysNextColFilter);
884    filterList.addFilter(prefixFilter);
885    filterList.filterRowKey(KeyValueUtil.createFirstOnRow(r22));
886    assertEquals(ReturnCode.INCLUDE, filterList.filterCell(new KeyValue(r22, r22, r22)));
887    assertEquals(ReturnCode.INCLUDE, filterList.filterCell(new KeyValue(r2, r2, r2)));
888
889    filterList.reset();
890    filterList.filterRowKey(KeyValueUtil.createFirstOnRow(r1));
891    assertEquals(ReturnCode.NEXT_COL, filterList.filterCell(new KeyValue(r1, r1, r1)));
892  }
893
894  @Test
895  public void testKeyOnlyFilterTransformCell() throws IOException {
896    Cell c;
897    KeyValue kv1 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("cf"), Bytes.toBytes("column1"),
898        1, Bytes.toBytes("value1"));
899    KeyValue kv2 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("cf"), Bytes.toBytes("column1"),
900        2, Bytes.toBytes("value2"));
901
902    Filter filter1 = new SingleColumnValueFilter(Bytes.toBytes("cf"), Bytes.toBytes("column1"),
903        CompareOperator.EQUAL, Bytes.toBytes("value1"));
904    Filter filter2 = new SingleColumnValueFilter(Bytes.toBytes("cf"), Bytes.toBytes("column1"),
905        CompareOperator.EQUAL, Bytes.toBytes("value2"));
906    FilterList internalFilterList = new FilterList(Operator.MUST_PASS_ONE, filter1, filter2);
907
908    FilterList keyOnlyFilterFirst =
909        new FilterList(Operator.MUST_PASS_ALL, new KeyOnlyFilter(), internalFilterList);
910
911    assertEquals(ReturnCode.INCLUDE, keyOnlyFilterFirst.filterCell(kv1));
912    c = keyOnlyFilterFirst.transformCell(kv1);
913    assertEquals(0, c.getValueLength());
914    assertEquals(ReturnCode.INCLUDE, keyOnlyFilterFirst.filterCell(kv2));
915    c = keyOnlyFilterFirst.transformCell(kv2);
916    assertEquals(0, c.getValueLength());
917
918    internalFilterList.reset();
919    FilterList keyOnlyFilterLast =
920        new FilterList(Operator.MUST_PASS_ALL, new KeyOnlyFilter(), internalFilterList);
921    assertEquals(ReturnCode.INCLUDE, keyOnlyFilterLast.filterCell(kv1));
922    c = keyOnlyFilterLast.transformCell(kv1);
923    assertEquals(0, c.getValueLength());
924    assertEquals(ReturnCode.INCLUDE, keyOnlyFilterLast.filterCell(kv2));
925    c = keyOnlyFilterLast.transformCell(kv2);
926    assertEquals(0, c.getValueLength());
927  }
928
929  @Test
930  public void testEmptyFilterListTransformCell() throws IOException {
931    KeyValue kv = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("cf"), Bytes.toBytes("column1"),
932        1, Bytes.toBytes("value"));
933    FilterList filterList = new FilterList(Operator.MUST_PASS_ALL);
934    assertEquals(ReturnCode.INCLUDE, filterList.filterCell(kv));
935    assertEquals(kv, filterList.transformCell(kv));
936
937    filterList = new FilterList(Operator.MUST_PASS_ONE);
938    assertEquals(ReturnCode.INCLUDE, filterList.filterCell(kv));
939    assertEquals(kv, filterList.transformCell(kv));
940  }
941
942  private static class MockNextRowFilter extends FilterBase {
943    private int hitCount = 0;
944
945    @Override
946    public ReturnCode filterCell(final Cell v) throws IOException {
947      hitCount++;
948      return ReturnCode.NEXT_ROW;
949    }
950
951    public int getHitCount() {
952      return hitCount;
953    }
954  }
955
956  @Test
957  public void testRowCountFilter() throws IOException {
958    KeyValue kv1 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam1"), Bytes.toBytes("a"), 1,
959        Bytes.toBytes("value"));
960    KeyValue kv2 = new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("fam2"), Bytes.toBytes("a"), 2,
961        Bytes.toBytes("value"));
962    MockNextRowFilter mockNextRowFilter = new MockNextRowFilter();
963    FilterList filter = new FilterList(Operator.MUST_PASS_ONE, mockNextRowFilter);
964    filter.filterCell(kv1);
965    filter.filterCell(kv2);
966    assertEquals(2, mockNextRowFilter.getHitCount());
967  }
968
969  private static class TransformFilter extends FilterBase {
970    private ReturnCode targetRetCode;
971    private boolean transformed = false;
972
973    public TransformFilter(ReturnCode targetRetCode) {
974      this.targetRetCode = targetRetCode;
975    }
976
977    @Override
978    public ReturnCode filterCell(final Cell v) throws IOException {
979      return targetRetCode;
980    }
981
982    @Override
983    public Cell transformCell(Cell c) throws IOException {
984      transformed = true;
985      return super.transformCell(c);
986    }
987
988    public boolean getTransformed() {
989      return this.transformed;
990    }
991  }
992
993  @Test
994  public void testTransformCell() throws IOException {
995    KeyValue kv =
996        new KeyValue(Bytes.toBytes("row"), Bytes.toBytes("cf"), Bytes.toBytes("column1"), 1,
997            Bytes.toBytes("value"));
998
999    // case MUST_PASS_ONE
1000    TransformFilter filter1 = new TransformFilter(ReturnCode.INCLUDE);
1001    TransformFilter filter2 = new TransformFilter(ReturnCode.NEXT_ROW);
1002    TransformFilter filter3 = new TransformFilter(ReturnCode.SEEK_NEXT_USING_HINT);
1003    FilterList filterList = new FilterList(Operator.MUST_PASS_ONE, filter1, filter2, filter3);
1004    Assert.assertEquals(ReturnCode.INCLUDE, filterList.filterCell(kv));
1005    Assert.assertEquals(kv, filterList.transformCell(kv));
1006    Assert.assertEquals(true, filter1.getTransformed());
1007    Assert.assertEquals(false, filter2.getTransformed());
1008    Assert.assertEquals(false, filter3.getTransformed());
1009
1010    // case MUST_PASS_ALL
1011    filter1 = new TransformFilter(ReturnCode.INCLUDE);
1012    filter2 = new TransformFilter(ReturnCode.INCLUDE_AND_SEEK_NEXT_ROW);
1013    filter3 = new TransformFilter(ReturnCode.INCLUDE_AND_NEXT_COL);
1014    filterList = new FilterList(Operator.MUST_PASS_ALL, filter1, filter2, filter3);
1015
1016    Assert.assertEquals(ReturnCode.INCLUDE_AND_SEEK_NEXT_ROW, filterList.filterCell(kv));
1017    Assert.assertEquals(kv, filterList.transformCell(kv));
1018    Assert.assertEquals(true, filter1.getTransformed());
1019    Assert.assertEquals(true, filter2.getTransformed());
1020    Assert.assertEquals(true, filter3.getTransformed());
1021  }
1022}
1023