/* * 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); private static final Random rand = new Random(); @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()); } private RangeTombstoneList makeRandom(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; for (int i = 0; i < TEST_COUNT; i++) { RangeTombstoneList l1 = makeRandom(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.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)); 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); } }