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.DataOutputBuffer;
40  import org.apache.hadoop.io.Writable;
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 method {@link #list()}.
65   * 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[kvs.size()]));
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 key that corresponds to
113    * the row from which this Result was created.
114    * @return row
115    */
116   public byte [] getRow() {
117     if (this.row == null) {
118       if(this.kvs == null) {
119         readFields();
120       }
121       this.row = this.kvs.length == 0? null: this.kvs[0].getRow();
122     }
123     return this.row;
124   }
125 
126   /**
127    * Return the array of KeyValues backing this Result instance.
128    *
129    * The array is sorted from smallest -> largest using the
130    * {@link KeyValue#COMPARATOR}.
131    *
132    * The array only contains what your Get or Scan specifies and no more.
133    * For example if you request column "A" 1 version you will have at most 1
134    * KeyValue in the array. If you request column "A" with 2 version you will
135    * have at most 2 KeyValues, with the first one being the newer timestamp and
136    * the second being the older timestamp (this is the sort order defined by
137    * {@link KeyValue#COMPARATOR}).  If columns don't exist, they won't be
138    * present in the result. Therefore if you ask for 1 version all columns,
139    * it is safe to iterate over this array and expect to see 1 KeyValue for
140    * each column and no more.
141    *
142    * This API is faster than using getFamilyMap() and getMap()
143    *
144    * @return array of KeyValues
145    */
146   public KeyValue[] raw() {
147     if(this.kvs == null) {
148       readFields();
149     }
150     return kvs;
151   }
152 
153   /**
154    * Create a sorted list of the KeyValue's in this result.
155    *
156    * Since HBase 0.20.5 this is equivalent to raw().
157    *
158    * @return The sorted list of KeyValue's.
159    */
160   public List<KeyValue> list() {
161     if(this.kvs == null) {
162       readFields();
163     }
164     return isEmpty()? null: Arrays.asList(raw());
165   }
166 
167   /**
168    * Return the KeyValues for the specific column.  The KeyValues are sorted in
169    * the {@link KeyValue#COMPARATOR} order.  That implies the first entry in
170    * the list is the most recent column.  If the query (Scan or Get) only
171    * requested 1 version the list will contain at most 1 entry.  If the column
172    * did not exist in the result set (either the column does not exist
173    * or the column was not selected in the query) the list will be empty.
174    *
175    * Also see getColumnLatest which returns just a KeyValue
176    *
177    * @param family the family
178    * @param qualifier
179    * @return a list of KeyValues for this column or empty list if the column
180    * did not exist in the result set
181    */
182   public List<KeyValue> getColumn(byte [] family, byte [] qualifier) {
183     List<KeyValue> result = new ArrayList<KeyValue>();
184 
185     KeyValue [] kvs = raw();
186 
187     if (kvs == null || kvs.length == 0) {
188       return result;
189     }
190     int pos = binarySearch(kvs, family, qualifier);
191     if (pos == -1) {
192       return result; // cant find it
193     }
194 
195     for (int i = pos ; i < kvs.length ; i++ ) {
196       KeyValue kv = kvs[i];
197       if (kv.matchingColumn(family,qualifier)) {
198         result.add(kv);
199       } else {
200         break;
201       }
202     }
203 
204     return result;
205   }
206 
207   protected int binarySearch(final KeyValue [] kvs,
208                              final byte [] family,
209                              final byte [] qualifier) {
210     KeyValue searchTerm =
211         KeyValue.createFirstOnRow(kvs[0].getRow(),
212             family, qualifier);
213 
214     // pos === ( -(insertion point) - 1)
215     int pos = Arrays.binarySearch(kvs, searchTerm, KeyValue.COMPARATOR);
216     // never will exact match
217     if (pos < 0) {
218       pos = (pos+1) * -1;
219       // pos is now insertion point
220     }
221     if (pos == kvs.length) {
222       return -1; // doesn't exist
223     }
224     return pos;
225   }
226 
227   /**
228    * The KeyValue for the most recent for a given column. If the column does
229    * not exist in the result set - if it wasn't selected in the query (Get/Scan)
230    * or just does not exist in the row the return value is null.
231    *
232    * @param family
233    * @param qualifier
234    * @return KeyValue for the column or null
235    */
236   public KeyValue getColumnLatest(byte [] family, byte [] qualifier) {
237     KeyValue [] kvs = raw(); // side effect possibly.
238     if (kvs == null || kvs.length == 0) {
239       return null;
240     }
241     int pos = binarySearch(kvs, family, qualifier);
242     if (pos == -1) {
243       return null;
244     }
245     KeyValue kv = kvs[pos];
246     if (kv.matchingColumn(family, qualifier)) {
247       return kv;
248     }
249     return null;
250   }
251 
252   /**
253    * Get the latest version of the specified column.
254    * @param family family name
255    * @param qualifier column qualifier
256    * @return value of latest version of column, null if none found
257    */
258   public byte[] getValue(byte [] family, byte [] qualifier) {
259     KeyValue kv = getColumnLatest(family, qualifier);
260     if (kv == null) {
261       return null;
262     }
263     return kv.getValue();
264   }
265 
266   /**
267    * Checks for existence of the specified column.
268    * @param family family name
269    * @param qualifier column qualifier
270    * @return true if at least one value exists in the result, false if not
271    */
272   public boolean containsColumn(byte [] family, byte [] qualifier) {
273     KeyValue kv = getColumnLatest(family, qualifier);
274     return kv != null;
275   }
276 
277   /**
278    * Map of families to all versions of its qualifiers and values.
279    * <p>
280    * Returns a three level Map of the form:
281    * <code>Map&amp;family,Map&lt;qualifier,Map&lt;timestamp,value>>></code>
282    * <p>
283    * Note: All other map returning methods make use of this map internally.
284    * @return map from families to qualifiers to versions
285    */
286   public NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> getMap() {
287     if(this.familyMap != null) {
288       return this.familyMap;
289     }
290     if(isEmpty()) {
291       return null;
292     }
293     this.familyMap =
294       new TreeMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>>
295       (Bytes.BYTES_COMPARATOR);
296     for(KeyValue kv : this.kvs) {
297       SplitKeyValue splitKV = kv.split();
298       byte [] family = splitKV.getFamily();
299       NavigableMap<byte[], NavigableMap<Long, byte[]>> columnMap =
300         familyMap.get(family);
301       if(columnMap == null) {
302         columnMap = new TreeMap<byte[], NavigableMap<Long, byte[]>>
303           (Bytes.BYTES_COMPARATOR);
304         familyMap.put(family, columnMap);
305       }
306       byte [] qualifier = splitKV.getQualifier();
307       NavigableMap<Long, byte[]> versionMap = columnMap.get(qualifier);
308       if(versionMap == null) {
309         versionMap = new TreeMap<Long, byte[]>(new Comparator<Long>() {
310           public int compare(Long l1, Long l2) {
311             return l2.compareTo(l1);
312           }
313         });
314         columnMap.put(qualifier, versionMap);
315       }
316       Long timestamp = Bytes.toLong(splitKV.getTimestamp());
317       byte [] value = splitKV.getValue();
318       versionMap.put(timestamp, value);
319     }
320     return this.familyMap;
321   }
322 
323   /**
324    * Map of families to their most recent qualifiers and values.
325    * <p>
326    * Returns a two level Map of the form: <code>Map&amp;family,Map&lt;qualifier,value>></code>
327    * <p>
328    * The most recent version of each qualifier will be used.
329    * @return map from families to qualifiers and value
330    */
331   public NavigableMap<byte[], NavigableMap<byte[], byte[]>> getNoVersionMap() {
332     if(this.familyMap == null) {
333       getMap();
334     }
335     if(isEmpty()) {
336       return null;
337     }
338     NavigableMap<byte[], NavigableMap<byte[], byte[]>> returnMap =
339       new TreeMap<byte[], NavigableMap<byte[], byte[]>>(Bytes.BYTES_COMPARATOR);
340     for(Map.Entry<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>>
341       familyEntry : familyMap.entrySet()) {
342       NavigableMap<byte[], byte[]> qualifierMap =
343         new TreeMap<byte[], byte[]>(Bytes.BYTES_COMPARATOR);
344       for(Map.Entry<byte[], NavigableMap<Long, byte[]>> qualifierEntry :
345         familyEntry.getValue().entrySet()) {
346         byte [] value =
347           qualifierEntry.getValue().get(qualifierEntry.getValue().firstKey());
348         qualifierMap.put(qualifierEntry.getKey(), value);
349       }
350       returnMap.put(familyEntry.getKey(), qualifierMap);
351     }
352     return returnMap;
353   }
354 
355   /**
356    * Map of qualifiers to values.
357    * <p>
358    * Returns a Map of the form: <code>Map&lt;qualifier,value></code>
359    * @param family column family to get
360    * @return map of qualifiers to values
361    */
362   public NavigableMap<byte[], byte[]> getFamilyMap(byte [] family) {
363     if(this.familyMap == null) {
364       getMap();
365     }
366     if(isEmpty()) {
367       return null;
368     }
369     NavigableMap<byte[], byte[]> returnMap =
370       new TreeMap<byte[], byte[]>(Bytes.BYTES_COMPARATOR);
371     NavigableMap<byte[], NavigableMap<Long, byte[]>> qualifierMap =
372       familyMap.get(family);
373     if(qualifierMap == null) {
374       return returnMap;
375     }
376     for(Map.Entry<byte[], NavigableMap<Long, byte[]>> entry :
377       qualifierMap.entrySet()) {
378       byte [] value =
379         entry.getValue().get(entry.getValue().firstKey());
380       returnMap.put(entry.getKey(), value);
381     }
382     return returnMap;
383   }
384 
385   /**
386    * Returns the value of the first column in the Result.
387    * @return value of the first column
388    */
389   public byte [] value() {
390     if (isEmpty()) {
391       return null;
392     }
393     return kvs[0].getValue();
394   }
395 
396   /**
397    * Returns the raw binary encoding of this Result.<p>
398    *
399    * Please note, there may be an offset into the underlying byte array of the
400    * returned ImmutableBytesWritable.  Be sure to use both
401    * {@link ImmutableBytesWritable#get()} and {@link ImmutableBytesWritable#getOffset()}
402    * @return pointer to raw binary of Result
403    */
404   public ImmutableBytesWritable getBytes() {
405     if (this.bytes == null && this.kvs != null) {
406       int totalLen = 0;
407       for(KeyValue kv : kvs) {
408         totalLen += kv.getLength() + Bytes.SIZEOF_INT;
409       }
410       DataOutputBuffer out = new DataOutputBuffer(totalLen);
411       try {
412         for(KeyValue kv : kvs) {
413           kv.write(out);
414         }
415         out.close();
416       } catch (IOException e) {
417         throw new RuntimeException("IOException in Result.getBytes()", e);
418       }
419       this.bytes = new ImmutableBytesWritable(out.getData(), 0, out.getLength());
420     }
421     return this.bytes;
422   }
423 
424   /**
425    * Check if the underlying KeyValue [] is empty or not
426    * @return true if empty
427    */
428   public boolean isEmpty() {
429     if(this.kvs == null) {
430       readFields();
431     }
432     return this.kvs == null || this.kvs.length == 0;
433   }
434 
435   /**
436    * @return the size of the underlying KeyValue []
437    */
438   public int size() {
439     if(this.kvs == null) {
440       readFields();
441     }
442     return this.kvs == null? 0: this.kvs.length;
443   }
444 
445   /**
446    * @return String
447    */
448   @Override
449   public String toString() {
450     StringBuilder sb = new StringBuilder();
451     sb.append("keyvalues=");
452     if(isEmpty()) {
453       sb.append("NONE");
454       return sb.toString();
455     }
456     sb.append("{");
457     boolean moreThanOne = false;
458     for(KeyValue kv : this.kvs) {
459       if(moreThanOne) {
460         sb.append(", ");
461       } else {
462         moreThanOne = true;
463       }
464       sb.append(kv.toString());
465     }
466     sb.append("}");
467     return sb.toString();
468   }
469 
470   //Writable
471   public void readFields(final DataInput in)
472   throws IOException {
473     familyMap = null;
474     row = null;
475     kvs = null;
476     int totalBuffer = in.readInt();
477     if(totalBuffer == 0) {
478       bytes = null;
479       return;
480     }
481     byte [] raw = new byte[totalBuffer];
482     readChunked(in, raw, 0, totalBuffer);
483     bytes = new ImmutableBytesWritable(raw, 0, totalBuffer);
484   }
485 
486   private void readChunked(final DataInput in, byte[] dest, int ofs, int len)
487   throws IOException {
488     int maxRead = 8192;
489 
490     for (; ofs < len; ofs += maxRead)
491       in.readFully(dest, ofs, Math.min(len - ofs, maxRead));
492   }
493 
494   //Create KeyValue[] when needed
495   private void readFields() {
496     if (bytes == null) {
497       this.kvs = new KeyValue[0];
498       return;
499     }
500     byte [] buf = bytes.get();
501     int offset = bytes.getOffset();
502     int finalOffset = bytes.getSize() + offset;
503     List<KeyValue> kvs = new ArrayList<KeyValue>();
504     while(offset < finalOffset) {
505       int keyLength = Bytes.toInt(buf, offset);
506       offset += Bytes.SIZEOF_INT;
507       kvs.add(new KeyValue(buf, offset, keyLength));
508       offset += keyLength;
509     }
510     this.kvs = kvs.toArray(new KeyValue[kvs.size()]);
511   }
512 
513   public long getWritableSize() {
514     if (isEmpty())
515       return Bytes.SIZEOF_INT; // int size = 0
516 
517     long size = Bytes.SIZEOF_INT; // totalLen
518 
519     for (KeyValue kv : kvs) {
520       size += kv.getLength();
521       size += Bytes.SIZEOF_INT; // kv.getLength
522     }
523 
524     return size;
525   }
526 
527   public void write(final DataOutput out)
528   throws IOException {
529     if(isEmpty()) {
530       out.writeInt(0);
531     } else {
532       int totalLen = 0;
533       for(KeyValue kv : kvs) {
534         totalLen += kv.getLength() + Bytes.SIZEOF_INT;
535       }
536       out.writeInt(totalLen);
537       for(KeyValue kv : kvs) {
538         out.writeInt(kv.getLength());
539         out.write(kv.getBuffer(), kv.getOffset(), kv.getLength());
540       }
541     }
542   }
543 
544   public static long getWriteArraySize(Result [] results) {
545     long size = Bytes.SIZEOF_BYTE; // RESULT_VERSION
546     if (results == null || results.length == 0) {
547       size += Bytes.SIZEOF_INT;
548       return size;
549     }
550 
551     size += Bytes.SIZEOF_INT; // results.length
552     size += Bytes.SIZEOF_INT; // bufLen
553     for (Result result : results) {
554       size += Bytes.SIZEOF_INT; // either 0 or result.size()
555       if (result == null || result.isEmpty())
556         continue;
557 
558       for (KeyValue kv : result.raw()) {
559         size += Bytes.SIZEOF_INT; // kv.getLength();
560         size += kv.getLength();
561       }
562     }
563 
564     return size;
565   }
566 
567   public static void writeArray(final DataOutput out, Result [] results)
568   throws IOException {
569     // Write version when writing array form.
570     // This assumes that results are sent to the client as Result[], so we
571     // have an opportunity to handle version differences without affecting
572     // efficiency.
573     out.writeByte(RESULT_VERSION);
574     if(results == null || results.length == 0) {
575       out.writeInt(0);
576       return;
577     }
578     out.writeInt(results.length);
579     int bufLen = 0;
580     for(Result result : results) {
581       bufLen += Bytes.SIZEOF_INT;
582       if(result == null || result.isEmpty()) {
583         continue;
584       }
585       for(KeyValue key : result.raw()) {
586         bufLen += key.getLength() + Bytes.SIZEOF_INT;
587       }
588     }
589     out.writeInt(bufLen);
590     for(Result result : results) {
591       if(result == null || result.isEmpty()) {
592         out.writeInt(0);
593         continue;
594       }
595       out.writeInt(result.size());
596       for(KeyValue kv : result.raw()) {
597         out.writeInt(kv.getLength());
598         out.write(kv.getBuffer(), kv.getOffset(), kv.getLength());
599       }
600     }
601   }
602 
603   public static Result [] readArray(final DataInput in)
604   throws IOException {
605     // Read version for array form.
606     // This assumes that results are sent to the client as Result[], so we
607     // have an opportunity to handle version differences without affecting
608     // efficiency.
609     int version = in.readByte();
610     if (version > RESULT_VERSION) {
611       throw new IOException("version not supported");
612     }
613     int numResults = in.readInt();
614     if(numResults == 0) {
615       return new Result[0];
616     }
617     Result [] results = new Result[numResults];
618     int bufSize = in.readInt();
619     byte [] buf = new byte[bufSize];
620     int offset = 0;
621     for(int i=0;i<numResults;i++) {
622       int numKeys = in.readInt();
623       offset += Bytes.SIZEOF_INT;
624       if(numKeys == 0) {
625         results[i] = new Result((ImmutableBytesWritable)null);
626         continue;
627       }
628       int initialOffset = offset;
629       for(int j=0;j<numKeys;j++) {
630         int keyLen = in.readInt();
631         Bytes.putInt(buf, offset, keyLen);
632         offset += Bytes.SIZEOF_INT;
633         in.readFully(buf, offset, keyLen);
634         offset += keyLen;
635       }
636       int totalLength = offset - initialOffset;
637       results[i] = new Result(new ImmutableBytesWritable(buf, initialOffset,
638           totalLength));
639     }
640     return results;
641   }
642 
643   /**
644    * Does a deep comparison of two Results, down to the byte arrays.
645    * @param res1 first result to compare
646    * @param res2 second result to compare
647    * @throws Exception Every difference is throwing an exception
648    */
649   public static void compareResults(Result res1, Result res2)
650       throws Exception {
651     if (res2 == null) {
652       throw new Exception("There wasn't enough rows, we stopped at "
653           + Bytes.toStringBinary(res1.getRow()));
654     }
655     if (res1.size() != res2.size()) {
656       throw new Exception("This row doesn't have the same number of KVs: "
657           + res1.toString() + " compared to " + res2.toString());
658     }
659     KeyValue[] ourKVs = res1.raw();
660     KeyValue[] replicatedKVs = res2.raw();
661     for (int i = 0; i < res1.size(); i++) {
662       if (!ourKVs[i].equals(replicatedKVs[i]) ||
663           !Bytes.equals(ourKVs[i].getValue(), replicatedKVs[i].getValue())) {
664         throw new Exception("This result was different: "
665             + res1.toString() + " compared to " + res2.toString());
666       }
667     }
668   }
669   
670   /**
671    * Copy another Result into this one. Needed for the old Mapred framework
672    * @param other
673    */
674   public void copyFrom(Result other) {
675     this.row = other.row;
676     this.bytes = other.bytes;
677     this.familyMap = other.familyMap;
678     this.kvs = other.kvs;
679   }
680 }