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