View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  
20  package org.apache.hadoop.hbase.regionserver;
21  
22  import java.io.IOException;
23  import java.util.Arrays;
24  import java.util.NavigableSet;
25  
26  import org.apache.hadoop.hbase.KeyValue.Type;
27  import org.apache.hadoop.hbase.classification.InterfaceAudience;
28  import org.apache.hadoop.hbase.Cell;
29  import org.apache.hadoop.hbase.CellComparator;
30  import org.apache.hadoop.hbase.CellUtil;
31  import org.apache.hadoop.hbase.HConstants;
32  import org.apache.hadoop.hbase.KeepDeletedCells;
33  import org.apache.hadoop.hbase.KeyValue;
34  import org.apache.hadoop.hbase.KeyValueUtil;
35  import org.apache.hadoop.hbase.client.Scan;
36  import org.apache.hadoop.hbase.filter.Filter;
37  import org.apache.hadoop.hbase.filter.Filter.ReturnCode;
38  import org.apache.hadoop.hbase.io.TimeRange;
39  import org.apache.hadoop.hbase.regionserver.DeleteTracker.DeleteResult;
40  import org.apache.hadoop.hbase.util.Bytes;
41  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
42  
43  import com.google.common.base.Preconditions;
44  
45  /**
46   * A query matcher that is specifically designed for the scan case.
47   */
48  @InterfaceAudience.Private
49  public class ScanQueryMatcher {
50    // Optimization so we can skip lots of compares when we decide to skip
51    // to the next row.
52    private boolean stickyNextRow;
53    private final byte[] stopRow;
54  
55    private final TimeRange tr;
56  
57    private final Filter filter;
58  
59    /** Keeps track of deletes */
60    private final DeleteTracker deletes;
61  
62    /*
63     * The following three booleans define how we deal with deletes.
64     * There are three different aspects:
65     * 1. Whether to keep delete markers. This is used in compactions.
66     *    Minor compactions always keep delete markers.
67     * 2. Whether to keep deleted rows. This is also used in compactions,
68     *    if the store is set to keep deleted rows. This implies keeping
69     *    the delete markers as well.
70     *    In this case deleted rows are subject to the normal max version
71     *    and TTL/min version rules just like "normal" rows.
72     * 3. Whether a scan can do time travel queries even before deleted
73     *    marker to reach deleted rows.
74     */
75    /** whether to retain delete markers */
76    private boolean retainDeletesInOutput;
77  
78    /** whether to return deleted rows */
79    private final KeepDeletedCells keepDeletedCells;
80    /** whether time range queries can see rows "behind" a delete */
81    private final boolean seePastDeleteMarkers;
82  
83  
84    /** Keeps track of columns and versions */
85    private final ColumnTracker columns;
86  
87    /** Key to seek to in memstore and StoreFiles */
88    private final Cell startKey;
89  
90    /** Row comparator for the region this query is for */
91    private final CellComparator rowComparator;
92  
93    /* row is not private for tests */
94    /** Row the query is on */
95    Cell curCell;
96    
97    /**
98     * Oldest put in any of the involved store files
99     * Used to decide whether it is ok to delete
100    * family delete marker of this store keeps
101    * deleted KVs.
102    */
103   private final long earliestPutTs;
104   private final long ttl;
105 
106   /** The oldest timestamp we are interested in, based on TTL */
107   private final long oldestUnexpiredTS;
108   private final long now;
109 
110   /** readPoint over which the KVs are unconditionally included */
111   protected long maxReadPointToTrackVersions;
112 
113   private byte[] dropDeletesFromRow = null, dropDeletesToRow = null;
114 
115   /**
116    * This variable shows whether there is an null column in the query. There
117    * always exists a null column in the wildcard column query.
118    * There maybe exists a null column in the explicit column query based on the
119    * first column.
120    * */
121   private boolean hasNullColumn = true;
122   
123   private RegionCoprocessorHost regionCoprocessorHost= null;
124 
125   // By default, when hbase.hstore.time.to.purge.deletes is 0ms, a delete
126   // marker is always removed during a major compaction. If set to non-zero
127   // value then major compaction will try to keep a delete marker around for
128   // the given number of milliseconds. We want to keep the delete markers
129   // around a bit longer because old puts might appear out-of-order. For
130   // example, during log replication between two clusters.
131   //
132   // If the delete marker has lived longer than its column-family's TTL then
133   // the delete marker will be removed even if time.to.purge.deletes has not
134   // passed. This is because all the Puts that this delete marker can influence
135   // would have also expired. (Removing of delete markers on col family TTL will
136   // not happen if min-versions is set to non-zero)
137   //
138   // But, if time.to.purge.deletes has not expired then a delete
139   // marker will not be removed just because there are no Puts that it is
140   // currently influencing. This is because Puts, that this delete can
141   // influence.  may appear out of order.
142   private final long timeToPurgeDeletes;
143   
144   private final boolean isUserScan;
145 
146   private final boolean isReversed;
147 
148   /**
149    * Construct a QueryMatcher for a scan
150    * @param scan
151    * @param scanInfo The store's immutable scan info
152    * @param columns
153    * @param scanType Type of the scan
154    * @param earliestPutTs Earliest put seen in any of the store files.
155    * @param oldestUnexpiredTS the oldest timestamp we are interested in,
156    *  based on TTL
157    * @param regionCoprocessorHost 
158    * @throws IOException 
159    */
160   public ScanQueryMatcher(Scan scan, ScanInfo scanInfo, NavigableSet<byte[]> columns,
161       ScanType scanType, long readPointToUse, long earliestPutTs, long oldestUnexpiredTS,
162       long now, RegionCoprocessorHost regionCoprocessorHost) throws IOException {
163     TimeRange timeRange = scan.getColumnFamilyTimeRange().get(scanInfo.getFamily());
164     if (timeRange == null) {
165       this.tr = scan.getTimeRange();
166     } else {
167       this.tr = timeRange;
168     }
169     this.rowComparator = scanInfo.getComparator();
170     this.regionCoprocessorHost = regionCoprocessorHost;
171     this.deletes =  instantiateDeleteTracker();
172     this.stopRow = scan.getStopRow();
173     this.startKey = CellUtil.createFirstDeleteFamilyCellOnRow(scan.getStartRow(),
174         scanInfo.getFamily());
175     this.filter = scan.getFilter();
176     this.earliestPutTs = earliestPutTs;
177     this.oldestUnexpiredTS = oldestUnexpiredTS;
178     this.now = now;
179 
180     this.maxReadPointToTrackVersions = readPointToUse;
181     this.timeToPurgeDeletes = scanInfo.getTimeToPurgeDeletes();
182     this.ttl = oldestUnexpiredTS;
183 
184     /* how to deal with deletes */
185     this.isUserScan = scanType == ScanType.USER_SCAN;
186     // keep deleted cells: if compaction or raw scan
187     this.keepDeletedCells = scan.isRaw() ? KeepDeletedCells.TRUE :
188       isUserScan ? KeepDeletedCells.FALSE : scanInfo.getKeepDeletedCells();
189     // retain deletes: if minor compaction or raw scanisDone
190     this.retainDeletesInOutput = scanType == ScanType.COMPACT_RETAIN_DELETES || scan.isRaw();
191     // seePastDeleteMarker: user initiated scans
192     this.seePastDeleteMarkers =
193         scanInfo.getKeepDeletedCells() != KeepDeletedCells.FALSE && isUserScan;
194 
195     int maxVersions =
196         scan.isRaw() ? scan.getMaxVersions() : Math.min(scan.getMaxVersions(),
197           scanInfo.getMaxVersions());
198 
199     // Single branch to deal with two types of reads (columns vs all in family)
200     if (columns == null || columns.size() == 0) {
201       // there is always a null column in the wildcard column query.
202       hasNullColumn = true;
203 
204       // use a specialized scan for wildcard column tracker.
205       this.columns = new ScanWildcardColumnTracker(
206           scanInfo.getMinVersions(), maxVersions, oldestUnexpiredTS);
207     } else {
208       // whether there is null column in the explicit column query
209       hasNullColumn = (columns.first().length == 0);
210 
211       // We can share the ExplicitColumnTracker, diff is we reset
212       // between rows, not between storefiles.
213       this.columns = new ExplicitColumnTracker(columns, scanInfo.getMinVersions(), maxVersions,
214           oldestUnexpiredTS);
215     }
216     this.isReversed = scan.isReversed();
217   }
218 
219   private DeleteTracker instantiateDeleteTracker() throws IOException {
220     DeleteTracker tracker = new ScanDeleteTracker();
221     if (regionCoprocessorHost != null) {
222       tracker = regionCoprocessorHost.postInstantiateDeleteTracker(tracker);
223     }
224     return tracker;
225   }
226 
227   /**
228    * Construct a QueryMatcher for a scan that drop deletes from a limited range of rows.
229    * @param scan
230    * @param scanInfo The store's immutable scan info
231    * @param columns
232    * @param earliestPutTs Earliest put seen in any of the store files.
233    * @param oldestUnexpiredTS the oldest timestamp we are interested in, based on TTL
234    * @param now the current server time
235    * @param dropDeletesFromRow The inclusive left bound of the range; can be EMPTY_START_ROW.
236    * @param dropDeletesToRow The exclusive right bound of the range; can be EMPTY_END_ROW.
237    * @param regionCoprocessorHost 
238    * @throws IOException 
239    */
240   public ScanQueryMatcher(Scan scan, ScanInfo scanInfo, NavigableSet<byte[]> columns,
241       long readPointToUse, long earliestPutTs, long oldestUnexpiredTS, long now,
242       byte[] dropDeletesFromRow, byte[] dropDeletesToRow,
243       RegionCoprocessorHost regionCoprocessorHost) throws IOException {
244     this(scan, scanInfo, columns, ScanType.COMPACT_RETAIN_DELETES, readPointToUse, earliestPutTs,
245         oldestUnexpiredTS, now, regionCoprocessorHost);
246     Preconditions.checkArgument((dropDeletesFromRow != null) && (dropDeletesToRow != null));
247     this.dropDeletesFromRow = dropDeletesFromRow;
248     this.dropDeletesToRow = dropDeletesToRow;
249   }
250 
251   /*
252    * Constructor for tests
253    */
254   ScanQueryMatcher(Scan scan, ScanInfo scanInfo,
255       NavigableSet<byte[]> columns, long oldestUnexpiredTS, long now) throws IOException {
256     this(scan, scanInfo, columns, ScanType.USER_SCAN,
257           Long.MAX_VALUE, /* max Readpoint to track versions */
258         HConstants.LATEST_TIMESTAMP, oldestUnexpiredTS, now, null);
259   }
260 
261   /**
262    *
263    * @return  whether there is an null column in the query
264    */
265   public boolean hasNullColumnInQuery() {
266     return hasNullColumn;
267   }
268 
269   /**
270    * Determines if the caller should do one of several things:
271    * - seek/skip to the next row (MatchCode.SEEK_NEXT_ROW)
272    * - seek/skip to the next column (MatchCode.SEEK_NEXT_COL)
273    * - include the current KeyValue (MatchCode.INCLUDE)
274    * - ignore the current KeyValue (MatchCode.SKIP)
275    * - got to the next row (MatchCode.DONE)
276    *
277    * @param cell KeyValue to check
278    * @return The match code instance.
279    * @throws IOException in case there is an internal consistency problem
280    *      caused by a data corruption.
281    */
282   public MatchCode match(Cell cell) throws IOException {
283       if (filter != null && filter.filterAllRemaining()) {
284       return MatchCode.DONE_SCAN;
285     }
286     if (curCell != null) {
287       int ret = this.rowComparator.compareRows(curCell, cell);
288       if (!this.isReversed) {
289         if (ret <= -1) {
290           return MatchCode.DONE;
291         } else if (ret >= 1) {
292           // could optimize this, if necessary?
293           // Could also be called SEEK_TO_CURRENT_ROW, but this
294           // should be rare/never happens.
295           return MatchCode.SEEK_NEXT_ROW;
296         }
297       } else {
298         if (ret <= -1) {
299           return MatchCode.SEEK_NEXT_ROW;
300         } else if (ret >= 1) {
301           return MatchCode.DONE;
302         }
303       }
304     } else {
305       // Since the curCell is null it means we are already sure that we have moved over to the next row
306       return MatchCode.DONE;
307     }
308 
309     // optimize case.
310     if (this.stickyNextRow) {
311       return MatchCode.SEEK_NEXT_ROW;
312     }
313 
314     if (this.columns.done()) {
315       stickyNextRow = true;
316       return MatchCode.SEEK_NEXT_ROW;
317     }
318 
319     long timestamp = cell.getTimestamp();
320     // check for early out based on timestamp alone
321     if (columns.isDone(timestamp)) {
322       return columns.getNextRowOrNextColumn(cell);
323     }
324     // check if the cell is expired by cell TTL
325     if (HStore.isCellTTLExpired(cell, this.oldestUnexpiredTS, this.now)) {
326       return MatchCode.SKIP;
327     }    
328 
329     /*
330      * The delete logic is pretty complicated now.
331      * This is corroborated by the following:
332      * 1. The store might be instructed to keep deleted rows around.
333      * 2. A scan can optionally see past a delete marker now.
334      * 3. If deleted rows are kept, we have to find out when we can
335      *    remove the delete markers.
336      * 4. Family delete markers are always first (regardless of their TS)
337      * 5. Delete markers should not be counted as version
338      * 6. Delete markers affect puts of the *same* TS
339      * 7. Delete marker need to be version counted together with puts
340      *    they affect
341      */
342     byte typeByte = cell.getTypeByte();
343     long mvccVersion = cell.getSequenceId();
344     if (CellUtil.isDelete(cell)) {
345       if (keepDeletedCells == KeepDeletedCells.FALSE
346           || (keepDeletedCells == KeepDeletedCells.TTL && timestamp < ttl)) {
347         // first ignore delete markers if the scanner can do so, and the
348         // range does not include the marker
349         //
350         // during flushes and compactions also ignore delete markers newer
351         // than the readpoint of any open scanner, this prevents deleted
352         // rows that could still be seen by a scanner from being collected
353         boolean includeDeleteMarker = seePastDeleteMarkers ?
354             tr.withinTimeRange(timestamp) :
355             tr.withinOrAfterTimeRange(timestamp);
356         if (includeDeleteMarker
357             && mvccVersion <= maxReadPointToTrackVersions) {
358           this.deletes.add(cell);
359         }
360         // Can't early out now, because DelFam come before any other keys
361       }
362      
363       if ((!isUserScan)
364           && timeToPurgeDeletes > 0
365           && (EnvironmentEdgeManager.currentTime() - timestamp) 
366             <= timeToPurgeDeletes) {
367         return MatchCode.INCLUDE;
368       } else if (retainDeletesInOutput || mvccVersion > maxReadPointToTrackVersions) {
369         // always include or it is not time yet to check whether it is OK
370         // to purge deltes or not
371         if (!isUserScan) {
372           // if this is not a user scan (compaction), we can filter this deletemarker right here
373           // otherwise (i.e. a "raw" scan) we fall through to normal version and timerange checking
374           return MatchCode.INCLUDE;
375         }
376       } else if (keepDeletedCells == KeepDeletedCells.TRUE
377           || (keepDeletedCells == KeepDeletedCells.TTL && timestamp >= ttl)) {
378         if (timestamp < earliestPutTs) {
379           // keeping delete rows, but there are no puts older than
380           // this delete in the store files.
381           return columns.getNextRowOrNextColumn(cell);
382         }
383         // else: fall through and do version counting on the
384         // delete markers
385       } else {
386         return MatchCode.SKIP;
387       }
388       // note the following next else if...
389       // delete marker are not subject to other delete markers
390     } else if (!this.deletes.isEmpty()) {
391       DeleteResult deleteResult = deletes.isDeleted(cell);
392       switch (deleteResult) {
393         case FAMILY_DELETED:
394         case COLUMN_DELETED:
395           return columns.getNextRowOrNextColumn(cell);
396         case VERSION_DELETED:
397         case FAMILY_VERSION_DELETED:
398           return MatchCode.SKIP;
399         case NOT_DELETED:
400           break;
401         default:
402           throw new RuntimeException("UNEXPECTED");
403         }
404     }
405 
406     int timestampComparison = tr.compare(timestamp);
407     if (timestampComparison >= 1) {
408       return MatchCode.SKIP;
409     } else if (timestampComparison <= -1) {
410       return columns.getNextRowOrNextColumn(cell);
411     }
412 
413     // STEP 1: Check if the column is part of the requested columns
414     MatchCode colChecker = columns.checkColumn(cell, typeByte);
415     if (colChecker == MatchCode.INCLUDE) {
416       ReturnCode filterResponse = ReturnCode.SKIP;
417       // STEP 2: Yes, the column is part of the requested columns. Check if filter is present
418       if (filter != null) {
419         // STEP 3: Filter the key value and return if it filters out
420         filterResponse = filter.filterKeyValue(cell);
421         switch (filterResponse) {
422         case SKIP:
423           return MatchCode.SKIP;
424         case NEXT_COL:
425           return columns.getNextRowOrNextColumn(cell);
426         case NEXT_ROW:
427           stickyNextRow = true;
428           return MatchCode.SEEK_NEXT_ROW;
429         case SEEK_NEXT_USING_HINT:
430           return MatchCode.SEEK_NEXT_USING_HINT;
431         default:
432           //It means it is either include or include and seek next
433           break;
434         }
435       }
436       /*
437        * STEP 4: Reaching this step means the column is part of the requested columns and either
438        * the filter is null or the filter has returned INCLUDE or INCLUDE_AND_NEXT_COL response.
439        * Now check the number of versions needed. This method call returns SKIP, INCLUDE,
440        * INCLUDE_AND_SEEK_NEXT_ROW, INCLUDE_AND_SEEK_NEXT_COL.
441        *
442        * FilterResponse            ColumnChecker               Desired behavior
443        * INCLUDE                   SKIP                        row has already been included, SKIP.
444        * INCLUDE                   INCLUDE                     INCLUDE
445        * INCLUDE                   INCLUDE_AND_SEEK_NEXT_COL   INCLUDE_AND_SEEK_NEXT_COL
446        * INCLUDE                   INCLUDE_AND_SEEK_NEXT_ROW   INCLUDE_AND_SEEK_NEXT_ROW
447        * INCLUDE_AND_SEEK_NEXT_COL SKIP                        row has already been included, SKIP.
448        * INCLUDE_AND_SEEK_NEXT_COL INCLUDE                     INCLUDE_AND_SEEK_NEXT_COL
449        * INCLUDE_AND_SEEK_NEXT_COL INCLUDE_AND_SEEK_NEXT_COL   INCLUDE_AND_SEEK_NEXT_COL
450        * INCLUDE_AND_SEEK_NEXT_COL INCLUDE_AND_SEEK_NEXT_ROW   INCLUDE_AND_SEEK_NEXT_ROW
451        *
452        * In all the above scenarios, we return the column checker return value except for
453        * FilterResponse (INCLUDE_AND_SEEK_NEXT_COL) and ColumnChecker(INCLUDE)
454        */
455       colChecker = columns.checkVersions(cell, timestamp, typeByte,
456           mvccVersion > maxReadPointToTrackVersions);
457       //Optimize with stickyNextRow
458       boolean seekNextRowFromEssential = filterResponse == ReturnCode.INCLUDE_AND_SEEK_NEXT_ROW &&
459           filter.isFamilyEssential(cell.getFamilyArray());
460       if (colChecker == MatchCode.INCLUDE_AND_SEEK_NEXT_ROW || seekNextRowFromEssential) {
461         stickyNextRow = true;
462       }
463       if (filterResponse == ReturnCode.INCLUDE_AND_SEEK_NEXT_ROW) {
464         if (colChecker != MatchCode.SKIP) {
465           return MatchCode.INCLUDE_AND_SEEK_NEXT_ROW;
466         }
467         return MatchCode.SEEK_NEXT_ROW;
468       }
469       return (filterResponse == ReturnCode.INCLUDE_AND_NEXT_COL &&
470           colChecker == MatchCode.INCLUDE) ? MatchCode.INCLUDE_AND_SEEK_NEXT_COL
471           : colChecker;
472     }
473     stickyNextRow = (colChecker == MatchCode.SEEK_NEXT_ROW) ? true
474         : stickyNextRow;
475     return colChecker;
476   }
477 
478   /** Handle partial-drop-deletes. As we match keys in order, when we have a range from which
479    * we can drop deletes, we can set retainDeletesInOutput to false for the duration of this
480    * range only, and maintain consistency. */
481   private void checkPartialDropDeleteRange(Cell curCell) {
482     // If partial-drop-deletes are used, initially, dropDeletesFromRow and dropDeletesToRow
483     // are both set, and the matcher is set to retain deletes. We assume ordered keys. When
484     // dropDeletesFromRow is leq current kv, we start dropping deletes and reset
485     // dropDeletesFromRow; thus the 2nd "if" starts to apply.
486     if ((dropDeletesFromRow != null)
487         && (Arrays.equals(dropDeletesFromRow, HConstants.EMPTY_START_ROW) ||
488             (CellComparator.COMPARATOR.compareRows(curCell, dropDeletesFromRow, 0,
489                 dropDeletesFromRow.length) >= 0))) {
490       retainDeletesInOutput = false;
491       dropDeletesFromRow = null;
492     }
493     // If dropDeletesFromRow is null and dropDeletesToRow is set, we are inside the partial-
494     // drop-deletes range. When dropDeletesToRow is leq current kv, we stop dropping deletes,
495     // and reset dropDeletesToRow so that we don't do any more compares.
496     if ((dropDeletesFromRow == null)
497         && (dropDeletesToRow != null)
498         && !Arrays.equals(dropDeletesToRow, HConstants.EMPTY_END_ROW)
499         && (CellComparator.COMPARATOR
500             .compareRows(curCell, dropDeletesToRow, 0, dropDeletesToRow.length) >= 0)) {
501       retainDeletesInOutput = true;
502       dropDeletesToRow = null;
503     }
504   }
505 
506   public boolean moreRowsMayExistAfter(Cell kv) {
507     if (this.isReversed) {
508       if (rowComparator.compareRows(kv, stopRow, 0, stopRow.length) <= 0) {
509         return false;
510       } else {
511         return true;
512       }
513     }
514     if (!Bytes.equals(stopRow , HConstants.EMPTY_END_ROW) &&
515         rowComparator.compareRows(kv, stopRow, 0, stopRow.length) >= 0) {
516       // KV >= STOPROW
517       // then NO there is nothing left.
518       return false;
519     } else {
520       return true;
521     }
522   }
523 
524   /**
525    * Set the row when there is change in row
526    * @param curCell
527    */
528   public void setToNewRow(Cell curCell) {
529     checkPartialDropDeleteRange(curCell);
530     this.curCell = curCell;
531     reset();
532   }
533 
534   public void reset() {
535     this.deletes.reset();
536     this.columns.reset();
537 
538     stickyNextRow = false;
539   }
540 
541   /**
542    *
543    * @return the start key
544    */
545   public Cell getStartKey() {
546     return this.startKey;
547   }
548 
549   /**
550    *
551    * @return the Filter
552    */
553   Filter getFilter() {
554     return this.filter;
555   }
556 
557   public Cell getNextKeyHint(Cell kv) throws IOException {
558     if (filter == null) {
559       return null;
560     } else {
561       return filter.getNextCellHint(kv);
562     }
563   }
564 
565   public Cell getKeyForNextColumn(Cell kv) {
566     ColumnCount nextColumn = columns.getColumnHint();
567     if (nextColumn == null) {
568       return CellUtil.createLastOnRowCol(kv);
569     } else {
570       return CellUtil.createFirstOnRowCol(kv, nextColumn.getBuffer(), nextColumn.getOffset(),
571           nextColumn.getLength());
572     }
573   }
574 
575   public Cell getKeyForNextRow(Cell c) {
576     return CellUtil.createLastOnRow(c);
577   }
578 
579   /**
580    * @param nextIndexed the key of the next entry in the block index (if any)
581    * @param kv The Cell we're using to calculate the seek key
582    * @return result of the compare between the indexed key and the key portion of the passed cell
583    */
584   public int compareKeyForNextRow(Cell nextIndexed, Cell kv) {
585     return rowComparator.compareKeyBasedOnColHint(nextIndexed, kv, 0, 0, null, 0, 0,
586         HConstants.OLDEST_TIMESTAMP, Type.Minimum.getCode());
587   }
588 
589   /**
590    * @param nextIndexed the key of the next entry in the block index (if any)
591    * @param currentCell The Cell we're using to calculate the seek key
592    * @return result of the compare between the indexed key and the key portion of the passed cell
593    */
594   public int compareKeyForNextColumn(Cell nextIndexed, Cell currentCell) {
595     ColumnCount nextColumn = columns.getColumnHint();
596     if (nextColumn == null) {
597       return rowComparator.compareKeyBasedOnColHint(nextIndexed, currentCell, 0, 0, null, 0, 0,
598           HConstants.OLDEST_TIMESTAMP, Type.Minimum.getCode());
599     } else {
600       return rowComparator.compareKeyBasedOnColHint(nextIndexed, currentCell,
601           currentCell.getFamilyOffset(), currentCell.getFamilyLength(), nextColumn.getBuffer(),
602           nextColumn.getOffset(), nextColumn.getLength(), HConstants.LATEST_TIMESTAMP,
603           Type.Maximum.getCode());
604     }
605   }
606 
607   //Used only for testing purposes
608   static MatchCode checkColumn(ColumnTracker columnTracker, byte[] bytes, int offset,
609       int length, long ttl, byte type, boolean ignoreCount) throws IOException {
610     KeyValue kv = KeyValueUtil.createFirstOnRow(HConstants.EMPTY_BYTE_ARRAY, 0, 0,
611         HConstants.EMPTY_BYTE_ARRAY, 0, 0, bytes, offset, length);
612     MatchCode matchCode = columnTracker.checkColumn(kv, type);
613     if (matchCode == MatchCode.INCLUDE) {
614       return columnTracker.checkVersions(kv, ttl, type, ignoreCount);
615     }
616     return matchCode;
617   }
618 
619   /**
620    * {@link #match} return codes.  These instruct the scanner moving through
621    * memstores and StoreFiles what to do with the current KeyValue.
622    * <p>
623    * Additionally, this contains "early-out" language to tell the scanner to
624    * move on to the next File (memstore or Storefile), or to return immediately.
625    */
626   public static enum MatchCode {
627     /**
628      * Include KeyValue in the returned result
629      */
630     INCLUDE,
631 
632     /**
633      * Do not include KeyValue in the returned result
634      */
635     SKIP,
636 
637     /**
638      * Do not include, jump to next StoreFile or memstore (in time order)
639      */
640     NEXT,
641 
642     /**
643      * Do not include, return current result
644      */
645     DONE,
646 
647     /**
648      * These codes are used by the ScanQueryMatcher
649      */
650 
651     /**
652      * Done with the row, seek there.
653      */
654     SEEK_NEXT_ROW,
655     /**
656      * Done with column, seek to next.
657      */
658     SEEK_NEXT_COL,
659 
660     /**
661      * Done with scan, thanks to the row filter.
662      */
663     DONE_SCAN,
664 
665     /*
666      * Seek to next key which is given as hint.
667      */
668     SEEK_NEXT_USING_HINT,
669 
670     /**
671      * Include KeyValue and done with column, seek to next.
672      */
673     INCLUDE_AND_SEEK_NEXT_COL,
674 
675     /**
676      * Include KeyValue and done with row, seek to next.
677      */
678     INCLUDE_AND_SEEK_NEXT_ROW,
679   }
680 }