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.List;
023import java.util.TreeSet;
024
025import org.apache.hadoop.hbase.Cell;
026import org.apache.hadoop.hbase.PrivateCellUtil;
027import org.apache.yetus.audience.InterfaceAudience;
028import org.apache.hadoop.hbase.exceptions.DeserializationException;
029import org.apache.hadoop.hbase.shaded.protobuf.generated.FilterProtos;
030
031import org.apache.hbase.thirdparty.com.google.common.base.Preconditions;
032import org.apache.hbase.thirdparty.com.google.protobuf.InvalidProtocolBufferException;
033
034/**
035 * Filter that returns only cells whose timestamp (version) is
036 * in the specified list of timestamps (versions).
037 * <p>
038 * Note: Use of this filter overrides any time range/time stamp
039 * options specified using {@link org.apache.hadoop.hbase.client.Get#setTimeRange(long, long)},
040 * {@link org.apache.hadoop.hbase.client.Scan#setTimeRange(long, long)},
041 * {@link org.apache.hadoop.hbase.client.Get#setTimestamp(long)},
042 * or {@link org.apache.hadoop.hbase.client.Scan#setTimestamp(long)}.
043 */
044@InterfaceAudience.Public
045public class TimestampsFilter extends FilterBase {
046
047  private final boolean canHint;
048  TreeSet<Long> timestamps;
049  private static final int MAX_LOG_TIMESTAMPS = 5;
050
051  // Used during scans to hint the scan to stop early
052  // once the timestamps fall below the minTimestamp.
053  long minTimestamp = Long.MAX_VALUE;
054
055  /**
056   * Constructor for filter that retains only the specified timestamps in the list.
057   * @param timestamps
058   */
059  public TimestampsFilter(List<Long> timestamps) {
060    this(timestamps, false);
061  }
062
063  /**
064   * Constructor for filter that retains only those
065   * cells whose timestamp (version) is in the specified
066   * list of timestamps.
067   *
068   * @param timestamps list of timestamps that are wanted.
069   * @param canHint should the filter provide a seek hint? This can skip
070   *                past delete tombstones, so it should only be used when that
071   *                is not an issue ( no deletes, or don't care if data
072   *                becomes visible)
073   */
074  public TimestampsFilter(List<Long> timestamps, boolean canHint) {
075    for (Long timestamp : timestamps) {
076      Preconditions.checkArgument(timestamp >= 0, "must be positive %s", timestamp);
077    }
078    this.canHint = canHint;
079    this.timestamps = new TreeSet<>(timestamps);
080    init();
081  }
082
083  /**
084   * @return the list of timestamps
085   */
086  public List<Long> getTimestamps() {
087    List<Long> list = new ArrayList<>(timestamps.size());
088    list.addAll(timestamps);
089    return list;
090  }
091
092  private void init() {
093    if (this.timestamps.size() > 0) {
094      minTimestamp = this.timestamps.first();
095    }
096  }
097
098  /**
099   * Gets the minimum timestamp requested by filter.
100   * @return  minimum timestamp requested by filter.
101   */
102  public long getMin() {
103    return minTimestamp;
104  }
105
106  @Override
107  public boolean filterRowKey(Cell cell) throws IOException {
108    // Impl in FilterBase might do unnecessary copy for Off heap backed Cells.
109    return false;
110  }
111
112  @Deprecated
113  @Override
114  public ReturnCode filterKeyValue(final Cell c) {
115    return filterCell(c);
116  }
117
118  @Override
119  public ReturnCode filterCell(final Cell c) {
120    if (this.timestamps.contains(c.getTimestamp())) {
121      return ReturnCode.INCLUDE;
122    } else if (c.getTimestamp() < minTimestamp) {
123      // The remaining versions of this column are guaranteed
124      // to be lesser than all of the other values.
125      return ReturnCode.NEXT_COL;
126    }
127    return canHint ? ReturnCode.SEEK_NEXT_USING_HINT : ReturnCode.SKIP;
128  }
129
130
131  /**
132   * Pick the next cell that the scanner should seek to. Since this can skip any number of cells
133   * any of which can be a delete this can resurect old data.
134   *
135   * The method will only be used if canHint was set to true while creating the filter.
136   *
137   * @throws IOException This will never happen.
138   */
139  @Override
140  public Cell getNextCellHint(Cell currentCell) throws IOException {
141    if (!canHint) {
142      return null;
143    }
144
145    Long nextTimestampObject = timestamps.lower(currentCell.getTimestamp());
146
147    if (nextTimestampObject == null) {
148      // This should only happen if the current column's
149      // timestamp is below the last one in the list.
150      //
151      // It should never happen as the filterCell should return NEXT_COL
152      // but it's always better to be extra safe and protect against future
153      // behavioral changes.
154
155      return PrivateCellUtil.createLastOnRowCol(currentCell);
156    }
157
158    // Since we know the nextTimestampObject isn't null here there must still be
159    // timestamps that can be included. Cast the Long to a long and return the
160    // a cell with the current row/cf/col and the next found timestamp.
161    long nextTimestamp = nextTimestampObject;
162    return PrivateCellUtil.createFirstOnRowColTS(currentCell, nextTimestamp);
163  }
164
165  public static Filter createFilterFromArguments(ArrayList<byte []> filterArguments) {
166    ArrayList<Long> timestamps = new ArrayList<>(filterArguments.size());
167    for (int i = 0; i<filterArguments.size(); i++) {
168      long timestamp = ParseFilter.convertByteArrayToLong(filterArguments.get(i));
169      timestamps.add(timestamp);
170    }
171    return new TimestampsFilter(timestamps);
172  }
173
174  /**
175   * @return The filter serialized using pb
176   */
177  @Override
178  public byte[] toByteArray() {
179    FilterProtos.TimestampsFilter.Builder builder =
180        FilterProtos.TimestampsFilter.newBuilder();
181    builder.addAllTimestamps(this.timestamps);
182    builder.setCanHint(canHint);
183    return builder.build().toByteArray();
184  }
185
186  /**
187   * @param pbBytes A pb serialized {@link TimestampsFilter} instance
188   *
189   * @return An instance of {@link TimestampsFilter} made from <code>bytes</code>
190   * @see #toByteArray
191   */
192  public static TimestampsFilter parseFrom(final byte[] pbBytes)
193      throws DeserializationException {
194    FilterProtos.TimestampsFilter proto;
195    try {
196      proto = FilterProtos.TimestampsFilter.parseFrom(pbBytes);
197    } catch (InvalidProtocolBufferException e) {
198      throw new DeserializationException(e);
199    }
200    return new TimestampsFilter(proto.getTimestampsList(),
201        proto.hasCanHint() && proto.getCanHint());
202  }
203
204  /**
205   * @param o the other filter to compare with
206   * @return true if and only if the fields of the filter that are serialized
207   * are equal to the corresponding fields in other.  Used for testing.
208   */
209  @Override
210  boolean areSerializedFieldsEqual(Filter o) {
211    if (o == this) return true;
212    if (!(o instanceof TimestampsFilter)) return false;
213
214    TimestampsFilter other = (TimestampsFilter)o;
215    return this.getTimestamps().equals(other.getTimestamps());
216  }
217
218  @Override
219  public String toString() {
220    return toString(MAX_LOG_TIMESTAMPS);
221  }
222
223  protected String toString(int maxTimestamps) {
224    StringBuilder tsList = new StringBuilder();
225
226    int count = 0;
227    for (Long ts : this.timestamps) {
228      if (count >= maxTimestamps) {
229        break;
230      }
231      ++count;
232      tsList.append(ts.toString());
233      if (count < this.timestamps.size() && count < maxTimestamps) {
234        tsList.append(", ");
235      }
236    }
237
238    return String.format("%s (%d/%d): [%s] canHint: [%b]", this.getClass().getSimpleName(),
239        count, this.timestamps.size(), tsList.toString(), canHint);
240  }
241}