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     if (!kv.matchingRow(row)) {
161       throw new IOException("The row in the recently added KeyValue "
162           + Bytes.toStringBinary(kv.getRow()) + " 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       for (KeyValue kv : list) {
268         if (Arrays.equals(kv.getFamily(), family) &&
269             Arrays.equals(kv.getQualifier(), qualifier) &&
270             Arrays.equals(kv.getValue(), value) &&
271             kv.getTimestamp() == ts) {
272           return true;
273         }
274       }
275     } else if (ignoreValue && !ignoreTS) {
276       for (KeyValue kv : list) {
277         if (Arrays.equals(kv.getFamily(), family) && Arrays.equals(kv.getQualifier(), qualifier)
278             && kv.getTimestamp() == ts) {
279           return true;
280         }
281       }
282     } else if (!ignoreValue && ignoreTS) {
283       for (KeyValue kv : list) {
284         if (Arrays.equals(kv.getFamily(), family) && Arrays.equals(kv.getQualifier(), qualifier)
285             && Arrays.equals(kv.getValue(), value)) {
286           return true;
287         }
288       }
289     } else {
290       for (KeyValue kv : list) {
291         if (Arrays.equals(kv.getFamily(), family) &&
292             Arrays.equals(kv.getQualifier(), qualifier)) {
293           return true;
294         }
295       }
296     }
297     return false;
298   }
299 
300   /**
301    * Returns a list of all KeyValue objects with matching column family and qualifier.
302    *
303    * @param family column family
304    * @param qualifier column qualifier
305    * @return a list of KeyValue objects with the matching family and qualifier,
306    * returns an empty list if one doesnt exist for the given family.
307    */
308   public List<KeyValue> get(byte[] family, byte[] qualifier) {
309     List<KeyValue> filteredList = new ArrayList<KeyValue>();
310     for (KeyValue kv: getKeyValueList(family)) {
311       if (Arrays.equals(kv.getQualifier(), qualifier)) {
312         filteredList.add(kv);
313       }
314     }
315     return filteredList;
316   }
317 
318   /**
319    * Creates an empty list if one doesnt exist for the given column family
320    * or else it returns the associated list of KeyValue objects.
321    *
322    * @param family column family
323    * @return a list of KeyValue objects, returns an empty list if one doesnt exist.
324    */
325   private List<KeyValue> getKeyValueList(byte[] family) {
326     List<KeyValue> list = familyMap.get(family);
327     if(list == null) {
328       list = new ArrayList<KeyValue>(0);
329     }
330     return list;
331   }
332 
333   //HeapSize
334   public long heapSize() {
335     long heapsize = OVERHEAD;
336     //Adding row
337     heapsize += ClassSize.align(ClassSize.ARRAY + this.row.length);
338 
339     //Adding map overhead
340     heapsize +=
341       ClassSize.align(this.familyMap.size() * ClassSize.MAP_ENTRY);
342     for(Map.Entry<byte [], List<KeyValue>> entry : this.familyMap.entrySet()) {
343       //Adding key overhead
344       heapsize +=
345         ClassSize.align(ClassSize.ARRAY + entry.getKey().length);
346 
347       //This part is kinds tricky since the JVM can reuse references if you
348       //store the same value, but have a good match with SizeOf at the moment
349       //Adding value overhead
350       heapsize += ClassSize.align(ClassSize.ARRAYLIST);
351       int size = entry.getValue().size();
352       heapsize += ClassSize.align(ClassSize.ARRAY +
353           size * ClassSize.REFERENCE);
354 
355       for(KeyValue kv : entry.getValue()) {
356         heapsize += kv.heapSize();
357       }
358     }
359     heapsize += getAttributeSize();
360 
361     return ClassSize.align((int)heapsize);
362   }
363 
364   //Writable
365   public void readFields(final DataInput in)
366   throws IOException {
367     int version = in.readByte();
368     if (version > PUT_VERSION) {
369       throw new IOException("version not supported");
370     }
371     this.row = Bytes.readByteArray(in);
372     this.ts = in.readLong();
373     this.lockId = in.readLong();
374     this.writeToWAL = in.readBoolean();
375     int numFamilies = in.readInt();
376     if (!this.familyMap.isEmpty()) this.familyMap.clear();
377     for(int i=0;i<numFamilies;i++) {
378       byte [] family = Bytes.readByteArray(in);
379       int numKeys = in.readInt();
380       List<KeyValue> keys = new ArrayList<KeyValue>(numKeys);
381       int totalLen = in.readInt();
382       byte [] buf = new byte[totalLen];
383       int offset = 0;
384       for (int j = 0; j < numKeys; j++) {
385         int keyLength = in.readInt();
386         in.readFully(buf, offset, keyLength);
387         keys.add(new KeyValue(buf, offset, keyLength));
388         offset += keyLength;
389       }
390       this.familyMap.put(family, keys);
391     }
392     if (version > 1) {
393       readAttributes(in);
394     }
395   }
396 
397   public void write(final DataOutput out)
398   throws IOException {
399     out.writeByte(PUT_VERSION);
400     Bytes.writeByteArray(out, this.row);
401     out.writeLong(this.ts);
402     out.writeLong(this.lockId);
403     out.writeBoolean(this.writeToWAL);
404     out.writeInt(familyMap.size());
405     for (Map.Entry<byte [], List<KeyValue>> entry : familyMap.entrySet()) {
406       Bytes.writeByteArray(out, entry.getKey());
407       List<KeyValue> keys = entry.getValue();
408       out.writeInt(keys.size());
409       int totalLen = 0;
410       for(KeyValue kv : keys) {
411         totalLen += kv.getLength();
412       }
413       out.writeInt(totalLen);
414       for(KeyValue kv : keys) {
415         kv.write(out);
416       }
417     }
418     writeAttributes(out);
419   }
420 }