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