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 */
019
020package org.apache.hadoop.hbase.filter;
021
022import java.io.IOException;
023import java.util.ArrayList;
024import java.util.Objects;
025
026import org.apache.hadoop.hbase.Cell;
027import org.apache.hadoop.hbase.CellUtil;
028import org.apache.hadoop.hbase.CompareOperator;
029import org.apache.hadoop.hbase.PrivateCellUtil;
030import org.apache.hadoop.hbase.exceptions.DeserializationException;
031import org.apache.hadoop.hbase.util.Bytes;
032import org.apache.yetus.audience.InterfaceAudience;
033
034import org.apache.hbase.thirdparty.com.google.common.base.Preconditions;
035import org.apache.hbase.thirdparty.com.google.protobuf.InvalidProtocolBufferException;
036import org.apache.hbase.thirdparty.com.google.protobuf.UnsafeByteOperations;
037
038import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
039import org.apache.hadoop.hbase.shaded.protobuf.generated.FilterProtos;
040import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos;
041
042/**
043 * Different from {@link SingleColumnValueFilter} which returns an <b>entire</b> row
044 * when specified condition is matched, {@link ColumnValueFilter} return the matched cell only.
045 * <p>
046 * This filter is used to filter cells based on column and value.
047 * It takes a {@link org.apache.hadoop.hbase.CompareOperator} operator (<, <=, =, !=, >, >=), and
048 * and a {@link ByteArrayComparable} comparator.
049 */
050@InterfaceAudience.Public
051public class ColumnValueFilter extends FilterBase {
052  private final byte[] family;
053  private final byte[] qualifier;
054  private final CompareOperator op;
055  private final ByteArrayComparable comparator;
056
057  // This flag is used to speed up seeking cells when matched column is found, such that following
058  // columns in the same row can be skipped faster by NEXT_ROW instead of NEXT_COL.
059  private boolean columnFound = false;
060
061  public ColumnValueFilter(final byte[] family, final byte[] qualifier,
062                           final CompareOperator op, final byte[] value) {
063    this(family, qualifier, op, new BinaryComparator(value));
064  }
065
066  public ColumnValueFilter(final byte[] family, final byte[] qualifier,
067                           final CompareOperator op,
068                           final ByteArrayComparable comparator) {
069    this.family = Preconditions.checkNotNull(family, "family should not be null.");
070    this.qualifier = qualifier == null ? new byte[0] : qualifier;
071    this.op = Preconditions.checkNotNull(op, "CompareOperator should not be null");
072    this.comparator = Preconditions.checkNotNull(comparator, "Comparator should not be null");
073  }
074
075  /**
076   * @return operator
077   */
078  public CompareOperator getCompareOperator() {
079    return op;
080  }
081
082  /**
083   * @return the comparator
084   */
085  public ByteArrayComparable getComparator() {
086    return comparator;
087  }
088
089  /**
090   * @return the column family
091   */
092  public byte[] getFamily() {
093    return family;
094  }
095
096  /**
097   * @return the qualifier
098   */
099  public byte[] getQualifier() {
100    return qualifier;
101  }
102
103  @Override
104  public void reset() throws IOException {
105    columnFound = false;
106  }
107
108  @Override
109  public boolean filterRowKey(Cell cell) throws IOException {
110    return false;
111  }
112
113  @Override
114  public ReturnCode filterCell(Cell c) throws IOException {
115    // 1. Check column match
116    if (!CellUtil.matchingColumn(c, this.family, this.qualifier)) {
117      return columnFound ? ReturnCode.NEXT_ROW : ReturnCode.NEXT_COL;
118    }
119    // Column found
120    columnFound = true;
121    // 2. Check value match:
122    // True means filter out, just skip this cell, else include it.
123    return compareValue(getCompareOperator(), getComparator(), c) ?
124      ReturnCode.SKIP : ReturnCode.INCLUDE;
125  }
126
127  /**
128   * This method is used to determine a cell should be included or filtered out.
129   * @param op one of operators {@link CompareOperator}
130   * @param comparator comparator used to compare cells.
131   * @param cell cell to be compared.
132   * @return true means cell should be filtered out, included otherwise.
133   */
134  private boolean compareValue(final CompareOperator op, final ByteArrayComparable comparator,
135    final Cell cell) {
136    if (op == CompareOperator.NO_OP) {
137      return true;
138    }
139    int compareResult = PrivateCellUtil.compareValue(cell, comparator);
140    return CompareFilter.compare(op, compareResult);
141  }
142
143  /**
144   * Creating this filter by reflection, it is used by {@link ParseFilter},
145   * @param filterArguments arguments for creating a ColumnValueFilter
146   * @return a ColumnValueFilter
147   */
148  public static Filter createFilterFromArguments(ArrayList<byte[]> filterArguments) {
149    Preconditions.checkArgument(filterArguments.size() == 4,
150      "Expect 4 arguments: %s", filterArguments.size());
151    byte[] family = ParseFilter.removeQuotesFromByteArray(filterArguments.get(0));
152    byte[] qualifier = ParseFilter.removeQuotesFromByteArray(filterArguments.get(1));
153    CompareOperator operator = ParseFilter.createCompareOperator(filterArguments.get(2));
154    ByteArrayComparable comparator =
155      ParseFilter.createComparator(ParseFilter.removeQuotesFromByteArray(filterArguments.get(3)));
156
157    if (comparator instanceof RegexStringComparator ||
158        comparator instanceof SubstringComparator) {
159      if (operator != CompareOperator.EQUAL &&
160          operator != CompareOperator.NOT_EQUAL) {
161        throw new IllegalArgumentException("A regexstring comparator and substring comparator " +
162            "can only be used with EQUAL and NOT_EQUAL");
163      }
164    }
165
166    return new ColumnValueFilter(family, qualifier, operator, comparator);
167  }
168
169  /**
170   * @return A pb instance to represent this instance.
171   */
172  FilterProtos.ColumnValueFilter convert() {
173    FilterProtos.ColumnValueFilter.Builder builder =
174      FilterProtos.ColumnValueFilter.newBuilder();
175
176    builder.setFamily(UnsafeByteOperations.unsafeWrap(this.family));
177    builder.setQualifier(UnsafeByteOperations.unsafeWrap(this.qualifier));
178    builder.setCompareOp(HBaseProtos.CompareType.valueOf(this.op.name()));
179    builder.setComparator(ProtobufUtil.toComparator(this.comparator));
180
181    return builder.build();
182  }
183
184  /**
185   * Parse protobuf bytes to a ColumnValueFilter
186   * @param pbBytes pbBytes
187   * @return a ColumnValueFilter
188   * @throws DeserializationException deserialization exception
189   */
190  public static ColumnValueFilter parseFrom(final byte[] pbBytes) throws DeserializationException {
191    FilterProtos.ColumnValueFilter proto;
192    try {
193      proto = FilterProtos.ColumnValueFilter.parseFrom(pbBytes);
194    } catch (InvalidProtocolBufferException e) {
195      throw new DeserializationException(e);
196    }
197
198    final CompareOperator compareOp = CompareOperator.valueOf(proto.getCompareOp().name());
199    final ByteArrayComparable comparator;
200    try {
201      comparator = ProtobufUtil.toComparator(proto.getComparator());
202    } catch (IOException ioe) {
203      throw new DeserializationException(ioe);
204    }
205
206    return new ColumnValueFilter(proto.getFamily().toByteArray(),
207      proto.getQualifier().toByteArray(), compareOp, comparator);
208  }
209
210  @Override
211  public byte[] toByteArray() throws IOException {
212    return convert().toByteArray();
213  }
214
215  @Override
216  boolean areSerializedFieldsEqual(Filter o) {
217    if (o == this) {
218      return true;
219    } else if (!(o instanceof ColumnValueFilter)) {
220      return false;
221    }
222
223    ColumnValueFilter other = (ColumnValueFilter) o;
224    return Bytes.equals(this.getFamily(), other.getFamily()) &&
225      Bytes.equals(this.getQualifier(), other.getQualifier()) &&
226      this.getCompareOperator().equals(other.getCompareOperator()) &&
227      this.getComparator().areSerializedFieldsEqual(other.getComparator());
228  }
229
230  @Override
231  public boolean isFamilyEssential(byte[] name) throws IOException {
232    return Bytes.equals(name, this.family);
233  }
234
235  @Override
236  public String toString() {
237    return String.format("%s (%s, %s, %s, %s)",
238      getClass().getSimpleName(), Bytes.toStringBinary(this.family),
239      Bytes.toStringBinary(this.qualifier), this.op.name(),
240      Bytes.toStringBinary(this.comparator.getValue()));
241  }
242
243  @Override
244  public boolean equals(Object obj) {
245    return obj instanceof Filter && areSerializedFieldsEqual((Filter) obj);
246  }
247
248  @Override
249  public int hashCode() {
250    return Objects.hash(Bytes.hashCode(getFamily()), Bytes.hashCode(getQualifier()),
251      getCompareOperator(), getComparator());
252  }
253}