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  
20  package org.apache.hadoop.hbase.filter;
21  
22  import static org.apache.hadoop.hbase.util.Bytes.len;
23  
24  import java.io.IOException;
25  import java.util.ArrayList;
26  
27  import org.apache.hadoop.hbase.Cell;
28  import org.apache.hadoop.hbase.CellComparator;
29  import org.apache.hadoop.hbase.CellUtil;
30  import org.apache.hadoop.hbase.classification.InterfaceAudience;
31  import org.apache.hadoop.hbase.classification.InterfaceStability;
32  import org.apache.hadoop.hbase.exceptions.DeserializationException;
33  import org.apache.hadoop.hbase.protobuf.generated.FilterProtos;
34  import org.apache.hadoop.hbase.util.ByteStringer;
35  import org.apache.hadoop.hbase.util.Bytes;
36  
37  import com.google.common.base.Preconditions;
38  import com.google.protobuf.InvalidProtocolBufferException;
39  
40  /**
41   * This filter is used for selecting only those keys with columns that are
42   * between minColumn to maxColumn. For example, if minColumn is 'an', and
43   * maxColumn is 'be', it will pass keys with columns like 'ana', 'bad', but not
44   * keys with columns like 'bed', 'eye'
45   *
46   * If minColumn is null, there is no lower bound. If maxColumn is null, there is
47   * no upper bound.
48   *
49   * minColumnInclusive and maxColumnInclusive specify if the ranges are inclusive
50   * or not.
51   */
52  @InterfaceAudience.Public
53  @InterfaceStability.Stable
54  public class ColumnRangeFilter extends FilterBase {
55    protected byte[] minColumn = null;
56    protected boolean minColumnInclusive = true;
57    protected byte[] maxColumn = null;
58    protected boolean maxColumnInclusive = false;
59  
60    /**
61     * Create a filter to select those keys with columns that are between minColumn
62     * and maxColumn.
63     * @param minColumn minimum value for the column range. If if it's null,
64     * there is no lower bound.
65     * @param minColumnInclusive if true, include minColumn in the range.
66     * @param maxColumn maximum value for the column range. If it's null,
67     * @param maxColumnInclusive if true, include maxColumn in the range.
68     * there is no upper bound.
69     */
70    public ColumnRangeFilter(final byte[] minColumn, boolean minColumnInclusive,
71        final byte[] maxColumn, boolean maxColumnInclusive) {
72      this.minColumn = minColumn;
73      this.minColumnInclusive = minColumnInclusive;
74      this.maxColumn = maxColumn;
75      this.maxColumnInclusive = maxColumnInclusive;
76    }
77  
78    /**
79     * @return if min column range is inclusive.
80     */
81    public boolean isMinColumnInclusive() {
82      return minColumnInclusive;
83    }
84  
85    /**
86     * @return if max column range is inclusive.
87     */
88    public boolean isMaxColumnInclusive() {
89      return maxColumnInclusive;
90    }
91  
92    /**
93     * @return the min column range for the filter
94     */
95    public byte[] getMinColumn() {
96      return this.minColumn;
97    }
98  
99    /**
100    * @return true if min column is inclusive, false otherwise
101    */
102   public boolean getMinColumnInclusive() {
103     return this.minColumnInclusive;
104   }
105 
106   /**
107    * @return the max column range for the filter
108    */
109   public byte[] getMaxColumn() {
110     return this.maxColumn;
111   }
112 
113   /**
114    * @return true if max column is inclusive, false otherwise
115    */
116   public boolean getMaxColumnInclusive() {
117     return this.maxColumnInclusive;
118   }
119 
120   @Override
121   public boolean filterRowKey(Cell cell) throws IOException {
122     // Impl in FilterBase might do unnecessary copy for Off heap backed Cells.
123     return false;
124   }
125 
126   @Override
127   public ReturnCode filterKeyValue(Cell kv) {
128     int cmpMin = 1;
129 
130     if (this.minColumn != null) {
131       cmpMin = CellComparator.compareQualifiers(kv, this.minColumn, 0, this.minColumn.length);
132     }
133 
134     if (cmpMin < 0) {
135       return ReturnCode.SEEK_NEXT_USING_HINT;
136     }
137 
138     if (!this.minColumnInclusive && cmpMin == 0) {
139       return ReturnCode.NEXT_COL;
140     }
141 
142     if (this.maxColumn == null) {
143       return ReturnCode.INCLUDE;
144     }
145 
146     int cmpMax = CellComparator.compareQualifiers(kv, this.maxColumn, 0, this.maxColumn.length);
147 
148     if (this.maxColumnInclusive && cmpMax <= 0 ||
149         !this.maxColumnInclusive && cmpMax < 0) {
150       return ReturnCode.INCLUDE;
151     }
152 
153     return ReturnCode.NEXT_ROW;
154   }
155 
156   public static Filter createFilterFromArguments(ArrayList<byte []> filterArguments) {
157     Preconditions.checkArgument(filterArguments.size() == 4,
158                                 "Expected 4 but got: %s", filterArguments.size());
159     byte [] minColumn = ParseFilter.removeQuotesFromByteArray(filterArguments.get(0));
160     boolean minColumnInclusive = ParseFilter.convertByteArrayToBoolean(filterArguments.get(1));
161     byte [] maxColumn = ParseFilter.removeQuotesFromByteArray(filterArguments.get(2));
162     boolean maxColumnInclusive = ParseFilter.convertByteArrayToBoolean(filterArguments.get(3));
163 
164     if (minColumn.length == 0)
165       minColumn = null;
166     if (maxColumn.length == 0)
167       maxColumn = null;
168     return new ColumnRangeFilter(minColumn, minColumnInclusive,
169                                  maxColumn, maxColumnInclusive);
170   }
171 
172   /**
173    * @return The filter serialized using pb
174    */
175   public byte [] toByteArray() {
176     FilterProtos.ColumnRangeFilter.Builder builder =
177       FilterProtos.ColumnRangeFilter.newBuilder();
178     if (this.minColumn != null) builder.setMinColumn(ByteStringer.wrap(this.minColumn));
179     builder.setMinColumnInclusive(this.minColumnInclusive);
180     if (this.maxColumn != null) builder.setMaxColumn(ByteStringer.wrap(this.maxColumn));
181     builder.setMaxColumnInclusive(this.maxColumnInclusive);
182     return builder.build().toByteArray();
183   }
184 
185   /**
186    * @param pbBytes A pb serialized {@link ColumnRangeFilter} instance
187    * @return An instance of {@link ColumnRangeFilter} made from <code>bytes</code>
188    * @throws DeserializationException
189    * @see #toByteArray
190    */
191   public static ColumnRangeFilter parseFrom(final byte [] pbBytes)
192   throws DeserializationException {
193     FilterProtos.ColumnRangeFilter proto;
194     try {
195       proto = FilterProtos.ColumnRangeFilter.parseFrom(pbBytes);
196     } catch (InvalidProtocolBufferException e) {
197       throw new DeserializationException(e);
198     }
199     return new ColumnRangeFilter(proto.hasMinColumn()?proto.getMinColumn().toByteArray():null,
200       proto.getMinColumnInclusive(),proto.hasMaxColumn()?proto.getMaxColumn().toByteArray():null,
201       proto.getMaxColumnInclusive());
202   }
203 
204   /**
205    * @param other
206    * @return true if and only if the fields of the filter that are serialized
207    * are equal to the corresponding fields in other.  Used for testing.
208    */
209   boolean areSerializedFieldsEqual(Filter o) {
210    if (o == this) return true;
211    if (!(o instanceof ColumnRangeFilter)) return false;
212 
213    ColumnRangeFilter other = (ColumnRangeFilter)o;
214    return Bytes.equals(this.getMinColumn(),other.getMinColumn())
215      && this.getMinColumnInclusive() == other.getMinColumnInclusive()
216      && Bytes.equals(this.getMaxColumn(), other.getMaxColumn())
217      && this.getMaxColumnInclusive() == other.getMaxColumnInclusive();
218   }
219 
220   @Override
221   public Cell getNextCellHint(Cell cell) {
222     return CellUtil.createFirstOnRowCol(cell, this.minColumn, 0, len(this.minColumn));
223   }
224 
225   @Override
226   public String toString() {
227     return this.getClass().getSimpleName() + " "
228         + (this.minColumnInclusive ? "[" : "(") + Bytes.toStringBinary(this.minColumn)
229         + ", " + Bytes.toStringBinary(this.maxColumn)
230         + (this.maxColumnInclusive ? "]" : ")");
231   }
232 }