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; 019 020import static org.junit.Assert.assertArrayEquals; 021import static org.junit.Assert.assertEquals; 022import static org.junit.Assert.assertFalse; 023import static org.junit.Assert.assertNull; 024import static org.junit.Assert.assertThrows; 025import static org.junit.Assert.assertTrue; 026import static org.junit.Assert.fail; 027 028import java.io.IOException; 029import java.util.ArrayList; 030import java.util.List; 031import org.apache.hadoop.hbase.client.Delete; 032import org.apache.hadoop.hbase.client.Put; 033import org.apache.hadoop.hbase.client.RegionInfo; 034import org.apache.hadoop.hbase.client.Result; 035import org.apache.hadoop.hbase.client.ResultScanner; 036import org.apache.hadoop.hbase.client.Scan; 037import org.apache.hadoop.hbase.client.Table; 038import org.apache.hadoop.hbase.filter.ColumnPrefixFilter; 039import org.apache.hadoop.hbase.filter.ColumnRangeFilter; 040import org.apache.hadoop.hbase.filter.Filter; 041import org.apache.hadoop.hbase.filter.FirstKeyOnlyFilter; 042import org.apache.hadoop.hbase.filter.FirstKeyValueMatchingQualifiersFilter; 043import org.apache.hadoop.hbase.filter.RandomRowFilter; 044import org.apache.hadoop.hbase.testclassification.LargeTests; 045import org.apache.hadoop.hbase.util.Bytes; 046import org.apache.hadoop.hbase.util.ClassSize; 047import org.apache.hadoop.hbase.util.Pair; 048import org.junit.AfterClass; 049import org.junit.BeforeClass; 050import org.junit.ClassRule; 051import org.junit.Rule; 052import org.junit.Test; 053import org.junit.experimental.categories.Category; 054import org.junit.rules.TestName; 055import org.slf4j.Logger; 056import org.slf4j.LoggerFactory; 057 058/** 059 * These tests are focused on testing how partial results appear to a client. Partial results are 060 * {@link Result}s that contain only a portion of a row's complete list of cells. Partial results 061 * are formed when the server breaches its maximum result size when trying to service a client's RPC 062 * request. It is the responsibility of the scanner on the client side to recognize when partial 063 * results have been returned and to take action to form the complete results. 064 * <p> 065 * Unless the flag {@link Scan#setAllowPartialResults(boolean)} has been set to true, the caller of 066 * {@link ResultScanner#next()} should never see partial results. 067 */ 068@Category(LargeTests.class) 069public class TestPartialResultsFromClientSide { 070 071 @ClassRule 072 public static final HBaseClassTestRule CLASS_RULE = 073 HBaseClassTestRule.forClass(TestPartialResultsFromClientSide.class); 074 075 private static final Logger LOG = LoggerFactory.getLogger(TestPartialResultsFromClientSide.class); 076 077 private final static HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil(); 078 private final static int MINICLUSTER_SIZE = 5; 079 private static Table TABLE = null; 080 081 /** 082 * Table configuration 083 */ 084 private static TableName TABLE_NAME = TableName.valueOf("testTable"); 085 086 private static int NUM_ROWS = 5; 087 private static byte[] ROW = Bytes.toBytes("testRow"); 088 private static byte[][] ROWS = HTestConst.makeNAscii(ROW, NUM_ROWS); 089 090 // Should keep this value below 10 to keep generation of expected kv's simple. If above 10 then 091 // table/row/cf1/... will be followed by table/row/cf10/... instead of table/row/cf2/... which 092 // breaks the simple generation of expected kv's 093 private static int NUM_FAMILIES = 10; 094 private static byte[] FAMILY = Bytes.toBytes("testFamily"); 095 private static byte[][] FAMILIES = HTestConst.makeNAscii(FAMILY, NUM_FAMILIES); 096 097 private static int NUM_QUALIFIERS = 10; 098 private static byte[] QUALIFIER = Bytes.toBytes("testQualifier"); 099 private static byte[][] QUALIFIERS = HTestConst.makeNAscii(QUALIFIER, NUM_QUALIFIERS); 100 101 private static int VALUE_SIZE = 1024; 102 private static byte[] VALUE = Bytes.createMaxByteArray(VALUE_SIZE); 103 104 private static int NUM_COLS = NUM_FAMILIES * NUM_QUALIFIERS; 105 106 // Approximation of how large the heap size of cells in our table. Should be accessed through 107 // getCellHeapSize(). 108 private static long CELL_HEAP_SIZE = -1; 109 110 private static long timeout = 10000; 111 112 @Rule 113 public TestName name = new TestName(); 114 115 @BeforeClass 116 public static void setUpBeforeClass() throws Exception { 117 TEST_UTIL.getConfiguration().setLong(HConstants.HBASE_CLIENT_SCANNER_TIMEOUT_PERIOD, timeout); 118 TEST_UTIL.startMiniCluster(MINICLUSTER_SIZE); 119 TEST_UTIL.getAdmin().balancerSwitch(false, true); 120 TABLE = createTestTable(TABLE_NAME, ROWS, FAMILIES, QUALIFIERS, VALUE); 121 } 122 123 static Table createTestTable(TableName name, byte[][] rows, byte[][] families, 124 byte[][] qualifiers, byte[] cellValue) throws IOException { 125 Table ht = TEST_UTIL.createTable(name, families); 126 List<Put> puts = createPuts(rows, families, qualifiers, cellValue); 127 ht.put(puts); 128 129 return ht; 130 } 131 132 @AfterClass 133 public static void tearDownAfterClass() throws Exception { 134 TEST_UTIL.shutdownMiniCluster(); 135 } 136 137 /** 138 * Ensure that the expected key values appear in a result returned from a scanner that is 139 * combining partial results into complete results 140 */ 141 @Test 142 public void testExpectedValuesOfPartialResults() throws Exception { 143 testExpectedValuesOfPartialResults(false); 144 testExpectedValuesOfPartialResults(true); 145 } 146 147 public void testExpectedValuesOfPartialResults(boolean reversed) throws Exception { 148 Scan partialScan = new Scan(); 149 partialScan.readAllVersions(); 150 // Max result size of 1 ensures that each RPC request will return a single cell. The scanner 151 // will need to reconstruct the results into a complete result before returning to the caller 152 partialScan.setMaxResultSize(1); 153 partialScan.setReversed(reversed); 154 ResultScanner partialScanner = TABLE.getScanner(partialScan); 155 156 final int startRow = reversed ? ROWS.length - 1 : 0; 157 final int endRow = reversed ? -1 : ROWS.length; 158 final int loopDelta = reversed ? -1 : 1; 159 String message; 160 161 for (int row = startRow; row != endRow; row = row + loopDelta) { 162 message = "Ensuring the expected keyValues are present for row " + row; 163 List<Cell> expectedKeyValues = createKeyValuesForRow(ROWS[row], FAMILIES, QUALIFIERS, VALUE); 164 Result result = partialScanner.next(); 165 assertFalse(result.mayHaveMoreCellsInRow()); 166 verifyResult(result, expectedKeyValues, message); 167 } 168 169 partialScanner.close(); 170 } 171 172 /** 173 * Ensure that we only see Results marked as partial when the allowPartial flag is set 174 */ 175 @Test 176 public void testAllowPartialResults() throws Exception { 177 Scan scan = new Scan(); 178 scan.setAllowPartialResults(true); 179 scan.setMaxResultSize(1); 180 ResultScanner scanner = TABLE.getScanner(scan); 181 Result result = scanner.next(); 182 183 assertTrue(result != null); 184 assertTrue(result.mayHaveMoreCellsInRow()); 185 assertTrue(result.rawCells() != null); 186 assertTrue(result.rawCells().length == 1); 187 188 scanner.close(); 189 190 scan.setAllowPartialResults(false); 191 scanner = TABLE.getScanner(scan); 192 result = scanner.next(); 193 194 assertTrue(result != null); 195 assertTrue(!result.mayHaveMoreCellsInRow()); 196 assertTrue(result.rawCells() != null); 197 assertTrue(result.rawCells().length == NUM_COLS); 198 199 scanner.close(); 200 } 201 202 /** 203 * Ensure that the results returned from a scanner that retrieves all results in a single RPC call 204 * matches the results that are returned from a scanner that must incrementally combine partial 205 * results into complete results. A variety of scan configurations can be tested 206 */ 207 @Test 208 public void testEquivalenceOfScanResults() throws Exception { 209 Scan oneShotScan = new Scan(); 210 oneShotScan.setMaxResultSize(Long.MAX_VALUE); 211 212 Scan partialScan = new Scan(oneShotScan); 213 partialScan.setMaxResultSize(1); 214 215 testEquivalenceOfScanResults(TABLE, oneShotScan, partialScan); 216 } 217 218 public void testEquivalenceOfScanResults(Table table, Scan scan1, Scan scan2) throws Exception { 219 ResultScanner scanner1 = table.getScanner(scan1); 220 ResultScanner scanner2 = table.getScanner(scan2); 221 222 Result r1 = null; 223 Result r2 = null; 224 int count = 0; 225 226 while ((r1 = scanner1.next()) != null) { 227 r2 = scanner2.next(); 228 229 assertTrue(r2 != null); 230 compareResults(r1, r2, "Comparing result #" + count); 231 count++; 232 } 233 234 r2 = scanner2.next(); 235 assertTrue("r2: " + r2 + " Should be null", r2 == null); 236 237 scanner1.close(); 238 scanner2.close(); 239 } 240 241 /** 242 * Order of cells in partial results matches the ordering of cells from complete results 243 */ 244 @Test 245 public void testOrderingOfCellsInPartialResults() throws Exception { 246 Scan scan = new Scan(); 247 248 for (int col = 1; col <= NUM_COLS; col++) { 249 scan.setMaxResultSize(getResultSizeForNumberOfCells(col)); 250 testOrderingOfCellsInPartialResults(scan); 251 252 // Test again with a reversed scanner 253 scan.setReversed(true); 254 testOrderingOfCellsInPartialResults(scan); 255 } 256 } 257 258 public void testOrderingOfCellsInPartialResults(final Scan basePartialScan) throws Exception { 259 // Scan that retrieves results in pieces (partials). By setting allowPartialResults to be true 260 // the results will NOT be reconstructed and instead the caller will see the partial results 261 // returned by the server 262 Scan partialScan = new Scan(basePartialScan); 263 partialScan.setAllowPartialResults(true); 264 ResultScanner partialScanner = TABLE.getScanner(partialScan); 265 266 // Scan that retrieves all table results in single RPC request 267 Scan oneShotScan = new Scan(basePartialScan); 268 oneShotScan.setMaxResultSize(Long.MAX_VALUE); 269 oneShotScan.setCaching(ROWS.length); 270 ResultScanner oneShotScanner = TABLE.getScanner(oneShotScan); 271 272 Result oneShotResult = oneShotScanner.next(); 273 Result partialResult = null; 274 int iterationCount = 0; 275 276 while (oneShotResult != null && oneShotResult.rawCells() != null) { 277 List<Cell> aggregatePartialCells = new ArrayList<>(); 278 do { 279 partialResult = partialScanner.next(); 280 assertTrue("Partial Result is null. iteration: " + iterationCount, partialResult != null); 281 assertTrue("Partial cells are null. iteration: " + iterationCount, 282 partialResult.rawCells() != null); 283 284 for (Cell c : partialResult.rawCells()) { 285 aggregatePartialCells.add(c); 286 } 287 } while (partialResult.mayHaveMoreCellsInRow()); 288 289 assertTrue("Number of cells differs. iteration: " + iterationCount, 290 oneShotResult.rawCells().length == aggregatePartialCells.size()); 291 final Cell[] oneShotCells = oneShotResult.rawCells(); 292 for (int cell = 0; cell < oneShotCells.length; cell++) { 293 Cell oneShotCell = oneShotCells[cell]; 294 Cell partialCell = aggregatePartialCells.get(cell); 295 296 assertTrue("One shot cell was null", oneShotCell != null); 297 assertTrue("Partial cell was null", partialCell != null); 298 assertTrue("Cell differs. oneShotCell:" + oneShotCell + " partialCell:" + partialCell, 299 oneShotCell.equals(partialCell)); 300 } 301 302 oneShotResult = oneShotScanner.next(); 303 iterationCount++; 304 } 305 306 assertTrue(partialScanner.next() == null); 307 308 partialScanner.close(); 309 oneShotScanner.close(); 310 } 311 312 /** 313 * Setting the max result size allows us to control how many cells we expect to see on each call 314 * to next on the scanner. Test a variety of different sizes for correctness 315 */ 316 @Test 317 public void testExpectedNumberOfCellsPerPartialResult() throws Exception { 318 Scan scan = new Scan(); 319 testExpectedNumberOfCellsPerPartialResult(scan); 320 321 scan.setReversed(true); 322 testExpectedNumberOfCellsPerPartialResult(scan); 323 } 324 325 public void testExpectedNumberOfCellsPerPartialResult(Scan baseScan) throws Exception { 326 for (int expectedCells = 1; expectedCells <= NUM_COLS; expectedCells++) { 327 testExpectedNumberOfCellsPerPartialResult(baseScan, expectedCells); 328 } 329 } 330 331 public void testExpectedNumberOfCellsPerPartialResult(Scan baseScan, int expectedNumberOfCells) 332 throws Exception { 333 334 if (LOG.isInfoEnabled()) LOG.info("groupSize:" + expectedNumberOfCells); 335 336 // Use the cellHeapSize to set maxResultSize such that we know how many cells to expect back 337 // from the call. The returned results should NOT exceed expectedNumberOfCells but may be less 338 // than it in cases where expectedNumberOfCells is not an exact multiple of the number of 339 // columns in the table. 340 Scan scan = new Scan(baseScan); 341 scan.setAllowPartialResults(true); 342 scan.setMaxResultSize(getResultSizeForNumberOfCells(expectedNumberOfCells)); 343 344 ResultScanner scanner = TABLE.getScanner(scan); 345 Result result = null; 346 byte[] prevRow = null; 347 while ((result = scanner.next()) != null) { 348 assertTrue(result.rawCells() != null); 349 350 // Cases when cell count won't equal expectedNumberOfCells: 351 // 1. Returned result is the final result needed to form the complete result for that row 352 // 2. It is the first result we have seen for that row and thus may have been fetched as 353 // the last group of cells that fit inside the maxResultSize 354 assertTrue("Result's cell count differed from expected number. result: " + result, 355 result.rawCells().length == expectedNumberOfCells || !result.mayHaveMoreCellsInRow() 356 || !Bytes.equals(prevRow, result.getRow())); 357 prevRow = result.getRow(); 358 } 359 360 scanner.close(); 361 } 362 363 /** 364 * @return The approximate heap size of a cell in the test table. All cells should have 365 * approximately the same heap size, so the value is cached to avoid repeating the 366 * calculation 367 */ 368 private long getCellHeapSize() throws Exception { 369 if (CELL_HEAP_SIZE == -1) { 370 // Do a partial scan that will return a single result with a single cell 371 Scan scan = new Scan(); 372 scan.setMaxResultSize(2); 373 scan.setAllowPartialResults(true); 374 ResultScanner scanner = TABLE.getScanner(scan); 375 376 Result result = scanner.next(); 377 378 assertTrue(result != null); 379 assertTrue(result.rawCells() != null); 380 assertTrue(result.rawCells().length == 1); 381 382 // Estimate the cell heap size. One difference is that on server side, the KV Heap size is 383 // estimated differently in case the cell is backed up by MSLAB byte[] (no overhead for 384 // backing array). Thus below calculation is a bit brittle. 385 CELL_HEAP_SIZE = result.rawCells()[0].heapSize() - (ClassSize.ARRAY + 3); 386 if (LOG.isInfoEnabled()) LOG.info("Cell heap size: " + CELL_HEAP_SIZE); 387 scanner.close(); 388 } 389 390 return CELL_HEAP_SIZE; 391 } 392 393 /** 394 * @return the result size that should be used in {@link Scan#setMaxResultSize(long)} if you want 395 * the server to return exactly numberOfCells cells 396 */ 397 private long getResultSizeForNumberOfCells(int numberOfCells) throws Exception { 398 return getCellHeapSize() * numberOfCells; 399 } 400 401 /** 402 * Test various combinations of batching and partial results for correctness 403 */ 404 @Test 405 public void testPartialResultsAndBatch() throws Exception { 406 for (int batch = 1; batch <= NUM_COLS / 4; batch++) { 407 for (int cellsPerPartial = 1; cellsPerPartial <= NUM_COLS / 4; cellsPerPartial++) { 408 testPartialResultsAndBatch(batch, cellsPerPartial); 409 } 410 } 411 } 412 413 public void testPartialResultsAndBatch(final int batch, final int cellsPerPartialResult) 414 throws Exception { 415 if (LOG.isInfoEnabled()) { 416 LOG.info("batch: " + batch + " cellsPerPartialResult: " + cellsPerPartialResult); 417 } 418 419 Scan scan = new Scan(); 420 scan.setMaxResultSize(getResultSizeForNumberOfCells(cellsPerPartialResult)); 421 scan.setBatch(batch); 422 ResultScanner scanner = TABLE.getScanner(scan); 423 Result result = scanner.next(); 424 int repCount = 0; 425 426 while ((result = scanner.next()) != null) { 427 assertTrue(result.rawCells() != null); 428 429 if (result.mayHaveMoreCellsInRow()) { 430 final String error = "Cells:" + result.rawCells().length + " Batch size:" + batch 431 + " cellsPerPartialResult:" + cellsPerPartialResult + " rep:" + repCount; 432 assertTrue(error, result.rawCells().length == batch); 433 } else { 434 assertTrue(result.rawCells().length <= batch); 435 } 436 repCount++; 437 } 438 439 scanner.close(); 440 } 441 442 /** 443 * Test the method {@link Result#createCompleteResult(Iterable)} 444 */ 445 @Test 446 public void testPartialResultsReassembly() throws Exception { 447 Scan scan = new Scan(); 448 testPartialResultsReassembly(scan); 449 scan.setReversed(true); 450 testPartialResultsReassembly(scan); 451 } 452 453 public void testPartialResultsReassembly(Scan scanBase) throws Exception { 454 Scan partialScan = new Scan(scanBase); 455 partialScan.setMaxResultSize(1); 456 partialScan.setAllowPartialResults(true); 457 ResultScanner partialScanner = TABLE.getScanner(partialScan); 458 459 Scan oneShotScan = new Scan(scanBase); 460 oneShotScan.setMaxResultSize(Long.MAX_VALUE); 461 ResultScanner oneShotScanner = TABLE.getScanner(oneShotScan); 462 463 ArrayList<Result> partials = new ArrayList<>(); 464 for (int i = 0; i < NUM_ROWS; i++) { 465 Result partialResult = null; 466 Result completeResult = null; 467 Result oneShotResult = null; 468 partials.clear(); 469 470 do { 471 partialResult = partialScanner.next(); 472 partials.add(partialResult); 473 } while (partialResult != null && partialResult.mayHaveMoreCellsInRow()); 474 475 completeResult = Result.createCompleteResult(partials); 476 oneShotResult = oneShotScanner.next(); 477 478 compareResults(completeResult, oneShotResult, null); 479 } 480 481 assertTrue(oneShotScanner.next() == null); 482 assertTrue(partialScanner.next() == null); 483 484 oneShotScanner.close(); 485 partialScanner.close(); 486 } 487 488 /** 489 * When reconstructing the complete result from its partials we ensure that the row of each 490 * partial result is the same. If one of the rows differs, an exception is thrown. 491 */ 492 @Test 493 public void testExceptionThrownOnMismatchedPartialResults() throws IOException { 494 assertTrue(NUM_ROWS >= 2); 495 496 ArrayList<Result> partials = new ArrayList<>(); 497 Scan scan = new Scan(); 498 scan.setMaxResultSize(Long.MAX_VALUE); 499 ResultScanner scanner = TABLE.getScanner(scan); 500 Result r1 = scanner.next(); 501 partials.add(r1); 502 Result r2 = scanner.next(); 503 partials.add(r2); 504 505 assertFalse(Bytes.equals(r1.getRow(), r2.getRow())); 506 507 try { 508 Result.createCompleteResult(partials); 509 fail("r1 and r2 are from different rows. It should not be possible to combine them into" 510 + " a single result"); 511 } catch (IOException e) { 512 } 513 514 scanner.close(); 515 } 516 517 /** 518 * When a scan has a filter where {@link org.apache.hadoop.hbase.filter.Filter#hasFilterRow()} is 519 * true, the scanner should not return partial results. The scanner cannot return partial results 520 * because the entire row needs to be read for the include/exclude decision to be made 521 */ 522 @Test 523 public void testNoPartialResultsWhenRowFilterPresent() throws Exception { 524 Scan scan = new Scan(); 525 scan.setMaxResultSize(1); 526 scan.setAllowPartialResults(true); 527 // If a filter hasFilter() is true then partial results should not be returned else filter 528 // application server side would break. 529 scan.setFilter(new RandomRowFilter(1.0f)); 530 ResultScanner scanner = TABLE.getScanner(scan); 531 532 Result r = null; 533 while ((r = scanner.next()) != null) { 534 assertFalse(r.mayHaveMoreCellsInRow()); 535 } 536 537 scanner.close(); 538 } 539 540 /** 541 * Examine the interaction between the maxResultSize and caching. If the caching limit is reached 542 * before the maxResultSize limit, we should not see partial results. On the other hand, if the 543 * maxResultSize limit is reached before the caching limit, it is likely that partial results will 544 * be seen. 545 */ 546 @Test 547 public void testPartialResultsAndCaching() throws Exception { 548 for (int caching = 1; caching <= NUM_ROWS; caching++) { 549 for (int maxResultRows = 0; maxResultRows <= NUM_ROWS; maxResultRows++) { 550 testPartialResultsAndCaching(maxResultRows, caching); 551 } 552 } 553 } 554 555 /** 556 * @param resultSizeRowLimit The row limit that will be enforced through maxResultSize 557 * @param cachingRowLimit The row limit that will be enforced through caching 558 */ 559 public void testPartialResultsAndCaching(int resultSizeRowLimit, int cachingRowLimit) 560 throws Exception { 561 Scan scan = new Scan(); 562 scan.setAllowPartialResults(true); 563 564 // The number of cells specified in the call to getResultSizeForNumberOfCells is offset to 565 // ensure that the result size we specify is not an exact multiple of the number of cells 566 // in a row. This ensures that partial results will be returned when the result size limit 567 // is reached before the caching limit. 568 int cellOffset = NUM_COLS / 3; 569 long maxResultSize = getResultSizeForNumberOfCells(resultSizeRowLimit * NUM_COLS + cellOffset); 570 scan.setMaxResultSize(maxResultSize); 571 scan.setCaching(cachingRowLimit); 572 573 try (ResultScanner scanner = TABLE.getScanner(scan)) { 574 Result r = null; 575 // Approximate the number of rows we expect will fit into the specified max rsult size. If 576 // this approximation is less than caching, then we expect that the max result size limit will 577 // be hit before the caching limit and thus partial results may be seen 578 boolean expectToSeePartialResults = resultSizeRowLimit < cachingRowLimit; 579 while ((r = scanner.next()) != null) { 580 assertTrue(!r.mayHaveMoreCellsInRow() || expectToSeePartialResults); 581 } 582 } 583 } 584 585 /** 586 * Make puts to put the input value into each combination of row, family, and qualifier 587 * @param rows the rows to use 588 * @param families the families to use 589 * @param qualifiers the qualifiers to use 590 * @param value the values to use 591 * @return the dot product of the given rows, families, qualifiers, and values 592 * @throws IOException if there is a problem creating one of the Put objects 593 */ 594 static ArrayList<Put> createPuts(byte[][] rows, byte[][] families, byte[][] qualifiers, 595 byte[] value) throws IOException { 596 Put put; 597 ArrayList<Put> puts = new ArrayList<>(); 598 599 for (int row = 0; row < rows.length; row++) { 600 put = new Put(rows[row]); 601 for (int fam = 0; fam < families.length; fam++) { 602 for (int qual = 0; qual < qualifiers.length; qual++) { 603 KeyValue kv = new KeyValue(rows[row], families[fam], qualifiers[qual], qual, value); 604 put.add(kv); 605 } 606 } 607 puts.add(put); 608 } 609 610 return puts; 611 } 612 613 /** 614 * Make key values to represent each possible combination of family and qualifier in the specified 615 * row. 616 * @param row the row to use 617 * @param families the families to use 618 * @param qualifiers the qualifiers to use 619 * @param value the values to use 620 * @return the dot product of the given families, qualifiers, and values for a given row 621 */ 622 static ArrayList<Cell> createKeyValuesForRow(byte[] row, byte[][] families, byte[][] qualifiers, 623 byte[] value) { 624 ArrayList<Cell> outList = new ArrayList<>(); 625 for (int fam = 0; fam < families.length; fam++) { 626 for (int qual = 0; qual < qualifiers.length; qual++) { 627 outList.add(new KeyValue(row, families[fam], qualifiers[qual], qual, value)); 628 } 629 } 630 return outList; 631 } 632 633 /** 634 * Verifies that result contains all the key values within expKvList. Fails the test otherwise 635 */ 636 static void verifyResult(Result result, List<Cell> expKvList, String msg) { 637 if (LOG.isInfoEnabled()) { 638 LOG.info(msg); 639 LOG.info("Expected count: " + expKvList.size()); 640 LOG.info("Actual count: " + result.size()); 641 } 642 643 if (expKvList.isEmpty()) return; 644 645 int i = 0; 646 for (Cell kv : result.rawCells()) { 647 if (i >= expKvList.size()) { 648 break; // we will check the size later 649 } 650 651 Cell kvExp = expKvList.get(i++); 652 assertTrue("Not equal. get kv: " + kv.toString() + " exp kv: " + kvExp.toString(), 653 kvExp.equals(kv)); 654 } 655 656 assertEquals(expKvList.size(), result.size()); 657 } 658 659 /** 660 * Compares two results and fails the test if the results are different 661 */ 662 static void compareResults(Result r1, Result r2, final String message) { 663 if (LOG.isInfoEnabled()) { 664 if (message != null) LOG.info(message); 665 LOG.info("r1: " + r1); 666 LOG.info("r2: " + r2); 667 } 668 669 final String failureMessage = "Results r1:" + r1 + " \nr2:" + r2 + " are not equivalent"; 670 if (r1 == null && r2 == null) fail(failureMessage); 671 else if (r1 == null || r2 == null) fail(failureMessage); 672 673 try { 674 Result.compareResults(r1, r2); 675 } catch (Exception e) { 676 fail(failureMessage); 677 } 678 } 679 680 @Test 681 public void testReadPointAndPartialResults() throws Exception { 682 final TableName tableName = TableName.valueOf(name.getMethodName()); 683 int numRows = 5; 684 int numFamilies = 5; 685 int numQualifiers = 5; 686 byte[][] rows = HTestConst.makeNAscii(Bytes.toBytes("testRow"), numRows); 687 byte[][] families = HTestConst.makeNAscii(Bytes.toBytes("testFamily"), numFamilies); 688 byte[][] qualifiers = HTestConst.makeNAscii(Bytes.toBytes("testQualifier"), numQualifiers); 689 byte[] value = Bytes.createMaxByteArray(100); 690 691 Table tmpTable = createTestTable(tableName, rows, families, qualifiers, value); 692 // Open scanner before deletes 693 ResultScanner scanner = 694 tmpTable.getScanner(new Scan().setMaxResultSize(1).setAllowPartialResults(true)); 695 // now the openScanner will also fetch data and will be executed lazily, i.e, only openScanner 696 // when you call next, so here we need to make a next call to open scanner. The maxResultSize 697 // limit can make sure that we will not fetch all the data at once, so the test sill works. 698 int scannerCount = scanner.next().rawCells().length; 699 Delete delete1 = new Delete(rows[0]); 700 delete1.addColumn(families[0], qualifiers[0], 0); 701 tmpTable.delete(delete1); 702 703 Delete delete2 = new Delete(rows[1]); 704 delete2.addColumn(families[1], qualifiers[1], 1); 705 tmpTable.delete(delete2); 706 707 // Should see all cells because scanner was opened prior to deletes 708 scannerCount += countCellsFromScanner(scanner); 709 int expectedCount = numRows * numFamilies * numQualifiers; 710 assertTrue("scannerCount: " + scannerCount + " expectedCount: " + expectedCount, 711 scannerCount == expectedCount); 712 713 // Minus 2 for the two cells that were deleted 714 scanner = tmpTable.getScanner(new Scan().setMaxResultSize(1).setAllowPartialResults(true)); 715 scannerCount = countCellsFromScanner(scanner); 716 expectedCount = numRows * numFamilies * numQualifiers - 2; 717 assertTrue("scannerCount: " + scannerCount + " expectedCount: " + expectedCount, 718 scannerCount == expectedCount); 719 720 scanner = tmpTable.getScanner(new Scan().setMaxResultSize(1).setAllowPartialResults(true)); 721 scannerCount = scanner.next().rawCells().length; 722 // Put in 2 new rows. The timestamps differ from the deleted rows 723 Put put1 = new Put(rows[0]); 724 put1.add(new KeyValue(rows[0], families[0], qualifiers[0], 1, value)); 725 tmpTable.put(put1); 726 727 Put put2 = new Put(rows[1]); 728 put2.add(new KeyValue(rows[1], families[1], qualifiers[1], 2, value)); 729 tmpTable.put(put2); 730 731 // Scanner opened prior to puts. Cell count shouldn't have changed 732 scannerCount += countCellsFromScanner(scanner); 733 expectedCount = numRows * numFamilies * numQualifiers - 2; 734 assertTrue("scannerCount: " + scannerCount + " expectedCount: " + expectedCount, 735 scannerCount == expectedCount); 736 737 // Now the scanner should see the cells that were added by puts 738 scanner = tmpTable.getScanner(new Scan().setMaxResultSize(1).setAllowPartialResults(true)); 739 scannerCount = countCellsFromScanner(scanner); 740 expectedCount = numRows * numFamilies * numQualifiers; 741 assertTrue("scannerCount: " + scannerCount + " expectedCount: " + expectedCount, 742 scannerCount == expectedCount); 743 744 TEST_UTIL.deleteTable(tableName); 745 } 746 747 /** 748 * Exhausts the scanner by calling next repetitively. Once completely exhausted, close scanner and 749 * return total cell count 750 * @param scanner the scanner to exhaust 751 * @return the number of cells counted 752 * @throws Exception if there is a problem retrieving cells from the scanner 753 */ 754 private int countCellsFromScanner(ResultScanner scanner) throws Exception { 755 Result result = null; 756 int numCells = 0; 757 while ((result = scanner.next()) != null) { 758 numCells += result.rawCells().length; 759 } 760 761 scanner.close(); 762 return numCells; 763 } 764 765 /** 766 * Test partial Result re-assembly in the presence of different filters. The Results from the 767 * partial scanner should match the Results returned from a scanner that receives all of the 768 * results in one RPC to the server. The partial scanner is tested with a variety of different 769 * result sizes (all of which are less than the size necessary to fetch an entire row) 770 */ 771 @Test 772 public void testPartialResultsWithColumnFilter() throws Exception { 773 testPartialResultsWithColumnFilter(new FirstKeyOnlyFilter()); 774 testPartialResultsWithColumnFilter(new ColumnPrefixFilter(Bytes.toBytes("testQualifier5"))); 775 testPartialResultsWithColumnFilter(new ColumnRangeFilter(Bytes.toBytes("testQualifer1"), true, 776 Bytes.toBytes("testQualifier7"), true)); 777 778 // Throw an Exception to the old version client to remind them not to use this filter anymore 779 assertThrows("Stop using", DoNotRetryIOException.class, 780 () -> testPartialResultsWithColumnFilter(new FirstKeyValueMatchingQualifiersFilter())); 781 } 782 783 public void testPartialResultsWithColumnFilter(Filter filter) throws Exception { 784 assertTrue(!filter.hasFilterRow()); 785 786 Scan partialScan = new Scan(); 787 partialScan.setFilter(filter); 788 789 Scan oneshotScan = new Scan(); 790 oneshotScan.setFilter(filter); 791 oneshotScan.setMaxResultSize(Long.MAX_VALUE); 792 793 for (int i = 1; i <= NUM_COLS; i++) { 794 partialScan.setMaxResultSize(getResultSizeForNumberOfCells(i)); 795 testEquivalenceOfScanResults(TABLE, partialScan, oneshotScan); 796 } 797 } 798 799 private void moveRegion(Table table, int index) throws IOException { 800 List<Pair<RegionInfo, ServerName>> regions = 801 MetaTableAccessor.getTableRegionsAndLocations(TEST_UTIL.getConnection(), table.getName()); 802 assertEquals(1, regions.size()); 803 RegionInfo regionInfo = regions.get(0).getFirst(); 804 ServerName name = TEST_UTIL.getHBaseCluster().getRegionServer(index).getServerName(); 805 TEST_UTIL.getAdmin().move(regionInfo.getEncodedNameAsBytes(), name); 806 } 807 808 private void assertCell(Cell cell, byte[] row, byte[] cf, byte[] cq) { 809 assertArrayEquals(row, 810 Bytes.copy(cell.getRowArray(), cell.getRowOffset(), cell.getRowLength())); 811 assertArrayEquals(cf, 812 Bytes.copy(cell.getFamilyArray(), cell.getFamilyOffset(), cell.getFamilyLength())); 813 assertArrayEquals(cq, 814 Bytes.copy(cell.getQualifierArray(), cell.getQualifierOffset(), cell.getQualifierLength())); 815 } 816 817 @Test 818 public void testPartialResultWhenRegionMove() throws IOException { 819 Table table = 820 createTestTable(TableName.valueOf(name.getMethodName()), ROWS, FAMILIES, QUALIFIERS, VALUE); 821 822 moveRegion(table, 1); 823 824 Scan scan = new Scan(); 825 scan.setMaxResultSize(1); 826 scan.setAllowPartialResults(true); 827 ResultScanner scanner = table.getScanner(scan); 828 for (int i = 0; i < NUM_FAMILIES * NUM_QUALIFIERS - 1; i++) { 829 scanner.next(); 830 } 831 Result result1 = scanner.next(); 832 assertEquals(1, result1.rawCells().length); 833 Cell c1 = result1.rawCells()[0]; 834 assertCell(c1, ROWS[0], FAMILIES[NUM_FAMILIES - 1], QUALIFIERS[NUM_QUALIFIERS - 1]); 835 assertFalse(result1.mayHaveMoreCellsInRow()); 836 837 moveRegion(table, 2); 838 839 Result result2 = scanner.next(); 840 assertEquals(1, result2.rawCells().length); 841 Cell c2 = result2.rawCells()[0]; 842 assertCell(c2, ROWS[1], FAMILIES[0], QUALIFIERS[0]); 843 assertTrue(result2.mayHaveMoreCellsInRow()); 844 845 moveRegion(table, 3); 846 847 Result result3 = scanner.next(); 848 assertEquals(1, result3.rawCells().length); 849 Cell c3 = result3.rawCells()[0]; 850 assertCell(c3, ROWS[1], FAMILIES[0], QUALIFIERS[1]); 851 assertTrue(result3.mayHaveMoreCellsInRow()); 852 853 } 854 855 @Test 856 public void testReversedPartialResultWhenRegionMove() throws IOException { 857 Table table = 858 createTestTable(TableName.valueOf(name.getMethodName()), ROWS, FAMILIES, QUALIFIERS, VALUE); 859 860 moveRegion(table, 1); 861 862 Scan scan = new Scan(); 863 scan.setMaxResultSize(1); 864 scan.setAllowPartialResults(true); 865 scan.setReversed(true); 866 ResultScanner scanner = table.getScanner(scan); 867 for (int i = 0; i < NUM_FAMILIES * NUM_QUALIFIERS - 1; i++) { 868 scanner.next(); 869 } 870 Result result1 = scanner.next(); 871 assertEquals(1, result1.rawCells().length); 872 Cell c1 = result1.rawCells()[0]; 873 assertCell(c1, ROWS[NUM_ROWS - 1], FAMILIES[NUM_FAMILIES - 1], QUALIFIERS[NUM_QUALIFIERS - 1]); 874 assertFalse(result1.mayHaveMoreCellsInRow()); 875 876 moveRegion(table, 2); 877 878 Result result2 = scanner.next(); 879 assertEquals(1, result2.rawCells().length); 880 Cell c2 = result2.rawCells()[0]; 881 assertCell(c2, ROWS[NUM_ROWS - 2], FAMILIES[0], QUALIFIERS[0]); 882 assertTrue(result2.mayHaveMoreCellsInRow()); 883 884 moveRegion(table, 3); 885 886 Result result3 = scanner.next(); 887 assertEquals(1, result3.rawCells().length); 888 Cell c3 = result3.rawCells()[0]; 889 assertCell(c3, ROWS[NUM_ROWS - 2], FAMILIES[0], QUALIFIERS[1]); 890 assertTrue(result3.mayHaveMoreCellsInRow()); 891 892 } 893 894 @Test 895 public void testCompleteResultWhenRegionMove() throws IOException { 896 Table table = 897 createTestTable(TableName.valueOf(name.getMethodName()), ROWS, FAMILIES, QUALIFIERS, VALUE); 898 899 moveRegion(table, 1); 900 901 Scan scan = new Scan(); 902 scan.setMaxResultSize(1); 903 scan.setCaching(1); 904 ResultScanner scanner = table.getScanner(scan); 905 906 Result result1 = scanner.next(); 907 assertEquals(NUM_FAMILIES * NUM_QUALIFIERS, result1.rawCells().length); 908 Cell c1 = result1.rawCells()[0]; 909 assertCell(c1, ROWS[0], FAMILIES[0], QUALIFIERS[0]); 910 assertFalse(result1.mayHaveMoreCellsInRow()); 911 912 moveRegion(table, 2); 913 914 Result result2 = scanner.next(); 915 assertEquals(NUM_FAMILIES * NUM_QUALIFIERS, result2.rawCells().length); 916 Cell c2 = result2.rawCells()[0]; 917 assertCell(c2, ROWS[1], FAMILIES[0], QUALIFIERS[0]); 918 assertFalse(result2.mayHaveMoreCellsInRow()); 919 920 moveRegion(table, 3); 921 922 Result result3 = scanner.next(); 923 assertEquals(NUM_FAMILIES * NUM_QUALIFIERS, result3.rawCells().length); 924 Cell c3 = result3.rawCells()[0]; 925 assertCell(c3, ROWS[2], FAMILIES[0], QUALIFIERS[0]); 926 assertFalse(result3.mayHaveMoreCellsInRow()); 927 928 } 929 930 @Test 931 public void testReversedCompleteResultWhenRegionMove() throws IOException { 932 Table table = 933 createTestTable(TableName.valueOf(name.getMethodName()), ROWS, FAMILIES, QUALIFIERS, VALUE); 934 935 moveRegion(table, 1); 936 937 Scan scan = new Scan(); 938 scan.setMaxResultSize(1); 939 scan.setCaching(1); 940 scan.setReversed(true); 941 ResultScanner scanner = table.getScanner(scan); 942 943 Result result1 = scanner.next(); 944 assertEquals(NUM_FAMILIES * NUM_QUALIFIERS, result1.rawCells().length); 945 Cell c1 = result1.rawCells()[0]; 946 assertCell(c1, ROWS[NUM_ROWS - 1], FAMILIES[0], QUALIFIERS[0]); 947 assertFalse(result1.mayHaveMoreCellsInRow()); 948 949 moveRegion(table, 2); 950 951 Result result2 = scanner.next(); 952 assertEquals(NUM_FAMILIES * NUM_QUALIFIERS, result2.rawCells().length); 953 Cell c2 = result2.rawCells()[0]; 954 assertCell(c2, ROWS[NUM_ROWS - 2], FAMILIES[0], QUALIFIERS[0]); 955 assertFalse(result2.mayHaveMoreCellsInRow()); 956 957 moveRegion(table, 3); 958 959 Result result3 = scanner.next(); 960 assertEquals(NUM_FAMILIES * NUM_QUALIFIERS, result3.rawCells().length); 961 Cell c3 = result3.rawCells()[0]; 962 assertCell(c3, ROWS[NUM_ROWS - 3], FAMILIES[0], QUALIFIERS[0]); 963 assertFalse(result3.mayHaveMoreCellsInRow()); 964 965 } 966 967 @Test 968 public void testBatchingResultWhenRegionMove() throws IOException { 969 // If user setBatch(5) and rpc returns 3+5+5+5+3 cells, 970 // we should return 5+5+5+5+1 to user. 971 // setBatch doesn't mean setAllowPartialResult(true) 972 Table table = 973 createTestTable(TableName.valueOf(name.getMethodName()), ROWS, FAMILIES, QUALIFIERS, VALUE); 974 975 Put put = new Put(ROWS[1]); 976 put.addColumn(FAMILIES[0], QUALIFIERS[1], new byte[VALUE_SIZE * 10]); 977 table.put(put); 978 Delete delete = new Delete(ROWS[1]); 979 delete.addColumn(FAMILIES[NUM_FAMILIES - 1], QUALIFIERS[NUM_QUALIFIERS - 1]); 980 table.delete(delete); 981 982 moveRegion(table, 1); 983 984 Scan scan = new Scan(); 985 scan.setCaching(1); 986 scan.setBatch(5); 987 scan.setMaxResultSize(VALUE_SIZE * 6); 988 989 ResultScanner scanner = table.getScanner(scan); 990 for (int i = 0; i < NUM_FAMILIES * NUM_QUALIFIERS / 5 - 1; i++) { 991 assertTrue(scanner.next().mayHaveMoreCellsInRow()); 992 } 993 Result result1 = scanner.next(); 994 assertEquals(5, result1.rawCells().length); 995 assertCell(result1.rawCells()[0], ROWS[0], FAMILIES[NUM_FAMILIES - 1], 996 QUALIFIERS[NUM_QUALIFIERS - 5]); 997 assertCell(result1.rawCells()[4], ROWS[0], FAMILIES[NUM_FAMILIES - 1], 998 QUALIFIERS[NUM_QUALIFIERS - 1]); 999 assertFalse(result1.mayHaveMoreCellsInRow()); 1000 1001 moveRegion(table, 2); 1002 1003 Result result2 = scanner.next(); 1004 assertEquals(5, result2.rawCells().length); 1005 assertCell(result2.rawCells()[0], ROWS[1], FAMILIES[0], QUALIFIERS[0]); 1006 assertCell(result2.rawCells()[4], ROWS[1], FAMILIES[0], QUALIFIERS[4]); 1007 assertTrue(result2.mayHaveMoreCellsInRow()); 1008 1009 moveRegion(table, 3); 1010 1011 Result result3 = scanner.next(); 1012 assertEquals(5, result3.rawCells().length); 1013 assertCell(result3.rawCells()[0], ROWS[1], FAMILIES[0], QUALIFIERS[5]); 1014 assertCell(result3.rawCells()[4], ROWS[1], FAMILIES[0], QUALIFIERS[9]); 1015 assertTrue(result3.mayHaveMoreCellsInRow()); 1016 1017 for (int i = 0; i < NUM_FAMILIES * NUM_QUALIFIERS / 5 - 3; i++) { 1018 Result result = scanner.next(); 1019 assertEquals(5, result.rawCells().length); 1020 assertTrue(result.mayHaveMoreCellsInRow()); 1021 } 1022 Result result = scanner.next(); 1023 assertEquals(4, result.rawCells().length); 1024 assertFalse(result.mayHaveMoreCellsInRow()); 1025 1026 for (int i = 2; i < NUM_ROWS; i++) { 1027 for (int j = 0; j < NUM_FAMILIES; j++) { 1028 for (int k = 0; k < NUM_QUALIFIERS; k += 5) { 1029 result = scanner.next(); 1030 assertCell(result.rawCells()[0], ROWS[i], FAMILIES[j], QUALIFIERS[k]); 1031 assertEquals(5, result.rawCells().length); 1032 if (j == NUM_FAMILIES - 1 && k == NUM_QUALIFIERS - 5) { 1033 assertFalse(result.mayHaveMoreCellsInRow()); 1034 } else { 1035 assertTrue(result.mayHaveMoreCellsInRow()); 1036 } 1037 } 1038 } 1039 } 1040 assertNull(scanner.next()); 1041 } 1042 1043 @Test 1044 public void testDontThrowUnknowScannerExceptionToClient() throws Exception { 1045 Table table = 1046 createTestTable(TableName.valueOf(name.getMethodName()), ROWS, FAMILIES, QUALIFIERS, VALUE); 1047 Scan scan = new Scan(); 1048 scan.setCaching(1); 1049 ResultScanner scanner = table.getScanner(scan); 1050 scanner.next(); 1051 Thread.sleep(timeout * 2); 1052 int count = 1; 1053 while (scanner.next() != null) { 1054 count++; 1055 } 1056 assertEquals(NUM_ROWS, count); 1057 scanner.close(); 1058 } 1059 1060 @Test 1061 public void testMayHaveMoreCellsInRowReturnsTrueAndSetBatch() throws IOException { 1062 Table table = 1063 createTestTable(TableName.valueOf(name.getMethodName()), ROWS, FAMILIES, QUALIFIERS, VALUE); 1064 Scan scan = new Scan(); 1065 scan.setBatch(1); 1066 scan.setFilter(new FirstKeyOnlyFilter()); 1067 ResultScanner scanner = table.getScanner(scan); 1068 Result result; 1069 while ((result = scanner.next()) != null) { 1070 assertTrue(result.rawCells() != null); 1071 assertEquals(1, result.rawCells().length); 1072 } 1073 } 1074 1075}