View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.filter;
19  
20  import java.io.IOException;
21  import java.util.ArrayList;
22  import java.util.Comparator;
23  import java.util.TreeSet;
24  
25  import org.apache.hadoop.hbase.Cell;
26  import org.apache.hadoop.hbase.CellUtil;
27  import org.apache.hadoop.hbase.classification.InterfaceAudience;
28  import org.apache.hadoop.hbase.classification.InterfaceStability;
29  import org.apache.hadoop.hbase.exceptions.DeserializationException;
30  import org.apache.hadoop.hbase.protobuf.generated.FilterProtos;
31  import org.apache.hadoop.hbase.util.ByteStringer;
32  import org.apache.hadoop.hbase.util.Bytes;
33  
34  import com.google.protobuf.InvalidProtocolBufferException;
35  
36  /**
37   * This filter is used for selecting only those keys with columns that matches
38   * a particular prefix. For example, if prefix is 'an', it will pass keys will
39   * columns like 'and', 'anti' but not keys with columns like 'ball', 'act'.
40   */
41  @InterfaceAudience.Public
42  @InterfaceStability.Stable
43  public class MultipleColumnPrefixFilter extends FilterBase {
44    protected byte [] hint = null;
45    protected TreeSet<byte []> sortedPrefixes = createTreeSet();
46    private final static int MAX_LOG_PREFIXES = 5;
47  
48    public MultipleColumnPrefixFilter(final byte [][] prefixes) {
49      if (prefixes != null) {
50        for (int i = 0; i < prefixes.length; i++) {
51          if (!sortedPrefixes.add(prefixes[i]))
52            throw new IllegalArgumentException ("prefixes must be distinct");
53        }
54      }
55    }
56  
57    public byte [][] getPrefix() {
58      int count = 0;
59      byte [][] temp = new byte [sortedPrefixes.size()][];
60      for (byte [] prefixes : sortedPrefixes) {
61        temp [count++] = prefixes;
62      }
63      return temp;
64    }
65  
66    @Override
67    public boolean filterRowKey(Cell cell) throws IOException {
68      // Impl in FilterBase might do unnecessary copy for Off heap backed Cells.
69      return false;
70    }
71  
72    @Override
73    public ReturnCode filterKeyValue(Cell kv) {
74      if (sortedPrefixes.size() == 0) {
75        return ReturnCode.INCLUDE;
76      } else {
77        return filterColumn(kv);
78      }
79    }
80  
81    public ReturnCode filterColumn(Cell cell) {
82      byte [] qualifier = CellUtil.cloneQualifier(cell);
83      TreeSet<byte []> lesserOrEqualPrefixes =
84        (TreeSet<byte []>) sortedPrefixes.headSet(qualifier, true);
85  
86      if (lesserOrEqualPrefixes.size() != 0) {
87        byte [] largestPrefixSmallerThanQualifier = lesserOrEqualPrefixes.last();
88        
89        if (Bytes.startsWith(qualifier, largestPrefixSmallerThanQualifier)) {
90          return ReturnCode.INCLUDE;
91        }
92        
93        if (lesserOrEqualPrefixes.size() == sortedPrefixes.size()) {
94          return ReturnCode.NEXT_ROW;
95        } else {
96          hint = sortedPrefixes.higher(largestPrefixSmallerThanQualifier);
97          return ReturnCode.SEEK_NEXT_USING_HINT;
98        }
99      } else {
100       hint = sortedPrefixes.first();
101       return ReturnCode.SEEK_NEXT_USING_HINT;
102     }
103   }
104 
105   public static Filter createFilterFromArguments(ArrayList<byte []> filterArguments) {
106     byte [][] prefixes = new byte [filterArguments.size()][];
107     for (int i = 0 ; i < filterArguments.size(); i++) {
108       byte [] columnPrefix = ParseFilter.removeQuotesFromByteArray(filterArguments.get(i));
109       prefixes[i] = columnPrefix;
110     }
111     return new MultipleColumnPrefixFilter(prefixes);
112   }
113 
114   /**
115    * @return The filter serialized using pb
116    */
117   public byte [] toByteArray() {
118     FilterProtos.MultipleColumnPrefixFilter.Builder builder =
119       FilterProtos.MultipleColumnPrefixFilter.newBuilder();
120     for (byte [] element : sortedPrefixes) {
121       if (element != null) builder.addSortedPrefixes(ByteStringer.wrap(element));
122     }
123     return builder.build().toByteArray();
124   }
125 
126   /**
127    * @param pbBytes A pb serialized {@link MultipleColumnPrefixFilter} instance
128    * @return An instance of {@link MultipleColumnPrefixFilter} made from <code>bytes</code>
129    * @throws DeserializationException
130    * @see #toByteArray
131    */
132   public static MultipleColumnPrefixFilter parseFrom(final byte [] pbBytes)
133   throws DeserializationException {
134     FilterProtos.MultipleColumnPrefixFilter proto;
135     try {
136       proto = FilterProtos.MultipleColumnPrefixFilter.parseFrom(pbBytes);
137     } catch (InvalidProtocolBufferException e) {
138       throw new DeserializationException(e);
139     }
140     int numPrefixes = proto.getSortedPrefixesCount();
141     byte [][] prefixes = new byte[numPrefixes][];
142     for (int i = 0; i < numPrefixes; ++i) {
143       prefixes[i] = proto.getSortedPrefixes(i).toByteArray();
144     }
145 
146     return new MultipleColumnPrefixFilter(prefixes);
147   }
148 
149   /**
150    * @param other
151    * @return true if and only if the fields of the filter that are serialized
152    * are equal to the corresponding fields in other.  Used for testing.
153    */
154   boolean areSerializedFieldsEqual(Filter o) {
155     if (o == this) return true;
156     if (!(o instanceof MultipleColumnPrefixFilter)) return false;
157 
158     MultipleColumnPrefixFilter other = (MultipleColumnPrefixFilter)o;
159     return this.sortedPrefixes.equals(other.sortedPrefixes);
160   }
161 
162   @Override
163   public Cell getNextCellHint(Cell cell) {
164     return CellUtil.createFirstOnRowCol(cell, hint, 0, hint.length);
165   }
166 
167   public TreeSet<byte []> createTreeSet() {
168     return new TreeSet<byte []>(new Comparator<Object>() {
169         @Override
170           public int compare (Object o1, Object o2) {
171           if (o1 == null || o2 == null)
172             throw new IllegalArgumentException ("prefixes can't be null");
173 
174           byte [] b1 = (byte []) o1;
175           byte [] b2 = (byte []) o2;
176           return Bytes.compareTo (b1, 0, b1.length, b2, 0, b2.length);
177         }
178       });
179   }
180 
181   @Override
182   public String toString() {
183     return toString(MAX_LOG_PREFIXES);
184   }
185 
186   protected String toString(int maxPrefixes) {
187     StringBuilder prefixes = new StringBuilder();
188 
189     int count = 0;
190     for (byte[] ba : this.sortedPrefixes) {
191       if (count >= maxPrefixes) {
192         break;
193       }
194       ++count;
195       prefixes.append(Bytes.toStringBinary(ba));
196       if (count < this.sortedPrefixes.size() && count < maxPrefixes) {
197         prefixes.append(", ");
198       }
199     }
200 
201     return String.format("%s (%d/%d): [%s]", this.getClass().getSimpleName(),
202         count, this.sortedPrefixes.size(), prefixes.toString());
203   }
204 }