View Javadoc

1   /*
2    * Copyright 2010 The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  
21  package org.apache.hadoop.hbase.client;
22  
23  import com.google.common.collect.Ordering;
24  import org.apache.hadoop.hbase.KeyValue;
25  import org.apache.hadoop.hbase.KeyValue.SplitKeyValue;
26  import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
27  import org.apache.hadoop.hbase.io.WritableWithSize;
28  import org.apache.hadoop.hbase.util.Bytes;
29  import org.apache.hadoop.io.Writable;
30  
31  import java.io.DataInput;
32  import java.io.DataOutput;
33  import java.io.IOException;
34  import java.util.ArrayList;
35  import java.util.Arrays;
36  import java.util.Comparator;
37  import java.util.List;
38  import java.util.Map;
39  import java.util.NavigableMap;
40  import java.util.TreeMap;
41  
42  /**
43   * Single row result of a {@link Get} or {@link Scan} query.<p>
44   *
45   * This class is NOT THREAD SAFE.<p>
46   *
47   * Convenience methods are available that return various {@link Map}
48   * structures and values directly.<p>
49   *
50   * To get a complete mapping of all cells in the Result, which can include
51   * multiple families and multiple versions, use {@link #getMap()}.<p>
52   *
53   * To get a mapping of each family to its columns (qualifiers and values),
54   * including only the latest version of each, use {@link #getNoVersionMap()}.
55   *
56   * To get a mapping of qualifiers to latest values for an individual family use
57   * {@link #getFamilyMap(byte[])}.<p>
58   *
59   * To get the latest value for a specific family and qualifier use {@link #getValue(byte[], byte[])}.
60   *
61   * A Result is backed by an array of {@link KeyValue} objects, each representing
62   * an HBase cell defined by the row, family, qualifier, timestamp, and value.<p>
63   *
64   * The underlying {@link KeyValue} objects can be accessed through the methods
65   * {@link #sorted()} and {@link #list()}.  Each KeyValue can then be accessed
66   * through {@link KeyValue#getRow()}, {@link KeyValue#getFamily()}, {@link KeyValue#getQualifier()},
67   * {@link KeyValue#getTimestamp()}, and {@link KeyValue#getValue()}.
68   */
69  public class Result implements Writable, WritableWithSize {
70    private static final byte RESULT_VERSION = (byte)1;
71  
72    private KeyValue [] kvs = null;
73    private NavigableMap<byte[],
74       NavigableMap<byte[], NavigableMap<Long, byte[]>>> familyMap = null;
75    // We're not using java serialization.  Transient here is just a marker to say
76    // that this is where we cache row if we're ever asked for it.
77    private transient byte [] row = null;
78    private ImmutableBytesWritable bytes = null;
79  
80    /**
81     * Constructor used for Writable.
82     */
83    public Result() {}
84  
85    /**
86     * Instantiate a Result with the specified array of KeyValues.
87     * @param kvs array of KeyValues
88     */
89    public Result(KeyValue [] kvs) {
90      if(kvs != null && kvs.length > 0) {
91        this.kvs = kvs;
92      }
93    }
94  
95    /**
96     * Instantiate a Result with the specified List of KeyValues.
97     * @param kvs List of KeyValues
98     */
99    public Result(List<KeyValue> kvs) {
100     this(kvs.toArray(new KeyValue[0]));
101   }
102 
103   /**
104    * Instantiate a Result from the specified raw binary format.
105    * @param bytes raw binary format of Result
106    */
107   public Result(ImmutableBytesWritable bytes) {
108     this.bytes = bytes;
109   }
110 
111   /**
112    * Method for retrieving the row that this result is for
113    * @return row
114    */
115   public byte [] getRow() {
116     if (this.row == null) {
117       if(this.kvs == null) {
118         readFields();
119       }
120       this.row = this.kvs.length == 0? null: this.kvs[0].getRow();
121     }
122     return this.row;
123   }
124 
125   /**
126    * Return the array of KeyValues backing this Result instance.
127    *
128    * The array is sorted from smallest -> largest using the
129    * {@link KeyValue#COMPARATOR}.
130    *
131    * The array only contains what your Get or Scan specifies and no more.
132    * For example if you request column "A" 1 version you will have at most 1
133    * KeyValue in the array. If you request column "A" with 2 version you will
134    * have at most 2 KeyValues, with the first one being the newer timestamp and
135    * the second being the older timestamp (this is the sort order defined by
136    * {@link KeyValue#COMPARATOR}).  If columns don't exist, they won't be
137    * present in the result. Therefore if you ask for 1 version all columns,
138    * it is safe to iterate over this array and expect to see 1 KeyValue for
139    * each column and no more.
140    *
141    * This API is faster than using getFamilyMap() and getMap()
142    *
143    * @return array of KeyValues
144    */
145   public KeyValue[] raw() {
146     if(this.kvs == null) {
147       readFields();
148     }
149     return kvs;
150   }
151 
152   /**
153    * Create a sorted list of the KeyValue's in this result.
154    *
155    * Since HBase 0.20.5 this is equivalent to raw().
156    *
157    * @return The sorted list of KeyValue's.
158    */
159   public List<KeyValue> list() {
160     if(this.kvs == null) {
161       readFields();
162     }
163     return isEmpty()? null: Arrays.asList(raw());
164   }
165 
166   /**
167    * Returns a sorted array of KeyValues in this Result.
168    * <p>
169    * Since HBase 0.20.5 this is equivalent to {@link #raw}. Use
170    * {@link #raw} instead.
171    *
172    * @return sorted array of KeyValues
173    * @deprecated
174    */
175   public KeyValue[] sorted() {
176     return raw(); // side effect of loading this.kvs
177   }
178 
179   /**
180    * Return the KeyValues for the specific column.  The KeyValues are sorted in
181    * the {@link KeyValue#COMPARATOR} order.  That implies the first entry in
182    * the list is the most recent column.  If the query (Scan or Get) only
183    * requested 1 version the list will contain at most 1 entry.  If the column
184    * did not exist in the result set (either the column does not exist
185    * or the column was not selected in the query) the list will be empty.
186    *
187    * Also see getColumnLatest which returns just a KeyValue
188    *
189    * @param family the family
190    * @param qualifier
191    * @return a list of KeyValues for this column or empty list if the column
192    * did not exist in the result set
193    */
194   public List<KeyValue> getColumn(byte [] family, byte [] qualifier) {
195     List<KeyValue> result = new ArrayList<KeyValue>();
196 
197     KeyValue [] kvs = raw();
198 
199     if (kvs == null || kvs.length == 0) {
200       return result;
201     }
202     int pos = binarySearch(kvs, family, qualifier);
203     if (pos == -1) {
204       return result; // cant find it
205     }
206 
207     for (int i = pos ; i < kvs.length ; i++ ) {
208       KeyValue kv = kvs[i];
209       if (kv.matchingColumn(family,qualifier)) {
210         result.add(kv);
211       } else {
212         break;
213       }
214     }
215 
216     return result;
217   }
218 
219   protected int binarySearch(final KeyValue [] kvs,
220                              final byte [] family,
221                              final byte [] qualifier) {
222     KeyValue searchTerm =
223         KeyValue.createFirstOnRow(kvs[0].getRow(),
224             family, qualifier);
225 
226     // pos === ( -(insertion point) - 1)
227     int pos = Arrays.binarySearch(kvs, searchTerm, KeyValue.COMPARATOR);
228     // never will exact match
229     if (pos < 0) {
230       pos = (pos+1) * -1;
231       // pos is now insertion point
232     }
233     if (pos == kvs.length) {
234       return -1; // doesn't exist
235     }
236     return pos;
237   }
238 
239   /**
240    * The KeyValue for the most recent for a given column. If the column does
241    * not exist in the result set - if it wasn't selected in the query (Get/Scan)
242    * or just does not exist in the row the return value is null.
243    *
244    * @param family
245    * @param qualifier
246    * @return KeyValue for the column or null
247    */
248   public KeyValue getColumnLatest(byte [] family, byte [] qualifier) {
249     KeyValue [] kvs = raw(); // side effect possibly.
250     if (kvs == null || kvs.length == 0) {
251       return null;
252     }
253     int pos = binarySearch(kvs, family, qualifier);
254     if (pos == -1) {
255       return null;
256     }
257     KeyValue kv = kvs[pos];
258     if (kv.matchingColumn(family, qualifier)) {
259       return kv;
260     }
261     return null;
262   }
263 
264   /**
265    * Get the latest version of the specified column.
266    * @param family family name
267    * @param qualifier column qualifier
268    * @return value of latest version of column, null if none found
269    */
270   public byte[] getValue(byte [] family, byte [] qualifier) {
271     KeyValue kv = getColumnLatest(family, qualifier);
272     if (kv == null) {
273       return null;
274     }
275     return kv.getValue();
276   }
277 
278   /**
279    * Checks for existence of the specified column.
280    * @param family family name
281    * @param qualifier column qualifier
282    * @return true if at least one value exists in the result, false if not
283    */
284   public boolean containsColumn(byte [] family, byte [] qualifier) {
285     KeyValue kv = getColumnLatest(family, qualifier);
286     return kv != null;
287   }
288 
289   /**
290    * Map of families to all versions of its qualifiers and values.
291    * <p>
292    * Returns a three level Map of the form:
293    * <code>Map&amp;family,Map&lt;qualifier,Map&lt;timestamp,value>>></code>
294    * <p>
295    * Note: All other map returning methods make use of this map internally.
296    * @return map from families to qualifiers to versions
297    */
298   public NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> getMap() {
299     if(this.familyMap != null) {
300       return this.familyMap;
301     }
302     if(isEmpty()) {
303       return null;
304     }
305     this.familyMap =
306       new TreeMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>>
307       (Bytes.BYTES_COMPARATOR);
308     for(KeyValue kv : this.kvs) {
309       SplitKeyValue splitKV = kv.split();
310       byte [] family = splitKV.getFamily();
311       NavigableMap<byte[], NavigableMap<Long, byte[]>> columnMap =
312         familyMap.get(family);
313       if(columnMap == null) {
314         columnMap = new TreeMap<byte[], NavigableMap<Long, byte[]>>
315           (Bytes.BYTES_COMPARATOR);
316         familyMap.put(family, columnMap);
317       }
318       byte [] qualifier = splitKV.getQualifier();
319       NavigableMap<Long, byte[]> versionMap = columnMap.get(qualifier);
320       if(versionMap == null) {
321         versionMap = new TreeMap<Long, byte[]>(new Comparator<Long>() {
322           public int compare(Long l1, Long l2) {
323             return l2.compareTo(l1);
324           }
325         });
326         columnMap.put(qualifier, versionMap);
327       }
328       Long timestamp = Bytes.toLong(splitKV.getTimestamp());
329       byte [] value = splitKV.getValue();
330       versionMap.put(timestamp, value);
331     }
332     return this.familyMap;
333   }
334 
335   /**
336    * Map of families to their most recent qualifiers and values.
337    * <p>
338    * Returns a two level Map of the form: <code>Map&amp;family,Map&lt;qualifier,value>></code>
339    * <p>
340    * The most recent version of each qualifier will be used.
341    * @return map from families to qualifiers and value
342    */
343   public NavigableMap<byte[], NavigableMap<byte[], byte[]>> getNoVersionMap() {
344     if(this.familyMap == null) {
345       getMap();
346     }
347     if(isEmpty()) {
348       return null;
349     }
350     NavigableMap<byte[], NavigableMap<byte[], byte[]>> returnMap =
351       new TreeMap<byte[], NavigableMap<byte[], byte[]>>(Bytes.BYTES_COMPARATOR);
352     for(Map.Entry<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>>
353       familyEntry : familyMap.entrySet()) {
354       NavigableMap<byte[], byte[]> qualifierMap =
355         new TreeMap<byte[], byte[]>(Bytes.BYTES_COMPARATOR);
356       for(Map.Entry<byte[], NavigableMap<Long, byte[]>> qualifierEntry :
357         familyEntry.getValue().entrySet()) {
358         byte [] value =
359           qualifierEntry.getValue().get(qualifierEntry.getValue().firstKey());
360         qualifierMap.put(qualifierEntry.getKey(), value);
361       }
362       returnMap.put(familyEntry.getKey(), qualifierMap);
363     }
364     return returnMap;
365   }
366 
367   /**
368    * Map of qualifiers to values.
369    * <p>
370    * Returns a Map of the form: <code>Map&lt;qualifier,value></code>
371    * @param family column family to get
372    * @return map of qualifiers to values
373    */
374   public NavigableMap<byte[], byte[]> getFamilyMap(byte [] family) {
375     if(this.familyMap == null) {
376       getMap();
377     }
378     if(isEmpty()) {
379       return null;
380     }
381     NavigableMap<byte[], byte[]> returnMap =
382       new TreeMap<byte[], byte[]>(Bytes.BYTES_COMPARATOR);
383     NavigableMap<byte[], NavigableMap<Long, byte[]>> qualifierMap =
384       familyMap.get(family);
385     if(qualifierMap == null) {
386       return returnMap;
387     }
388     for(Map.Entry<byte[], NavigableMap<Long, byte[]>> entry :
389       qualifierMap.entrySet()) {
390       byte [] value =
391         entry.getValue().get(entry.getValue().firstKey());
392       returnMap.put(entry.getKey(), value);
393     }
394     return returnMap;
395   }
396 
397   private Map.Entry<Long,byte[]> getKeyValue(byte[] family, byte[] qualifier) {
398     if(this.familyMap == null) {
399       getMap();
400     }
401     if(isEmpty()) {
402       return null;
403     }
404     NavigableMap<byte [], NavigableMap<Long, byte[]>> qualifierMap =
405       familyMap.get(family);
406     if(qualifierMap == null) {
407       return null;
408     }
409     NavigableMap<Long, byte[]> versionMap =
410       getVersionMap(qualifierMap, qualifier);
411     if(versionMap == null) {
412       return null;
413     }
414     return versionMap.firstEntry();
415   }
416 
417   private NavigableMap<Long, byte[]> getVersionMap(
418       NavigableMap<byte [], NavigableMap<Long, byte[]>> qualifierMap, byte [] qualifier) {
419     return qualifier != null?
420       qualifierMap.get(qualifier): qualifierMap.get(new byte[0]);
421   }
422 
423   /**
424    * Returns the value of the first column in the Result.
425    * @return value of the first column
426    */
427   public byte [] value() {
428     if (isEmpty()) {
429       return null;
430     }
431     return kvs[0].getValue();
432   }
433 
434   /**
435    * Returns the raw binary encoding of this Result.<p>
436    *
437    * Please note, there may be an offset into the underlying byte array of the
438    * returned ImmutableBytesWritable.  Be sure to use both
439    * {@link ImmutableBytesWritable#get()} and {@link ImmutableBytesWritable#getOffset()}
440    * @return pointer to raw binary of Result
441    */
442   public ImmutableBytesWritable getBytes() {
443     return this.bytes;
444   }
445 
446   /**
447    * Check if the underlying KeyValue [] is empty or not
448    * @return true if empty
449    */
450   public boolean isEmpty() {
451     if(this.kvs == null) {
452       readFields();
453     }
454     return this.kvs == null || this.kvs.length == 0;
455   }
456 
457   /**
458    * @return the size of the underlying KeyValue []
459    */
460   public int size() {
461     if(this.kvs == null) {
462       readFields();
463     }
464     return this.kvs == null? 0: this.kvs.length;
465   }
466 
467   /**
468    * @return String
469    */
470   @Override
471   public String toString() {
472     StringBuilder sb = new StringBuilder();
473     sb.append("keyvalues=");
474     if(isEmpty()) {
475       sb.append("NONE");
476       return sb.toString();
477     }
478     sb.append("{");
479     boolean moreThanOne = false;
480     for(KeyValue kv : this.kvs) {
481       if(moreThanOne) {
482         sb.append(", ");
483       } else {
484         moreThanOne = true;
485       }
486       sb.append(kv.toString());
487     }
488     sb.append("}");
489     return sb.toString();
490   }
491 
492   //Writable
493   public void readFields(final DataInput in)
494   throws IOException {
495     familyMap = null;
496     row = null;
497     kvs = null;
498     int totalBuffer = in.readInt();
499     if(totalBuffer == 0) {
500       bytes = null;
501       return;
502     }
503     byte [] raw = new byte[totalBuffer];
504     in.readFully(raw, 0, totalBuffer);
505     bytes = new ImmutableBytesWritable(raw, 0, totalBuffer);
506   }
507 
508   //Create KeyValue[] when needed
509   private void readFields() {
510     if (bytes == null) {
511       this.kvs = new KeyValue[0];
512       return;
513     }
514     byte [] buf = bytes.get();
515     int offset = bytes.getOffset();
516     int finalOffset = bytes.getSize() + offset;
517     List<KeyValue> kvs = new ArrayList<KeyValue>();
518     while(offset < finalOffset) {
519       int keyLength = Bytes.toInt(buf, offset);
520       offset += Bytes.SIZEOF_INT;
521       kvs.add(new KeyValue(buf, offset, keyLength));
522       offset += keyLength;
523     }
524     this.kvs = kvs.toArray(new KeyValue[kvs.size()]);
525   }
526 
527   public long getWritableSize() {
528     if (isEmpty())
529       return Bytes.SIZEOF_INT; // int size = 0
530 
531     long size = Bytes.SIZEOF_INT; // totalLen
532 
533     for (KeyValue kv : kvs) {
534       size += kv.getLength();
535       size += Bytes.SIZEOF_INT; // kv.getLength
536     }
537 
538     return size;
539   }
540 
541   public void write(final DataOutput out)
542   throws IOException {
543     if(isEmpty()) {
544       out.writeInt(0);
545     } else {
546       int totalLen = 0;
547       for(KeyValue kv : kvs) {
548         totalLen += kv.getLength() + Bytes.SIZEOF_INT;
549       }
550       out.writeInt(totalLen);
551       for(KeyValue kv : kvs) {
552         out.writeInt(kv.getLength());
553         out.write(kv.getBuffer(), kv.getOffset(), kv.getLength());
554       }
555     }
556   }
557 
558   public static long getWriteArraySize(Result [] results) {
559     long size = Bytes.SIZEOF_BYTE; // RESULT_VERSION
560     if (results == null || results.length == 0) {
561       size += Bytes.SIZEOF_INT;
562       return size;
563     }
564 
565     size += Bytes.SIZEOF_INT; // results.length
566     size += Bytes.SIZEOF_INT; // bufLen
567     for (Result result : results) {
568       size += Bytes.SIZEOF_INT; // either 0 or result.size()
569       if (result == null || result.isEmpty())
570         continue;
571 
572       for (KeyValue kv : result.raw()) {
573         size += Bytes.SIZEOF_INT; // kv.getLength();
574         size += kv.getLength();
575       }
576     }
577 
578     return size;
579   }
580 
581   public static void writeArray(final DataOutput out, Result [] results)
582   throws IOException {
583     // Write version when writing array form.
584     // This assumes that results are sent to the client as Result[], so we
585     // have an opportunity to handle version differences without affecting
586     // efficiency.
587     out.writeByte(RESULT_VERSION);
588     if(results == null || results.length == 0) {
589       out.writeInt(0);
590       return;
591     }
592     out.writeInt(results.length);
593     int bufLen = 0;
594     for(Result result : results) {
595       bufLen += Bytes.SIZEOF_INT;
596       if(result == null || result.isEmpty()) {
597         continue;
598       }
599       for(KeyValue key : result.raw()) {
600         bufLen += key.getLength() + Bytes.SIZEOF_INT;
601       }
602     }
603     out.writeInt(bufLen);
604     for(Result result : results) {
605       if(result == null || result.isEmpty()) {
606         out.writeInt(0);
607         continue;
608       }
609       out.writeInt(result.size());
610       for(KeyValue kv : result.raw()) {
611         out.writeInt(kv.getLength());
612         out.write(kv.getBuffer(), kv.getOffset(), kv.getLength());
613       }
614     }
615   }
616 
617   public static Result [] readArray(final DataInput in)
618   throws IOException {
619     // Read version for array form.
620     // This assumes that results are sent to the client as Result[], so we
621     // have an opportunity to handle version differences without affecting
622     // efficiency.
623     int version = in.readByte();
624     if (version > RESULT_VERSION) {
625       throw new IOException("version not supported");
626     }
627     int numResults = in.readInt();
628     if(numResults == 0) {
629       return new Result[0];
630     }
631     Result [] results = new Result[numResults];
632     int bufSize = in.readInt();
633     byte [] buf = new byte[bufSize];
634     int offset = 0;
635     for(int i=0;i<numResults;i++) {
636       int numKeys = in.readInt();
637       offset += Bytes.SIZEOF_INT;
638       if(numKeys == 0) {
639         results[i] = new Result((ImmutableBytesWritable)null);
640         continue;
641       }
642       int initialOffset = offset;
643       for(int j=0;j<numKeys;j++) {
644         int keyLen = in.readInt();
645         Bytes.putInt(buf, offset, keyLen);
646         offset += Bytes.SIZEOF_INT;
647         in.readFully(buf, offset, keyLen);
648         offset += keyLen;
649       }
650       int totalLength = offset - initialOffset;
651       results[i] = new Result(new ImmutableBytesWritable(buf, initialOffset,
652           totalLength));
653     }
654     return results;
655   }
656 
657   /**
658    * Does a deep comparison of two Results, down to the byte arrays.
659    * @param res1 first result to compare
660    * @param res2 second result to compare
661    * @throws Exception Every difference is throwing an exception
662    */
663   public static void compareResults(Result res1, Result res2)
664       throws Exception {
665     if (res2 == null) {
666       throw new Exception("There wasn't enough rows, we stopped at "
667           + Bytes.toStringBinary(res1.getRow()));
668     }
669     if (res1.size() != res2.size()) {
670       throw new Exception("This row doesn't have the same number of KVs: "
671           + res1.toString() + " compared to " + res2.toString());
672     }
673     KeyValue[] ourKVs = res1.sorted();
674     KeyValue[] replicatedKVs = res2.sorted();
675     for (int i = 0; i < res1.size(); i++) {
676       if (!ourKVs[i].equals(replicatedKVs[i]) &&
677           !Bytes.equals(ourKVs[i].getValue(), replicatedKVs[i].getValue())) {
678         throw new Exception("This result was different: "
679             + res1.toString() + " compared to " + res2.toString());
680       }
681     }
682   }
683 }