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   public byte [] toByteArray() {
221     FilterProtos.DependentColumnFilter.Builder builder =
222       FilterProtos.DependentColumnFilter.newBuilder();
223     builder.setCompareFilter(super.convert());
224     if (this.columnFamily != null) {
225       builder.setColumnFamily(ByteStringer.wrap(this.columnFamily));
226     }
227     if (this.columnQualifier != null) {
228       builder.setColumnQualifier(ByteStringer.wrap(this.columnQualifier));
229     }
230     builder.setDropDependentColumn(this.dropDependentColumn);
231     return builder.build().toByteArray();
232   }
233 
234   /**
235    * @param pbBytes A pb serialized {@link DependentColumnFilter} instance
236    * @return An instance of {@link DependentColumnFilter} made from <code>bytes</code>
237    * @throws DeserializationException
238    * @see #toByteArray
239    */
240   public static DependentColumnFilter parseFrom(final byte [] pbBytes)
241   throws DeserializationException {
242     FilterProtos.DependentColumnFilter proto;
243     try {
244       proto = FilterProtos.DependentColumnFilter.parseFrom(pbBytes);
245     } catch (InvalidProtocolBufferException e) {
246       throw new DeserializationException(e);
247     }
248     final CompareOp valueCompareOp =
249       CompareOp.valueOf(proto.getCompareFilter().getCompareOp().name());
250     ByteArrayComparable valueComparator = null;
251     try {
252       if (proto.getCompareFilter().hasComparator()) {
253         valueComparator = ProtobufUtil.toComparator(proto.getCompareFilter().getComparator());
254       }
255     } catch (IOException ioe) {
256       throw new DeserializationException(ioe);
257     }
258     return new DependentColumnFilter(
259       proto.hasColumnFamily()?proto.getColumnFamily().toByteArray():null,
260       proto.hasColumnQualifier()?proto.getColumnQualifier().toByteArray():null,
261       proto.getDropDependentColumn(), valueCompareOp, valueComparator);
262   }
263 
264   /**
265    * @param o
266    * @return true if and only if the fields of the filter that are serialized
267    * are equal to the corresponding fields in other.  Used for testing.
268    */
269   @edu.umd.cs.findbugs.annotations.SuppressWarnings(
270       value="RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE")
271   boolean areSerializedFieldsEqual(Filter o) {
272     if (o == this) return true;
273     if (!(o instanceof DependentColumnFilter)) return false;
274 
275     DependentColumnFilter other = (DependentColumnFilter)o;
276     return other != null && super.areSerializedFieldsEqual(other)
277       && Bytes.equals(this.getFamily(), other.getFamily())
278       && Bytes.equals(this.getQualifier(), other.getQualifier())
279       && this.dropDependentColumn() == other.dropDependentColumn();
280   }
281 
282   @Override
283   public String toString() {
284     return String.format("%s (%s, %s, %s, %s, %s)",
285         this.getClass().getSimpleName(),
286         Bytes.toStringBinary(this.columnFamily),
287         Bytes.toStringBinary(this.columnQualifier),
288         this.dropDependentColumn,
289         this.compareOp.name(),
290         this.comparator != null ? Bytes.toStringBinary(this.comparator.getValue()) : "null");
291   }
292 }