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  package org.apache.hadoop.hbase.filter;
20  
21  import java.io.IOException;
22  import java.util.ArrayList;
23  import java.util.HashSet;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Set;
27  
28  import org.apache.hadoop.hbase.util.ByteStringer;
29  import org.apache.hadoop.hbase.classification.InterfaceAudience;
30  import org.apache.hadoop.hbase.classification.InterfaceStability;
31  import org.apache.hadoop.hbase.Cell;
32  import org.apache.hadoop.hbase.CellUtil;
33  import org.apache.hadoop.hbase.exceptions.DeserializationException;
34  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
35  import org.apache.hadoop.hbase.protobuf.generated.FilterProtos;
36  import org.apache.hadoop.hbase.util.Bytes;
37  
38  import com.google.common.base.Preconditions;
39  import com.google.protobuf.InvalidProtocolBufferException;
40  
41  /**
42   * A filter for adding inter-column timestamp matching
43   * Only cells with a correspondingly timestamped entry in
44   * the target column will be retained
45   * Not compatible with Scan.setBatch as operations need 
46   * full rows for correct filtering 
47   */
48  @InterfaceAudience.Public
49  @InterfaceStability.Stable
50  public class DependentColumnFilter extends CompareFilter {
51  
52    protected byte[] columnFamily;
53    protected byte[] columnQualifier;
54    protected boolean dropDependentColumn;
55  
56    protected Set<Long> stampSet = new HashSet<Long>();
57    
58    /**
59     * Build a dependent column filter with value checking
60     * dependent column varies will be compared using the supplied
61     * compareOp and comparator, for usage of which
62     * refer to {@link CompareFilter}
63     * 
64     * @param family dependent column family
65     * @param qualifier dependent column qualifier
66     * @param dropDependentColumn whether the column should be discarded after
67     * @param valueCompareOp comparison op 
68     * @param valueComparator comparator
69     */
70    public DependentColumnFilter(final byte [] family, final byte[] qualifier,
71        final boolean dropDependentColumn, final CompareOp valueCompareOp,
72        final ByteArrayComparable valueComparator) {
73      // set up the comparator   
74      super(valueCompareOp, valueComparator);
75      this.columnFamily = family;
76      this.columnQualifier = qualifier;
77      this.dropDependentColumn = dropDependentColumn;
78    }
79    
80    /**
81     * Constructor for DependentColumn filter.
82     * Cells where a Cell from target column
83     * with the same timestamp do not exist will be dropped.
84     *
85     * @param family name of target column family
86     * @param qualifier name of column qualifier
87     */
88    public DependentColumnFilter(final byte [] family, final byte [] qualifier) {
89      this(family, qualifier, false);
90    }
91    
92    /**
93     * Constructor for DependentColumn filter.
94     * Cells where a Cell from target column
95     * with the same timestamp do not exist will be dropped.
96     *
97     * @param family name of dependent column family
98     * @param qualifier name of dependent qualifier
99     * @param dropDependentColumn whether the dependent columns Cells should be discarded
100    */
101   public DependentColumnFilter(final byte [] family, final byte [] qualifier,
102       final boolean dropDependentColumn) {
103     this(family, qualifier, dropDependentColumn, CompareOp.NO_OP, null);
104   }
105 
106   /**
107    * @return the column family
108    */
109   public byte[] getFamily() {
110     return this.columnFamily;
111   }
112 
113   /**
114    * @return the column qualifier
115    */
116   public byte[] getQualifier() {
117     return this.columnQualifier;
118   }
119 
120   /**
121    * @return true if we should drop the dependent column, false otherwise
122    */
123   public boolean dropDependentColumn() {
124     return this.dropDependentColumn;
125   }
126 
127   public boolean getDropDependentColumn() {
128     return this.dropDependentColumn;
129   }
130 
131   @Override
132   public boolean filterAllRemaining() {
133     return false;
134   }
135 
136   @Override
137   public ReturnCode filterKeyValue(Cell c) {
138     // Check if the column and qualifier match
139     if (!CellUtil.matchingColumn(c, this.columnFamily, this.columnQualifier)) {
140       // include non-matches for the time being, they'll be discarded afterwards
141       return ReturnCode.INCLUDE;
142     }
143     // If it doesn't pass the op, skip it
144     if (comparator != null
145         && doCompare(compareOp, comparator, c.getValueArray(), c.getValueOffset(),
146             c.getValueLength()))
147       return ReturnCode.SKIP;
148 
149     stampSet.add(c.getTimestamp());
150     if(dropDependentColumn) {
151       return ReturnCode.SKIP;
152     }
153     return ReturnCode.INCLUDE;
154   }
155 
156   @Override
157   public void filterRowCells(List<Cell> kvs) {
158     Iterator<? extends Cell> it = kvs.iterator();
159     Cell kv;
160     while(it.hasNext()) {
161       kv = it.next();
162       if(!stampSet.contains(kv.getTimestamp())) {
163         it.remove();
164       }
165     }
166   }
167 
168   @Override
169   public boolean hasFilterRow() {
170     return true;
171   }
172   
173   @Override
174   public boolean filterRow() {
175     return false;
176   }
177 
178   @Override
179   public boolean filterRowKey(byte[] buffer, int offset, int length) {
180     return false;
181   }
182   @Override
183   public void reset() {
184     stampSet.clear();    
185   }
186 
187   public static Filter createFilterFromArguments(ArrayList<byte []> filterArguments) {
188     Preconditions.checkArgument(filterArguments.size() == 2 ||
189                                 filterArguments.size() == 3 ||
190                                 filterArguments.size() == 5,
191                                 "Expected 2, 3 or 5 but got: %s", filterArguments.size());
192     if (filterArguments.size() == 2) {
193       byte [] family = ParseFilter.removeQuotesFromByteArray(filterArguments.get(0));
194       byte [] qualifier = ParseFilter.removeQuotesFromByteArray(filterArguments.get(1));
195       return new DependentColumnFilter(family, qualifier);
196 
197     } else if (filterArguments.size() == 3) {
198       byte [] family = ParseFilter.removeQuotesFromByteArray(filterArguments.get(0));
199       byte [] qualifier = ParseFilter.removeQuotesFromByteArray(filterArguments.get(1));
200       boolean dropDependentColumn = ParseFilter.convertByteArrayToBoolean(filterArguments.get(2));
201       return new DependentColumnFilter(family, qualifier, dropDependentColumn);
202 
203     } else if (filterArguments.size() == 5) {
204       byte [] family = ParseFilter.removeQuotesFromByteArray(filterArguments.get(0));
205       byte [] qualifier = ParseFilter.removeQuotesFromByteArray(filterArguments.get(1));
206       boolean dropDependentColumn = ParseFilter.convertByteArrayToBoolean(filterArguments.get(2));
207       CompareOp compareOp = ParseFilter.createCompareOp(filterArguments.get(3));
208       ByteArrayComparable comparator = ParseFilter.createComparator(
209         ParseFilter.removeQuotesFromByteArray(filterArguments.get(4)));
210       return new DependentColumnFilter(family, qualifier, dropDependentColumn,
211                                        compareOp, comparator);
212     } else {
213       throw new IllegalArgumentException("Expected 2, 3 or 5 but got: " + filterArguments.size());
214     }
215   }
216 
217   /**
218    * @return The filter serialized using pb
219    */
220   @Override
221   public byte [] toByteArray() {
222     FilterProtos.DependentColumnFilter.Builder builder =
223       FilterProtos.DependentColumnFilter.newBuilder();
224     builder.setCompareFilter(super.convert());
225     if (this.columnFamily != null) {
226       builder.setColumnFamily(ByteStringer.wrap(this.columnFamily));
227     }
228     if (this.columnQualifier != null) {
229       builder.setColumnQualifier(ByteStringer.wrap(this.columnQualifier));
230     }
231     builder.setDropDependentColumn(this.dropDependentColumn);
232     return builder.build().toByteArray();
233   }
234 
235   /**
236    * @param pbBytes A pb serialized {@link DependentColumnFilter} instance
237    * @return An instance of {@link DependentColumnFilter} made from <code>bytes</code>
238    * @throws DeserializationException
239    * @see #toByteArray
240    */
241   public static DependentColumnFilter parseFrom(final byte [] pbBytes)
242   throws DeserializationException {
243     FilterProtos.DependentColumnFilter proto;
244     try {
245       proto = FilterProtos.DependentColumnFilter.parseFrom(pbBytes);
246     } catch (InvalidProtocolBufferException e) {
247       throw new DeserializationException(e);
248     }
249     final CompareOp valueCompareOp =
250       CompareOp.valueOf(proto.getCompareFilter().getCompareOp().name());
251     ByteArrayComparable valueComparator = null;
252     try {
253       if (proto.getCompareFilter().hasComparator()) {
254         valueComparator = ProtobufUtil.toComparator(proto.getCompareFilter().getComparator());
255       }
256     } catch (IOException ioe) {
257       throw new DeserializationException(ioe);
258     }
259     return new DependentColumnFilter(
260       proto.hasColumnFamily()?proto.getColumnFamily().toByteArray():null,
261       proto.hasColumnQualifier()?proto.getColumnQualifier().toByteArray():null,
262       proto.getDropDependentColumn(), valueCompareOp, valueComparator);
263   }
264 
265   /**
266    * @param o
267    * @return true if and only if the fields of the filter that are serialized
268    * are equal to the corresponding fields in other.  Used for testing.
269    */
270   @edu.umd.cs.findbugs.annotations.SuppressWarnings(
271       value="RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE")
272   @Override
273   boolean areSerializedFieldsEqual(Filter o) {
274     if (o == this) return true;
275     if (!(o instanceof DependentColumnFilter)) return false;
276 
277     DependentColumnFilter other = (DependentColumnFilter)o;
278     return other != null && super.areSerializedFieldsEqual(other)
279       && Bytes.equals(this.getFamily(), other.getFamily())
280       && Bytes.equals(this.getQualifier(), other.getQualifier())
281       && this.dropDependentColumn() == other.dropDependentColumn();
282   }
283 
284   @Override
285   public String toString() {
286     return String.format("%s (%s, %s, %s, %s, %s)",
287         this.getClass().getSimpleName(),
288         Bytes.toStringBinary(this.columnFamily),
289         Bytes.toStringBinary(this.columnQualifier),
290         this.dropDependentColumn,
291         this.compareOp.name(),
292         this.comparator != null ? Bytes.toStringBinary(this.comparator.getValue()) : "null");
293   }
294 }