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