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