blob: 4dc9e3f1e3a4b2128b0a586eae22a4536066ad7e [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.index.sasi.utils;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import org.apache.cassandra.index.sasi.disk.Token;
import org.apache.cassandra.index.sasi.utils.RangeIntersectionIterator.Strategy;
import org.apache.cassandra.index.sasi.utils.RangeIntersectionIterator.LookupIntersectionIterator;
import org.apache.cassandra.index.sasi.utils.RangeIntersectionIterator.BounceIntersectionIterator;
import org.apache.cassandra.io.util.FileUtils;
import com.carrotsearch.hppc.LongOpenHashSet;
import com.carrotsearch.hppc.LongSet;
import org.junit.Assert;
import org.junit.Test;
import static org.apache.cassandra.index.sasi.utils.LongIterator.convert;
public class RangeIntersectionIteratorTest
{
@Test
public void testNoOverlappingValues()
{
for (Strategy strategy : Strategy.values())
testNoOverlappingValues(strategy);
}
private void testNoOverlappingValues(Strategy strategy)
{
RangeIterator.Builder<Long, Token> builder = RangeIntersectionIterator.builder(strategy);
builder.add(new LongIterator(new long[] { 2L, 3L, 5L, 6L }));
builder.add(new LongIterator(new long[] { 1L, 7L }));
builder.add(new LongIterator(new long[] { 4L, 8L, 9L, 10L }));
Assert.assertEquals(convert(), convert(builder.build()));
builder = RangeIntersectionIterator.builder(strategy);
// both ranges overlap by min/max but not by value
builder.add(new LongIterator(new long[] { 1L, 5L, 7L, 9L }));
builder.add(new LongIterator(new long[] { 6L }));
RangeIterator<Long, Token> range = builder.build();
Assert.assertNotNull(range);
Assert.assertFalse(range.hasNext());
builder = RangeIntersectionIterator.builder(strategy);
// both ranges overlap by min/max but not by value
builder.add(new LongIterator(new long[] { 1L, 5L, 7L, 9L }));
builder.add(new LongIterator(new long[] { 0L, 10L, 12L }));
range = builder.build();
Assert.assertNotNull(range);
Assert.assertFalse(range.hasNext());
}
@Test
public void testOverlappingValues()
{
for (Strategy strategy : Strategy.values())
testOverlappingValues(strategy);
}
private void testOverlappingValues(Strategy strategy)
{
RangeIterator.Builder<Long, Token> builder = RangeIntersectionIterator.builder(strategy);
builder.add(new LongIterator(new long[] { 1L, 4L, 6L, 7L }));
builder.add(new LongIterator(new long[] { 2L, 4L, 5L, 6L }));
builder.add(new LongIterator(new long[] { 4L, 6L, 8L, 9L, 10L }));
Assert.assertEquals(convert(4L, 6L), convert(builder.build()));
}
@Test
public void testSingleIterator()
{
for (Strategy strategy : Strategy.values())
testSingleIterator(strategy);
}
private void testSingleIterator(Strategy strategy)
{
RangeIntersectionIterator.Builder<Long, Token> builder = RangeIntersectionIterator.builder(strategy);
builder.add(new LongIterator(new long[] { 1L, 2L, 4L, 9L }));
Assert.assertEquals(convert(1L, 2L, 4L, 9L), convert(builder.build()));
}
@Test
public void testSkipTo()
{
for (Strategy strategy : Strategy.values())
testSkipTo(strategy);
}
private void testSkipTo(Strategy strategy)
{
RangeIterator.Builder<Long, Token> builder = RangeIntersectionIterator.builder(strategy);
builder.add(new LongIterator(new long[] { 1L, 4L, 6L, 7L, 9L, 10L }));
builder.add(new LongIterator(new long[] { 2L, 4L, 5L, 6L, 7L, 10L, 12L }));
builder.add(new LongIterator(new long[] { 4L, 6L, 7L, 9L, 10L }));
RangeIterator<Long, Token> range = builder.build();
Assert.assertNotNull(range);
// first let's skipTo something before range
Assert.assertEquals(4L, (long) range.skipTo(3L).get());
Assert.assertEquals(4L, (long) range.getCurrent());
// now let's skip right to the send value
Assert.assertEquals(6L, (long) range.skipTo(5L).get());
Assert.assertEquals(6L, (long) range.getCurrent());
// now right to the element
Assert.assertEquals(7L, (long) range.skipTo(7L).get());
Assert.assertEquals(7L, (long) range.getCurrent());
Assert.assertEquals(7L, (long) range.next().get());
Assert.assertTrue(range.hasNext());
Assert.assertEquals(10L, (long) range.getCurrent());
// now right after the last element
Assert.assertNull(range.skipTo(11L));
Assert.assertFalse(range.hasNext());
}
@Test
public void testMinMaxAndCount()
{
for (Strategy strategy : Strategy.values())
testMinMaxAndCount(strategy);
}
private void testMinMaxAndCount(Strategy strategy)
{
RangeIterator.Builder<Long, Token> builder = RangeIntersectionIterator.builder(strategy);
builder.add(new LongIterator(new long[]{1L, 2L, 9L}));
builder.add(new LongIterator(new long[]{4L, 5L, 9L}));
builder.add(new LongIterator(new long[]{7L, 8L, 9L}));
Assert.assertEquals(9L, (long) builder.getMaximum());
Assert.assertEquals(9L, builder.getTokenCount());
RangeIterator<Long, Token> tokens = builder.build();
Assert.assertNotNull(tokens);
Assert.assertEquals(7L, (long) tokens.getMinimum());
Assert.assertEquals(9L, (long) tokens.getMaximum());
Assert.assertEquals(9L, tokens.getCount());
Assert.assertEquals(convert(9L), convert(builder.build()));
}
@Test
public void testBuilder()
{
for (Strategy strategy : Strategy.values())
testBuilder(strategy);
}
private void testBuilder(Strategy strategy)
{
RangeIterator.Builder<Long, Token> builder = RangeIntersectionIterator.builder(strategy);
Assert.assertNull(builder.getMinimum());
Assert.assertNull(builder.getMaximum());
Assert.assertEquals(0L, builder.getTokenCount());
Assert.assertEquals(0L, builder.rangeCount());
builder.add(new LongIterator(new long[] { 1L, 2L, 6L }));
builder.add(new LongIterator(new long[] { 4L, 5L, 6L }));
builder.add(new LongIterator(new long[] { 6L, 8L, 9L }));
Assert.assertEquals(6L, (long) builder.getMinimum());
Assert.assertEquals(6L, (long) builder.getMaximum());
Assert.assertEquals(9L, builder.getTokenCount());
Assert.assertEquals(3L, builder.rangeCount());
Assert.assertFalse(builder.statistics.isDisjoint());
Assert.assertEquals(1L, (long) builder.ranges.poll().getMinimum());
Assert.assertEquals(4L, (long) builder.ranges.poll().getMinimum());
Assert.assertEquals(6L, (long) builder.ranges.poll().getMinimum());
builder.add(new LongIterator(new long[] { 1L, 2L, 6L }));
builder.add(new LongIterator(new long[] { 4L, 5L, 6L }));
builder.add(new LongIterator(new long[] { 6L, 8L, 9L }));
Assert.assertEquals(convert(6L), convert(builder.build()));
builder = RangeIntersectionIterator.builder(strategy);
builder.add(new LongIterator(new long[]{ 1L, 5L, 6L }));
builder.add(new LongIterator(new long[]{ 3L, 5L, 6L }));
RangeIterator<Long, Token> tokens = builder.build();
Assert.assertEquals(convert(5L, 6L), convert(tokens));
FileUtils.closeQuietly(tokens);
RangeIterator emptyTokens = RangeIntersectionIterator.builder(strategy).build();
Assert.assertEquals(0, emptyTokens.getCount());
builder = RangeIntersectionIterator.builder(strategy);
Assert.assertEquals(0L, builder.add((RangeIterator<Long, Token>) null).rangeCount());
Assert.assertEquals(0L, builder.add((List<RangeIterator<Long, Token>>) null).getTokenCount());
Assert.assertEquals(0L, builder.add(new LongIterator(new long[] {})).rangeCount());
RangeIterator<Long, Token> single = new LongIterator(new long[] { 1L, 2L, 3L });
RangeIterator<Long, Token> range = RangeIntersectionIterator.<Long, Token>builder().add(single).build();
// because build should return first element if it's only one instead of building yet another iterator
Assert.assertEquals(range, single);
// Make a difference between empty and null ranges.
builder = RangeIntersectionIterator.builder(strategy);
builder.add(new LongIterator(new long[] {}));
Assert.assertEquals(0L, builder.rangeCount());
builder.add(single);
Assert.assertEquals(1L, builder.rangeCount());
range = builder.build();
Assert.assertEquals(0, range.getCount());
// disjoint case
builder = RangeIntersectionIterator.builder();
builder.add(new LongIterator(new long[] { 1L, 2L, 3L }));
builder.add(new LongIterator(new long[] { 4L, 5L, 6L }));
Assert.assertTrue(builder.statistics.isDisjoint());
RangeIterator<Long, Token> disjointIntersection = builder.build();
Assert.assertNotNull(disjointIntersection);
Assert.assertFalse(disjointIntersection.hasNext());
}
@Test
public void emptyRangeTest() {
RangeIterator.Builder<Long, Token> builder;
// empty, then non-empty
builder = RangeIntersectionIterator.builder();
builder.add(new LongIterator(new long[] {}));
builder.add(new LongIterator(new long[] {10}));
assertEmpty(builder.build());
builder = RangeIntersectionIterator.builder();
builder.add(new LongIterator(new long[] {}));
for (int i = 0; i < 10; i++)
builder.add(new LongIterator(new long[] {0, i + 10}));
assertEmpty(builder.build());
// non-empty, then empty
builder = RangeIntersectionIterator.builder();
builder.add(new LongIterator(new long[] {10}));
builder.add(new LongIterator(new long[] {}));
assertEmpty(builder.build());
builder = RangeIntersectionIterator.builder();
for (int i = 0; i < 10; i++)
builder.add(new LongIterator(new long[] {0, i + 10}));
builder.add(new LongIterator(new long[] {}));
assertEmpty(builder.build());
// empty, then non-empty then empty again
builder = RangeIntersectionIterator.builder();
builder.add(new LongIterator(new long[] {}));
builder.add(new LongIterator(new long[] {0, 10}));
builder.add(new LongIterator(new long[] {}));
assertEmpty(builder.build());
builder = RangeIntersectionIterator.builder();
builder.add(new LongIterator(new long[] {}));
for (int i = 0; i < 10; i++)
builder.add(new LongIterator(new long[] {0, i + 10}));
builder.add(new LongIterator(new long[] {}));
assertEmpty(builder.build());
// non-empty, empty, then non-empty again
builder = RangeIntersectionIterator.builder();
builder.add(new LongIterator(new long[] {0, 10}));
builder.add(new LongIterator(new long[] {}));
builder.add(new LongIterator(new long[] {0, 10}));
assertEmpty(builder.build());
builder = RangeIntersectionIterator.builder();
for (int i = 0; i < 5; i++)
builder.add(new LongIterator(new long[] {0, i + 10}));
builder.add(new LongIterator(new long[] {}));
for (int i = 5; i < 10; i++)
builder.add(new LongIterator(new long[] {0, i + 10}));
assertEmpty(builder.build());
}
public static void assertEmpty(RangeIterator<Long, Token> range)
{
Assert.assertNull(range.getMinimum());
Assert.assertNull(range.getMaximum());
Assert.assertFalse(range.hasNext());
Assert.assertEquals(0, range.getCount());
}
@Test
public void testClose() throws IOException
{
for (Strategy strategy : Strategy.values())
testClose(strategy);
}
private void testClose(Strategy strategy) throws IOException
{
RangeIterator<Long, Token> tokens = RangeIntersectionIterator.<Long, Token>builder(strategy)
.add(new LongIterator(new long[] { 1L, 2L, 3L }))
.build();
Assert.assertNotNull(tokens);
tokens.close();
}
@Test
public void testIsOverlapping()
{
RangeIterator<Long, Token> rangeA, rangeB;
rangeA = new LongIterator(new long[] { 1L, 5L });
rangeB = new LongIterator(new long[] { 5L, 9L });
Assert.assertTrue(RangeIterator.isOverlapping(rangeA, rangeB));
rangeA = new LongIterator(new long[] { 5L, 9L });
rangeB = new LongIterator(new long[] { 1L, 6L });
Assert.assertTrue(RangeIterator.isOverlapping(rangeA, rangeB));
rangeA = new LongIterator(new long[] { 5L, 9L });
rangeB = new LongIterator(new long[] { 5L, 9L });
Assert.assertTrue(RangeIterator.isOverlapping(rangeA, rangeB));
rangeA = new LongIterator(new long[] { 1L, 4L });
rangeB = new LongIterator(new long[] { 5L, 9L });
Assert.assertFalse(RangeIterator.isOverlapping(rangeA, rangeB));
rangeA = new LongIterator(new long[] { 6L, 9L });
rangeB = new LongIterator(new long[] { 1L, 4L });
Assert.assertFalse(RangeIterator.isOverlapping(rangeA, rangeB));
}
@Test
public void testIntersectionOfRandomRanges()
{
for (Strategy strategy : Strategy.values())
testIntersectionOfRandomRanges(strategy);
}
private void testIntersectionOfRandomRanges(Strategy strategy)
{
for (int attempt = 0; attempt < 16; attempt++)
{
final ThreadLocalRandom random = ThreadLocalRandom.current();
final int maxRanges = random.nextInt(2, 16);
// generate randomize ranges
long[][] ranges = new long[maxRanges][];
for (int i = 0; i < ranges.length; i++)
{
int rangeSize = random.nextInt(16, 512);
LongSet range = new LongOpenHashSet(rangeSize);
for (int j = 0; j < rangeSize; j++)
range.add(random.nextLong(0, 100));
ranges[i] = range.toArray();
Arrays.sort(ranges[i]);
}
List<Long> expected = new ArrayList<>();
// determine unique tokens which intersect every range
for (long token : ranges[0])
{
boolean intersectsAll = true;
for (int i = 1; i < ranges.length; i++)
{
if (Arrays.binarySearch(ranges[i], token) < 0)
{
intersectsAll = false;
break;
}
}
if (intersectsAll)
expected.add(token);
}
RangeIterator.Builder<Long, Token> builder = RangeIntersectionIterator.builder(strategy);
for (long[] range : ranges)
builder.add(new LongIterator(range));
Assert.assertEquals(expected, convert(builder.build()));
}
}
@Test
public void testIteratorPeeking()
{
RangeIterator.Builder<Long, Token> builder = RangeIntersectionIterator.builder();
// iterator with only one element
builder.add(new LongIterator(new long[] { 10L }));
// iterator with 150 elements (lookup is going to be advantageous over bound in this case)
long[] tokens = new long[150];
for (int i = 0; i < tokens.length; i++)
tokens[i] = i;
builder.add(new LongIterator(tokens));
RangeIterator<Long, Token> intersection = builder.build();
Assert.assertNotNull(intersection);
Assert.assertEquals(LookupIntersectionIterator.class, intersection.getClass());
Assert.assertTrue(intersection.hasNext());
Assert.assertEquals(convert(10L), convert(intersection));
builder = RangeIntersectionIterator.builder();
builder.add(new LongIterator(new long[] { 1L, 3L, 5L, 7L, 9L }));
builder.add(new LongIterator(new long[] { 1L, 2L, 5L, 6L }));
intersection = builder.build();
// in the situation when there is a similar number of elements inside ranges
// ping-pong (bounce) intersection is preferred as it covers gaps quicker then linear scan + lookup.
Assert.assertNotNull(intersection);
Assert.assertEquals(BounceIntersectionIterator.class, intersection.getClass());
Assert.assertTrue(intersection.hasNext());
Assert.assertEquals(convert(1L, 5L), convert(intersection));
}
}