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;
021
022import java.io.IOException;
023import java.util.ArrayList;
024import java.util.Arrays;
025import java.util.List;
026import org.apache.hadoop.hbase.Cell;
027import org.apache.hadoop.hbase.HBaseClassTestRule;
028import org.apache.hadoop.hbase.HBaseTestingUtility;
029import org.apache.hadoop.hbase.HConstants;
030import org.apache.hadoop.hbase.KeyValueUtil;
031import org.apache.hadoop.hbase.TableName;
032import org.apache.hadoop.hbase.client.Put;
033import org.apache.hadoop.hbase.client.Result;
034import org.apache.hadoop.hbase.client.ResultScanner;
035import org.apache.hadoop.hbase.client.Scan;
036import org.apache.hadoop.hbase.client.Table;
037import org.apache.hadoop.hbase.filter.MultiRowRangeFilter.RowRange;
038import org.apache.hadoop.hbase.testclassification.MediumTests;
039import org.apache.hadoop.hbase.util.Bytes;
040import org.junit.AfterClass;
041import org.junit.Assert;
042import org.junit.BeforeClass;
043import org.junit.ClassRule;
044import org.junit.Rule;
045import org.junit.Test;
046import org.junit.experimental.categories.Category;
047import org.junit.rules.TestName;
048import org.slf4j.Logger;
049import org.slf4j.LoggerFactory;
050
051@Category(MediumTests.class)
052public class TestMultiRowRangeFilter {
053
054  @ClassRule
055  public static final HBaseClassTestRule CLASS_RULE =
056      HBaseClassTestRule.forClass(TestMultiRowRangeFilter.class);
057
058  private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
059  private static final Logger LOG = LoggerFactory.getLogger(TestMultiRowRangeFilter.class);
060  private byte[] family = Bytes.toBytes("family");
061  private byte[] qf = Bytes.toBytes("qf");
062  private byte[] value = Bytes.toBytes("val");
063  private TableName tableName;
064  private int numRows = 100;
065
066  @Rule
067  public TestName name = new TestName();
068
069  /**
070   * @throws Exception
071   */
072  @BeforeClass
073  public static void setUpBeforeClass() throws Exception {
074    TEST_UTIL.startMiniCluster();
075  }
076
077  /**
078   * @throws Exception
079   */
080  @AfterClass
081  public static void tearDownAfterClass() throws Exception {
082    TEST_UTIL.shutdownMiniCluster();
083  }
084
085  @Test
086  public void testRowKeyPrefixWithEmptyPrefix() throws IOException {
087    byte[] prefix = {};
088    byte[][] rowKeyPrefixes = new byte[1][];
089    rowKeyPrefixes[0] = prefix;
090    MultiRowRangeFilter filter = new MultiRowRangeFilter(rowKeyPrefixes);
091    List<RowRange> actualRanges = filter.getRowRanges();
092    List<RowRange> expectedRanges = new ArrayList<>();
093    expectedRanges.add(
094      new RowRange(HConstants.EMPTY_START_ROW, true, HConstants.EMPTY_END_ROW, false)
095    );
096    assertRangesEqual(expectedRanges, actualRanges);
097  }
098
099  @Test
100  public void testRowKeyPrefixWithLastIncrementablePrefix() throws IOException {
101    byte[] prefix = {(byte) 0x12, (byte) 0x23, (byte) 0xFF, (byte) 0xFE};
102    byte[][] rowKeyPrefixes = new byte[1][];
103    rowKeyPrefixes[0] = prefix;
104    MultiRowRangeFilter filter = new MultiRowRangeFilter(rowKeyPrefixes);
105    List<RowRange> actualRanges = filter.getRowRanges();
106    List<RowRange> expectedRanges = new ArrayList<>();
107    final byte[] expectedStop = {(byte) 0x12, (byte) 0x23, (byte) 0xFF, (byte) 0xFF};
108    expectedRanges.add(new RowRange(prefix, true, expectedStop , false));
109    assertRangesEqual(expectedRanges, actualRanges);
110  }
111
112  @Test
113  public void testRowKeyPrefixWithoutLastIncrementablePrefix() throws IOException {
114    byte[] prefix = {(byte) 0x12, (byte) 0x23, (byte) 0xFF, (byte) 0xFF};
115    byte[][] rowKeyPrefixes = new byte[1][];
116    rowKeyPrefixes[0] = prefix;
117    MultiRowRangeFilter filter = new MultiRowRangeFilter(rowKeyPrefixes);
118    List<RowRange> actualRanges = filter.getRowRanges();
119    List<RowRange> expectedRanges = new ArrayList<>();
120    final byte[] expectedStop = {(byte) 0x12, (byte) 0x24};
121    expectedRanges.add(new RowRange(prefix, true, expectedStop , false));
122    assertRangesEqual(expectedRanges, actualRanges);
123  }
124
125  @Test
126  public void testRowKeyPrefixWithMergablePrefix() throws IOException {
127    byte[] prefix1 = {(byte) 0x12, (byte) 0x23, (byte) 0xFF, (byte) 0xFE};
128    byte[] prefix2 = {(byte) 0x12, (byte) 0x23, (byte) 0xFF, (byte) 0xFF};
129    byte[][] rowKeyPrefixes = new byte[2][];
130    rowKeyPrefixes[0] = prefix1;
131    rowKeyPrefixes[1] = prefix2;
132    MultiRowRangeFilter filter = new MultiRowRangeFilter(rowKeyPrefixes);
133    List<RowRange> actualRanges = filter.getRowRanges();
134    List<RowRange> expectedRanges = new ArrayList<>();
135    final byte[] expectedStop = {(byte) 0x12, (byte) 0x24};
136    expectedRanges.add(new RowRange(prefix1, true, expectedStop , false));
137    assertRangesEqual(expectedRanges, actualRanges);
138  }
139
140  @Test
141  public void testRanges() throws IOException {
142    byte[] key1Start = new byte[] {-3};
143    byte[] key1End  = new byte[] {-2};
144
145    byte[] key2Start = new byte[] {5};
146    byte[] key2End  = new byte[] {6};
147
148    byte[] badKey = new byte[] {-10};
149
150    MultiRowRangeFilter filter = new MultiRowRangeFilter(Arrays.asList(
151      new MultiRowRangeFilter.RowRange(key1Start, true, key1End, false),
152      new MultiRowRangeFilter.RowRange(key2Start, true, key2End, false)
153        ));
154    filter.filterRowKey(KeyValueUtil.createFirstOnRow(badKey));
155    /*
156     * FAILS -- includes BAD key!
157     * Expected :SEEK_NEXT_USING_HINT
158     * Actual   :INCLUDE
159     * */
160    assertEquals(Filter.ReturnCode.SEEK_NEXT_USING_HINT, filter.filterCell(null));
161  }
162
163  @Test
164  public void testOutOfOrderScannerNextException() throws Exception {
165    MultiRowRangeFilter filter = new MultiRowRangeFilter(Arrays.asList(
166            new MultiRowRangeFilter.RowRange(Bytes.toBytes("b"), true, Bytes.toBytes("c"), true),
167            new MultiRowRangeFilter.RowRange(Bytes.toBytes("d"), true, Bytes.toBytes("e"), true)
168    ));
169    filter.filterRowKey(KeyValueUtil.createFirstOnRow(Bytes.toBytes("a")));
170    assertEquals(Filter.ReturnCode.SEEK_NEXT_USING_HINT, filter.filterCell(null));
171    filter.filterRowKey(KeyValueUtil.createFirstOnRow(Bytes.toBytes("b")));
172    assertEquals(Filter.ReturnCode.INCLUDE, filter.filterCell(null));
173    filter.filterRowKey(KeyValueUtil.createFirstOnRow(Bytes.toBytes("c")));
174    assertEquals(Filter.ReturnCode.INCLUDE, filter.filterCell(null));
175    filter.filterRowKey(KeyValueUtil.createFirstOnRow(Bytes.toBytes("d")));
176    assertEquals(Filter.ReturnCode.INCLUDE, filter.filterCell(null));
177    filter.filterRowKey(KeyValueUtil.createFirstOnRow(Bytes.toBytes("e")));
178    assertEquals(Filter.ReturnCode.INCLUDE, filter.filterCell(null));
179  }
180
181  @Test
182  public void testMergeAndSortWithEmptyStartRow() throws IOException {
183    List<RowRange> ranges = new ArrayList<>();
184    ranges.add(new RowRange(Bytes.toBytes(""), true, Bytes.toBytes(20), false));
185    ranges.add(new RowRange(Bytes.toBytes(15), true, Bytes.toBytes(40), false));
186    List<RowRange> actualRanges = MultiRowRangeFilter.sortAndMerge(ranges);
187    List<RowRange> expectedRanges = new ArrayList<>();
188    expectedRanges.add(new RowRange(Bytes.toBytes(""), true, Bytes.toBytes(40), false));
189    assertRangesEqual(expectedRanges, actualRanges);
190  }
191
192  @Test
193  public void testMergeAndSortWithEmptyStopRow() throws IOException {
194    List<RowRange> ranges = new ArrayList<>();
195    ranges.add(new RowRange(Bytes.toBytes(10), true, Bytes.toBytes(20), false));
196    ranges.add(new RowRange(Bytes.toBytes(15), true, Bytes.toBytes(""), false));
197    ranges.add(new RowRange(Bytes.toBytes(30), true, Bytes.toBytes(70), false));
198    List<RowRange> actualRanges = MultiRowRangeFilter.sortAndMerge(ranges);
199    List<RowRange> expectedRanges = new ArrayList<>();
200    expectedRanges.add(new RowRange(Bytes.toBytes(10), true, Bytes.toBytes(""), false));
201    assertRangesEqual(expectedRanges, actualRanges);
202  }
203
204  @Test
205  public void testMergeAndSortWithEmptyStartRowAndStopRow() throws IOException {
206    List<RowRange> ranges = new ArrayList<>();
207    ranges.add(new RowRange(Bytes.toBytes(10), true, Bytes.toBytes(20), false));
208    ranges.add(new RowRange(Bytes.toBytes(""), true, Bytes.toBytes(""), false));
209    ranges.add(new RowRange(Bytes.toBytes(30), true, Bytes.toBytes(70), false));
210    List<RowRange> actualRanges = MultiRowRangeFilter.sortAndMerge(ranges);
211    List<RowRange> expectedRanges = new ArrayList<>();
212    expectedRanges.add(new RowRange(Bytes.toBytes(""), true, Bytes.toBytes(""), false));
213    assertRangesEqual(expectedRanges, actualRanges);
214  }
215
216  @Test(expected=IllegalArgumentException.class)
217  public void testMultiRowRangeWithoutRange() throws IOException {
218    List<RowRange> ranges = new ArrayList<>();
219    new MultiRowRangeFilter(ranges);
220  }
221
222  @Test(expected=IllegalArgumentException.class)
223  public void testMultiRowRangeWithInvalidRange() throws IOException {
224    List<RowRange> ranges = new ArrayList<>();
225    ranges.add(new RowRange(Bytes.toBytes(10), true, Bytes.toBytes(20), false));
226    // the start row larger than the stop row
227    ranges.add(new RowRange(Bytes.toBytes(80), true, Bytes.toBytes(20), false));
228    ranges.add(new RowRange(Bytes.toBytes(30), true, Bytes.toBytes(70), false));
229    new MultiRowRangeFilter(ranges);
230  }
231
232  @Test
233  public void testMergeAndSortWithoutOverlap() throws IOException {
234    List<RowRange> ranges = new ArrayList<>();
235    ranges.add(new RowRange(Bytes.toBytes(10), true, Bytes.toBytes(20), false));
236    ranges.add(new RowRange(Bytes.toBytes(30), true, Bytes.toBytes(40), false));
237    ranges.add(new RowRange(Bytes.toBytes(60), true, Bytes.toBytes(70), false));
238    List<RowRange> actualRanges = MultiRowRangeFilter.sortAndMerge(ranges);
239    List<RowRange> expectedRanges = new ArrayList<>();
240    expectedRanges.add(new RowRange(Bytes.toBytes(10), true, Bytes.toBytes(20), false));
241    expectedRanges.add(new RowRange(Bytes.toBytes(30), true, Bytes.toBytes(40), false));
242    expectedRanges.add(new RowRange(Bytes.toBytes(60), true, Bytes.toBytes(70), false));
243    assertRangesEqual(expectedRanges, actualRanges);
244  }
245
246  @Test
247  public void testMergeAndSortWithOverlap() throws IOException {
248    List<RowRange> ranges = new ArrayList<>();
249    ranges.add(new RowRange(Bytes.toBytes(10), true, Bytes.toBytes(20), false));
250    ranges.add(new RowRange(Bytes.toBytes(15), true, Bytes.toBytes(40), false));
251    ranges.add(new RowRange(Bytes.toBytes(20), true, Bytes.toBytes(30), false));
252    ranges.add(new RowRange(Bytes.toBytes(30), true, Bytes.toBytes(50), false));
253    ranges.add(new RowRange(Bytes.toBytes(30), true, Bytes.toBytes(70), false));
254    ranges.add(new RowRange(Bytes.toBytes(90), true, Bytes.toBytes(100), false));
255    ranges.add(new RowRange(Bytes.toBytes(95), true, Bytes.toBytes(100), false));
256    List<RowRange> actualRanges = MultiRowRangeFilter.sortAndMerge(ranges);
257    List<RowRange> expectedRanges = new ArrayList<>();
258    expectedRanges.add(new RowRange(Bytes.toBytes(10), true, Bytes.toBytes(70), false));
259    expectedRanges.add(new RowRange(Bytes.toBytes(90), true, Bytes.toBytes(100), false));
260    assertRangesEqual(expectedRanges, actualRanges);
261  }
262
263  @Test
264  public void testMergeAndSortWithStartRowInclusive() throws IOException {
265    List<RowRange> ranges = new ArrayList<>();
266    ranges.add(new RowRange(Bytes.toBytes(10), true, Bytes.toBytes(20), false));
267    ranges.add(new RowRange(Bytes.toBytes(20), true, Bytes.toBytes(""), false));
268    List<RowRange> actualRanges = MultiRowRangeFilter.sortAndMerge(ranges);
269    List<RowRange> expectedRanges = new ArrayList<>();
270    expectedRanges.add(new RowRange(Bytes.toBytes(10), true, Bytes.toBytes(""), false));
271    assertRangesEqual(expectedRanges, actualRanges);
272  }
273
274  @Test
275  public void testMergeAndSortWithRowExclusive() throws IOException {
276    List<RowRange> ranges = new ArrayList<>();
277    ranges.add(new RowRange(Bytes.toBytes(10), true, Bytes.toBytes(20), false));
278    ranges.add(new RowRange(Bytes.toBytes(20), false, Bytes.toBytes(""), false));
279    List<RowRange> actualRanges = MultiRowRangeFilter.sortAndMerge(ranges);
280    List<RowRange> expectedRanges = new ArrayList<>();
281    expectedRanges.add(new RowRange(Bytes.toBytes(10), true, Bytes.toBytes(20), false));
282    expectedRanges.add(new RowRange(Bytes.toBytes(20), false, Bytes.toBytes(""), false));
283    assertRangesEqual(expectedRanges, actualRanges);
284  }
285
286  @Test
287  public void testMergeAndSortWithRowInclusive() throws IOException {
288    List<RowRange> ranges = new ArrayList<>();
289    ranges.add(new RowRange(Bytes.toBytes(10), true, Bytes.toBytes(20), true));
290    ranges.add(new RowRange(Bytes.toBytes(20), false, Bytes.toBytes(""), false));
291    List<RowRange> actualRanges = MultiRowRangeFilter.sortAndMerge(ranges);
292    List<RowRange> expectedRanges = new ArrayList<>();
293    expectedRanges.add(new RowRange(Bytes.toBytes(10), true, Bytes.toBytes(""), false));
294    assertRangesEqual(expectedRanges, actualRanges);
295  }
296
297  public void assertRangesEqual(List<RowRange> expected, List<RowRange> actual) {
298    assertEquals(expected.size(), actual.size());
299    for(int i = 0; i < expected.size(); i++) {
300      Assert.assertTrue(Bytes.equals(expected.get(i).getStartRow(), actual.get(i).getStartRow()));
301      Assert.assertTrue(expected.get(i).isStartRowInclusive() ==
302          actual.get(i).isStartRowInclusive());
303      Assert.assertTrue(Bytes.equals(expected.get(i).getStopRow(), actual.get(i).getStopRow()));
304      Assert.assertTrue(expected.get(i).isStopRowInclusive() ==
305          actual.get(i).isStopRowInclusive());
306    }
307  }
308
309  @Test
310  public void testMultiRowRangeFilterWithRangeOverlap() throws IOException {
311    tableName = TableName.valueOf(name.getMethodName());
312    Table ht = TEST_UTIL.createTable(tableName, family, Integer.MAX_VALUE);
313    generateRows(numRows, ht, family, qf, value);
314
315    Scan scan = new Scan();
316    scan.setMaxVersions();
317
318    List<RowRange> ranges = new ArrayList<>();
319    ranges.add(new RowRange(Bytes.toBytes(10), true, Bytes.toBytes(20), false));
320    ranges.add(new RowRange(Bytes.toBytes(15), true, Bytes.toBytes(40), false));
321    ranges.add(new RowRange(Bytes.toBytes(65), true, Bytes.toBytes(75), false));
322    ranges.add(new RowRange(Bytes.toBytes(60), true, null, false));
323    ranges.add(new RowRange(Bytes.toBytes(60), true, Bytes.toBytes(80), false));
324
325    MultiRowRangeFilter filter = new MultiRowRangeFilter(ranges);
326    scan.setFilter(filter);
327    int resultsSize = getResultsSize(ht, scan);
328    LOG.info("found " + resultsSize + " results");
329    List<Cell> results1 = getScanResult(Bytes.toBytes(10), Bytes.toBytes(40), ht);
330    List<Cell> results2 = getScanResult(Bytes.toBytes(60), Bytes.toBytes(""), ht);
331
332    assertEquals(results1.size() + results2.size(), resultsSize);
333
334    ht.close();
335  }
336
337  @Test
338  public void testMultiRowRangeFilterWithoutRangeOverlap() throws IOException {
339    tableName = TableName.valueOf(name.getMethodName());
340    Table ht = TEST_UTIL.createTable(tableName, family, Integer.MAX_VALUE);
341    generateRows(numRows, ht, family, qf, value);
342
343    Scan scan = new Scan();
344    scan.setMaxVersions();
345
346    List<RowRange> ranges = new ArrayList<>();
347    ranges.add(new RowRange(Bytes.toBytes(30), true, Bytes.toBytes(40), false));
348    ranges.add(new RowRange(Bytes.toBytes(10), true, Bytes.toBytes(20), false));
349    ranges.add(new RowRange(Bytes.toBytes(60), true, Bytes.toBytes(70), false));
350
351    MultiRowRangeFilter filter = new MultiRowRangeFilter(ranges);
352    scan.setFilter(filter);
353    int resultsSize = getResultsSize(ht, scan);
354    LOG.info("found " + resultsSize + " results");
355    List<Cell> results1 = getScanResult(Bytes.toBytes(10), Bytes.toBytes(20), ht);
356    List<Cell> results2 = getScanResult(Bytes.toBytes(30), Bytes.toBytes(40), ht);
357    List<Cell> results3 = getScanResult(Bytes.toBytes(60), Bytes.toBytes(70), ht);
358
359    assertEquals(results1.size() + results2.size() + results3.size(), resultsSize);
360
361    ht.close();
362  }
363
364  @Test
365  public void testMultiRowRangeFilterWithEmptyStartRow() throws IOException {
366    tableName = TableName.valueOf(name.getMethodName());
367    Table ht = TEST_UTIL.createTable(tableName, family, Integer.MAX_VALUE);
368    generateRows(numRows, ht, family, qf, value);
369    Scan scan = new Scan();
370    scan.setMaxVersions();
371
372    List<RowRange> ranges = new ArrayList<>();
373    ranges.add(new RowRange(Bytes.toBytes(""), true, Bytes.toBytes(10), false));
374    ranges.add(new RowRange(Bytes.toBytes(30), true, Bytes.toBytes(40), false));
375
376    MultiRowRangeFilter filter = new MultiRowRangeFilter(ranges);
377    scan.setFilter(filter);
378    int resultsSize = getResultsSize(ht, scan);
379    List<Cell> results1 = getScanResult(Bytes.toBytes(""), Bytes.toBytes(10), ht);
380    List<Cell> results2 = getScanResult(Bytes.toBytes(30), Bytes.toBytes(40), ht);
381    assertEquals(results1.size() + results2.size(), resultsSize);
382
383    ht.close();
384  }
385
386  @Test
387  public void testMultiRowRangeFilterWithEmptyStopRow() throws IOException {
388    tableName = TableName.valueOf(name.getMethodName());
389    Table ht = TEST_UTIL.createTable(tableName, family, Integer.MAX_VALUE);
390    generateRows(numRows, ht, family, qf, value);
391    Scan scan = new Scan();
392    scan.setMaxVersions();
393
394    List<RowRange> ranges = new ArrayList<>();
395    ranges.add(new RowRange(Bytes.toBytes(10), true, Bytes.toBytes(""), false));
396    ranges.add(new RowRange(Bytes.toBytes(30), true, Bytes.toBytes(40), false));
397
398    MultiRowRangeFilter filter = new MultiRowRangeFilter(ranges);
399    scan.setFilter(filter);
400    int resultsSize = getResultsSize(ht, scan);
401    List<Cell> results1 = getScanResult(Bytes.toBytes(10), Bytes.toBytes(""), ht);
402    assertEquals(results1.size(), resultsSize);
403
404    ht.close();
405  }
406
407  @Test
408  public void testMultiRowRangeFilterWithInclusive() throws IOException {
409    tableName = TableName.valueOf(name.getMethodName());
410    Table ht = TEST_UTIL.createTable(tableName, family, Integer.MAX_VALUE);
411    generateRows(numRows, ht, family, qf, value);
412
413    Scan scan = new Scan();
414    scan.setMaxVersions();
415
416    List<RowRange> ranges = new ArrayList<>();
417    ranges.add(new RowRange(Bytes.toBytes(10), true, Bytes.toBytes(20), false));
418    ranges.add(new RowRange(Bytes.toBytes(20), true, Bytes.toBytes(40), false));
419    ranges.add(new RowRange(Bytes.toBytes(65), true, Bytes.toBytes(75), false));
420    ranges.add(new RowRange(Bytes.toBytes(60), true, null, false));
421    ranges.add(new RowRange(Bytes.toBytes(60), true, Bytes.toBytes(80), false));
422
423    MultiRowRangeFilter filter = new MultiRowRangeFilter(ranges);
424    scan.setFilter(filter);
425    int resultsSize = getResultsSize(ht, scan);
426    LOG.info("found " + resultsSize + " results");
427    List<Cell> results1 = getScanResult(Bytes.toBytes(10), Bytes.toBytes(40), ht);
428    List<Cell> results2 = getScanResult(Bytes.toBytes(60), Bytes.toBytes(""), ht);
429
430    assertEquals(results1.size() + results2.size(), resultsSize);
431
432    ht.close();
433  }
434
435  @Test
436  public void testMultiRowRangeFilterWithExclusive() throws IOException {
437    tableName = TableName.valueOf(name.getMethodName());
438    TEST_UTIL.getConfiguration().setInt(HConstants.HBASE_CLIENT_SCANNER_TIMEOUT_PERIOD, 6000000);
439    Table ht = TEST_UTIL.createTable(tableName, family, Integer.MAX_VALUE);
440    ht.setReadRpcTimeout(600000);
441    ht.setOperationTimeout(6000000);
442    generateRows(numRows, ht, family, qf, value);
443
444    Scan scan = new Scan();
445    scan.setMaxVersions();
446
447    List<RowRange> ranges = new ArrayList<>();
448    ranges.add(new RowRange(Bytes.toBytes(10), true, Bytes.toBytes(20), false));
449    ranges.add(new RowRange(Bytes.toBytes(20), false, Bytes.toBytes(40), false));
450    ranges.add(new RowRange(Bytes.toBytes(65), true, Bytes.toBytes(75), false));
451
452    MultiRowRangeFilter filter = new MultiRowRangeFilter(ranges);
453    scan.setFilter(filter);
454    int resultsSize = getResultsSize(ht, scan);
455    LOG.info("found " + resultsSize + " results");
456    List<Cell> results1 = getScanResult(Bytes.toBytes(10), Bytes.toBytes(40), ht);
457    List<Cell> results2 = getScanResult(Bytes.toBytes(65), Bytes.toBytes(75), ht);
458
459    assertEquals((results1.size() - 1) + results2.size(), resultsSize);
460
461    ht.close();
462  }
463
464  @Test
465  public void testMultiRowRangeWithFilterListAndOperator() throws IOException {
466    tableName = TableName.valueOf(name.getMethodName());
467    Table ht = TEST_UTIL.createTable(tableName, family, Integer.MAX_VALUE);
468    generateRows(numRows, ht, family, qf, value);
469
470    Scan scan = new Scan();
471    scan.setMaxVersions();
472
473    List<RowRange> ranges1 = new ArrayList<>();
474    ranges1.add(new RowRange(Bytes.toBytes(10), true, Bytes.toBytes(20), false));
475    ranges1.add(new RowRange(Bytes.toBytes(30), true, Bytes.toBytes(40), false));
476    ranges1.add(new RowRange(Bytes.toBytes(60), true, Bytes.toBytes(70), false));
477
478    MultiRowRangeFilter filter1 = new MultiRowRangeFilter(ranges1);
479
480    List<RowRange> ranges2 = new ArrayList<>();
481    ranges2.add(new RowRange(Bytes.toBytes(20), true, Bytes.toBytes(40), false));
482    ranges2.add(new RowRange(Bytes.toBytes(80), true, Bytes.toBytes(90), false));
483
484    MultiRowRangeFilter filter2 = new MultiRowRangeFilter(ranges2);
485
486    FilterList filterList = new FilterList(FilterList.Operator.MUST_PASS_ALL);
487    filterList.addFilter(filter1);
488    filterList.addFilter(filter2);
489    scan.setFilter(filterList);
490    int resultsSize = getResultsSize(ht, scan);
491    LOG.info("found " + resultsSize + " results");
492    List<Cell> results1 = getScanResult(Bytes.toBytes(30), Bytes.toBytes(40), ht);
493
494    assertEquals(results1.size(), resultsSize);
495
496    ht.close();
497  }
498
499  @Test
500  public void testMultiRowRangeWithFilterListOrOperator() throws IOException {
501    tableName = TableName.valueOf(name.getMethodName());
502    Table ht = TEST_UTIL.createTable(tableName, family, Integer.MAX_VALUE);
503    generateRows(numRows, ht, family, qf, value);
504
505    Scan scan = new Scan();
506    scan.setMaxVersions();
507
508    List<RowRange> ranges1 = new ArrayList<>();
509    ranges1.add(new RowRange(Bytes.toBytes(30), true, Bytes.toBytes(40), false));
510    ranges1.add(new RowRange(Bytes.toBytes(10), true, Bytes.toBytes(20), false));
511    ranges1.add(new RowRange(Bytes.toBytes(60), true, Bytes.toBytes(70), false));
512
513    MultiRowRangeFilter filter1 = new MultiRowRangeFilter(ranges1);
514
515    List<RowRange> ranges2 = new ArrayList<>();
516    ranges2.add(new RowRange(Bytes.toBytes(20), true, Bytes.toBytes(40), false));
517    ranges2.add(new RowRange(Bytes.toBytes(80), true, Bytes.toBytes(90), false));
518
519    MultiRowRangeFilter filter2 = new MultiRowRangeFilter(ranges2);
520
521    FilterList filterList = new FilterList(FilterList.Operator.MUST_PASS_ONE);
522    filterList.addFilter(filter1);
523    filterList.addFilter(filter2);
524    scan.setFilter(filterList);
525    int resultsSize = getResultsSize(ht, scan);
526    LOG.info("found " + resultsSize + " results");
527    List<Cell> results1 = getScanResult(Bytes.toBytes(10), Bytes.toBytes(40), ht);
528    List<Cell> results2 = getScanResult(Bytes.toBytes(60), Bytes.toBytes(70), ht);
529    List<Cell> results3 = getScanResult(Bytes.toBytes(80), Bytes.toBytes(90), ht);
530
531    assertEquals(results1.size() + results2.size() + results3.size(),resultsSize);
532
533    ht.close();
534  }
535
536  @Test
537  public void testOneRowRange() throws IOException {
538    tableName = TableName.valueOf(name.getMethodName());
539    Table ht = TEST_UTIL.createTable(tableName, family, Integer.MAX_VALUE);
540    generateRows(numRows, ht, family, qf, value);
541    ArrayList<MultiRowRangeFilter.RowRange> rowRangesList = new ArrayList<>();
542    rowRangesList
543        .add(new MultiRowRangeFilter.RowRange(Bytes.toBytes(50), true, Bytes.toBytes(50), true));
544    Scan scan = new Scan();
545    scan.setFilter(new MultiRowRangeFilter(rowRangesList));
546    int resultsSize = getResultsSize(ht, scan);
547    assertEquals(1, resultsSize);
548    rowRangesList.clear();
549    rowRangesList
550        .add(new MultiRowRangeFilter.RowRange(Bytes.toBytes(50), true, Bytes.toBytes(51), false));
551    scan = new Scan();
552    scan.setFilter(new MultiRowRangeFilter(rowRangesList));
553    resultsSize = getResultsSize(ht, scan);
554    assertEquals(1, resultsSize);
555    rowRangesList.clear();
556    rowRangesList
557        .add(new MultiRowRangeFilter.RowRange(Bytes.toBytes(50), true, Bytes.toBytes(51), true));
558    scan = new Scan();
559    scan.setFilter(new MultiRowRangeFilter(rowRangesList));
560    resultsSize = getResultsSize(ht, scan);
561    assertEquals(2, resultsSize);
562    ht.close();
563  }
564
565  @Test
566  public void testReverseMultiRowRangeFilterWithinTable() throws IOException {
567    tableName = TableName.valueOf(name.getMethodName());
568    Table ht = TEST_UTIL.createTable(tableName, family);
569    generateRows(numRows, ht, family, qf, value);
570
571    Scan scan = new Scan();
572    scan.setReversed(true);
573    List<RowRange> ranges = Arrays.asList(
574        new RowRange(Bytes.toBytes(20), true, Bytes.toBytes(30), true),
575        new RowRange(Bytes.toBytes(50), true, Bytes.toBytes(60), true)
576    );
577    MultiRowRangeFilter filter = new MultiRowRangeFilter(ranges);
578    scan.setFilter(filter);
579
580    List<Integer> expectedResults = new ArrayList<>();
581    for (int i = 60; i >= 50; i--) {
582      expectedResults.add(i);
583    }
584    for (int i = 30; i >= 20; i--) {
585      expectedResults.add(i);
586    }
587
588    List<Cell> results = getResults(ht, scan);
589    List<Integer> actualResults = new ArrayList<>();
590    StringBuilder sb = new StringBuilder();
591    for (Cell result : results) {
592      int observedValue = Bytes.toInt(
593          result.getRowArray(), result.getRowOffset(), result.getRowLength());
594      actualResults.add(observedValue);
595      if (sb.length() > 0) {
596        sb.append(", ");
597      }
598      sb.append(observedValue);
599    }
600    assertEquals("Saw results: " + sb.toString(), 22, results.size());
601  }
602
603  @Test
604  public void testReverseMultiRowRangeFilterIncludingMaxRow() throws IOException {
605    tableName = TableName.valueOf(name.getMethodName());
606    Table ht = TEST_UTIL.createTable(tableName, family);
607    for (String rowkey : Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h")) {
608      byte[] row = Bytes.toBytes(rowkey);
609      Put p = new Put(row);
610      p.addColumn(family, qf, value);
611      ht.put(p);
612    }
613    TEST_UTIL.flush();
614
615    Scan scan = new Scan();
616    scan.setReversed(true);
617    List<RowRange> ranges = Arrays.asList(
618        new RowRange(Bytes.toBytes("b"), true, Bytes.toBytes("c"), true),
619        new RowRange(Bytes.toBytes("f"), true, Bytes.toBytes("h"), true)
620    );
621    MultiRowRangeFilter filter = new MultiRowRangeFilter(ranges);
622    scan.setFilter(filter);
623
624    List<String> expected = Arrays.asList("h", "g", "f", "c", "b");
625    List<String> actual = new ArrayList<>();
626    for (Cell cell : getResults(ht, scan)) {
627      actual.add(Bytes.toString(cell.getRowArray(), cell.getRowOffset(), cell.getRowLength()));
628    }
629
630    assertEquals(expected, actual);
631  }
632
633  @Test
634  public void testReverseMultiRowRangeFilterIncludingMinRow() throws IOException {
635    tableName = TableName.valueOf(name.getMethodName());
636    Table ht = TEST_UTIL.createTable(tableName, family);
637    for (String rowkey : Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h")) {
638      byte[] row = Bytes.toBytes(rowkey);
639      Put p = new Put(row);
640      p.addColumn(family, qf, value);
641      ht.put(p);
642    }
643    TEST_UTIL.flush();
644
645    Scan scan = new Scan();
646    scan.setReversed(true);
647    List<RowRange> ranges = Arrays.asList(
648        new RowRange(Bytes.toBytes("a"), true, Bytes.toBytes("c"), true),
649        new RowRange(Bytes.toBytes("f"), true, Bytes.toBytes("g"), true)
650    );
651    MultiRowRangeFilter filter = new MultiRowRangeFilter(ranges);
652    scan.setFilter(filter);
653
654    List<String> expected = Arrays.asList("g", "f", "c", "b", "a");
655    List<String> actual = new ArrayList<>();
656    for (Cell cell : getResults(ht, scan)) {
657      actual.add(Bytes.toString(cell.getRowArray(), cell.getRowOffset(), cell.getRowLength()));
658    }
659
660    assertEquals(expected, actual);
661  }
662
663  @Test
664  public void testReverseMultiRowRangeFilterIncludingMinAndMaxRow() throws IOException {
665    tableName = TableName.valueOf(name.getMethodName());
666    Table ht = TEST_UTIL.createTable(tableName, family);
667    for (String rowkey : Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h")) {
668      byte[] row = Bytes.toBytes(rowkey);
669      Put p = new Put(row);
670      p.addColumn(family, qf, value);
671      ht.put(p);
672    }
673    TEST_UTIL.flush();
674
675    Scan scan = new Scan();
676    scan.setReversed(true);
677    List<RowRange> ranges = Arrays.asList(
678        new RowRange(Bytes.toBytes("a"), true, Bytes.toBytes("c"), true),
679        new RowRange(Bytes.toBytes("f"), true, Bytes.toBytes("h"), true)
680    );
681    MultiRowRangeFilter filter = new MultiRowRangeFilter(ranges);
682    scan.setFilter(filter);
683
684    List<String> expected = Arrays.asList("h", "g", "f", "c", "b", "a");
685    List<String> actual = new ArrayList<>();
686    for (Cell cell : getResults(ht, scan)) {
687      actual.add(Bytes.toString(cell.getRowArray(), cell.getRowOffset(), cell.getRowLength()));
688    }
689
690    assertEquals(expected, actual);
691  }
692
693  private void generateRows(int numberOfRows, Table ht, byte[] family, byte[] qf, byte[] value)
694      throws IOException {
695    for (int i = 0; i < numberOfRows; i++) {
696      byte[] row = Bytes.toBytes(i);
697      Put p = new Put(row);
698      p.addColumn(family, qf, value);
699      ht.put(p);
700    }
701    TEST_UTIL.flush();
702  }
703
704  private List<Cell> getScanResult(byte[] startRow, byte[] stopRow, Table ht) throws IOException {
705    Scan scan = new Scan();
706    scan.setMaxVersions();
707    if(!Bytes.toString(startRow).isEmpty()) {
708      scan.setStartRow(startRow);
709    }
710    if(!Bytes.toString(stopRow).isEmpty()) {
711      scan.setStopRow(stopRow);
712    }
713    ResultScanner scanner = ht.getScanner(scan);
714    List<Cell> kvList = new ArrayList<>();
715    Result r;
716    while ((r = scanner.next()) != null) {
717      for (Cell kv : r.listCells()) {
718        kvList.add(kv);
719      }
720    }
721    scanner.close();
722    return kvList;
723  }
724
725  private List<Cell> getResults(Table ht, Scan scan) throws IOException {
726    ResultScanner scanner = ht.getScanner(scan);
727    List<Cell> results = new ArrayList<>();
728    Result r;
729    while ((r = scanner.next()) != null) {
730      for (Cell kv : r.listCells()) {
731        results.add(kv);
732      }
733    }
734    scanner.close();
735    return results;
736  }
737
738  private int getResultsSize(Table ht, Scan scan) throws IOException {
739    return getResults(ht, scan).size();
740  }
741}