View Javadoc

1   /**
2    * Copyright The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one or more
5    * contributor license agreements. See the NOTICE file distributed with this
6    * work for additional information regarding copyright ownership. The ASF
7    * licenses this file to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   *
11   * http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16   * License for the specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.hadoop.hbase.regionserver;
20  
21  import static org.junit.Assert.assertEquals;
22  import static org.junit.Assert.assertFalse;
23  import static org.junit.Assert.assertTrue;
24  
25  import java.io.IOException;
26  import java.util.ArrayList;
27  import java.util.Collections;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.NavigableSet;
31  import java.util.Random;
32  
33  import org.apache.commons.logging.Log;
34  import org.apache.commons.logging.LogFactory;
35  import org.apache.hadoop.conf.Configuration;
36  import org.apache.hadoop.fs.FileSystem;
37  import org.apache.hadoop.fs.Path;
38  import org.apache.hadoop.hbase.Cell;
39  import org.apache.hadoop.hbase.HBaseConfiguration;
40  import org.apache.hadoop.hbase.HBaseTestingUtility;
41  import org.apache.hadoop.hbase.HConstants;
42  import org.apache.hadoop.hbase.KeyValue;
43  import org.apache.hadoop.hbase.MediumTests;
44  import org.apache.hadoop.hbase.client.Durability;
45  import org.apache.hadoop.hbase.client.Put;
46  import org.apache.hadoop.hbase.client.Result;
47  import org.apache.hadoop.hbase.client.Scan;
48  import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
49  import org.apache.hadoop.hbase.filter.Filter;
50  import org.apache.hadoop.hbase.filter.FilterList;
51  import org.apache.hadoop.hbase.filter.FilterList.Operator;
52  import org.apache.hadoop.hbase.filter.PageFilter;
53  import org.apache.hadoop.hbase.filter.SingleColumnValueFilter;
54  import org.apache.hadoop.hbase.io.hfile.CacheConfig;
55  import org.apache.hadoop.hbase.io.hfile.HFileContext;
56  import org.apache.hadoop.hbase.io.hfile.HFileContextBuilder;
57  import org.apache.hadoop.hbase.util.Bytes;
58  import org.apache.hadoop.hbase.util.Pair;
59  import org.junit.Test;
60  import org.junit.experimental.categories.Category;
61  
62  import com.google.common.collect.Lists;
63  /**
64   * Test cases against ReversibleKeyValueScanner
65   */
66  @Category(MediumTests.class)
67  public class TestReversibleScanners {
68    private static final Log LOG = LogFactory.getLog(TestReversibleScanners.class);
69    HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
70  
71    private static byte[] FAMILYNAME = Bytes.toBytes("testCf");
72    private static long TS = System.currentTimeMillis();
73    private static int MAXMVCC = 7;
74    private static byte[] ROW = Bytes.toBytes("testRow");
75    private static final int ROWSIZE = 200;
76    private static byte[][] ROWS = makeN(ROW, ROWSIZE);
77    private static byte[] QUAL = Bytes.toBytes("testQual");
78    private static final int QUALSIZE = 5;
79    private static byte[][] QUALS = makeN(QUAL, QUALSIZE);
80    private static byte[] VALUE = Bytes.toBytes("testValue");
81    private static final int VALUESIZE = 3;
82    private static byte[][] VALUES = makeN(VALUE, VALUESIZE);
83  
84    @Test
85    public void testReversibleStoreFileScanner() throws IOException {
86      FileSystem fs = TEST_UTIL.getTestFileSystem();
87      Path hfilePath = new Path(new Path(
88          TEST_UTIL.getDataTestDir("testReversibleStoreFileScanner"),
89          "regionname"), "familyname");
90      CacheConfig cacheConf = new CacheConfig(TEST_UTIL.getConfiguration());
91      HFileContextBuilder hcBuilder = new HFileContextBuilder();
92      hcBuilder.withBlockSize(2 * 1024);
93      HFileContext hFileContext = hcBuilder.build();
94      StoreFile.Writer writer = new StoreFile.WriterBuilder(
95          TEST_UTIL.getConfiguration(), cacheConf, fs).withOutputDir(
96          hfilePath).withFileContext(hFileContext).build();
97      writeStoreFile(writer);
98  
99      StoreFile sf = new StoreFile(fs, writer.getPath(),
100         TEST_UTIL.getConfiguration(), cacheConf, BloomType.NONE);
101 
102     List<StoreFileScanner> scanners = StoreFileScanner
103         .getScannersForStoreFiles(Collections.singletonList(sf), false, true,
104             false, Long.MAX_VALUE);
105     StoreFileScanner scanner = scanners.get(0);
106     seekTestOfReversibleKeyValueScanner(scanner);
107     for (int readPoint = 0; readPoint < MAXMVCC; readPoint++) {
108       LOG.info("Setting read point to " + readPoint);
109       scanners = StoreFileScanner.getScannersForStoreFiles(
110           Collections.singletonList(sf), false, true, false, readPoint);
111       seekTestOfReversibleKeyValueScannerWithMVCC(scanners.get(0), readPoint);
112     }
113   }
114 
115   @Test
116   public void testReversibleMemstoreScanner() throws IOException {
117     MemStore memstore = new MemStore();
118     writeMemstore(memstore);
119     List<KeyValueScanner> scanners = memstore.getScanners(Long.MAX_VALUE);
120     seekTestOfReversibleKeyValueScanner(scanners.get(0));
121     for (int readPoint = 0; readPoint < MAXMVCC; readPoint++) {
122       LOG.info("Setting read point to " + readPoint);
123       scanners = memstore.getScanners(readPoint);
124       seekTestOfReversibleKeyValueScannerWithMVCC(scanners.get(0), readPoint);
125     }
126 
127   }
128 
129   @Test
130   public void testReversibleKeyValueHeap() throws IOException {
131     // write data to one memstore and two store files
132     FileSystem fs = TEST_UTIL.getTestFileSystem();
133     Path hfilePath = new Path(new Path(
134         TEST_UTIL.getDataTestDir("testReversibleKeyValueHeap"), "regionname"),
135         "familyname");
136     CacheConfig cacheConf = new CacheConfig(TEST_UTIL.getConfiguration());
137     HFileContextBuilder hcBuilder = new HFileContextBuilder();
138     hcBuilder.withBlockSize(2 * 1024);
139     HFileContext hFileContext = hcBuilder.build();
140     StoreFile.Writer writer1 = new StoreFile.WriterBuilder(
141         TEST_UTIL.getConfiguration(), cacheConf, fs).withOutputDir(
142         hfilePath).withFileContext(hFileContext).build();
143     StoreFile.Writer writer2 = new StoreFile.WriterBuilder(
144         TEST_UTIL.getConfiguration(), cacheConf, fs).withOutputDir(
145         hfilePath).withFileContext(hFileContext).build();
146 
147     MemStore memstore = new MemStore();
148     writeMemstoreAndStoreFiles(memstore, new StoreFile.Writer[] { writer1,
149         writer2 });
150 
151     StoreFile sf1 = new StoreFile(fs, writer1.getPath(),
152         TEST_UTIL.getConfiguration(), cacheConf, BloomType.NONE);
153 
154     StoreFile sf2 = new StoreFile(fs, writer2.getPath(),
155         TEST_UTIL.getConfiguration(), cacheConf, BloomType.NONE);
156     /**
157      * Test without MVCC
158      */
159     int startRowNum = ROWSIZE / 2;
160     ReversedKeyValueHeap kvHeap = getReversibleKeyValueHeap(memstore, sf1, sf2,
161         ROWS[startRowNum], MAXMVCC);
162     internalTestSeekAndNextForReversibleKeyValueHeap(kvHeap, startRowNum);
163 
164     startRowNum = ROWSIZE - 1;
165     kvHeap = getReversibleKeyValueHeap(memstore, sf1, sf2,
166         HConstants.EMPTY_START_ROW, MAXMVCC);
167     internalTestSeekAndNextForReversibleKeyValueHeap(kvHeap, startRowNum);
168 
169     /**
170      * Test with MVCC
171      */
172     for (int readPoint = 0; readPoint < MAXMVCC; readPoint++) {
173       LOG.info("Setting read point to " + readPoint);
174       startRowNum = ROWSIZE - 1;
175       kvHeap = getReversibleKeyValueHeap(memstore, sf1, sf2,
176           HConstants.EMPTY_START_ROW, readPoint);
177       for (int i = startRowNum; i >= 0; i--) {
178         if (i - 2 < 0) break;
179         i = i - 2;
180         kvHeap.seekToPreviousRow(KeyValue.createFirstOnRow(ROWS[i + 1]));
181         Pair<Integer, Integer> nextReadableNum = getNextReadableNumWithBackwardScan(
182             i, 0, readPoint);
183         if (nextReadableNum == null) break;
184         KeyValue expecedKey = makeKV(nextReadableNum.getFirst(),
185             nextReadableNum.getSecond());
186         assertEquals(expecedKey, kvHeap.peek());
187         i = nextReadableNum.getFirst();
188         int qualNum = nextReadableNum.getSecond();
189         if (qualNum + 1 < QUALSIZE) {
190           kvHeap.backwardSeek(makeKV(i, qualNum + 1));
191           nextReadableNum = getNextReadableNumWithBackwardScan(i, qualNum + 1,
192               readPoint);
193           if (nextReadableNum == null) break;
194           expecedKey = makeKV(nextReadableNum.getFirst(),
195               nextReadableNum.getSecond());
196           assertEquals(expecedKey, kvHeap.peek());
197           i = nextReadableNum.getFirst();
198           qualNum = nextReadableNum.getSecond();
199         }
200 
201         kvHeap.next();
202 
203         if (qualNum + 1 >= QUALSIZE) {
204           nextReadableNum = getNextReadableNumWithBackwardScan(i - 1, 0,
205               readPoint);
206         } else {
207           nextReadableNum = getNextReadableNumWithBackwardScan(i, qualNum + 1,
208               readPoint);
209         }
210         if (nextReadableNum == null) break;
211         expecedKey = makeKV(nextReadableNum.getFirst(),
212             nextReadableNum.getSecond());
213         assertEquals(expecedKey, kvHeap.peek());
214         i = nextReadableNum.getFirst();
215       }
216     }
217   }
218 
219   @Test
220   public void testReversibleStoreScanner() throws IOException {
221     // write data to one memstore and two store files
222     FileSystem fs = TEST_UTIL.getTestFileSystem();
223     Path hfilePath = new Path(new Path(
224         TEST_UTIL.getDataTestDir("testReversibleStoreScanner"), "regionname"),
225         "familyname");
226     CacheConfig cacheConf = new CacheConfig(TEST_UTIL.getConfiguration());
227     HFileContextBuilder hcBuilder = new HFileContextBuilder();
228     hcBuilder.withBlockSize(2 * 1024);
229     HFileContext hFileContext = hcBuilder.build();
230     StoreFile.Writer writer1 = new StoreFile.WriterBuilder(
231         TEST_UTIL.getConfiguration(), cacheConf, fs).withOutputDir(
232         hfilePath).withFileContext(hFileContext).build();
233     StoreFile.Writer writer2 = new StoreFile.WriterBuilder(
234         TEST_UTIL.getConfiguration(), cacheConf, fs).withOutputDir(
235         hfilePath).withFileContext(hFileContext).build();
236 
237     MemStore memstore = new MemStore();
238     writeMemstoreAndStoreFiles(memstore, new StoreFile.Writer[] { writer1,
239         writer2 });
240 
241     StoreFile sf1 = new StoreFile(fs, writer1.getPath(),
242         TEST_UTIL.getConfiguration(), cacheConf, BloomType.NONE);
243 
244     StoreFile sf2 = new StoreFile(fs, writer2.getPath(),
245         TEST_UTIL.getConfiguration(), cacheConf, BloomType.NONE);
246 
247     ScanType scanType = ScanType.USER_SCAN;
248     ScanInfo scanInfo = new ScanInfo(FAMILYNAME, 0, Integer.MAX_VALUE,
249         Long.MAX_VALUE, false, 0, KeyValue.COMPARATOR);
250 
251     // Case 1.Test a full reversed scan
252     Scan scan = new Scan();
253     scan.setReversed(true);
254     StoreScanner storeScanner = getReversibleStoreScanner(memstore, sf1, sf2,
255         scan, scanType, scanInfo, MAXMVCC);
256     verifyCountAndOrder(storeScanner, QUALSIZE * ROWSIZE, ROWSIZE, false);
257 
258     // Case 2.Test reversed scan with a specified start row
259     int startRowNum = ROWSIZE / 2;
260     byte[] startRow = ROWS[startRowNum];
261     scan.setStartRow(startRow);
262     storeScanner = getReversibleStoreScanner(memstore, sf1, sf2, scan,
263         scanType, scanInfo, MAXMVCC);
264     verifyCountAndOrder(storeScanner, QUALSIZE * (startRowNum + 1),
265         startRowNum + 1, false);
266 
267     // Case 3.Test reversed scan with a specified start row and specified
268     // qualifiers
269     assertTrue(QUALSIZE > 2);
270     scan.addColumn(FAMILYNAME, QUALS[0]);
271     scan.addColumn(FAMILYNAME, QUALS[2]);
272     storeScanner = getReversibleStoreScanner(memstore, sf1, sf2, scan,
273         scanType, scanInfo, MAXMVCC);
274     verifyCountAndOrder(storeScanner, 2 * (startRowNum + 1), startRowNum + 1,
275         false);
276 
277     // Case 4.Test reversed scan with mvcc based on case 3
278     for (int readPoint = 0; readPoint < MAXMVCC; readPoint++) {
279       LOG.info("Setting read point to " + readPoint);
280       storeScanner = getReversibleStoreScanner(memstore, sf1, sf2, scan,
281           scanType, scanInfo, readPoint);
282       int expectedRowCount = 0;
283       int expectedKVCount = 0;
284       for (int i = startRowNum; i >= 0; i--) {
285         int kvCount = 0;
286         if (makeMVCC(i, 0) <= readPoint) {
287           kvCount++;
288         }
289         if (makeMVCC(i, 2) <= readPoint) {
290           kvCount++;
291         }
292         if (kvCount > 0) {
293           expectedRowCount++;
294           expectedKVCount += kvCount;
295         }
296       }
297       verifyCountAndOrder(storeScanner, expectedKVCount, expectedRowCount,
298           false);
299     }
300   }
301 
302   @Test
303   public void testReversibleRegionScanner() throws IOException {
304     byte[] tableName = Bytes.toBytes("testtable");
305     byte[] FAMILYNAME2 = Bytes.toBytes("testCf2");
306     Configuration conf = HBaseConfiguration.create();
307     HRegion region = TEST_UTIL.createLocalHRegion(tableName, null, null,
308         "testReversibleRegionScanner", conf, false, Durability.SYNC_WAL, null,
309         FAMILYNAME, FAMILYNAME2);
310     loadDataToRegion(region, FAMILYNAME2);
311 
312     // verify row count with forward scan
313     Scan scan = new Scan();
314     InternalScanner scanner = region.getScanner(scan);
315     verifyCountAndOrder(scanner, ROWSIZE * QUALSIZE * 2, ROWSIZE, true);
316 
317     // Case1:Full reversed scan
318     scan.setReversed(true);
319     scanner = region.getScanner(scan);
320     verifyCountAndOrder(scanner, ROWSIZE * QUALSIZE * 2, ROWSIZE, false);
321 
322     // Case2:Full reversed scan with one family
323     scan = new Scan();
324     scan.setReversed(true);
325     scan.addFamily(FAMILYNAME);
326     scanner = region.getScanner(scan);
327     verifyCountAndOrder(scanner, ROWSIZE * QUALSIZE, ROWSIZE, false);
328 
329     // Case3:Specify qualifiers + One family
330     byte[][] specifiedQualifiers = { QUALS[1], QUALS[2] };
331     for (byte[] specifiedQualifier : specifiedQualifiers)
332       scan.addColumn(FAMILYNAME, specifiedQualifier);
333     scanner = region.getScanner(scan);
334     verifyCountAndOrder(scanner, ROWSIZE * 2, ROWSIZE, false);
335 
336     // Case4:Specify qualifiers + Two families
337     for (byte[] specifiedQualifier : specifiedQualifiers)
338       scan.addColumn(FAMILYNAME2, specifiedQualifier);
339     scanner = region.getScanner(scan);
340     verifyCountAndOrder(scanner, ROWSIZE * 2 * 2, ROWSIZE, false);
341 
342     // Case5: Case4 + specify start row
343     int startRowNum = ROWSIZE * 3 / 4;
344     scan.setStartRow(ROWS[startRowNum]);
345     scanner = region.getScanner(scan);
346     verifyCountAndOrder(scanner, (startRowNum + 1) * 2 * 2, (startRowNum + 1),
347         false);
348 
349     // Case6: Case4 + specify stop row
350     int stopRowNum = ROWSIZE / 4;
351     scan.setStartRow(HConstants.EMPTY_BYTE_ARRAY);
352     scan.setStopRow(ROWS[stopRowNum]);
353     scanner = region.getScanner(scan);
354     verifyCountAndOrder(scanner, (ROWSIZE - stopRowNum - 1) * 2 * 2, (ROWSIZE
355         - stopRowNum - 1), false);
356 
357     // Case7: Case4 + specify start row + specify stop row
358     scan.setStartRow(ROWS[startRowNum]);
359     scanner = region.getScanner(scan);
360     verifyCountAndOrder(scanner, (startRowNum - stopRowNum) * 2 * 2,
361         (startRowNum - stopRowNum), false);
362 
363     // Case8: Case7 + SingleColumnValueFilter
364     int valueNum = startRowNum % VALUESIZE;
365     Filter filter = new SingleColumnValueFilter(FAMILYNAME,
366         specifiedQualifiers[0], CompareOp.EQUAL, VALUES[valueNum]);
367     scan.setFilter(filter);
368     scanner = region.getScanner(scan);
369     int unfilteredRowNum = (startRowNum - stopRowNum) / VALUESIZE
370         + (stopRowNum / VALUESIZE == valueNum ? 0 : 1);
371     verifyCountAndOrder(scanner, unfilteredRowNum * 2 * 2, unfilteredRowNum,
372         false);
373 
374     // Case9: Case7 + PageFilter
375     int pageSize = 10;
376     filter = new PageFilter(pageSize);
377     scan.setFilter(filter);
378     scanner = region.getScanner(scan);
379     int expectedRowNum = pageSize;
380     verifyCountAndOrder(scanner, expectedRowNum * 2 * 2, expectedRowNum, false);
381 
382     // Case10: Case7 + FilterList+MUST_PASS_ONE
383     SingleColumnValueFilter scvFilter1 = new SingleColumnValueFilter(
384         FAMILYNAME, specifiedQualifiers[0], CompareOp.EQUAL, VALUES[0]);
385     SingleColumnValueFilter scvFilter2 = new SingleColumnValueFilter(
386         FAMILYNAME, specifiedQualifiers[0], CompareOp.EQUAL, VALUES[1]);
387     expectedRowNum = 0;
388     for (int i = startRowNum; i > stopRowNum; i--) {
389       if (i % VALUESIZE == 0 || i % VALUESIZE == 1) {
390         expectedRowNum++;
391       }
392     }
393     filter = new FilterList(Operator.MUST_PASS_ONE, scvFilter1, scvFilter2);
394     scan.setFilter(filter);
395     scanner = region.getScanner(scan);
396     verifyCountAndOrder(scanner, expectedRowNum * 2 * 2, expectedRowNum, false);
397 
398     // Case10: Case7 + FilterList+MUST_PASS_ALL
399     filter = new FilterList(Operator.MUST_PASS_ALL, scvFilter1, scvFilter2);
400     expectedRowNum = 0;
401     scan.setFilter(filter);
402     scanner = region.getScanner(scan);
403     verifyCountAndOrder(scanner, expectedRowNum * 2 * 2, expectedRowNum, false);
404   }
405 
406   private StoreScanner getReversibleStoreScanner(MemStore memstore,
407       StoreFile sf1, StoreFile sf2, Scan scan, ScanType scanType,
408       ScanInfo scanInfo, int readPoint) throws IOException {
409     List<KeyValueScanner> scanners = getScanners(memstore, sf1, sf2, null,
410         false, readPoint);
411     NavigableSet<byte[]> columns = null;
412     for (Map.Entry<byte[], NavigableSet<byte[]>> entry : scan.getFamilyMap()
413         .entrySet()) {
414       // Should only one family
415       columns = entry.getValue();
416     }
417     StoreScanner storeScanner = new ReversedStoreScanner(scan, scanInfo,
418         scanType, columns, scanners);
419     return storeScanner;
420   }
421 
422   private void verifyCountAndOrder(InternalScanner scanner,
423       int expectedKVCount, int expectedRowCount, boolean forward)
424       throws IOException {
425     List<Cell> kvList = new ArrayList<Cell>();
426     Result lastResult = null;
427     int rowCount = 0;
428     int kvCount = 0;
429     try {
430       while (scanner.next(kvList)) {
431         if (kvList.isEmpty()) continue;
432         rowCount++;
433         kvCount += kvList.size();
434         if (lastResult != null) {
435           Result curResult = Result.create(kvList);
436           assertEquals("LastResult:" + lastResult + "CurResult:" + curResult,
437               forward,
438               Bytes.compareTo(curResult.getRow(), lastResult.getRow()) > 0);
439         }
440         lastResult = Result.create(kvList);
441         kvList.clear();
442       }
443     } finally {
444       scanner.close();
445     }
446     if (!kvList.isEmpty()) {
447       rowCount++;
448       kvCount += kvList.size();
449       kvList.clear();
450     }
451     assertEquals(expectedKVCount, kvCount);
452     assertEquals(expectedRowCount, rowCount);
453   }
454 
455   private void internalTestSeekAndNextForReversibleKeyValueHeap(
456       ReversedKeyValueHeap kvHeap, int startRowNum) throws IOException {
457     // Test next and seek
458     for (int i = startRowNum; i >= 0; i--) {
459       if (i % 2 == 1 && i - 2 >= 0) {
460         i = i - 2;
461         kvHeap.seekToPreviousRow(KeyValue.createFirstOnRow(ROWS[i + 1]));
462       }
463       for (int j = 0; j < QUALSIZE; j++) {
464         if (j % 2 == 1 && (j + 1) < QUALSIZE) {
465           j = j + 1;
466           kvHeap.backwardSeek(makeKV(i, j));
467         }
468         assertEquals(makeKV(i, j), kvHeap.peek());
469         kvHeap.next();
470       }
471     }
472     assertEquals(null, kvHeap.peek());
473   }
474 
475   private ReversedKeyValueHeap getReversibleKeyValueHeap(MemStore memstore,
476       StoreFile sf1, StoreFile sf2, byte[] startRow, int readPoint)
477       throws IOException {
478     List<KeyValueScanner> scanners = getScanners(memstore, sf1, sf2, startRow,
479         true, readPoint);
480     ReversedKeyValueHeap kvHeap = new ReversedKeyValueHeap(scanners,
481         KeyValue.COMPARATOR);
482     return kvHeap;
483   }
484 
485   private List<KeyValueScanner> getScanners(MemStore memstore, StoreFile sf1,
486       StoreFile sf2, byte[] startRow, boolean doSeek, int readPoint)
487       throws IOException {
488     List<StoreFileScanner> fileScanners = StoreFileScanner
489         .getScannersForStoreFiles(Lists.newArrayList(sf1, sf2), false, true,
490             false, readPoint);
491     List<KeyValueScanner> memScanners = memstore.getScanners(readPoint);
492     List<KeyValueScanner> scanners = new ArrayList<KeyValueScanner>(
493         fileScanners.size() + 1);
494     scanners.addAll(fileScanners);
495     scanners.addAll(memScanners);
496 
497     if (doSeek) {
498       if (Bytes.equals(HConstants.EMPTY_START_ROW, startRow)) {
499         for (KeyValueScanner scanner : scanners) {
500           scanner.seekToLastRow();
501         }
502       } else {
503         KeyValue startKey = KeyValue.createFirstOnRow(startRow);
504         for (KeyValueScanner scanner : scanners) {
505           scanner.backwardSeek(startKey);
506         }
507       }
508     }
509     return scanners;
510   }
511 
512   private void seekTestOfReversibleKeyValueScanner(KeyValueScanner scanner)
513       throws IOException {
514     /**
515      * Test without MVCC
516      */
517     // Test seek to last row
518     assertTrue(scanner.seekToLastRow());
519     assertEquals(makeKV(ROWSIZE - 1, 0), scanner.peek());
520 
521     // Test backward seek in three cases
522     // Case1: seek in the same row in backwardSeek
523     KeyValue seekKey = makeKV(ROWSIZE - 2, QUALSIZE - 2);
524     assertTrue(scanner.backwardSeek(seekKey));
525     assertEquals(seekKey, scanner.peek());
526 
527     // Case2: seek to the previous row in backwardSeek
528     int seekRowNum = ROWSIZE - 2;
529     assertTrue(scanner.backwardSeek(KeyValue.createLastOnRow(ROWS[seekRowNum])));
530     KeyValue expectedKey = makeKV(seekRowNum - 1, 0);
531     assertEquals(expectedKey, scanner.peek());
532 
533     // Case3: unable to backward seek
534     assertFalse(scanner.backwardSeek(KeyValue.createLastOnRow(ROWS[0])));
535     assertEquals(null, scanner.peek());
536 
537     // Test seek to previous row
538     seekRowNum = ROWSIZE - 4;
539     assertTrue(scanner.seekToPreviousRow(KeyValue
540         .createFirstOnRow(ROWS[seekRowNum])));
541     expectedKey = makeKV(seekRowNum - 1, 0);
542     assertEquals(expectedKey, scanner.peek());
543 
544     // Test seek to previous row for the first row
545     assertFalse(scanner.seekToPreviousRow(makeKV(0, 0)));
546     assertEquals(null, scanner.peek());
547 
548   }
549 
550   private void seekTestOfReversibleKeyValueScannerWithMVCC(
551       KeyValueScanner scanner, int readPoint) throws IOException {
552     /**
553      * Test with MVCC
554      */
555       // Test seek to last row
556       KeyValue expectedKey = getNextReadableKeyValueWithBackwardScan(
557           ROWSIZE - 1, 0, readPoint);
558       assertEquals(expectedKey != null, scanner.seekToLastRow());
559       assertEquals(expectedKey, scanner.peek());
560 
561       // Test backward seek in two cases
562       // Case1: seek in the same row in backwardSeek
563       expectedKey = getNextReadableKeyValueWithBackwardScan(ROWSIZE - 2,
564           QUALSIZE - 2, readPoint);
565       assertEquals(expectedKey != null, scanner.backwardSeek(expectedKey));
566       assertEquals(expectedKey, scanner.peek());
567 
568       // Case2: seek to the previous row in backwardSeek
569     int seekRowNum = ROWSIZE - 3;
570     KeyValue seekKey = KeyValue.createLastOnRow(ROWS[seekRowNum]);
571       expectedKey = getNextReadableKeyValueWithBackwardScan(seekRowNum - 1, 0,
572           readPoint);
573       assertEquals(expectedKey != null, scanner.backwardSeek(seekKey));
574       assertEquals(expectedKey, scanner.peek());
575 
576       // Test seek to previous row
577       seekRowNum = ROWSIZE - 4;
578       expectedKey = getNextReadableKeyValueWithBackwardScan(seekRowNum - 1, 0,
579           readPoint);
580       assertEquals(expectedKey != null, scanner.seekToPreviousRow(KeyValue
581           .createFirstOnRow(ROWS[seekRowNum])));
582       assertEquals(expectedKey, scanner.peek());
583   }
584 
585   private KeyValue getNextReadableKeyValueWithBackwardScan(int startRowNum,
586       int startQualNum, int readPoint) {
587     Pair<Integer, Integer> nextReadableNum = getNextReadableNumWithBackwardScan(
588         startRowNum, startQualNum, readPoint);
589     if (nextReadableNum == null)
590       return null;
591     return makeKV(nextReadableNum.getFirst(), nextReadableNum.getSecond());
592   }
593 
594   private Pair<Integer, Integer> getNextReadableNumWithBackwardScan(
595       int startRowNum, int startQualNum, int readPoint) {
596     Pair<Integer, Integer> nextReadableNum = null;
597     boolean findExpected = false;
598     for (int i = startRowNum; i >= 0; i--) {
599       for (int j = (i == startRowNum ? startQualNum : 0); j < QUALSIZE; j++) {
600         if (makeMVCC(i, j) <= readPoint) {
601           nextReadableNum = new Pair<Integer, Integer>(i, j);
602           findExpected = true;
603           break;
604         }
605       }
606       if (findExpected)
607         break;
608     }
609     return nextReadableNum;
610   }
611 
612   private static void loadDataToRegion(HRegion region, byte[] additionalFamily)
613       throws IOException {
614     for (int i = 0; i < ROWSIZE; i++) {
615       Put put = new Put(ROWS[i]);
616       for (int j = 0; j < QUALSIZE; j++) {
617         put.add(makeKV(i, j));
618         // put additional family
619         put.add(makeKV(i, j, additionalFamily));
620       }
621       region.put(put);
622       if (i == ROWSIZE / 3 || i == ROWSIZE * 2 / 3) {
623         region.flushcache();
624       }
625     }
626   }
627 
628   private static void writeMemstoreAndStoreFiles(MemStore memstore,
629       final StoreFile.Writer[] writers) throws IOException {
630     Random rand = new Random();
631     try {
632       for (int i = 0; i < ROWSIZE; i++) {
633         for (int j = 0; j < QUALSIZE; j++) {
634           if (i % 2 == 0) {
635             memstore.add(makeKV(i, j));
636           } else {
637             writers[(i + j) % writers.length].append(makeKV(i, j));
638           }
639         }
640       }
641     } finally {
642       for (int i = 0; i < writers.length; i++) {
643         writers[i].close();
644       }
645     }
646   }
647 
648   private static void writeStoreFile(final StoreFile.Writer writer)
649       throws IOException {
650     try {
651       for (int i = 0; i < ROWSIZE; i++) {
652         for (int j = 0; j < QUALSIZE; j++) {
653           writer.append(makeKV(i, j));
654         }
655       }
656     } finally {
657       writer.close();
658     }
659   }
660 
661   private static void writeMemstore(MemStore memstore) throws IOException {
662     // Add half of the keyvalues to memstore
663     for (int i = 0; i < ROWSIZE; i++) {
664       for (int j = 0; j < QUALSIZE; j++) {
665         if ((i + j) % 2 == 0) {
666           memstore.add(makeKV(i, j));
667         }
668       }
669     }
670     memstore.snapshot();
671     // Add another half of the keyvalues to snapshot
672     for (int i = 0; i < ROWSIZE; i++) {
673       for (int j = 0; j < QUALSIZE; j++) {
674         if ((i + j) % 2 == 1) {
675           memstore.add(makeKV(i, j));
676         }
677       }
678     }
679   }
680 
681   private static KeyValue makeKV(int rowNum, int cqNum) {
682     return makeKV(rowNum, cqNum, FAMILYNAME);
683   }
684 
685   private static KeyValue makeKV(int rowNum, int cqNum, byte[] familyName) {
686     KeyValue kv = new KeyValue(ROWS[rowNum], familyName, QUALS[cqNum], TS,
687         VALUES[rowNum % VALUESIZE]);
688     kv.setMvccVersion(makeMVCC(rowNum, cqNum));
689     return kv;
690   }
691 
692   private static long makeMVCC(int rowNum, int cqNum) {
693     return (rowNum + cqNum) % (MAXMVCC + 1);
694   }
695 
696   private static byte[][] makeN(byte[] base, int n) {
697     byte[][] ret = new byte[n][];
698     for (int i = 0; i < n; i++) {
699       ret[i] = Bytes.add(base, Bytes.toBytes(String.format("%04d", i)));
700     }
701     return ret;
702   }
703 }