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