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 org.apache.hadoop.classification.InterfaceAudience;
23  import org.apache.hadoop.classification.InterfaceStability;
24  import org.apache.hadoop.hbase.Cell;
25  import org.apache.hadoop.hbase.CellScannable;
26  import org.apache.hadoop.hbase.CellScanner;
27  import org.apache.hadoop.hbase.CellUtil;
28  import org.apache.hadoop.hbase.KeyValue;
29  import org.apache.hadoop.hbase.KeyValue.SplitKeyValue;
30  import org.apache.hadoop.hbase.util.Bytes;
31  
32  import java.nio.BufferOverflowException;
33  import java.nio.ByteBuffer;
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 <b>NOT THREAD SAFE</b>.<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 through
66   * {@link KeyValue#getRow()}, {@link KeyValue#getFamily()}, {@link KeyValue#getQualifier()},
67   * {@link KeyValue#getTimestamp()}, and {@link KeyValue#getValue()}.<p>
68   *
69   * If you need to overwrite a Result with another Result instance -- as in the old 'mapred' RecordReader next
70   * invocations -- then create an empty Result with the null constructor and in then use {@link #copyFrom(Result)}
71   */
72  @InterfaceAudience.Public
73  @InterfaceStability.Stable
74  public class Result implements CellScannable {
75    private KeyValue [] kvs;
76    // We're not using java serialization.  Transient here is just a marker to say
77    // that this is where we cache row if we're ever asked for it.
78    private transient byte [] row = null;
79    // Ditto for familyMap.  It can be composed on fly from passed in kvs.
80    private transient NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> familyMap = null;
81  
82    // never use directly
83    private static byte [] buffer = null;
84    private static final int PAD_WIDTH = 128;
85    public static final Result EMPTY_RESULT = new Result();
86  
87    /**
88     * Creates an empty Result w/ no KeyValue payload; returns null if you call {@link #raw()}.
89     * Use this to represent no results if <code>null</code> won't do or in old 'mapred' as oppposed to 'mapreduce' package
90     * MapReduce where you need to overwrite a Result
91     * instance with a {@link #copyFrom(Result)} call.
92     */
93    public Result() {
94      super();
95    }
96  
97    /**
98     * Instantiate a Result with the specified array of KeyValues.
99     * <br><strong>Note:</strong> You must ensure that the keyvalues
100    * are already sorted
101    * @param kvs array of KeyValues
102    */
103   public Result(KeyValue [] kvs) {
104     this.kvs = kvs;
105   }
106 
107   /**
108    * Instantiate a Result with the specified List of KeyValues.
109    * <br><strong>Note:</strong> You must ensure that the keyvalues
110    * are already sorted
111    * @param kvs List of KeyValues
112    */
113   public Result(List<? extends Cell> kvs) {
114     // TODO: Here we presume the passed in Cells are KVs.  One day this won't always be so.
115     this(kvs.toArray(new KeyValue[kvs.size()]));
116   }
117 
118   /**
119    * Method for retrieving the row key that corresponds to
120    * the row from which this Result was created.
121    * @return row
122    */
123   public byte [] getRow() {
124     if (this.row == null) {
125       this.row = this.kvs == null || this.kvs.length == 0? null: this.kvs[0].getRow();
126     }
127     return this.row;
128   }
129 
130   /**
131    * Return the array of KeyValues backing this Result instance.
132    *
133    * The array is sorted from smallest -> largest using the
134    * {@link KeyValue#COMPARATOR}.
135    *
136    * The array only contains what your Get or Scan specifies and no more.
137    * For example if you request column "A" 1 version you will have at most 1
138    * KeyValue in the array. If you request column "A" with 2 version you will
139    * have at most 2 KeyValues, with the first one being the newer timestamp and
140    * the second being the older timestamp (this is the sort order defined by
141    * {@link KeyValue#COMPARATOR}).  If columns don't exist, they won't be
142    * present in the result. Therefore if you ask for 1 version all columns,
143    * it is safe to iterate over this array and expect to see 1 KeyValue for
144    * each column and no more.
145    *
146    * This API is faster than using getFamilyMap() and getMap()
147    *
148    * @return array of KeyValues; can be null if nothing in the result
149    */
150   public KeyValue[] raw() {
151     return kvs;
152   }
153 
154   /**
155    * Create a sorted list of the KeyValue's in this result.
156    *
157    * Since HBase 0.20.5 this is equivalent to raw().
158    *
159    * @return The sorted list of KeyValue's.
160    */
161   public List<KeyValue> list() {
162     return isEmpty()? null: Arrays.asList(raw());
163   }
164 
165   /**
166    * Return the KeyValues for the specific column.  The KeyValues are sorted in
167    * the {@link KeyValue#COMPARATOR} order.  That implies the first entry in
168    * the list is the most recent column.  If the query (Scan or Get) only
169    * requested 1 version the list will contain at most 1 entry.  If the column
170    * did not exist in the result set (either the column does not exist
171    * or the column was not selected in the query) the list will be empty.
172    *
173    * Also see getColumnLatest which returns just a KeyValue
174    *
175    * @param family the family
176    * @param qualifier
177    * @return a list of KeyValues for this column or empty list if the column
178    * did not exist in the result set
179    */
180   public List<KeyValue> getColumn(byte [] family, byte [] qualifier) {
181     List<KeyValue> result = new ArrayList<KeyValue>();
182 
183     KeyValue [] kvs = raw();
184 
185     if (kvs == null || kvs.length == 0) {
186       return result;
187     }
188     int pos = binarySearch(kvs, family, qualifier);
189     if (pos == -1) {
190       return result; // cant find it
191     }
192 
193     for (int i = pos ; i < kvs.length ; i++ ) {
194       KeyValue kv = kvs[i];
195       if (kv.matchingColumn(family,qualifier)) {
196         result.add(kv);
197       } else {
198         break;
199       }
200     }
201 
202     return result;
203   }
204 
205   protected int binarySearch(final KeyValue [] kvs,
206                              final byte [] family,
207                              final byte [] qualifier) {
208     KeyValue searchTerm =
209         KeyValue.createFirstOnRow(kvs[0].getRow(),
210             family, qualifier);
211 
212     // pos === ( -(insertion point) - 1)
213     int pos = Arrays.binarySearch(kvs, searchTerm, KeyValue.COMPARATOR);
214     // never will exact match
215     if (pos < 0) {
216       pos = (pos+1) * -1;
217       // pos is now insertion point
218     }
219     if (pos == kvs.length) {
220       return -1; // doesn't exist
221     }
222     return pos;
223   }
224 
225   /**
226    * Searches for the latest value for the specified column.
227    *
228    * @param kvs the array to search
229    * @param family family name
230    * @param foffset family offset
231    * @param flength family length
232    * @param qualifier column qualifier
233    * @param qoffset qualifier offset
234    * @param qlength qualifier length
235    *
236    * @return the index where the value was found, or -1 otherwise
237    */
238   protected int binarySearch(final KeyValue [] kvs,
239       final byte [] family, final int foffset, final int flength,
240       final byte [] qualifier, final int qoffset, final int qlength) {
241 
242     double keyValueSize = (double)
243         KeyValue.getKeyValueDataStructureSize(kvs[0].getRowLength(), flength, qlength, 0);
244 
245     if (buffer == null || keyValueSize > buffer.length) {
246       // pad to the smallest multiple of the pad width
247       buffer = new byte[(int) Math.ceil(keyValueSize / PAD_WIDTH) * PAD_WIDTH];
248     }
249 
250     KeyValue searchTerm = KeyValue.createFirstOnRow(buffer, 0,
251         kvs[0].getBuffer(), kvs[0].getRowOffset(), kvs[0].getRowLength(),
252         family, foffset, flength,
253         qualifier, qoffset, qlength);
254 
255     // pos === ( -(insertion point) - 1)
256     int pos = Arrays.binarySearch(kvs, searchTerm, KeyValue.COMPARATOR);
257     // never will exact match
258     if (pos < 0) {
259       pos = (pos+1) * -1;
260       // pos is now insertion point
261     }
262     if (pos == kvs.length) {
263       return -1; // doesn't exist
264     }
265     return pos;
266   }
267 
268   /**
269    * The KeyValue for the most recent timestamp for a given column.
270    *
271    * @param family
272    * @param qualifier
273    *
274    * @return the KeyValue for the column, or null if no value exists in the row or none have been
275    * selected in the query (Get/Scan)
276    */
277   public KeyValue getColumnLatest(byte [] family, byte [] qualifier) {
278     KeyValue [] kvs = raw(); // side effect possibly.
279     if (kvs == null || kvs.length == 0) {
280       return null;
281     }
282     int pos = binarySearch(kvs, family, qualifier);
283     if (pos == -1) {
284       return null;
285     }
286     KeyValue kv = kvs[pos];
287     if (kv.matchingColumn(family, qualifier)) {
288       return kv;
289     }
290     return null;
291   }
292 
293   /**
294    * The KeyValue for the most recent timestamp for a given column.
295    *
296    * @param family family name
297    * @param foffset family offset
298    * @param flength family length
299    * @param qualifier column qualifier
300    * @param qoffset qualifier offset
301    * @param qlength qualifier length
302    *
303    * @return the KeyValue for the column, or null if no value exists in the row or none have been
304    * selected in the query (Get/Scan)
305    */
306   public KeyValue getColumnLatest(byte [] family, int foffset, int flength,
307       byte [] qualifier, int qoffset, int qlength) {
308 
309     KeyValue [] kvs = raw(); // side effect possibly.
310     if (kvs == null || kvs.length == 0) {
311       return null;
312     }
313     int pos = binarySearch(kvs, family, foffset, flength, qualifier, qoffset, qlength);
314     if (pos == -1) {
315       return null;
316     }
317     KeyValue kv = kvs[pos];
318     if (kv.matchingColumn(family, foffset, flength, qualifier, qoffset, qlength)) {
319       return kv;
320     }
321     return null;
322   }
323 
324   /**
325    * Get the latest version of the specified column.
326    * @param family family name
327    * @param qualifier column qualifier
328    * @return value of latest version of column, null if none found
329    */
330   public byte[] getValue(byte [] family, byte [] qualifier) {
331     KeyValue kv = getColumnLatest(family, qualifier);
332     if (kv == null) {
333       return null;
334     }
335     return kv.getValue();
336   }
337 
338   /**
339    * Returns the value wrapped in a new <code>ByteBuffer</code>.
340    *
341    * @param family family name
342    * @param qualifier column qualifier
343    *
344    * @return the latest version of the column, or <code>null</code> if none found
345    */
346   public ByteBuffer getValueAsByteBuffer(byte [] family, byte [] qualifier) {
347 
348     KeyValue kv = getColumnLatest(family, 0, family.length, qualifier, 0, qualifier.length);
349 
350     if (kv == null) {
351       return null;
352     }
353     return kv.getValueAsByteBuffer();
354   }
355 
356   /**
357    * Returns the value wrapped in a new <code>ByteBuffer</code>.
358    *
359    * @param family family name
360    * @param foffset family offset
361    * @param flength family length
362    * @param qualifier column qualifier
363    * @param qoffset qualifier offset
364    * @param qlength qualifier length
365    *
366    * @return the latest version of the column, or <code>null</code> if none found
367    */
368   public ByteBuffer getValueAsByteBuffer(byte [] family, int foffset, int flength,
369       byte [] qualifier, int qoffset, int qlength) {
370 
371     KeyValue kv = getColumnLatest(family, foffset, flength, qualifier, qoffset, qlength);
372 
373     if (kv == null) {
374       return null;
375     }
376     return kv.getValueAsByteBuffer();
377   }
378 
379   /**
380    * Loads the latest version of the specified column into the provided <code>ByteBuffer</code>.
381    * <p>
382    * Does not clear or flip the buffer.
383    *
384    * @param family family name
385    * @param qualifier column qualifier
386    * @param dst the buffer where to write the value
387    *
388    * @return <code>true</code> if a value was found, <code>false</code> otherwise
389    *
390    * @throws BufferOverflowException there is insufficient space remaining in the buffer
391    */
392   public boolean loadValue(byte [] family, byte [] qualifier, ByteBuffer dst)
393           throws BufferOverflowException {
394     return loadValue(family, 0, family.length, qualifier, 0, qualifier.length, dst);
395   }
396 
397   /**
398    * Loads the latest version of the specified column into the provided <code>ByteBuffer</code>.
399    * <p>
400    * Does not clear or flip the buffer.
401    *
402    * @param family family name
403    * @param foffset family offset
404    * @param flength family length
405    * @param qualifier column qualifier
406    * @param qoffset qualifier offset
407    * @param qlength qualifier length
408    * @param dst the buffer where to write the value
409    *
410    * @return <code>true</code> if a value was found, <code>false</code> otherwise
411    *
412    * @throws BufferOverflowException there is insufficient space remaining in the buffer
413    */
414   public boolean loadValue(byte [] family, int foffset, int flength,
415       byte [] qualifier, int qoffset, int qlength, ByteBuffer dst)
416           throws BufferOverflowException {
417     KeyValue kv = getColumnLatest(family, foffset, flength, qualifier, qoffset, qlength);
418 
419     if (kv == null) {
420       return false;
421     }
422     kv.loadValue(dst);
423     return true;
424   }
425 
426   /**
427    * Checks if the specified column contains a non-empty value (not a zero-length byte array).
428    *
429    * @param family family name
430    * @param qualifier column qualifier
431    *
432    * @return whether or not a latest value exists and is not empty
433    */
434   public boolean containsNonEmptyColumn(byte [] family, byte [] qualifier) {
435 
436     return containsNonEmptyColumn(family, 0, family.length, qualifier, 0, qualifier.length);
437   }
438 
439   /**
440    * Checks if the specified column contains a non-empty value (not a zero-length byte array).
441    *
442    * @param family family name
443    * @param foffset family offset
444    * @param flength family length
445    * @param qualifier column qualifier
446    * @param qoffset qualifier offset
447    * @param qlength qualifier length
448    *
449    * @return whether or not a latest value exists and is not empty
450    */
451   public boolean containsNonEmptyColumn(byte [] family, int foffset, int flength,
452       byte [] qualifier, int qoffset, int qlength) {
453 
454     KeyValue kv = getColumnLatest(family, foffset, flength, qualifier, qoffset, qlength);
455 
456     return (kv != null) && (kv.getValueLength() > 0);
457   }
458 
459   /**
460    * Checks if the specified column contains an empty value (a zero-length byte array).
461    *
462    * @param family family name
463    * @param qualifier column qualifier
464    *
465    * @return whether or not a latest value exists and is empty
466    */
467   public boolean containsEmptyColumn(byte [] family, byte [] qualifier) {
468 
469     return containsEmptyColumn(family, 0, family.length, qualifier, 0, qualifier.length);
470   }
471 
472   /**
473    * Checks if the specified column contains an empty value (a zero-length byte array).
474    *
475    * @param family family name
476    * @param foffset family offset
477    * @param flength family length
478    * @param qualifier column qualifier
479    * @param qoffset qualifier offset
480    * @param qlength qualifier length
481    *
482    * @return whether or not a latest value exists and is empty
483    */
484   public boolean containsEmptyColumn(byte [] family, int foffset, int flength,
485       byte [] qualifier, int qoffset, int qlength) {
486     KeyValue kv = getColumnLatest(family, foffset, flength, qualifier, qoffset, qlength);
487 
488     return (kv != null) && (kv.getValueLength() == 0);
489   }
490 
491   /**
492    * Checks for existence of a value for the specified column (empty or not).
493    *
494    * @param family family name
495    * @param qualifier column qualifier
496    *
497    * @return true if at least one value exists in the result, false if not
498    */
499   public boolean containsColumn(byte [] family, byte [] qualifier) {
500     KeyValue kv = getColumnLatest(family, qualifier);
501     return kv != null;
502   }
503 
504   /**
505    * Checks for existence of a value for the specified column (empty or not).
506    *
507    * @param family family name
508    * @param foffset family offset
509    * @param flength family length
510    * @param qualifier column qualifier
511    * @param qoffset qualifier offset
512    * @param qlength qualifier length
513    *
514    * @return true if at least one value exists in the result, false if not
515    */
516   public boolean containsColumn(byte [] family, int foffset, int flength,
517       byte [] qualifier, int qoffset, int qlength) {
518 
519     return getColumnLatest(family, foffset, flength, qualifier, qoffset, qlength) != null;
520   }
521 
522   /**
523    * Map of families to all versions of its qualifiers and values.
524    * <p>
525    * Returns a three level Map of the form:
526    * <code>Map&amp;family,Map&lt;qualifier,Map&lt;timestamp,value>>></code>
527    * <p>
528    * Note: All other map returning methods make use of this map internally.
529    * @return map from families to qualifiers to versions
530    */
531   public NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> getMap() {
532     if (this.familyMap != null) {
533       return this.familyMap;
534     }
535     if(isEmpty()) {
536       return null;
537     }
538     this.familyMap = new TreeMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>>(Bytes.BYTES_COMPARATOR);
539     for(KeyValue kv : this.kvs) {
540       SplitKeyValue splitKV = kv.split();
541       byte [] family = splitKV.getFamily();
542       NavigableMap<byte[], NavigableMap<Long, byte[]>> columnMap =
543         familyMap.get(family);
544       if(columnMap == null) {
545         columnMap = new TreeMap<byte[], NavigableMap<Long, byte[]>>
546           (Bytes.BYTES_COMPARATOR);
547         familyMap.put(family, columnMap);
548       }
549       byte [] qualifier = splitKV.getQualifier();
550       NavigableMap<Long, byte[]> versionMap = columnMap.get(qualifier);
551       if(versionMap == null) {
552         versionMap = new TreeMap<Long, byte[]>(new Comparator<Long>() {
553           public int compare(Long l1, Long l2) {
554             return l2.compareTo(l1);
555           }
556         });
557         columnMap.put(qualifier, versionMap);
558       }
559       Long timestamp = Bytes.toLong(splitKV.getTimestamp());
560       byte [] value = splitKV.getValue();
561       versionMap.put(timestamp, value);
562     }
563     return this.familyMap;
564   }
565 
566   /**
567    * Map of families to their most recent qualifiers and values.
568    * <p>
569    * Returns a two level Map of the form: <code>Map&amp;family,Map&lt;qualifier,value>></code>
570    * <p>
571    * The most recent version of each qualifier will be used.
572    * @return map from families to qualifiers and value
573    */
574   public NavigableMap<byte[], NavigableMap<byte[], byte[]>> getNoVersionMap() {
575     if(this.familyMap == null) {
576       getMap();
577     }
578     if(isEmpty()) {
579       return null;
580     }
581     NavigableMap<byte[], NavigableMap<byte[], byte[]>> returnMap =
582       new TreeMap<byte[], NavigableMap<byte[], byte[]>>(Bytes.BYTES_COMPARATOR);
583     for(Map.Entry<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>>
584       familyEntry : familyMap.entrySet()) {
585       NavigableMap<byte[], byte[]> qualifierMap =
586         new TreeMap<byte[], byte[]>(Bytes.BYTES_COMPARATOR);
587       for(Map.Entry<byte[], NavigableMap<Long, byte[]>> qualifierEntry :
588         familyEntry.getValue().entrySet()) {
589         byte [] value =
590           qualifierEntry.getValue().get(qualifierEntry.getValue().firstKey());
591         qualifierMap.put(qualifierEntry.getKey(), value);
592       }
593       returnMap.put(familyEntry.getKey(), qualifierMap);
594     }
595     return returnMap;
596   }
597 
598   /**
599    * Map of qualifiers to values.
600    * <p>
601    * Returns a Map of the form: <code>Map&lt;qualifier,value></code>
602    * @param family column family to get
603    * @return map of qualifiers to values
604    */
605   public NavigableMap<byte[], byte[]> getFamilyMap(byte [] family) {
606     if(this.familyMap == null) {
607       getMap();
608     }
609     if(isEmpty()) {
610       return null;
611     }
612     NavigableMap<byte[], byte[]> returnMap =
613       new TreeMap<byte[], byte[]>(Bytes.BYTES_COMPARATOR);
614     NavigableMap<byte[], NavigableMap<Long, byte[]>> qualifierMap =
615       familyMap.get(family);
616     if(qualifierMap == null) {
617       return returnMap;
618     }
619     for(Map.Entry<byte[], NavigableMap<Long, byte[]>> entry :
620       qualifierMap.entrySet()) {
621       byte [] value =
622         entry.getValue().get(entry.getValue().firstKey());
623       returnMap.put(entry.getKey(), value);
624     }
625     return returnMap;
626   }
627 
628   /**
629    * Returns the value of the first column in the Result.
630    * @return value of the first column
631    */
632   public byte [] value() {
633     if (isEmpty()) {
634       return null;
635     }
636     return kvs[0].getValue();
637   }
638 
639   /**
640    * Check if the underlying KeyValue [] is empty or not
641    * @return true if empty
642    */
643   public boolean isEmpty() {
644     return this.kvs == null || this.kvs.length == 0;
645   }
646 
647   /**
648    * @return the size of the underlying KeyValue []
649    */
650   public int size() {
651     return this.kvs == null? 0: this.kvs.length;
652   }
653 
654   /**
655    * @return String
656    */
657   @Override
658   public String toString() {
659     StringBuilder sb = new StringBuilder();
660     sb.append("keyvalues=");
661     if(isEmpty()) {
662       sb.append("NONE");
663       return sb.toString();
664     }
665     sb.append("{");
666     boolean moreThanOne = false;
667     for(KeyValue kv : this.kvs) {
668       if(moreThanOne) {
669         sb.append(", ");
670       } else {
671         moreThanOne = true;
672       }
673       sb.append(kv.toString());
674     }
675     sb.append("}");
676     return sb.toString();
677   }
678 
679   /**
680    * Does a deep comparison of two Results, down to the byte arrays.
681    * @param res1 first result to compare
682    * @param res2 second result to compare
683    * @throws Exception Every difference is throwing an exception
684    */
685   public static void compareResults(Result res1, Result res2)
686       throws Exception {
687     if (res2 == null) {
688       throw new Exception("There wasn't enough rows, we stopped at "
689           + Bytes.toStringBinary(res1.getRow()));
690     }
691     if (res1.size() != res2.size()) {
692       throw new Exception("This row doesn't have the same number of KVs: "
693           + res1.toString() + " compared to " + res2.toString());
694     }
695     KeyValue[] ourKVs = res1.raw();
696     KeyValue[] replicatedKVs = res2.raw();
697     for (int i = 0; i < res1.size(); i++) {
698       if (!ourKVs[i].equals(replicatedKVs[i]) ||
699           !Bytes.equals(ourKVs[i].getValue(), replicatedKVs[i].getValue())) {
700         throw new Exception("This result was different: "
701             + res1.toString() + " compared to " + res2.toString());
702       }
703     }
704   }
705 
706   /**
707    * Copy another Result into this one. Needed for the old Mapred framework
708    * @param other
709    */
710   public void copyFrom(Result other) {
711     this.row = null;
712     this.familyMap = null;
713     this.kvs = other.kvs;
714   }
715 
716   @Override
717   public CellScanner cellScanner() {
718     return CellUtil.createCellScanner(this.kvs);
719   }
720 }