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