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