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  package org.apache.hadoop.hbase.client;
21  
22  import java.io.DataInput;
23  import java.io.DataOutput;
24  import java.io.IOException;
25  import java.util.Map;
26  import java.util.NavigableMap;
27  import java.util.Set;
28  import java.util.TreeMap;
29  
30  import org.apache.hadoop.hbase.io.TimeRange;
31  import org.apache.hadoop.hbase.util.Bytes;
32  import org.apache.hadoop.io.Writable;
33  
34  /**
35   * Used to perform Increment operations on a single row.
36   * <p>
37   * This operation does not appear atomic to readers.  Increments are done
38   * under a single row lock, so write operations to a row are synchronized, but
39   * readers do not take row locks so get and scan operations can see this
40   * operation partially completed.
41   * <p>
42   * To increment columns of a row, instantiate an Increment object with the row
43   * to increment.  At least one column to increment must be specified using the
44   * {@link #addColumn(byte[], byte[], long)} method.
45   */
46  public class Increment implements Writable {
47    private static final byte INCREMENT_VERSION = (byte)2;
48  
49    private byte [] row = null;
50    private long lockId = -1L;
51    private boolean writeToWAL = true;
52    private TimeRange tr = new TimeRange();
53    private Map<byte [], NavigableMap<byte [], Long>> familyMap =
54      new TreeMap<byte [], NavigableMap<byte [], Long>>(Bytes.BYTES_COMPARATOR);
55  
56    /** Constructor for Writable.  DO NOT USE */
57    public Increment() {}
58  
59    /**
60     * Create a Increment operation for the specified row.
61     * <p>
62     * At least one column must be incremented.
63     * @param row row key
64     */
65    public Increment(byte [] row) {
66      this(row, null);
67    }
68  
69    /**
70     * Create a Increment operation for the specified row, using an existing row
71     * lock.
72     * <p>
73     * At least one column must be incremented.
74     * @param row row key
75     * @param rowLock previously acquired row lock, or null
76     */
77    public Increment(byte [] row, RowLock rowLock) {
78      this.row = row;
79      if(rowLock != null) {
80        this.lockId = rowLock.getLockId();
81      }
82    }
83  
84    /**
85     * Increment the column from the specific family with the specified qualifier
86     * by the specified amount.
87     * <p>
88     * Overrides previous calls to addColumn for this family and qualifier.
89     * @param family family name
90     * @param qualifier column qualifier
91     * @param amount amount to increment by
92     * @return the Increment object
93     */
94    public Increment addColumn(byte [] family, byte [] qualifier, long amount) {
95      NavigableMap<byte [], Long> set = familyMap.get(family);
96      if(set == null) {
97        set = new TreeMap<byte [], Long>(Bytes.BYTES_COMPARATOR);
98      }
99      set.put(qualifier, amount);
100     familyMap.put(family, set);
101     return this;
102   }
103 
104   /* Accessors */
105 
106   /**
107    * Method for retrieving the increment's row
108    * @return row
109    */
110   public byte [] getRow() {
111     return this.row;
112   }
113 
114   /**
115    * Method for retrieving the increment's RowLock
116    * @return RowLock
117    */
118   public RowLock getRowLock() {
119     return new RowLock(this.row, this.lockId);
120   }
121 
122   /**
123    * Method for retrieving the increment's lockId
124    * @return lockId
125    */
126   public long getLockId() {
127     return this.lockId;
128   }
129 
130   /**
131    * Method for retrieving whether WAL will be written to or not
132    * @return true if WAL should be used, false if not
133    */
134   public boolean getWriteToWAL() {
135     return this.writeToWAL;
136   }
137 
138   /**
139    * Sets whether this operation should write to the WAL or not.
140    * @param writeToWAL true if WAL should be used, false if not
141    * @return this increment operation
142    */
143   public Increment setWriteToWAL(boolean writeToWAL) {
144     this.writeToWAL = writeToWAL;
145     return this;
146   }
147 
148   /**
149    * Gets the TimeRange used for this increment.
150    * @return TimeRange
151    */
152   public TimeRange getTimeRange() {
153     return this.tr;
154   }
155 
156   /**
157    * Sets the TimeRange to be used on the Get for this increment.
158    * <p>
159    * This is useful for when you have counters that only last for specific
160    * periods of time (ie. counters that are partitioned by time).  By setting
161    * the range of valid times for this increment, you can potentially gain
162    * some performance with a more optimal Get operation.
163    * <p>
164    * This range is used as [minStamp, maxStamp).
165    * @param minStamp minimum timestamp value, inclusive
166    * @param maxStamp maximum timestamp value, exclusive
167    * @throws IOException if invalid time range
168    * @return this
169    */
170   public Increment setTimeRange(long minStamp, long maxStamp)
171   throws IOException {
172     tr = new TimeRange(minStamp, maxStamp);
173     return this;
174   }
175 
176   /**
177    * Method for retrieving the keys in the familyMap
178    * @return keys in the current familyMap
179    */
180   public Set<byte[]> familySet() {
181     return this.familyMap.keySet();
182   }
183 
184   /**
185    * Method for retrieving the number of families to increment from
186    * @return number of families
187    */
188   public int numFamilies() {
189     return this.familyMap.size();
190   }
191 
192   /**
193    * Method for retrieving the number of columns to increment
194    * @return number of columns across all families
195    */
196   public int numColumns() {
197     if (!hasFamilies()) return 0;
198     int num = 0;
199     for (NavigableMap<byte [], Long> family : familyMap.values()) {
200       num += family.size();
201     }
202     return num;
203   }
204 
205   /**
206    * Method for checking if any families have been inserted into this Increment
207    * @return true if familyMap is non empty false otherwise
208    */
209   public boolean hasFamilies() {
210     return !this.familyMap.isEmpty();
211   }
212 
213   /**
214    * Method for retrieving the increment's familyMap
215    * @return familyMap
216    */
217   public Map<byte[],NavigableMap<byte[], Long>> getFamilyMap() {
218     return this.familyMap;
219   }
220 
221   /**
222    * @return String
223    */
224   @Override
225   public String toString() {
226     StringBuilder sb = new StringBuilder();
227     sb.append("row=");
228     sb.append(Bytes.toStringBinary(this.row));
229     if(this.familyMap.size() == 0) {
230       sb.append(", no columns set to be incremented");
231       return sb.toString();
232     }
233     sb.append(", families=");
234     boolean moreThanOne = false;
235     for(Map.Entry<byte [], NavigableMap<byte[], Long>> entry :
236       this.familyMap.entrySet()) {
237       if(moreThanOne) {
238         sb.append("), ");
239       } else {
240         moreThanOne = true;
241         sb.append("{");
242       }
243       sb.append("(family=");
244       sb.append(Bytes.toString(entry.getKey()));
245       sb.append(", columns=");
246       if(entry.getValue() == null) {
247         sb.append("NONE");
248       } else {
249         sb.append("{");
250         boolean moreThanOneB = false;
251         for(Map.Entry<byte [], Long> column : entry.getValue().entrySet()) {
252           if(moreThanOneB) {
253             sb.append(", ");
254           } else {
255             moreThanOneB = true;
256           }
257           sb.append(Bytes.toStringBinary(column.getKey()) + "+=" + column.getValue());
258         }
259         sb.append("}");
260       }
261     }
262     sb.append("}");
263     return sb.toString();
264   }
265 
266   //Writable
267   public void readFields(final DataInput in)
268   throws IOException {
269     int version = in.readByte();
270     if (version > INCREMENT_VERSION) {
271       throw new IOException("unsupported version");
272     }
273     this.row = Bytes.readByteArray(in);
274     this.tr = new TimeRange();
275     tr.readFields(in);
276     this.lockId = in.readLong();
277     int numFamilies = in.readInt();
278     if (numFamilies == 0) {
279       throw new IOException("At least one column required");
280     }
281     this.familyMap =
282       new TreeMap<byte [],NavigableMap<byte [], Long>>(Bytes.BYTES_COMPARATOR);
283     for(int i=0; i<numFamilies; i++) {
284       byte [] family = Bytes.readByteArray(in);
285       boolean hasColumns = in.readBoolean();
286       NavigableMap<byte [], Long> set = null;
287       if(hasColumns) {
288         int numColumns = in.readInt();
289         set = new TreeMap<byte [], Long>(Bytes.BYTES_COMPARATOR);
290         for(int j=0; j<numColumns; j++) {
291           byte [] qualifier = Bytes.readByteArray(in);
292           set.put(qualifier, in.readLong());
293         }
294       } else {
295         throw new IOException("At least one column required per family");
296       }
297       this.familyMap.put(family, set);
298     }
299     if (version > 1) {
300       this.writeToWAL = in.readBoolean();
301     }
302   }
303 
304   public void write(final DataOutput out)
305   throws IOException {
306     out.writeByte(INCREMENT_VERSION);
307     Bytes.writeByteArray(out, this.row);
308     tr.write(out);
309     out.writeLong(this.lockId);
310     if (familyMap.size() == 0) {
311       throw new IOException("At least one column required");
312     }
313     out.writeInt(familyMap.size());
314     for(Map.Entry<byte [], NavigableMap<byte [], Long>> entry :
315       familyMap.entrySet()) {
316       Bytes.writeByteArray(out, entry.getKey());
317       NavigableMap<byte [], Long> columnSet = entry.getValue();
318       if(columnSet == null) {
319         throw new IOException("At least one column required per family");
320       } else {
321         out.writeBoolean(true);
322         out.writeInt(columnSet.size());
323         for(Map.Entry<byte [], Long> qualifier : columnSet.entrySet()) {
324           Bytes.writeByteArray(out, qualifier.getKey());
325           out.writeLong(qualifier.getValue());
326         }
327       }
328     }
329     out.writeBoolean(writeToWAL);
330   }
331 }