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 com.google.protobuf.InvalidProtocolBufferException;
21  
22  import java.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.Comparator;
25  import java.util.Objects;
26  import java.util.TreeSet;
27  
28  import org.apache.hadoop.hbase.Cell;
29  import org.apache.hadoop.hbase.KeyValueUtil;
30  import org.apache.hadoop.hbase.classification.InterfaceAudience;
31  import org.apache.hadoop.hbase.classification.InterfaceStability;
32  import org.apache.hadoop.hbase.exceptions.DeserializationException;
33  import org.apache.hadoop.hbase.protobuf.generated.FilterProtos;
34  import org.apache.hadoop.hbase.util.ByteStringer;
35  import org.apache.hadoop.hbase.util.Bytes;
36  import org.slf4j.Logger;
37  import org.slf4j.LoggerFactory;
38  
39  /**
40   * This filter is used for selecting only those keys with columns that matches
41   * a particular prefix. For example, if prefix is 'an', it will pass keys will
42   * columns like 'and', 'anti' but not keys with columns like 'ball', 'act'.
43   */
44  @InterfaceAudience.Public
45  @InterfaceStability.Stable
46  public class MultipleColumnPrefixFilter extends FilterBase {
47    private static final Logger LOG = LoggerFactory.getLogger(MultipleColumnPrefixFilter.class);
48    protected byte [] hint = null;
49    protected TreeSet<byte []> sortedPrefixes = createTreeSet();
50    private final static int MAX_LOG_PREFIXES = 5;
51  
52    public MultipleColumnPrefixFilter(final byte [][] prefixes) {
53      if (prefixes != null) {
54        for (byte[] prefix : prefixes) {
55          if (!sortedPrefixes.add(prefix)) {
56            LOG.error("prefix {} is repeated", Bytes.toString(prefix));
57            throw new IllegalArgumentException("prefixes must be distinct");
58          }
59        }
60      }
61    }
62  
63    public byte [][] getPrefix() {
64      int count = 0;
65      byte [][] temp = new byte [sortedPrefixes.size()][];
66      for (byte [] prefixes : sortedPrefixes) {
67        temp [count++] = prefixes;
68      }
69      return temp;
70    }
71  
72    @Override
73    public ReturnCode filterKeyValue(Cell kv) {
74      if (sortedPrefixes.size() == 0 || kv.getQualifierArray() == null) {
75        return ReturnCode.INCLUDE;
76      } else {
77        return filterColumn(kv.getQualifierArray(), kv.getQualifierOffset(), kv.getQualifierLength());
78      }
79    }
80  
81    // Override here explicitly as the method in super class FilterBase might do a KeyValue recreate.
82    // See HBASE-12068
83    @Override
84    public Cell transformCell(Cell v) {
85      return v;
86    }
87  
88    public ReturnCode filterColumn(byte[] buffer, int qualifierOffset, int qualifierLength) {
89      byte [] qualifier = Arrays.copyOfRange(buffer, qualifierOffset,
90                                             qualifierLength + qualifierOffset);
91      TreeSet<byte []> lesserOrEqualPrefixes =
92        (TreeSet<byte []>) sortedPrefixes.headSet(qualifier, true);
93  
94      if (lesserOrEqualPrefixes.size() != 0) {
95        byte [] largestPrefixSmallerThanQualifier = lesserOrEqualPrefixes.last();
96        
97        if (Bytes.startsWith(qualifier, largestPrefixSmallerThanQualifier)) {
98          return ReturnCode.INCLUDE;
99        }
100       
101       if (lesserOrEqualPrefixes.size() == sortedPrefixes.size()) {
102         return ReturnCode.NEXT_ROW;
103       } else {
104         hint = sortedPrefixes.higher(largestPrefixSmallerThanQualifier);
105         return ReturnCode.SEEK_NEXT_USING_HINT;
106       }
107     } else {
108       hint = sortedPrefixes.first();
109       return ReturnCode.SEEK_NEXT_USING_HINT;
110     }
111   }
112 
113   public static Filter createFilterFromArguments(ArrayList<byte []> filterArguments) {
114     byte [][] prefixes = new byte [filterArguments.size()][];
115     for (int i = 0 ; i < filterArguments.size(); i++) {
116       byte [] columnPrefix = ParseFilter.removeQuotesFromByteArray(filterArguments.get(i));
117       prefixes[i] = columnPrefix;
118     }
119     return new MultipleColumnPrefixFilter(prefixes);
120   }
121 
122   /**
123    * @return The filter serialized using pb
124    */
125   @Override
126   public byte [] toByteArray() {
127     FilterProtos.MultipleColumnPrefixFilter.Builder builder =
128       FilterProtos.MultipleColumnPrefixFilter.newBuilder();
129     for (byte [] element : sortedPrefixes) {
130       if (element != null) builder.addSortedPrefixes(ByteStringer.wrap(element));
131     }
132     return builder.build().toByteArray();
133   }
134 
135   /**
136    * @param pbBytes A pb serialized {@link MultipleColumnPrefixFilter} instance
137    * @return An instance of {@link MultipleColumnPrefixFilter} made from <code>bytes</code>
138    * @throws DeserializationException
139    * @see #toByteArray
140    */
141   public static MultipleColumnPrefixFilter parseFrom(final byte [] pbBytes)
142   throws DeserializationException {
143     FilterProtos.MultipleColumnPrefixFilter proto;
144     try {
145       proto = FilterProtos.MultipleColumnPrefixFilter.parseFrom(pbBytes);
146     } catch (InvalidProtocolBufferException e) {
147       throw new DeserializationException(e);
148     }
149     int numPrefixes = proto.getSortedPrefixesCount();
150     byte [][] prefixes = new byte[numPrefixes][];
151     for (int i = 0; i < numPrefixes; ++i) {
152       prefixes[i] = proto.getSortedPrefixes(i).toByteArray();
153     }
154 
155     return new MultipleColumnPrefixFilter(prefixes);
156   }
157 
158   /**
159    * @param other
160    * @return true if and only if the fields of the filter that are serialized
161    * are equal to the corresponding fields in other.  Used for testing.
162    */
163   @Override
164   boolean areSerializedFieldsEqual(Filter o) {
165     if (o == this) return true;
166     if (!(o instanceof MultipleColumnPrefixFilter)) return false;
167 
168     MultipleColumnPrefixFilter other = (MultipleColumnPrefixFilter)o;
169     return this.sortedPrefixes.equals(other.sortedPrefixes);
170   }
171 
172   @Override
173   public Cell getNextCellHint(Cell kv) {
174     return KeyValueUtil.createFirstOnRow(
175       kv.getRowArray(), kv.getRowOffset(), kv.getRowLength(), kv.getFamilyArray(),
176       kv.getFamilyOffset(), kv.getFamilyLength(), hint, 0, hint.length);
177   }
178 
179   public TreeSet<byte []> createTreeSet() {
180     return new TreeSet<byte []>(new Comparator<Object>() {
181         @Override
182           public int compare (Object o1, Object o2) {
183           if (o1 == null || o2 == null)
184             throw new IllegalArgumentException ("prefixes can't be null");
185 
186           byte [] b1 = (byte []) o1;
187           byte [] b2 = (byte []) o2;
188           return Bytes.compareTo (b1, 0, b1.length, b2, 0, b2.length);
189         }
190       });
191   }
192 
193   @Override
194   public String toString() {
195     return toString(MAX_LOG_PREFIXES);
196   }
197 
198   protected String toString(int maxPrefixes) {
199     StringBuilder prefixes = new StringBuilder();
200 
201     int count = 0;
202     for (byte[] ba : this.sortedPrefixes) {
203       if (count >= maxPrefixes) {
204         break;
205       }
206       ++count;
207       prefixes.append(Bytes.toStringBinary(ba));
208       if (count < this.sortedPrefixes.size() && count < maxPrefixes) {
209         prefixes.append(", ");
210       }
211     }
212 
213     return String.format("%s (%d/%d): [%s]", this.getClass().getSimpleName(),
214         count, this.sortedPrefixes.size(), prefixes.toString());
215   }
216 
217   @Override
218   public boolean equals(Object obj) {
219     return obj instanceof Filter && areSerializedFieldsEqual((Filter) obj);
220   }
221 
222   @Override
223   public int hashCode() {
224     return Objects.hash(this.sortedPrefixes);
225   }
226 }