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