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    // We aren't sure whether any DeleteFamily cells exist, so we can't skip to next column.
296    // TODO: Current way disable us to seek to next column quickly. Is there any better solution?
297    // see HBASE-18471 for more details
298    // see TestFromClientSide3#testScanAfterDeletingSpecifiedRow
299    // see TestFromClientSide3#testScanAfterDeletingSpecifiedRowV2
300    if (cell.getQualifierLength() == 0) {
301      ExtendedCell nextKey = PrivateCellUtil.createNextOnRowCol(cell);
302      if (nextKey != cell) {
303        return nextKey;
304      }
305      // The cell is at the end of row/family/qualifier, so it is impossible to find any
306      // DeleteFamily cells.
307      // Let us seek to next column.
308    }
309    ColumnCount nextColumn = columns.getColumnHint();
310    if (nextColumn == null) {
311      return PrivateCellUtil.createLastOnRowCol(cell);
312    } else {
313      return PrivateCellUtil.createFirstOnRowCol(cell, nextColumn.getBuffer(),
314        nextColumn.getOffset(), nextColumn.getLength());
315    }
316  }
317
318  /**
319   * @param nextIndexed the key of the next entry in the block index (if any)
320   * @param currentCell The Cell we're using to calculate the seek key
321   * @return result of the compare between the indexed key and the key portion of the passed cell
322   */
323  public int compareKeyForNextRow(ExtendedCell nextIndexed, ExtendedCell currentCell) {
324    return PrivateCellUtil.compareKeyBasedOnColHint(rowComparator, nextIndexed, currentCell, 0, 0,
325      null, 0, 0, PrivateConstants.OLDEST_TIMESTAMP, Type.Minimum.getCode());
326  }
327
328  /**
329   * @param nextIndexed the key of the next entry in the block index (if any)
330   * @param currentCell The Cell we're using to calculate the seek key
331   * @return result of the compare between the indexed key and the key portion of the passed cell
332   */
333  public int compareKeyForNextColumn(ExtendedCell nextIndexed, ExtendedCell currentCell) {
334    ColumnCount nextColumn = columns.getColumnHint();
335    if (nextColumn == null) {
336      return PrivateCellUtil.compareKeyBasedOnColHint(rowComparator, nextIndexed, currentCell, 0, 0,
337        null, 0, 0, PrivateConstants.OLDEST_TIMESTAMP, Type.Minimum.getCode());
338    } else {
339      return PrivateCellUtil.compareKeyBasedOnColHint(rowComparator, nextIndexed, currentCell,
340        currentCell.getFamilyOffset(), currentCell.getFamilyLength(), nextColumn.getBuffer(),
341        nextColumn.getOffset(), nextColumn.getLength(), HConstants.LATEST_TIMESTAMP,
342        Type.Maximum.getCode());
343    }
344  }
345
346  /** Returns the Filter */
347  public abstract Filter getFilter();
348
349  /**
350   * Delegate to {@link Filter#getNextCellHint(Cell)}. If no filter, return {@code null}.
351   */
352  public abstract ExtendedCell getNextKeyHint(ExtendedCell cell) throws IOException;
353
354  @Override
355  public void beforeShipped() throws IOException {
356    if (this.currentRow != null) {
357      this.currentRow = PrivateCellUtil.createFirstOnRow(CellUtil.copyRow(this.currentRow));
358    }
359    if (columns != null) {
360      columns.beforeShipped();
361    }
362  }
363
364  protected static ExtendedCell createStartKeyFromRow(byte[] startRow, ScanInfo scanInfo) {
365    return PrivateCellUtil.createFirstDeleteFamilyCellOnRow(startRow, scanInfo.getFamily());
366  }
367
368  protected static Pair<DeleteTracker, ColumnTracker> getTrackers(RegionCoprocessorHost host,
369    NavigableSet<byte[]> columns, ScanInfo scanInfo, long oldestUnexpiredTS, Scan userScan)
370    throws IOException {
371    int resultMaxVersion = scanInfo.getMaxVersions();
372    int maxVersionToCheck = resultMaxVersion;
373    if (userScan != null) {
374      if (userScan.isRaw()) {
375        resultMaxVersion = userScan.getMaxVersions();
376        maxVersionToCheck = userScan.hasFilter() ? Integer.MAX_VALUE : resultMaxVersion;
377      } else {
378        resultMaxVersion = Math.min(userScan.getMaxVersions(), scanInfo.getMaxVersions());
379        maxVersionToCheck = userScan.hasFilter() ? scanInfo.getMaxVersions() : resultMaxVersion;
380      }
381    }
382
383    DeleteTracker deleteTracker;
384    if (scanInfo.isNewVersionBehavior() && (userScan == null || !userScan.isRaw())) {
385      deleteTracker = new NewVersionBehaviorTracker(columns, scanInfo.getComparator(),
386        scanInfo.getMinVersions(), scanInfo.getMaxVersions(), resultMaxVersion, oldestUnexpiredTS);
387    } else {
388      deleteTracker = new ScanDeleteTracker(scanInfo.getComparator());
389    }
390    if (host != null) {
391      deleteTracker = host.postInstantiateDeleteTracker(deleteTracker);
392      if (deleteTracker instanceof VisibilityScanDeleteTracker && scanInfo.isNewVersionBehavior()) {
393        deleteTracker = new VisibilityNewVersionBehaivorTracker(columns, scanInfo.getComparator(),
394          scanInfo.getMinVersions(), scanInfo.getMaxVersions(), resultMaxVersion,
395          oldestUnexpiredTS);
396      }
397    }
398
399    ColumnTracker columnTracker;
400
401    if (deleteTracker instanceof NewVersionBehaviorTracker) {
402      columnTracker = (NewVersionBehaviorTracker) deleteTracker;
403    } else if (columns == null || columns.size() == 0) {
404      columnTracker = new ScanWildcardColumnTracker(scanInfo.getMinVersions(), maxVersionToCheck,
405        oldestUnexpiredTS, scanInfo.getComparator());
406    } else {
407      columnTracker = new ExplicitColumnTracker(columns, scanInfo.getMinVersions(),
408        maxVersionToCheck, oldestUnexpiredTS);
409    }
410    return new Pair<>(deleteTracker, columnTracker);
411  }
412
413  // Used only for testing purposes
414  static MatchCode checkColumn(ColumnTracker columnTracker, byte[] bytes, int offset, int length,
415    long ttl, byte type, boolean ignoreCount) throws IOException {
416    KeyValue kv = KeyValueUtil.createFirstOnRow(HConstants.EMPTY_BYTE_ARRAY, 0, 0,
417      HConstants.EMPTY_BYTE_ARRAY, 0, 0, bytes, offset, length);
418    MatchCode matchCode = columnTracker.checkColumn(kv, type);
419    if (matchCode == MatchCode.INCLUDE) {
420      return columnTracker.checkVersions(kv, ttl, type, ignoreCount);
421    }
422    return matchCode;
423  }
424}