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