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