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.util.ArrayList;
021import org.apache.hadoop.hbase.ByteBufferExtendedCell;
022import org.apache.hadoop.hbase.Cell;
023import org.apache.hadoop.hbase.PrivateCellUtil;
024import org.apache.hadoop.hbase.exceptions.DeserializationException;
025import org.apache.hadoop.hbase.util.ByteBufferUtils;
026import org.apache.hadoop.hbase.util.Bytes;
027import org.apache.yetus.audience.InterfaceAudience;
028
029import org.apache.hbase.thirdparty.com.google.common.base.Preconditions;
030import org.apache.hbase.thirdparty.com.google.protobuf.InvalidProtocolBufferException;
031import org.apache.hbase.thirdparty.com.google.protobuf.UnsafeByteOperations;
032
033import org.apache.hadoop.hbase.shaded.protobuf.generated.FilterProtos;
034
035/**
036 * Pass results that have same row prefix.
037 */
038@InterfaceAudience.Public
039public class PrefixFilter extends FilterBase implements HintingFilter {
040  protected byte[] prefix = null;
041  protected boolean passedPrefix = false;
042  protected boolean filterRow = true;
043  protected boolean provideHint = false;
044  protected Cell reversedNextCellHint;
045  protected Cell forwardNextCellHint;
046
047  public PrefixFilter(final byte[] prefix) {
048    this.prefix = prefix;
049    // Pre-compute hints at creation to avoid re-computing them several times in the corner
050    // case where there are a lot of cells between the hint and the first real match.
051    createCellHints();
052  }
053
054  private void createCellHints() {
055    if (prefix == null) {
056      return;
057    }
058    // On reversed scan hint should be the prefix with last byte incremented
059    byte[] reversedHintBytes = PrivateCellUtil.increaseLastNonMaxByte(this.prefix);
060    this.reversedNextCellHint =
061      PrivateCellUtil.createFirstOnRow(reversedHintBytes, 0, (short) reversedHintBytes.length);
062    // On forward scan hint should be the prefix
063    this.forwardNextCellHint = PrivateCellUtil.createFirstOnRow(prefix, 0, (short) prefix.length);
064  }
065
066  public byte[] getPrefix() {
067    return prefix;
068  }
069
070  @Override
071  public boolean filterRowKey(Cell firstRowCell) {
072    if (firstRowCell == null || this.prefix == null) {
073      return true;
074    }
075    if (filterAllRemaining()) {
076      return true;
077    }
078    // if the cell is before => return false so that getNextCellHint() is invoked.
079    // if they are equal, return false => pass row
080    // if the cell is after => return true, filter row
081    // if we are passed the prefix, set flag
082    int cmp;
083    if (firstRowCell instanceof ByteBufferExtendedCell) {
084      cmp = ByteBufferUtils.compareTo(((ByteBufferExtendedCell) firstRowCell).getRowByteBuffer(),
085        ((ByteBufferExtendedCell) firstRowCell).getRowPosition(), this.prefix.length, this.prefix,
086        0, this.prefix.length);
087    } else {
088      cmp = Bytes.compareTo(firstRowCell.getRowArray(), firstRowCell.getRowOffset(),
089        this.prefix.length, this.prefix, 0, this.prefix.length);
090    }
091    if ((!isReversed() && cmp > 0) || (isReversed() && cmp < 0)) {
092      passedPrefix = true;
093    }
094    filterRow = (cmp != 0);
095    provideHint = (!isReversed() && cmp < 0) || (isReversed() && cmp > 0);
096    return passedPrefix;
097  }
098
099  @Deprecated
100  @Override
101  public ReturnCode filterKeyValue(final Cell c) {
102    return filterCell(c);
103  }
104
105  @Override
106  public ReturnCode filterCell(final Cell c) {
107    if (provideHint) {
108      return ReturnCode.SEEK_NEXT_USING_HINT;
109    }
110    if (filterRow) {
111      return ReturnCode.NEXT_ROW;
112    }
113    return ReturnCode.INCLUDE;
114  }
115
116  @Override
117  public boolean filterRow() {
118    return filterRow;
119  }
120
121  @Override
122  public void reset() {
123    filterRow = true;
124  }
125
126  @Override
127  public boolean filterAllRemaining() {
128    return passedPrefix;
129  }
130
131  @Override
132  public Cell getNextCellHint(Cell cell) {
133    if (reversed) {
134      return reversedNextCellHint;
135    } else {
136      return forwardNextCellHint;
137    }
138  }
139
140  public static Filter createFilterFromArguments(ArrayList<byte[]> filterArguments) {
141    Preconditions.checkArgument(filterArguments.size() == 1, "Expected 1 but got: %s",
142      filterArguments.size());
143    byte[] prefix = ParseFilter.removeQuotesFromByteArray(filterArguments.get(0));
144    return new PrefixFilter(prefix);
145  }
146
147  /** Returns The filter serialized using pb */
148  @Override
149  public byte[] toByteArray() {
150    FilterProtos.PrefixFilter.Builder builder = FilterProtos.PrefixFilter.newBuilder();
151    if (this.prefix != null) {
152      builder.setPrefix(UnsafeByteOperations.unsafeWrap(this.prefix));
153    }
154    return builder.build().toByteArray();
155  }
156
157  /**
158   * Parse a serialized representation of {@link PrefixFilter}
159   * @param pbBytes A pb serialized {@link PrefixFilter} instance
160   * @return An instance of {@link PrefixFilter} made from <code>bytes</code>
161   * @throws DeserializationException if an error occurred
162   * @see #toByteArray
163   */
164  public static PrefixFilter parseFrom(final byte[] pbBytes) throws DeserializationException {
165    FilterProtos.PrefixFilter proto;
166    try {
167      proto = FilterProtos.PrefixFilter.parseFrom(pbBytes);
168    } catch (InvalidProtocolBufferException e) {
169      throw new DeserializationException(e);
170    }
171    return new PrefixFilter(proto.hasPrefix() ? proto.getPrefix().toByteArray() : null);
172  }
173
174  /**
175   * Returns true if and only if the fields of the filter that are serialized are equal to the
176   * corresponding fields in other. Used for testing.
177   */
178  @Override
179  boolean areSerializedFieldsEqual(Filter o) {
180    if (o == this) {
181      return true;
182    }
183    if (!(o instanceof PrefixFilter)) {
184      return false;
185    }
186    PrefixFilter other = (PrefixFilter) o;
187    return Bytes.equals(this.getPrefix(), other.getPrefix());
188  }
189
190  @Override
191  public String toString() {
192    return this.getClass().getSimpleName() + " " + Bytes.toStringBinary(this.prefix);
193  }
194
195  @Override
196  public boolean equals(Object obj) {
197    return obj instanceof Filter && areSerializedFieldsEqual((Filter) obj);
198  }
199
200  @Override
201  public int hashCode() {
202    return Bytes.hashCode(this.getPrefix());
203  }
204}