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.regionserver.querymatcher;
019
020import java.io.IOException;
021import java.util.Iterator;
022import java.util.NavigableSet;
023import org.apache.hadoop.hbase.Cell;
024import org.apache.hadoop.hbase.CellComparator;
025import org.apache.hadoop.hbase.CellUtil;
026import org.apache.hadoop.hbase.ExtendedCell;
027import org.apache.hadoop.hbase.HConstants;
028import org.apache.hadoop.hbase.KeyValue;
029import org.apache.hadoop.hbase.KeyValue.Type;
030import org.apache.hadoop.hbase.KeyValueUtil;
031import org.apache.hadoop.hbase.PrivateCellUtil;
032import org.apache.hadoop.hbase.PrivateConstants;
033import org.apache.hadoop.hbase.Tag;
034import org.apache.hadoop.hbase.TagType;
035import org.apache.hadoop.hbase.client.Scan;
036import org.apache.hadoop.hbase.filter.Filter;
037import org.apache.hadoop.hbase.regionserver.RegionCoprocessorHost;
038import org.apache.hadoop.hbase.regionserver.ScanInfo;
039import org.apache.hadoop.hbase.regionserver.ShipperListener;
040import org.apache.hadoop.hbase.regionserver.querymatcher.DeleteTracker.DeleteResult;
041import org.apache.hadoop.hbase.security.visibility.VisibilityNewVersionBehaivorTracker;
042import org.apache.hadoop.hbase.security.visibility.VisibilityScanDeleteTracker;
043import org.apache.hadoop.hbase.util.Bytes;
044import org.apache.hadoop.hbase.util.Pair;
045import org.apache.yetus.audience.InterfaceAudience;
046
047/**
048 * A query matcher that is specifically designed for the scan case.
049 */
050@InterfaceAudience.Private
051public abstract class ScanQueryMatcher implements ShipperListener {
052
053  /**
054   * {@link #match} return codes. These instruct the scanner moving through memstores and StoreFiles
055   * what to do with the current KeyValue.
056   * <p>
057   * Additionally, this contains "early-out" language to tell the scanner to move on to the next
058   * File (memstore or Storefile), or to return immediately.
059   */
060  public static enum MatchCode {
061    /**
062     * Include KeyValue in the returned result
063     */
064    INCLUDE,
065
066    /**
067     * Do not include KeyValue in the returned result
068     */
069    SKIP,
070
071    /**
072     * Do not include, jump to next StoreFile or memstore (in time order)
073     */
074    NEXT,
075
076    /**
077     * Do not include, return current result
078     */
079    DONE,
080
081    /**
082     * These codes are used by the ScanQueryMatcher
083     */
084
085    /**
086     * Done with the row, seek there.
087     */
088    SEEK_NEXT_ROW,
089
090    /**
091     * Done with column, seek to next.
092     */
093    SEEK_NEXT_COL,
094
095    /**
096     * Done with scan, thanks to the row filter.
097     */
098    DONE_SCAN,
099
100    /**
101     * Seek to next key which is given as hint.
102     */
103    SEEK_NEXT_USING_HINT,
104
105    /**
106     * Include KeyValue and done with column, seek to next.
107     */
108    INCLUDE_AND_SEEK_NEXT_COL,
109
110    /**
111     * Include KeyValue and done with row, seek to next.
112     */
113    INCLUDE_AND_SEEK_NEXT_ROW,
114  }
115
116  /** Row comparator for the region this query is for */
117  protected final CellComparator rowComparator;
118
119  /** Key to seek to in memstore and StoreFiles */
120  protected final ExtendedCell startKey;
121
122  /** Keeps track of columns and versions */
123  protected final ColumnTracker columns;
124
125  /** The oldest timestamp we are interested in, based on TTL */
126  protected final long oldestUnexpiredTS;
127
128  protected final long now;
129
130  /** Row the query is on */
131  protected ExtendedCell currentRow;
132
133  protected ScanQueryMatcher(ExtendedCell startKey, ScanInfo scanInfo, ColumnTracker columns,
134    long oldestUnexpiredTS, long now) {
135    this.rowComparator = scanInfo.getComparator();
136    this.startKey = startKey;
137    this.oldestUnexpiredTS = oldestUnexpiredTS;
138    this.now = now;
139    this.columns = columns;
140  }
141
142  /** Returns true if the cell is expired */
143  private static boolean isCellTTLExpired(final ExtendedCell cell, final long now) {
144    // Look for a TTL tag first. Use it instead of the family setting if
145    // found. If a cell has multiple TTLs, resolve the conflict by using the
146    // first tag encountered.
147    Iterator<Tag> i = PrivateCellUtil.tagsIterator(cell);
148    while (i.hasNext()) {
149      Tag t = i.next();
150      if (TagType.TTL_TAG_TYPE == t.getType()) {
151        // Unlike in schema cell TTLs are stored in milliseconds, no need
152        // to convert
153        long ts = cell.getTimestamp();
154        assert t.getValueLength() == Bytes.SIZEOF_LONG;
155        long ttl = Tag.getValueAsLong(t);
156        if (ts + ttl < now) {
157          return true;
158        }
159        // Per cell TTLs cannot extend lifetime beyond family settings, so
160        // fall through to check that
161        break;
162      }
163    }
164    return false;
165  }
166
167  /**
168   * Check before the delete logic.
169   * @return null means continue.
170   */
171  protected final MatchCode preCheck(ExtendedCell cell) {
172    final MatchCode code = preCheckRaw(cell);
173    if (code != null) {
174      return code;
175    }
176
177    // check if the cell is expired by cell TTL
178    if (isCellTTLExpired(cell, this.now)) {
179      return MatchCode.SKIP;
180    }
181
182    return null;
183  }
184
185  /**
186   * preCheck for raw scan. This should not skip expired cells.
187   * @return null means continue.
188   */
189  protected final MatchCode preCheckRaw(ExtendedCell cell) {
190    if (currentRow == null) {
191      // Since the curCell is null it means we are already sure that we have moved over to the next
192      // row
193      return MatchCode.DONE;
194    }
195    // if row key is changed, then we know that we have moved over to the next row
196    if (rowComparator.compareRows(currentRow, cell) != 0) {
197      return MatchCode.DONE;
198    }
199
200    if (this.columns.done()) {
201      return MatchCode.SEEK_NEXT_ROW;
202    }
203
204    long timestamp = cell.getTimestamp();
205    // check if this is a fake cell. The fake cell is an optimization, we should make the scanner
206    // seek to next column or next row. See StoreFileScanner.requestSeek for more details.
207    // check for early out based on timestamp alone
208    if (timestamp == PrivateConstants.OLDEST_TIMESTAMP || columns.isDone(timestamp)) {
209      return columns.getNextRowOrNextColumn(cell);
210    }
211    return null;
212  }
213
214  protected final MatchCode checkDeleted(DeleteTracker deletes, ExtendedCell cell) {
215    if (deletes.isEmpty() && !(deletes instanceof NewVersionBehaviorTracker)) {
216      return null;
217    }
218    // MvccSensitiveTracker always need check all cells to save some infos.
219    DeleteResult deleteResult = deletes.isDeleted(cell);
220    switch (deleteResult) {
221      case FAMILY_DELETED:
222      case COLUMN_DELETED:
223        if (!(deletes instanceof NewVersionBehaviorTracker)) {
224          // MvccSensitive can not seek to next because the Put with lower ts may have higher mvcc
225          return columns.getNextRowOrNextColumn(cell);
226        }
227      case VERSION_DELETED:
228      case FAMILY_VERSION_DELETED:
229      case VERSION_MASKED:
230        return MatchCode.SKIP;
231      case NOT_DELETED:
232        return null;
233      default:
234        throw new RuntimeException("Unexpected delete result: " + deleteResult);
235    }
236  }
237
238  /**
239   * Determines if the caller should do one of several things:
240   * <ul>
241   * <li>seek/skip to the next row (MatchCode.SEEK_NEXT_ROW)</li>
242   * <li>seek/skip to the next column (MatchCode.SEEK_NEXT_COL)</li>
243   * <li>include the current KeyValue (MatchCode.INCLUDE)</li>
244   * <li>ignore the current KeyValue (MatchCode.SKIP)</li>
245   * <li>got to the next row (MatchCode.DONE)</li>
246   * </ul>
247   * @param cell KeyValue to check
248   * @return The match code instance.
249   * @throws IOException in case there is an internal consistency problem caused by a data
250   *                     corruption.
251   */
252  public abstract MatchCode match(ExtendedCell cell) throws IOException;
253
254  /** Returns the start key */
255  public ExtendedCell getStartKey() {
256    return startKey;
257  }
258
259  /** Returns whether there is an null column in the query */
260  public abstract boolean hasNullColumnInQuery();
261
262  /** Returns a cell represent the current row */
263  public ExtendedCell currentRow() {
264    return currentRow;
265  }
266
267  /**
268   * Make {@link #currentRow()} return null.
269   */
270  public void clearCurrentRow() {
271    currentRow = null;
272  }
273
274  protected abstract void reset();
275
276  /**
277   * Set the row when there is change in row
278   */
279  public void setToNewRow(ExtendedCell currentRow) {
280    this.currentRow = currentRow;
281    columns.reset();
282    reset();
283  }
284
285  public abstract boolean isUserScan();
286
287  /**
288   * @return Returns false if we know there are no more rows to be scanned (We've reached the
289   *         <code>stopRow</code> or we are scanning on row only because this Scan is for a Get,
290   *         etc.
291   */
292  public abstract boolean moreRowsMayExistAfter(ExtendedCell cell);
293
294  public ExtendedCell getKeyForNextColumn(ExtendedCell cell) {
295    // For cells with empty qualifier, we generally can't skip to the next column because
296    // DeleteFamily cells might exist that we haven't seen yet (see HBASE-18471).
297    // However, if the cell itself IS a DeleteFamily marker, we know we've already processed it,
298    // so we can safely seek to the next real column.
299    if (cell.getQualifierLength() == 0 && !PrivateCellUtil.isDeleteFamily(cell)) {
300      ExtendedCell nextKey = PrivateCellUtil.createNextOnRowCol(cell);
301      if (nextKey != cell) {
302        return nextKey;
303      }
304      // The cell is at the end of row/family/qualifier, so it is impossible to find any
305      // DeleteFamily cells.
306      // Let us seek to next column.
307    }
308    ColumnCount nextColumn = columns.getColumnHint();
309    if (nextColumn == null) {
310      return PrivateCellUtil.createLastOnRowCol(cell);
311    } else {
312      return PrivateCellUtil.createFirstOnRowCol(cell, nextColumn.getBuffer(),
313        nextColumn.getOffset(), nextColumn.getLength());
314    }
315  }
316
317  /**
318   * @param nextIndexed the key of the next entry in the block index (if any)
319   * @param currentCell The Cell we're using to calculate the seek key
320   * @return result of the compare between the indexed key and the key portion of the passed cell
321   */
322  public int compareKeyForNextRow(ExtendedCell nextIndexed, ExtendedCell currentCell) {
323    return PrivateCellUtil.compareKeyBasedOnColHint(rowComparator, nextIndexed, currentCell, 0, 0,
324      null, 0, 0, PrivateConstants.OLDEST_TIMESTAMP, Type.Minimum.getCode());
325  }
326
327  /**
328   * @param nextIndexed the key of the next entry in the block index (if any)
329   * @param currentCell The Cell we're using to calculate the seek key
330   * @return result of the compare between the indexed key and the key portion of the passed cell
331   */
332  public int compareKeyForNextColumn(ExtendedCell nextIndexed, ExtendedCell currentCell) {
333    ColumnCount nextColumn = columns.getColumnHint();
334    if (nextColumn == null) {
335      return PrivateCellUtil.compareKeyBasedOnColHint(rowComparator, nextIndexed, currentCell, 0, 0,
336        null, 0, 0, PrivateConstants.OLDEST_TIMESTAMP, Type.Minimum.getCode());
337    } else {
338      return PrivateCellUtil.compareKeyBasedOnColHint(rowComparator, nextIndexed, currentCell,
339        currentCell.getFamilyOffset(), currentCell.getFamilyLength(), nextColumn.getBuffer(),
340        nextColumn.getOffset(), nextColumn.getLength(), HConstants.LATEST_TIMESTAMP,
341        Type.Maximum.getCode());
342    }
343  }
344
345  /** Returns the Filter */
346  public abstract Filter getFilter();
347
348  /**
349   * Delegate to {@link Filter#getNextCellHint(Cell)}. If no filter, return {@code null}.
350   */
351  public abstract ExtendedCell getNextKeyHint(ExtendedCell cell) throws IOException;
352
353  @Override
354  public void beforeShipped() throws IOException {
355    if (this.currentRow != null) {
356      this.currentRow = PrivateCellUtil.createFirstOnRow(CellUtil.copyRow(this.currentRow));
357    }
358    if (columns != null) {
359      columns.beforeShipped();
360    }
361  }
362
363  protected static ExtendedCell createStartKeyFromRow(byte[] startRow, ScanInfo scanInfo) {
364    return PrivateCellUtil.createFirstDeleteFamilyCellOnRow(startRow, scanInfo.getFamily());
365  }
366
367  protected static Pair<DeleteTracker, ColumnTracker> getTrackers(RegionCoprocessorHost host,
368    NavigableSet<byte[]> columns, ScanInfo scanInfo, long oldestUnexpiredTS, Scan userScan)
369    throws IOException {
370    int resultMaxVersion = scanInfo.getMaxVersions();
371    int maxVersionToCheck = resultMaxVersion;
372    if (userScan != null) {
373      if (userScan.isRaw()) {
374        resultMaxVersion = userScan.getMaxVersions();
375        maxVersionToCheck = userScan.hasFilter() ? Integer.MAX_VALUE : resultMaxVersion;
376      } else {
377        resultMaxVersion = Math.min(userScan.getMaxVersions(), scanInfo.getMaxVersions());
378        maxVersionToCheck = userScan.hasFilter() ? scanInfo.getMaxVersions() : resultMaxVersion;
379      }
380    }
381
382    DeleteTracker deleteTracker;
383    if (scanInfo.isNewVersionBehavior() && (userScan == null || !userScan.isRaw())) {
384      deleteTracker = new NewVersionBehaviorTracker(columns, scanInfo.getComparator(),
385        scanInfo.getMinVersions(), scanInfo.getMaxVersions(), resultMaxVersion, oldestUnexpiredTS);
386    } else {
387      deleteTracker = new ScanDeleteTracker(scanInfo.getComparator());
388    }
389    if (host != null) {
390      deleteTracker = host.postInstantiateDeleteTracker(deleteTracker);
391      if (deleteTracker instanceof VisibilityScanDeleteTracker && scanInfo.isNewVersionBehavior()) {
392        deleteTracker = new VisibilityNewVersionBehaivorTracker(columns, scanInfo.getComparator(),
393          scanInfo.getMinVersions(), scanInfo.getMaxVersions(), resultMaxVersion,
394          oldestUnexpiredTS);
395      }
396    }
397
398    ColumnTracker columnTracker;
399
400    if (deleteTracker instanceof NewVersionBehaviorTracker) {
401      columnTracker = (NewVersionBehaviorTracker) deleteTracker;
402    } else if (columns == null || columns.size() == 0) {
403      columnTracker = new ScanWildcardColumnTracker(scanInfo.getMinVersions(), maxVersionToCheck,
404        oldestUnexpiredTS, scanInfo.getComparator());
405    } else {
406      columnTracker = new ExplicitColumnTracker(columns, scanInfo.getMinVersions(),
407        maxVersionToCheck, oldestUnexpiredTS);
408    }
409    return new Pair<>(deleteTracker, columnTracker);
410  }
411
412  // Used only for testing purposes
413  static MatchCode checkColumn(ColumnTracker columnTracker, byte[] bytes, int offset, int length,
414    long ttl, byte type, boolean ignoreCount) throws IOException {
415    KeyValue kv = KeyValueUtil.createFirstOnRow(HConstants.EMPTY_BYTE_ARRAY, 0, 0,
416      HConstants.EMPTY_BYTE_ARRAY, 0, 0, bytes, offset, length);
417    MatchCode matchCode = columnTracker.checkColumn(kv, type);
418    if (matchCode == MatchCode.INCLUDE) {
419      return columnTracker.checkVersions(kv, ttl, type, ignoreCount);
420    }
421    return matchCode;
422  }
423}