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 com.google.common.base.Preconditions;
25  import org.apache.hadoop.hbase.util.ByteStringer;
26  import com.google.protobuf.InvalidProtocolBufferException;
27  
28  import org.apache.hadoop.hbase.classification.InterfaceAudience;
29  import org.apache.hadoop.hbase.classification.InterfaceStability;
30  import org.apache.hadoop.hbase.Cell;
31  import org.apache.hadoop.hbase.KeyValueUtil;
32  import org.apache.hadoop.hbase.exceptions.DeserializationException;
33  import org.apache.hadoop.hbase.protobuf.generated.FilterProtos;
34  import org.apache.hadoop.hbase.util.Bytes;
35  
36  import java.util.ArrayList;
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.NEXT_COL;
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   // Override here explicitly as the method in super class FilterBase might do a KeyValue recreate.
155   // See HBASE-12068
156   @Override
157   public Cell transformCell(Cell v) {
158     return v;
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 kv) {
227     return KeyValueUtil.createFirstOnRow(kv.getRowArray(), kv.getRowOffset(), kv
228         .getRowLength(), kv.getFamilyArray(), kv.getFamilyOffset(), kv
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 }