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