View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.regionserver.querymatcher;
19  
20  import java.io.IOException;
21  import java.util.NavigableSet;
22  
23  import org.apache.hadoop.hbase.Cell;
24  import org.apache.hadoop.hbase.KeyValueUtil;
25  import org.apache.hadoop.hbase.classification.InterfaceAudience;
26  import org.apache.hadoop.hbase.client.Scan;
27  import org.apache.hadoop.hbase.filter.Filter;
28  import org.apache.hadoop.hbase.filter.Filter.ReturnCode;
29  import org.apache.hadoop.hbase.io.TimeRange;
30  import org.apache.hadoop.hbase.regionserver.RegionCoprocessorHost;
31  import org.apache.hadoop.hbase.regionserver.ScanInfo;
32  
33  /**
34   * Query matcher for user scan.
35   * <p>
36   * We do not consider mvcc here because
37   * {@link org.apache.hadoop.hbase.regionserver.StoreFileScanner} and
38   * {@link org.apache.hadoop.hbase.regionserver.MemStore#getScanners(long)} will only return a cell
39   * whose mvcc is less than or equal to given read point. For
40   * {@link org.apache.hadoop.hbase.client.IsolationLevel#READ_UNCOMMITTED}, we just set the read
41   * point to {@link Long#MAX_VALUE}, i.e. still do not need to consider it.
42   */
43  @InterfaceAudience.Private
44  public abstract class UserScanQueryMatcher extends ScanQueryMatcher {
45  
46    protected final boolean hasNullColumn;
47  
48    protected final Filter filter;
49  
50    protected final byte[] stopRow;
51  
52    protected final TimeRange tr;
53  
54    private static Cell createStartKey(Scan scan, ScanInfo scanInfo) {
55      if (scan.includeStartRow()) {
56        return createStartKeyFromRow(scan.getStartRow(), scanInfo);
57      } else {
58        return KeyValueUtil.createLastOnRow(scan.getStartRow());
59      }
60    }
61  
62    protected UserScanQueryMatcher(Scan scan, ScanInfo scanInfo, ColumnTracker columns,
63        boolean hasNullColumn, long oldestUnexpiredTS, long now) {
64      super(createStartKey(scan, scanInfo), scanInfo, columns, oldestUnexpiredTS, now);
65      this.hasNullColumn = hasNullColumn;
66      this.filter = scan.getFilter();
67      this.stopRow = scan.getStopRow();
68      TimeRange timeRange = scan.getColumnFamilyTimeRange().get(scanInfo.getFamily());
69      if (timeRange == null) {
70        this.tr = scan.getTimeRange();
71      } else {
72        this.tr = timeRange;
73      }
74    }
75  
76    @Override
77    public boolean hasNullColumnInQuery() {
78      return hasNullColumn;
79    }
80  
81    @Override
82    public boolean isUserScan() {
83      return true;
84    }
85  
86    @Override
87    public Filter getFilter() {
88      return filter;
89    }
90  
91    @Override
92    public Cell getNextKeyHint(Cell cell) throws IOException {
93      if (filter == null) {
94        return null;
95      } else {
96        return filter.getNextCellHint(cell);
97      }
98    }
99  
100   protected final MatchCode matchColumn(Cell cell, long timestamp, byte typeByte)
101       throws IOException {
102     int tsCmp = tr.compare(timestamp);
103     if (tsCmp > 0) {
104       return MatchCode.SKIP;
105     }
106     int qualifierOffset = cell.getQualifierOffset();
107     int qualifierLength = cell.getQualifierLength();
108     if (tsCmp < 0) {
109       return columns.getNextRowOrNextColumn(cell.getQualifierArray(), qualifierOffset,
110         qualifierLength);
111     }
112     // STEP 1: Check if the column is part of the requested columns
113     MatchCode colChecker = columns.checkColumn(cell.getQualifierArray(), qualifierOffset,
114       qualifierLength, typeByte);
115     if (colChecker != MatchCode.INCLUDE) {
116       return colChecker;
117     }
118     ReturnCode filterResponse = ReturnCode.SKIP;
119     // STEP 2: Yes, the column is part of the requested columns. Check if filter is present
120     if (filter != null) {
121       // STEP 3: Filter the key value and return if it filters out
122       filterResponse = filter.filterKeyValue(cell);
123       switch (filterResponse) {
124         case SKIP:
125           return MatchCode.SKIP;
126         case NEXT_COL:
127           return columns.getNextRowOrNextColumn(cell.getQualifierArray(), qualifierOffset,
128             qualifierLength);
129         case NEXT_ROW:
130           return MatchCode.SEEK_NEXT_ROW;
131         case SEEK_NEXT_USING_HINT:
132           return MatchCode.SEEK_NEXT_USING_HINT;
133         default:
134           // It means it is either include or include and seek next
135           break;
136       }
137     }
138     /*
139      * STEP 4: Reaching this step means the column is part of the requested columns and either
140      * the filter is null or the filter has returned INCLUDE or INCLUDE_AND_NEXT_COL response.
141      * Now check the number of versions needed. This method call returns SKIP, INCLUDE,
142      * INCLUDE_AND_SEEK_NEXT_ROW, INCLUDE_AND_SEEK_NEXT_COL.
143      *
144      * FilterResponse            ColumnChecker               Desired behavior
145      * INCLUDE                   SKIP                        row has already been included, SKIP.
146      * INCLUDE                   INCLUDE                     INCLUDE
147      * INCLUDE                   INCLUDE_AND_SEEK_NEXT_COL   INCLUDE_AND_SEEK_NEXT_COL
148      * INCLUDE                   INCLUDE_AND_SEEK_NEXT_ROW   INCLUDE_AND_SEEK_NEXT_ROW
149      * INCLUDE_AND_SEEK_NEXT_COL SKIP                        row has already been included, SKIP.
150      * INCLUDE_AND_SEEK_NEXT_COL INCLUDE                     INCLUDE_AND_SEEK_NEXT_COL
151      * INCLUDE_AND_SEEK_NEXT_COL INCLUDE_AND_SEEK_NEXT_COL   INCLUDE_AND_SEEK_NEXT_COL
152      * INCLUDE_AND_SEEK_NEXT_COL INCLUDE_AND_SEEK_NEXT_ROW   INCLUDE_AND_SEEK_NEXT_ROW
153      *
154      * In all the above scenarios, we return the column checker return value except for
155      * FilterResponse (INCLUDE_AND_SEEK_NEXT_COL) and ColumnChecker(INCLUDE)
156      */
157     colChecker = columns.checkVersions(cell.getQualifierArray(), qualifierOffset, qualifierLength,
158       timestamp, typeByte, false);
159     return (filterResponse == ReturnCode.INCLUDE_AND_NEXT_COL && colChecker == MatchCode.INCLUDE)
160         ? MatchCode.INCLUDE_AND_SEEK_NEXT_COL : colChecker;
161   }
162 
163   protected abstract boolean isGet();
164 
165   protected abstract boolean moreRowsMayExistsAfter(int cmpToStopRow);
166 
167   @Override
168   public boolean moreRowsMayExistAfter(Cell cell) {
169     // If a 'get' Scan -- we are doing a Get (every Get is a single-row Scan in implementation) --
170     // then we are looking at one row only, the one specified in the Get coordinate..so we know
171     // for sure that there are no more rows on this Scan
172     if (isGet()) {
173       return false;
174     }
175     // If no stopRow, return that there may be more rows. The tests that follow depend on a
176     // non-empty, non-default stopRow so this little test below short-circuits out doing the
177     // following compares.
178     if (this.stopRow == null || this.stopRow.length == 0) {
179       return true;
180     }
181     return moreRowsMayExistsAfter(rowComparator.compareRows(cell, stopRow, 0, stopRow.length));
182   }
183 
184   public static UserScanQueryMatcher create(Scan scan, ScanInfo scanInfo,
185       NavigableSet<byte[]> columns, long oldestUnexpiredTS, long now,
186       RegionCoprocessorHost regionCoprocessorHost) throws IOException {
187     int maxVersions = scan.isRaw() ? scan.getMaxVersions()
188         : Math.min(scan.getMaxVersions(), scanInfo.getMaxVersions());
189     boolean hasNullColumn;
190     ColumnTracker columnTracker;
191     if (columns == null || columns.size() == 0) {
192       // there is always a null column in the wildcard column query.
193       hasNullColumn = true;
194       // use a specialized scan for wildcard column tracker.
195       columnTracker = new ScanWildcardColumnTracker(scanInfo.getMinVersions(), maxVersions,
196           oldestUnexpiredTS);
197     } else {
198       // We can share the ExplicitColumnTracker, diff is we reset
199       // between rows, not between storefiles.
200       // whether there is null column in the explicit column query
201       hasNullColumn = columns.first().length == 0;
202       columnTracker = new ExplicitColumnTracker(columns, scanInfo.getMinVersions(), maxVersions,
203           oldestUnexpiredTS);
204     }
205     if (scan.isRaw()) {
206       return RawScanQueryMatcher.create(scan, scanInfo, columnTracker, hasNullColumn,
207         oldestUnexpiredTS, now);
208     } else {
209       return NormalUserScanQueryMatcher.create(scan, scanInfo, columnTracker, hasNullColumn,
210         oldestUnexpiredTS, now, regionCoprocessorHost);
211     }
212   }
213 }