001/**
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *     http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019package org.apache.hadoop.hbase.filter;
020
021import java.io.IOException;
022import java.util.ArrayList;
023import java.util.HashSet;
024import java.util.List;
025import java.util.Objects;
026import java.util.Set;
027
028import org.apache.hadoop.hbase.Cell;
029import org.apache.hadoop.hbase.CellUtil;
030import org.apache.hadoop.hbase.CompareOperator;
031import org.apache.yetus.audience.InterfaceAudience;
032import org.apache.hadoop.hbase.exceptions.DeserializationException;
033import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
034import org.apache.hadoop.hbase.shaded.protobuf.generated.FilterProtos;
035import org.apache.hadoop.hbase.util.Bytes;
036
037import org.apache.hbase.thirdparty.com.google.common.base.Preconditions;
038
039import org.apache.hbase.thirdparty.com.google.protobuf.InvalidProtocolBufferException;
040import org.apache.hbase.thirdparty.com.google.protobuf.UnsafeByteOperations;
041
042/**
043 * A filter for adding inter-column timestamp matching
044 * Only cells with a correspondingly timestamped entry in
045 * the target column will be retained
046 * Not compatible with Scan.setBatch as operations need 
047 * full rows for correct filtering 
048 */
049@InterfaceAudience.Public
050public class DependentColumnFilter extends CompareFilter {
051
052  protected byte[] columnFamily;
053  protected byte[] columnQualifier;
054  protected boolean dropDependentColumn;
055
056  protected Set<Long> stampSet = new HashSet<>();
057  
058  /**
059   * Build a dependent column filter with value checking
060   * dependent column varies will be compared using the supplied
061   * compareOp and comparator, for usage of which
062   * refer to {@link CompareFilter}
063   * 
064   * @param family dependent column family
065   * @param qualifier dependent column qualifier
066   * @param dropDependentColumn whether the column should be discarded after
067   * @param valueCompareOp comparison op 
068   * @param valueComparator comparator
069   * @deprecated Since 2.0.0. Will be removed in 3.0.0. Use
070   * {@link #DependentColumnFilter(byte[], byte[], boolean, CompareOperator, ByteArrayComparable)}
071   * instead.
072   */
073  @Deprecated
074  public DependentColumnFilter(final byte [] family, final byte[] qualifier,
075      final boolean dropDependentColumn, final CompareOp valueCompareOp,
076        final ByteArrayComparable valueComparator) {
077    this(family, qualifier, dropDependentColumn, CompareOperator.valueOf(valueCompareOp.name()),
078      valueComparator);
079  }
080
081  /**
082   * Build a dependent column filter with value checking
083   * dependent column varies will be compared using the supplied
084   * compareOp and comparator, for usage of which
085   * refer to {@link CompareFilter}
086   *
087   * @param family dependent column family
088   * @param qualifier dependent column qualifier
089   * @param dropDependentColumn whether the column should be discarded after
090   * @param op Value comparison op
091   * @param valueComparator comparator
092   */
093  public DependentColumnFilter(final byte [] family, final byte[] qualifier,
094                               final boolean dropDependentColumn, final CompareOperator op,
095                               final ByteArrayComparable valueComparator) {
096    // set up the comparator
097    super(op, valueComparator);
098    this.columnFamily = family;
099    this.columnQualifier = qualifier;
100    this.dropDependentColumn = dropDependentColumn;
101  }
102  
103  /**
104   * Constructor for DependentColumn filter.
105   * Cells where a Cell from target column
106   * with the same timestamp do not exist will be dropped.
107   *
108   * @param family name of target column family
109   * @param qualifier name of column qualifier
110   */
111  public DependentColumnFilter(final byte [] family, final byte [] qualifier) {
112    this(family, qualifier, false);
113  }
114  
115  /**
116   * Constructor for DependentColumn filter.
117   * Cells where a Cell from target column
118   * with the same timestamp do not exist will be dropped.
119   *
120   * @param family name of dependent column family
121   * @param qualifier name of dependent qualifier
122   * @param dropDependentColumn whether the dependent columns Cells should be discarded
123   */
124  public DependentColumnFilter(final byte [] family, final byte [] qualifier,
125      final boolean dropDependentColumn) {
126    this(family, qualifier, dropDependentColumn, CompareOp.NO_OP, null);
127  }
128
129  /**
130   * @return the column family
131   */
132  public byte[] getFamily() {
133    return this.columnFamily;
134  }
135
136  /**
137   * @return the column qualifier
138   */
139  public byte[] getQualifier() {
140    return this.columnQualifier;
141  }
142
143  /**
144   * @return true if we should drop the dependent column, false otherwise
145   */
146  public boolean dropDependentColumn() {
147    return this.dropDependentColumn;
148  }
149
150  public boolean getDropDependentColumn() {
151    return this.dropDependentColumn;
152  }
153
154  @Override
155  public boolean filterAllRemaining() {
156    return false;
157  }
158
159  @Deprecated
160  @Override
161  public ReturnCode filterKeyValue(final Cell c) {
162    return filterCell(c);
163  }
164
165  @Override
166  public ReturnCode filterCell(final Cell c) {
167    // Check if the column and qualifier match
168    if (!CellUtil.matchingColumn(c, this.columnFamily, this.columnQualifier)) {
169        // include non-matches for the time being, they'll be discarded afterwards
170        return ReturnCode.INCLUDE;
171    }
172    // If it doesn't pass the op, skip it
173    if (comparator != null
174        && compareValue(getCompareOperator(), comparator, c))
175      return ReturnCode.SKIP;
176  
177    stampSet.add(c.getTimestamp());
178    if(dropDependentColumn) {
179      return ReturnCode.SKIP;
180    }
181    return ReturnCode.INCLUDE;
182  }
183
184  @Override
185  public void filterRowCells(List<Cell> kvs) {
186    kvs.removeIf(kv -> !stampSet.contains(kv.getTimestamp()));
187  }
188
189  @Override
190  public boolean hasFilterRow() {
191    return true;
192  }
193  
194  @Override
195  public boolean filterRow() {
196    return false;
197  }
198
199  @Override
200  public boolean filterRowKey(byte[] buffer, int offset, int length) {
201    return false;
202  }
203  @Override
204  public void reset() {
205    stampSet.clear();    
206  }
207
208  public static Filter createFilterFromArguments(ArrayList<byte []> filterArguments) {
209    Preconditions.checkArgument(filterArguments.size() == 2 ||
210                                filterArguments.size() == 3 ||
211                                filterArguments.size() == 5,
212                                "Expected 2, 3 or 5 but got: %s", filterArguments.size());
213    if (filterArguments.size() == 2) {
214      byte [] family = ParseFilter.removeQuotesFromByteArray(filterArguments.get(0));
215      byte [] qualifier = ParseFilter.removeQuotesFromByteArray(filterArguments.get(1));
216      return new DependentColumnFilter(family, qualifier);
217
218    } else if (filterArguments.size() == 3) {
219      byte [] family = ParseFilter.removeQuotesFromByteArray(filterArguments.get(0));
220      byte [] qualifier = ParseFilter.removeQuotesFromByteArray(filterArguments.get(1));
221      boolean dropDependentColumn = ParseFilter.convertByteArrayToBoolean(filterArguments.get(2));
222      return new DependentColumnFilter(family, qualifier, dropDependentColumn);
223
224    } else if (filterArguments.size() == 5) {
225      byte [] family = ParseFilter.removeQuotesFromByteArray(filterArguments.get(0));
226      byte [] qualifier = ParseFilter.removeQuotesFromByteArray(filterArguments.get(1));
227      boolean dropDependentColumn = ParseFilter.convertByteArrayToBoolean(filterArguments.get(2));
228      CompareOperator op = ParseFilter.createCompareOperator(filterArguments.get(3));
229      ByteArrayComparable comparator = ParseFilter.createComparator(
230        ParseFilter.removeQuotesFromByteArray(filterArguments.get(4)));
231      return new DependentColumnFilter(family, qualifier, dropDependentColumn,
232                                       op, comparator);
233    } else {
234      throw new IllegalArgumentException("Expected 2, 3 or 5 but got: " + filterArguments.size());
235    }
236  }
237
238  /**
239   * @return The filter serialized using pb
240   */
241  @Override
242  public byte [] toByteArray() {
243    FilterProtos.DependentColumnFilter.Builder builder =
244      FilterProtos.DependentColumnFilter.newBuilder();
245    builder.setCompareFilter(super.convert());
246    if (this.columnFamily != null) {
247      builder.setColumnFamily(UnsafeByteOperations.unsafeWrap(this.columnFamily));
248    }
249    if (this.columnQualifier != null) {
250      builder.setColumnQualifier(UnsafeByteOperations.unsafeWrap(this.columnQualifier));
251    }
252    builder.setDropDependentColumn(this.dropDependentColumn);
253    return builder.build().toByteArray();
254  }
255
256  /**
257   * @param pbBytes A pb serialized {@link DependentColumnFilter} instance
258   * @return An instance of {@link DependentColumnFilter} made from <code>bytes</code>
259   * @throws DeserializationException
260   * @see #toByteArray
261   */
262  public static DependentColumnFilter parseFrom(final byte [] pbBytes)
263  throws DeserializationException {
264    FilterProtos.DependentColumnFilter proto;
265    try {
266      proto = FilterProtos.DependentColumnFilter.parseFrom(pbBytes);
267    } catch (InvalidProtocolBufferException e) {
268      throw new DeserializationException(e);
269    }
270    final CompareOperator valueCompareOp =
271    CompareOperator.valueOf(proto.getCompareFilter().getCompareOp().name());
272    ByteArrayComparable valueComparator = null;
273    try {
274      if (proto.getCompareFilter().hasComparator()) {
275        valueComparator = ProtobufUtil.toComparator(proto.getCompareFilter().getComparator());
276      }
277    } catch (IOException ioe) {
278      throw new DeserializationException(ioe);
279    }
280    return new DependentColumnFilter(
281      proto.hasColumnFamily()?proto.getColumnFamily().toByteArray():null,
282      proto.hasColumnQualifier()?proto.getColumnQualifier().toByteArray():null,
283      proto.getDropDependentColumn(), valueCompareOp, valueComparator);
284  }
285
286  /**
287   * @param o
288   * @return true if and only if the fields of the filter that are serialized
289   * are equal to the corresponding fields in other.  Used for testing.
290   */
291  @edu.umd.cs.findbugs.annotations.SuppressWarnings(
292      value="RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE")
293  @Override
294  boolean areSerializedFieldsEqual(Filter o) {
295    if (o == this) return true;
296    if (!(o instanceof DependentColumnFilter)) return false;
297
298    DependentColumnFilter other = (DependentColumnFilter)o;
299    return other != null && super.areSerializedFieldsEqual(other)
300      && Bytes.equals(this.getFamily(), other.getFamily())
301      && Bytes.equals(this.getQualifier(), other.getQualifier())
302      && this.dropDependentColumn() == other.dropDependentColumn();
303  }
304
305  @Override
306  public String toString() {
307    return String.format("%s (%s, %s, %s, %s, %s)",
308        this.getClass().getSimpleName(),
309        Bytes.toStringBinary(this.columnFamily),
310        Bytes.toStringBinary(this.columnQualifier),
311        this.dropDependentColumn,
312        this.op.name(),
313        this.comparator != null ? Bytes.toStringBinary(this.comparator.getValue()) : "null");
314  }
315
316  @Override
317  public boolean equals(Object obj) {
318    return obj instanceof Filter && areSerializedFieldsEqual((Filter) obj);
319  }
320
321  @Override
322  public int hashCode() {
323    return Objects.hash(Bytes.hashCode(getFamily()), Bytes.hashCode(getQualifier()),
324      dropDependentColumn(), getComparator(), getCompareOperator());
325  }
326}