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.client;
019
020import static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertFalse;
022import static org.junit.Assert.assertTrue;
023
024import java.io.IOException;
025import java.util.ArrayList;
026import java.util.List;
027import org.apache.hadoop.hbase.HBaseClassTestRule;
028import org.apache.hadoop.hbase.HBaseTestingUtil;
029import org.apache.hadoop.hbase.TableName;
030import org.apache.hadoop.hbase.testclassification.ClientTests;
031import org.apache.hadoop.hbase.testclassification.LargeTests;
032import org.apache.hadoop.hbase.util.Bytes;
033import org.junit.AfterClass;
034import org.junit.BeforeClass;
035import org.junit.ClassRule;
036import org.junit.Test;
037import org.junit.experimental.categories.Category;
038import org.junit.runner.RunWith;
039import org.junit.runners.Parameterized;
040import org.junit.runners.Parameterized.Parameter;
041import org.junit.runners.Parameterized.Parameters;
042
043/**
044 * Testcase for newly added feature in HBASE-17143, such as startRow and stopRow
045 * inclusive/exclusive, limit for rows, etc.
046 */
047@RunWith(Parameterized.class)
048@Category({ LargeTests.class, ClientTests.class })
049public class TestScannersFromClientSide2 {
050
051  @ClassRule
052  public static final HBaseClassTestRule CLASS_RULE =
053    HBaseClassTestRule.forClass(TestScannersFromClientSide2.class);
054
055  private static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil();
056
057  private static TableName TABLE_NAME = TableName.valueOf("scan");
058
059  private static byte[] FAMILY = Bytes.toBytes("cf");
060
061  private static byte[] CQ1 = Bytes.toBytes("cq1");
062
063  private static byte[] CQ2 = Bytes.toBytes("cq2");
064
065  @Parameter(0)
066  public boolean batch;
067
068  @Parameter(1)
069  public boolean smallResultSize;
070
071  @Parameter(2)
072  public boolean allowPartial;
073
074  @Parameters(name = "{index}: batch={0}, smallResultSize={1}, allowPartial={2}")
075  public static List<Object[]> params() {
076    List<Object[]> params = new ArrayList<>();
077    boolean[] values = new boolean[] { false, true };
078    for (int i = 0; i < 2; i++) {
079      for (int j = 0; j < 2; j++) {
080        for (int k = 0; k < 2; k++) {
081          params.add(new Object[] { values[i], values[j], values[k] });
082        }
083      }
084    }
085    return params;
086  }
087
088  @BeforeClass
089  public static void setUp() throws Exception {
090    TEST_UTIL.startMiniCluster(3);
091    byte[][] splitKeys = new byte[8][];
092    for (int i = 111; i < 999; i += 111) {
093      splitKeys[i / 111 - 1] = Bytes.toBytes(String.format("%03d", i));
094    }
095    Table table = TEST_UTIL.createTable(TABLE_NAME, FAMILY, splitKeys);
096    List<Put> puts = new ArrayList<>();
097    for (int i = 0; i < 1000; i++) {
098      puts.add(new Put(Bytes.toBytes(String.format("%03d", i)))
099        .addColumn(FAMILY, CQ1, Bytes.toBytes(i)).addColumn(FAMILY, CQ2, Bytes.toBytes(i * i)));
100    }
101    TEST_UTIL.waitTableAvailable(TABLE_NAME);
102    table.put(puts);
103  }
104
105  @AfterClass
106  public static void tearDown() throws Exception {
107    TEST_UTIL.shutdownMiniCluster();
108  }
109
110  private Scan createScan() {
111    Scan scan = new Scan();
112    if (batch) {
113      scan.setBatch(1);
114    }
115    if (smallResultSize) {
116      scan.setMaxResultSize(1);
117    }
118    if (allowPartial) {
119      scan.setAllowPartialResults(true);
120    }
121    return scan;
122  }
123
124  private void assertResultEquals(Result result, int i) {
125    assertEquals(String.format("%03d", i), Bytes.toString(result.getRow()));
126    assertEquals(i, Bytes.toInt(result.getValue(FAMILY, CQ1)));
127    assertEquals(i * i, Bytes.toInt(result.getValue(FAMILY, CQ2)));
128  }
129
130  private List<Result> doScan(Scan scan) throws IOException {
131    List<Result> results = new ArrayList<>();
132    try (Table table = TEST_UTIL.getConnection().getTable(TABLE_NAME);
133      ResultScanner scanner = table.getScanner(scan)) {
134      for (Result r; (r = scanner.next()) != null;) {
135        results.add(r);
136      }
137    }
138    return assertAndCreateCompleteResults(results);
139  }
140
141  private List<Result> assertAndCreateCompleteResults(List<Result> results) throws IOException {
142    if ((!batch && !allowPartial) || (allowPartial && !batch && !smallResultSize)) {
143      for (Result result : results) {
144        assertFalse("Should not have partial result", result.mayHaveMoreCellsInRow());
145      }
146      return results;
147    }
148    List<Result> completeResults = new ArrayList<>();
149    List<Result> partialResults = new ArrayList<>();
150    for (Result result : results) {
151      if (!result.mayHaveMoreCellsInRow()) {
152        assertFalse("Should have partial result", partialResults.isEmpty());
153        partialResults.add(result);
154        completeResults.add(Result.createCompleteResult(partialResults));
155        partialResults.clear();
156      } else {
157        partialResults.add(result);
158      }
159    }
160    assertTrue("Should not have orphan partial result", partialResults.isEmpty());
161    return completeResults;
162  }
163
164  private void testScan(int start, boolean startInclusive, int stop, boolean stopInclusive,
165    int limit) throws Exception {
166    Scan scan =
167      createScan().withStartRow(Bytes.toBytes(String.format("%03d", start)), startInclusive)
168        .withStopRow(Bytes.toBytes(String.format("%03d", stop)), stopInclusive);
169    if (limit > 0) {
170      scan.setLimit(limit);
171    }
172    List<Result> results = doScan(scan);
173    int actualStart = startInclusive ? start : start + 1;
174    int actualStop = stopInclusive ? stop + 1 : stop;
175    int count = actualStop - actualStart;
176    if (limit > 0) {
177      count = Math.min(count, limit);
178    }
179    assertEquals(count, results.size());
180    for (int i = 0; i < count; i++) {
181      assertResultEquals(results.get(i), actualStart + i);
182    }
183  }
184
185  private void testReversedScan(int start, boolean startInclusive, int stop, boolean stopInclusive,
186    int limit) throws Exception {
187    Scan scan =
188      createScan().withStartRow(Bytes.toBytes(String.format("%03d", start)), startInclusive)
189        .withStopRow(Bytes.toBytes(String.format("%03d", stop)), stopInclusive).setReversed(true);
190    if (limit > 0) {
191      scan.setLimit(limit);
192    }
193    List<Result> results = doScan(scan);
194    int actualStart = startInclusive ? start : start - 1;
195    int actualStop = stopInclusive ? stop - 1 : stop;
196    int count = actualStart - actualStop;
197    if (limit > 0) {
198      count = Math.min(count, limit);
199    }
200    assertEquals(count, results.size());
201    for (int i = 0; i < count; i++) {
202      assertResultEquals(results.get(i), actualStart - i);
203    }
204  }
205
206  @Test
207  public void testScanWithLimit() throws Exception {
208    testScan(1, true, 998, false, 900); // from first region to last region
209    testScan(123, true, 345, true, 100);
210    testScan(234, true, 456, false, 100);
211    testScan(345, false, 567, true, 100);
212    testScan(456, false, 678, false, 100);
213
214  }
215
216  @Test
217  public void testScanWithLimitGreaterThanActualCount() throws Exception {
218    testScan(1, true, 998, false, 1000); // from first region to last region
219    testScan(123, true, 345, true, 200);
220    testScan(234, true, 456, false, 200);
221    testScan(345, false, 567, true, 200);
222    testScan(456, false, 678, false, 200);
223  }
224
225  @Test
226  public void testReversedScanWithLimit() throws Exception {
227    testReversedScan(998, true, 1, false, 900); // from last region to first region
228    testReversedScan(543, true, 321, true, 100);
229    testReversedScan(654, true, 432, false, 100);
230    testReversedScan(765, false, 543, true, 100);
231    testReversedScan(876, false, 654, false, 100);
232  }
233
234  @Test
235  public void testReversedScanWithLimitGreaterThanActualCount() throws Exception {
236    testReversedScan(998, true, 1, false, 1000); // from last region to first region
237    testReversedScan(543, true, 321, true, 200);
238    testReversedScan(654, true, 432, false, 200);
239    testReversedScan(765, false, 543, true, 200);
240    testReversedScan(876, false, 654, false, 200);
241  }
242
243  @Test
244  public void testStartRowStopRowInclusive() throws Exception {
245    testScan(1, true, 998, false, -1); // from first region to last region
246    testScan(123, true, 345, true, -1);
247    testScan(234, true, 456, false, -1);
248    testScan(345, false, 567, true, -1);
249    testScan(456, false, 678, false, -1);
250  }
251
252  @Test
253  public void testReversedStartRowStopRowInclusive() throws Exception {
254    testReversedScan(998, true, 1, false, -1); // from last region to first region
255    testReversedScan(543, true, 321, true, -1);
256    testReversedScan(654, true, 432, false, -1);
257    testReversedScan(765, false, 543, true, -1);
258    testReversedScan(876, false, 654, false, -1);
259  }
260}