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