View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.filter;
19  
20  import java.io.IOException;
21  import java.util.ArrayList;
22  import java.util.Collections;
23  import java.util.List;
24  import java.util.Objects;
25  
26  import org.apache.hadoop.hbase.Cell;
27  import org.apache.hadoop.hbase.HConstants;
28  import org.apache.hadoop.hbase.KeyValueUtil;
29  import org.apache.hadoop.hbase.classification.InterfaceAudience;
30  import org.apache.hadoop.hbase.classification.InterfaceStability;
31  import org.apache.hadoop.hbase.client.ClientUtil;
32  import org.apache.hadoop.hbase.exceptions.DeserializationException;
33  import org.apache.hadoop.hbase.protobuf.generated.FilterProtos;
34  import org.apache.hadoop.hbase.util.ByteStringer;
35  import org.apache.hadoop.hbase.util.Bytes;
36  
37  import com.google.protobuf.InvalidProtocolBufferException;
38  
39  /**
40   * Filter to support scan multiple row key ranges. It can construct the row key ranges from the
41   * passed list which can be accessed by each region server.
42   *
43   * HBase is quite efficient when scanning only one small row key range. If user needs to specify
44   * multiple row key ranges in one scan, the typical solutions are: 1. through FilterList which is a
45   * list of row key Filters, 2. using the SQL layer over HBase to join with two table, such as hive,
46   * phoenix etc. However, both solutions are inefficient. Both of them can't utilize the range info
47   * to perform fast forwarding during scan which is quite time consuming. If the number of ranges
48   * are quite big (e.g. millions), join is a proper solution though it is slow. However, there are
49   * cases that user wants to specify a small number of ranges to scan (e.g. <1000 ranges). Both
50   * solutions can't provide satisfactory performance in such case. MultiRowRangeFilter is to support
51   * such usec ase (scan multiple row key ranges), which can construct the row key ranges from user
52   * specified list and perform fast-forwarding during scan. Thus, the scan will be quite efficient.
53   */
54  @InterfaceAudience.Public
55  @InterfaceStability.Evolving
56  public class MultiRowRangeFilter extends FilterBase {
57  
58    private static final int ROW_BEFORE_FIRST_RANGE = -1;
59  
60    private final List<RowRange> rangeList;
61    private final RangeIteration ranges;
62  
63    private boolean done = false;
64    private int index;
65    private BasicRowRange range;
66    private ReturnCode currentReturnCode;
67  
68    /**
69     * @param list A list of <code>RowRange</code>
70     * @throws java.io.IOException
71     *           throw an exception if the range list is not in an natural order or any
72     *           <code>RowRange</code> is invalid
73     */
74    public MultiRowRangeFilter(List<RowRange> list) throws IOException {
75      // We don't use rangeList anywhere else, but keeping it lets us pay a little
76      // memory to avoid touching the serialization logic.
77      this.rangeList = Collections.unmodifiableList(sortAndMerge(list));
78      this.ranges = new RangeIteration(rangeList);
79    }
80  
81    /**
82     * Constructor for creating a <code>MultiRowRangeFilter</code> from multiple rowkey prefixes.
83     *
84     * As <code>MultiRowRangeFilter</code> javadoc says (See the solution 1 of the first statement),
85     * if you try to create a filter list that scans row keys corresponding to given prefixes (e.g.,
86     * <code>FilterList</code> composed of multiple <code>PrefixFilter</code>s), this constructor
87     * provides a way to avoid creating an inefficient one.
88     *
89     * @param rowKeyPrefixes the array of byte array
90     */
91    public MultiRowRangeFilter(byte[][] rowKeyPrefixes) throws IOException {
92      this(createRangeListFromRowKeyPrefixes(rowKeyPrefixes));
93    }
94  
95    private static List<RowRange> createRangeListFromRowKeyPrefixes(byte[][] rowKeyPrefixes) {
96      if (rowKeyPrefixes == null) {
97        throw new IllegalArgumentException("Invalid rowkey prefixes");
98      }
99  
100     List<RowRange> list = new ArrayList<>();
101     for (byte[] rowKeyPrefix: rowKeyPrefixes) {
102       byte[] stopRow = ClientUtil.calculateTheClosestNextRowKeyForPrefix(rowKeyPrefix);
103       list.add(new RowRange(rowKeyPrefix, true, stopRow, false));
104     }
105     return list;
106   }
107 
108   public List<RowRange> getRowRanges() {
109     // Used by hbase-rest
110     return this.rangeList;
111   }
112 
113   @Override
114   public boolean filterAllRemaining() {
115     return done;
116   }
117 
118   @Override
119   public boolean filterRowKey(byte[] buffer, int offset, int length) {
120     // N.b. We can only do this after we're iterating over records. If we try to do
121     // it before, the Scan (and this filter) may not yet be fully initialized. This is a
122     // wart on Filter and something that'd be nice to clean up (like CP's in HBase2.0)
123     if (!ranges.isInitialized()) {
124       ranges.initialize(isReversed());
125     }
126 
127     // If it is the first time of running, calculate the current range index for
128     // the row key. If index is out of bound which happens when the start row
129     // user sets is after the largest stop row of the ranges, stop the scan.
130     // If row key is after the current range, find the next range and update index.
131     if (!ranges.hasFoundFirstRange() || !range.contains(buffer, offset, length)) {
132       byte[] rowkey = new byte[length];
133       System.arraycopy(buffer, offset, rowkey, 0, length);
134       index = ranges.getNextRangeIndex(rowkey);
135       if (ranges.isIterationComplete(index)) {
136         done = true;
137         currentReturnCode = ReturnCode.NEXT_ROW;
138         return false;
139       }
140       if(index != ROW_BEFORE_FIRST_RANGE) {
141         range = ranges.get(index);
142       } else {
143         range = ranges.get(0);
144       }
145       if (ranges.isExclusive()) {
146         ranges.resetExclusive();
147         currentReturnCode = ReturnCode.NEXT_ROW;
148         return false;
149       }
150       if (!ranges.hasFoundFirstRange()) {
151         if(index != ROW_BEFORE_FIRST_RANGE) {
152           currentReturnCode = ReturnCode.INCLUDE;
153         } else {
154           currentReturnCode = ReturnCode.SEEK_NEXT_USING_HINT;
155         }
156         ranges.setFoundFirstRange();
157       } else {
158         if (range.contains(buffer, offset, length)) {
159           currentReturnCode = ReturnCode.INCLUDE;
160         } else {
161           currentReturnCode = ReturnCode.SEEK_NEXT_USING_HINT;
162         }
163       }
164     } else {
165       currentReturnCode = ReturnCode.INCLUDE;
166     }
167     return false;
168   }
169 
170   @Override
171   public ReturnCode filterKeyValue(Cell ignored) {
172     return currentReturnCode;
173   }
174 
175   @Override
176   public Cell getNextCellHint(Cell currentKV) {
177     // skip to the next range's start row
178     // #getComparisonData lets us avoid the `if (reversed)` branch
179     byte[] comparisonData = range.getComparisonData();
180     return KeyValueUtil.createFirstOnRow(comparisonData);
181   }
182 
183   /**
184    * @return The filter serialized using pb
185    */
186   @Override
187   public byte[] toByteArray() {
188     FilterProtos.MultiRowRangeFilter.Builder builder = FilterProtos.MultiRowRangeFilter
189         .newBuilder();
190     for (RowRange range : rangeList) {
191       if (range != null) {
192         FilterProtos.RowRange.Builder rangebuilder = FilterProtos.RowRange.newBuilder();
193         if (range.startRow != null)
194           rangebuilder.setStartRow(ByteStringer.wrap(range.startRow));
195         rangebuilder.setStartRowInclusive(range.startRowInclusive);
196         if (range.stopRow != null)
197           rangebuilder.setStopRow(ByteStringer.wrap(range.stopRow));
198         rangebuilder.setStopRowInclusive(range.stopRowInclusive);
199         builder.addRowRangeList(rangebuilder.build());
200       }
201     }
202     return builder.build().toByteArray();
203   }
204 
205   /**
206    * @param pbBytes A pb serialized instance
207    * @return An instance of MultiRowRangeFilter
208    * @throws org.apache.hadoop.hbase.exceptions.DeserializationException
209    */
210   public static MultiRowRangeFilter parseFrom(final byte[] pbBytes)
211       throws DeserializationException {
212     FilterProtos.MultiRowRangeFilter proto;
213     try {
214       proto = FilterProtos.MultiRowRangeFilter.parseFrom(pbBytes);
215     } catch (InvalidProtocolBufferException e) {
216       throw new DeserializationException(e);
217     }
218     int length = proto.getRowRangeListCount();
219     List<FilterProtos.RowRange> rangeProtos = proto.getRowRangeListList();
220     List<RowRange> rangeList = new ArrayList<RowRange>(length);
221     for (FilterProtos.RowRange rangeProto : rangeProtos) {
222       RowRange range = new RowRange(rangeProto.hasStartRow() ? rangeProto.getStartRow()
223           .toByteArray() : null, rangeProto.getStartRowInclusive(), rangeProto.hasStopRow() ?
224               rangeProto.getStopRow().toByteArray() : null, rangeProto.getStopRowInclusive());
225       rangeList.add(range);
226     }
227     try {
228       return new MultiRowRangeFilter(rangeList);
229     } catch (IOException e) {
230       throw new DeserializationException("Fail to instantiate the MultiRowRangeFilter", e);
231     }
232   }
233 
234   /**
235    * @param o the filter to compare
236    * @return true if and only if the fields of the filter that are serialized are equal to the
237    *         corresponding fields in other. Used for testing.
238    */
239   @Override
240   boolean areSerializedFieldsEqual(Filter o) {
241     if (o == this)
242       return true;
243     if (!(o instanceof MultiRowRangeFilter))
244       return false;
245 
246     MultiRowRangeFilter other = (MultiRowRangeFilter) o;
247     if (this.rangeList.size() != other.rangeList.size())
248       return false;
249     for (int i = 0; i < rangeList.size(); ++i) {
250       RowRange thisRange = this.rangeList.get(i);
251       RowRange otherRange = other.rangeList.get(i);
252       if (!(Bytes.equals(thisRange.startRow, otherRange.startRow) && Bytes.equals(
253           thisRange.stopRow, otherRange.stopRow) && (thisRange.startRowInclusive ==
254           otherRange.startRowInclusive) && (thisRange.stopRowInclusive ==
255           otherRange.stopRowInclusive))) {
256         return false;
257       }
258     }
259     return true;
260   }
261 
262   /**
263    * sort the ranges and if the ranges with overlap, then merge them.
264    *
265    * @param ranges the list of ranges to sort and merge.
266    * @return the ranges after sort and merge.
267    */
268   public static List<RowRange> sortAndMerge(List<RowRange> ranges) {
269     if (ranges.size() == 0) {
270       throw new IllegalArgumentException("No ranges found.");
271     }
272     List<RowRange> invalidRanges = new ArrayList<RowRange>();
273     List<RowRange> newRanges = new ArrayList<RowRange>(ranges.size());
274     Collections.sort(ranges);
275     if(ranges.get(0).isValid()) {
276       if (ranges.size() == 1) {
277         newRanges.add(ranges.get(0));
278       }
279     } else {
280       invalidRanges.add(ranges.get(0));
281     }
282 
283     byte[] lastStartRow = ranges.get(0).startRow;
284     boolean lastStartRowInclusive = ranges.get(0).startRowInclusive;
285     byte[] lastStopRow = ranges.get(0).stopRow;
286     boolean lastStopRowInclusive = ranges.get(0).stopRowInclusive;
287     int i = 1;
288     for (; i < ranges.size(); i++) {
289       RowRange range = ranges.get(i);
290       if (!range.isValid()) {
291         invalidRanges.add(range);
292       }
293       if(Bytes.equals(lastStopRow, HConstants.EMPTY_BYTE_ARRAY)) {
294         newRanges.add(new RowRange(lastStartRow, lastStartRowInclusive, lastStopRow,
295             lastStopRowInclusive));
296         break;
297       }
298       // with overlap in the ranges
299       if ((Bytes.compareTo(lastStopRow, range.startRow) > 0) ||
300           (Bytes.compareTo(lastStopRow, range.startRow) == 0 && !(lastStopRowInclusive == false &&
301           range.isStartRowInclusive() == false))) {
302         if(Bytes.equals(range.stopRow, HConstants.EMPTY_BYTE_ARRAY)) {
303           newRanges.add(new RowRange(lastStartRow, lastStartRowInclusive, range.stopRow,
304               range.stopRowInclusive));
305           break;
306         }
307         // if first range contains second range, ignore the second range
308         if (Bytes.compareTo(lastStopRow, range.stopRow) >= 0) {
309           if((Bytes.compareTo(lastStopRow, range.stopRow) == 0)) {
310             if(lastStopRowInclusive == true || range.stopRowInclusive == true) {
311               lastStopRowInclusive = true;
312             }
313           }
314           if ((i + 1) == ranges.size()) {
315             newRanges.add(new RowRange(lastStartRow, lastStartRowInclusive, lastStopRow,
316                 lastStopRowInclusive));
317           }
318         } else {
319           lastStopRow = range.stopRow;
320           lastStopRowInclusive = range.stopRowInclusive;
321           if ((i + 1) < ranges.size()) {
322             i++;
323             range = ranges.get(i);
324             if (!range.isValid()) {
325               invalidRanges.add(range);
326             }
327           } else {
328             newRanges.add(new RowRange(lastStartRow, lastStartRowInclusive, lastStopRow,
329                 lastStopRowInclusive));
330             break;
331           }
332           while ((Bytes.compareTo(lastStopRow, range.startRow) > 0) ||
333               (Bytes.compareTo(lastStopRow, range.startRow) == 0 &&
334               (lastStopRowInclusive == true || range.startRowInclusive==true))) {
335             if(Bytes.equals(range.stopRow, HConstants.EMPTY_BYTE_ARRAY)) {
336               break;
337             }
338             // if this first range contain second range, ignore the second range
339             if (Bytes.compareTo(lastStopRow, range.stopRow) >= 0) {
340               if(lastStopRowInclusive == true || range.stopRowInclusive == true) {
341                 lastStopRowInclusive = true;
342               }
343               i++;
344               if (i < ranges.size()) {
345                 range = ranges.get(i);
346                 if (!range.isValid()) {
347                   invalidRanges.add(range);
348                 }
349               } else {
350                 break;
351               }
352             } else {
353               lastStopRow = range.stopRow;
354               lastStopRowInclusive = range.stopRowInclusive;
355               i++;
356               if (i < ranges.size()) {
357                 range = ranges.get(i);
358                 if (!range.isValid()) {
359                   invalidRanges.add(range);
360                 }
361               } else {
362                 break;
363               }
364             }
365           }
366           if(Bytes.equals(range.stopRow, HConstants.EMPTY_BYTE_ARRAY)) {
367             if((Bytes.compareTo(lastStopRow, range.startRow) < 0) ||
368                 (Bytes.compareTo(lastStopRow, range.startRow) == 0 &&
369                 lastStopRowInclusive == false && range.startRowInclusive == false)) {
370               newRanges.add(new RowRange(lastStartRow, lastStartRowInclusive, lastStopRow,
371                   lastStopRowInclusive));
372               newRanges.add(range);
373             } else {
374               newRanges.add(new RowRange(lastStartRow, lastStartRowInclusive, range.stopRow,
375                   range.stopRowInclusive));
376               break;
377             }
378           }
379           newRanges.add(new RowRange(lastStartRow, lastStartRowInclusive, lastStopRow,
380               lastStopRowInclusive));
381           if ((i + 1) == ranges.size()) {
382             newRanges.add(range);
383           }
384           lastStartRow = range.startRow;
385           lastStartRowInclusive = range.startRowInclusive;
386           lastStopRow = range.stopRow;
387           lastStopRowInclusive = range.stopRowInclusive;
388         }
389       } else {
390         newRanges.add(new RowRange(lastStartRow, lastStartRowInclusive, lastStopRow,
391             lastStopRowInclusive));
392         if ((i + 1) == ranges.size()) {
393           newRanges.add(range);
394         }
395         lastStartRow = range.startRow;
396         lastStartRowInclusive = range.startRowInclusive;
397         lastStopRow = range.stopRow;
398         lastStopRowInclusive = range.stopRowInclusive;
399       }
400     }
401     // check the remaining ranges
402     for(int j=i; j < ranges.size(); j++) {
403       if(!ranges.get(j).isValid()) {
404         invalidRanges.add(ranges.get(j));
405       }
406     }
407     // if invalid range exists, throw the exception
408     if (invalidRanges.size() != 0) {
409       throwExceptionForInvalidRanges(invalidRanges, true);
410     }
411     // If no valid ranges found, throw the exception
412     if(newRanges.size() == 0) {
413       throw new IllegalArgumentException("No valid ranges found.");
414     }
415     return newRanges;
416   }
417 
418   private static void throwExceptionForInvalidRanges(List<RowRange> invalidRanges,
419       boolean details) {
420     StringBuilder sb = new StringBuilder();
421     sb.append(invalidRanges.size()).append(" invaild ranges.\n");
422     if (details) {
423       for (RowRange range : invalidRanges) {
424         sb.append(
425             "Invalid range: start row => " + Bytes.toString(range.startRow) + ", stop row => "
426                 + Bytes.toString(range.stopRow)).append('\n');
427       }
428     }
429     throw new IllegalArgumentException(sb.toString());
430   }
431 
432   private static abstract class BasicRowRange {
433     protected byte[] startRow;
434     protected boolean startRowInclusive = true;
435     protected byte[] stopRow;
436     protected boolean stopRowInclusive = false;
437 
438     public BasicRowRange() {
439     }
440     /**
441      * If the startRow is empty or null, set it to HConstants.EMPTY_BYTE_ARRAY, means begin at the
442      * start row of the table. If the stopRow is empty or null, set it to
443      * HConstants.EMPTY_BYTE_ARRAY, means end of the last row of table.
444      */
445     public BasicRowRange(String startRow, boolean startRowInclusive, String stopRow,
446         boolean stopRowInclusive) {
447       this((startRow == null || startRow.isEmpty()) ? HConstants.EMPTY_BYTE_ARRAY :
448         Bytes.toBytes(startRow), startRowInclusive,
449         (stopRow == null || stopRow.isEmpty()) ? HConstants.EMPTY_BYTE_ARRAY :
450         Bytes.toBytes(stopRow), stopRowInclusive);
451     }
452 
453     public BasicRowRange(byte[] startRow,  boolean startRowInclusive, byte[] stopRow,
454         boolean stopRowInclusive) {
455       this.startRow = (startRow == null) ? HConstants.EMPTY_BYTE_ARRAY : startRow;
456       this.startRowInclusive = startRowInclusive;
457       this.stopRow = (stopRow == null) ? HConstants.EMPTY_BYTE_ARRAY :stopRow;
458       this.stopRowInclusive = stopRowInclusive;
459     }
460 
461     public byte[] getStartRow() {
462       return startRow;
463     }
464 
465     public byte[] getStopRow() {
466       return stopRow;
467     }
468 
469     /**
470      * @return if start row is inclusive.
471      */
472     public boolean isStartRowInclusive() {
473       return startRowInclusive;
474     }
475 
476     /**
477      * @return if stop row is inclusive.
478      */
479     public boolean isStopRowInclusive() {
480       return stopRowInclusive;
481     }
482 
483     public boolean contains(byte[] row) {
484       return contains(row, 0, row.length);
485     }
486 
487     public boolean contains(byte[] buffer, int offset, int length) {
488       if(startRowInclusive) {
489         if(stopRowInclusive) {
490           return Bytes.compareTo(buffer, offset, length, startRow, 0, startRow.length) >= 0
491               && (Bytes.equals(stopRow, HConstants.EMPTY_BYTE_ARRAY) ||
492                   Bytes.compareTo(buffer, offset, length, stopRow, 0, stopRow.length) <= 0);
493         } else {
494           return Bytes.compareTo(buffer, offset, length, startRow, 0, startRow.length) >= 0
495               && (Bytes.equals(stopRow, HConstants.EMPTY_BYTE_ARRAY) ||
496                   Bytes.compareTo(buffer, offset, length, stopRow, 0, stopRow.length) < 0);
497         }
498       } else {
499         if(stopRowInclusive) {
500           return Bytes.compareTo(buffer, offset, length, startRow, 0, startRow.length) > 0
501               && (Bytes.equals(stopRow, HConstants.EMPTY_BYTE_ARRAY) ||
502                   Bytes.compareTo(buffer, offset, length, stopRow, 0, stopRow.length) <= 0);
503         } else {
504           return Bytes.compareTo(buffer, offset, length, startRow, 0, startRow.length) > 0
505               && (Bytes.equals(stopRow, HConstants.EMPTY_BYTE_ARRAY) ||
506                   Bytes.compareTo(buffer, offset, length, stopRow, 0, stopRow.length) < 0);
507         }
508       }
509     }
510 
511     public boolean isValid() {
512       return Bytes.equals(startRow, HConstants.EMPTY_BYTE_ARRAY)
513           || Bytes.equals(stopRow, HConstants.EMPTY_BYTE_ARRAY)
514           || Bytes.compareTo(startRow, stopRow) < 0
515           || (Bytes.compareTo(startRow, stopRow) == 0 && stopRowInclusive == true);
516     }
517 
518     @Override
519     public boolean equals(Object obj){
520       if (!(obj instanceof BasicRowRange)) {
521         return false;
522       }
523       if (this == obj) {
524         return true;
525       }
526       BasicRowRange rr = (BasicRowRange) obj;
527       return Bytes.equals(this.stopRow, rr.getStopRow()) &&
528           Bytes.equals(this.startRow, this.getStartRow()) &&
529           this.startRowInclusive == rr.isStartRowInclusive() &&
530           this.stopRowInclusive == rr.isStopRowInclusive();
531     }
532 
533     @Override
534     public int hashCode() {
535       return Objects.hash(Bytes.hashCode(this.stopRow),
536           Bytes.hashCode(this.startRow),
537           this.startRowInclusive,
538           this.stopRowInclusive);
539     }
540 
541     /**
542      * Returns the data to be used to compare {@code this} to another object.
543      */
544     public abstract byte[] getComparisonData();
545 
546     /**
547      * Returns whether the bounding row used for binary-search is inclusive or not.
548      *
549      * For forward scans, we would check the starRow, but we would check the stopRow for
550      * the reverse scan case.
551      */
552     public abstract boolean isSearchRowInclusive();
553 
554     public abstract boolean isAscendingOrder();
555   }
556 
557   /**
558    * Internal RowRange that reverses the sort-order to handle reverse scans.
559    */
560   @InterfaceAudience.Private
561   private static class ReversedRowRange extends BasicRowRange implements Comparable<ReversedRowRange> {
562     public ReversedRowRange(byte[] startRow,  boolean startRowInclusive, byte[] stopRow,
563         boolean stopRowInclusive) {
564       super(startRow, startRowInclusive, stopRow, stopRowInclusive);
565     }
566 
567     @Override
568     public byte[] getComparisonData() {
569       return this.stopRow;
570     }
571 
572     @Override
573     public boolean isSearchRowInclusive() {
574       return this.stopRowInclusive;
575     }
576 
577     @Override
578     public boolean isAscendingOrder() {
579       return false;
580     }
581 
582     @Override
583     public int compareTo(ReversedRowRange other) {
584       byte[] left = other.getComparisonData();
585       byte[] right = this.getComparisonData();
586       return Bytes.compareTo(left, right);
587     }
588   }
589 
590   @InterfaceAudience.Public
591   @InterfaceStability.Evolving
592   public static class RowRange extends BasicRowRange implements Comparable<RowRange> {
593     public RowRange() {
594     }
595     /**
596      * If the startRow is empty or null, set it to HConstants.EMPTY_BYTE_ARRAY, means begin at the
597      * start row of the table. If the stopRow is empty or null, set it to
598      * HConstants.EMPTY_BYTE_ARRAY, means end of the last row of table.
599      */
600     public RowRange(String startRow, boolean startRowInclusive, String stopRow,
601         boolean stopRowInclusive) {
602       super(startRow, startRowInclusive, stopRow, stopRowInclusive);
603     }
604 
605     public RowRange(byte[] startRow,  boolean startRowInclusive, byte[] stopRow,
606         boolean stopRowInclusive) {
607       super(startRow, startRowInclusive, stopRow, stopRowInclusive);
608     }
609 
610     @Override
611     public byte[] getComparisonData() {
612       return startRow;
613     }
614 
615     @Override
616     public boolean isSearchRowInclusive() {
617       return startRowInclusive;
618     }
619 
620     @Override
621     public boolean isAscendingOrder() {
622       return true;
623     }
624 
625     @Override
626     public int compareTo(RowRange other) {
627       byte[] left = this.getComparisonData();
628       byte[] right = other.getComparisonData();
629       return Bytes.compareTo(left, right);
630     }
631   }
632 
633   /**
634    * Abstraction over the ranges of rows to return from this filter, regardless of forward or
635    * reverse scans being used. This Filter can use this class, agnostic of iteration direction,
636    * as the same algorithm can be applied in both cases.
637    */
638   @InterfaceAudience.Private
639   private static class RangeIteration {
640     private boolean exclusive = false;
641     private boolean initialized = false;
642     private boolean foundFirstRange = false;
643     private boolean reversed = false;
644     private final List<RowRange> sortedAndMergedRanges;
645     private List<? extends BasicRowRange> ranges;
646 
647     public RangeIteration(List<RowRange> sortedAndMergedRanges) {
648       this.sortedAndMergedRanges = sortedAndMergedRanges;
649     }
650 
651     void initialize(boolean reversed) {
652       // Avoid double initialization
653       assert !this.initialized;
654       this.reversed = reversed;
655       if (reversed) {
656         // If we are doing a reverse scan, we can reverse the ranges (both the elements in
657         // the list as well as their start/stop key), and use the same algorithm.
658         this.ranges = flipAndReverseRanges(sortedAndMergedRanges);
659       } else {
660         this.ranges = sortedAndMergedRanges;
661       }
662       this.initialized = true;
663     }
664 
665     /**
666      * Rebuilds the sorted ranges (by startKey) into an equivalent sorted list of ranges, only by
667      * stopKey instead. Descending order and the ReversedRowRange compareTo implementation make
668      * sure that we can use Collections.binarySearch().
669      */
670     static List<ReversedRowRange> flipAndReverseRanges(List<RowRange> ranges) {
671       List<ReversedRowRange> flippedRanges = new ArrayList<>(ranges.size());
672       for (int i = ranges.size() - 1; i >= 0; i--) {
673         RowRange origRange = ranges.get(i);
674         ReversedRowRange newRowRange = new ReversedRowRange(
675             origRange.startRow, origRange.startRowInclusive, origRange.stopRow,
676             origRange.isStopRowInclusive());
677         flippedRanges.add(newRowRange);
678       }
679       return flippedRanges;
680     }
681 
682     /**
683      * Calculates the position where the given rowkey fits in the ranges list.
684      *
685      * @param rowKey the row key to calculate
686      * @return index the position of the row key
687      */
688     @SuppressWarnings("unchecked")
689     public int getNextRangeIndex(byte[] rowKey) {
690       // Because we make sure that `ranges` has the correct natural ordering (given it containing
691       // RowRange or ReverseRowRange objects). This keeps us from having to have two different
692       // implementations below.
693       final int index;
694       if (reversed) {
695         ReversedRowRange searchFor = new ReversedRowRange(null, true, rowKey, true);
696         index = Collections.binarySearch((List<ReversedRowRange>) ranges, searchFor);
697       } else {
698         RowRange searchFor = new RowRange(rowKey, true, null, true);
699         index = Collections.binarySearch((List<RowRange>) ranges, searchFor);
700       }
701       if (index < 0) {
702         int insertionPosition = -index - 1;
703         // check if the row key in the range before the insertion position
704         if (insertionPosition != 0 && ranges.get(insertionPosition - 1).contains(rowKey)) {
705           return insertionPosition - 1;
706         }
707         // check if the row key is before the first range
708         if (insertionPosition == 0 && !ranges.get(insertionPosition).contains(rowKey)) {
709           return ROW_BEFORE_FIRST_RANGE;
710         }
711         if (!foundFirstRange) {
712           foundFirstRange = true;
713         }
714         return insertionPosition;
715       }
716       // the row key equals one of the start keys, and the the range exclude the start key
717       if(ranges.get(index).isSearchRowInclusive() == false) {
718         exclusive = true;
719       }
720       return index;
721     }
722 
723     /**
724      * Sets {@link #foundFirstRange} to {@code true}, indicating that we found a matching row range.
725      */
726     public void setFoundFirstRange() {
727       this.foundFirstRange = true;
728     }
729 
730     /**
731      * Gets the RowRange at the given offset.
732      */
733     @SuppressWarnings("unchecked")
734     public <T extends BasicRowRange> T get(int i) {
735       return (T) ranges.get(i);
736     }
737 
738     /**
739      * Returns true if the first matching row range was found.
740      */
741     public boolean hasFoundFirstRange() {
742       return foundFirstRange;
743     }
744 
745     /**
746      * Returns true if the current range's key is exclusive
747      */
748     public boolean isExclusive() {
749       return exclusive;
750     }
751 
752     /**
753      * Resets the exclusive flag.
754      */
755     public void resetExclusive() {
756       exclusive = false;
757     }
758 
759     /**
760      * Returns true if this class has been initialized by calling {@link #initialize(boolean)}.
761      */
762     public boolean isInitialized() {
763       return initialized;
764     }
765 
766     /**
767      * Returns true if we exhausted searching all row ranges.
768      */
769     public boolean isIterationComplete(int index) {
770       return index >= ranges.size();
771     }
772   }
773 
774   @Override
775   public boolean equals(Object obj) {
776     return obj instanceof Filter && areSerializedFieldsEqual((Filter) obj);
777   }
778 
779   @Override
780   public int hashCode() {
781     return Objects.hash(this.ranges);
782   }
783 }