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