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