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   * Used to perform Put operations for a single row.
41   * <p>
42   * To perform a Put, instantiate a Put object with the row to insert to and
43   * for each column to be inserted, execute {@link #add(byte[], byte[], byte[]) add} or
44   * {@link #add(byte[], byte[], long, byte[]) add} if setting the timestamp.
45   */
46  public class Put extends Mutation
47    implements HeapSize, Writable, Row, Comparable<Row> {
48    private static final byte PUT_VERSION = (byte)2;
49  
50    private static final long OVERHEAD = ClassSize.align(
51        ClassSize.OBJECT + 2 * ClassSize.REFERENCE +
52        2 * Bytes.SIZEOF_LONG + Bytes.SIZEOF_BOOLEAN +
53        ClassSize.REFERENCE + ClassSize.TREEMAP);
54  
55    /** Constructor for Writable. DO NOT USE */
56    public Put() {}
57  
58    /**
59     * Create a Put operation for the specified row.
60     * @param row row key
61     */
62    public Put(byte [] row) {
63      this(row, null);
64    }
65  
66    /**
67     * Create a Put operation for the specified row, using an existing row lock.
68     * @param row row key
69     * @param rowLock previously acquired row lock, or null
70     */
71    public Put(byte [] row, RowLock rowLock) {
72        this(row, HConstants.LATEST_TIMESTAMP, rowLock);
73    }
74  
75    /**
76     * Create a Put operation for the specified row, using a given timestamp.
77     *
78     * @param row row key
79     * @param ts timestamp
80     */
81    public Put(byte[] row, long ts) {
82      this(row, ts, null);
83    }
84  
85    /**
86     * Create a Put operation for the specified row, using a given timestamp, and an existing row lock.
87     * @param row row key
88     * @param ts timestamp
89     * @param rowLock previously acquired row lock, or null
90     */
91    public Put(byte [] row, long ts, RowLock rowLock) {
92      if(row == null || row.length > HConstants.MAX_ROW_LENGTH) {
93        throw new IllegalArgumentException("Row key is invalid");
94      }
95      this.row = Arrays.copyOf(row, row.length);
96      this.ts = ts;
97      if(rowLock != null) {
98        this.lockId = rowLock.getLockId();
99      }
100   }
101 
102   /**
103    * Copy constructor.  Creates a Put operation cloned from the specified Put.
104    * @param putToCopy put to copy
105    */
106   public Put(Put putToCopy) {
107     this(putToCopy.getRow(), putToCopy.ts, putToCopy.getRowLock());
108     this.familyMap =
109       new TreeMap<byte [], List<KeyValue>>(Bytes.BYTES_COMPARATOR);
110     for(Map.Entry<byte [], List<KeyValue>> entry :
111       putToCopy.getFamilyMap().entrySet()) {
112       this.familyMap.put(entry.getKey(), entry.getValue());
113     }
114     this.writeToWAL = putToCopy.writeToWAL;
115   }
116 
117   /**
118    * Add the specified column and value to this Put operation.
119    * @param family family name
120    * @param qualifier column qualifier
121    * @param value column value
122    * @return this
123    */
124   public Put add(byte [] family, byte [] qualifier, byte [] value) {
125     return add(family, qualifier, this.ts, value);
126   }
127 
128   /**
129    * Add the specified column and value, with the specified timestamp as
130    * its version to this Put operation.
131    * @param family family name
132    * @param qualifier column qualifier
133    * @param ts version timestamp
134    * @param value column value
135    * @return this
136    */
137   public Put add(byte [] family, byte [] qualifier, long ts, byte [] value) {
138     List<KeyValue> list = getKeyValueList(family);
139     KeyValue kv = createPutKeyValue(family, qualifier, ts, value);
140     list.add(kv);
141     familyMap.put(kv.getFamily(), list);
142     return this;
143   }
144 
145   /**
146    * Add the specified KeyValue to this Put operation.  Operation assumes that
147    * the passed KeyValue is immutable and its backing array will not be modified
148    * for the duration of this Put.
149    * @param kv individual KeyValue
150    * @return this
151    * @throws java.io.IOException e
152    */
153   public Put add(KeyValue kv) throws IOException{
154     byte [] family = kv.getFamily();
155     List<KeyValue> list = getKeyValueList(family);
156     //Checking that the row of the kv is the same as the put
157     int res = Bytes.compareTo(this.row, 0, row.length,
158         kv.getBuffer(), kv.getRowOffset(), kv.getRowLength());
159     if(res != 0) {
160       throw new IOException("The row in the recently added KeyValue " +
161           Bytes.toStringBinary(kv.getBuffer(), kv.getRowOffset(),
162         kv.getRowLength()) + " doesn't match the original one " +
163         Bytes.toStringBinary(this.row));
164     }
165     list.add(kv);
166     familyMap.put(family, list);
167     return this;
168   }
169 
170   /*
171    * Create a KeyValue with this objects row key and the Put identifier.
172    *
173    * @return a KeyValue with this objects row key and the Put identifier.
174    */
175   private KeyValue createPutKeyValue(byte[] family, byte[] qualifier, long ts,
176       byte[] value) {
177   return  new KeyValue(this.row, family, qualifier, ts, KeyValue.Type.Put,
178       value);
179   }
180 
181   /**
182    * A convenience method to determine if this object's familyMap contains
183    * a value assigned to the given family & qualifier.
184    * Both given arguments must match the KeyValue object to return true.
185    *
186    * @param family column family
187    * @param qualifier column qualifier
188    * @return returns true if the given family and qualifier already has an
189    * existing KeyValue object in the family map.
190    */
191   public boolean has(byte [] family, byte [] qualifier) {
192   return has(family, qualifier, this.ts, new byte[0], true, true);
193   }
194 
195   /**
196    * A convenience method to determine if this object's familyMap contains
197    * a value assigned to the given family, qualifier and timestamp.
198    * All 3 given arguments must match the KeyValue object to return true.
199    *
200    * @param family column family
201    * @param qualifier column qualifier
202    * @param ts timestamp
203    * @return returns true if the given family, qualifier and timestamp already has an
204    * existing KeyValue object in the family map.
205    */
206   public boolean has(byte [] family, byte [] qualifier, long ts) {
207   return has(family, qualifier, ts, new byte[0], false, true);
208   }
209 
210   /**
211    * A convenience method to determine if this object's familyMap contains
212    * a value assigned to the given family, qualifier and timestamp.
213    * All 3 given arguments must match the KeyValue object to return true.
214    *
215    * @param family column family
216    * @param qualifier column qualifier
217    * @param value value to check
218    * @return returns true if the given family, qualifier and value already has an
219    * existing KeyValue object in the family map.
220    */
221   public boolean has(byte [] family, byte [] qualifier, byte [] value) {
222     return has(family, qualifier, this.ts, value, true, false);
223   }
224 
225   /**
226    * A convenience method to determine if this object's familyMap contains
227    * the given value assigned to the given family, qualifier and timestamp.
228    * All 4 given arguments must match the KeyValue object to return true.
229    *
230    * @param family column family
231    * @param qualifier column qualifier
232    * @param ts timestamp
233    * @param value value to check
234    * @return returns true if the given family, qualifier timestamp and value
235    * already has an existing KeyValue object in the family map.
236    */
237   public boolean has(byte [] family, byte [] qualifier, long ts, byte [] value) {
238       return has(family, qualifier, ts, value, false, false);
239   }
240 
241   /*
242    * Private method to determine if this object's familyMap contains
243    * the given value assigned to the given family, qualifier and timestamp
244    * respecting the 2 boolean arguments
245    *
246    * @param family
247    * @param qualifier
248    * @param ts
249    * @param value
250    * @param ignoreTS
251    * @param ignoreValue
252    * @return returns true if the given family, qualifier timestamp and value
253    * already has an existing KeyValue object in the family map.
254    */
255   private boolean has(byte [] family, byte [] qualifier, long ts, byte [] value,
256       boolean ignoreTS, boolean ignoreValue) {
257     List<KeyValue> list = getKeyValueList(family);
258     if (list.size() == 0) {
259       return false;
260     }
261     // Boolean analysis of ignoreTS/ignoreValue.
262     // T T => 2
263     // T F => 3 (first is always true)
264     // F T => 2
265     // F F => 1
266     if (!ignoreTS && !ignoreValue) {
267       KeyValue kv = createPutKeyValue(family, qualifier, ts, value);
268       return (list.contains(kv));
269     } else if (ignoreValue) {
270       for (KeyValue kv: list) {
271         if (Arrays.equals(kv.getFamily(), family) && Arrays.equals(kv.getQualifier(), qualifier)
272             && kv.getTimestamp() == ts) {
273           return true;
274         }
275       }
276     } else {
277       // ignoreTS is always true
278       for (KeyValue kv: list) {
279       if (Arrays.equals(kv.getFamily(), family) && Arrays.equals(kv.getQualifier(), qualifier)
280               && Arrays.equals(kv.getValue(), value)) {
281           return true;
282         }
283       }
284     }
285     return false;
286   }
287 
288   /**
289    * Returns a list of all KeyValue objects with matching column family and qualifier.
290    *
291    * @param family column family
292    * @param qualifier column qualifier
293    * @return a list of KeyValue objects with the matching family and qualifier,
294    * returns an empty list if one doesnt exist for the given family.
295    */
296   public List<KeyValue> get(byte[] family, byte[] qualifier) {
297     List<KeyValue> filteredList = new ArrayList<KeyValue>();
298     for (KeyValue kv: getKeyValueList(family)) {
299       if (Arrays.equals(kv.getQualifier(), qualifier)) {
300         filteredList.add(kv);
301       }
302     }
303     return filteredList;
304   }
305 
306   /**
307    * Creates an empty list if one doesnt exist for the given column family
308    * or else it returns the associated list of KeyValue objects.
309    *
310    * @param family column family
311    * @return a list of KeyValue objects, returns an empty list if one doesnt exist.
312    */
313   private List<KeyValue> getKeyValueList(byte[] family) {
314     List<KeyValue> list = familyMap.get(family);
315     if(list == null) {
316       list = new ArrayList<KeyValue>(0);
317     }
318     return list;
319   }
320 
321   /**
322    * @return the number of different families included in this put
323    */
324   public int numFamilies() {
325     return familyMap.size();
326   }
327 
328   /**
329    * @return the total number of KeyValues that will be added with this put
330    */
331   public int size() {
332     int size = 0;
333     for(List<KeyValue> kvList : this.familyMap.values()) {
334       size += kvList.size();
335     }
336     return size;
337   }
338 
339   //HeapSize
340   public long heapSize() {
341     long heapsize = OVERHEAD;
342     //Adding row
343     heapsize += ClassSize.align(ClassSize.ARRAY + this.row.length);
344 
345     //Adding map overhead
346     heapsize +=
347       ClassSize.align(this.familyMap.size() * ClassSize.MAP_ENTRY);
348     for(Map.Entry<byte [], List<KeyValue>> entry : this.familyMap.entrySet()) {
349       //Adding key overhead
350       heapsize +=
351         ClassSize.align(ClassSize.ARRAY + entry.getKey().length);
352 
353       //This part is kinds tricky since the JVM can reuse references if you
354       //store the same value, but have a good match with SizeOf at the moment
355       //Adding value overhead
356       heapsize += ClassSize.align(ClassSize.ARRAYLIST);
357       int size = entry.getValue().size();
358       heapsize += ClassSize.align(ClassSize.ARRAY +
359           size * ClassSize.REFERENCE);
360 
361       for(KeyValue kv : entry.getValue()) {
362         heapsize += kv.heapSize();
363       }
364     }
365     heapsize += getAttributeSize();
366 
367     return ClassSize.align((int)heapsize);
368   }
369 
370   //Writable
371   public void readFields(final DataInput in)
372   throws IOException {
373     int version = in.readByte();
374     if (version > PUT_VERSION) {
375       throw new IOException("version not supported");
376     }
377     this.row = Bytes.readByteArray(in);
378     this.ts = in.readLong();
379     this.lockId = in.readLong();
380     this.writeToWAL = in.readBoolean();
381     int numFamilies = in.readInt();
382     if (!this.familyMap.isEmpty()) this.familyMap.clear();
383     for(int i=0;i<numFamilies;i++) {
384       byte [] family = Bytes.readByteArray(in);
385       int numKeys = in.readInt();
386       List<KeyValue> keys = new ArrayList<KeyValue>(numKeys);
387       int totalLen = in.readInt();
388       byte [] buf = new byte[totalLen];
389       int offset = 0;
390       for (int j = 0; j < numKeys; j++) {
391         int keyLength = in.readInt();
392         in.readFully(buf, offset, keyLength);
393         keys.add(new KeyValue(buf, offset, keyLength));
394         offset += keyLength;
395       }
396       this.familyMap.put(family, keys);
397     }
398     if (version > 1) {
399       readAttributes(in);
400     }
401   }
402 
403   public void write(final DataOutput out)
404   throws IOException {
405     out.writeByte(PUT_VERSION);
406     Bytes.writeByteArray(out, this.row);
407     out.writeLong(this.ts);
408     out.writeLong(this.lockId);
409     out.writeBoolean(this.writeToWAL);
410     out.writeInt(familyMap.size());
411     for (Map.Entry<byte [], List<KeyValue>> entry : familyMap.entrySet()) {
412       Bytes.writeByteArray(out, entry.getKey());
413       List<KeyValue> keys = entry.getValue();
414       out.writeInt(keys.size());
415       int totalLen = 0;
416       for(KeyValue kv : keys) {
417         totalLen += kv.getLength();
418       }
419       out.writeInt(totalLen);
420       for(KeyValue kv : keys) {
421         out.writeInt(kv.getLength());
422         out.write(kv.getBuffer(), kv.getOffset(), kv.getLength());
423       }
424     }
425     writeAttributes(out);
426   }
427 }