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 org.apache.hadoop.hbase.KeyValue;
24  import org.apache.hadoop.hbase.KeyValue.SplitKeyValue;
25  import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
26  import org.apache.hadoop.hbase.util.Bytes;
27  import org.apache.hadoop.io.Writable;
28  
29  import java.io.DataInput;
30  import java.io.DataOutput;
31  import java.io.IOException;
32  import java.util.ArrayList;
33  import java.util.Arrays;
34  import java.util.Comparator;
35  import java.util.List;
36  import java.util.Map;
37  import java.util.NavigableMap;
38  import java.util.TreeMap;
39  
40  /**
41   * Single row result of a {@link Get} or {@link Scan} query.<p>
42   *
43   * Convenience methods are available that return various {@link Map}
44   * structures and values directly.<p>
45   *
46   * To get a complete mapping of all cells in the Result, which can include
47   * multiple families and multiple versions, use {@link #getMap()}.<p>
48   *
49   * To get a mapping of each family to its columns (qualifiers and values),
50   * including only the latest version of each, use {@link #getNoVersionMap()}.
51   *
52   * To get a mapping of qualifiers to latest values for an individual family use
53   * {@link #getFamilyMap(byte[])}.<p>
54   *
55   * To get the latest value for a specific family and qualifier use {@link #getValue(byte[], byte[])}.
56   *
57   * A Result is backed by an array of {@link KeyValue} objects, each representing
58   * an HBase cell defined by the row, family, qualifier, timestamp, and value.<p>
59   *
60   * The underlying {@link KeyValue} objects can be accessed through the methods
61   * {@link #sorted()} and {@link #list()}.  Each KeyValue can then be accessed
62   * through {@link KeyValue#getRow()}, {@link KeyValue#getFamily()}, {@link KeyValue#getQualifier()},
63   * {@link KeyValue#getTimestamp()}, and {@link KeyValue#getValue()}.
64   */
65  public class Result implements Writable {
66    private static final byte RESULT_VERSION = (byte)1;
67  
68    private KeyValue [] kvs = null;
69    private NavigableMap<byte[],
70       NavigableMap<byte[], NavigableMap<Long, byte[]>>> familyMap = null;
71    // We're not using java serialization.  Transient here is just a marker to say
72    // that this is where we cache row if we're ever asked for it.
73    private transient byte [] row = null;
74    private ImmutableBytesWritable bytes = null;
75  
76    /**
77     * Constructor used for Writable.
78     */
79    public Result() {}
80  
81    /**
82     * Instantiate a Result with the specified array of KeyValues.
83     * @param kvs array of KeyValues
84     */
85    public Result(KeyValue [] kvs) {
86      if(kvs != null && kvs.length > 0) {
87        this.kvs = kvs;
88      }
89    }
90  
91    /**
92     * Instantiate a Result with the specified List of KeyValues.
93     * @param kvs List of KeyValues
94     */
95    public Result(List<KeyValue> kvs) {
96      this(kvs.toArray(new KeyValue[0]));
97    }
98  
99    /**
100    * Instantiate a Result from the specified raw binary format.
101    * @param bytes raw binary format of Result
102    */
103   public Result(ImmutableBytesWritable bytes) {
104     this.bytes = bytes;
105   }
106 
107   /**
108    * Method for retrieving the row that this result is for
109    * @return row
110    */
111   public synchronized byte [] getRow() {
112     if (this.row == null) {
113       if(this.kvs == null) {
114         readFields();
115       }
116       this.row = this.kvs.length == 0? null: this.kvs[0].getRow();
117     }
118     return this.row;
119   }
120 
121   /**
122    * Return the unsorted array of KeyValues backing this Result instance.
123    * @return unsorted array of KeyValues
124    */
125   public KeyValue[] raw() {
126     if(this.kvs == null) {
127       readFields();
128     }
129     return kvs;
130   }
131 
132   /**
133    * Create a sorted list of the KeyValue's in this result.
134    *
135    * @return The sorted list of KeyValue's.
136    */
137   public List<KeyValue> list() {
138     if(this.kvs == null) {
139       readFields();
140     }
141     return isEmpty()? null: Arrays.asList(sorted());
142   }
143 
144   /**
145    * Returns a sorted array of KeyValues in this Result.
146    * <p>
147    * Note: Sorting is done in place, so the backing array will be sorted
148    * after calling this method.
149    * @return sorted array of KeyValues
150    */
151   public KeyValue[] sorted() {
152     if (isEmpty()) {
153       return null;
154     }
155     Arrays.sort(kvs, KeyValue.COMPARATOR);
156     return kvs;
157   }
158 
159   /**
160    * Map of families to all versions of its qualifiers and values.
161    * <p>
162    * Returns a three level Map of the form:
163    * <code>Map<family,Map&lt;qualifier,Map&lt;timestamp,value>>></code>
164    * <p>
165    * Note: All other map returning methods make use of this map internally.
166    * @return map from families to qualifiers to versions
167    */
168   public NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> getMap() {
169     if(this.familyMap != null) {
170       return this.familyMap;
171     }
172     if(isEmpty()) {
173       return null;
174     }
175     this.familyMap =
176       new TreeMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>>
177       (Bytes.BYTES_COMPARATOR);
178     for(KeyValue kv : this.kvs) {
179       SplitKeyValue splitKV = kv.split();
180       byte [] family = splitKV.getFamily();
181       NavigableMap<byte[], NavigableMap<Long, byte[]>> columnMap =
182         familyMap.get(family);
183       if(columnMap == null) {
184         columnMap = new TreeMap<byte[], NavigableMap<Long, byte[]>>
185           (Bytes.BYTES_COMPARATOR);
186         familyMap.put(family, columnMap);
187       }
188       byte [] qualifier = splitKV.getQualifier();
189       NavigableMap<Long, byte[]> versionMap = columnMap.get(qualifier);
190       if(versionMap == null) {
191         versionMap = new TreeMap<Long, byte[]>(new Comparator<Long>() {
192           public int compare(Long l1, Long l2) {
193             return l2.compareTo(l1);
194           }
195         });
196         columnMap.put(qualifier, versionMap);
197       }
198       Long timestamp = Bytes.toLong(splitKV.getTimestamp());
199       byte [] value = splitKV.getValue();
200       versionMap.put(timestamp, value);
201     }
202     return this.familyMap;
203   }
204 
205   /**
206    * Map of families to their most recent qualifiers and values.
207    * <p>
208    * Returns a two level Map of the form: <code>Map<family,Map&lt;qualifier,value>></code>
209    * <p>
210    * The most recent version of each qualifier will be used.
211    * @return map from families to qualifiers and value
212    */
213   public NavigableMap<byte[], NavigableMap<byte[], byte[]>> getNoVersionMap() {
214     if(this.familyMap == null) {
215       getMap();
216     }
217     if(isEmpty()) {
218       return null;
219     }
220     NavigableMap<byte[], NavigableMap<byte[], byte[]>> returnMap =
221       new TreeMap<byte[], NavigableMap<byte[], byte[]>>(Bytes.BYTES_COMPARATOR);
222     for(Map.Entry<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>>
223       familyEntry : familyMap.entrySet()) {
224       NavigableMap<byte[], byte[]> qualifierMap =
225         new TreeMap<byte[], byte[]>(Bytes.BYTES_COMPARATOR);
226       for(Map.Entry<byte[], NavigableMap<Long, byte[]>> qualifierEntry :
227         familyEntry.getValue().entrySet()) {
228         byte [] value =
229           qualifierEntry.getValue().get(qualifierEntry.getValue().firstKey());
230         qualifierMap.put(qualifierEntry.getKey(), value);
231       }
232       returnMap.put(familyEntry.getKey(), qualifierMap);
233     }
234     return returnMap;
235   }
236 
237   /**
238    * Map of qualifiers to values.
239    * <p>
240    * Returns a Map of the form: <code>Map&lt;qualifier,value></code>
241    * @param family column family to get
242    * @return map of qualifiers to values
243    */
244   public NavigableMap<byte[], byte[]> getFamilyMap(byte [] family) {
245     if(this.familyMap == null) {
246       getMap();
247     }
248     if(isEmpty()) {
249       return null;
250     }
251     NavigableMap<byte[], byte[]> returnMap =
252       new TreeMap<byte[], byte[]>(Bytes.BYTES_COMPARATOR);
253     NavigableMap<byte[], NavigableMap<Long, byte[]>> qualifierMap =
254       familyMap.get(family);
255     if(qualifierMap == null) {
256       return returnMap;
257     }
258     for(Map.Entry<byte[], NavigableMap<Long, byte[]>> entry :
259       qualifierMap.entrySet()) {
260       byte [] value =
261         entry.getValue().get(entry.getValue().firstKey());
262       returnMap.put(entry.getKey(), value);
263     }
264     return returnMap;
265   }
266 
267   /**
268    * Get the latest version of the specified column.
269    * @param family family name
270    * @param qualifier column qualifier
271    * @return value of latest version of column, null if none found
272    */
273   public byte [] getValue(byte [] family, byte [] qualifier) {
274     Map.Entry<Long,byte[]> entry = getKeyValue(family, qualifier);
275     return entry == null? null: entry.getValue();
276   }
277 
278   private Map.Entry<Long,byte[]> getKeyValue(byte[] family, byte[] qualifier) {
279     if(this.familyMap == null) {
280       getMap();
281     }
282     if(isEmpty()) {
283       return null;
284     }
285     NavigableMap<byte [], NavigableMap<Long, byte[]>> qualifierMap =
286       familyMap.get(family);
287     if(qualifierMap == null) {
288       return null;
289     }
290     NavigableMap<Long, byte[]> versionMap =
291       getVersionMap(qualifierMap, qualifier);
292     if(versionMap == null) {
293       return null;
294     }
295     return versionMap.firstEntry();
296   }
297 
298   private NavigableMap<Long, byte[]> getVersionMap(
299       NavigableMap<byte [], NavigableMap<Long, byte[]>> qualifierMap, byte [] qualifier) {
300     return qualifier != null?
301       qualifierMap.get(qualifier): qualifierMap.get(new byte[0]);
302   }
303 
304   /**
305    * Checks for existence of the specified column.
306    * @param family family name
307    * @param qualifier column qualifier
308    * @return true if at least one value exists in the result, false if not
309    */
310   public boolean containsColumn(byte [] family, byte [] qualifier) {
311     if(this.familyMap == null) {
312       getMap();
313     }
314     if(isEmpty()) {
315       return false;
316     }
317     NavigableMap<byte [], NavigableMap<Long, byte[]>> qualifierMap =
318       familyMap.get(family);
319     if(qualifierMap == null) {
320       return false;
321     }
322     NavigableMap<Long, byte[]> versionMap = getVersionMap(qualifierMap, qualifier);
323     return versionMap != null;
324   }
325 
326   /**
327    * Returns the value of the first column in the Result.
328    * @return value of the first column
329    */
330   public byte [] value() {
331     if (isEmpty()) {
332       return null;
333     }
334     return kvs[0].getValue();
335   }
336 
337   /**
338    * Returns the raw binary encoding of this Result.<p>
339    *
340    * Please note, there may be an offset into the underlying byte array of the
341    * returned ImmutableBytesWritable.  Be sure to use both
342    * {@link ImmutableBytesWritable#get()} and {@link ImmutableBytesWritable#getOffset()}
343    * @return pointer to raw binary of Result
344    */
345   public ImmutableBytesWritable getBytes() {
346     return this.bytes;
347   }
348 
349   /**
350    * Check if the underlying KeyValue [] is empty or not
351    * @return true if empty
352    */
353   public boolean isEmpty() {
354     if(this.kvs == null) {
355       readFields();
356     }
357     return this.kvs == null || this.kvs.length == 0;
358   }
359 
360   /**
361    * @return the size of the underlying KeyValue []
362    */
363   public int size() {
364     if(this.kvs == null) {
365       readFields();
366     }
367     return this.kvs == null? 0: this.kvs.length;
368   }
369 
370   /**
371    * @return String
372    */
373   @Override
374   public String toString() {
375     StringBuilder sb = new StringBuilder();
376     sb.append("keyvalues=");
377     if(isEmpty()) {
378       sb.append("NONE");
379       return sb.toString();
380     }
381     sb.append("{");
382     boolean moreThanOne = false;
383     for(KeyValue kv : this.kvs) {
384       if(moreThanOne) {
385         sb.append(", ");
386       } else {
387         moreThanOne = true;
388       }
389       sb.append(kv.toString());
390     }
391     sb.append("}");
392     return sb.toString();
393   }
394 
395   //Writable
396   public void readFields(final DataInput in)
397   throws IOException {
398     familyMap = null;
399     row = null;
400     kvs = null;
401     int totalBuffer = in.readInt();
402     if(totalBuffer == 0) {
403       bytes = null;
404       return;
405     }
406     byte [] raw = new byte[totalBuffer];
407     in.readFully(raw, 0, totalBuffer);
408     bytes = new ImmutableBytesWritable(raw, 0, totalBuffer);
409   }
410 
411   //Create KeyValue[] when needed
412   private void readFields() {
413     if (bytes == null) {
414       this.kvs = new KeyValue[0];
415       return;
416     }
417     byte [] buf = bytes.get();
418     int offset = bytes.getOffset();
419     int finalOffset = bytes.getSize() + offset;
420     List<KeyValue> kvs = new ArrayList<KeyValue>();
421     while(offset < finalOffset) {
422       int keyLength = Bytes.toInt(buf, offset);
423       offset += Bytes.SIZEOF_INT;
424       kvs.add(new KeyValue(buf, offset, keyLength));
425       offset += keyLength;
426     }
427     this.kvs = kvs.toArray(new KeyValue[kvs.size()]);
428   }
429 
430   public void write(final DataOutput out)
431   throws IOException {
432     if(isEmpty()) {
433       out.writeInt(0);
434     } else {
435       int totalLen = 0;
436       for(KeyValue kv : kvs) {
437         totalLen += kv.getLength() + Bytes.SIZEOF_INT;
438       }
439       out.writeInt(totalLen);
440       for(KeyValue kv : kvs) {
441         out.writeInt(kv.getLength());
442         out.write(kv.getBuffer(), kv.getOffset(), kv.getLength());
443       }
444     }
445   }
446 
447   public static void writeArray(final DataOutput out, Result [] results)
448   throws IOException {
449     // Write version when writing array form.
450     // This assumes that results are sent to the client as Result[], so we
451     // have an opportunity to handle version differences without affecting
452     // efficiency.
453     out.writeByte(RESULT_VERSION);
454     if(results == null || results.length == 0) {
455       out.writeInt(0);
456       return;
457     }
458     out.writeInt(results.length);
459     int bufLen = 0;
460     for(Result result : results) {
461       bufLen += Bytes.SIZEOF_INT;
462       if(result == null || result.isEmpty()) {
463         continue;
464       }
465       for(KeyValue key : result.raw()) {
466         bufLen += key.getLength() + Bytes.SIZEOF_INT;
467       }
468     }
469     out.writeInt(bufLen);
470     for(Result result : results) {
471       if(result == null || result.isEmpty()) {
472         out.writeInt(0);
473         continue;
474       }
475       out.writeInt(result.size());
476       for(KeyValue kv : result.raw()) {
477         out.writeInt(kv.getLength());
478         out.write(kv.getBuffer(), kv.getOffset(), kv.getLength());
479       }
480     }
481   }
482 
483   public static Result [] readArray(final DataInput in)
484   throws IOException {
485     // Read version for array form.
486     // This assumes that results are sent to the client as Result[], so we
487     // have an opportunity to handle version differences without affecting
488     // efficiency.
489     int version = in.readByte();
490     if (version > RESULT_VERSION) {
491       throw new IOException("version not supported");
492     }
493     int numResults = in.readInt();
494     if(numResults == 0) {
495       return new Result[0];
496     }
497     Result [] results = new Result[numResults];
498     int bufSize = in.readInt();
499     byte [] buf = new byte[bufSize];
500     int offset = 0;
501     for(int i=0;i<numResults;i++) {
502       int numKeys = in.readInt();
503       offset += Bytes.SIZEOF_INT;
504       if(numKeys == 0) {
505         results[i] = new Result((ImmutableBytesWritable)null);
506         continue;
507       }
508       int initialOffset = offset;
509       for(int j=0;j<numKeys;j++) {
510         int keyLen = in.readInt();
511         Bytes.putInt(buf, offset, keyLen);
512         offset += Bytes.SIZEOF_INT;
513         in.readFully(buf, offset, keyLen);
514         offset += keyLen;
515       }
516       int totalLength = offset - initialOffset;
517       results[i] = new Result(new ImmutableBytesWritable(buf, initialOffset,
518           totalLength));
519     }
520     return results;
521   }
522 }