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  @Override
100  public ReturnCode filterCell(final Cell c) {
101    if (provideHint) {
102      return ReturnCode.SEEK_NEXT_USING_HINT;
103    }
104    if (filterRow) {
105      return ReturnCode.NEXT_ROW;
106    }
107    return ReturnCode.INCLUDE;
108  }
109
110  @Override
111  public boolean filterRow() {
112    return filterRow;
113  }
114
115  @Override
116  public void reset() {
117    filterRow = true;
118  }
119
120  @Override
121  public boolean filterAllRemaining() {
122    return passedPrefix;
123  }
124
125  @Override
126  public Cell getNextCellHint(Cell cell) {
127    if (reversed) {
128      return reversedNextCellHint;
129    } else {
130      return forwardNextCellHint;
131    }
132  }
133
134  public static Filter createFilterFromArguments(ArrayList<byte[]> filterArguments) {
135    Preconditions.checkArgument(filterArguments.size() == 1, "Expected 1 but got: %s",
136      filterArguments.size());
137    byte[] prefix = ParseFilter.removeQuotesFromByteArray(filterArguments.get(0));
138    return new PrefixFilter(prefix);
139  }
140
141  /** Returns The filter serialized using pb */
142  @Override
143  public byte[] toByteArray() {
144    FilterProtos.PrefixFilter.Builder builder = FilterProtos.PrefixFilter.newBuilder();
145    if (this.prefix != null) {
146      builder.setPrefix(UnsafeByteOperations.unsafeWrap(this.prefix));
147    }
148    return builder.build().toByteArray();
149  }
150
151  /**
152   * Parse a serialized representation of {@link PrefixFilter}
153   * @param pbBytes A pb serialized {@link PrefixFilter} instance
154   * @return An instance of {@link PrefixFilter} made from <code>bytes</code>
155   * @throws DeserializationException if an error occurred
156   * @see #toByteArray
157   */
158  public static PrefixFilter parseFrom(final byte[] pbBytes) throws DeserializationException {
159    FilterProtos.PrefixFilter proto;
160    try {
161      proto = FilterProtos.PrefixFilter.parseFrom(pbBytes);
162    } catch (InvalidProtocolBufferException e) {
163      throw new DeserializationException(e);
164    }
165    return new PrefixFilter(proto.hasPrefix() ? proto.getPrefix().toByteArray() : null);
166  }
167
168  /**
169   * Returns true if and only if the fields of the filter that are serialized are equal to the
170   * corresponding fields in other. Used for testing.
171   */
172  @Override
173  boolean areSerializedFieldsEqual(Filter o) {
174    if (o == this) {
175      return true;
176    }
177    if (!(o instanceof PrefixFilter)) {
178      return false;
179    }
180    PrefixFilter other = (PrefixFilter) o;
181    return Bytes.equals(this.getPrefix(), other.getPrefix());
182  }
183
184  @Override
185  public String toString() {
186    return this.getClass().getSimpleName() + " " + Bytes.toStringBinary(this.prefix);
187  }
188
189  @Override
190  public boolean equals(Object obj) {
191    return obj instanceof Filter && areSerializedFieldsEqual((Filter) obj);
192  }
193
194  @Override
195  public int hashCode() {
196    return Bytes.hashCode(this.getPrefix());
197  }
198}