View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.regionserver;
19  
20  import static org.apache.hadoop.hbase.HBaseTestingUtility.COLUMNS;
21  import static org.junit.Assert.assertArrayEquals;
22  import static org.junit.Assert.assertEquals;
23  import static org.junit.Assert.assertFalse;
24  import static org.junit.Assert.assertTrue;
25  import static org.junit.Assert.fail;
26  
27  import java.io.IOException;
28  import java.util.ArrayList;
29  import java.util.List;
30  
31  import org.apache.hadoop.hbase.Cell;
32  import org.apache.hadoop.hbase.CellUtil;
33  import org.apache.hadoop.hbase.HBaseTestingUtility;
34  import org.apache.hadoop.hbase.HConstants;
35  import org.apache.hadoop.hbase.HTableDescriptor;
36  import org.apache.hadoop.hbase.KeepDeletedCells;
37  import org.apache.hadoop.hbase.client.Delete;
38  import org.apache.hadoop.hbase.client.Get;
39  import org.apache.hadoop.hbase.client.Put;
40  import org.apache.hadoop.hbase.client.Result;
41  import org.apache.hadoop.hbase.client.Scan;
42  import org.apache.hadoop.hbase.testclassification.SmallTests;
43  import org.apache.hadoop.hbase.util.Bytes;
44  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
45  import org.apache.hadoop.hbase.util.EnvironmentEdgeManagerTestHelper;
46  import org.apache.hadoop.hbase.util.IncrementingEnvironmentEdge;
47  import org.junit.After;
48  import org.junit.Before;
49  import org.junit.Rule;
50  import org.junit.Test;
51  import org.junit.experimental.categories.Category;
52  import org.junit.rules.TestName;
53  
54  @Category(SmallTests.class)
55  public class TestKeepDeletes {
56    HBaseTestingUtility hbu = HBaseTestingUtility.createLocalHTU();
57    private final byte[] T0 = Bytes.toBytes("0");
58    private final byte[] T1 = Bytes.toBytes("1");
59    private final byte[] T2 = Bytes.toBytes("2");
60    private final byte[] T3 = Bytes.toBytes("3");
61    private final byte[] T4 = Bytes.toBytes("4");
62    private final byte[] T5 = Bytes.toBytes("5");
63    private final byte[] T6 = Bytes.toBytes("6");
64  
65    private final byte[] c0 = COLUMNS[0];
66    private final byte[] c1 = COLUMNS[1];
67  
68    @Rule public TestName name = new TestName();
69    
70    @Before
71    public void setUp() throws Exception {
72      /* HBASE-6832: [WINDOWS] Tests should use explicit timestamp for Puts, and not rely on
73       * implicit RS timing.
74       * Use an explicit timer (IncrementingEnvironmentEdge) so that the put, delete
75       * compact timestamps are tracked. Otherwise, forced major compaction will not purge
76       * Delete's having the same timestamp. see ScanQueryMatcher.match():
77       * if (retainDeletesInOutput
78       *     || (!isUserScan && (EnvironmentEdgeManager.currentTime() - timestamp)
79       *     <= timeToPurgeDeletes) ... )
80       *
81       */
82      EnvironmentEdgeManagerTestHelper.injectEdge(new IncrementingEnvironmentEdge());
83    }
84  
85    @After
86    public void tearDown() throws Exception {
87      EnvironmentEdgeManager.reset();
88    }
89  
90    /**
91     * Make sure that deleted rows are retained.
92     * Family delete markers are deleted.
93     * Column Delete markers are versioned
94     * Time range scan of deleted rows are possible
95     */
96    @Test
97    public void testBasicScenario() throws Exception {
98      // keep 3 versions, rows do not expire
99      HTableDescriptor htd = hbu.createTableDescriptor(name.getMethodName(), 0, 3,
100         HConstants.FOREVER, KeepDeletedCells.TRUE);
101     HRegion region = hbu.createLocalHRegion(htd, null, null);
102 
103     long ts = EnvironmentEdgeManager.currentTime();
104     Put p = new Put(T1, ts);
105     p.add(c0, c0, T1);
106     region.put(p);
107     p = new Put(T1, ts+1);
108     p.add(c0, c0, T2);
109     region.put(p);
110     p = new Put(T1, ts+2);
111     p.add(c0, c0, T3);
112     region.put(p);
113     p = new Put(T1, ts+4);
114     p.add(c0, c0, T4);
115     region.put(p);
116 
117     // now place a delete marker at ts+2
118     Delete d = new Delete(T1, ts+2);
119     region.delete(d);
120 
121     // a raw scan can see the delete markers
122     // (one for each column family)
123     assertEquals(3, countDeleteMarkers(region));
124 
125     // get something *before* the delete marker
126     Get g = new Get(T1);
127     g.setMaxVersions();
128     g.setTimeRange(0L, ts+2);
129     Result r = region.get(g);
130     checkResult(r, c0, c0, T2,T1);
131 
132     // flush
133     region.flush(true);
134 
135     // yep, T2 still there, T1 gone
136     r = region.get(g);
137     checkResult(r, c0, c0, T2);
138 
139     // major compact
140     region.compact(true);
141     region.compact(true);
142 
143     // one delete marker left (the others did not
144     // have older puts)
145     assertEquals(1, countDeleteMarkers(region));
146 
147     // still there (even after multiple compactions)
148     r = region.get(g);
149     checkResult(r, c0, c0, T2);
150 
151     // a timerange that includes the delete marker won't see past rows
152     g.setTimeRange(0L, ts+4);
153     r = region.get(g);
154     assertTrue(r.isEmpty());
155 
156     // two more puts, this will expire the older puts.
157     p = new Put(T1, ts+5);
158     p.add(c0, c0, T5);
159     region.put(p);
160     p = new Put(T1, ts+6);
161     p.add(c0, c0, T6);
162     region.put(p);
163 
164     // also add an old put again
165     // (which is past the max versions)
166     p = new Put(T1, ts);
167     p.add(c0, c0, T1);
168     region.put(p);
169     r = region.get(g);
170     assertTrue(r.isEmpty());
171 
172     region.flush(true);
173     region.compact(true);
174     region.compact(true);
175 
176     // verify that the delete marker itself was collected
177     region.put(p);
178     r = region.get(g);
179     checkResult(r, c0, c0, T1);
180     assertEquals(0, countDeleteMarkers(region));
181 
182     HRegion.closeHRegion(region);
183   }
184 
185   /**
186    * Even when the store does not keep deletes a "raw" scan will
187    * return everything it can find (unless discarding cells is guaranteed
188    * to have no effect).
189    * Assuming this the desired behavior. Could also disallow "raw" scanning
190    * if the store does not have KEEP_DELETED_CELLS enabled.
191    * (can be changed easily)
192    */
193   @Test
194   public void testRawScanWithoutKeepingDeletes() throws Exception {
195     // KEEP_DELETED_CELLS is NOT enabled
196     HTableDescriptor htd = hbu.createTableDescriptor(name.getMethodName(), 0, 3,
197         HConstants.FOREVER, KeepDeletedCells.FALSE);
198     HRegion region = hbu.createLocalHRegion(htd, null, null);
199 
200     long ts = EnvironmentEdgeManager.currentTime();
201     Put p = new Put(T1, ts);
202     p.add(c0, c0, T1);
203     region.put(p);
204 
205     Delete d = new Delete(T1, ts);
206     d.deleteColumn(c0, c0, ts);
207     region.delete(d);
208 
209     // scan still returns delete markers and deletes rows
210     Scan s = new Scan();
211     s.setRaw(true);
212     s.setMaxVersions();
213     InternalScanner scan = region.getScanner(s);
214     List<Cell> kvs = new ArrayList<Cell>();
215     scan.next(kvs);
216     assertEquals(2, kvs.size());
217 
218     region.flush(true);
219     region.compact(true);
220 
221     // after compaction they are gone
222     // (note that this a test with a Store without
223     //  KEEP_DELETED_CELLS)
224     s = new Scan();
225     s.setRaw(true);
226     s.setMaxVersions();
227     scan = region.getScanner(s);
228     kvs = new ArrayList<Cell>();
229     scan.next(kvs);
230     assertTrue(kvs.isEmpty());
231 
232     HRegion.closeHRegion(region);
233   }
234 
235   /**
236    * basic verification of existing behavior
237    */
238   @Test
239   public void testWithoutKeepingDeletes() throws Exception {
240     // KEEP_DELETED_CELLS is NOT enabled
241     HTableDescriptor htd = hbu.createTableDescriptor(name.getMethodName(), 0, 3,
242         HConstants.FOREVER, KeepDeletedCells.FALSE);
243     HRegion region = hbu.createLocalHRegion(htd, null, null);
244 
245     long ts = EnvironmentEdgeManager.currentTime();
246     Put p = new Put(T1, ts);
247     p.add(c0, c0, T1);
248     region.put(p);
249 
250     Get gOne = new Get(T1);
251     gOne.setMaxVersions();
252     gOne.setTimeRange(0L, ts + 1);
253     Result rOne = region.get(gOne);
254     assertFalse(rOne.isEmpty());
255 
256 
257     Delete d = new Delete(T1, ts+2);
258     d.deleteColumn(c0, c0, ts);
259     region.delete(d);
260 
261     // "past" get does not see rows behind delete marker
262     Get g = new Get(T1);
263     g.setMaxVersions();
264     g.setTimeRange(0L, ts+1);
265     Result r = region.get(g);
266     assertTrue(r.isEmpty());
267 
268     // "past" scan does not see rows behind delete marker
269     Scan s = new Scan();
270     s.setMaxVersions();
271     s.setTimeRange(0L, ts+1);
272     InternalScanner scanner = region.getScanner(s);
273     List<Cell> kvs = new ArrayList<Cell>();
274     while (scanner.next(kvs))
275       ;
276     assertTrue(kvs.isEmpty());
277 
278     // flushing and minor compaction keep delete markers
279     region.flush(true);
280     region.compact(false);
281     assertEquals(1, countDeleteMarkers(region));
282     region.compact(true);
283     // major compaction deleted it
284     assertEquals(0, countDeleteMarkers(region));
285 
286     HRegion.closeHRegion(region);
287   }
288 
289   /**
290    * The ExplicitColumnTracker does not support "raw" scanning.
291    */
292   @Test
293   public void testRawScanWithColumns() throws Exception {
294     HTableDescriptor htd = hbu.createTableDescriptor(name.getMethodName(), 0, 3,
295         HConstants.FOREVER, KeepDeletedCells.TRUE);
296     HRegion region = hbu.createLocalHRegion(htd, null, null);
297 
298     Scan s = new Scan();
299     s.setRaw(true);
300     s.setMaxVersions();
301     s.addColumn(c0, c0);
302 
303     try {
304       region.getScanner(s);
305       fail("raw scanner with columns should have failed");
306     } catch (org.apache.hadoop.hbase.DoNotRetryIOException dnre) {
307       // ok!
308     }
309 
310     HRegion.closeHRegion(region);
311   }
312 
313   /**
314    * Verify that "raw" scanning mode return delete markers and deletes rows.
315    */
316   @Test
317   public void testRawScan() throws Exception {
318     HTableDescriptor htd = hbu.createTableDescriptor(name.getMethodName(), 0, 3,
319         HConstants.FOREVER, KeepDeletedCells.TRUE);
320     HRegion region = hbu.createLocalHRegion(htd, null, null);
321 
322     long ts = EnvironmentEdgeManager.currentTime();
323     Put p = new Put(T1, ts);
324     p.add(c0, c0, T1);
325     region.put(p);
326     p = new Put(T1, ts+2);
327     p.add(c0, c0, T2);
328     region.put(p);
329     p = new Put(T1, ts+4);
330     p.add(c0, c0, T3);
331     region.put(p);
332 
333     Delete d = new Delete(T1, ts+1);
334     region.delete(d);
335 
336     d = new Delete(T1, ts+2);
337     d.deleteColumn(c0, c0, ts+2);
338     region.delete(d);
339 
340     d = new Delete(T1, ts+3);
341     d.deleteColumns(c0, c0, ts+3);
342     region.delete(d);
343 
344     Scan s = new Scan();
345     s.setRaw(true);
346     s.setMaxVersions();
347     InternalScanner scan = region.getScanner(s);
348     List<Cell> kvs = new ArrayList<Cell>();
349     scan.next(kvs);
350     assertEquals(8, kvs.size());
351     assertTrue(CellUtil.isDeleteFamily(kvs.get(0)));
352     assertArrayEquals(CellUtil.cloneValue(kvs.get(1)), T3);
353     assertTrue(CellUtil.isDelete(kvs.get(2)));
354     assertTrue(CellUtil.isDelete(kvs.get(3))); // .isDeleteType());
355     assertArrayEquals(CellUtil.cloneValue(kvs.get(4)), T2);
356     assertArrayEquals(CellUtil.cloneValue(kvs.get(5)), T1);
357     // we have 3 CFs, so there are two more delete markers
358     assertTrue(CellUtil.isDeleteFamily(kvs.get(6)));
359     assertTrue(CellUtil.isDeleteFamily(kvs.get(7)));
360 
361     // verify that raw scans honor the passed timerange
362     s = new Scan();
363     s.setRaw(true);
364     s.setMaxVersions();
365     s.setTimeRange(0, 1);
366     scan = region.getScanner(s);
367     kvs = new ArrayList<Cell>();
368     scan.next(kvs);
369     // nothing in this interval, not even delete markers
370     assertTrue(kvs.isEmpty());
371 
372     // filter new delete markers
373     s = new Scan();
374     s.setRaw(true);
375     s.setMaxVersions();
376     s.setTimeRange(0, ts+2);
377     scan = region.getScanner(s);
378     kvs = new ArrayList<Cell>();
379     scan.next(kvs);
380     assertEquals(4, kvs.size());
381     assertTrue(CellUtil.isDeleteFamily(kvs.get(0)));
382     assertArrayEquals(CellUtil.cloneValue(kvs.get(1)), T1);
383     // we have 3 CFs
384     assertTrue(CellUtil.isDeleteFamily(kvs.get(2)));
385     assertTrue(CellUtil.isDeleteFamily(kvs.get(3)));
386 
387     // filter old delete markers
388     s = new Scan();
389     s.setRaw(true);
390     s.setMaxVersions();
391     s.setTimeRange(ts+3, ts+5);
392     scan = region.getScanner(s);
393     kvs = new ArrayList<Cell>();
394     scan.next(kvs);
395     assertEquals(2, kvs.size());
396     assertArrayEquals(CellUtil.cloneValue(kvs.get(0)), T3);
397     assertTrue(CellUtil.isDelete(kvs.get(1)));
398 
399 
400     HRegion.closeHRegion(region);
401   }
402 
403   /**
404    * Verify that delete markers are removed from an otherwise empty store.
405    */
406   @Test
407   public void testDeleteMarkerExpirationEmptyStore() throws Exception {
408     HTableDescriptor htd = hbu.createTableDescriptor(name.getMethodName(), 0, 1,
409         HConstants.FOREVER, KeepDeletedCells.TRUE);
410     HRegion region = hbu.createLocalHRegion(htd, null, null);
411 
412     long ts = EnvironmentEdgeManager.currentTime();
413 
414     Delete d = new Delete(T1, ts);
415     d.deleteColumns(c0, c0, ts);
416     region.delete(d);
417 
418     d = new Delete(T1, ts);
419     d.deleteFamily(c0);
420     region.delete(d);
421 
422     d = new Delete(T1, ts);
423     d.deleteColumn(c0, c0, ts+1);
424     region.delete(d);
425 
426     d = new Delete(T1, ts);
427     d.deleteColumn(c0, c0, ts+2);
428     region.delete(d);
429 
430     // 1 family marker, 1 column marker, 2 version markers
431     assertEquals(4, countDeleteMarkers(region));
432 
433     // neither flush nor minor compaction removes any marker
434     region.flush(true);
435     assertEquals(4, countDeleteMarkers(region));
436     region.compact(false);
437     assertEquals(4, countDeleteMarkers(region));
438 
439     // major compaction removes all, since there are no puts they affect
440     region.compact(true);
441     assertEquals(0, countDeleteMarkers(region));
442 
443     HRegion.closeHRegion(region);
444   }
445 
446   /**
447    * Test delete marker removal from store files.
448    */
449   @Test
450   public void testDeleteMarkerExpiration() throws Exception {
451     HTableDescriptor htd = hbu.createTableDescriptor(name.getMethodName(), 0, 1,
452         HConstants.FOREVER, KeepDeletedCells.TRUE);
453     HRegion region = hbu.createLocalHRegion(htd, null, null);
454 
455     long ts = EnvironmentEdgeManager.currentTime();
456 
457     Put p = new Put(T1, ts);
458     p.add(c0, c0, T1);
459     region.put(p);
460 
461     // a put into another store (CF) should have no effect
462     p = new Put(T1, ts-10);
463     p.add(c1, c0, T1);
464     region.put(p);
465 
466     // all the following deletes affect the put
467     Delete d = new Delete(T1, ts);
468     d.deleteColumns(c0, c0, ts);
469     region.delete(d);
470 
471     d = new Delete(T1, ts);
472     d.deleteFamily(c0, ts);
473     region.delete(d);
474 
475     d = new Delete(T1, ts);
476     d.deleteColumn(c0, c0, ts+1);
477     region.delete(d);
478 
479     d = new Delete(T1, ts);
480     d.deleteColumn(c0, c0, ts+2);
481     region.delete(d);
482 
483     // 1 family marker, 1 column marker, 2 version markers
484     assertEquals(4, countDeleteMarkers(region));
485 
486     region.flush(true);
487     assertEquals(4, countDeleteMarkers(region));
488     region.compact(false);
489     assertEquals(4, countDeleteMarkers(region));
490 
491     // another put will push out the earlier put...
492     p = new Put(T1, ts+3);
493     p.add(c0, c0, T1);
494     region.put(p);
495 
496     region.flush(true);
497     // no markers are collected, since there is an affected put
498     region.compact(true);
499     assertEquals(4, countDeleteMarkers(region));
500 
501     // the last collections collected the earlier put
502     // so after this collection all markers
503     region.compact(true);
504     assertEquals(0, countDeleteMarkers(region));
505 
506     HRegion.closeHRegion(region);
507   }
508 
509   /**
510    * Test delete marker removal from store files.
511    */
512   @Test
513   public void testWithOldRow() throws Exception {
514     HTableDescriptor htd = hbu.createTableDescriptor(name.getMethodName(), 0, 1,
515         HConstants.FOREVER, KeepDeletedCells.TRUE);
516     HRegion region = hbu.createLocalHRegion(htd, null, null);
517 
518     long ts = EnvironmentEdgeManager.currentTime();
519 
520     Put p = new Put(T1, ts);
521     p.add(c0, c0, T1);
522     region.put(p);
523 
524     // a put another (older) row in the same store
525     p = new Put(T2, ts-10);
526     p.add(c0, c0, T1);
527     region.put(p);
528 
529     // all the following deletes affect the put
530     Delete d = new Delete(T1, ts);
531     d.deleteColumns(c0, c0, ts);
532     region.delete(d);
533 
534     d = new Delete(T1, ts);
535     d.deleteFamily(c0, ts);
536     region.delete(d);
537 
538     d = new Delete(T1, ts);
539     d.deleteColumn(c0, c0, ts+1);
540     region.delete(d);
541 
542     d = new Delete(T1, ts);
543     d.deleteColumn(c0, c0, ts+2);
544     region.delete(d);
545 
546     // 1 family marker, 1 column marker, 2 version markers
547     assertEquals(4, countDeleteMarkers(region));
548 
549     region.flush(true);
550     assertEquals(4, countDeleteMarkers(region));
551     region.compact(false);
552     assertEquals(4, countDeleteMarkers(region));
553 
554     // another put will push out the earlier put...
555     p = new Put(T1, ts+3);
556     p.add(c0, c0, T1);
557     region.put(p);
558 
559     region.flush(true);
560     // no markers are collected, since there is an affected put
561     region.compact(true);
562     assertEquals(4, countDeleteMarkers(region));
563 
564     // all markers remain, since we have the older row
565     // and we haven't pushed the inlined markers past MAX_VERSIONS
566     region.compact(true);
567     assertEquals(4, countDeleteMarkers(region));
568 
569     // another put will push out the earlier put...
570     p = new Put(T1, ts+4);
571     p.add(c0, c0, T1);
572     region.put(p);
573 
574     // this pushed out the column and version marker
575     // but the family markers remains. THIS IS A PROBLEM!
576     region.compact(true);
577     assertEquals(1, countDeleteMarkers(region));
578 
579     // no amount of compacting is getting this of this one
580     // KEEP_DELETED_CELLS=>TTL is an option to avoid this.
581     region.compact(true);
582     assertEquals(1, countDeleteMarkers(region));
583 
584     HRegion.closeHRegion(region);
585   }
586 
587   /**
588    * Verify correct range demarcation
589    */
590   @Test
591   public void testRanges() throws Exception {
592     HTableDescriptor htd = hbu.createTableDescriptor(name.getMethodName(), 0, 3,
593         HConstants.FOREVER, KeepDeletedCells.TRUE);
594     HRegion region = hbu.createLocalHRegion(htd, null, null);
595 
596     long ts = EnvironmentEdgeManager.currentTime();
597     Put p = new Put(T1, ts);
598     p.add(c0, c0, T1);
599     p.add(c0, c1, T1);
600     p.add(c1, c0, T1);
601     p.add(c1, c1, T1);
602     region.put(p);
603 
604     p = new Put(T2, ts);
605     p.add(c0, c0, T1);
606     p.add(c0, c1, T1);
607     p.add(c1, c0, T1);
608     p.add(c1, c1, T1);
609     region.put(p);
610 
611     p = new Put(T1, ts+1);
612     p.add(c0, c0, T2);
613     p.add(c0, c1, T2);
614     p.add(c1, c0, T2);
615     p.add(c1, c1, T2);
616     region.put(p);
617 
618     p = new Put(T2, ts+1);
619     p.add(c0, c0, T2);
620     p.add(c0, c1, T2);
621     p.add(c1, c0, T2);
622     p.add(c1, c1, T2);
623     region.put(p);
624 
625     Delete d = new Delete(T1, ts+2);
626     d.deleteColumns(c0, c0, ts+2);
627     region.delete(d);
628 
629     d = new Delete(T1, ts+2);
630     d.deleteFamily(c1, ts+2);
631     region.delete(d);
632 
633     d = new Delete(T2, ts+2);
634     d.deleteFamily(c0, ts+2);
635     region.delete(d);
636 
637     // add an older delete, to make sure it is filtered
638     d = new Delete(T1, ts-10);
639     d.deleteFamily(c1, ts-10);
640     region.delete(d);
641 
642     // ts + 2 does NOT include the delete at ts+2
643     checkGet(region, T1, c0, c0, ts+2, T2, T1);
644     checkGet(region, T1, c0, c1, ts+2, T2, T1);
645     checkGet(region, T1, c1, c0, ts+2, T2, T1);
646     checkGet(region, T1, c1, c1, ts+2, T2, T1);
647 
648     checkGet(region, T2, c0, c0, ts+2, T2, T1);
649     checkGet(region, T2, c0, c1, ts+2, T2, T1);
650     checkGet(region, T2, c1, c0, ts+2, T2, T1);
651     checkGet(region, T2, c1, c1, ts+2, T2, T1);
652 
653     // ts + 3 does
654     checkGet(region, T1, c0, c0, ts+3);
655     checkGet(region, T1, c0, c1, ts+3, T2, T1);
656     checkGet(region, T1, c1, c0, ts+3);
657     checkGet(region, T1, c1, c1, ts+3);
658 
659     checkGet(region, T2, c0, c0, ts+3);
660     checkGet(region, T2, c0, c1, ts+3);
661     checkGet(region, T2, c1, c0, ts+3, T2, T1);
662     checkGet(region, T2, c1, c1, ts+3, T2, T1);
663 
664     HRegion.closeHRegion(region);
665   }
666 
667   /**
668    * Verify that column/version delete makers are sorted
669    * with their respective puts and removed correctly by
670    * versioning (i.e. not relying on the store earliestPutTS).
671    */
672   @Test
673   public void testDeleteMarkerVersioning() throws Exception {
674     HTableDescriptor htd = hbu.createTableDescriptor(name.getMethodName(), 0, 1,
675         HConstants.FOREVER, KeepDeletedCells.TRUE);
676     HRegion region = hbu.createLocalHRegion(htd, null, null);
677 
678     long ts = EnvironmentEdgeManager.currentTime();
679     Put p = new Put(T1, ts);
680     p.add(c0, c0, T1);
681     region.put(p);
682 
683     // this prevents marker collection based on earliestPut
684     // (cannot keep earliest put per column in the store file)
685     p = new Put(T1, ts-10);
686     p.add(c0, c1, T1);
687     region.put(p);
688 
689     Delete d = new Delete(T1, ts);
690     // test corner case (Put and Delete have same TS)
691     d.deleteColumns(c0, c0, ts);
692     region.delete(d);
693 
694     d = new Delete(T1, ts+1);
695     d.deleteColumn(c0, c0, ts+1);
696     region.delete(d);
697 
698     d = new Delete(T1, ts+3);
699     d.deleteColumn(c0, c0, ts+3);
700     region.delete(d);
701 
702     region.flush(true);
703     region.compact(true);
704     region.compact(true);
705     assertEquals(3, countDeleteMarkers(region));
706 
707     // add two more puts, since max version is 1
708     // the 2nd put (and all delete markers following)
709     // will be removed.
710     p = new Put(T1, ts+2);
711     p.add(c0, c0, T2);
712     region.put(p);
713 
714     // delete, put, delete, delete, put
715     assertEquals(3, countDeleteMarkers(region));
716 
717     p = new Put(T1, ts+3);
718     p.add(c0, c0, T3);
719     region.put(p);
720 
721     // This is potentially questionable behavior.
722     // This could be changed by not letting the ScanQueryMatcher
723     // return SEEK_NEXT_COL if a put is past VERSIONS, but instead
724     // return SKIP if the store has KEEP_DELETED_CELLS set.
725     //
726     // As it stands, the 1 here is correct here.
727     // There are two puts, VERSIONS is one, so after the 1st put the scanner
728     // knows that there can be no more KVs (put or delete) that have any effect.
729     //
730     // delete, put, put | delete, delete
731     assertEquals(1, countDeleteMarkers(region));
732 
733     // flush cache only sees what is in the memstore
734     region.flush(true);
735 
736     // Here we have the three markers again, because the flush above
737     // removed the 2nd put before the file is written.
738     // So there's only one put, and hence the deletes already in the store
739     // files cannot be removed safely.
740     // delete, put, delete, delete
741     assertEquals(3, countDeleteMarkers(region));
742 
743     region.compact(true);
744     assertEquals(3, countDeleteMarkers(region));
745 
746     // add one more put
747     p = new Put(T1, ts+4);
748     p.add(c0, c0, T4);
749     region.put(p);
750 
751     region.flush(true);
752     // one trailing delete marker remains (but only one)
753     // because delete markers do not increase the version count
754     assertEquals(1, countDeleteMarkers(region));
755     region.compact(true);
756     region.compact(true);
757     assertEquals(1, countDeleteMarkers(region));
758 
759     HRegion.closeHRegion(region);
760   }
761 
762   /**
763    * Verify scenarios with multiple CFs and columns
764    */
765   public void testWithMixedCFs() throws Exception {
766     HTableDescriptor htd = hbu.createTableDescriptor(name.getMethodName(), 0, 1,
767         HConstants.FOREVER, KeepDeletedCells.TRUE);
768     HRegion region = hbu.createLocalHRegion(htd, null, null);
769 
770     long ts = EnvironmentEdgeManager.currentTime();
771 
772     Put p = new Put(T1, ts);
773     p.add(c0, c0, T1);
774     p.add(c0, c1, T1);
775     p.add(c1, c0, T1);
776     p.add(c1, c1, T1);
777     region.put(p);
778 
779     p = new Put(T2, ts+1);
780     p.add(c0, c0, T2);
781     p.add(c0, c1, T2);
782     p.add(c1, c0, T2);
783     p.add(c1, c1, T2);
784     region.put(p);
785 
786     // family markers are each family
787     Delete d = new Delete(T1, ts+1);
788     region.delete(d);
789 
790     d = new Delete(T2, ts+2);
791     region.delete(d);
792 
793     Scan s = new Scan(T1);
794     s.setTimeRange(0, ts+1);
795     InternalScanner scanner = region.getScanner(s);
796     List<Cell> kvs = new ArrayList<Cell>();
797     scanner.next(kvs);
798     assertEquals(4, kvs.size());
799     scanner.close();
800 
801     s = new Scan(T2);
802     s.setTimeRange(0, ts+2);
803     scanner = region.getScanner(s);
804     kvs = new ArrayList<Cell>();
805     scanner.next(kvs);
806     assertEquals(4, kvs.size());
807     scanner.close();
808 
809     HRegion.closeHRegion(region);
810   }
811 
812   /**
813    * Test keeping deleted rows together with min versions set
814    * @throws Exception
815    */
816   @Test
817   public void testWithMinVersions() throws Exception {
818     HTableDescriptor htd =
819         hbu.createTableDescriptor(name.getMethodName(), 3, 1000, 1, KeepDeletedCells.TRUE);
820     HRegion region = hbu.createLocalHRegion(htd, null, null);
821 
822     long ts = EnvironmentEdgeManager.currentTime() - 2000; // 2s in the past
823 
824     Put p = new Put(T1, ts);
825     p.add(c0, c0, T3);
826     region.put(p);
827     p = new Put(T1, ts-1);
828     p.add(c0, c0, T2);
829     region.put(p);
830     p = new Put(T1, ts-3);
831     p.add(c0, c0, T1);
832     region.put(p);
833     p = new Put(T1, ts-4);
834     p.add(c0, c0, T0);
835     region.put(p);
836 
837     // all puts now are just retained because of min versions = 3
838 
839     // place a family delete marker
840     Delete d = new Delete(T1, ts-1);
841     region.delete(d);
842     // and a column delete marker
843     d = new Delete(T1, ts-2);
844     d.deleteColumns(c0, c0, ts-1);
845     region.delete(d);
846 
847     Get g = new Get(T1);
848     g.setMaxVersions();
849     g.setTimeRange(0L, ts-2);
850     Result r = region.get(g);
851     checkResult(r, c0, c0, T1,T0);
852 
853     // 3 families, one column delete marker
854     assertEquals(4, countDeleteMarkers(region));
855 
856     region.flush(true);
857     // no delete marker removes by the flush
858     assertEquals(4, countDeleteMarkers(region));
859 
860     r = region.get(g);
861     checkResult(r, c0, c0, T1);
862     p = new Put(T1, ts+1);
863     p.add(c0, c0, T4);
864     region.put(p);
865     region.flush(true);
866 
867     assertEquals(4, countDeleteMarkers(region));
868 
869     r = region.get(g);
870     checkResult(r, c0, c0, T1);
871 
872     // this will push out the last put before
873     // family delete marker
874     p = new Put(T1, ts+2);
875     p.add(c0, c0, T5);
876     region.put(p);
877 
878     region.flush(true);
879     region.compact(true);
880     // the two family markers without puts are gone
881     assertEquals(2, countDeleteMarkers(region));
882 
883     // the last compactStores updated the earliestPutTs,
884     // so after the next compaction the last family delete marker is also gone
885     region.compact(true);
886     assertEquals(0, countDeleteMarkers(region));
887 
888     HRegion.closeHRegion(region);
889   }
890 
891   /**
892    * Test keeping deleted rows together with min versions set
893    * @throws Exception
894    */
895   @Test
896   public void testWithTTL() throws Exception {
897     HTableDescriptor htd =
898         hbu.createTableDescriptor(name.getMethodName(), 1, 1000, 1, KeepDeletedCells.TTL);
899     HRegion region = hbu.createLocalHRegion(htd, null, null);
900 
901     long ts = EnvironmentEdgeManager.currentTime() - 2000; // 2s in the past
902 
903     Put p = new Put(T1, ts);
904     p.add(c0, c0, T3);
905     region.put(p);
906 
907     // place an old row, to make the family marker expires anyway
908     p = new Put(T2, ts-10);
909     p.add(c0, c0, T1);
910     region.put(p);
911 
912     checkGet(region, T1, c0, c0, ts+1, T3);
913     // place a family delete marker
914     Delete d = new Delete(T1, ts+2);
915     region.delete(d);
916 
917     checkGet(region, T1, c0, c0, ts+1, T3);
918 
919     // 3 families, one column delete marker
920     assertEquals(3, countDeleteMarkers(region));
921 
922     region.flush(true);
923     // no delete marker removes by the flush
924     assertEquals(3, countDeleteMarkers(region));
925 
926     // but the Put is gone
927     checkGet(region, T1, c0, c0, ts+1);
928 
929     region.compact(true);
930     // all delete marker gone
931     assertEquals(0, countDeleteMarkers(region));
932 
933     HRegion.closeHRegion(region);
934   }
935 
936   private void checkGet(Region region, byte[] row, byte[] fam, byte[] col,
937       long time, byte[]... vals) throws IOException {
938     Get g = new Get(row);
939     g.addColumn(fam, col);
940     g.setMaxVersions();
941     g.setTimeRange(0L, time);
942     Result r = region.get(g);
943     checkResult(r, fam, col, vals);
944 
945   }
946 
947   private int countDeleteMarkers(Region region) throws IOException {
948     Scan s = new Scan();
949     s.setRaw(true);
950     // use max versions from the store(s)
951     s.setMaxVersions(region.getStores().iterator().next().getScanInfo().getMaxVersions());
952     InternalScanner scan = region.getScanner(s);
953     List<Cell> kvs = new ArrayList<Cell>();
954     int res = 0;
955     boolean hasMore;
956     do {
957       hasMore = scan.next(kvs);
958       for (Cell kv : kvs) {
959         if(CellUtil.isDelete(kv)) res++;
960       }
961       kvs.clear();
962     } while (hasMore);
963     scan.close();
964     return res;
965   }
966 
967   private void checkResult(Result r, byte[] fam, byte[] col, byte[] ... vals) {
968     assertEquals(r.size(), vals.length);
969     List<Cell> kvs = r.getColumnCells(fam, col);
970     assertEquals(kvs.size(), vals.length);
971     for (int i=0;i<vals.length;i++) {
972       assertArrayEquals(CellUtil.cloneValue(kvs.get(i)), vals[i]);
973     }
974   }
975 
976 
977 }
978