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