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