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.currentTimeMillis() - 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.currentTimeMillis();
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.flushcache();
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.compactStores(true);
140     region.compactStores(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.flushcache();
172     region.compactStores(true);
173     region.compactStores(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.currentTimeMillis();
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.flushcache();
218     region.compactStores(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.currentTimeMillis();
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     assertTrue(kvs.isEmpty());
267 
268     // flushing and minor compaction keep delete markers
269     region.flushcache();
270     region.compactStores();
271     assertEquals(1, countDeleteMarkers(region));
272     region.compactStores(true);
273     // major compaction deleted it
274     assertEquals(0, countDeleteMarkers(region));
275 
276     HRegion.closeHRegion(region);
277   }
278 
279   /**
280    * The ExplicitColumnTracker does not support "raw" scanning.
281    */
282   @Test
283   public void testRawScanWithColumns() throws Exception {
284     HTableDescriptor htd = hbu.createTableDescriptor(name.getMethodName(), 0, 3,
285         HConstants.FOREVER, KeepDeletedCells.TRUE);
286     HRegion region = hbu.createLocalHRegion(htd, null, null);
287 
288     Scan s = new Scan();
289     s.setRaw(true);
290     s.setMaxVersions();
291     s.addColumn(c0, c0);
292 
293     try {
294       region.getScanner(s);
295       fail("raw scanner with columns should have failed");
296     } catch (org.apache.hadoop.hbase.DoNotRetryIOException dnre) {
297       // ok!
298     }
299 
300     HRegion.closeHRegion(region);
301   }
302 
303   /**
304    * Verify that "raw" scanning mode return delete markers and deletes rows.
305    */
306   @Test
307   public void testRawScan() throws Exception {
308     HTableDescriptor htd = hbu.createTableDescriptor(name.getMethodName(), 0, 3,
309         HConstants.FOREVER, KeepDeletedCells.TRUE);
310     HRegion region = hbu.createLocalHRegion(htd, null, null);
311 
312     long ts = EnvironmentEdgeManager.currentTimeMillis();
313     Put p = new Put(T1, ts);
314     p.add(c0, c0, T1);
315     region.put(p);
316     p = new Put(T1, ts+2);
317     p.add(c0, c0, T2);
318     region.put(p);
319     p = new Put(T1, ts+4);
320     p.add(c0, c0, T3);
321     region.put(p);
322 
323     Delete d = new Delete(T1, ts+1);
324     region.delete(d);
325 
326     d = new Delete(T1, ts+2);
327     d.deleteColumn(c0, c0, ts+2);
328     region.delete(d);
329 
330     d = new Delete(T1, ts+3);
331     d.deleteColumns(c0, c0, ts+3);
332     region.delete(d);
333 
334     Scan s = new Scan();
335     s.setRaw(true);
336     s.setMaxVersions();
337     InternalScanner scan = region.getScanner(s);
338     List<Cell> kvs = new ArrayList<Cell>();
339     scan.next(kvs);
340     assertEquals(8, kvs.size());
341     assertTrue(CellUtil.isDeleteFamily(kvs.get(0)));
342     assertArrayEquals(CellUtil.cloneValue(kvs.get(1)), T3);
343     assertTrue(CellUtil.isDelete(kvs.get(2)));
344     assertTrue(CellUtil.isDelete(kvs.get(3))); // .isDeleteType());
345     assertArrayEquals(CellUtil.cloneValue(kvs.get(4)), T2);
346     assertArrayEquals(CellUtil.cloneValue(kvs.get(5)), T1);
347     // we have 3 CFs, so there are two more delete markers
348     assertTrue(CellUtil.isDeleteFamily(kvs.get(6)));
349     assertTrue(CellUtil.isDeleteFamily(kvs.get(7)));
350 
351     // verify that raw scans honor the passed timerange
352     s = new Scan();
353     s.setRaw(true);
354     s.setMaxVersions();
355     s.setTimeRange(0, 1);
356     scan = region.getScanner(s);
357     kvs = new ArrayList<Cell>();
358     scan.next(kvs);
359     // nothing in this interval, not even delete markers
360     assertTrue(kvs.isEmpty());
361 
362     // filter new delete markers
363     s = new Scan();
364     s.setRaw(true);
365     s.setMaxVersions();
366     s.setTimeRange(0, ts+2);
367     scan = region.getScanner(s);
368     kvs = new ArrayList<Cell>();
369     scan.next(kvs);
370     assertEquals(4, kvs.size());
371     assertTrue(CellUtil.isDeleteFamily(kvs.get(0)));
372     assertArrayEquals(CellUtil.cloneValue(kvs.get(1)), T1);
373     // we have 3 CFs
374     assertTrue(CellUtil.isDeleteFamily(kvs.get(2)));
375     assertTrue(CellUtil.isDeleteFamily(kvs.get(3)));
376 
377     // filter old delete markers
378     s = new Scan();
379     s.setRaw(true);
380     s.setMaxVersions();
381     s.setTimeRange(ts+3, ts+5);
382     scan = region.getScanner(s);
383     kvs = new ArrayList<Cell>();
384     scan.next(kvs);
385     assertEquals(2, kvs.size());
386     assertArrayEquals(CellUtil.cloneValue(kvs.get(0)), T3);
387     assertTrue(CellUtil.isDelete(kvs.get(1)));
388 
389 
390     HRegion.closeHRegion(region);
391   }
392 
393   /**
394    * Verify that delete markers are removed from an otherwise empty store.
395    */
396   @Test
397   public void testDeleteMarkerExpirationEmptyStore() throws Exception {
398     HTableDescriptor htd = hbu.createTableDescriptor(name.getMethodName(), 0, 1,
399         HConstants.FOREVER, KeepDeletedCells.TRUE);
400     HRegion region = hbu.createLocalHRegion(htd, null, null);
401 
402     long ts = EnvironmentEdgeManager.currentTimeMillis();
403 
404     Delete d = new Delete(T1, ts);
405     d.deleteColumns(c0, c0, ts);
406     region.delete(d);
407 
408     d = new Delete(T1, ts);
409     d.deleteFamily(c0);
410     region.delete(d);
411 
412     d = new Delete(T1, ts);
413     d.deleteColumn(c0, c0, ts+1);
414     region.delete(d);
415 
416     d = new Delete(T1, ts);
417     d.deleteColumn(c0, c0, ts+2);
418     region.delete(d);
419 
420     // 1 family marker, 1 column marker, 2 version markers
421     assertEquals(4, countDeleteMarkers(region));
422 
423     // neither flush nor minor compaction removes any marker
424     region.flushcache();
425     assertEquals(4, countDeleteMarkers(region));
426     region.compactStores(false);
427     assertEquals(4, countDeleteMarkers(region));
428 
429     // major compaction removes all, since there are no puts they affect
430     region.compactStores(true);
431     assertEquals(0, countDeleteMarkers(region));
432 
433     HRegion.closeHRegion(region);
434   }
435 
436   /**
437    * Test delete marker removal from store files.
438    */
439   @Test
440   public void testDeleteMarkerExpiration() throws Exception {
441     HTableDescriptor htd = hbu.createTableDescriptor(name.getMethodName(), 0, 1,
442         HConstants.FOREVER, KeepDeletedCells.TRUE);
443     HRegion region = hbu.createLocalHRegion(htd, null, null);
444 
445     long ts = EnvironmentEdgeManager.currentTimeMillis();
446 
447     Put p = new Put(T1, ts);
448     p.add(c0, c0, T1);
449     region.put(p);
450 
451     // a put into another store (CF) should have no effect
452     p = new Put(T1, ts-10);
453     p.add(c1, c0, T1);
454     region.put(p);
455 
456     // all the following deletes affect the put
457     Delete d = new Delete(T1, ts);
458     d.deleteColumns(c0, c0, ts);
459     region.delete(d);
460 
461     d = new Delete(T1, ts);
462     d.deleteFamily(c0, ts);
463     region.delete(d);
464 
465     d = new Delete(T1, ts);
466     d.deleteColumn(c0, c0, ts+1);
467     region.delete(d);
468 
469     d = new Delete(T1, ts);
470     d.deleteColumn(c0, c0, ts+2);
471     region.delete(d);
472 
473     // 1 family marker, 1 column marker, 2 version markers
474     assertEquals(4, countDeleteMarkers(region));
475 
476     region.flushcache();
477     assertEquals(4, countDeleteMarkers(region));
478     region.compactStores(false);
479     assertEquals(4, countDeleteMarkers(region));
480 
481     // another put will push out the earlier put...
482     p = new Put(T1, ts+3);
483     p.add(c0, c0, T1);
484     region.put(p);
485 
486     region.flushcache();
487     // no markers are collected, since there is an affected put
488     region.compactStores(true);
489     assertEquals(4, countDeleteMarkers(region));
490 
491     // the last collections collected the earlier put
492     // so after this collection all markers
493     region.compactStores(true);
494     assertEquals(0, countDeleteMarkers(region));
495 
496     HRegion.closeHRegion(region);
497   }
498 
499   /**
500    * Test delete marker removal from store files.
501    */
502   @Test
503   public void testWithOldRow() throws Exception {
504     HTableDescriptor htd = hbu.createTableDescriptor(name.getMethodName(), 0, 1,
505         HConstants.FOREVER, KeepDeletedCells.TRUE);
506     HRegion region = hbu.createLocalHRegion(htd, null, null);
507 
508     long ts = EnvironmentEdgeManager.currentTimeMillis();
509 
510     Put p = new Put(T1, ts);
511     p.add(c0, c0, T1);
512     region.put(p);
513 
514     // a put another (older) row in the same store
515     p = new Put(T2, ts-10);
516     p.add(c0, c0, T1);
517     region.put(p);
518 
519     // all the following deletes affect the put
520     Delete d = new Delete(T1, ts);
521     d.deleteColumns(c0, c0, ts);
522     region.delete(d);
523 
524     d = new Delete(T1, ts);
525     d.deleteFamily(c0, ts);
526     region.delete(d);
527 
528     d = new Delete(T1, ts);
529     d.deleteColumn(c0, c0, ts+1);
530     region.delete(d);
531 
532     d = new Delete(T1, ts);
533     d.deleteColumn(c0, c0, ts+2);
534     region.delete(d);
535 
536     // 1 family marker, 1 column marker, 2 version markers
537     assertEquals(4, countDeleteMarkers(region));
538 
539     region.flushcache();
540     assertEquals(4, countDeleteMarkers(region));
541     region.compactStores(false);
542     assertEquals(4, countDeleteMarkers(region));
543 
544     // another put will push out the earlier put...
545     p = new Put(T1, ts+3);
546     p.add(c0, c0, T1);
547     region.put(p);
548 
549     region.flushcache();
550     // no markers are collected, since there is an affected put
551     region.compactStores(true);
552     assertEquals(4, countDeleteMarkers(region));
553 
554     // all markers remain, since we have the older row
555     // and we haven't pushed the inlined markers past MAX_VERSIONS
556     region.compactStores(true);
557     assertEquals(4, countDeleteMarkers(region));
558 
559     // another put will push out the earlier put...
560     p = new Put(T1, ts+4);
561     p.add(c0, c0, T1);
562     region.put(p);
563 
564     // this pushed out the column and version marker
565     // but the family markers remains. THIS IS A PROBLEM!
566     region.compactStores(true);
567     assertEquals(1, countDeleteMarkers(region));
568 
569     // no amount of compacting is getting this of this one
570     // KEEP_DELETED_CELLS=>TTL is an option to avoid this.
571     region.compactStores(true);
572     assertEquals(1, countDeleteMarkers(region));
573 
574     HRegion.closeHRegion(region);
575   }
576 
577   /**
578    * Verify correct range demarcation
579    */
580   @Test
581   public void testRanges() throws Exception {
582     HTableDescriptor htd = hbu.createTableDescriptor(name.getMethodName(), 0, 3,
583         HConstants.FOREVER, KeepDeletedCells.TRUE);
584     HRegion region = hbu.createLocalHRegion(htd, null, null);
585 
586     long ts = EnvironmentEdgeManager.currentTimeMillis();
587     Put p = new Put(T1, ts);
588     p.add(c0, c0, T1);
589     p.add(c0, c1, T1);
590     p.add(c1, c0, T1);
591     p.add(c1, c1, T1);
592     region.put(p);
593 
594     p = new Put(T2, ts);
595     p.add(c0, c0, T1);
596     p.add(c0, c1, T1);
597     p.add(c1, c0, T1);
598     p.add(c1, c1, T1);
599     region.put(p);
600 
601     p = new Put(T1, ts+1);
602     p.add(c0, c0, T2);
603     p.add(c0, c1, T2);
604     p.add(c1, c0, T2);
605     p.add(c1, c1, T2);
606     region.put(p);
607 
608     p = new Put(T2, ts+1);
609     p.add(c0, c0, T2);
610     p.add(c0, c1, T2);
611     p.add(c1, c0, T2);
612     p.add(c1, c1, T2);
613     region.put(p);
614 
615     Delete d = new Delete(T1, ts+2);
616     d.deleteColumns(c0, c0, ts+2);
617     region.delete(d);
618 
619     d = new Delete(T1, ts+2);
620     d.deleteFamily(c1, ts+2);
621     region.delete(d);
622 
623     d = new Delete(T2, ts+2);
624     d.deleteFamily(c0, ts+2);
625     region.delete(d);
626 
627     // add an older delete, to make sure it is filtered
628     d = new Delete(T1, ts-10);
629     d.deleteFamily(c1, ts-10);
630     region.delete(d);
631 
632     // ts + 2 does NOT include the delete at ts+2
633     checkGet(region, T1, c0, c0, ts+2, T2, T1);
634     checkGet(region, T1, c0, c1, ts+2, T2, T1);
635     checkGet(region, T1, c1, c0, ts+2, T2, T1);
636     checkGet(region, T1, c1, c1, ts+2, T2, T1);
637 
638     checkGet(region, T2, c0, c0, ts+2, T2, T1);
639     checkGet(region, T2, c0, c1, ts+2, T2, T1);
640     checkGet(region, T2, c1, c0, ts+2, T2, T1);
641     checkGet(region, T2, c1, c1, ts+2, T2, T1);
642 
643     // ts + 3 does
644     checkGet(region, T1, c0, c0, ts+3);
645     checkGet(region, T1, c0, c1, ts+3, T2, T1);
646     checkGet(region, T1, c1, c0, ts+3);
647     checkGet(region, T1, c1, c1, ts+3);
648 
649     checkGet(region, T2, c0, c0, ts+3);
650     checkGet(region, T2, c0, c1, ts+3);
651     checkGet(region, T2, c1, c0, ts+3, T2, T1);
652     checkGet(region, T2, c1, c1, ts+3, T2, T1);
653 
654     HRegion.closeHRegion(region);
655   }
656 
657   /**
658    * Verify that column/version delete makers are sorted
659    * with their respective puts and removed correctly by
660    * versioning (i.e. not relying on the store earliestPutTS).
661    */
662   @Test
663   public void testDeleteMarkerVersioning() throws Exception {
664     HTableDescriptor htd = hbu.createTableDescriptor(name.getMethodName(), 0, 1,
665         HConstants.FOREVER, KeepDeletedCells.TRUE);
666     HRegion region = hbu.createLocalHRegion(htd, null, null);
667 
668     long ts = EnvironmentEdgeManager.currentTimeMillis();
669     Put p = new Put(T1, ts);
670     p.add(c0, c0, T1);
671     region.put(p);
672 
673     // this prevents marker collection based on earliestPut
674     // (cannot keep earliest put per column in the store file)
675     p = new Put(T1, ts-10);
676     p.add(c0, c1, T1);
677     region.put(p);
678 
679     Delete d = new Delete(T1, ts);
680     // test corner case (Put and Delete have same TS)
681     d.deleteColumns(c0, c0, ts);
682     region.delete(d);
683 
684     d = new Delete(T1, ts+1);
685     d.deleteColumn(c0, c0, ts+1);
686     region.delete(d);
687 
688     d = new Delete(T1, ts+3);
689     d.deleteColumn(c0, c0, ts+3);
690     region.delete(d);
691 
692     region.flushcache();
693     region.compactStores(true);
694     region.compactStores(true);
695     assertEquals(3, countDeleteMarkers(region));
696 
697     // add two more puts, since max version is 1
698     // the 2nd put (and all delete markers following)
699     // will be removed.
700     p = new Put(T1, ts+2);
701     p.add(c0, c0, T2);
702     region.put(p);
703 
704     // delete, put, delete, delete, put
705     assertEquals(3, countDeleteMarkers(region));
706 
707     p = new Put(T1, ts+3);
708     p.add(c0, c0, T3);
709     region.put(p);
710 
711     // This is potentially questionable behavior.
712     // This could be changed by not letting the ScanQueryMatcher
713     // return SEEK_NEXT_COL if a put is past VERSIONS, but instead
714     // return SKIP if the store has KEEP_DELETED_CELLS set.
715     //
716     // As it stands, the 1 here is correct here.
717     // There are two puts, VERSIONS is one, so after the 1st put the scanner
718     // knows that there can be no more KVs (put or delete) that have any effect.
719     //
720     // delete, put, put | delete, delete
721     assertEquals(1, countDeleteMarkers(region));
722 
723     // flush cache only sees what is in the memstore
724     region.flushcache();
725 
726     // Here we have the three markers again, because the flush above
727     // removed the 2nd put before the file is written.
728     // So there's only one put, and hence the deletes already in the store
729     // files cannot be removed safely.
730     // delete, put, delete, delete
731     assertEquals(3, countDeleteMarkers(region));
732 
733     region.compactStores(true);
734     assertEquals(3, countDeleteMarkers(region));
735 
736     // add one more put
737     p = new Put(T1, ts+4);
738     p.add(c0, c0, T4);
739     region.put(p);
740 
741     region.flushcache();
742     // one trailing delete marker remains (but only one)
743     // because delete markers do not increase the version count
744     assertEquals(1, countDeleteMarkers(region));
745     region.compactStores(true);
746     region.compactStores(true);
747     assertEquals(1, countDeleteMarkers(region));
748 
749     HRegion.closeHRegion(region);
750   }
751 
752   /**
753    * Verify scenarios with multiple CFs and columns
754    */
755   public void testWithMixedCFs() throws Exception {
756     HTableDescriptor htd = hbu.createTableDescriptor(name.getMethodName(), 0, 1,
757         HConstants.FOREVER, KeepDeletedCells.TRUE);
758     HRegion region = hbu.createLocalHRegion(htd, null, null);
759 
760     long ts = EnvironmentEdgeManager.currentTimeMillis();
761 
762     Put p = new Put(T1, ts);
763     p.add(c0, c0, T1);
764     p.add(c0, c1, T1);
765     p.add(c1, c0, T1);
766     p.add(c1, c1, T1);
767     region.put(p);
768 
769     p = new Put(T2, ts+1);
770     p.add(c0, c0, T2);
771     p.add(c0, c1, T2);
772     p.add(c1, c0, T2);
773     p.add(c1, c1, T2);
774     region.put(p);
775 
776     // family markers are each family
777     Delete d = new Delete(T1, ts+1);
778     region.delete(d);
779 
780     d = new Delete(T2, ts+2);
781     region.delete(d);
782 
783     Scan s = new Scan(T1);
784     s.setTimeRange(0, ts+1);
785     InternalScanner scanner = region.getScanner(s);
786     List<Cell> kvs = new ArrayList<Cell>();
787     scanner.next(kvs);
788     assertEquals(4, kvs.size());
789     scanner.close();
790 
791     s = new Scan(T2);
792     s.setTimeRange(0, ts+2);
793     scanner = region.getScanner(s);
794     kvs = new ArrayList<Cell>();
795     scanner.next(kvs);
796     assertEquals(4, kvs.size());
797     scanner.close();
798 
799     HRegion.closeHRegion(region);
800   }
801 
802   /**
803    * Test keeping deleted rows together with min versions set
804    * @throws Exception
805    */
806   @Test
807   public void testWithMinVersions() throws Exception {
808     HTableDescriptor htd =
809         hbu.createTableDescriptor(name.getMethodName(), 3, 1000, 1, KeepDeletedCells.TRUE);
810     HRegion region = hbu.createLocalHRegion(htd, null, null);
811 
812     long ts = EnvironmentEdgeManager.currentTimeMillis() - 2000; // 2s in the past
813 
814     Put p = new Put(T1, ts);
815     p.add(c0, c0, T3);
816     region.put(p);
817     p = new Put(T1, ts-1);
818     p.add(c0, c0, T2);
819     region.put(p);
820     p = new Put(T1, ts-3);
821     p.add(c0, c0, T1);
822     region.put(p);
823     p = new Put(T1, ts-4);
824     p.add(c0, c0, T0);
825     region.put(p);
826 
827     // all puts now are just retained because of min versions = 3
828 
829     // place a family delete marker
830     Delete d = new Delete(T1, ts-1);
831     region.delete(d);
832     // and a column delete marker
833     d = new Delete(T1, ts-2);
834     d.deleteColumns(c0, c0, ts-1);
835     region.delete(d);
836 
837     Get g = new Get(T1);
838     g.setMaxVersions();
839     g.setTimeRange(0L, ts-2);
840     Result r = region.get(g);
841     checkResult(r, c0, c0, T1,T0);
842 
843     // 3 families, one column delete marker
844     assertEquals(4, countDeleteMarkers(region));
845 
846     region.flushcache();
847     // no delete marker removes by the flush
848     assertEquals(4, countDeleteMarkers(region));
849 
850     r = region.get(g);
851     checkResult(r, c0, c0, T1);
852     p = new Put(T1, ts+1);
853     p.add(c0, c0, T4);
854     region.put(p);
855     region.flushcache();
856 
857     assertEquals(4, countDeleteMarkers(region));
858 
859     r = region.get(g);
860     checkResult(r, c0, c0, T1);
861 
862     // this will push out the last put before
863     // family delete marker
864     p = new Put(T1, ts+2);
865     p.add(c0, c0, T5);
866     region.put(p);
867 
868     region.flushcache();
869     region.compactStores(true);
870     // the two family markers without puts are gone
871     assertEquals(2, countDeleteMarkers(region));
872 
873     // the last compactStores updated the earliestPutTs,
874     // so after the next compaction the last family delete marker is also gone
875     region.compactStores(true);
876     assertEquals(0, countDeleteMarkers(region));
877 
878     HRegion.closeHRegion(region);
879   }
880 
881   /**
882    * Test keeping deleted rows together with min versions set
883    * @throws Exception
884    */
885   @Test
886   public void testWithTTL() throws Exception {
887     HTableDescriptor htd =
888         hbu.createTableDescriptor(name.getMethodName(), 1, 1000, 1, KeepDeletedCells.TTL);
889     HRegion region = hbu.createLocalHRegion(htd, null, null);
890 
891     long ts = EnvironmentEdgeManager.currentTimeMillis() - 2000; // 2s in the past
892 
893     Put p = new Put(T1, ts);
894     p.add(c0, c0, T3);
895     region.put(p);
896 
897     // place an old row, to make the family marker expires anyway
898     p = new Put(T2, ts-10);
899     p.add(c0, c0, T1);
900     region.put(p);
901 
902     checkGet(region, T1, c0, c0, ts+1, T3);
903     // place a family delete marker
904     Delete d = new Delete(T1, ts+2);
905     region.delete(d);
906 
907     checkGet(region, T1, c0, c0, ts+1, T3);
908 
909     // 3 families, one column delete marker
910     assertEquals(3, countDeleteMarkers(region));
911 
912     region.flushcache();
913     // no delete marker removes by the flush
914     assertEquals(3, countDeleteMarkers(region));
915 
916     // but the Put is gone
917     checkGet(region, T1, c0, c0, ts+1);
918 
919     region.compactStores(true);
920     // all delete marker gone
921     assertEquals(0, countDeleteMarkers(region));
922 
923     HRegion.closeHRegion(region);
924   }
925 
926   private void checkGet(HRegion region, byte[] row, byte[] fam, byte[] col,
927       long time, byte[]... vals) throws IOException {
928     Get g = new Get(row);
929     g.addColumn(fam, col);
930     g.setMaxVersions();
931     g.setTimeRange(0L, time);
932     Result r = region.get(g);
933     checkResult(r, fam, col, vals);
934 
935   }
936 
937   private int countDeleteMarkers(HRegion region) throws IOException {
938     Scan s = new Scan();
939     s.setRaw(true);
940     // use max versions from the store(s)
941     s.setMaxVersions(region.getStores().values().iterator().next().getScanInfo().getMaxVersions());
942     InternalScanner scan = region.getScanner(s);
943     List<Cell> kvs = new ArrayList<Cell>();
944     int res = 0;
945     boolean hasMore;
946     do {
947       hasMore = scan.next(kvs);
948       for (Cell kv : kvs) {
949         if(CellUtil.isDelete(kv)) res++;
950       }
951       kvs.clear();
952     } while (hasMore);
953     scan.close();
954     return res;
955   }
956 
957   private void checkResult(Result r, byte[] fam, byte[] col, byte[] ... vals) {
958     assertEquals(r.size(), vals.length);
959     List<Cell> kvs = r.getColumnCells(fam, col);
960     assertEquals(kvs.size(), vals.length);
961     for (int i=0;i<vals.length;i++) {
962       assertArrayEquals(CellUtil.cloneValue(kvs.get(i)), vals[i]);
963     }
964   }
965 
966 
967 }
968