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