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