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