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.client;
21  
22  import java.io.IOException;
23  import java.util.ArrayList;
24  import java.util.Arrays;
25  import java.util.HashMap;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.NavigableSet;
29  import java.util.TreeMap;
30  import java.util.TreeSet;
31  
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  import org.apache.hadoop.hbase.classification.InterfaceAudience;
35  import org.apache.hadoop.hbase.classification.InterfaceStability;
36  import org.apache.hadoop.hbase.HConstants;
37  import org.apache.hadoop.hbase.client.metrics.ScanMetrics;
38  import org.apache.hadoop.hbase.filter.Filter;
39  import org.apache.hadoop.hbase.filter.IncompatibleFilterException;
40  import org.apache.hadoop.hbase.io.TimeRange;
41  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
42  import org.apache.hadoop.hbase.security.access.Permission;
43  import org.apache.hadoop.hbase.security.visibility.Authorizations;
44  import org.apache.hadoop.hbase.util.Bytes;
45  
46  /**
47   * Used to perform Scan operations.
48   * <p>
49   * All operations are identical to {@link Get} with the exception of
50   * instantiation.  Rather than specifying a single row, an optional startRow
51   * and stopRow may be defined.  If rows are not specified, the Scanner will
52   * iterate over all rows.
53   * <p>
54   * To scan everything for each row, instantiate a Scan object.
55   * <p>
56   * To modify scanner caching for just this scan, use {@link #setCaching(int) setCaching}.
57   * If caching is NOT set, we will use the caching value of the hosting {@link Table}.
58   * In addition to row caching, it is possible to specify a
59   * maximum result size, using {@link #setMaxResultSize(long)}. When both are used,
60   * single server requests are limited by either number of rows or maximum result size, whichever
61   * limit comes first.
62   * <p>
63   * To further define the scope of what to get when scanning, perform additional
64   * methods as outlined below.
65   * <p>
66   * To get all columns from specific families, execute {@link #addFamily(byte[]) addFamily}
67   * for each family to retrieve.
68   * <p>
69   * To get specific columns, execute {@link #addColumn(byte[], byte[]) addColumn}
70   * for each column to retrieve.
71   * <p>
72   * To only retrieve columns within a specific range of version timestamps,
73   * execute {@link #setTimeRange(long, long) setTimeRange}.
74   * <p>
75   * To only retrieve columns with a specific timestamp, execute
76   * {@link #setTimeStamp(long) setTimestamp}.
77   * <p>
78   * To limit the number of versions of each column to be returned, execute
79   * {@link #setMaxVersions(int) setMaxVersions}.
80   * <p>
81   * To limit the maximum number of values returned for each call to next(),
82   * execute {@link #setBatch(int) setBatch}.
83   * <p>
84   * To add a filter, execute {@link #setFilter(org.apache.hadoop.hbase.filter.Filter) setFilter}.
85   * <p>
86   * Expert: To explicitly disable server-side block caching for this scan,
87   * execute {@link #setCacheBlocks(boolean)}.
88   */
89  @InterfaceAudience.Public
90  @InterfaceStability.Stable
91  public class Scan extends Query {
92    private static final Log LOG = LogFactory.getLog(Scan.class);
93  
94    private static final String RAW_ATTR = "_raw_";
95  
96    /**
97     * EXPERT ONLY.
98     * An integer (not long) indicating to the scanner logic how many times we attempt to retrieve the
99     * next KV before we schedule a reseek.
100    * The right value depends on the size of the average KV. A reseek is more efficient when
101    * it can skip 5-10 KVs or 512B-1KB, or when the next KV is likely found in another HFile block.
102    * Setting this only has any effect when columns were added with
103    * {@link #addColumn(byte[], byte[])}
104    * <pre>{@code
105    * Scan s = new Scan(...);
106    * s.addColumn(...);
107    * s.setAttribute(Scan.HINT_LOOKAHEAD, Bytes.toBytes(2));
108    * }</pre>
109    * Default is 0 (always reseek).
110    */
111   public static final String HINT_LOOKAHEAD = "_look_ahead_";
112 
113   private byte [] startRow = HConstants.EMPTY_START_ROW;
114   private byte [] stopRow  = HConstants.EMPTY_END_ROW;
115   private int maxVersions = 1;
116   private int batch = -1;
117 
118   private int storeLimit = -1;
119   private int storeOffset = 0;
120   private boolean getScan;
121 
122   /**
123    * @deprecated since 1.0.0. Use {@link #setScanMetricsEnabled(boolean)}
124    */
125   // Make private or remove.
126   @Deprecated
127   static public final String SCAN_ATTRIBUTES_METRICS_ENABLE = "scan.attributes.metrics.enable";
128 
129   /**
130    * Use {@link #getScanMetrics()}
131    */
132   // Make this private or remove.
133   @Deprecated
134   static public final String SCAN_ATTRIBUTES_METRICS_DATA = "scan.attributes.metrics.data";
135 
136   // If an application wants to use multiple scans over different tables each scan must
137   // define this attribute with the appropriate table name by calling
138   // scan.setAttribute(Scan.SCAN_ATTRIBUTES_TABLE_NAME, Bytes.toBytes(tableName))
139   static public final String SCAN_ATTRIBUTES_TABLE_NAME = "scan.attributes.table.name";
140 
141   /*
142    * -1 means no caching
143    */
144   private int caching = -1;
145   private long maxResultSize = -1;
146   private boolean cacheBlocks = true;
147   private boolean reversed = false;
148   private TimeRange tr = new TimeRange();
149   private Map<byte [], NavigableSet<byte []>> familyMap =
150     new TreeMap<byte [], NavigableSet<byte []>>(Bytes.BYTES_COMPARATOR);
151   private Boolean loadColumnFamiliesOnDemand = null;
152 
153   /**
154    * Set it true for small scan to get better performance
155    *
156    * Small scan should use pread and big scan can use seek + read
157    *
158    * seek + read is fast but can cause two problem (1) resource contention (2)
159    * cause too much network io
160    *
161    * [89-fb] Using pread for non-compaction read request
162    * https://issues.apache.org/jira/browse/HBASE-7266
163    *
164    * On the other hand, if setting it true, we would do
165    * openScanner,next,closeScanner in one RPC call. It means the better
166    * performance for small scan. [HBASE-9488].
167    *
168    * Generally, if the scan range is within one data block(64KB), it could be
169    * considered as a small scan.
170    */
171   private boolean small = false;
172 
173   /**
174    * Create a Scan operation across all rows.
175    */
176   public Scan() {}
177 
178   public Scan(byte [] startRow, Filter filter) {
179     this(startRow);
180     this.filter = filter;
181   }
182 
183   /**
184    * Create a Scan operation starting at the specified row.
185    * <p>
186    * If the specified row does not exist, the Scanner will start from the
187    * next closest row after the specified row.
188    * @param startRow row to start scanner at or after
189    */
190   public Scan(byte [] startRow) {
191     this.startRow = startRow;
192   }
193 
194   /**
195    * Create a Scan operation for the range of rows specified.
196    * @param startRow row to start scanner at or after (inclusive)
197    * @param stopRow row to stop scanner before (exclusive)
198    */
199   public Scan(byte [] startRow, byte [] stopRow) {
200     this.startRow = startRow;
201     this.stopRow = stopRow;
202     //if the startRow and stopRow both are empty, it is not a Get
203     this.getScan = isStartRowAndEqualsStopRow();
204   }
205 
206   /**
207    * Creates a new instance of this class while copying all values.
208    *
209    * @param scan  The scan instance to copy from.
210    * @throws IOException When copying the values fails.
211    */
212   public Scan(Scan scan) throws IOException {
213     startRow = scan.getStartRow();
214     stopRow  = scan.getStopRow();
215     maxVersions = scan.getMaxVersions();
216     batch = scan.getBatch();
217     storeLimit = scan.getMaxResultsPerColumnFamily();
218     storeOffset = scan.getRowOffsetPerColumnFamily();
219     caching = scan.getCaching();
220     maxResultSize = scan.getMaxResultSize();
221     cacheBlocks = scan.getCacheBlocks();
222     getScan = scan.isGetScan();
223     filter = scan.getFilter(); // clone?
224     loadColumnFamiliesOnDemand = scan.getLoadColumnFamiliesOnDemandValue();
225     consistency = scan.getConsistency();
226     reversed = scan.isReversed();
227     small = scan.isSmall();
228     TimeRange ctr = scan.getTimeRange();
229     tr = new TimeRange(ctr.getMin(), ctr.getMax());
230     Map<byte[], NavigableSet<byte[]>> fams = scan.getFamilyMap();
231     for (Map.Entry<byte[],NavigableSet<byte[]>> entry : fams.entrySet()) {
232       byte [] fam = entry.getKey();
233       NavigableSet<byte[]> cols = entry.getValue();
234       if (cols != null && cols.size() > 0) {
235         for (byte[] col : cols) {
236           addColumn(fam, col);
237         }
238       } else {
239         addFamily(fam);
240       }
241     }
242     for (Map.Entry<String, byte[]> attr : scan.getAttributesMap().entrySet()) {
243       setAttribute(attr.getKey(), attr.getValue());
244     }
245   }
246 
247   /**
248    * Builds a scan object with the same specs as get.
249    * @param get get to model scan after
250    */
251   public Scan(Get get) {
252     this.startRow = get.getRow();
253     this.stopRow = get.getRow();
254     this.filter = get.getFilter();
255     this.cacheBlocks = get.getCacheBlocks();
256     this.maxVersions = get.getMaxVersions();
257     this.storeLimit = get.getMaxResultsPerColumnFamily();
258     this.storeOffset = get.getRowOffsetPerColumnFamily();
259     this.tr = get.getTimeRange();
260     this.familyMap = get.getFamilyMap();
261     this.getScan = true;
262     this.consistency = get.getConsistency();
263     for (Map.Entry<String, byte[]> attr : get.getAttributesMap().entrySet()) {
264       setAttribute(attr.getKey(), attr.getValue());
265     }
266   }
267 
268   public boolean isGetScan() {
269     return this.getScan || isStartRowAndEqualsStopRow();
270   }
271 
272   private boolean isStartRowAndEqualsStopRow() {
273     return this.startRow != null && this.startRow.length > 0 &&
274         Bytes.equals(this.startRow, this.stopRow);
275   }
276   /**
277    * Get all columns from the specified family.
278    * <p>
279    * Overrides previous calls to addColumn for this family.
280    * @param family family name
281    * @return this
282    */
283   public Scan addFamily(byte [] family) {
284     familyMap.remove(family);
285     familyMap.put(family, null);
286     return this;
287   }
288 
289   /**
290    * Get the column from the specified family with the specified qualifier.
291    * <p>
292    * Overrides previous calls to addFamily for this family.
293    * @param family family name
294    * @param qualifier column qualifier
295    * @return this
296    */
297   public Scan addColumn(byte [] family, byte [] qualifier) {
298     NavigableSet<byte []> set = familyMap.get(family);
299     if(set == null) {
300       set = new TreeSet<byte []>(Bytes.BYTES_COMPARATOR);
301     }
302     if (qualifier == null) {
303       qualifier = HConstants.EMPTY_BYTE_ARRAY;
304     }
305     set.add(qualifier);
306     familyMap.put(family, set);
307     return this;
308   }
309 
310   /**
311    * Get versions of columns only within the specified timestamp range,
312    * [minStamp, maxStamp).  Note, default maximum versions to return is 1.  If
313    * your time range spans more than one version and you want all versions
314    * returned, up the number of versions beyond the default.
315    * @param minStamp minimum timestamp value, inclusive
316    * @param maxStamp maximum timestamp value, exclusive
317    * @throws IOException if invalid time range
318    * @see #setMaxVersions()
319    * @see #setMaxVersions(int)
320    * @return this
321    */
322   public Scan setTimeRange(long minStamp, long maxStamp)
323   throws IOException {
324     tr = new TimeRange(minStamp, maxStamp);
325     return this;
326   }
327 
328   /**
329    * Get versions of columns with the specified timestamp. Note, default maximum
330    * versions to return is 1.  If your time range spans more than one version
331    * and you want all versions returned, up the number of versions beyond the
332    * defaut.
333    * @param timestamp version timestamp
334    * @see #setMaxVersions()
335    * @see #setMaxVersions(int)
336    * @return this
337    */
338   public Scan setTimeStamp(long timestamp)
339   throws IOException {
340     try {
341       tr = new TimeRange(timestamp, timestamp+1);
342     } catch(IOException e) {
343       // This should never happen, unless integer overflow or something extremely wrong...
344       LOG.error("TimeRange failed, likely caused by integer overflow. ", e);
345       throw e;
346     }
347     return this;
348   }
349 
350   /**
351    * Set the start row of the scan.
352    * @param startRow row to start scan on (inclusive)
353    * Note: In order to make startRow exclusive add a trailing 0 byte
354    * @return this
355    */
356   public Scan setStartRow(byte [] startRow) {
357     this.startRow = startRow;
358     return this;
359   }
360 
361   /**
362    * Set the stop row.
363    * @param stopRow row to end at (exclusive)
364    * <p><b>Note:</b> In order to make stopRow inclusive add a trailing 0 byte</p>
365    * <p><b>Note:</b> When doing a filter for a rowKey <u>Prefix</u>
366    * use {@link #setRowPrefixFilter(byte[])}.
367    * The 'trailing 0' will not yield the desired result.</p>
368    * @return this
369    */
370   public Scan setStopRow(byte [] stopRow) {
371     this.stopRow = stopRow;
372     return this;
373   }
374 
375   /**
376    * <p>Set a filter (using stopRow and startRow) so the result set only contains rows where the
377    * rowKey starts with the specified prefix.</p>
378    * <p>This is a utility method that converts the desired rowPrefix into the appropriate values
379    * for the startRow and stopRow to achieve the desired result.</p>
380    * <p>This can safely be used in combination with setFilter.</p>
381    * <p><b>NOTE: Doing a {@link #setStartRow(byte[])} and/or {@link #setStopRow(byte[])}
382    * after this method will yield undefined results.</b></p>
383    * @param rowPrefix the prefix all rows must start with. (Set <i>null</i> to remove the filter.)
384    * @return this
385    */
386   public Scan setRowPrefixFilter(byte[] rowPrefix) {
387     if (rowPrefix == null) {
388       setStartRow(HConstants.EMPTY_START_ROW);
389       setStopRow(HConstants.EMPTY_END_ROW);
390     } else {
391       this.setStartRow(rowPrefix);
392       this.setStopRow(calculateTheClosestNextRowKeyForPrefix(rowPrefix));
393     }
394     return this;
395   }
396 
397   /**
398    * <p>When scanning for a prefix the scan should stop immediately after the the last row that
399    * has the specified prefix. This method calculates the closest next rowKey immediately following
400    * the given rowKeyPrefix.</p>
401    * <p><b>IMPORTANT: This converts a rowKey<u>Prefix</u> into a rowKey</b>.</p>
402    * <p>If the prefix is an 'ASCII' string put into a byte[] then this is easy because you can
403    * simply increment the last byte of the array.
404    * But if your application uses real binary rowids you may run into the scenario that your
405    * prefix is something like:</p>
406    * &nbsp;&nbsp;&nbsp;<b>{ 0x12, 0x23, 0xFF, 0xFF }</b><br/>
407    * Then this stopRow needs to be fed into the actual scan<br/>
408    * &nbsp;&nbsp;&nbsp;<b>{ 0x12, 0x24 }</b> (Notice that it is shorter now)<br/>
409    * This method calculates the correct stop row value for this usecase.
410    *
411    * @param rowKeyPrefix the rowKey<u>Prefix</u>.
412    * @return the closest next rowKey immediately following the given rowKeyPrefix.
413    */
414   private byte[] calculateTheClosestNextRowKeyForPrefix(byte[] rowKeyPrefix) {
415     // Essentially we are treating it like an 'unsigned very very long' and doing +1 manually.
416     // Search for the place where the trailing 0xFFs start
417     int offset = rowKeyPrefix.length;
418     while (offset > 0) {
419       if (rowKeyPrefix[offset - 1] != (byte) 0xFF) {
420         break;
421       }
422       offset--;
423     }
424 
425     if (offset == 0) {
426       // We got an 0xFFFF... (only FFs) stopRow value which is
427       // the last possible prefix before the end of the table.
428       // So set it to stop at the 'end of the table'
429       return HConstants.EMPTY_END_ROW;
430     }
431 
432     // Copy the right length of the original
433     byte[] newStopRow = Arrays.copyOfRange(rowKeyPrefix, 0, offset);
434     // And increment the last one
435     newStopRow[newStopRow.length - 1]++;
436     return newStopRow;
437   }
438 
439   /**
440    * Get all available versions.
441    * @return this
442    */
443   public Scan setMaxVersions() {
444     this.maxVersions = Integer.MAX_VALUE;
445     return this;
446   }
447 
448   /**
449    * Get up to the specified number of versions of each column.
450    * @param maxVersions maximum versions for each column
451    * @return this
452    */
453   public Scan setMaxVersions(int maxVersions) {
454     this.maxVersions = maxVersions;
455     return this;
456   }
457 
458   /**
459    * Set the maximum number of values to return for each call to next()
460    * @param batch the maximum number of values
461    */
462   public Scan setBatch(int batch) {
463     if (this.hasFilter() && this.filter.hasFilterRow()) {
464       throw new IncompatibleFilterException(
465         "Cannot set batch on a scan using a filter" +
466         " that returns true for filter.hasFilterRow");
467     }
468     this.batch = batch;
469     return this;
470   }
471 
472   /**
473    * Set the maximum number of values to return per row per Column Family
474    * @param limit the maximum number of values returned / row / CF
475    */
476   public Scan setMaxResultsPerColumnFamily(int limit) {
477     this.storeLimit = limit;
478     return this;
479   }
480 
481   /**
482    * Set offset for the row per Column Family.
483    * @param offset is the number of kvs that will be skipped.
484    */
485   public Scan setRowOffsetPerColumnFamily(int offset) {
486     this.storeOffset = offset;
487     return this;
488   }
489 
490   /**
491    * Set the number of rows for caching that will be passed to scanners.
492    * If not set, the Configuration setting {@link HConstants#HBASE_CLIENT_SCANNER_CACHING} will
493    * apply.
494    * Higher caching values will enable faster scanners but will use more memory.
495    * @param caching the number of rows for caching
496    */
497   public Scan setCaching(int caching) {
498     this.caching = caching;
499     return this;
500   }
501 
502   /**
503    * @return the maximum result size in bytes. See {@link #setMaxResultSize(long)}
504    */
505   public long getMaxResultSize() {
506     return maxResultSize;
507   }
508 
509   /**
510    * Set the maximum result size. The default is -1; this means that no specific
511    * maximum result size will be set for this scan, and the global configured
512    * value will be used instead. (Defaults to unlimited).
513    *
514    * @param maxResultSize The maximum result size in bytes.
515    */
516   public Scan setMaxResultSize(long maxResultSize) {
517     this.maxResultSize = maxResultSize;
518     return this;
519   }
520 
521   @Override
522   public Scan setFilter(Filter filter) {
523     super.setFilter(filter);
524     return this;
525   }
526 
527   /**
528    * Setting the familyMap
529    * @param familyMap map of family to qualifier
530    * @return this
531    */
532   public Scan setFamilyMap(Map<byte [], NavigableSet<byte []>> familyMap) {
533     this.familyMap = familyMap;
534     return this;
535   }
536 
537   /**
538    * Getting the familyMap
539    * @return familyMap
540    */
541   public Map<byte [], NavigableSet<byte []>> getFamilyMap() {
542     return this.familyMap;
543   }
544 
545   /**
546    * @return the number of families in familyMap
547    */
548   public int numFamilies() {
549     if(hasFamilies()) {
550       return this.familyMap.size();
551     }
552     return 0;
553   }
554 
555   /**
556    * @return true if familyMap is non empty, false otherwise
557    */
558   public boolean hasFamilies() {
559     return !this.familyMap.isEmpty();
560   }
561 
562   /**
563    * @return the keys of the familyMap
564    */
565   public byte[][] getFamilies() {
566     if(hasFamilies()) {
567       return this.familyMap.keySet().toArray(new byte[0][0]);
568     }
569     return null;
570   }
571 
572   /**
573    * @return the startrow
574    */
575   public byte [] getStartRow() {
576     return this.startRow;
577   }
578 
579   /**
580    * @return the stoprow
581    */
582   public byte [] getStopRow() {
583     return this.stopRow;
584   }
585 
586   /**
587    * @return the max number of versions to fetch
588    */
589   public int getMaxVersions() {
590     return this.maxVersions;
591   }
592 
593   /**
594    * @return maximum number of values to return for a single call to next()
595    */
596   public int getBatch() {
597     return this.batch;
598   }
599 
600   /**
601    * @return maximum number of values to return per row per CF
602    */
603   public int getMaxResultsPerColumnFamily() {
604     return this.storeLimit;
605   }
606 
607   /**
608    * Method for retrieving the scan's offset per row per column
609    * family (#kvs to be skipped)
610    * @return row offset
611    */
612   public int getRowOffsetPerColumnFamily() {
613     return this.storeOffset;
614   }
615 
616   /**
617    * @return caching the number of rows fetched when calling next on a scanner
618    */
619   public int getCaching() {
620     return this.caching;
621   }
622 
623   /**
624    * @return TimeRange
625    */
626   public TimeRange getTimeRange() {
627     return this.tr;
628   }
629 
630   /**
631    * @return RowFilter
632    */
633   @Override
634   public Filter getFilter() {
635     return filter;
636   }
637 
638   /**
639    * @return true is a filter has been specified, false if not
640    */
641   public boolean hasFilter() {
642     return filter != null;
643   }
644 
645   /**
646    * Set whether blocks should be cached for this Scan.
647    * <p>
648    * This is true by default.  When true, default settings of the table and
649    * family are used (this will never override caching blocks if the block
650    * cache is disabled for that family or entirely).
651    *
652    * @param cacheBlocks if false, default settings are overridden and blocks
653    * will not be cached
654    */
655   public Scan setCacheBlocks(boolean cacheBlocks) {
656     this.cacheBlocks = cacheBlocks;
657     return this;
658   }
659 
660   /**
661    * Get whether blocks should be cached for this Scan.
662    * @return true if default caching should be used, false if blocks should not
663    * be cached
664    */
665   public boolean getCacheBlocks() {
666     return cacheBlocks;
667   }
668 
669   /**
670    * Set whether this scan is a reversed one
671    * <p>
672    * This is false by default which means forward(normal) scan.
673    *
674    * @param reversed if true, scan will be backward order
675    * @return this
676    */
677   public Scan setReversed(boolean reversed) {
678     this.reversed = reversed;
679     return this;
680   }
681 
682   /**
683    * Get whether this scan is a reversed one.
684    * @return true if backward scan, false if forward(default) scan
685    */
686   public boolean isReversed() {
687     return reversed;
688   }
689 
690   /**
691    * Set the value indicating whether loading CFs on demand should be allowed (cluster
692    * default is false). On-demand CF loading doesn't load column families until necessary, e.g.
693    * if you filter on one column, the other column family data will be loaded only for the rows
694    * that are included in result, not all rows like in normal case.
695    * With column-specific filters, like SingleColumnValueFilter w/filterIfMissing == true,
696    * this can deliver huge perf gains when there's a cf with lots of data; however, it can
697    * also lead to some inconsistent results, as follows:
698    * - if someone does a concurrent update to both column families in question you may get a row
699    *   that never existed, e.g. for { rowKey = 5, { cat_videos => 1 }, { video => "my cat" } }
700    *   someone puts rowKey 5 with { cat_videos => 0 }, { video => "my dog" }, concurrent scan
701    *   filtering on "cat_videos == 1" can get { rowKey = 5, { cat_videos => 1 },
702    *   { video => "my dog" } }.
703    * - if there's a concurrent split and you have more than 2 column families, some rows may be
704    *   missing some column families.
705    */
706   public Scan setLoadColumnFamiliesOnDemand(boolean value) {
707     this.loadColumnFamiliesOnDemand = value;
708     return this;
709   }
710 
711   /**
712    * Get the raw loadColumnFamiliesOnDemand setting; if it's not set, can be null.
713    */
714   public Boolean getLoadColumnFamiliesOnDemandValue() {
715     return this.loadColumnFamiliesOnDemand;
716   }
717 
718   /**
719    * Get the logical value indicating whether on-demand CF loading should be allowed.
720    */
721   public boolean doLoadColumnFamiliesOnDemand() {
722     return (this.loadColumnFamiliesOnDemand != null)
723       && this.loadColumnFamiliesOnDemand.booleanValue();
724   }
725 
726   /**
727    * Compile the table and column family (i.e. schema) information
728    * into a String. Useful for parsing and aggregation by debugging,
729    * logging, and administration tools.
730    * @return Map
731    */
732   @Override
733   public Map<String, Object> getFingerprint() {
734     Map<String, Object> map = new HashMap<String, Object>();
735     List<String> families = new ArrayList<String>();
736     if(this.familyMap.size() == 0) {
737       map.put("families", "ALL");
738       return map;
739     } else {
740       map.put("families", families);
741     }
742     for (Map.Entry<byte [], NavigableSet<byte[]>> entry :
743         this.familyMap.entrySet()) {
744       families.add(Bytes.toStringBinary(entry.getKey()));
745     }
746     return map;
747   }
748 
749   /**
750    * Compile the details beyond the scope of getFingerprint (row, columns,
751    * timestamps, etc.) into a Map along with the fingerprinted information.
752    * Useful for debugging, logging, and administration tools.
753    * @param maxCols a limit on the number of columns output prior to truncation
754    * @return Map
755    */
756   @Override
757   public Map<String, Object> toMap(int maxCols) {
758     // start with the fingerpring map and build on top of it
759     Map<String, Object> map = getFingerprint();
760     // map from families to column list replaces fingerprint's list of families
761     Map<String, List<String>> familyColumns =
762       new HashMap<String, List<String>>();
763     map.put("families", familyColumns);
764     // add scalar information first
765     map.put("startRow", Bytes.toStringBinary(this.startRow));
766     map.put("stopRow", Bytes.toStringBinary(this.stopRow));
767     map.put("maxVersions", this.maxVersions);
768     map.put("batch", this.batch);
769     map.put("caching", this.caching);
770     map.put("maxResultSize", this.maxResultSize);
771     map.put("cacheBlocks", this.cacheBlocks);
772     map.put("loadColumnFamiliesOnDemand", this.loadColumnFamiliesOnDemand);
773     List<Long> timeRange = new ArrayList<Long>();
774     timeRange.add(this.tr.getMin());
775     timeRange.add(this.tr.getMax());
776     map.put("timeRange", timeRange);
777     int colCount = 0;
778     // iterate through affected families and list out up to maxCols columns
779     for (Map.Entry<byte [], NavigableSet<byte[]>> entry :
780       this.familyMap.entrySet()) {
781       List<String> columns = new ArrayList<String>();
782       familyColumns.put(Bytes.toStringBinary(entry.getKey()), columns);
783       if(entry.getValue() == null) {
784         colCount++;
785         --maxCols;
786         columns.add("ALL");
787       } else {
788         colCount += entry.getValue().size();
789         if (maxCols <= 0) {
790           continue;
791         }
792         for (byte [] column : entry.getValue()) {
793           if (--maxCols <= 0) {
794             continue;
795           }
796           columns.add(Bytes.toStringBinary(column));
797         }
798       }
799     }
800     map.put("totalColumns", colCount);
801     if (this.filter != null) {
802       map.put("filter", this.filter.toString());
803     }
804     // add the id if set
805     if (getId() != null) {
806       map.put("id", getId());
807     }
808     return map;
809   }
810 
811   /**
812    * Enable/disable "raw" mode for this scan.
813    * If "raw" is enabled the scan will return all
814    * delete marker and deleted rows that have not
815    * been collected, yet.
816    * This is mostly useful for Scan on column families
817    * that have KEEP_DELETED_ROWS enabled.
818    * It is an error to specify any column when "raw" is set.
819    * @param raw True/False to enable/disable "raw" mode.
820    */
821   public Scan setRaw(boolean raw) {
822     setAttribute(RAW_ATTR, Bytes.toBytes(raw));
823     return this;
824   }
825 
826   /**
827    * @return True if this Scan is in "raw" mode.
828    */
829   public boolean isRaw() {
830     byte[] attr = getAttribute(RAW_ATTR);
831     return attr == null ? false : Bytes.toBoolean(attr);
832   }
833 
834 
835 
836   /**
837    * Set whether this scan is a small scan
838    * <p>
839    * Small scan should use pread and big scan can use seek + read
840    *
841    * seek + read is fast but can cause two problem (1) resource contention (2)
842    * cause too much network io
843    *
844    * [89-fb] Using pread for non-compaction read request
845    * https://issues.apache.org/jira/browse/HBASE-7266
846    *
847    * On the other hand, if setting it true, we would do
848    * openScanner,next,closeScanner in one RPC call. It means the better
849    * performance for small scan. [HBASE-9488].
850    *
851    * Generally, if the scan range is within one data block(64KB), it could be
852    * considered as a small scan.
853    *
854    * @param small
855    */
856   public Scan setSmall(boolean small) {
857     this.small = small;
858     return this;
859   }
860 
861   /**
862    * Get whether this scan is a small scan
863    * @return true if small scan
864    */
865   public boolean isSmall() {
866     return small;
867   }
868 
869   @Override
870   public Scan setAttribute(String name, byte[] value) {
871     return (Scan) super.setAttribute(name, value);
872   }
873 
874   @Override
875   public Scan setId(String id) {
876     return (Scan) super.setId(id);
877   }
878 
879   @Override
880   public Scan setAuthorizations(Authorizations authorizations) {
881     return (Scan) super.setAuthorizations(authorizations);
882   }
883 
884   @Override
885   public Scan setACL(Map<String, Permission> perms) {
886     return (Scan) super.setACL(perms);
887   }
888 
889   @Override
890   public Scan setACL(String user, Permission perms) {
891     return (Scan) super.setACL(user, perms);
892   }
893 
894   @Override
895   public Scan setConsistency(Consistency consistency) {
896     return (Scan) super.setConsistency(consistency);
897   }
898 
899   @Override
900   public Scan setReplicaId(int Id) {
901     return (Scan) super.setReplicaId(Id);
902   }
903 
904   @Override
905   public Scan setIsolationLevel(IsolationLevel level) {
906     return (Scan) super.setIsolationLevel(level);
907   }
908 
909   /**
910    * Utility that creates a Scan that will do a  small scan in reverse from passed row
911    * looking for next closest row.
912    * @param row
913    * @param family
914    * @return An instance of Scan primed with passed <code>row</code> and <code>family</code> to
915    * scan in reverse for one row only.
916    */
917   static Scan createGetClosestRowOrBeforeReverseScan(byte[] row) {
918     // Below does not work if you add in family; need to add the family qualifier that is highest
919     // possible family qualifier.  Do we have such a notion?  Would have to be magic.
920     Scan scan = new Scan(row);
921     scan.setSmall(true);
922     scan.setReversed(true);
923     scan.setCaching(1);
924     return scan;
925   }
926 
927   /**
928    * Enable collection of {@link ScanMetrics}. For advanced users.
929    * @param enabled Set to true to enable accumulating scan metrics
930    */
931   public Scan setScanMetricsEnabled(final boolean enabled) {
932     setAttribute(Scan.SCAN_ATTRIBUTES_METRICS_ENABLE, Bytes.toBytes(Boolean.valueOf(enabled)));
933     return this;
934   }
935 
936   /**
937    * @return True if collection of scan metrics is enabled. For advanced users.
938    */
939   public boolean isScanMetricsEnabled() {
940     byte[] attr = getAttribute(Scan.SCAN_ATTRIBUTES_METRICS_ENABLE);
941     return attr == null ? false : Bytes.toBoolean(attr);
942   }
943 
944   /**
945    * @return Metrics on this Scan, if metrics were enabled.
946    * @see #setScanMetricsEnabled(boolean)
947    */
948   public ScanMetrics getScanMetrics() {
949     byte [] bytes = getAttribute(Scan.SCAN_ATTRIBUTES_METRICS_DATA);
950     if (bytes == null) return null;
951     return ProtobufUtil.toScanMetrics(bytes);
952   }
953 }