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