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