blob: 7dc7300760e2586c199ed2bfbb1073c56997a53e [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.cassandra.db;
import java.util.*;
import org.junit.Test;
import static org.junit.Assert.*;
import org.apache.cassandra.Util;
import org.apache.cassandra.db.composites.*;
import org.apache.cassandra.db.marshal.IntegerType;
import org.apache.cassandra.utils.ByteBufferUtil;
public class RangeTombstoneListTest
{
private static final Comparator<Composite> cmp = new SimpleDenseCellNameType(IntegerType.instance);
@Test
public void testDiff()
{
RangeTombstoneList superset;
RangeTombstoneList subset;
RangeTombstoneList diff;
Iterator<RangeTombstone> iter;
// no difference
superset = new RangeTombstoneList(cmp, 10);
subset = new RangeTombstoneList(cmp, 10);
superset.add(rt(1, 10, 10));
superset.add(rt(20, 30, 10));
superset.add(rt(40, 50, 10));
subset.add(rt(1, 10, 10));
subset.add(rt(20, 30, 10));
subset.add(rt(40, 50, 10));
assertNull( subset.diff(superset));
// all items in subset are contained by the first range in the superset
superset = new RangeTombstoneList(cmp, 10);
subset = new RangeTombstoneList(cmp, 10);
subset.add(rt(1, 2, 3));
subset.add(rt(3, 4, 4));
subset.add(rt(5, 6, 5));
superset.add(rt(1, 10, 10));
superset.add(rt(20, 30, 10));
superset.add(rt(40, 50, 10));
diff = subset.diff(superset);
iter = diff.iterator();
assertRT(rt(1, 10, 10), iter.next());
assertRT(rt(20, 30, 10), iter.next());
assertRT(rt(40, 50, 10), iter.next());
assertFalse(iter.hasNext());
// multiple subset RTs are contained by superset RTs
superset = new RangeTombstoneList(cmp, 10);
subset = new RangeTombstoneList(cmp, 10);
subset.add(rt(1, 2, 1));
subset.add(rt(3, 4, 2));
subset.add(rt(5, 6, 3));
superset.add(rt(1, 5, 2));
superset.add(rt(5, 6, 3));
superset.add(rt(6, 10, 2));
diff = subset.diff(superset);
iter = diff.iterator();
assertRT(rt(1, 5, 2), iter.next());
assertRT(rt(6, 10, 2), iter.next());
assertFalse(iter.hasNext());
// the superset has one RT that covers the entire subset
superset = new RangeTombstoneList(cmp, 10);
subset = new RangeTombstoneList(cmp, 10);
superset.add(rt(1, 50, 10));
subset.add(rt(1, 10, 10));
subset.add(rt(20, 30, 10));
subset.add(rt(40, 50, 10));
diff = subset.diff(superset);
iter = diff.iterator();
assertRT(rt(1, 50, 10), iter.next());
assertFalse(iter.hasNext());
// the superset has one RT that covers the remainder of the subset
superset = new RangeTombstoneList(cmp, 10);
subset = new RangeTombstoneList(cmp, 10);
superset.add(rt(1, 10, 10));
superset.add(rt(20, 50, 10));
subset.add(rt(1, 10, 10));
subset.add(rt(20, 30, 10));
subset.add(rt(40, 50, 10));
diff = subset.diff(superset);
iter = diff.iterator();
assertRT(rt(20, 50, 10), iter.next());
assertFalse(iter.hasNext());
// only the timestamp differs on one RT
superset = new RangeTombstoneList(cmp, 10);
subset = new RangeTombstoneList(cmp, 10);
superset.add(rt(1, 10, 10));
superset.add(rt(20, 30, 20));
superset.add(rt(40, 50, 10));
subset.add(rt(1, 10, 10));
subset.add(rt(20, 30, 10));
subset.add(rt(40, 50, 10));
diff = subset.diff(superset);
iter = diff.iterator();
assertRT(rt(20, 30, 20), iter.next());
assertFalse(iter.hasNext());
// superset has a large range on an RT at the start
superset = new RangeTombstoneList(cmp, 10);
subset = new RangeTombstoneList(cmp, 10);
superset.add(rt(1, 10, 10));
superset.add(rt(20, 30, 10));
superset.add(rt(40, 50, 10));
subset.add(rt(1, 2, 3));
subset.add(rt(20, 30, 10));
subset.add(rt(40, 50, 10));
diff = subset.diff(superset);
iter = diff.iterator();
assertRT(rt(1, 10, 10), iter.next());
assertFalse(iter.hasNext());
// superset has a larger range on an RT in the middle
superset = new RangeTombstoneList(cmp, 10);
subset = new RangeTombstoneList(cmp, 10);
superset.add(rt(1, 10, 10));
superset.add(rt(20, 30, 10));
superset.add(rt(40, 50, 10));
subset.add(rt(1, 10, 10));
subset.add(rt(20, 25, 10));
subset.add(rt(40, 50, 10));
diff = subset.diff(superset);
iter = diff.iterator();
assertRT(rt(20, 30, 10), iter.next());
assertFalse(iter.hasNext());
// superset has a larger range on an RT at the end
superset = new RangeTombstoneList(cmp, 10);
subset = new RangeTombstoneList(cmp, 10);
superset.add(rt(1, 10, 10));
superset.add(rt(20, 30, 10));
superset.add(rt(40, 55, 10));
subset.add(rt(1, 10, 10));
subset.add(rt(20, 30, 10));
subset.add(rt(40, 50, 10));
diff = subset.diff(superset);
iter = diff.iterator();
assertRT(rt(40, 55, 10), iter.next());
assertFalse(iter.hasNext());
// superset has one additional RT in the middle
superset = new RangeTombstoneList(cmp, 10);
subset = new RangeTombstoneList(cmp, 10);
superset.add(rt(1, 10, 10));
superset.add(rt(20, 30, 10));
superset.add(rt(40, 50, 10));
subset.add(rt(1, 10, 10));
subset.add(rt(40, 50, 10));
diff = subset.diff(superset);
iter = diff.iterator();
assertRT(rt(20, 30, 10), iter.next());
assertFalse(iter.hasNext());
// superset has one additional RT at the start
superset = new RangeTombstoneList(cmp, 10);
subset = new RangeTombstoneList(cmp, 10);
superset.add(rt(1, 10, 10));
superset.add(rt(20, 30, 10));
superset.add(rt(40, 50, 10));
subset.add(rt(20, 30, 10));
subset.add(rt(40, 50, 10));
diff = subset.diff(superset);
iter = diff.iterator();
assertRT(rt(1, 10, 10), iter.next());
assertFalse(iter.hasNext());
// superset has one additional RT at the end
superset = new RangeTombstoneList(cmp, 10);
subset = new RangeTombstoneList(cmp, 10);
superset.add(rt(1, 10, 10));
superset.add(rt(20, 30, 10));
superset.add(rt(40, 50, 10));
subset.add(rt(1, 10, 10));
subset.add(rt(20, 30, 10));
diff = subset.diff(superset);
iter = diff.iterator();
assertRT(rt(40, 50, 10), iter.next());
assertFalse(iter.hasNext());
}
@Test
public void sortedAdditionTest()
{
sortedAdditionTest(0);
sortedAdditionTest(10);
}
private void sortedAdditionTest(int initialCapacity)
{
RangeTombstoneList l = new RangeTombstoneList(cmp, initialCapacity);
RangeTombstone rt1 = rt(1, 5, 3);
RangeTombstone rt2 = rt(7, 10, 2);
RangeTombstone rt3 = rt(10, 13, 1);
l.add(rt1);
l.add(rt2);
l.add(rt3);
Iterator<RangeTombstone> iter = l.iterator();
assertRT(rt1, iter.next());
assertRT(rt2, iter.next());
assertRT(rt3, iter.next());
assert !iter.hasNext();
}
@Test
public void nonSortedAdditionTest()
{
nonSortedAdditionTest(0);
nonSortedAdditionTest(10);
}
private void nonSortedAdditionTest(int initialCapacity)
{
RangeTombstoneList l = new RangeTombstoneList(cmp, initialCapacity);
RangeTombstone rt1 = rt(1, 5, 3);
RangeTombstone rt2 = rt(7, 10, 2);
RangeTombstone rt3 = rt(10, 13, 1);
l.add(rt2);
l.add(rt1);
l.add(rt3);
Iterator<RangeTombstone> iter = l.iterator();
assertRT(rt1, iter.next());
assertRT(rt2, iter.next());
assertRT(rt3, iter.next());
assert !iter.hasNext();
}
@Test
public void overlappingAdditionTest()
{
overlappingAdditionTest(0);
overlappingAdditionTest(10);
}
private void overlappingAdditionTest(int initialCapacity)
{
RangeTombstoneList l = new RangeTombstoneList(cmp, initialCapacity);
l.add(rt(4, 10, 3));
l.add(rt(1, 7, 2));
l.add(rt(8, 13, 4));
l.add(rt(0, 15, 1));
Iterator<RangeTombstone> iter = l.iterator();
assertRT(rt(0, 1, 1), iter.next());
assertRT(rt(1, 4, 2), iter.next());
assertRT(rt(4, 8, 3), iter.next());
assertRT(rt(8, 13, 4), iter.next());
assertRT(rt(13, 15, 1), iter.next());
assert !iter.hasNext();
RangeTombstoneList l2 = new RangeTombstoneList(cmp, initialCapacity);
l2.add(rt(4, 10, 12L));
l2.add(rt(0, 8, 25L));
assertEquals(25L, l2.searchDeletionTime(b(8)).markedForDeleteAt);
}
@Test
public void largeAdditionTest()
{
int N = 3000;
// Test that the StackOverflow from #6181 is fixed
RangeTombstoneList l = new RangeTombstoneList(cmp, N);
for (int i = 0; i < N; i++)
l.add(rt(2*i+1, 2*i+2, 1));
assertEquals(l.size(), N);
l.add(rt(0, 2*N+3, 2));
}
@Test
public void simpleOverlapTest()
{
RangeTombstoneList l1 = new RangeTombstoneList(cmp, 0);
l1.add(rt(0, 10, 3));
l1.add(rt(3, 7, 5));
Iterator<RangeTombstone> iter1 = l1.iterator();
assertRT(rt(0, 3, 3), iter1.next());
assertRT(rt(3, 7, 5), iter1.next());
assertRT(rt(7, 10, 3), iter1.next());
assert !iter1.hasNext();
RangeTombstoneList l2 = new RangeTombstoneList(cmp, 0);
l2.add(rt(0, 10, 3));
l2.add(rt(3, 7, 2));
Iterator<RangeTombstone> iter2 = l2.iterator();
assertRT(rt(0, 10, 3), iter2.next());
assert !iter2.hasNext();
}
@Test
public void overlappingPreviousEndEqualsStartTest()
{
RangeTombstoneList l = new RangeTombstoneList(cmp, 0);
// add a RangeTombstone, so, last insert is not in insertion order
l.add(rt(11, 12, 2));
l.add(rt(1, 4, 2));
l.add(rt(4, 10, 5));
assertEquals(2, l.searchDeletionTime(b(3)).markedForDeleteAt);
assertEquals(5, l.searchDeletionTime(b(4)).markedForDeleteAt);
assertEquals(5, l.searchDeletionTime(b(8)).markedForDeleteAt);
assertEquals(3, l.size());
}
@Test
public void searchTest()
{
RangeTombstoneList l = new RangeTombstoneList(cmp, 0);
l.add(rt(0, 4, 5));
l.add(rt(4, 6, 2));
l.add(rt(9, 12, 1));
l.add(rt(14, 15, 3));
l.add(rt(15, 17, 6));
assertEquals(null, l.searchDeletionTime(b(-1)));
assertEquals(5, l.searchDeletionTime(b(0)).markedForDeleteAt);
assertEquals(5, l.searchDeletionTime(b(3)).markedForDeleteAt);
assertEquals(5, l.searchDeletionTime(b(4)).markedForDeleteAt);
assertEquals(2, l.searchDeletionTime(b(5)).markedForDeleteAt);
assertEquals(null, l.searchDeletionTime(b(7)));
assertEquals(3, l.searchDeletionTime(b(14)).markedForDeleteAt);
assertEquals(6, l.searchDeletionTime(b(15)).markedForDeleteAt);
assertEquals(null, l.searchDeletionTime(b(18)));
}
@Test
public void addAllTest()
{
RangeTombstoneList l1 = new RangeTombstoneList(cmp, 0);
l1.add(rt(0, 4, 5));
l1.add(rt(6, 10, 2));
l1.add(rt(15, 17, 1));
RangeTombstoneList l2 = new RangeTombstoneList(cmp, 0);
l2.add(rt(3, 5, 7));
l2.add(rt(7, 8, 3));
l2.add(rt(8, 12, 1));
l2.add(rt(14, 17, 4));
l1.addAll(l2);
Iterator<RangeTombstone> iter = l1.iterator();
assertRT(rt(0, 3, 5), iter.next());
assertRT(rt(3, 4, 7), iter.next());
assertRT(rt(4, 5, 7), iter.next());
assertRT(rt(6, 7, 2), iter.next());
assertRT(rt(7, 8, 3), iter.next());
assertRT(rt(8, 10, 2), iter.next());
assertRT(rt(10, 12, 1), iter.next());
assertRT(rt(14, 17, 4), iter.next());
assert !iter.hasNext();
}
@Test
public void addAllSequentialTest()
{
RangeTombstoneList l1 = new RangeTombstoneList(cmp, 0);
l1.add(rt(3, 5, 2));
RangeTombstoneList l2 = new RangeTombstoneList(cmp, 0);
l2.add(rt(5, 7, 7));
l1.addAll(l2);
Iterator<RangeTombstone> iter = l1.iterator();
assertRT(rt(3, 5, 2), iter.next());
assertRT(rt(5, 7, 7), iter.next());
assert !iter.hasNext();
}
@Test
public void addAllIncludedTest()
{
RangeTombstoneList l1 = new RangeTombstoneList(cmp, 0);
l1.add(rt(3, 10, 5));
RangeTombstoneList l2 = new RangeTombstoneList(cmp, 0);
l2.add(rt(4, 5, 2));
l2.add(rt(5, 7, 3));
l2.add(rt(7, 9, 4));
l1.addAll(l2);
Iterator<RangeTombstone> iter = l1.iterator();
assertRT(rt(3, 10, 5), iter.next());
assert !iter.hasNext();
}
@Test
public void purgetTest()
{
RangeTombstoneList l = new RangeTombstoneList(cmp, 0);
l.add(rt(0, 4, 5, 110));
l.add(rt(4, 6, 2, 98));
l.add(rt(9, 12, 1, 200));
l.add(rt(14, 15, 3, 3));
l.add(rt(15, 17, 6, 45));
l.purge(100);
Iterator<RangeTombstone> iter = l.iterator();
assertRT(rt(0, 4, 5, 110), iter.next());
assertRT(rt(9, 12, 1, 200), iter.next());
assert !iter.hasNext();
}
@Test
public void minMaxTest()
{
RangeTombstoneList l = new RangeTombstoneList(cmp, 0);
l.add(rt(0, 4, 5, 110));
l.add(rt(4, 6, 2, 98));
l.add(rt(9, 12, 1, 200));
l.add(rt(14, 15, 3, 3));
l.add(rt(15, 17, 6, 45));
assertEquals(1, l.minMarkedAt());
assertEquals(6, l.maxMarkedAt());
}
@Test
public void insertSameTest()
{
// Simple test that adding the same element multiple time ends up
// with that element only a single time (CASSANDRA-9485)
RangeTombstoneList l = new RangeTombstoneList(cmp, 0);
l.add(rt(4, 4, 5, 100));
l.add(rt(4, 4, 6, 110));
l.add(rt(4, 4, 4, 90));
Iterator<RangeTombstone> iter = l.iterator();
assertRT(rt(4, 4, 6, 110), iter.next());
assert !iter.hasNext();
}
private RangeTombstoneList makeRandom(Random rand, int size, int maxItSize, int maxItDistance, int maxMarkedAt)
{
RangeTombstoneList l = new RangeTombstoneList(cmp, size);
int prevStart = -1;
int prevEnd = 0;
for (int i = 0; i < size; i++)
{
int nextStart = prevEnd + rand.nextInt(maxItDistance);
int nextEnd = nextStart + rand.nextInt(maxItSize);
// We can have an interval [x, x], but not 2 consecutives ones for the same x
if (nextEnd == nextStart && prevEnd == prevStart && prevEnd == nextStart)
nextEnd += 1 + rand.nextInt(maxItDistance);
l.add(rt(nextStart, nextEnd, rand.nextInt(maxMarkedAt)));
prevStart = nextStart;
prevEnd = nextEnd;
}
return l;
}
@Test
public void addAllRandomTest() throws Throwable
{
int TEST_COUNT = 1000;
int MAX_LIST_SIZE = 50;
int MAX_IT_SIZE = 20;
int MAX_IT_DISTANCE = 10;
int MAX_MARKEDAT = 10;
long seed = System.nanoTime();
Random rand = new Random(seed);
for (int i = 0; i < TEST_COUNT; i++)
{
RangeTombstoneList l1 = makeRandom(rand, rand.nextInt(MAX_LIST_SIZE) + 1, rand.nextInt(MAX_IT_SIZE) + 1, rand.nextInt(MAX_IT_DISTANCE) + 1, rand.nextInt(MAX_MARKEDAT) + 1);
RangeTombstoneList l2 = makeRandom(rand, rand.nextInt(MAX_LIST_SIZE) + 1, rand.nextInt(MAX_IT_SIZE) + 1, rand.nextInt(MAX_IT_DISTANCE) + 1, rand.nextInt(MAX_MARKEDAT) + 1);
RangeTombstoneList l1Initial = l1.copy();
try
{
// We generate the list randomly, so "all" we check is that the resulting range tombstone list looks valid.
l1.addAll(l2);
assertValid(l1);
}
catch (Throwable e)
{
System.out.println("Error merging:");
System.out.println(" l1: " + toString(l1Initial));
System.out.println(" l2: " + toString(l2));
System.out.println("Seed was: " + seed);
throw e;
}
}
}
private static void assertRT(RangeTombstone expected, RangeTombstone actual)
{
assertEquals(String.format("Expected %s but got %s", toString(expected), toString(actual)), expected, actual);
}
private static void assertValid(RangeTombstoneList l)
{
// We check that ranges are in the right order and that we never have something
// like ...[x, x][x, x] ...
int prevStart = -2;
int prevEnd = -1;
for (RangeTombstone rt : l)
{
int curStart = i(rt.min);
int curEnd = i(rt.max);
assertTrue("Invalid " + toString(l), prevEnd <= curStart);
assertTrue("Invalid " + toString(l), curStart <= curEnd);
if (curStart == curEnd && prevEnd == curStart)
assertTrue("Invalid " + toString(l), prevStart != prevEnd);
prevStart = curStart;
prevEnd = curEnd;
}
}
private static String toString(RangeTombstone rt)
{
return String.format("[%d, %d]@%d", i(rt.min), i(rt.max), rt.data.markedForDeleteAt);
}
private static String toString(RangeTombstoneList l)
{
StringBuilder sb = new StringBuilder();
sb.append("{");
for (RangeTombstone rt : l)
sb.append(" ").append(toString(rt));
return sb.append(" }").toString();
}
private static Composite b(int i)
{
return Util.cellname(i);
}
private static int i(Composite c)
{
return ByteBufferUtil.toInt(c.toByteBuffer());
}
private static RangeTombstone rt(int start, int end, long tstamp)
{
return rt(start, end, tstamp, 0);
}
private static RangeTombstone rt(int start, int end, long tstamp, int delTime)
{
return new RangeTombstone(b(start), b(end), tstamp, delTime);
}
}