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