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 Row {
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     * @deprecated {@link RowLock} and associated operations are deprecated,
77     * use {@link #Increment(byte[])}
78     */
79    public Increment(byte [] row, RowLock rowLock) {
80      this.row = row;
81      if(rowLock != null) {
82        this.lockId = rowLock.getLockId();
83      }
84    }
85  
86    /**
87     * Increment the column from the specific family with the specified qualifier
88     * by the specified amount.
89     * <p>
90     * Overrides previous calls to addColumn for this family and qualifier.
91     * @param family family name
92     * @param qualifier column qualifier
93     * @param amount amount to increment by
94     * @return the Increment object
95     */
96    public Increment addColumn(byte [] family, byte [] qualifier, long amount) {
97      NavigableMap<byte [], Long> set = familyMap.get(family);
98      if(set == null) {
99        set = new TreeMap<byte [], Long>(Bytes.BYTES_COMPARATOR);
100     }
101     set.put(qualifier, amount);
102     familyMap.put(family, set);
103     return this;
104   }
105 
106   /* Accessors */
107 
108   /**
109    * Method for retrieving the increment's row
110    * @return row
111    */
112   public byte [] getRow() {
113     return this.row;
114   }
115 
116   /**
117    * Method for retrieving the increment's RowLock
118    * @return RowLock
119    * @deprecated {@link RowLock} and associated operations are deprecated
120    */
121   public RowLock getRowLock() {
122     return new RowLock(this.row, this.lockId);
123   }
124 
125   /**
126    * Method for retrieving the increment's lockId
127    * @return lockId
128    * @deprecated {@link RowLock} and associated operations are deprecated
129    */
130   public long getLockId() {
131     return this.lockId;
132   }
133 
134   /**
135    * Method for retrieving whether WAL will be written to or not
136    * @return true if WAL should be used, false if not
137    */
138   public boolean getWriteToWAL() {
139     return this.writeToWAL;
140   }
141 
142   /**
143    * Sets whether this operation should write to the WAL or not.
144    * @param writeToWAL true if WAL should be used, false if not
145    * @return this increment operation
146    */
147   public Increment setWriteToWAL(boolean writeToWAL) {
148     this.writeToWAL = writeToWAL;
149     return this;
150   }
151 
152   /**
153    * Gets the TimeRange used for this increment.
154    * @return TimeRange
155    */
156   public TimeRange getTimeRange() {
157     return this.tr;
158   }
159 
160   /**
161    * Sets the TimeRange to be used on the Get for this increment.
162    * <p>
163    * This is useful for when you have counters that only last for specific
164    * periods of time (ie. counters that are partitioned by time).  By setting
165    * the range of valid times for this increment, you can potentially gain
166    * some performance with a more optimal Get operation.
167    * <p>
168    * This range is used as [minStamp, maxStamp).
169    * @param minStamp minimum timestamp value, inclusive
170    * @param maxStamp maximum timestamp value, exclusive
171    * @throws IOException if invalid time range
172    * @return this
173    */
174   public Increment setTimeRange(long minStamp, long maxStamp)
175   throws IOException {
176     tr = new TimeRange(minStamp, maxStamp);
177     return this;
178   }
179 
180   /**
181    * Method for retrieving the keys in the familyMap
182    * @return keys in the current familyMap
183    */
184   public Set<byte[]> familySet() {
185     return this.familyMap.keySet();
186   }
187 
188   /**
189    * Method for retrieving the number of families to increment from
190    * @return number of families
191    */
192   public int numFamilies() {
193     return this.familyMap.size();
194   }
195 
196   /**
197    * Method for retrieving the number of columns to increment
198    * @return number of columns across all families
199    */
200   public int numColumns() {
201     if (!hasFamilies()) return 0;
202     int num = 0;
203     for (NavigableMap<byte [], Long> family : familyMap.values()) {
204       num += family.size();
205     }
206     return num;
207   }
208 
209   /**
210    * Method for checking if any families have been inserted into this Increment
211    * @return true if familyMap is non empty false otherwise
212    */
213   public boolean hasFamilies() {
214     return !this.familyMap.isEmpty();
215   }
216 
217   /**
218    * Method for retrieving the increment's familyMap
219    * @return familyMap
220    */
221   public Map<byte[],NavigableMap<byte[], Long>> getFamilyMap() {
222     return this.familyMap;
223   }
224 
225   /**
226    * @return String
227    */
228   @Override
229   public String toString() {
230     StringBuilder sb = new StringBuilder();
231     sb.append("row=");
232     sb.append(Bytes.toStringBinary(this.row));
233     if(this.familyMap.size() == 0) {
234       sb.append(", no columns set to be incremented");
235       return sb.toString();
236     }
237     sb.append(", families=");
238     boolean moreThanOne = false;
239     for(Map.Entry<byte [], NavigableMap<byte[], Long>> entry :
240       this.familyMap.entrySet()) {
241       if(moreThanOne) {
242         sb.append("), ");
243       } else {
244         moreThanOne = true;
245         sb.append("{");
246       }
247       sb.append("(family=");
248       sb.append(Bytes.toString(entry.getKey()));
249       sb.append(", columns=");
250       if(entry.getValue() == null) {
251         sb.append("NONE");
252       } else {
253         sb.append("{");
254         boolean moreThanOneB = false;
255         for(Map.Entry<byte [], Long> column : entry.getValue().entrySet()) {
256           if(moreThanOneB) {
257             sb.append(", ");
258           } else {
259             moreThanOneB = true;
260           }
261           sb.append(Bytes.toStringBinary(column.getKey()) + "+=" + column.getValue());
262         }
263         sb.append("}");
264       }
265     }
266     sb.append("}");
267     return sb.toString();
268   }
269 
270   //Writable
271   public void readFields(final DataInput in)
272   throws IOException {
273     int version = in.readByte();
274     if (version > INCREMENT_VERSION) {
275       throw new IOException("unsupported version");
276     }
277     this.row = Bytes.readByteArray(in);
278     this.tr = new TimeRange();
279     tr.readFields(in);
280     this.lockId = in.readLong();
281     int numFamilies = in.readInt();
282     if (numFamilies == 0) {
283       throw new IOException("At least one column required");
284     }
285     this.familyMap =
286       new TreeMap<byte [],NavigableMap<byte [], Long>>(Bytes.BYTES_COMPARATOR);
287     for(int i=0; i<numFamilies; i++) {
288       byte [] family = Bytes.readByteArray(in);
289       boolean hasColumns = in.readBoolean();
290       NavigableMap<byte [], Long> set = null;
291       if(hasColumns) {
292         int numColumns = in.readInt();
293         set = new TreeMap<byte [], Long>(Bytes.BYTES_COMPARATOR);
294         for(int j=0; j<numColumns; j++) {
295           byte [] qualifier = Bytes.readByteArray(in);
296           set.put(qualifier, in.readLong());
297         }
298       } else {
299         throw new IOException("At least one column required per family");
300       }
301       this.familyMap.put(family, set);
302     }
303     if (version > 1) {
304       this.writeToWAL = in.readBoolean();
305     }
306   }
307 
308   public void write(final DataOutput out)
309   throws IOException {
310     out.writeByte(INCREMENT_VERSION);
311     Bytes.writeByteArray(out, this.row);
312     tr.write(out);
313     out.writeLong(this.lockId);
314     if (familyMap.size() == 0) {
315       throw new IOException("At least one column required");
316     }
317     out.writeInt(familyMap.size());
318     for(Map.Entry<byte [], NavigableMap<byte [], Long>> entry :
319       familyMap.entrySet()) {
320       Bytes.writeByteArray(out, entry.getKey());
321       NavigableMap<byte [], Long> columnSet = entry.getValue();
322       if(columnSet == null) {
323         throw new IOException("At least one column required per family");
324       } else {
325         out.writeBoolean(true);
326         out.writeInt(columnSet.size());
327         for(Map.Entry<byte [], Long> qualifier : columnSet.entrySet()) {
328           Bytes.writeByteArray(out, qualifier.getKey());
329           out.writeLong(qualifier.getValue());
330         }
331       }
332     }
333     out.writeBoolean(writeToWAL);
334   }
335 
336   @Override
337   public int compareTo(Row i) {
338     return Bytes.compareTo(this.getRow(), i.getRow());
339   }
340 }