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