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