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