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