View Javadoc

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