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