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; 022 023import org.apache.hadoop.hbase.Cell; 024import org.apache.hadoop.hbase.CellUtil; 025import org.apache.hadoop.hbase.PrivateCellUtil; 026import org.apache.hadoop.hbase.KeyValueUtil; 027import org.apache.yetus.audience.InterfaceAudience; 028import org.apache.hadoop.hbase.client.Scan; 029import org.apache.hadoop.hbase.filter.Filter; 030import org.apache.hadoop.hbase.filter.Filter.ReturnCode; 031import org.apache.hadoop.hbase.io.TimeRange; 032import org.apache.hadoop.hbase.regionserver.RegionCoprocessorHost; 033import org.apache.hadoop.hbase.regionserver.ScanInfo; 034import org.apache.hadoop.hbase.util.Pair; 035 036/** 037 * Query matcher for user scan. 038 * <p> 039 * We do not consider mvcc here because 040 * {@link org.apache.hadoop.hbase.regionserver.StoreFileScanner} and 041 * {@link org.apache.hadoop.hbase.regionserver.SegmentScanner} will only return a cell whose mvcc is 042 * less than or equal to given read point. For 043 * {@link org.apache.hadoop.hbase.client.IsolationLevel#READ_UNCOMMITTED}, we just set the read 044 * point to {@link Long#MAX_VALUE}, i.e. still do not need to consider it. 045 */ 046@InterfaceAudience.Private 047public abstract class UserScanQueryMatcher extends ScanQueryMatcher { 048 049 protected final boolean hasNullColumn; 050 051 protected final Filter filter; 052 053 protected final byte[] stopRow; 054 055 protected final TimeRange tr; 056 057 private final int versionsAfterFilter; 058 059 private int count = 0; 060 061 private Cell curColCell = null; 062 063 private static Cell createStartKey(Scan scan, ScanInfo scanInfo) { 064 if (scan.includeStartRow()) { 065 return createStartKeyFromRow(scan.getStartRow(), scanInfo); 066 } else { 067 return PrivateCellUtil.createLastOnRow(scan.getStartRow()); 068 } 069 } 070 071 protected UserScanQueryMatcher(Scan scan, ScanInfo scanInfo, ColumnTracker columns, 072 boolean hasNullColumn, long oldestUnexpiredTS, long now) { 073 super(createStartKey(scan, scanInfo), scanInfo, columns, oldestUnexpiredTS, now); 074 this.hasNullColumn = hasNullColumn; 075 this.filter = scan.getFilter(); 076 if (this.filter != null) { 077 this.versionsAfterFilter = 078 scan.isRaw() ? scan.getMaxVersions() : Math.min(scan.getMaxVersions(), 079 scanInfo.getMaxVersions()); 080 } else { 081 this.versionsAfterFilter = 0; 082 } 083 this.stopRow = scan.getStopRow(); 084 TimeRange timeRange = scan.getColumnFamilyTimeRange().get(scanInfo.getFamily()); 085 if (timeRange == null) { 086 this.tr = scan.getTimeRange(); 087 } else { 088 this.tr = timeRange; 089 } 090 } 091 092 @Override 093 public boolean hasNullColumnInQuery() { 094 return hasNullColumn; 095 } 096 097 @Override 098 public boolean isUserScan() { 099 return true; 100 } 101 102 @Override 103 public Filter getFilter() { 104 return filter; 105 } 106 107 @Override 108 public Cell getNextKeyHint(Cell cell) throws IOException { 109 if (filter == null) { 110 return null; 111 } else { 112 return filter.getNextCellHint(cell); 113 } 114 } 115 116 @Override 117 public void beforeShipped() throws IOException { 118 super.beforeShipped(); 119 if (curColCell != null) { 120 this.curColCell = KeyValueUtil.toNewKeyCell(this.curColCell); 121 } 122 } 123 124 protected final MatchCode matchColumn(Cell cell, long timestamp, byte typeByte) 125 throws IOException { 126 int tsCmp = tr.compare(timestamp); 127 if (tsCmp > 0) { 128 return MatchCode.SKIP; 129 } 130 if (tsCmp < 0) { 131 return columns.getNextRowOrNextColumn(cell); 132 } 133 // STEP 1: Check if the column is part of the requested columns 134 MatchCode matchCode = columns.checkColumn(cell, typeByte); 135 if (matchCode != MatchCode.INCLUDE) { 136 return matchCode; 137 } 138 /* 139 * STEP 2: check the number of versions needed. This method call returns SKIP, SEEK_NEXT_COL, 140 * INCLUDE, INCLUDE_AND_SEEK_NEXT_COL, or INCLUDE_AND_SEEK_NEXT_ROW. 141 */ 142 matchCode = columns.checkVersions(cell, timestamp, typeByte, false); 143 switch (matchCode) { 144 case SKIP: 145 return MatchCode.SKIP; 146 case SEEK_NEXT_COL: 147 return MatchCode.SEEK_NEXT_COL; 148 default: 149 // It means it is INCLUDE, INCLUDE_AND_SEEK_NEXT_COL or INCLUDE_AND_SEEK_NEXT_ROW. 150 assert matchCode == MatchCode.INCLUDE || matchCode == MatchCode.INCLUDE_AND_SEEK_NEXT_COL 151 || matchCode == MatchCode.INCLUDE_AND_SEEK_NEXT_ROW; 152 break; 153 } 154 155 return filter == null ? matchCode : mergeFilterResponse(cell, matchCode, 156 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 = getTrackers(regionCoprocessorHost, columns, 284 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}