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