blob: 07bae0ed9f1713cf57d0538fb618ec4c83ab3619 [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.tries;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.NavigableMap;
import java.util.Random;
import java.util.SortedMap;
import java.util.TreeMap;
import org.junit.Assert;
import org.junit.Test;
import com.googlecode.concurrenttrees.common.Iterables;
import org.apache.cassandra.utils.bytecomparable.ByteComparable;
import static org.apache.cassandra.db.tries.InMemoryTrieTestBase.asString;
import static org.apache.cassandra.db.tries.InMemoryTrieTestBase.assertSameContent;
import static org.apache.cassandra.db.tries.InMemoryTrieTestBase.generateKeys;
import static org.apache.cassandra.db.tries.InMemoryTrieTestBase.makeInMemoryTrie;
import static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals;
public class SlicedTrieTest
{
public static final ByteComparable[] BOUNDARIES = toByteComparable(new String[]{
"test1",
"test11",
"test12",
"test13",
"test2",
"test21",
"te",
"s",
"q",
"\000",
"\777",
"\777\000",
"\000\777",
"\000\000",
"\000\000\000",
"\000\000\777",
"\777\777"
});
public static final ByteComparable[] KEYS = toByteComparable(new String[]{
"test1",
"test2",
"test55",
"test123",
"test124",
"test12",
"test21",
"tease",
"sort",
"sorting",
"square",
"\777\000",
"\000\777",
"\000\000",
"\000\000\000",
"\000\000\777",
"\777\777"
});
public static final Comparator<ByteComparable> BYTE_COMPARABLE_COMPARATOR = (bytes1, bytes2) -> ByteComparable.compare(bytes1, bytes2, Trie.BYTE_COMPARABLE_VERSION);
private static final int COUNT = 15000;
Random rand = new Random();
@Test
public void testIntersectRangeDirect()
{
testIntersectRange(COUNT);
}
public void testIntersectRange(int count)
{
ByteComparable[] src1 = generateKeys(rand, count);
NavigableMap<ByteComparable, ByteBuffer> content1 = new TreeMap<>((bytes1, bytes2) -> ByteComparable.compare(bytes1, bytes2, Trie.BYTE_COMPARABLE_VERSION));
InMemoryTrie<ByteBuffer> trie1 = makeInMemoryTrie(src1, content1, true);
checkEqualRange(content1, trie1, null, true, null, true);
checkEqualRange(content1, trie1, InMemoryTrieTestBase.generateKey(rand), true, null, true);
checkEqualRange(content1, trie1, null, true, InMemoryTrieTestBase.generateKey(rand), true);
for (int i = 0; i < 4; ++i)
{
ByteComparable l = rand.nextBoolean() ? InMemoryTrieTestBase.generateKey(rand) : src1[rand.nextInt(src1.length)];
ByteComparable r = rand.nextBoolean() ? InMemoryTrieTestBase.generateKey(rand) : src1[rand.nextInt(src1.length)];
int cmp = ByteComparable.compare(l, r, Trie.BYTE_COMPARABLE_VERSION);
if (cmp > 0)
{
ByteComparable t = l;
l = r;
r = t; // swap
}
boolean includeLeft = (i & 1) != 0 || cmp == 0;
boolean includeRight = (i & 2) != 0 || cmp == 0;
checkEqualRange(content1, trie1, l, includeLeft, r, includeRight);
checkEqualRange(content1, trie1, null, includeLeft, r, includeRight);
checkEqualRange(content1, trie1, l, includeLeft, null, includeRight);
}
}
private static ByteComparable[] toByteComparable(String[] keys)
{
return Arrays.stream(keys)
.map(x -> ByteComparable.fixedLength(x.getBytes(StandardCharsets.UTF_8)))
.toArray(ByteComparable[]::new);
}
@Test
public void testSingletonSubtrie()
{
Arrays.sort(BOUNDARIES, (a, b) -> ByteComparable.compare(a, b, ByteComparable.Version.OSS50));
for (int li = -1; li < BOUNDARIES.length; ++li)
{
ByteComparable l = li < 0 ? null : BOUNDARIES[li];
for (int ri = Math.max(0, li); ri <= BOUNDARIES.length; ++ri)
{
ByteComparable r = ri == BOUNDARIES.length ? null : BOUNDARIES[ri];
for (int i = li == ri ? 3 : 0; i < 4; ++i)
{
boolean includeLeft = (i & 1) != 0;
boolean includeRight = (i & 2) != 0;
for (ByteComparable key : KEYS)
{
int cmp1 = l != null ? ByteComparable.compare(key, l, ByteComparable.Version.OSS50) : 1;
int cmp2 = r != null ? ByteComparable.compare(r, key, ByteComparable.Version.OSS50) : 1;
Trie<Boolean> ix = new SlicedTrie<>(Trie.singleton(key, true), l, includeLeft, r, includeRight);
boolean expected = true;
if (cmp1 < 0 || cmp1 == 0 && !includeLeft)
expected = false;
if (cmp2 < 0 || cmp2 == 0 && !includeRight)
expected = false;
boolean actual = com.google.common.collect.Iterables.getFirst(ix.values(), false);
if (expected != actual)
{
System.err.println("Intersection");
System.err.println(ix.dump());
Assert.fail(String.format("Failed on range %s%s,%s%s key %s expected %s got %s\n",
includeLeft ? "[" : "(",
l != null ? l.byteComparableAsString(ByteComparable.Version.OSS50) : null,
r != null ? r.byteComparableAsString(ByteComparable.Version.OSS50) : null,
includeRight ? "]" : ")",
key.byteComparableAsString(ByteComparable.Version.OSS50),
expected,
actual));
}
}
}
}
}
}
@Test
public void testMemtableSubtrie()
{
Arrays.sort(BOUNDARIES, BYTE_COMPARABLE_COMPARATOR);
NavigableMap<ByteComparable, ByteBuffer> content1 = new TreeMap<>(BYTE_COMPARABLE_COMPARATOR);
InMemoryTrie<ByteBuffer> trie1 = makeInMemoryTrie(KEYS, content1, true);
for (int li = -1; li < BOUNDARIES.length; ++li)
{
ByteComparable l = li < 0 ? null : BOUNDARIES[li];
for (int ri = Math.max(0, li); ri <= BOUNDARIES.length; ++ri)
{
ByteComparable r = ri == BOUNDARIES.length ? null : BOUNDARIES[ri];
for (int i = 0; i < 4; ++i)
{
boolean includeLeft = (i & 1) != 0;
boolean includeRight = (i & 2) != 0;
if ((!includeLeft || !includeRight) && li == ri)
continue;
checkEqualRange(content1, trie1, l, includeLeft, r, includeRight);
}
}
}
}
@Test
public void testMergeSubtrie()
{
testMergeSubtrie(2);
}
@Test
public void testCollectionMergeSubtrie3()
{
testMergeSubtrie(3);
}
@Test
public void testCollectionMergeSubtrie5()
{
testMergeSubtrie(5);
}
public void testMergeSubtrie(int mergeCount)
{
Arrays.sort(BOUNDARIES, BYTE_COMPARABLE_COMPARATOR);
NavigableMap<ByteComparable, ByteBuffer> content1 = new TreeMap<>(BYTE_COMPARABLE_COMPARATOR);
List<Trie<ByteBuffer>> tries = new ArrayList<>();
for (int i = 0; i < mergeCount; ++i)
{
tries.add(makeInMemoryTrie(Arrays.copyOfRange(KEYS,
KEYS.length * i / mergeCount,
KEYS.length * (i + 1) / mergeCount),
content1,
true));
}
Trie<ByteBuffer> trie1 = Trie.mergeDistinct(tries);
for (int li = -1; li < BOUNDARIES.length; ++li)
{
ByteComparable l = li < 0 ? null : BOUNDARIES[li];
for (int ri = Math.max(0, li); ri <= BOUNDARIES.length; ++ri)
{
ByteComparable r = ri == BOUNDARIES.length ? null : BOUNDARIES[ri];
for (int i = 0; i < 4; ++i)
{
boolean includeLeft = (i & 1) != 0;
boolean includeRight = (i & 2) != 0;
if ((!includeLeft || !includeRight) && li == ri)
continue;
checkEqualRange(content1, trie1, l, includeLeft, r, includeRight);
}
}
}
}
public void checkEqualRange(NavigableMap<ByteComparable, ByteBuffer> content1,
Trie<ByteBuffer> t1,
ByteComparable l,
boolean includeLeft,
ByteComparable r,
boolean includeRight)
{
System.out.println(String.format("Intersection with %s%s:%s%s", includeLeft ? "[" : "(", asString(l), asString(r), includeRight ? "]" : ")"));
SortedMap<ByteComparable, ByteBuffer> imap = l == null
? r == null
? content1
: content1.headMap(r, includeRight)
: r == null
? content1.tailMap(l, includeLeft)
: content1.subMap(l, includeLeft, r, includeRight);
Trie<ByteBuffer> intersection = t1.subtrie(l, includeLeft, r, includeRight);
assertSameContent(intersection, imap);
if (l == null || r == null)
return;
// Test intersecting intersection.
intersection = t1.subtrie(l, includeLeft, null, false).subtrie(null, false, r, includeRight);
assertSameContent(intersection, imap);
intersection = t1.subtrie(null, false, r, includeRight).subtrie(l, includeLeft, null, false);
assertSameContent(intersection, imap);
}
/**
* Extract the values of the provide trie into a list.
*/
private static <T> List<T> toList(Trie<T> trie)
{
return Iterables.toList(trie.values());
}
/**
* Creates a simple trie with a root having the provided number of childs, where each child is a leaf whose content
* is simply the value of the transition leading to it.
*
* In other words, {@code singleLevelIntTrie(4)} creates the following trie:
* Root
* t= 0 1 2 3
* | | | |
* 0 1 2 3
*/
private static Trie<Integer> singleLevelIntTrie(int childs)
{
return new Trie<Integer>()
{
@Override
protected Cursor<Integer> cursor()
{
return new singleLevelCursor();
}
class singleLevelCursor implements Cursor<Integer>
{
int current = -1;
@Override
public int advance()
{
++current;
return depth();
}
@Override
public int skipChildren()
{
return advance();
}
@Override
public int depth()
{
if (current == -1)
return 0;
if (current < childs)
return 1;
return -1;
}
@Override
public int incomingTransition()
{
return current;
}
@Override
public Integer content()
{
return current;
}
}
};
}
/** Creates a single byte {@link ByteComparable} with the provide value */
private static ByteComparable of(int value)
{
assert value >= 0 && value <= Byte.MAX_VALUE;
return ByteComparable.fixedLength(new byte[]{ (byte)value });
}
@Test
public void testSimpleIntersectionII()
{
Trie<Integer> trie = singleLevelIntTrie(10);
assertEquals(asList(-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9), toList(trie));
Trie<Integer> intersection = trie.subtrie(of(3), true, of(7), true);
assertEquals(asList(3, 4, 5, 6, 7), toList(intersection));
}
@Test
public void testSimpleIntersectionEI()
{
Trie<Integer> trie = singleLevelIntTrie(10);
assertEquals(asList(-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9), toList(trie));
Trie<Integer> intersection = trie.subtrie(of(3), false, of(7), true);
assertEquals(asList(4, 5, 6, 7), toList(intersection));
}
@Test
public void testSimpleIntersectionIE()
{
Trie<Integer> trie = singleLevelIntTrie(10);
assertEquals(asList(-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9), toList(trie));
Trie<Integer> intersection = trie.subtrie(of(3), true, of(7), false);
assertEquals(asList(3, 4, 5, 6), toList(intersection));
}
@Test
public void testSimpleIntersectionEE()
{
Trie<Integer> trie = singleLevelIntTrie(10);
assertEquals(asList(-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9), toList(trie));
Trie<Integer> intersection = trie.subtrie(of(3), false, of(7), false);
assertEquals(asList(4, 5, 6), toList(intersection));
}
@Test
public void testSimpleLeftIntersectionE()
{
Trie<Integer> trie = singleLevelIntTrie(10);
assertEquals(asList(-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9), toList(trie));
Trie<Integer> intersection = trie.subtrie(of(3), false, null, true);
assertEquals(asList(4, 5, 6, 7, 8, 9), toList(intersection));
}
@Test
public void testSimpleLeftIntersectionI()
{
Trie<Integer> trie = singleLevelIntTrie(10);
assertEquals(asList(-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9), toList(trie));
Trie<Integer> intersection = trie.subtrie(of(3), true, null, true);
assertEquals(asList(3, 4, 5, 6, 7, 8, 9), toList(intersection));
}
@Test
public void testSimpleRightIntersectionE()
{
Trie<Integer> trie = singleLevelIntTrie(10);
assertEquals(asList(-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9), toList(trie));
Trie<Integer> intersection = trie.subtrie(null, true, of(7), false);
assertEquals(asList(-1, 0, 1, 2, 3, 4, 5, 6), toList(intersection));
}
@Test
public void testSimpleRightIntersectionI()
{
Trie<Integer> trie = singleLevelIntTrie(10);
assertEquals(asList(-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9), toList(trie));
Trie<Integer> intersection = trie.subtrie(null, true, of(7), true);
assertEquals(asList(-1, 0, 1, 2, 3, 4, 5, 6, 7), toList(intersection));
}
@Test
public void testSimpleNoIntersection()
{
Trie<Integer> trie = singleLevelIntTrie(10);
assertEquals(asList(-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9), toList(trie));
Trie<Integer> intersection = trie.subtrie(null, true, null, true);
assertEquals(asList(-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9), toList(intersection));
// The two boolean flags don't have a meaning when the bound does not exist. For completeness, also test
// with them set to false.
intersection = trie.subtrie(null, false, null, false);
assertEquals(asList(-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9), toList(intersection));
}
@Test
public void testSimpleEmptyIntersectionLeft()
{
Trie<Integer> trie = singleLevelIntTrie(10);
assertEquals(asList(-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9), toList(trie));
Trie<Integer> intersection = trie.subtrie(ByteComparable.EMPTY, true, null, true);
assertEquals(asList(-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9), toList(intersection));
intersection = trie.subtrie(ByteComparable.EMPTY, false, null, true);
assertEquals(asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9), toList(intersection));
intersection = trie.subtrie(ByteComparable.EMPTY, true, of(5), true);
assertEquals(asList(-1, 0, 1, 2, 3, 4, 5), toList(intersection));
intersection = trie.subtrie(ByteComparable.EMPTY, false, of(5), true);
assertEquals(asList(0, 1, 2, 3, 4, 5), toList(intersection));
}
@Test
public void testSimpleEmptyIntersectionRight()
{
Trie<Integer> trie = singleLevelIntTrie(10);
assertEquals(asList(-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9), toList(trie));
Trie<Integer> intersection = trie.subtrie(null, true, ByteComparable.EMPTY, true);
assertEquals(asList(-1), toList(intersection));
intersection = trie.subtrie(null, true, ByteComparable.EMPTY, false);
assertEquals(asList(), toList(intersection));
intersection = trie.subtrie(ByteComparable.EMPTY, true, ByteComparable.EMPTY, true);
assertEquals(asList(-1), toList(intersection));
intersection = trie.subtrie(ByteComparable.EMPTY, false, ByteComparable.EMPTY, true);
assertEquals(asList(), toList(intersection));
intersection = trie.subtrie(ByteComparable.EMPTY, true, ByteComparable.EMPTY, false);
assertEquals(asList(), toList(intersection));
// (empty, empty) is an invalid call as the "(empty" is greater than "empty)"
}
@Test
public void testSubtrieOnSubtrie()
{
Trie<Integer> trie = singleLevelIntTrie(15);
// non-overlapping
Trie<Integer> intersection = trie.subtrie(of(0), of(4)).subtrie(of(4), of(8));
assertEquals(asList(), toList(intersection));
// touching
intersection = trie.subtrie(of(0), true, of(3), true).subtrie(of(3), of(8));
assertEquals(asList(3), toList(intersection));
// overlapping 1
intersection = trie.subtrie(of(0), of(4)).subtrie(of(2), of(8));
assertEquals(asList(2, 3), toList(intersection));
// overlapping 2
intersection = trie.subtrie(of(0), of(4)).subtrie(of(1), of(8));
assertEquals(asList(1, 2, 3), toList(intersection));
// covered
intersection = trie.subtrie(of(0), of(4)).subtrie(of(0), of(8));
assertEquals(asList(0, 1, 2, 3), toList(intersection));
// covered 2
intersection = trie.subtrie(of(4), true, of(8), true).subtrie(of(0), of(8));
assertEquals(asList(4, 5, 6, 7), toList(intersection));
// covered 3
intersection = trie.subtrie(of(1), false, of(4), true).subtrie(of(0), of(8));
assertEquals(asList(2, 3, 4), toList(intersection));
}
}