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