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