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.Cell;
29  import org.apache.hadoop.hbase.CellUtil;
30  import org.apache.hadoop.hbase.classification.InterfaceAudience;
31  import org.apache.hadoop.hbase.classification.InterfaceStability;
32  import org.apache.hadoop.hbase.exceptions.DeserializationException;
33  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
34  import org.apache.hadoop.hbase.protobuf.generated.FilterProtos;
35  import org.apache.hadoop.hbase.util.ByteStringer;
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         && compareValue(compareOp, comparator, c))
146       return ReturnCode.SKIP;
147   
148     stampSet.add(c.getTimestamp());
149     if(dropDependentColumn) {
150       return ReturnCode.SKIP;
151     }
152     return ReturnCode.INCLUDE;
153   }
154 
155   @Override
156   public void filterRowCells(List<Cell> kvs) {
157     Iterator<? extends Cell> it = kvs.iterator();
158     Cell kv;
159     while(it.hasNext()) {
160       kv = it.next();
161       if(!stampSet.contains(kv.getTimestamp())) {
162         it.remove();
163       }
164     }
165   }
166 
167   @Override
168   public boolean hasFilterRow() {
169     return true;
170   }
171   
172   @Override
173   public boolean filterRow() {
174     return false;
175   }
176 
177   @Override
178   public boolean filterRowKey(byte[] buffer, int offset, int length) {
179     return false;
180   }
181   @Override
182   public void reset() {
183     stampSet.clear();    
184   }
185 
186   public static Filter createFilterFromArguments(ArrayList<byte []> filterArguments) {
187     Preconditions.checkArgument(filterArguments.size() == 2 ||
188                                 filterArguments.size() == 3 ||
189                                 filterArguments.size() == 5,
190                                 "Expected 2, 3 or 5 but got: %s", filterArguments.size());
191     if (filterArguments.size() == 2) {
192       byte [] family = ParseFilter.removeQuotesFromByteArray(filterArguments.get(0));
193       byte [] qualifier = ParseFilter.removeQuotesFromByteArray(filterArguments.get(1));
194       return new DependentColumnFilter(family, qualifier);
195 
196     } else if (filterArguments.size() == 3) {
197       byte [] family = ParseFilter.removeQuotesFromByteArray(filterArguments.get(0));
198       byte [] qualifier = ParseFilter.removeQuotesFromByteArray(filterArguments.get(1));
199       boolean dropDependentColumn = ParseFilter.convertByteArrayToBoolean(filterArguments.get(2));
200       return new DependentColumnFilter(family, qualifier, dropDependentColumn);
201 
202     } else if (filterArguments.size() == 5) {
203       byte [] family = ParseFilter.removeQuotesFromByteArray(filterArguments.get(0));
204       byte [] qualifier = ParseFilter.removeQuotesFromByteArray(filterArguments.get(1));
205       boolean dropDependentColumn = ParseFilter.convertByteArrayToBoolean(filterArguments.get(2));
206       CompareOp compareOp = ParseFilter.createCompareOp(filterArguments.get(3));
207       ByteArrayComparable comparator = ParseFilter.createComparator(
208         ParseFilter.removeQuotesFromByteArray(filterArguments.get(4)));
209       return new DependentColumnFilter(family, qualifier, dropDependentColumn,
210                                        compareOp, comparator);
211     } else {
212       throw new IllegalArgumentException("Expected 2, 3 or 5 but got: " + filterArguments.size());
213     }
214   }
215 
216   /**
217    * @return The filter serialized using pb
218    */
219   public byte [] toByteArray() {
220     FilterProtos.DependentColumnFilter.Builder builder =
221       FilterProtos.DependentColumnFilter.newBuilder();
222     builder.setCompareFilter(super.convert());
223     if (this.columnFamily != null) {
224       builder.setColumnFamily(ByteStringer.wrap(this.columnFamily));
225     }
226     if (this.columnQualifier != null) {
227       builder.setColumnQualifier(ByteStringer.wrap(this.columnQualifier));
228     }
229     builder.setDropDependentColumn(this.dropDependentColumn);
230     return builder.build().toByteArray();
231   }
232 
233   /**
234    * @param pbBytes A pb serialized {@link DependentColumnFilter} instance
235    * @return An instance of {@link DependentColumnFilter} made from <code>bytes</code>
236    * @throws DeserializationException
237    * @see #toByteArray
238    */
239   public static DependentColumnFilter parseFrom(final byte [] pbBytes)
240   throws DeserializationException {
241     FilterProtos.DependentColumnFilter proto;
242     try {
243       proto = FilterProtos.DependentColumnFilter.parseFrom(pbBytes);
244     } catch (InvalidProtocolBufferException e) {
245       throw new DeserializationException(e);
246     }
247     final CompareOp valueCompareOp =
248       CompareOp.valueOf(proto.getCompareFilter().getCompareOp().name());
249     ByteArrayComparable valueComparator = null;
250     try {
251       if (proto.getCompareFilter().hasComparator()) {
252         valueComparator = ProtobufUtil.toComparator(proto.getCompareFilter().getComparator());
253       }
254     } catch (IOException ioe) {
255       throw new DeserializationException(ioe);
256     }
257     return new DependentColumnFilter(
258       proto.hasColumnFamily()?proto.getColumnFamily().toByteArray():null,
259       proto.hasColumnQualifier()?proto.getColumnQualifier().toByteArray():null,
260       proto.getDropDependentColumn(), valueCompareOp, valueComparator);
261   }
262 
263   /**
264    * @param o
265    * @return true if and only if the fields of the filter that are serialized
266    * are equal to the corresponding fields in other.  Used for testing.
267    */
268   @edu.umd.cs.findbugs.annotations.SuppressWarnings(
269       value="RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE")
270   boolean areSerializedFieldsEqual(Filter o) {
271     if (o == this) return true;
272     if (!(o instanceof DependentColumnFilter)) return false;
273 
274     DependentColumnFilter other = (DependentColumnFilter)o;
275     return other != null && super.areSerializedFieldsEqual(other)
276       && Bytes.equals(this.getFamily(), other.getFamily())
277       && Bytes.equals(this.getQualifier(), other.getQualifier())
278       && this.dropDependentColumn() == other.dropDependentColumn();
279   }
280 
281   @Override
282   public String toString() {
283     return String.format("%s (%s, %s, %s, %s, %s)",
284         this.getClass().getSimpleName(),
285         Bytes.toStringBinary(this.columnFamily),
286         Bytes.toStringBinary(this.columnQualifier),
287         this.dropDependentColumn,
288         this.compareOp.name(),
289         this.comparator != null ? Bytes.toStringBinary(this.comparator.getValue()) : "null");
290   }
291 }