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.HConstants;
24  import org.apache.hadoop.hbase.KeyValue;
25  import org.apache.hadoop.hbase.io.HeapSize;
26  import org.apache.hadoop.hbase.util.Bytes;
27  import org.apache.hadoop.hbase.util.ClassSize;
28  import org.apache.hadoop.io.Writable;
29  
30  import java.io.DataInput;
31  import java.io.DataOutput;
32  import java.io.IOException;
33  import java.util.ArrayList;
34  import java.util.Arrays;
35  import java.util.List;
36  import java.util.Map;
37  import java.util.TreeMap;
38  
39  
40  /**
41   * Used to perform Put operations for a single row.
42   * <p>
43   * To perform a Put, instantiate a Put object with the row to insert to and
44   * for each column to be inserted, execute {@link #add(byte[], byte[], byte[]) add} or
45   * {@link #add(byte[], byte[], long, byte[]) add} if setting the timestamp.
46   */
47  public class Put implements HeapSize, Writable, Row, Comparable<Row> {
48    private static final byte PUT_VERSION = (byte)1;
49  
50    private byte [] row = null;
51    private long timestamp = HConstants.LATEST_TIMESTAMP;
52    private long lockId = -1L;
53    private boolean writeToWAL = true;
54  
55    private Map<byte [], List<KeyValue>> familyMap =
56      new TreeMap<byte [], List<KeyValue>>(Bytes.BYTES_COMPARATOR);
57  
58    private static final long OVERHEAD = ClassSize.align(
59        ClassSize.OBJECT + ClassSize.REFERENCE +
60        2 * Bytes.SIZEOF_LONG + Bytes.SIZEOF_BOOLEAN +
61        ClassSize.REFERENCE + ClassSize.TREEMAP);
62  
63    /** Constructor for Writable. DO NOT USE */
64    public Put() {}
65  
66    /**
67     * Create a Put operation for the specified row.
68     * @param row row key
69     */
70    public Put(byte [] row) {
71      this(row, null);
72    }
73  
74    /**
75     * Create a Put operation for the specified row, using an existing row lock.
76     * @param row row key
77     * @param rowLock previously acquired row lock, or null
78     */
79    public Put(byte [] row, RowLock rowLock) {
80        this(row, HConstants.LATEST_TIMESTAMP, rowLock);
81    }
82  
83    /**
84     * Create a Put operation for the specified row, using a given timestamp.
85     *
86     * @param row row key
87     * @param ts timestamp
88     */
89    public Put(byte[] row, long ts) {
90      this(row, ts, null);
91    }
92  
93    /**
94     * Create a Put operation for the specified row, using a given timestamp, and an existing row lock.
95     * @param row row key
96     * @param ts timestamp
97     * @param rowLock previously acquired row lock, or null
98     */
99    public Put(byte [] row, long ts, RowLock rowLock) {
100     if(row == null || row.length > HConstants.MAX_ROW_LENGTH) {
101       throw new IllegalArgumentException("Row key is invalid");
102     }
103     this.row = Arrays.copyOf(row, row.length);
104     this.timestamp = ts;
105     if(rowLock != null) {
106       this.lockId = rowLock.getLockId();
107     }
108   }
109 
110   /**
111    * Copy constructor.  Creates a Put operation cloned from the specified Put.
112    * @param putToCopy put to copy
113    */
114   public Put(Put putToCopy) {
115     this(putToCopy.getRow(), putToCopy.timestamp, putToCopy.getRowLock());
116     this.familyMap =
117       new TreeMap<byte [], List<KeyValue>>(Bytes.BYTES_COMPARATOR);
118     for(Map.Entry<byte [], List<KeyValue>> entry :
119       putToCopy.getFamilyMap().entrySet()) {
120       this.familyMap.put(entry.getKey(), entry.getValue());
121     }
122     this.writeToWAL = putToCopy.writeToWAL;
123   }
124 
125   /**
126    * Add the specified column and value to this Put operation.
127    * @param family family name
128    * @param qualifier column qualifier
129    * @param value column value
130    * @return this
131    */
132   public Put add(byte [] family, byte [] qualifier, byte [] value) {
133     return add(family, qualifier, this.timestamp, value);
134   }
135 
136   /**
137    * Add the specified column and value, with the specified timestamp as
138    * its version to this Put operation.
139    * @param family family name
140    * @param qualifier column qualifier
141    * @param ts version timestamp
142    * @param value column value
143    * @return this
144    */
145   public Put add(byte [] family, byte [] qualifier, long ts, byte [] value) {
146     List<KeyValue> list = getKeyValueList(family);
147     KeyValue kv = createPutKeyValue(family, qualifier, ts, value);
148     list.add(kv);
149     familyMap.put(kv.getFamily(), list);
150     return this;
151   }
152 
153   /**
154    * Add the specified KeyValue to this Put operation.  Operation assumes that
155    * the passed KeyValue is immutable and its backing array will not be modified
156    * for the duration of this Put.
157    * @param kv individual KeyValue
158    * @return this
159    * @throws java.io.IOException e
160    */
161   public Put add(KeyValue kv) throws IOException{
162     byte [] family = kv.getFamily();
163     List<KeyValue> list = getKeyValueList(family);
164     //Checking that the row of the kv is the same as the put
165     int res = Bytes.compareTo(this.row, 0, row.length,
166         kv.getBuffer(), kv.getRowOffset(), kv.getRowLength());
167     if(res != 0) {
168       throw new IOException("The row in the recently added KeyValue " +
169           Bytes.toStringBinary(kv.getBuffer(), kv.getRowOffset(),
170         kv.getRowLength()) + " doesn't match the original one " +
171         Bytes.toStringBinary(this.row));
172     }
173     list.add(kv);
174     familyMap.put(family, list);
175     return this;
176   }
177 
178   /*
179    * Create a KeyValue with this objects row key and the Put identifier.
180    *
181    * @return a KeyValue with this objects row key and the Put identifier.
182    */
183   private KeyValue createPutKeyValue(byte[] family, byte[] qualifier, long ts,
184       byte[] value) {
185   return  new KeyValue(this.row, family, qualifier, ts, KeyValue.Type.Put,
186       value);
187   }
188 
189   /**
190    * A convenience method to determine if this object's familyMap contains
191    * a value assigned to the given family & qualifier.
192    * Both given arguments must match the KeyValue object to return true.
193    *
194    * @param family column family
195    * @param qualifier column qualifier
196    * @return returns true if the given family and qualifier already has an
197    * existing KeyValue object in the family map.
198    */
199   public boolean has(byte [] family, byte [] qualifier) {
200   return has(family, qualifier, this.timestamp, new byte[0], true, true);
201   }
202 
203   /**
204    * A convenience method to determine if this object's familyMap contains
205    * a value assigned to the given family, qualifier and timestamp.
206    * All 3 given arguments must match the KeyValue object to return true.
207    *
208    * @param family column family
209    * @param qualifier column qualifier
210    * @param ts timestamp
211    * @return returns true if the given family, qualifier and timestamp already has an
212    * existing KeyValue object in the family map.
213    */
214   public boolean has(byte [] family, byte [] qualifier, long ts) {
215   return has(family, qualifier, ts, new byte[0], false, true);
216   }
217 
218   /**
219    * A convenience method to determine if this object's familyMap contains
220    * a value assigned to the given family, qualifier and timestamp.
221    * All 3 given arguments must match the KeyValue object to return true.
222    *
223    * @param family column family
224    * @param qualifier column qualifier
225    * @param value value to check
226    * @return returns true if the given family, qualifier and value already has an
227    * existing KeyValue object in the family map.
228    */
229   public boolean has(byte [] family, byte [] qualifier, byte [] value) {
230     return has(family, qualifier, this.timestamp, value, true, false);
231   }
232 
233   /**
234    * A convenience method to determine if this object's familyMap contains
235    * the given value assigned to the given family, qualifier and timestamp.
236    * All 4 given arguments must match the KeyValue object to return true.
237    *
238    * @param family column family
239    * @param qualifier column qualifier
240    * @param ts timestamp
241    * @param value value to check
242    * @return returns true if the given family, qualifier timestamp and value
243    * already has an existing KeyValue object in the family map.
244    */
245   public boolean has(byte [] family, byte [] qualifier, long ts, byte [] value) {
246       return has(family, qualifier, ts, value, false, false);
247   }
248 
249   /*
250    * Private method to determine if this object's familyMap contains
251    * the given value assigned to the given family, qualifier and timestamp
252    * respecting the 2 boolean arguments
253    *
254    * @param family
255    * @param qualifier
256    * @param ts
257    * @param value
258    * @param ignoreTS
259    * @param ignoreValue
260    * @return returns true if the given family, qualifier timestamp and value
261    * already has an existing KeyValue object in the family map.
262    */
263   private boolean has(byte [] family, byte [] qualifier, long ts, byte [] value,
264       boolean ignoreTS, boolean ignoreValue) {
265     List<KeyValue> list = getKeyValueList(family);
266     if (list.size() == 0) {
267       return false;
268     }
269     // Boolean analysis of ignoreTS/ignoreValue.
270     // T T => 2
271     // T F => 3 (first is always true)
272     // F T => 2
273     // F F => 1
274     if (!ignoreTS && !ignoreValue) {
275       KeyValue kv = createPutKeyValue(family, qualifier, ts, value);
276       return (list.contains(kv));
277     } else if (ignoreValue) {
278       for (KeyValue kv: list) {
279         if (Arrays.equals(kv.getFamily(), family) && Arrays.equals(kv.getQualifier(), qualifier)
280             && kv.getTimestamp() == ts) {
281           return true;
282         }
283       }
284     } else {
285       // ignoreTS is always true
286       for (KeyValue kv: list) {
287       if (Arrays.equals(kv.getFamily(), family) && Arrays.equals(kv.getQualifier(), qualifier)
288               && Arrays.equals(kv.getValue(), value)) {
289           return true;
290         }
291       }
292     }
293     return false;
294   }
295 
296   /**
297    * Returns a list of all KeyValue objects with matching column family and qualifier.
298    *
299    * @param family column family
300    * @param qualifier column qualifier
301    * @return a list of KeyValue objects with the matching family and qualifier,
302    * returns an empty list if one doesnt exist for the given family.
303    */
304   public List<KeyValue> get(byte[] family, byte[] qualifier) {
305     List<KeyValue> filteredList = new ArrayList<KeyValue>();
306     for (KeyValue kv: getKeyValueList(family)) {
307       if (Arrays.equals(kv.getQualifier(), qualifier)) {
308         filteredList.add(kv);
309       }
310     }
311     return filteredList;
312   }
313 
314   /**
315    * Creates an empty list if one doesnt exist for the given column family
316    * or else it returns the associated list of KeyValue objects.
317    *
318    * @param family column family
319    * @return a list of KeyValue objects, returns an empty list if one doesnt exist.
320    */
321   private List<KeyValue> getKeyValueList(byte[] family) {
322     List<KeyValue> list = familyMap.get(family);
323     if(list == null) {
324       list = new ArrayList<KeyValue>(0);
325     }
326     return list;
327   }
328 
329   /**
330    * Method for retrieving the put's familyMap
331    * @return familyMap
332    */
333   public Map<byte [], List<KeyValue>> getFamilyMap() {
334     return this.familyMap;
335   }
336 
337   /**
338    * Method for retrieving the put's row
339    * @return row
340    */
341   public byte [] getRow() {
342     return this.row;
343   }
344 
345   /**
346    * Method for retrieving the put's RowLock
347    * @return RowLock
348    */
349   public RowLock getRowLock() {
350     return new RowLock(this.row, this.lockId);
351   }
352 
353   /**
354    * Method for retrieving the put's lockId
355    * @return lockId
356    */
357   public long getLockId() {
358   	return this.lockId;
359   }
360 
361   /**
362    * Method to check if the familyMap is empty
363    * @return true if empty, false otherwise
364    */
365   public boolean isEmpty() {
366     return familyMap.isEmpty();
367   }
368 
369   /**
370    * @return Timestamp
371    */
372   public long getTimeStamp() {
373     return this.timestamp;
374   }
375 
376   /**
377    * @return the number of different families included in this put
378    */
379   public int numFamilies() {
380     return familyMap.size();
381   }
382 
383   /**
384    * @return the total number of KeyValues that will be added with this put
385    */
386   public int size() {
387     int size = 0;
388     for(List<KeyValue> kvList : this.familyMap.values()) {
389       size += kvList.size();
390     }
391     return size;
392   }
393 
394   /**
395    * @return true if edits should be applied to WAL, false if not
396    */
397   public boolean getWriteToWAL() {
398     return this.writeToWAL;
399   }
400 
401   /**
402    * Set whether this Put should be written to the WAL or not.
403    * Not writing the WAL means you may lose edits on server crash.
404    * @param write true if edits should be written to WAL, false if not
405    */
406   public void setWriteToWAL(boolean write) {
407     this.writeToWAL = write;
408   }
409 
410   /**
411    * @return String
412    */
413   @Override
414   public String toString() {
415     StringBuilder sb = new StringBuilder();
416     sb.append("row=");
417     sb.append(Bytes.toStringBinary(this.row));
418     sb.append(", families={");
419     boolean moreThanOne = false;
420     for(Map.Entry<byte [], List<KeyValue>> entry : this.familyMap.entrySet()) {
421       if(moreThanOne) {
422         sb.append(", ");
423       } else {
424         moreThanOne = true;
425       }
426       sb.append("(family=");
427       sb.append(Bytes.toString(entry.getKey()));
428       sb.append(", keyvalues=(");
429       boolean moreThanOneB = false;
430       for(KeyValue kv : entry.getValue()) {
431         if(moreThanOneB) {
432           sb.append(", ");
433         } else {
434           moreThanOneB = true;
435         }
436         sb.append(kv.toString());
437       }
438       sb.append(")");
439     }
440     sb.append("}");
441     return sb.toString();
442   }
443 
444   public int compareTo(Row p) {
445     return Bytes.compareTo(this.getRow(), p.getRow());
446   }
447 
448   //HeapSize
449   public long heapSize() {
450     long heapsize = OVERHEAD;
451     //Adding row
452     heapsize += ClassSize.align(ClassSize.ARRAY + this.row.length);
453 
454     //Adding map overhead
455     heapsize +=
456       ClassSize.align(this.familyMap.size() * ClassSize.MAP_ENTRY);
457     for(Map.Entry<byte [], List<KeyValue>> entry : this.familyMap.entrySet()) {
458       //Adding key overhead
459       heapsize +=
460         ClassSize.align(ClassSize.ARRAY + entry.getKey().length);
461 
462       //This part is kinds tricky since the JVM can reuse references if you
463       //store the same value, but have a good match with SizeOf at the moment
464       //Adding value overhead
465       heapsize += ClassSize.align(ClassSize.ARRAYLIST);
466       int size = entry.getValue().size();
467       heapsize += ClassSize.align(ClassSize.ARRAY +
468           size * ClassSize.REFERENCE);
469 
470       for(KeyValue kv : entry.getValue()) {
471         heapsize += kv.heapSize();
472       }
473     }
474     return ClassSize.align((int)heapsize);
475   }
476 
477   //Writable
478   public void readFields(final DataInput in)
479   throws IOException {
480     int version = in.readByte();
481     if (version > PUT_VERSION) {
482       throw new IOException("version not supported");
483     }
484     this.row = Bytes.readByteArray(in);
485     this.timestamp = in.readLong();
486     this.lockId = in.readLong();
487     this.writeToWAL = in.readBoolean();
488     int numFamilies = in.readInt();
489     if (!this.familyMap.isEmpty()) this.familyMap.clear();
490     for(int i=0;i<numFamilies;i++) {
491       byte [] family = Bytes.readByteArray(in);
492       int numKeys = in.readInt();
493       List<KeyValue> keys = new ArrayList<KeyValue>(numKeys);
494       int totalLen = in.readInt();
495       byte [] buf = new byte[totalLen];
496       int offset = 0;
497       for (int j = 0; j < numKeys; j++) {
498         int keyLength = in.readInt();
499         in.readFully(buf, offset, keyLength);
500         keys.add(new KeyValue(buf, offset, keyLength));
501         offset += keyLength;
502       }
503       this.familyMap.put(family, keys);
504     }
505   }
506 
507   public void write(final DataOutput out)
508   throws IOException {
509     out.writeByte(PUT_VERSION);
510     Bytes.writeByteArray(out, this.row);
511     out.writeLong(this.timestamp);
512     out.writeLong(this.lockId);
513     out.writeBoolean(this.writeToWAL);
514     out.writeInt(familyMap.size());
515     for (Map.Entry<byte [], List<KeyValue>> entry : familyMap.entrySet()) {
516       Bytes.writeByteArray(out, entry.getKey());
517       List<KeyValue> keys = entry.getValue();
518       out.writeInt(keys.size());
519       int totalLen = 0;
520       for(KeyValue kv : keys) {
521         totalLen += kv.getLength();
522       }
523       out.writeInt(totalLen);
524       for(KeyValue kv : keys) {
525         out.writeInt(kv.getLength());
526         out.write(kv.getBuffer(), kv.getOffset(), kv.getLength());
527       }
528     }
529   }
530 
531   /**
532    * Add the specified column and value, with the specified timestamp as
533    * its version to this Put operation.
534    * @param column Old style column name with family and qualifier put together
535    * with a colon.
536    * @param ts version timestamp
537    * @param value column value
538    * @deprecated use {@link #add(byte[], byte[], long, byte[])} instead
539    * @return true
540    */
541   public Put add(byte [] column, long ts, byte [] value) {
542     byte [][] parts = KeyValue.parseColumn(column);
543     return add(parts[0], parts[1], ts, value);
544   }
545 }