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 org.apache.hadoop.hbase.CellUtil;
022import org.apache.hadoop.hbase.ExtendedCell;
023import org.apache.hadoop.hbase.KeepDeletedCells;
024import org.apache.hadoop.hbase.KeyValue;
025import org.apache.hadoop.hbase.KeyValueUtil;
026import org.apache.hadoop.hbase.PrivateCellUtil;
027import org.apache.hadoop.hbase.client.Scan;
028import org.apache.hadoop.hbase.regionserver.ScanInfo;
029import org.apache.yetus.audience.InterfaceAudience;
030
031/**
032 * Query matcher for normal user scan.
033 */
034@InterfaceAudience.Private
035public abstract class NormalUserScanQueryMatcher extends UserScanQueryMatcher {
036
037  /**
038   * Number of consecutive range delete markers (DeleteColumn/DeleteFamily) to skip before switching
039   * to seek. Seeking is more expensive than skipping for a single marker, but much faster when
040   * markers accumulate. This threshold avoids the seek overhead for the common case (one delete per
041   * row/column) while still kicking in when markers pile up.
042   */
043  static final int SEEK_ON_DELETE_MARKER_THRESHOLD = 10;
044
045  /** Keeps track of deletes */
046  private final DeleteTracker deletes;
047
048  /** True if we are doing a 'Get' Scan. Every Get is actually a one-row Scan. */
049  private final boolean get;
050
051  /** whether time range queries can see rows "behind" a delete */
052  protected final boolean seePastDeleteMarkers;
053
054  /** Whether seek optimization for range delete markers is applicable */
055  private final boolean canSeekOnDeleteMarker;
056
057  /** Count of consecutive range delete markers seen for the same column */
058  private int rangeDeleteCount;
059
060  /** Last range delete cell, for qualifier comparison across consecutive markers */
061  private ExtendedCell lastDelete;
062
063  protected NormalUserScanQueryMatcher(Scan scan, ScanInfo scanInfo, ColumnTracker columns,
064    boolean hasNullColumn, DeleteTracker deletes, long oldestUnexpiredTS, long now) {
065    super(scan, scanInfo, columns, hasNullColumn, oldestUnexpiredTS, now);
066    this.deletes = deletes;
067    this.get = scan.isGetScan();
068    this.seePastDeleteMarkers = scanInfo.getKeepDeletedCells() != KeepDeletedCells.FALSE;
069    this.canSeekOnDeleteMarker =
070      !seePastDeleteMarkers && deletes.getClass() == ScanDeleteTracker.class;
071  }
072
073  @Override
074  public void beforeShipped() throws IOException {
075    super.beforeShipped();
076    deletes.beforeShipped();
077    if (lastDelete != null) {
078      lastDelete = KeyValueUtil.toNewKeyCell(lastDelete);
079    }
080  }
081
082  @Override
083  public MatchCode match(ExtendedCell cell) throws IOException {
084    if (filter != null && filter.filterAllRemaining()) {
085      return MatchCode.DONE_SCAN;
086    }
087    MatchCode returnCode = preCheck(cell);
088    if (returnCode != null) {
089      return returnCode;
090    }
091    long timestamp = cell.getTimestamp();
092    byte typeByte = cell.getTypeByte();
093    if (PrivateCellUtil.isDelete(typeByte)) {
094      boolean includeDeleteMarker =
095        seePastDeleteMarkers ? tr.withinTimeRange(timestamp) : tr.withinOrAfterTimeRange(timestamp);
096      if (includeDeleteMarker) {
097        this.deletes.add(cell);
098      }
099
100      // A DeleteColumn or DeleteFamily masks all remaining cells for this column/family.
101      // Seek past them instead of skipping one cell at a time, but only after seeing
102      // enough consecutive markers for the same column to justify the seek overhead.
103      // Only safe with plain ScanDeleteTracker. Not safe with newVersionBehavior (sequence
104      // IDs determine visibility), visibility labels (delete/put label mismatch), or
105      // seePastDeleteMarkers (KEEP_DELETED_CELLS).
106      if (
107        canSeekOnDeleteMarker && (typeByte == KeyValue.Type.DeleteFamily.getCode()
108          || (typeByte == KeyValue.Type.DeleteColumn.getCode() && cell.getQualifierLength() > 0))
109      ) {
110        if (lastDelete != null && !CellUtil.matchingQualifier(cell, lastDelete)) {
111          rangeDeleteCount = 0;
112        }
113        lastDelete = cell;
114        if (++rangeDeleteCount >= SEEK_ON_DELETE_MARKER_THRESHOLD) {
115          rangeDeleteCount = 0;
116          return columns.getNextRowOrNextColumn(cell);
117        }
118      } else {
119        rangeDeleteCount = 0;
120      }
121      return MatchCode.SKIP;
122    }
123    rangeDeleteCount = 0;
124    returnCode = checkDeleted(deletes, cell);
125    if (returnCode != null) {
126      return returnCode;
127    }
128    return matchColumn(cell, timestamp, typeByte);
129  }
130
131  @Override
132  protected void reset() {
133    deletes.reset();
134    rangeDeleteCount = 0;
135    lastDelete = null;
136  }
137
138  @Override
139  protected boolean isGet() {
140    return get;
141  }
142
143  public static NormalUserScanQueryMatcher create(Scan scan, ScanInfo scanInfo,
144    ColumnTracker columns, DeleteTracker deletes, boolean hasNullColumn, long oldestUnexpiredTS,
145    long now) throws IOException {
146    if (scan.isReversed()) {
147      if (scan.includeStopRow()) {
148        return new NormalUserScanQueryMatcher(scan, scanInfo, columns, hasNullColumn, deletes,
149          oldestUnexpiredTS, now) {
150
151          @Override
152          protected boolean moreRowsMayExistsAfter(int cmpToStopRow) {
153            return cmpToStopRow >= 0;
154          }
155        };
156      } else {
157        return new NormalUserScanQueryMatcher(scan, scanInfo, columns, hasNullColumn, deletes,
158          oldestUnexpiredTS, now) {
159
160          @Override
161          protected boolean moreRowsMayExistsAfter(int cmpToStopRow) {
162            return cmpToStopRow > 0;
163          }
164        };
165      }
166    } else {
167      if (scan.includeStopRow()) {
168        return new NormalUserScanQueryMatcher(scan, scanInfo, columns, hasNullColumn, deletes,
169          oldestUnexpiredTS, now) {
170
171          @Override
172          protected boolean moreRowsMayExistsAfter(int cmpToStopRow) {
173            return cmpToStopRow <= 0;
174          }
175        };
176      } else {
177        return new NormalUserScanQueryMatcher(scan, scanInfo, columns, hasNullColumn, deletes,
178          oldestUnexpiredTS, now) {
179
180          @Override
181          protected boolean moreRowsMayExistsAfter(int cmpToStopRow) {
182            return cmpToStopRow < 0;
183          }
184        };
185      }
186    }
187  }
188}