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