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}