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.NavigableSet;
022import org.apache.hadoop.hbase.Cell;
023import org.apache.hadoop.hbase.CellUtil;
024import org.apache.hadoop.hbase.KeyValueUtil;
025import org.apache.hadoop.hbase.PrivateCellUtil;
026import org.apache.hadoop.hbase.client.Scan;
027import org.apache.hadoop.hbase.filter.Filter;
028import org.apache.hadoop.hbase.filter.Filter.ReturnCode;
029import org.apache.hadoop.hbase.io.TimeRange;
030import org.apache.hadoop.hbase.regionserver.RegionCoprocessorHost;
031import org.apache.hadoop.hbase.regionserver.ScanInfo;
032import org.apache.hadoop.hbase.util.Pair;
033import org.apache.yetus.audience.InterfaceAudience;
034
035/**
036 * Query matcher for user scan.
037 * <p>
038 * We do not consider mvcc here because
039 * {@link org.apache.hadoop.hbase.regionserver.StoreFileScanner} and
040 * {@link org.apache.hadoop.hbase.regionserver.SegmentScanner} will only return a cell whose mvcc is
041 * less than or equal to given read point. For
042 * {@link org.apache.hadoop.hbase.client.IsolationLevel#READ_UNCOMMITTED}, we just set the read
043 * point to {@link Long#MAX_VALUE}, i.e. still do not need to consider it.
044 */
045@InterfaceAudience.Private
046public abstract class UserScanQueryMatcher extends ScanQueryMatcher {
047
048  protected final boolean hasNullColumn;
049
050  protected final Filter filter;
051
052  protected final byte[] stopRow;
053
054  protected final TimeRange tr;
055
056  private final int versionsAfterFilter;
057
058  private int count = 0;
059
060  private Cell curColCell = null;
061
062  private static Cell createStartKey(Scan scan, ScanInfo scanInfo) {
063    if (scan.includeStartRow()) {
064      return createStartKeyFromRow(scan.getStartRow(), scanInfo);
065    } else {
066      return PrivateCellUtil.createLastOnRow(scan.getStartRow());
067    }
068  }
069
070  protected UserScanQueryMatcher(Scan scan, ScanInfo scanInfo, ColumnTracker columns,
071    boolean hasNullColumn, long oldestUnexpiredTS, long now) {
072    super(createStartKey(scan, scanInfo), scanInfo, columns, oldestUnexpiredTS, now);
073    this.hasNullColumn = hasNullColumn;
074    this.filter = scan.getFilter();
075    if (this.filter != null) {
076      this.versionsAfterFilter = scan.isRaw()
077        ? scan.getMaxVersions()
078        : Math.min(scan.getMaxVersions(), scanInfo.getMaxVersions());
079    } else {
080      this.versionsAfterFilter = 0;
081    }
082    this.stopRow = scan.getStopRow();
083    TimeRange timeRange = scan.getColumnFamilyTimeRange().get(scanInfo.getFamily());
084    if (timeRange == null) {
085      this.tr = scan.getTimeRange();
086    } else {
087      this.tr = timeRange;
088    }
089  }
090
091  @Override
092  public boolean hasNullColumnInQuery() {
093    return hasNullColumn;
094  }
095
096  @Override
097  public boolean isUserScan() {
098    return true;
099  }
100
101  @Override
102  public Filter getFilter() {
103    return filter;
104  }
105
106  @Override
107  public Cell getNextKeyHint(Cell cell) throws IOException {
108    if (filter == null) {
109      return null;
110    } else {
111      return filter.getNextCellHint(cell);
112    }
113  }
114
115  @Override
116  public void beforeShipped() throws IOException {
117    super.beforeShipped();
118    if (curColCell != null) {
119      this.curColCell = KeyValueUtil.toNewKeyCell(this.curColCell);
120    }
121  }
122
123  protected final MatchCode matchColumn(Cell cell, long timestamp, byte typeByte)
124    throws IOException {
125    int tsCmp = tr.compare(timestamp);
126    if (tsCmp > 0) {
127      return MatchCode.SKIP;
128    }
129    if (tsCmp < 0) {
130      return columns.getNextRowOrNextColumn(cell);
131    }
132    // STEP 1: Check if the column is part of the requested columns
133    MatchCode matchCode = columns.checkColumn(cell, typeByte);
134    if (matchCode != MatchCode.INCLUDE) {
135      return matchCode;
136    }
137    /*
138     * STEP 2: check the number of versions needed. This method call returns SKIP, SEEK_NEXT_COL,
139     * INCLUDE, INCLUDE_AND_SEEK_NEXT_COL, or INCLUDE_AND_SEEK_NEXT_ROW.
140     */
141    matchCode = columns.checkVersions(cell, timestamp, typeByte, false);
142    switch (matchCode) {
143      case SKIP:
144        return MatchCode.SKIP;
145      case SEEK_NEXT_COL:
146        return MatchCode.SEEK_NEXT_COL;
147      default:
148        // It means it is INCLUDE, INCLUDE_AND_SEEK_NEXT_COL or INCLUDE_AND_SEEK_NEXT_ROW.
149        assert matchCode == MatchCode.INCLUDE || matchCode == MatchCode.INCLUDE_AND_SEEK_NEXT_COL
150          || matchCode == MatchCode.INCLUDE_AND_SEEK_NEXT_ROW;
151        break;
152    }
153
154    return filter == null
155      ? matchCode
156      : mergeFilterResponse(cell, matchCode, filter.filterCell(cell));
157  }
158
159  /**
160   * Call this when scan has filter. Decide the desired behavior by checkVersions's MatchCode and
161   * filterCell's ReturnCode. Cell may be skipped by filter, so the column versions in result may be
162   * less than user need. It need to check versions again when filter and columnTracker both include
163   * the cell. <br/>
164   *
165   * <pre>
166   * ColumnChecker                FilterResponse               Desired behavior
167   * INCLUDE                      SKIP                         SKIP
168   * INCLUDE                      NEXT_COL                     SEEK_NEXT_COL or SEEK_NEXT_ROW
169   * INCLUDE                      NEXT_ROW                     SEEK_NEXT_ROW
170   * INCLUDE                      SEEK_NEXT_USING_HINT         SEEK_NEXT_USING_HINT
171   * INCLUDE                      INCLUDE                      INCLUDE
172   * INCLUDE                      INCLUDE_AND_NEXT_COL         INCLUDE_AND_SEEK_NEXT_COL
173   * INCLUDE                      INCLUDE_AND_SEEK_NEXT_ROW    INCLUDE_AND_SEEK_NEXT_ROW
174   * INCLUDE_AND_SEEK_NEXT_COL    SKIP                         SEEK_NEXT_COL
175   * INCLUDE_AND_SEEK_NEXT_COL    NEXT_COL                     SEEK_NEXT_COL or SEEK_NEXT_ROW
176   * INCLUDE_AND_SEEK_NEXT_COL    NEXT_ROW                     SEEK_NEXT_ROW
177   * INCLUDE_AND_SEEK_NEXT_COL    SEEK_NEXT_USING_HINT         SEEK_NEXT_USING_HINT
178   * INCLUDE_AND_SEEK_NEXT_COL    INCLUDE                      INCLUDE_AND_SEEK_NEXT_COL
179   * INCLUDE_AND_SEEK_NEXT_COL    INCLUDE_AND_NEXT_COL         INCLUDE_AND_SEEK_NEXT_COL
180   * INCLUDE_AND_SEEK_NEXT_COL    INCLUDE_AND_SEEK_NEXT_ROW    INCLUDE_AND_SEEK_NEXT_ROW
181   * INCLUDE_AND_SEEK_NEXT_ROW    SKIP                         SEEK_NEXT_ROW
182   * INCLUDE_AND_SEEK_NEXT_ROW    NEXT_COL                     SEEK_NEXT_ROW
183   * INCLUDE_AND_SEEK_NEXT_ROW    NEXT_ROW                     SEEK_NEXT_ROW
184   * INCLUDE_AND_SEEK_NEXT_ROW    SEEK_NEXT_USING_HINT         SEEK_NEXT_USING_HINT
185   * INCLUDE_AND_SEEK_NEXT_ROW    INCLUDE                      INCLUDE_AND_SEEK_NEXT_ROW
186   * INCLUDE_AND_SEEK_NEXT_ROW    INCLUDE_AND_NEXT_COL         INCLUDE_AND_SEEK_NEXT_ROW
187   * INCLUDE_AND_SEEK_NEXT_ROW    INCLUDE_AND_SEEK_NEXT_ROW    INCLUDE_AND_SEEK_NEXT_ROW
188   * </pre>
189   */
190  private final MatchCode mergeFilterResponse(Cell cell, MatchCode matchCode,
191    ReturnCode filterResponse) {
192    switch (filterResponse) {
193      case SKIP:
194        if (matchCode == MatchCode.INCLUDE) {
195          return MatchCode.SKIP;
196        } else if (matchCode == MatchCode.INCLUDE_AND_SEEK_NEXT_COL) {
197          return MatchCode.SEEK_NEXT_COL;
198        } else if (matchCode == MatchCode.INCLUDE_AND_SEEK_NEXT_ROW) {
199          return MatchCode.SEEK_NEXT_ROW;
200        }
201        break;
202      case NEXT_COL:
203        if (matchCode == MatchCode.INCLUDE || matchCode == MatchCode.INCLUDE_AND_SEEK_NEXT_COL) {
204          return columns.getNextRowOrNextColumn(cell);
205        } else if (matchCode == MatchCode.INCLUDE_AND_SEEK_NEXT_ROW) {
206          return MatchCode.SEEK_NEXT_ROW;
207        }
208        break;
209      case NEXT_ROW:
210        return MatchCode.SEEK_NEXT_ROW;
211      case SEEK_NEXT_USING_HINT:
212        return MatchCode.SEEK_NEXT_USING_HINT;
213      case INCLUDE:
214        break;
215      case INCLUDE_AND_NEXT_COL:
216        if (matchCode == MatchCode.INCLUDE) {
217          matchCode = MatchCode.INCLUDE_AND_SEEK_NEXT_COL;
218        }
219        break;
220      case INCLUDE_AND_SEEK_NEXT_ROW:
221        matchCode = MatchCode.INCLUDE_AND_SEEK_NEXT_ROW;
222        break;
223      default:
224        throw new RuntimeException("UNEXPECTED");
225    }
226
227    // It means it is INCLUDE, INCLUDE_AND_SEEK_NEXT_COL or INCLUDE_AND_SEEK_NEXT_ROW.
228    assert matchCode == MatchCode.INCLUDE || matchCode == MatchCode.INCLUDE_AND_SEEK_NEXT_COL
229      || matchCode == MatchCode.INCLUDE_AND_SEEK_NEXT_ROW;
230
231    // We need to make sure that the number of cells returned will not exceed max version in scan
232    // when the match code is INCLUDE* case.
233    if (curColCell == null || !CellUtil.matchingRowColumn(cell, curColCell)) {
234      count = 0;
235      curColCell = cell;
236    }
237    count += 1;
238
239    if (count > versionsAfterFilter) {
240      // when the number of cells exceed max version in scan, we should return SEEK_NEXT_COL match
241      // code, but if current code is INCLUDE_AND_SEEK_NEXT_ROW, we can optimize to choose the max
242      // step between SEEK_NEXT_COL and INCLUDE_AND_SEEK_NEXT_ROW, which is SEEK_NEXT_ROW.
243      if (matchCode == MatchCode.INCLUDE_AND_SEEK_NEXT_ROW) {
244        matchCode = MatchCode.SEEK_NEXT_ROW;
245      } else {
246        matchCode = MatchCode.SEEK_NEXT_COL;
247      }
248    }
249    if (matchCode == MatchCode.INCLUDE_AND_SEEK_NEXT_COL || matchCode == MatchCode.SEEK_NEXT_COL) {
250      // Update column tracker to next column, As we use the column hint from the tracker to seek
251      // to next cell (HBASE-19749)
252      columns.doneWithColumn(cell);
253    }
254    return matchCode;
255  }
256
257  protected abstract boolean isGet();
258
259  protected abstract boolean moreRowsMayExistsAfter(int cmpToStopRow);
260
261  @Override
262  public boolean moreRowsMayExistAfter(Cell cell) {
263    // If a 'get' Scan -- we are doing a Get (every Get is a single-row Scan in implementation) --
264    // then we are looking at one row only, the one specified in the Get coordinate..so we know
265    // for sure that there are no more rows on this Scan
266    if (isGet()) {
267      return false;
268    }
269    // If no stopRow, return that there may be more rows. The tests that follow depend on a
270    // non-empty, non-default stopRow so this little test below short-circuits out doing the
271    // following compares.
272    if (this.stopRow == null || this.stopRow.length == 0) {
273      return true;
274    }
275    return moreRowsMayExistsAfter(rowComparator.compareRows(cell, stopRow, 0, stopRow.length));
276  }
277
278  public static UserScanQueryMatcher create(Scan scan, ScanInfo scanInfo,
279    NavigableSet<byte[]> columns, long oldestUnexpiredTS, long now,
280    RegionCoprocessorHost regionCoprocessorHost) throws IOException {
281    boolean hasNullColumn =
282      !(columns != null && columns.size() != 0 && columns.first().length != 0);
283    Pair<DeleteTracker, ColumnTracker> trackers =
284      getTrackers(regionCoprocessorHost, columns, scanInfo, oldestUnexpiredTS, scan);
285    DeleteTracker deleteTracker = trackers.getFirst();
286    ColumnTracker columnTracker = trackers.getSecond();
287    if (scan.isRaw()) {
288      return RawScanQueryMatcher.create(scan, scanInfo, columnTracker, hasNullColumn,
289        oldestUnexpiredTS, now);
290    } else {
291      return NormalUserScanQueryMatcher.create(scan, scanInfo, columnTracker, deleteTracker,
292        hasNullColumn, oldestUnexpiredTS, now);
293    }
294  }
295}