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 static org.junit.Assert.assertArrayEquals; 021import static org.junit.Assert.assertEquals; 022import static org.junit.Assert.assertFalse; 023 024import java.io.IOException; 025import java.util.ArrayList; 026import java.util.List; 027import org.apache.hadoop.hbase.Cell; 028import org.apache.hadoop.hbase.HBaseClassTestRule; 029import org.apache.hadoop.hbase.HConstants; 030import org.apache.hadoop.hbase.KeepDeletedCells; 031import org.apache.hadoop.hbase.KeyValue; 032import org.apache.hadoop.hbase.PrivateCellUtil; 033import org.apache.hadoop.hbase.client.Scan; 034import org.apache.hadoop.hbase.filter.FilterBase; 035import org.apache.hadoop.hbase.regionserver.ScanInfo; 036import org.apache.hadoop.hbase.regionserver.querymatcher.ScanQueryMatcher.MatchCode; 037import org.apache.hadoop.hbase.testclassification.RegionServerTests; 038import org.apache.hadoop.hbase.testclassification.SmallTests; 039import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 040import org.junit.ClassRule; 041import org.junit.Test; 042import org.junit.experimental.categories.Category; 043import org.slf4j.Logger; 044import org.slf4j.LoggerFactory; 045 046@Category({ RegionServerTests.class, SmallTests.class }) 047public class TestUserScanQueryMatcher extends AbstractTestScanQueryMatcher { 048 049 @ClassRule 050 public static final HBaseClassTestRule CLASS_RULE = 051 HBaseClassTestRule.forClass(TestUserScanQueryMatcher.class); 052 053 private static final Logger LOG = LoggerFactory.getLogger(TestUserScanQueryMatcher.class); 054 055 /** 056 * This is a cryptic test. It is checking that we don't include a fake cell, one that has a 057 * timestamp of {@link HConstants#OLDEST_TIMESTAMP}. See HBASE-16074 for background. 058 * @throws IOException 059 */ 060 @Test 061 public void testNeverIncludeFakeCell() throws IOException { 062 long now = EnvironmentEdgeManager.currentTime(); 063 // Do with fam2 which has a col2 qualifier. 064 UserScanQueryMatcher qm = UserScanQueryMatcher.create(scan, 065 new ScanInfo(this.conf, fam2, 10, 1, ttl, KeepDeletedCells.FALSE, 066 HConstants.DEFAULT_BLOCKSIZE, 0, rowComparator, false), 067 get.getFamilyMap().get(fam2), now - ttl, now, null); 068 Cell kv = new KeyValue(row1, fam2, col2, 1, data); 069 Cell cell = PrivateCellUtil.createLastOnRowCol(kv); 070 qm.setToNewRow(kv); 071 MatchCode code = qm.match(cell); 072 assertFalse(code.compareTo(MatchCode.SEEK_NEXT_COL) != 0); 073 } 074 075 @Test 076 public void testMatchExplicitColumns() throws IOException { 077 // Moving up from the Tracker by using Gets and List<KeyValue> instead 078 // of just byte [] 079 080 // Expected result 081 List<MatchCode> expected = new ArrayList<>(6); 082 expected.add(ScanQueryMatcher.MatchCode.SEEK_NEXT_COL); 083 expected.add(ScanQueryMatcher.MatchCode.INCLUDE_AND_SEEK_NEXT_COL); 084 expected.add(ScanQueryMatcher.MatchCode.SEEK_NEXT_COL); 085 expected.add(ScanQueryMatcher.MatchCode.INCLUDE_AND_SEEK_NEXT_COL); 086 expected.add(ScanQueryMatcher.MatchCode.INCLUDE_AND_SEEK_NEXT_ROW); 087 expected.add(ScanQueryMatcher.MatchCode.DONE); 088 089 long now = EnvironmentEdgeManager.currentTime(); 090 // 2,4,5 091 UserScanQueryMatcher qm = UserScanQueryMatcher.create( 092 scan, new ScanInfo(this.conf, fam2, 0, 1, ttl, KeepDeletedCells.FALSE, 093 HConstants.DEFAULT_BLOCKSIZE, 0, rowComparator, false), 094 get.getFamilyMap().get(fam2), now - ttl, now, null); 095 096 List<KeyValue> memstore = new ArrayList<>(6); 097 memstore.add(new KeyValue(row1, fam2, col1, 1, data)); 098 memstore.add(new KeyValue(row1, fam2, col2, 1, data)); 099 memstore.add(new KeyValue(row1, fam2, col3, 1, data)); 100 memstore.add(new KeyValue(row1, fam2, col4, 1, data)); 101 memstore.add(new KeyValue(row1, fam2, col5, 1, data)); 102 103 memstore.add(new KeyValue(row2, fam1, col1, data)); 104 105 List<ScanQueryMatcher.MatchCode> actual = new ArrayList<>(memstore.size()); 106 KeyValue k = memstore.get(0); 107 qm.setToNewRow(k); 108 109 for (KeyValue kv : memstore) { 110 actual.add(qm.match(kv)); 111 } 112 113 assertEquals(expected.size(), actual.size()); 114 for (int i = 0; i < expected.size(); i++) { 115 LOG.debug("expected " + expected.get(i) + ", actual " + actual.get(i)); 116 assertEquals(expected.get(i), actual.get(i)); 117 } 118 } 119 120 @Test 121 public void testMatch_Wildcard() throws IOException { 122 // Moving up from the Tracker by using Gets and List<KeyValue> instead 123 // of just byte [] 124 125 // Expected result 126 List<MatchCode> expected = new ArrayList<>(6); 127 expected.add(ScanQueryMatcher.MatchCode.INCLUDE); 128 expected.add(ScanQueryMatcher.MatchCode.INCLUDE); 129 expected.add(ScanQueryMatcher.MatchCode.INCLUDE); 130 expected.add(ScanQueryMatcher.MatchCode.INCLUDE); 131 expected.add(ScanQueryMatcher.MatchCode.INCLUDE); 132 expected.add(ScanQueryMatcher.MatchCode.DONE); 133 134 long now = EnvironmentEdgeManager.currentTime(); 135 UserScanQueryMatcher qm = UserScanQueryMatcher.create(scan, new ScanInfo(this.conf, fam2, 0, 1, 136 ttl, KeepDeletedCells.FALSE, HConstants.DEFAULT_BLOCKSIZE, 0, rowComparator, false), 137 null, now - ttl, now, null); 138 139 List<KeyValue> memstore = new ArrayList<>(6); 140 memstore.add(new KeyValue(row1, fam2, col1, 1, data)); 141 memstore.add(new KeyValue(row1, fam2, col2, 1, data)); 142 memstore.add(new KeyValue(row1, fam2, col3, 1, data)); 143 memstore.add(new KeyValue(row1, fam2, col4, 1, data)); 144 memstore.add(new KeyValue(row1, fam2, col5, 1, data)); 145 memstore.add(new KeyValue(row2, fam1, col1, 1, data)); 146 147 List<ScanQueryMatcher.MatchCode> actual = new ArrayList<>(memstore.size()); 148 149 KeyValue k = memstore.get(0); 150 qm.setToNewRow(k); 151 152 for (KeyValue kv : memstore) { 153 actual.add(qm.match(kv)); 154 } 155 156 assertEquals(expected.size(), actual.size()); 157 for (int i = 0; i < expected.size(); i++) { 158 LOG.debug("expected " + expected.get(i) + ", actual " + actual.get(i)); 159 assertEquals(expected.get(i), actual.get(i)); 160 } 161 } 162 163 /** 164 * Verify that {@link ScanQueryMatcher} only skips expired KeyValue instances and does not exit 165 * early from the row (skipping later non-expired KeyValues). This version mimics a Get with 166 * explicitly specified column qualifiers. 167 * @throws IOException 168 */ 169 @Test 170 public void testMatch_ExpiredExplicit() throws IOException { 171 172 long testTTL = 1000; 173 MatchCode[] expected = new MatchCode[] { ScanQueryMatcher.MatchCode.SEEK_NEXT_COL, 174 ScanQueryMatcher.MatchCode.INCLUDE_AND_SEEK_NEXT_COL, 175 ScanQueryMatcher.MatchCode.SEEK_NEXT_COL, 176 ScanQueryMatcher.MatchCode.INCLUDE_AND_SEEK_NEXT_COL, 177 ScanQueryMatcher.MatchCode.SEEK_NEXT_ROW, ScanQueryMatcher.MatchCode.DONE }; 178 179 long now = EnvironmentEdgeManager.currentTime(); 180 UserScanQueryMatcher qm = UserScanQueryMatcher.create(scan, 181 new ScanInfo(this.conf, fam2, 0, 1, testTTL, KeepDeletedCells.FALSE, 182 HConstants.DEFAULT_BLOCKSIZE, 0, rowComparator, false), 183 get.getFamilyMap().get(fam2), now - testTTL, now, null); 184 185 KeyValue[] kvs = new KeyValue[] { new KeyValue(row1, fam2, col1, now - 100, data), 186 new KeyValue(row1, fam2, col2, now - 50, data), 187 new KeyValue(row1, fam2, col3, now - 5000, data), 188 new KeyValue(row1, fam2, col4, now - 500, data), 189 new KeyValue(row1, fam2, col5, now - 10000, data), 190 new KeyValue(row2, fam1, col1, now - 10, data) }; 191 192 KeyValue k = kvs[0]; 193 qm.setToNewRow(k); 194 195 List<MatchCode> actual = new ArrayList<>(kvs.length); 196 for (KeyValue kv : kvs) { 197 actual.add(qm.match(kv)); 198 } 199 200 assertEquals(expected.length, actual.size()); 201 for (int i = 0; i < expected.length; i++) { 202 LOG.debug("expected " + expected[i] + ", actual " + actual.get(i)); 203 assertEquals(expected[i], actual.get(i)); 204 } 205 } 206 207 /** 208 * Verify that {@link ScanQueryMatcher} only skips expired KeyValue instances and does not exit 209 * early from the row (skipping later non-expired KeyValues). This version mimics a Get with 210 * wildcard-inferred column qualifiers. 211 * @throws IOException 212 */ 213 @Test 214 public void testMatch_ExpiredWildcard() throws IOException { 215 216 long testTTL = 1000; 217 MatchCode[] expected = 218 new MatchCode[] { ScanQueryMatcher.MatchCode.INCLUDE, ScanQueryMatcher.MatchCode.INCLUDE, 219 ScanQueryMatcher.MatchCode.SEEK_NEXT_COL, ScanQueryMatcher.MatchCode.INCLUDE, 220 ScanQueryMatcher.MatchCode.SEEK_NEXT_COL, ScanQueryMatcher.MatchCode.DONE }; 221 222 long now = EnvironmentEdgeManager.currentTime(); 223 UserScanQueryMatcher qm = 224 UserScanQueryMatcher.create(scan, 225 new ScanInfo(this.conf, fam2, 0, 1, testTTL, KeepDeletedCells.FALSE, 226 HConstants.DEFAULT_BLOCKSIZE, 0, rowComparator, false), 227 null, now - testTTL, now, null); 228 229 KeyValue[] kvs = new KeyValue[] { new KeyValue(row1, fam2, col1, now - 100, data), 230 new KeyValue(row1, fam2, col2, now - 50, data), 231 new KeyValue(row1, fam2, col3, now - 5000, data), 232 new KeyValue(row1, fam2, col4, now - 500, data), 233 new KeyValue(row1, fam2, col5, now - 10000, data), 234 new KeyValue(row2, fam1, col1, now - 10, data) }; 235 KeyValue k = kvs[0]; 236 qm.setToNewRow(k); 237 238 List<ScanQueryMatcher.MatchCode> actual = new ArrayList<>(kvs.length); 239 for (KeyValue kv : kvs) { 240 actual.add(qm.match(kv)); 241 } 242 243 assertEquals(expected.length, actual.size()); 244 for (int i = 0; i < expected.length; i++) { 245 LOG.debug("expected " + expected[i] + ", actual " + actual.get(i)); 246 assertEquals(expected[i], actual.get(i)); 247 } 248 } 249 250 private static class AlwaysIncludeAndSeekNextRowFilter extends FilterBase { 251 252 @Override 253 public ReturnCode filterKeyValue(final Cell c) throws IOException { 254 return ReturnCode.INCLUDE_AND_SEEK_NEXT_ROW; 255 } 256 } 257 258 @Test 259 public void testMatchWhenFilterReturnsIncludeAndSeekNextRow() throws IOException { 260 List<MatchCode> expected = new ArrayList<>(); 261 expected.add(ScanQueryMatcher.MatchCode.INCLUDE_AND_SEEK_NEXT_ROW); 262 expected.add(ScanQueryMatcher.MatchCode.DONE); 263 264 Scan scanWithFilter = new Scan(scan).setFilter(new AlwaysIncludeAndSeekNextRowFilter()); 265 266 long now = EnvironmentEdgeManager.currentTime(); 267 268 // scan with column 2,4,5 269 UserScanQueryMatcher qm = UserScanQueryMatcher.create( 270 scanWithFilter, new ScanInfo(this.conf, fam2, 0, 1, ttl, KeepDeletedCells.FALSE, 271 HConstants.DEFAULT_BLOCKSIZE, 0, rowComparator, false), 272 get.getFamilyMap().get(fam2), now - ttl, now, null); 273 274 List<KeyValue> memstore = new ArrayList<>(); 275 // ColumnTracker will return INCLUDE_AND_SEEK_NEXT_COL , and filter will return 276 // INCLUDE_AND_SEEK_NEXT_ROW, so final match code will be INCLUDE_AND_SEEK_NEXT_ROW. 277 memstore.add(new KeyValue(row1, fam2, col2, 1, data)); 278 memstore.add(new KeyValue(row2, fam1, col1, data)); 279 280 List<ScanQueryMatcher.MatchCode> actual = new ArrayList<>(memstore.size()); 281 KeyValue k = memstore.get(0); 282 qm.setToNewRow(k); 283 284 for (KeyValue kv : memstore) { 285 actual.add(qm.match(kv)); 286 } 287 288 assertEquals(expected.size(), actual.size()); 289 for (int i = 0; i < expected.size(); i++) { 290 LOG.debug("expected " + expected.get(i) + ", actual " + actual.get(i)); 291 assertEquals(expected.get(i), actual.get(i)); 292 } 293 } 294 295 private static class AlwaysIncludeFilter extends FilterBase { 296 @Override 297 public ReturnCode filterKeyValue(final Cell c) throws IOException { 298 return ReturnCode.INCLUDE; 299 } 300 } 301 302 /** 303 * Here is the unit test for UserScanQueryMatcher#mergeFilterResponse, when the number of cells 304 * exceed the versions requested in scan, we should return SEEK_NEXT_COL, but if current match 305 * code is INCLUDE_AND_SEEK_NEXT_ROW, we can optimize to choose the max step between SEEK_NEXT_COL 306 * and INCLUDE_AND_SEEK_NEXT_ROW, which is SEEK_NEXT_ROW. <br/> 307 */ 308 @Test 309 public void testMergeFilterResponseCase1() throws IOException { 310 List<MatchCode> expected = new ArrayList<>(); 311 expected.add(MatchCode.INCLUDE); 312 expected.add(MatchCode.INCLUDE); 313 expected.add(MatchCode.SEEK_NEXT_ROW); 314 315 Scan scanWithFilter = new Scan(scan).setFilter(new AlwaysIncludeFilter()).readVersions(2); 316 317 long now = EnvironmentEdgeManager.currentTime(); 318 // scan with column 2,4,5, the family with maxVersion = 3 319 UserScanQueryMatcher qm = UserScanQueryMatcher.create( 320 scanWithFilter, new ScanInfo(this.conf, fam2, 0, 3, ttl, KeepDeletedCells.FALSE, 321 HConstants.DEFAULT_BLOCKSIZE, 0, rowComparator, false), 322 get.getFamilyMap().get(fam2), now - ttl, now, null); 323 324 List<KeyValue> memstore = new ArrayList<>(); 325 memstore.add(new KeyValue(row1, fam1, col5, 1, data)); // match code will be INCLUDE 326 memstore.add(new KeyValue(row1, fam1, col5, 2, data)); // match code will be INCLUDE 327 328 // match code will be SEEK_NEXT_ROW , which is max(INCLUDE_AND_SEEK_NEXT_ROW, SEEK_NEXT_COL). 329 memstore.add(new KeyValue(row1, fam1, col5, 3, data)); 330 331 KeyValue k = memstore.get(0); 332 qm.setToNewRow(k); 333 334 for (int i = 0; i < memstore.size(); i++) { 335 assertEquals(expected.get(i), qm.match(memstore.get(i))); 336 } 337 338 scanWithFilter = new Scan(scan).setFilter(new AlwaysIncludeFilter()).readVersions(1); 339 qm = UserScanQueryMatcher.create( 340 scanWithFilter, new ScanInfo(this.conf, fam2, 0, 2, ttl, KeepDeletedCells.FALSE, 341 HConstants.DEFAULT_BLOCKSIZE, 0, rowComparator, false), 342 get.getFamilyMap().get(fam2), now - ttl, now, null); 343 344 List<KeyValue> memstore2 = new ArrayList<>(); 345 memstore2.add(new KeyValue(row2, fam1, col2, 1, data)); // match code will be INCLUDE 346 // match code will be SEEK_NEXT_COL, which is max(INCLUDE_AND_SEEK_NEXT_COL, SEEK_NEXT_COL). 347 memstore2.add(new KeyValue(row2, fam1, col2, 2, data)); 348 349 k = memstore2.get(0); 350 qm.setToNewRow(k); 351 352 assertEquals(MatchCode.INCLUDE, qm.match(memstore2.get(0))); 353 assertEquals(MatchCode.SEEK_NEXT_COL, qm.match(memstore2.get(1))); 354 } 355 356 /** 357 * Here is the unit test for UserScanQueryMatcher#mergeFilterResponse: the match code may be 358 * changed to SEEK_NEXT_COL or INCLUDE_AND_SEEK_NEXT_COL after merging with filterResponse, even 359 * if the passed match code is neither SEEK_NEXT_COL nor INCLUDE_AND_SEEK_NEXT_COL. In that case, 360 * we need to make sure that the ColumnTracker has been switched to the next column. <br/> 361 * An effective test way is: we only need to check the cell from getKeyForNextColumn(). because 362 * that as long as the UserScanQueryMatcher returns SEEK_NEXT_COL or INCLUDE_AND_SEEK_NEXT_COL, 363 * UserScanQueryMatcher#getKeyForNextColumn should return an cell whose column is larger than the 364 * current cell's. 365 */ 366 @Test 367 public void testMergeFilterResponseCase2() throws Exception { 368 List<MatchCode> expected = new ArrayList<>(); 369 expected.add(ScanQueryMatcher.MatchCode.INCLUDE); 370 expected.add(ScanQueryMatcher.MatchCode.INCLUDE); 371 expected.add(ScanQueryMatcher.MatchCode.INCLUDE); 372 expected.add(ScanQueryMatcher.MatchCode.SEEK_NEXT_COL); 373 374 Scan scanWithFilter = new Scan(scan).setFilter(new AlwaysIncludeFilter()).readVersions(3); 375 376 long now = EnvironmentEdgeManager.currentTime(); 377 378 // scan with column 2,4,5, the family with maxVersion = 5 379 UserScanQueryMatcher qm = UserScanQueryMatcher.create( 380 scanWithFilter, new ScanInfo(this.conf, fam2, 0, 5, ttl, KeepDeletedCells.FALSE, 381 HConstants.DEFAULT_BLOCKSIZE, 0, rowComparator, false), 382 get.getFamilyMap().get(fam2), now - ttl, now, null); 383 384 List<KeyValue> memstore = new ArrayList<>(); 385 386 memstore.add(new KeyValue(row1, fam1, col2, 1, data)); // match code will be INCLUDE 387 memstore.add(new KeyValue(row1, fam1, col2, 2, data)); // match code will be INCLUDE 388 memstore.add(new KeyValue(row1, fam1, col2, 3, data)); // match code will be INCLUDE 389 memstore.add(new KeyValue(row1, fam1, col2, 4, data)); // match code will be SEEK_NEXT_COL 390 391 KeyValue k = memstore.get(0); 392 qm.setToNewRow(k); 393 394 for (int i = 0; i < memstore.size(); i++) { 395 assertEquals(expected.get(i), qm.match(memstore.get(i))); 396 } 397 398 // For last cell, the query matcher will return SEEK_NEXT_COL, and the 399 // ColumnTracker will skip to the next column, which is col4. 400 Cell lastCell = memstore.get(memstore.size() - 1); 401 Cell nextCell = qm.getKeyForNextColumn(lastCell); 402 assertArrayEquals(nextCell.getQualifierArray(), col4); 403 } 404}