HBASE-23741 Data loss when WAL split to HFile enabled (#1254)
[hbase.git] / hbase-server / src / test / java / org / apache / hadoop / hbase / TestPartialResultsFromClientSide.java
blob53670c4652ae6df60ab71c565de53ff93bed1510
1 /**
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
18 package org.apache.hadoop.hbase;
20 import static org.junit.Assert.assertArrayEquals;
21 import static org.junit.Assert.assertEquals;
22 import static org.junit.Assert.assertFalse;
23 import static org.junit.Assert.assertNull;
24 import static org.junit.Assert.assertTrue;
25 import static org.junit.Assert.fail;
27 import java.io.IOException;
28 import java.util.ArrayList;
29 import java.util.LinkedHashSet;
30 import java.util.List;
31 import java.util.Set;
32 import org.apache.hadoop.hbase.client.Delete;
33 import org.apache.hadoop.hbase.client.Put;
34 import org.apache.hadoop.hbase.client.RegionInfo;
35 import org.apache.hadoop.hbase.client.Result;
36 import org.apache.hadoop.hbase.client.ResultScanner;
37 import org.apache.hadoop.hbase.client.Scan;
38 import org.apache.hadoop.hbase.client.Table;
39 import org.apache.hadoop.hbase.filter.ColumnPrefixFilter;
40 import org.apache.hadoop.hbase.filter.ColumnRangeFilter;
41 import org.apache.hadoop.hbase.filter.Filter;
42 import org.apache.hadoop.hbase.filter.FirstKeyOnlyFilter;
43 import org.apache.hadoop.hbase.filter.FirstKeyValueMatchingQualifiersFilter;
44 import org.apache.hadoop.hbase.filter.RandomRowFilter;
45 import org.apache.hadoop.hbase.testclassification.LargeTests;
46 import org.apache.hadoop.hbase.util.Bytes;
47 import org.apache.hadoop.hbase.util.ClassSize;
48 import org.apache.hadoop.hbase.util.Pair;
49 import org.junit.AfterClass;
50 import org.junit.BeforeClass;
51 import org.junit.ClassRule;
52 import org.junit.Rule;
53 import org.junit.Test;
54 import org.junit.experimental.categories.Category;
55 import org.junit.rules.TestName;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
59 /**
60 * These tests are focused on testing how partial results appear to a client. Partial results are
61 * {@link Result}s that contain only a portion of a row's complete list of cells. Partial results
62 * are formed when the server breaches its maximum result size when trying to service a client's RPC
63 * request. It is the responsibility of the scanner on the client side to recognize when partial
64 * results have been returned and to take action to form the complete results.
65 * <p>
66 * Unless the flag {@link Scan#setAllowPartialResults(boolean)} has been set to true, the caller of
67 * {@link ResultScanner#next()} should never see partial results.
69 @Category(LargeTests.class)
70 public class TestPartialResultsFromClientSide {
72 @ClassRule
73 public static final HBaseClassTestRule CLASS_RULE =
74 HBaseClassTestRule.forClass(TestPartialResultsFromClientSide.class);
76 private static final Logger LOG = LoggerFactory.getLogger(TestPartialResultsFromClientSide.class);
78 private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
79 private final static int MINICLUSTER_SIZE = 5;
80 private static Table TABLE = null;
82 /**
83 * Table configuration
85 private static TableName TABLE_NAME = TableName.valueOf("testTable");
87 private static int NUM_ROWS = 5;
88 private static byte[] ROW = Bytes.toBytes("testRow");
89 private static byte[][] ROWS = HTestConst.makeNAscii(ROW, NUM_ROWS);
91 // Should keep this value below 10 to keep generation of expected kv's simple. If above 10 then
92 // table/row/cf1/... will be followed by table/row/cf10/... instead of table/row/cf2/... which
93 // breaks the simple generation of expected kv's
94 private static int NUM_FAMILIES = 10;
95 private static byte[] FAMILY = Bytes.toBytes("testFamily");
96 private static byte[][] FAMILIES = HTestConst.makeNAscii(FAMILY, NUM_FAMILIES);
98 private static int NUM_QUALIFIERS = 10;
99 private static byte[] QUALIFIER = Bytes.toBytes("testQualifier");
100 private static byte[][] QUALIFIERS = HTestConst.makeNAscii(QUALIFIER, NUM_QUALIFIERS);
102 private static int VALUE_SIZE = 1024;
103 private static byte[] VALUE = Bytes.createMaxByteArray(VALUE_SIZE);
105 private static int NUM_COLS = NUM_FAMILIES * NUM_QUALIFIERS;
107 // Approximation of how large the heap size of cells in our table. Should be accessed through
108 // getCellHeapSize().
109 private static long CELL_HEAP_SIZE = -1;
111 private static long timeout = 10000;
113 @Rule
114 public TestName name = new TestName();
116 @BeforeClass
117 public static void setUpBeforeClass() throws Exception {
118 TEST_UTIL.getConfiguration().setLong(HConstants.HBASE_CLIENT_SCANNER_TIMEOUT_PERIOD, timeout);
119 TEST_UTIL.startMiniCluster(MINICLUSTER_SIZE);
120 TEST_UTIL.getAdmin().balancerSwitch(false, true);
121 TABLE = createTestTable(TABLE_NAME, ROWS, FAMILIES, QUALIFIERS, VALUE);
124 static Table createTestTable(TableName name, byte[][] rows, byte[][] families,
125 byte[][] qualifiers, byte[] cellValue) throws IOException {
126 Table ht = TEST_UTIL.createTable(name, families);
127 List<Put> puts = createPuts(rows, families, qualifiers, cellValue);
128 ht.put(puts);
130 return ht;
133 @AfterClass
134 public static void tearDownAfterClass() throws Exception {
135 TEST_UTIL.shutdownMiniCluster();
139 * Ensure that the expected key values appear in a result returned from a scanner that is
140 * combining partial results into complete results
141 * @throws Exception
143 @Test
144 public void testExpectedValuesOfPartialResults() throws Exception {
145 testExpectedValuesOfPartialResults(false);
146 testExpectedValuesOfPartialResults(true);
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);
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;
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);
171 partialScanner.close();
175 * Ensure that we only see Results marked as partial when the allowPartial flag is set
176 * @throws Exception
178 @Test
179 public void testAllowPartialResults() throws Exception {
180 Scan scan = new Scan();
181 scan.setAllowPartialResults(true);
182 scan.setMaxResultSize(1);
183 ResultScanner scanner = TABLE.getScanner(scan);
184 Result result = scanner.next();
186 assertTrue(result != null);
187 assertTrue(result.mayHaveMoreCellsInRow());
188 assertTrue(result.rawCells() != null);
189 assertTrue(result.rawCells().length == 1);
191 scanner.close();
193 scan.setAllowPartialResults(false);
194 scanner = TABLE.getScanner(scan);
195 result = scanner.next();
197 assertTrue(result != null);
198 assertTrue(!result.mayHaveMoreCellsInRow());
199 assertTrue(result.rawCells() != null);
200 assertTrue(result.rawCells().length == NUM_COLS);
202 scanner.close();
206 * Ensure that the results returned from a scanner that retrieves all results in a single RPC call
207 * matches the results that are returned from a scanner that must incrementally combine partial
208 * results into complete results. A variety of scan configurations can be tested
209 * @throws Exception
211 @Test
212 public void testEquivalenceOfScanResults() throws Exception {
213 Scan oneShotScan = new Scan();
214 oneShotScan.setMaxResultSize(Long.MAX_VALUE);
216 Scan partialScan = new Scan(oneShotScan);
217 partialScan.setMaxResultSize(1);
219 testEquivalenceOfScanResults(TABLE, oneShotScan, partialScan);
222 public void testEquivalenceOfScanResults(Table table, Scan scan1, Scan scan2) throws Exception {
223 ResultScanner scanner1 = table.getScanner(scan1);
224 ResultScanner scanner2 = table.getScanner(scan2);
226 Result r1 = null;
227 Result r2 = null;
228 int count = 0;
230 while ((r1 = scanner1.next()) != null) {
231 r2 = scanner2.next();
233 assertTrue(r2 != null);
234 compareResults(r1, r2, "Comparing result #" + count);
235 count++;
238 r2 = scanner2.next();
239 assertTrue("r2: " + r2 + " Should be null", r2 == null);
241 scanner1.close();
242 scanner2.close();
246 * Order of cells in partial results matches the ordering of cells from complete results
247 * @throws Exception
249 @Test
250 public void testOrderingOfCellsInPartialResults() throws Exception {
251 Scan scan = new Scan();
253 for (int col = 1; col <= NUM_COLS; col++) {
254 scan.setMaxResultSize(getResultSizeForNumberOfCells(col));
255 testOrderingOfCellsInPartialResults(scan);
257 // Test again with a reversed scanner
258 scan.setReversed(true);
259 testOrderingOfCellsInPartialResults(scan);
263 public void testOrderingOfCellsInPartialResults(final Scan basePartialScan) throws Exception {
264 // Scan that retrieves results in pieces (partials). By setting allowPartialResults to be true
265 // the results will NOT be reconstructed and instead the caller will see the partial results
266 // returned by the server
267 Scan partialScan = new Scan(basePartialScan);
268 partialScan.setAllowPartialResults(true);
269 ResultScanner partialScanner = TABLE.getScanner(partialScan);
271 // Scan that retrieves all table results in single RPC request
272 Scan oneShotScan = new Scan(basePartialScan);
273 oneShotScan.setMaxResultSize(Long.MAX_VALUE);
274 oneShotScan.setCaching(ROWS.length);
275 ResultScanner oneShotScanner = TABLE.getScanner(oneShotScan);
277 Result oneShotResult = oneShotScanner.next();
278 Result partialResult = null;
279 int iterationCount = 0;
281 while (oneShotResult != null && oneShotResult.rawCells() != null) {
282 List<Cell> aggregatePartialCells = new ArrayList<>();
283 do {
284 partialResult = partialScanner.next();
285 assertTrue("Partial Result is null. iteration: " + iterationCount, partialResult != null);
286 assertTrue("Partial cells are null. iteration: " + iterationCount,
287 partialResult.rawCells() != null);
289 for (Cell c : partialResult.rawCells()) {
290 aggregatePartialCells.add(c);
292 } while (partialResult.mayHaveMoreCellsInRow());
294 assertTrue("Number of cells differs. iteration: " + iterationCount,
295 oneShotResult.rawCells().length == aggregatePartialCells.size());
296 final Cell[] oneShotCells = oneShotResult.rawCells();
297 for (int cell = 0; cell < oneShotCells.length; cell++) {
298 Cell oneShotCell = oneShotCells[cell];
299 Cell partialCell = aggregatePartialCells.get(cell);
301 assertTrue("One shot cell was null", oneShotCell != null);
302 assertTrue("Partial cell was null", partialCell != null);
303 assertTrue("Cell differs. oneShotCell:" + oneShotCell + " partialCell:" + partialCell,
304 oneShotCell.equals(partialCell));
307 oneShotResult = oneShotScanner.next();
308 iterationCount++;
311 assertTrue(partialScanner.next() == null);
313 partialScanner.close();
314 oneShotScanner.close();
318 * Setting the max result size allows us to control how many cells we expect to see on each call
319 * to next on the scanner. Test a variety of different sizes for correctness
320 * @throws Exception
322 @Test
323 public void testExpectedNumberOfCellsPerPartialResult() throws Exception {
324 Scan scan = new Scan();
325 testExpectedNumberOfCellsPerPartialResult(scan);
327 scan.setReversed(true);
328 testExpectedNumberOfCellsPerPartialResult(scan);
331 public void testExpectedNumberOfCellsPerPartialResult(Scan baseScan) throws Exception {
332 for (int expectedCells = 1; expectedCells <= NUM_COLS; expectedCells++) {
333 testExpectedNumberOfCellsPerPartialResult(baseScan, expectedCells);
337 public void testExpectedNumberOfCellsPerPartialResult(Scan baseScan, int expectedNumberOfCells)
338 throws Exception {
340 if (LOG.isInfoEnabled()) LOG.info("groupSize:" + expectedNumberOfCells);
342 // Use the cellHeapSize to set maxResultSize such that we know how many cells to expect back
343 // from the call. The returned results should NOT exceed expectedNumberOfCells but may be less
344 // than it in cases where expectedNumberOfCells is not an exact multiple of the number of
345 // columns in the table.
346 Scan scan = new Scan(baseScan);
347 scan.setAllowPartialResults(true);
348 scan.setMaxResultSize(getResultSizeForNumberOfCells(expectedNumberOfCells));
350 ResultScanner scanner = TABLE.getScanner(scan);
351 Result result = null;
352 byte[] prevRow = null;
353 while ((result = scanner.next()) != null) {
354 assertTrue(result.rawCells() != null);
356 // Cases when cell count won't equal expectedNumberOfCells:
357 // 1. Returned result is the final result needed to form the complete result for that row
358 // 2. It is the first result we have seen for that row and thus may have been fetched as
359 // the last group of cells that fit inside the maxResultSize
360 assertTrue(
361 "Result's cell count differed from expected number. result: " + result,
362 result.rawCells().length == expectedNumberOfCells || !result.mayHaveMoreCellsInRow()
363 || !Bytes.equals(prevRow, result.getRow()));
364 prevRow = result.getRow();
367 scanner.close();
371 * @return The approximate heap size of a cell in the test table. All cells should have
372 * approximately the same heap size, so the value is cached to avoid repeating the
373 * calculation
374 * @throws Exception
376 private long getCellHeapSize() throws Exception {
377 if (CELL_HEAP_SIZE == -1) {
378 // Do a partial scan that will return a single result with a single cell
379 Scan scan = new Scan();
380 scan.setMaxResultSize(2);
381 scan.setAllowPartialResults(true);
382 ResultScanner scanner = TABLE.getScanner(scan);
384 Result result = scanner.next();
386 assertTrue(result != null);
387 assertTrue(result.rawCells() != null);
388 assertTrue(result.rawCells().length == 1);
390 // Estimate the cell heap size. One difference is that on server side, the KV Heap size is
391 // estimated differently in case the cell is backed up by MSLAB byte[] (no overhead for
392 // backing array). Thus below calculation is a bit brittle.
393 CELL_HEAP_SIZE = result.rawCells()[0].heapSize() - (ClassSize.ARRAY + 3);
394 if (LOG.isInfoEnabled()) LOG.info("Cell heap size: " + CELL_HEAP_SIZE);
395 scanner.close();
398 return CELL_HEAP_SIZE;
402 * @param numberOfCells
403 * @return the result size that should be used in {@link Scan#setMaxResultSize(long)} if you want
404 * the server to return exactly numberOfCells cells
405 * @throws Exception
407 private long getResultSizeForNumberOfCells(int numberOfCells) throws Exception {
408 return getCellHeapSize() * numberOfCells;
412 * Test various combinations of batching and partial results for correctness
414 @Test
415 public void testPartialResultsAndBatch() throws Exception {
416 for (int batch = 1; batch <= NUM_COLS / 4; batch++) {
417 for (int cellsPerPartial = 1; cellsPerPartial <= NUM_COLS / 4; cellsPerPartial++) {
418 testPartialResultsAndBatch(batch, cellsPerPartial);
423 public void testPartialResultsAndBatch(final int batch, final int cellsPerPartialResult)
424 throws Exception {
425 if (LOG.isInfoEnabled()) {
426 LOG.info("batch: " + batch + " cellsPerPartialResult: " + cellsPerPartialResult);
429 Scan scan = new Scan();
430 scan.setMaxResultSize(getResultSizeForNumberOfCells(cellsPerPartialResult));
431 scan.setBatch(batch);
432 ResultScanner scanner = TABLE.getScanner(scan);
433 Result result = scanner.next();
434 int repCount = 0;
436 while ((result = scanner.next()) != null) {
437 assertTrue(result.rawCells() != null);
439 if (result.mayHaveMoreCellsInRow()) {
440 final String error =
441 "Cells:" + result.rawCells().length + " Batch size:" + batch
442 + " cellsPerPartialResult:" + cellsPerPartialResult + " rep:" + repCount;
443 assertTrue(error, result.rawCells().length == batch);
444 } else {
445 assertTrue(result.rawCells().length <= batch);
447 repCount++;
450 scanner.close();
454 * Test the method {@link Result#createCompleteResult(Iterable)}
455 * @throws Exception
457 @Test
458 public void testPartialResultsReassembly() throws Exception {
459 Scan scan = new Scan();
460 testPartialResultsReassembly(scan);
461 scan.setReversed(true);
462 testPartialResultsReassembly(scan);
465 public void testPartialResultsReassembly(Scan scanBase) throws Exception {
466 Scan partialScan = new Scan(scanBase);
467 partialScan.setMaxResultSize(1);
468 partialScan.setAllowPartialResults(true);
469 ResultScanner partialScanner = TABLE.getScanner(partialScan);
471 Scan oneShotScan = new Scan(scanBase);
472 oneShotScan.setMaxResultSize(Long.MAX_VALUE);
473 ResultScanner oneShotScanner = TABLE.getScanner(oneShotScan);
475 ArrayList<Result> partials = new ArrayList<>();
476 for (int i = 0; i < NUM_ROWS; i++) {
477 Result partialResult = null;
478 Result completeResult = null;
479 Result oneShotResult = null;
480 partials.clear();
482 do {
483 partialResult = partialScanner.next();
484 partials.add(partialResult);
485 } while (partialResult != null && partialResult.mayHaveMoreCellsInRow());
487 completeResult = Result.createCompleteResult(partials);
488 oneShotResult = oneShotScanner.next();
490 compareResults(completeResult, oneShotResult, null);
493 assertTrue(oneShotScanner.next() == null);
494 assertTrue(partialScanner.next() == null);
496 oneShotScanner.close();
497 partialScanner.close();
501 * When reconstructing the complete result from its partials we ensure that the row of each
502 * partial result is the same. If one of the rows differs, an exception is thrown.
504 @Test
505 public void testExceptionThrownOnMismatchedPartialResults() throws IOException {
506 assertTrue(NUM_ROWS >= 2);
508 ArrayList<Result> partials = new ArrayList<>();
509 Scan scan = new Scan();
510 scan.setMaxResultSize(Long.MAX_VALUE);
511 ResultScanner scanner = TABLE.getScanner(scan);
512 Result r1 = scanner.next();
513 partials.add(r1);
514 Result r2 = scanner.next();
515 partials.add(r2);
517 assertFalse(Bytes.equals(r1.getRow(), r2.getRow()));
519 try {
520 Result.createCompleteResult(partials);
521 fail("r1 and r2 are from different rows. It should not be possible to combine them into"
522 + " a single result");
523 } catch (IOException e) {
526 scanner.close();
530 * When a scan has a filter where {@link org.apache.hadoop.hbase.filter.Filter#hasFilterRow()} is
531 * true, the scanner should not return partial results. The scanner cannot return partial results
532 * because the entire row needs to be read for the include/exclude decision to be made
534 @Test
535 public void testNoPartialResultsWhenRowFilterPresent() throws Exception {
536 Scan scan = new Scan();
537 scan.setMaxResultSize(1);
538 scan.setAllowPartialResults(true);
539 // If a filter hasFilter() is true then partial results should not be returned else filter
540 // application server side would break.
541 scan.setFilter(new RandomRowFilter(1.0f));
542 ResultScanner scanner = TABLE.getScanner(scan);
544 Result r = null;
545 while ((r = scanner.next()) != null) {
546 assertFalse(r.mayHaveMoreCellsInRow());
549 scanner.close();
553 * Examine the interaction between the maxResultSize and caching. If the caching limit is reached
554 * before the maxResultSize limit, we should not see partial results. On the other hand, if the
555 * maxResultSize limit is reached before the caching limit, it is likely that partial results will
556 * be seen.
557 * @throws Exception
559 @Test
560 public void testPartialResultsAndCaching() throws Exception {
561 for (int caching = 1; caching <= NUM_ROWS; caching++) {
562 for (int maxResultRows = 0; maxResultRows <= NUM_ROWS; maxResultRows++) {
563 testPartialResultsAndCaching(maxResultRows, caching);
569 * @param resultSizeRowLimit The row limit that will be enforced through maxResultSize
570 * @param cachingRowLimit The row limit that will be enforced through caching
572 public void testPartialResultsAndCaching(int resultSizeRowLimit, int cachingRowLimit)
573 throws Exception {
574 Scan scan = new Scan();
575 scan.setAllowPartialResults(true);
577 // The number of cells specified in the call to getResultSizeForNumberOfCells is offset to
578 // ensure that the result size we specify is not an exact multiple of the number of cells
579 // in a row. This ensures that partial results will be returned when the result size limit
580 // is reached before the caching limit.
581 int cellOffset = NUM_COLS / 3;
582 long maxResultSize = getResultSizeForNumberOfCells(resultSizeRowLimit * NUM_COLS + cellOffset);
583 scan.setMaxResultSize(maxResultSize);
584 scan.setCaching(cachingRowLimit);
586 try (ResultScanner scanner = TABLE.getScanner(scan)) {
587 Result r = null;
588 // Approximate the number of rows we expect will fit into the specified max rsult size. If
589 // this approximation is less than caching, then we expect that the max result size limit will
590 // be hit before the caching limit and thus partial results may be seen
591 boolean expectToSeePartialResults = resultSizeRowLimit < cachingRowLimit;
592 while ((r = scanner.next()) != null) {
593 assertTrue(!r.mayHaveMoreCellsInRow() || expectToSeePartialResults);
599 * Make puts to put the input value into each combination of row, family, and qualifier
600 * @param rows the rows to use
601 * @param families the families to use
602 * @param qualifiers the qualifiers to use
603 * @param value the values to use
604 * @return the dot product of the given rows, families, qualifiers, and values
605 * @throws IOException if there is a problem creating one of the Put objects
607 static ArrayList<Put> createPuts(byte[][] rows, byte[][] families, byte[][] qualifiers,
608 byte[] value) throws IOException {
609 Put put;
610 ArrayList<Put> puts = new ArrayList<>();
612 for (int row = 0; row < rows.length; row++) {
613 put = new Put(rows[row]);
614 for (int fam = 0; fam < families.length; fam++) {
615 for (int qual = 0; qual < qualifiers.length; qual++) {
616 KeyValue kv = new KeyValue(rows[row], families[fam], qualifiers[qual], qual, value);
617 put.add(kv);
620 puts.add(put);
623 return puts;
627 * Make key values to represent each possible combination of family and qualifier in the specified
628 * row.
629 * @param row the row to use
630 * @param families the families to use
631 * @param qualifiers the qualifiers to use
632 * @param value the values to use
633 * @return the dot product of the given families, qualifiers, and values for a given row
635 static ArrayList<Cell> createKeyValuesForRow(byte[] row, byte[][] families, byte[][] qualifiers,
636 byte[] value) {
637 ArrayList<Cell> outList = new ArrayList<>();
638 for (int fam = 0; fam < families.length; fam++) {
639 for (int qual = 0; qual < qualifiers.length; qual++) {
640 outList.add(new KeyValue(row, families[fam], qualifiers[qual], qual, value));
643 return outList;
647 * Verifies that result contains all the key values within expKvList. Fails the test otherwise
648 * @param result
649 * @param expKvList
650 * @param msg
652 static void verifyResult(Result result, List<Cell> expKvList, String msg) {
653 if (LOG.isInfoEnabled()) {
654 LOG.info(msg);
655 LOG.info("Expected count: " + expKvList.size());
656 LOG.info("Actual count: " + result.size());
659 if (expKvList.isEmpty()) return;
661 int i = 0;
662 for (Cell kv : result.rawCells()) {
663 if (i >= expKvList.size()) {
664 break; // we will check the size later
667 Cell kvExp = expKvList.get(i++);
668 assertTrue("Not equal. get kv: " + kv.toString() + " exp kv: " + kvExp.toString(),
669 kvExp.equals(kv));
672 assertEquals(expKvList.size(), result.size());
676 * Compares two results and fails the test if the results are different
677 * @param r1
678 * @param r2
679 * @param message
681 static void compareResults(Result r1, Result r2, final String message) {
682 if (LOG.isInfoEnabled()) {
683 if (message != null) LOG.info(message);
684 LOG.info("r1: " + r1);
685 LOG.info("r2: " + r2);
688 final String failureMessage = "Results r1:" + r1 + " \nr2:" + r2 + " are not equivalent";
689 if (r1 == null && r2 == null) fail(failureMessage);
690 else if (r1 == null || r2 == null) fail(failureMessage);
692 try {
693 Result.compareResults(r1, r2);
694 } catch (Exception e) {
695 fail(failureMessage);
699 @Test
700 public void testReadPointAndPartialResults() throws Exception {
701 final TableName tableName = TableName.valueOf(name.getMethodName());
702 int numRows = 5;
703 int numFamilies = 5;
704 int numQualifiers = 5;
705 byte[][] rows = HTestConst.makeNAscii(Bytes.toBytes("testRow"), numRows);
706 byte[][] families = HTestConst.makeNAscii(Bytes.toBytes("testFamily"), numFamilies);
707 byte[][] qualifiers = HTestConst.makeNAscii(Bytes.toBytes("testQualifier"), numQualifiers);
708 byte[] value = Bytes.createMaxByteArray(100);
710 Table tmpTable = createTestTable(tableName, rows, families, qualifiers, value);
711 // Open scanner before deletes
712 ResultScanner scanner =
713 tmpTable.getScanner(new Scan().setMaxResultSize(1).setAllowPartialResults(true));
714 // now the openScanner will also fetch data and will be executed lazily, i.e, only openScanner
715 // when you call next, so here we need to make a next call to open scanner. The maxResultSize
716 // limit can make sure that we will not fetch all the data at once, so the test sill works.
717 int scannerCount = scanner.next().rawCells().length;
718 Delete delete1 = new Delete(rows[0]);
719 delete1.addColumn(families[0], qualifiers[0], 0);
720 tmpTable.delete(delete1);
722 Delete delete2 = new Delete(rows[1]);
723 delete2.addColumn(families[1], qualifiers[1], 1);
724 tmpTable.delete(delete2);
726 // Should see all cells because scanner was opened prior to deletes
727 scannerCount += countCellsFromScanner(scanner);
728 int expectedCount = numRows * numFamilies * numQualifiers;
729 assertTrue("scannerCount: " + scannerCount + " expectedCount: " + expectedCount,
730 scannerCount == expectedCount);
732 // Minus 2 for the two cells that were deleted
733 scanner = tmpTable.getScanner(new Scan().setMaxResultSize(1).setAllowPartialResults(true));
734 scannerCount = countCellsFromScanner(scanner);
735 expectedCount = numRows * numFamilies * numQualifiers - 2;
736 assertTrue("scannerCount: " + scannerCount + " expectedCount: " + expectedCount,
737 scannerCount == expectedCount);
739 scanner = tmpTable.getScanner(new Scan().setMaxResultSize(1).setAllowPartialResults(true));
740 scannerCount = scanner.next().rawCells().length;
741 // Put in 2 new rows. The timestamps differ from the deleted rows
742 Put put1 = new Put(rows[0]);
743 put1.add(new KeyValue(rows[0], families[0], qualifiers[0], 1, value));
744 tmpTable.put(put1);
746 Put put2 = new Put(rows[1]);
747 put2.add(new KeyValue(rows[1], families[1], qualifiers[1], 2, value));
748 tmpTable.put(put2);
750 // Scanner opened prior to puts. Cell count shouldn't have changed
751 scannerCount += countCellsFromScanner(scanner);
752 expectedCount = numRows * numFamilies * numQualifiers - 2;
753 assertTrue("scannerCount: " + scannerCount + " expectedCount: " + expectedCount,
754 scannerCount == expectedCount);
756 // Now the scanner should see the cells that were added by puts
757 scanner = tmpTable.getScanner(new Scan().setMaxResultSize(1).setAllowPartialResults(true));
758 scannerCount = countCellsFromScanner(scanner);
759 expectedCount = numRows * numFamilies * numQualifiers;
760 assertTrue("scannerCount: " + scannerCount + " expectedCount: " + expectedCount,
761 scannerCount == expectedCount);
763 TEST_UTIL.deleteTable(tableName);
767 * Exhausts the scanner by calling next repetitively. Once completely exhausted, close scanner and
768 * return total cell count
769 * @param scanner the scanner to exhaust
770 * @return the number of cells counted
771 * @throws Exception if there is a problem retrieving cells from the scanner
773 private int countCellsFromScanner(ResultScanner scanner) throws Exception {
774 Result result = null;
775 int numCells = 0;
776 while ((result = scanner.next()) != null) {
777 numCells += result.rawCells().length;
780 scanner.close();
781 return numCells;
785 * Test partial Result re-assembly in the presence of different filters. The Results from the
786 * partial scanner should match the Results returned from a scanner that receives all of the
787 * results in one RPC to the server. The partial scanner is tested with a variety of different
788 * result sizes (all of which are less than the size necessary to fetch an entire row)
789 * @throws Exception
791 @Test
792 public void testPartialResultsWithColumnFilter() throws Exception {
793 testPartialResultsWithColumnFilter(new FirstKeyOnlyFilter());
794 testPartialResultsWithColumnFilter(new ColumnPrefixFilter(Bytes.toBytes("testQualifier5")));
795 testPartialResultsWithColumnFilter(new ColumnRangeFilter(Bytes.toBytes("testQualifer1"), true,
796 Bytes.toBytes("testQualifier7"), true));
798 Set<byte[]> qualifiers = new LinkedHashSet<>();
799 qualifiers.add(Bytes.toBytes("testQualifier5"));
800 testPartialResultsWithColumnFilter(new FirstKeyValueMatchingQualifiersFilter(qualifiers));
803 public void testPartialResultsWithColumnFilter(Filter filter) throws Exception {
804 assertTrue(!filter.hasFilterRow());
806 Scan partialScan = new Scan();
807 partialScan.setFilter(filter);
809 Scan oneshotScan = new Scan();
810 oneshotScan.setFilter(filter);
811 oneshotScan.setMaxResultSize(Long.MAX_VALUE);
813 for (int i = 1; i <= NUM_COLS; i++) {
814 partialScan.setMaxResultSize(getResultSizeForNumberOfCells(i));
815 testEquivalenceOfScanResults(TABLE, partialScan, oneshotScan);
819 private void moveRegion(Table table, int index) throws IOException{
820 List<Pair<RegionInfo, ServerName>> regions = MetaTableAccessor
821 .getTableRegionsAndLocations(TEST_UTIL.getConnection(),
822 table.getName());
823 assertEquals(1, regions.size());
824 RegionInfo regionInfo = regions.get(0).getFirst();
825 ServerName name = TEST_UTIL.getHBaseCluster().getRegionServer(index).getServerName();
826 TEST_UTIL.getAdmin().move(regionInfo.getEncodedNameAsBytes(), name);
829 private void assertCell(Cell cell, byte[] row, byte[] cf, byte[] cq) {
830 assertArrayEquals(row,
831 Bytes.copy(cell.getRowArray(), cell.getRowOffset(), cell.getRowLength()));
832 assertArrayEquals(cf,
833 Bytes.copy(cell.getFamilyArray(), cell.getFamilyOffset(), cell.getFamilyLength()));
834 assertArrayEquals(cq,
835 Bytes.copy(cell.getQualifierArray(), cell.getQualifierOffset(), cell.getQualifierLength()));
838 @Test
839 public void testPartialResultWhenRegionMove() throws IOException {
840 Table table = createTestTable(TableName.valueOf(name.getMethodName()),
841 ROWS, FAMILIES, QUALIFIERS, VALUE);
843 moveRegion(table, 1);
845 Scan scan = new Scan();
846 scan.setMaxResultSize(1);
847 scan.setAllowPartialResults(true);
848 ResultScanner scanner = table.getScanner(scan);
849 for (int i = 0; i < NUM_FAMILIES * NUM_QUALIFIERS - 1; i++) {
850 scanner.next();
852 Result result1 = scanner.next();
853 assertEquals(1, result1.rawCells().length);
854 Cell c1 = result1.rawCells()[0];
855 assertCell(c1, ROWS[0], FAMILIES[NUM_FAMILIES - 1], QUALIFIERS[NUM_QUALIFIERS - 1]);
856 assertFalse(result1.mayHaveMoreCellsInRow());
858 moveRegion(table, 2);
860 Result result2 = scanner.next();
861 assertEquals(1, result2.rawCells().length);
862 Cell c2 = result2.rawCells()[0];
863 assertCell(c2, ROWS[1], FAMILIES[0], QUALIFIERS[0]);
864 assertTrue(result2.mayHaveMoreCellsInRow());
866 moveRegion(table, 3);
868 Result result3 = scanner.next();
869 assertEquals(1, result3.rawCells().length);
870 Cell c3 = result3.rawCells()[0];
871 assertCell(c3, ROWS[1], FAMILIES[0], QUALIFIERS[1]);
872 assertTrue(result3.mayHaveMoreCellsInRow());
876 @Test
877 public void testReversedPartialResultWhenRegionMove() throws IOException {
878 Table table = createTestTable(TableName.valueOf(name.getMethodName()),
879 ROWS, FAMILIES, QUALIFIERS, VALUE);
881 moveRegion(table, 1);
883 Scan scan = new Scan();
884 scan.setMaxResultSize(1);
885 scan.setAllowPartialResults(true);
886 scan.setReversed(true);
887 ResultScanner scanner = table.getScanner(scan);
888 for (int i = 0; i < NUM_FAMILIES * NUM_QUALIFIERS-1; i++) {
889 scanner.next();
891 Result result1 = scanner.next();
892 assertEquals(1, result1.rawCells().length);
893 Cell c1 = result1.rawCells()[0];
894 assertCell(c1, ROWS[NUM_ROWS-1], FAMILIES[NUM_FAMILIES - 1], QUALIFIERS[NUM_QUALIFIERS - 1]);
895 assertFalse(result1.mayHaveMoreCellsInRow());
897 moveRegion(table, 2);
899 Result result2 = scanner.next();
900 assertEquals(1, result2.rawCells().length);
901 Cell c2 = result2.rawCells()[0];
902 assertCell(c2, ROWS[NUM_ROWS-2], FAMILIES[0], QUALIFIERS[0]);
903 assertTrue(result2.mayHaveMoreCellsInRow());
905 moveRegion(table, 3);
907 Result result3 = scanner.next();
908 assertEquals(1, result3.rawCells().length);
909 Cell c3 = result3.rawCells()[0];
910 assertCell(c3, ROWS[NUM_ROWS-2], FAMILIES[0], QUALIFIERS[1]);
911 assertTrue(result3.mayHaveMoreCellsInRow());
915 @Test
916 public void testCompleteResultWhenRegionMove() throws IOException {
917 Table table = createTestTable(TableName.valueOf(name.getMethodName()),
918 ROWS, FAMILIES, QUALIFIERS, VALUE);
920 moveRegion(table, 1);
922 Scan scan = new Scan();
923 scan.setMaxResultSize(1);
924 scan.setCaching(1);
925 ResultScanner scanner = table.getScanner(scan);
927 Result result1 = scanner.next();
928 assertEquals(NUM_FAMILIES * NUM_QUALIFIERS, result1.rawCells().length);
929 Cell c1 = result1.rawCells()[0];
930 assertCell(c1, ROWS[0], FAMILIES[0], QUALIFIERS[0]);
931 assertFalse(result1.mayHaveMoreCellsInRow());
933 moveRegion(table, 2);
935 Result result2 = scanner.next();
936 assertEquals(NUM_FAMILIES * NUM_QUALIFIERS, result2.rawCells().length);
937 Cell c2 = result2.rawCells()[0];
938 assertCell(c2, ROWS[1], FAMILIES[0], QUALIFIERS[0]);
939 assertFalse(result2.mayHaveMoreCellsInRow());
941 moveRegion(table, 3);
943 Result result3 = scanner.next();
944 assertEquals(NUM_FAMILIES * NUM_QUALIFIERS, result3.rawCells().length);
945 Cell c3 = result3.rawCells()[0];
946 assertCell(c3, ROWS[2], FAMILIES[0], QUALIFIERS[0]);
947 assertFalse(result3.mayHaveMoreCellsInRow());
951 @Test
952 public void testReversedCompleteResultWhenRegionMove() throws IOException {
953 Table table = createTestTable(TableName.valueOf(name.getMethodName()),
954 ROWS, FAMILIES, QUALIFIERS, VALUE);
956 moveRegion(table, 1);
958 Scan scan = new Scan();
959 scan.setMaxResultSize(1);
960 scan.setCaching(1);
961 scan.setReversed(true);
962 ResultScanner scanner = table.getScanner(scan);
964 Result result1 = scanner.next();
965 assertEquals(NUM_FAMILIES*NUM_QUALIFIERS, result1.rawCells().length);
966 Cell c1 = result1.rawCells()[0];
967 assertCell(c1, ROWS[NUM_ROWS-1], FAMILIES[0], QUALIFIERS[0]);
968 assertFalse(result1.mayHaveMoreCellsInRow());
970 moveRegion(table, 2);
972 Result result2 = scanner.next();
973 assertEquals(NUM_FAMILIES*NUM_QUALIFIERS, result2.rawCells().length);
974 Cell c2 = result2.rawCells()[0];
975 assertCell(c2, ROWS[NUM_ROWS-2], FAMILIES[0], QUALIFIERS[0]);
976 assertFalse(result2.mayHaveMoreCellsInRow());
978 moveRegion(table, 3);
980 Result result3 = scanner.next();
981 assertEquals(NUM_FAMILIES*NUM_QUALIFIERS, result3.rawCells().length);
982 Cell c3 = result3.rawCells()[0];
983 assertCell(c3, ROWS[NUM_ROWS-3], FAMILIES[0], QUALIFIERS[0]);
984 assertFalse(result3.mayHaveMoreCellsInRow());
988 @Test
989 public void testBatchingResultWhenRegionMove() throws IOException {
990 // If user setBatch(5) and rpc returns 3+5+5+5+3 cells,
991 // we should return 5+5+5+5+1 to user.
992 // setBatch doesn't mean setAllowPartialResult(true)
993 Table table = createTestTable(TableName.valueOf(name.getMethodName()), ROWS, FAMILIES,
994 QUALIFIERS, VALUE);
996 Put put = new Put(ROWS[1]);
997 put.addColumn(FAMILIES[0], QUALIFIERS[1], new byte[VALUE_SIZE * 10]);
998 table.put(put);
999 Delete delete = new Delete(ROWS[1]);
1000 delete.addColumn(FAMILIES[NUM_FAMILIES - 1], QUALIFIERS[NUM_QUALIFIERS - 1]);
1001 table.delete(delete);
1003 moveRegion(table, 1);
1005 Scan scan = new Scan();
1006 scan.setCaching(1);
1007 scan.setBatch(5);
1008 scan.setMaxResultSize(VALUE_SIZE * 6);
1010 ResultScanner scanner = table.getScanner(scan);
1011 for (int i = 0; i < NUM_FAMILIES * NUM_QUALIFIERS / 5 - 1; i++) {
1012 assertTrue(scanner.next().mayHaveMoreCellsInRow());
1014 Result result1 = scanner.next();
1015 assertEquals(5, result1.rawCells().length);
1016 assertCell(result1.rawCells()[0], ROWS[0], FAMILIES[NUM_FAMILIES - 1],
1017 QUALIFIERS[NUM_QUALIFIERS - 5]);
1018 assertCell(result1.rawCells()[4], ROWS[0], FAMILIES[NUM_FAMILIES - 1],
1019 QUALIFIERS[NUM_QUALIFIERS - 1]);
1020 assertFalse(result1.mayHaveMoreCellsInRow());
1022 moveRegion(table, 2);
1024 Result result2 = scanner.next();
1025 assertEquals(5, result2.rawCells().length);
1026 assertCell(result2.rawCells()[0], ROWS[1], FAMILIES[0], QUALIFIERS[0]);
1027 assertCell(result2.rawCells()[4], ROWS[1], FAMILIES[0], QUALIFIERS[4]);
1028 assertTrue(result2.mayHaveMoreCellsInRow());
1030 moveRegion(table, 3);
1032 Result result3 = scanner.next();
1033 assertEquals(5, result3.rawCells().length);
1034 assertCell(result3.rawCells()[0], ROWS[1], FAMILIES[0], QUALIFIERS[5]);
1035 assertCell(result3.rawCells()[4], ROWS[1], FAMILIES[0], QUALIFIERS[9]);
1036 assertTrue(result3.mayHaveMoreCellsInRow());
1038 for (int i = 0; i < NUM_FAMILIES * NUM_QUALIFIERS / 5 - 3; i++) {
1039 Result result = scanner.next();
1040 assertEquals(5, result.rawCells().length);
1041 assertTrue(result.mayHaveMoreCellsInRow());
1043 Result result = scanner.next();
1044 assertEquals(4, result.rawCells().length);
1045 assertFalse(result.mayHaveMoreCellsInRow());
1048 for (int i = 2; i < NUM_ROWS; i++) {
1049 for (int j = 0; j < NUM_FAMILIES; j++) {
1050 for (int k = 0; k < NUM_QUALIFIERS; k += 5) {
1051 result = scanner.next();
1052 assertCell(result.rawCells()[0], ROWS[i], FAMILIES[j], QUALIFIERS[k]);
1053 assertEquals(5, result.rawCells().length);
1054 if (j == NUM_FAMILIES - 1 && k == NUM_QUALIFIERS - 5) {
1055 assertFalse(result.mayHaveMoreCellsInRow());
1056 } else {
1057 assertTrue(result.mayHaveMoreCellsInRow());
1062 assertNull(scanner.next());
1065 @Test
1066 public void testDontThrowUnknowScannerExceptionToClient() throws Exception {
1067 Table table = createTestTable(TableName.valueOf(name.getMethodName()), ROWS, FAMILIES,
1068 QUALIFIERS, VALUE);
1069 Scan scan = new Scan();
1070 scan.setCaching(1);
1071 ResultScanner scanner = table.getScanner(scan);
1072 scanner.next();
1073 Thread.sleep(timeout * 2);
1074 int count = 1;
1075 while (scanner.next() != null) {
1076 count++;
1078 assertEquals(NUM_ROWS, count);
1079 scanner.close();
1082 @Test
1083 public void testMayHaveMoreCellsInRowReturnsTrueAndSetBatch() throws IOException {
1084 Table table = createTestTable(TableName.valueOf(name.getMethodName()), ROWS, FAMILIES,
1085 QUALIFIERS, VALUE);
1086 Scan scan = new Scan();
1087 scan.setBatch(1);
1088 scan.setFilter(new FirstKeyOnlyFilter());
1089 ResultScanner scanner = table.getScanner(scan);
1090 Result result;
1091 while ((result = scanner.next()) != null) {
1092 assertTrue(result.rawCells() != null);
1093 assertEquals(1, result.rawCells().length);