View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.client;
20  
21  import java.io.IOException;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.NavigableMap;
25  import java.util.TreeMap;
26  
27  import org.apache.hadoop.classification.InterfaceAudience;
28  import org.apache.hadoop.classification.InterfaceStability;
29  import org.apache.hadoop.hbase.Cell;
30  import org.apache.hadoop.hbase.KeyValue;
31  import org.apache.hadoop.hbase.KeyValueUtil;
32  import org.apache.hadoop.hbase.io.TimeRange;
33  import org.apache.hadoop.hbase.util.Bytes;
34  import org.apache.hadoop.hbase.util.ClassSize;
35  
36  /**
37   * Used to perform Increment operations on a single row.
38   * <p>
39   * This operation does not appear atomic to readers.  Increments are done
40   * under a single row lock, so write operations to a row are synchronized, but
41   * readers do not take row locks so get and scan operations can see this
42   * operation partially completed.
43   * <p>
44   * To increment columns of a row, instantiate an Increment object with the row
45   * to increment.  At least one column to increment must be specified using the
46   * {@link #addColumn(byte[], byte[], long)} method.
47   */
48  @InterfaceAudience.Public
49  @InterfaceStability.Stable
50  public class Increment extends Mutation implements Comparable<Row> {
51    private static final long HEAP_OVERHEAD =  ClassSize.REFERENCE + ClassSize.TIMERANGE;
52  
53    private TimeRange tr = new TimeRange();
54  
55    /**
56     * Create a Increment operation for the specified row.
57     * <p>
58     * At least one column must be incremented.
59     * @param row row key (we will make a copy of this).
60     */
61    public Increment(byte [] row) {
62      this(row, 0, row.length);
63    }
64  
65    /**
66     * Create a Increment operation for the specified row.
67     * <p>
68     * At least one column must be incremented.
69     * @param row row key (we will make a copy of this).
70     */
71    public Increment(final byte [] row, final int offset, final int length) {
72      checkRow(row, offset, length);
73      this.row = Bytes.copy(row, offset, length);
74    }
75  
76    /**
77     * Add the specified KeyValue to this operation.
78     * @param cell individual Cell
79     * @return this
80     * @throws java.io.IOException e
81     */
82    @SuppressWarnings("unchecked")
83    public Increment add(Cell cell) throws IOException{
84      KeyValue kv = KeyValueUtil.ensureKeyValue(cell);
85      byte [] family = kv.getFamily();
86      List<Cell> list = getCellList(family);
87      //Checking that the row of the kv is the same as the put
88      int res = Bytes.compareTo(this.row, 0, row.length,
89          kv.getRowArray(), kv.getRowOffset(), kv.getRowLength());
90      if (res != 0) {
91        throw new WrongRowIOException("The row in " + kv.toString() +
92          " doesn't match the original one " +  Bytes.toStringBinary(this.row));
93      }
94      list.add(kv);
95      familyMap.put(family, list);
96      return this;
97    }
98  
99    /**
100    * Increment the column from the specific family with the specified qualifier
101    * by the specified amount.
102    * <p>
103    * Overrides previous calls to addColumn for this family and qualifier.
104    * @param family family name
105    * @param qualifier column qualifier
106    * @param amount amount to increment by
107    * @return the Increment object
108    */
109   @SuppressWarnings("unchecked")
110   public Increment addColumn(byte [] family, byte [] qualifier, long amount) {
111     if (family == null) {
112       throw new IllegalArgumentException("family cannot be null");
113     }
114     if (qualifier == null) {
115       throw new IllegalArgumentException("qualifier cannot be null");
116     }
117     List<Cell> list = getCellList(family);
118     KeyValue kv = createPutKeyValue(family, qualifier, ts, Bytes.toBytes(amount));
119     list.add(kv);
120     familyMap.put(kv.getFamily(), list);
121     return this;
122   }
123 
124   /**
125    * Gets the TimeRange used for this increment.
126    * @return TimeRange
127    */
128   public TimeRange getTimeRange() {
129     return this.tr;
130   }
131 
132   /**
133    * Sets the TimeRange to be used on the Get for this increment.
134    * <p>
135    * This is useful for when you have counters that only last for specific
136    * periods of time (ie. counters that are partitioned by time).  By setting
137    * the range of valid times for this increment, you can potentially gain
138    * some performance with a more optimal Get operation.
139    * <p>
140    * This range is used as [minStamp, maxStamp).
141    * @param minStamp minimum timestamp value, inclusive
142    * @param maxStamp maximum timestamp value, exclusive
143    * @throws IOException if invalid time range
144    * @return this
145    */
146   public Increment setTimeRange(long minStamp, long maxStamp)
147   throws IOException {
148     tr = new TimeRange(minStamp, maxStamp);
149     return this;
150   }
151 
152   /**
153    * Method for retrieving the number of families to increment from
154    * @return number of families
155    */
156   public int numFamilies() {
157     return this.familyMap.size();
158   }
159 
160   /**
161    * Method for checking if any families have been inserted into this Increment
162    * @return true if familyMap is non empty false otherwise
163    */
164   public boolean hasFamilies() {
165     return !this.familyMap.isEmpty();
166   }
167 
168   /**
169    * Before 0.95, when you called Increment#getFamilyMap(), you got back
170    * a map of families to a list of Longs.  Now, {@link #getFamilyCellMap()} returns
171    * families by list of Cells.  This method has been added so you can have the
172    * old behavior.
173    * @return Map of families to a Map of qualifers and their Long increments.
174    * @since 0.95.0
175    */
176   public Map<byte[], NavigableMap<byte [], Long>> getFamilyMapOfLongs() {
177     NavigableMap<byte[], List<Cell>> map = super.getFamilyCellMap();
178     Map<byte [], NavigableMap<byte[], Long>> results =
179       new TreeMap<byte[], NavigableMap<byte [], Long>>(Bytes.BYTES_COMPARATOR);
180     for (Map.Entry<byte [], List<Cell>> entry: map.entrySet()) {
181       NavigableMap<byte [], Long> longs = new TreeMap<byte [], Long>(Bytes.BYTES_COMPARATOR);
182       for (Cell cell: entry.getValue()) {
183         KeyValue kv = KeyValueUtil.ensureKeyValue(cell);
184         longs.put(kv.getQualifier(),
185             Bytes.toLong(kv.getValueArray(), kv.getValueOffset(), kv.getValueLength()));
186       }
187       results.put(entry.getKey(), longs);
188     }
189     return results;
190   }
191 
192   /**
193    * @return String
194    */
195   @Override
196   public String toString() {
197     StringBuilder sb = new StringBuilder();
198     sb.append("row=");
199     sb.append(Bytes.toStringBinary(this.row));
200     if(this.familyMap.size() == 0) {
201       sb.append(", no columns set to be incremented");
202       return sb.toString();
203     }
204     sb.append(", families=");
205     boolean moreThanOne = false;
206     for(Map.Entry<byte [], List<Cell>> entry: this.familyMap.entrySet()) {
207       if(moreThanOne) {
208         sb.append("), ");
209       } else {
210         moreThanOne = true;
211         sb.append("{");
212       }
213       sb.append("(family=");
214       sb.append(Bytes.toString(entry.getKey()));
215       sb.append(", columns=");
216       if(entry.getValue() == null) {
217         sb.append("NONE");
218       } else {
219         sb.append("{");
220         boolean moreThanOneB = false;
221         for(Cell cell : entry.getValue()) {
222           if(moreThanOneB) {
223             sb.append(", ");
224           } else {
225             moreThanOneB = true;
226           }
227           KeyValue kv = KeyValueUtil.ensureKeyValue(cell);
228           sb.append(Bytes.toStringBinary(kv.getKey()) + "+=" +
229               Bytes.toLong(kv.getValueArray(), kv.getValueOffset(), kv.getValueLength()));
230         }
231         sb.append("}");
232       }
233     }
234     sb.append("}");
235     return sb.toString();
236   }
237 
238   @Override
239   public int compareTo(Row i) {
240     // TODO: This is wrong.  Can't have two the same just because on same row.
241     return Bytes.compareTo(this.getRow(), i.getRow());
242   }
243 
244   @Override
245   public int hashCode() {
246     // TODO: This is wrong.  Can't have two gets the same just because on same row.  But it
247     // matches how equals works currently and gets rid of the findbugs warning.
248     return Bytes.hashCode(this.getRow());
249   }
250 
251   @Override
252   public boolean equals(Object obj) {
253     // TODO: This is wrong.  Can't have two the same just because on same row.
254     if (this == obj) {
255       return true;
256     }
257     if (obj == null || getClass() != obj.getClass()) {
258       return false;
259     }
260     Row other = (Row) obj;
261     return compareTo(other) == 0;
262   }
263 
264   protected long extraHeapSize(){
265     return HEAP_OVERHEAD;
266   }
267 }