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