blob: 56309e82c1cc1e88b68932cfb1be1f5b7cb5fdac [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.utils.bytecomparable;
import java.math.BigDecimal;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.Util;
import org.apache.cassandra.db.Clustering;
import org.apache.cassandra.db.ClusteringComparator;
import org.apache.cassandra.db.ClusteringPrefix;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.marshal.*;
import org.apache.cassandra.dht.ByteOrderedPartitioner;
import org.apache.cassandra.dht.IPartitioner;
import org.apache.cassandra.dht.LocalPartitioner;
import org.apache.cassandra.dht.Murmur3Partitioner;
import org.apache.cassandra.dht.RandomPartitioner;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.MurmurHash;
import org.apache.cassandra.utils.TimeUUID;
import org.apache.cassandra.utils.bytecomparable.ByteComparable.Version;
import static org.junit.Assert.assertEquals;
/**
* Tests forward conversion to ByteSource/ByteComparable and that the result compares correctly.
*/
public class ByteSourceComparisonTest extends ByteSourceTestBase
{
private final static Logger logger = LoggerFactory.getLogger(ByteSourceComparisonTest.class);
@Rule
public final ExpectedException expectedException = ExpectedException.none();
@Test
public void testStringsAscii()
{
testType(AsciiType.instance, testStrings);
}
@Test
public void testStringsUTF8()
{
testType(UTF8Type.instance, testStrings);
testDirect(x -> ByteSource.of(x, Version.OSS50), Ordering.<String>natural()::compare, testStrings);
}
@Test
public void testBooleans()
{
testType(BooleanType.instance, testBools);
}
@Test
public void testInts()
{
testType(Int32Type.instance, testInts);
testDirect(x -> ByteSource.of(x), Integer::compare, testInts);
}
@Test
public void randomTestInts()
{
Random rand = new Random();
for (int i=0; i<10000; ++i)
{
int i1 = rand.nextInt();
int i2 = rand.nextInt();
assertComparesSame(Int32Type.instance, i1, i2);
}
}
@Test
public void testLongs()
{
testType(LongType.instance, testLongs);
testDirect(x -> ByteSource.of(x), Long::compare, testLongs);
}
@Test
public void testShorts()
{
testType(ShortType.instance, testShorts);
}
@Test
public void testBytes()
{
testType(ByteType.instance, testBytes);
}
@Test
public void testDoubles()
{
testType(DoubleType.instance, testDoubles);
}
@Test
public void testFloats()
{
testType(FloatType.instance, testFloats);
}
@Test
public void testBigInts()
{
testType(IntegerType.instance, testBigInts);
}
@Test
public void testBigDecimals()
{
testType(DecimalType.instance, testBigDecimals);
}
@Test
public void testBigDecimalInCombination()
{
BigDecimal b1 = new BigDecimal("123456.78901201");
BigDecimal b2 = new BigDecimal("123456.789012");
Boolean b = false;
assertClusteringPairComparesSame(DecimalType.instance, BooleanType.instance, b1, b, b2, b);
assertClusteringPairComparesSame(BooleanType.instance, DecimalType.instance, b, b1, b, b2);
b1 = b1.negate();
b2 = b2.negate();
assertClusteringPairComparesSame(DecimalType.instance, BooleanType.instance, b1, b, b2, b);
assertClusteringPairComparesSame(BooleanType.instance, DecimalType.instance, b, b1, b, b2);
b1 = new BigDecimal("-123456.78901289");
b2 = new BigDecimal("-123456.789012");
assertClusteringPairComparesSame(DecimalType.instance, BooleanType.instance, b1, b, b2, b);
assertClusteringPairComparesSame(BooleanType.instance, DecimalType.instance, b, b1, b, b2);
b1 = new BigDecimal("1");
b2 = new BigDecimal("1.1");
assertClusteringPairComparesSame(DecimalType.instance, BooleanType.instance, b1, b, b2, b);
assertClusteringPairComparesSame(BooleanType.instance, DecimalType.instance, b, b1, b, b2);
b1 = b1.negate();
b2 = b2.negate();
assertClusteringPairComparesSame(DecimalType.instance, BooleanType.instance, b1, b, b2, b);
assertClusteringPairComparesSame(BooleanType.instance, DecimalType.instance, b, b1, b, b2);
}
@Test
public void testUUIDs()
{
testType(UUIDType.instance, testUUIDs);
}
@Test
public void testTimeUUIDs()
{
testType(TimeUUIDType.instance, Arrays.stream(testUUIDs)
.filter(x -> x == null || x.version() == 1)
.map(x -> x != null ? TimeUUID.fromUuid(x) : null)
.toArray());
}
@Test
public void testLexicalUUIDs()
{
testType(LexicalUUIDType.instance, testUUIDs);
}
@Test
public void testSimpleDate()
{
testType(SimpleDateType.instance, Arrays.stream(testInts).filter(x -> x != null).toArray());
}
@Test
public void testTimeType()
{
testType(TimeType.instance, Arrays.stream(testLongs).filter(x -> x != null && x >= 0 && x <= 24L * 60 * 60 * 1000 * 1000 * 1000).toArray());
}
@SuppressWarnings("deprecation")
@Test
public void testDateType()
{
testType(DateType.instance, testDates);
}
@Test
public void testTimestampType()
{
testType(TimestampType.instance, testDates);
}
@Test
public void testBytesType()
{
List<ByteBuffer> values = new ArrayList<>();
for (int i = 0; i < testValues.length; ++i)
for (Object o : testValues[i])
values.add(testTypes[i].decompose(o));
testType(BytesType.instance, values.toArray());
}
@Test
public void testInetAddressType() throws UnknownHostException
{
testType(InetAddressType.instance, testInets);
}
@Test
public void testEmptyType()
{
testType(EmptyType.instance, new Void[] { null });
}
@Test
public void testPatitionerDefinedOrder()
{
List<ByteBuffer> values = new ArrayList<>();
for (int i = 0; i < testValues.length; ++i)
for (Object o : testValues[i])
values.add(testTypes[i].decompose(o));
testBuffers(new PartitionerDefinedOrder(Murmur3Partitioner.instance), values);
testBuffers(new PartitionerDefinedOrder(RandomPartitioner.instance), values);
testBuffers(new PartitionerDefinedOrder(ByteOrderedPartitioner.instance), values);
}
@Test
public void testPatitionerOrder()
{
List<ByteBuffer> values = new ArrayList<>();
for (int i = 0; i < testValues.length; ++i)
for (Object o : testValues[i])
values.add(testTypes[i].decompose(o));
testDecoratedKeys(Murmur3Partitioner.instance, values);
testDecoratedKeys(RandomPartitioner.instance, values);
testDecoratedKeys(ByteOrderedPartitioner.instance, values);
}
@Test
public void testLocalPatitionerOrder()
{
for (int i = 0; i < testValues.length; ++i)
{
final AbstractType testType = testTypes[i];
testDecoratedKeys(new LocalPartitioner(testType), Lists.transform(Arrays.asList(testValues[i]),
v -> testType.decompose(v)));
}
}
interface PairTester
{
void test(AbstractType t1, AbstractType t2, Object o1, Object o2, Object o3, Object o4);
}
void testCombinationSampling(Random rand, PairTester tester)
{
for (int i=0;i<testTypes.length;++i)
for (int j=0;j<testTypes.length;++j)
{
Object[] tv1 = new Object[3];
Object[] tv2 = new Object[3];
for (int t=0; t<tv1.length; ++t)
{
tv1[t] = testValues[i][rand.nextInt(testValues[i].length)];
tv2[t] = testValues[j][rand.nextInt(testValues[j].length)];
}
for (Object o1 : tv1)
for (Object o2 : tv2)
for (Object o3 : tv1)
for (Object o4 : tv2)
{
tester.test(testTypes[i], testTypes[j], o1, o2, o3, o4);
}
}
}
@Test
public void testCombinations()
{
Random rand = new Random(0);
testCombinationSampling(rand, this::assertClusteringPairComparesSame);
}
@Test
public void testNullsInClustering()
{
ByteBuffer[][] inputs = new ByteBuffer[][]
{
new ByteBuffer[] {decomposeAndRandomPad(UTF8Type.instance, "a"),
decomposeAndRandomPad(Int32Type.instance, 0)},
new ByteBuffer[] {decomposeAndRandomPad(UTF8Type.instance, "a"),
decomposeAndRandomPad(Int32Type.instance, null)},
new ByteBuffer[] {decomposeAndRandomPad(UTF8Type.instance, "a"),
null},
new ByteBuffer[] {decomposeAndRandomPad(UTF8Type.instance, ""),
decomposeAndRandomPad(Int32Type.instance, 0)},
new ByteBuffer[] {decomposeAndRandomPad(UTF8Type.instance, ""),
decomposeAndRandomPad(Int32Type.instance, null)},
new ByteBuffer[] {decomposeAndRandomPad(UTF8Type.instance, ""),
null},
new ByteBuffer[] {null,
decomposeAndRandomPad(Int32Type.instance, 0)},
new ByteBuffer[] {null,
decomposeAndRandomPad(Int32Type.instance, null)},
new ByteBuffer[] {null,
null}
};
for (ByteBuffer[] input1 : inputs)
for (ByteBuffer[] input2 : inputs)
{
assertClusteringPairComparesSame(UTF8Type.instance, Int32Type.instance,
input1[0], input1[1], input2[0], input2[1],
(t, v) -> (ByteBuffer) v,
input1[0] != null && input1[1] != null && input2[0] != null && input2[1] != null);
}
}
@Test
public void testNullsInClusteringLegacy()
{
// verify the legacy encoding treats null clustering the same as null value
ClusteringPrefix<ByteBuffer> aNull = makeBound(ClusteringPrefix.Kind.CLUSTERING,
decomposeAndRandomPad(UTF8Type.instance, "a"),
decomposeAndRandomPad(Int32Type.instance, null));
ClusteringPrefix<ByteBuffer> aEmpty = makeBound(ClusteringPrefix.Kind.CLUSTERING,
decomposeAndRandomPad(UTF8Type.instance, "a"),
null);
ClusteringComparator comp = new ClusteringComparator(UTF8Type.instance, Int32Type.instance);
assertEquals(0, ByteComparable.compare(comp.asByteComparable(aNull), comp.asByteComparable(aEmpty), Version.LEGACY));
ClusteringComparator compReversed = new ClusteringComparator(UTF8Type.instance, ReversedType.getInstance(Int32Type.instance));
assertEquals(0, ByteComparable.compare(compReversed.asByteComparable(aNull), compReversed.asByteComparable(aEmpty), Version.LEGACY));
}
@Test
public void testEmptyClustering()
{
assertEmptyComparedToStatic(1, ClusteringPrefix.Kind.CLUSTERING, Version.OSS50);
assertEmptyComparedToStatic(0, ClusteringPrefix.Kind.STATIC_CLUSTERING, Version.OSS50);
assertEmptyComparedToStatic(1, ClusteringPrefix.Kind.INCL_START_BOUND, Version.OSS50);
assertEmptyComparedToStatic(1, ClusteringPrefix.Kind.INCL_END_BOUND, Version.OSS50);
assertEmptyComparedToStatic(1, ClusteringPrefix.Kind.CLUSTERING, Version.LEGACY);
assertEmptyComparedToStatic(0, ClusteringPrefix.Kind.STATIC_CLUSTERING, Version.LEGACY);
assertEmptyComparedToStatic(-1, ClusteringPrefix.Kind.INCL_START_BOUND, Version.LEGACY);
assertEmptyComparedToStatic(1, ClusteringPrefix.Kind.INCL_END_BOUND, Version.LEGACY);
}
private void assertEmptyComparedToStatic(int expected, ClusteringPrefix.Kind kind, Version version)
{
ClusteringPrefix<ByteBuffer> empty = makeBound(kind);
ClusteringComparator compEmpty = new ClusteringComparator();
assertEquals(expected, Integer.signum(ByteComparable.compare(compEmpty.asByteComparable(empty),
compEmpty.asByteComparable(Clustering.STATIC_CLUSTERING),
version)));
}
void assertClusteringPairComparesSame(AbstractType<?> t1, AbstractType<?> t2, Object o1, Object o2, Object o3, Object o4)
{
assertClusteringPairComparesSame(t1, t2, o1, o2, o3, o4, AbstractType::decompose, true);
}
void assertClusteringPairComparesSame(AbstractType<?> t1, AbstractType<?> t2,
Object o1, Object o2, Object o3, Object o4,
BiFunction<AbstractType, Object, ByteBuffer> decompose,
boolean testLegacy)
{
EnumSet<ClusteringPrefix.Kind> skippedKinds = EnumSet.of(ClusteringPrefix.Kind.SSTABLE_LOWER_BOUND, ClusteringPrefix.Kind.SSTABLE_UPPER_BOUND);
for (Version v : Version.values())
for (ClusteringPrefix.Kind k1 : EnumSet.complementOf(skippedKinds))
for (ClusteringPrefix.Kind k2 : EnumSet.complementOf(skippedKinds))
{
if (!testLegacy && v == Version.LEGACY)
continue;
ClusteringComparator comp = new ClusteringComparator(t1, t2);
ByteBuffer[] b = new ByteBuffer[2];
ByteBuffer[] d = new ByteBuffer[2];
b[0] = decompose.apply(t1, o1);
b[1] = decompose.apply(t2, o2);
d[0] = decompose.apply(t1, o3);
d[1] = decompose.apply(t2, o4);
ClusteringPrefix<ByteBuffer> c = makeBound(k1, b);
ClusteringPrefix<ByteBuffer> e = makeBound(k2, d);
final ByteComparable bsc = comp.asByteComparable(c);
final ByteComparable bse = comp.asByteComparable(e);
int expected = Integer.signum(comp.compare(c, e));
assertEquals(String.format("Failed comparing %s and %s, %s vs %s version %s",
safeStr(c.clusteringString(comp.subtypes())),
safeStr(e.clusteringString(comp.subtypes())), bsc, bse, v),
expected, Integer.signum(ByteComparable.compare(bsc, bse, v)));
maybeCheck41Properties(expected, bsc, bse, v);
maybeAssertNotPrefix(bsc, bse, v);
ClusteringComparator compR = new ClusteringComparator(ReversedType.getInstance(t1), ReversedType.getInstance(t2));
final ByteComparable bsrc = compR.asByteComparable(c);
final ByteComparable bsre = compR.asByteComparable(e);
int expectedR = Integer.signum(compR.compare(c, e));
assertEquals(String.format("Failed comparing reversed %s and %s, %s vs %s version %s",
safeStr(c.clusteringString(comp.subtypes())),
safeStr(e.clusteringString(comp.subtypes())), bsrc, bsre, v),
expectedR, Integer.signum(ByteComparable.compare(bsrc, bsre, v)));
maybeCheck41Properties(expectedR, bsrc, bsre, v);
maybeAssertNotPrefix(bsrc, bsre, v);
}
}
static ClusteringPrefix<ByteBuffer> makeBound(ClusteringPrefix.Kind k1, ByteBuffer... b)
{
return makeBound(ByteBufferAccessor.instance.factory(), k1, b);
}
static <T> ClusteringPrefix<T> makeBound(ValueAccessor.ObjectFactory<T> factory, ClusteringPrefix.Kind k1, T[] b)
{
switch (k1)
{
case INCL_END_EXCL_START_BOUNDARY:
case EXCL_END_INCL_START_BOUNDARY:
return factory.boundary(k1, b);
case INCL_END_BOUND:
case EXCL_END_BOUND:
case INCL_START_BOUND:
case EXCL_START_BOUND:
return factory.bound(k1, b);
case CLUSTERING:
return factory.clustering(b);
case STATIC_CLUSTERING:
return factory.staticClustering();
default:
throw new AssertionError(k1);
}
}
@Test
public void testTupleType()
{
Random rand = ThreadLocalRandom.current();
testCombinationSampling(rand, this::assertTupleComparesSame);
}
@Test
public void testTupleTypeNonFull()
{
TupleType tt = new TupleType(ImmutableList.of(UTF8Type.instance, Int32Type.instance));
List<ByteBuffer> tests = ImmutableList.of
(
TupleType.buildValue(ByteBufferAccessor.instance,
decomposeAndRandomPad(UTF8Type.instance, ""),
decomposeAndRandomPad(Int32Type.instance, 0)),
// Note: a decomposed null (e.g. decomposeAndRandomPad(Int32Type.instance, null)) should not reach a tuple
TupleType.buildValue(ByteBufferAccessor.instance,
decomposeAndRandomPad(UTF8Type.instance, ""),
null),
TupleType.buildValue(ByteBufferAccessor.instance,
null,
decomposeAndRandomPad(Int32Type.instance, 0)),
TupleType.buildValue(ByteBufferAccessor.instance,
decomposeAndRandomPad(UTF8Type.instance, "")),
TupleType.buildValue(ByteBufferAccessor.instance, (ByteBuffer) null),
TupleType.buildValue(ByteBufferAccessor.instance)
);
testBuffers(tt, tests);
}
@Test
public void testTupleNewField()
{
TupleType t1 = new TupleType(ImmutableList.of(UTF8Type.instance));
TupleType t2 = new TupleType(ImmutableList.of(UTF8Type.instance, Int32Type.instance));
ByteBuffer vOne = TupleType.buildValue(ByteBufferAccessor.instance,
decomposeAndRandomPad(UTF8Type.instance, "str"));
ByteBuffer vOneAndNull = TupleType.buildValue(ByteBufferAccessor.instance,
decomposeAndRandomPad(UTF8Type.instance, "str"),
null);
ByteComparable bOne1 = typeToComparable(t1, vOne);
ByteComparable bOne2 = typeToComparable(t2, vOne);
ByteComparable bOneAndNull2 = typeToComparable(t2, vOneAndNull);
assertEquals("The byte-comparable version of a one-field tuple must be the same as a two-field tuple with non-present second component.",
bOne1.byteComparableAsString(Version.OSS50),
bOne2.byteComparableAsString(Version.OSS50));
assertEquals("The byte-comparable version of a one-field tuple must be the same as a two-field tuple with null as second component.",
bOne1.byteComparableAsString(Version.OSS50),
bOneAndNull2.byteComparableAsString(Version.OSS50));
}
void assertTupleComparesSame(AbstractType t1, AbstractType t2, Object o1, Object o2, Object o3, Object o4)
{
TupleType tt = new TupleType(ImmutableList.of(t1, t2));
ByteBuffer b1 = TupleType.buildValue(ByteBufferAccessor.instance,
decomposeForTuple(t1, o1),
decomposeForTuple(t2, o2));
ByteBuffer b2 = TupleType.buildValue(ByteBufferAccessor.instance,
decomposeForTuple(t1, o3),
decomposeForTuple(t2, o4));
assertComparesSameBuffers(tt, b1, b2);
}
static <T> ByteBuffer decomposeForTuple(AbstractType<T> t, T o)
{
return o != null ? t.decompose(o) : null;
}
@Test
public void testCompositeType()
{
Random rand = new Random(0);
testCombinationSampling(rand, this::assertCompositeComparesSame);
}
@Test
public void testCompositeTypeNonFull()
{
CompositeType tt = CompositeType.getInstance(UTF8Type.instance, Int32Type.instance);
List<ByteBuffer> tests = ImmutableList.of
(
CompositeType.build(ByteBufferAccessor.instance, decomposeAndRandomPad(UTF8Type.instance, ""), decomposeAndRandomPad(Int32Type.instance, 0)),
CompositeType.build(ByteBufferAccessor.instance, decomposeAndRandomPad(UTF8Type.instance, ""), decomposeAndRandomPad(Int32Type.instance, null)),
CompositeType.build(ByteBufferAccessor.instance, decomposeAndRandomPad(UTF8Type.instance, "")),
CompositeType.build(ByteBufferAccessor.instance),
CompositeType.build(ByteBufferAccessor.instance, true, decomposeAndRandomPad(UTF8Type.instance, "")),
CompositeType.build(ByteBufferAccessor.instance,true)
);
for (ByteBuffer b : tests)
tt.validate(b);
testBuffers(tt, tests);
}
void assertCompositeComparesSame(AbstractType t1, AbstractType t2, Object o1, Object o2, Object o3, Object o4)
{
CompositeType tt = CompositeType.getInstance(t1, t2);
ByteBuffer b1 = CompositeType.build(ByteBufferAccessor.instance, decomposeAndRandomPad(t1, o1), decomposeAndRandomPad(t2, o2));
ByteBuffer b2 = CompositeType.build(ByteBufferAccessor.instance, decomposeAndRandomPad(t1, o3), decomposeAndRandomPad(t2, o4));
assertComparesSameBuffers(tt, b1, b2);
}
@Test
public void testDynamicComposite()
{
DynamicCompositeType tt = DynamicCompositeType.getInstance(DynamicCompositeTypeTest.aliases);
UUID[] uuids = DynamicCompositeTypeTest.uuids;
List<ByteBuffer> tests = ImmutableList.of
(
DynamicCompositeTypeTest.createDynamicCompositeKey("test1", null, -1, false, true),
DynamicCompositeTypeTest.createDynamicCompositeKey("test1", uuids[0], 24, false, true),
DynamicCompositeTypeTest.createDynamicCompositeKey("test1", uuids[0], 42, false, true),
DynamicCompositeTypeTest.createDynamicCompositeKey("test2", uuids[0], -1, false, true),
DynamicCompositeTypeTest.createDynamicCompositeKey("test2", uuids[1], 42, false, true)
);
for (ByteBuffer b : tests)
tt.validate(b);
testBuffers(tt, tests);
}
@Test
public void testListTypeString()
{
testCollection(ListType.getInstance(UTF8Type.instance, true), testStrings, () -> new ArrayList<>(), new Random());
}
@Test
public void testListTypeLong()
{
testCollection(ListType.getInstance(LongType.instance, true), testLongs, () -> new ArrayList<>(), new Random());
}
@Test
public void testSetTypeString()
{
testCollection(SetType.getInstance(UTF8Type.instance, true), testStrings, () -> new HashSet<>(), new Random());
}
@Test
public void testSetTypeLong()
{
testCollection(SetType.getInstance(LongType.instance, true), testLongs, () -> new HashSet<>(), new Random());
}
<T, CT extends Collection<T>> void testCollection(CollectionType<CT> tt, T[] values, Supplier<CT> gen, Random rand)
{
int cnt = 0;
List<CT> tests = new ArrayList<>();
tests.add(gen.get());
for (int c = 1; c <= 3; ++c)
for (int j = 0; j < 5; ++j)
{
CT l = gen.get();
for (int i = 0; i < c; ++i)
l.add(values[cnt++ % values.length]);
tests.add(l);
}
testType(tt, tests);
}
@Test
public void testMapTypeStringLong()
{
testMap(MapType.getInstance(UTF8Type.instance, LongType.instance, true), testStrings, testLongs, () -> new HashMap<>(), new Random());
}
@Test
public void testMapTypeStringLongTree()
{
testMap(MapType.getInstance(UTF8Type.instance, LongType.instance, true), testStrings, testLongs, () -> new TreeMap<>(), new Random());
}
@Test
public void testDecoratedKeyPrefixesVOSS50()
{
// This should pass with the OSS 4.1 encoding
testDecoratedKeyPrefixes(Version.OSS50);
}
@Test
public void testDecoratedKeyPrefixesVLegacy()
{
// ... and fail with the legacy encoding
try
{
testDecoratedKeyPrefixes(Version.LEGACY);
}
catch (AssertionError e)
{
// Correct path, test failing.
return;
}
Assert.fail("Test expected to fail.");
}
@Test
public void testFixedLengthWithOffset()
{
byte[] bytes = new byte[]{ 1, 2, 3, 4, 5, 6, 7, 8, 9 };
ByteSource source = ByteSource.fixedLength(bytes, 0, 1);
assertEquals(1, source.next());
assertEquals(ByteSource.END_OF_STREAM, source.next());
source = ByteSource.fixedLength(bytes, 4, 5);
assertEquals(5, source.next());
assertEquals(6, source.next());
assertEquals(7, source.next());
assertEquals(8, source.next());
assertEquals(9, source.next());
assertEquals(ByteSource.END_OF_STREAM, source.next());
ByteSource.fixedLength(bytes, 9, 0);
assertEquals(ByteSource.END_OF_STREAM, source.next());
}
@Test
public void testFixedLengthNegativeLength()
{
byte[] bytes = new byte[]{ 1, 2, 3 };
expectedException.expect(IllegalArgumentException.class);
ByteSource.fixedLength(bytes, 0, -1);
}
@Test
public void testFixedLengthNegativeOffset()
{
byte[] bytes = new byte[]{ 1, 2, 3 };
expectedException.expect(IllegalArgumentException.class);
ByteSource.fixedLength(bytes, -1, 1);
}
@Test
public void testFixedLengthOutOfBounds()
{
byte[] bytes = new byte[]{ 1, 2, 3 };
expectedException.expect(IllegalArgumentException.class);
ByteSource.fixedLength(bytes, 0, 4);
}
@Test
public void testFixedOffsetOutOfBounds()
{
byte[] bytes = new byte[]{ 1, 2, 3 };
expectedException.expect(IllegalArgumentException.class);
ByteSource.fixedLength(bytes, 4, 1);
}
@Test
public void testSeparatorGT()
{
testSeparator(ByteComparable::separatorGt, testLongs, LongType.instance);
}
@Test
public void testSeparatorPrefix()
{
testSeparator(ByteComparable::separatorPrefix, testLongs, LongType.instance);
}
@Test
public void testSeparatorPrefixViaDiffPoint()
{
testSeparator((x, y) -> version -> ByteSource.cut(y.asComparableBytes(version),
ByteComparable.diffPoint(x, y, version)),
testLongs,
LongType.instance);
}
@Test
public void testSeparatorNext()
{
// Appending a 00 byte at the end gives the immediate next possible value after x.
testSeparator((x, y) -> version -> ByteSource.cutOrRightPad(x.asComparableBytes(version),
ByteComparable.length(x, version) + 1,
0),
testLongs,
LongType.instance);
}
private <T> void testSeparator(BiFunction<ByteComparable, ByteComparable, ByteComparable> separatorMethod, T[] testValues, AbstractType<T> type)
{
for (T v1 : testValues)
for (T v2 : testValues)
{
if (v1 == null || v2 == null)
continue;
if (type.compare(type.decompose(v1), type.decompose(v2)) >= 0)
continue;
ByteComparable bc1 = getByteComparable(type, v1);
ByteComparable bc2 = getByteComparable(type, v2);
ByteComparable separator = separatorMethod.apply(bc1, bc2);
for (Version version : Version.values())
{
Assert.assertTrue("Sanity check failed", ByteComparable.compare(bc1, bc2, version) < 0);
Assert.assertTrue(String.format("Separator %s must be greater than left %s (for %s) (version %s)",
separator.byteComparableAsString(version),
bc1.byteComparableAsString(version),
v1,
version),
ByteComparable.compare(bc1, separator, version) < 0);
Assert.assertTrue(String.format("Separator %s must be less than or equal to right %s (for %s) (version %s)",
separator.byteComparableAsString(version),
bc2.byteComparableAsString(version),
v2,
version),
ByteComparable.compare(separator, bc2, version) <= 0);
}
}
}
private <T> ByteComparable getByteComparable(AbstractType<T> type, T v1)
{
return version -> type.asComparableBytes(type.decompose(v1), version);
}
public void testDecoratedKeyPrefixes(Version version)
{
testDecoratedKeyPrefixes("012345678BCDE\0", "", version);
testDecoratedKeyPrefixes("012345678ABCDE\0", "ABC", version);
testDecoratedKeyPrefixes("0123456789ABCDE\0", "\0AB", version);
testDecoratedKeyPrefixes("0123456789ABCDEF\0", "\0", version);
testDecoratedKeyPrefixes("0123456789ABCDEF0", "ABC", version);
testDecoratedKeyPrefixes("0123456789ABCDEF", "", version);
testDecoratedKeyPrefixes("0123456789ABCDE", "", version);
testDecoratedKeyPrefixes("0123456789ABCD", "\0AB", version);
testDecoratedKeyPrefixes("0123456789ABC", "\0", version);
}
public void testDecoratedKeyPrefixes(String key, String append, Version version)
{
logger.info("Testing {} + {}", safeStr(key), safeStr(append));
IPartitioner partitioner = Murmur3Partitioner.instance;
ByteBuffer original = ByteBufferUtil.bytes(key);
ByteBuffer collision = Util.generateMurmurCollision(original, append.getBytes(StandardCharsets.UTF_8));
long[] hash = new long[2];
MurmurHash.hash3_x64_128(original, 0, original.limit(), 0, hash);
logger.info(String.format("Original hash %016x,%016x", hash[0], hash[1]));
MurmurHash.hash3_x64_128(collision, 0, collision.limit(), 0, hash);
logger.info(String.format("Collision hash %016x,%016x", hash[0], hash[1]));
DecoratedKey kk1 = partitioner.decorateKey(original);
DecoratedKey kk2 = partitioner.decorateKey(collision);
logger.info("{}\n{}\n{}\n{}", kk1, kk2, kk1.byteComparableAsString(version), kk2.byteComparableAsString(version));
final ByteSource s1 = kk1.asComparableBytes(version);
final ByteSource s2 = kk2.asComparableBytes(version);
logger.info("{}\n{}", s1, s2);
// Check that the representations compare correctly
Assert.assertEquals(Long.signum(kk1.compareTo(kk2)), ByteComparable.compare(kk1, kk2, version));
// s1 must not be a prefix of s2
assertNotPrefix(s1, s2);
}
private void assertNotPrefix(ByteSource s1, ByteSource s2)
{
int c1, c2;
do
{
c1 = s1.next();
c2 = s2.next();
}
while (c1 == c2 && c1 != ByteSource.END_OF_STREAM);
// Equal is ok
if (c1 == c2)
return;
Assert.assertNotEquals("ByteComparable is a prefix of other", ByteSource.END_OF_STREAM, c1);
Assert.assertNotEquals("ByteComparable is a prefix of other", ByteSource.END_OF_STREAM, c2);
}
private int compare(ByteSource s1, ByteSource s2)
{
int c1, c2;
do
{
c1 = s1.next();
c2 = s2.next();
}
while (c1 == c2 && c1 != ByteSource.END_OF_STREAM);
return Integer.compare(c1, c2);
}
private void maybeAssertNotPrefix(ByteComparable s1, ByteComparable s2, Version version)
{
if (version == Version.OSS50)
assertNotPrefix(s1.asComparableBytes(version), s2.asComparableBytes(version));
}
private void maybeCheck41Properties(int expectedComparison, ByteComparable s1, ByteComparable s2, Version version)
{
if (version != Version.OSS50)
return;
if (s1 == null || s2 == null || 0 == expectedComparison)
return;
int b1 = randomTerminator();
int b2 = randomTerminator();
assertEquals(String.format("Comparison failed for %s(%s + %02x) and %s(%s + %02x)", s1, s1.byteComparableAsString(version), b1, s2, s2.byteComparableAsString(version), b2),
expectedComparison, Integer.signum(compare(ByteSource.withTerminator(b1, s1.asComparableBytes(version)), ByteSource.withTerminator(b2, s2.asComparableBytes(version)))));
assertNotPrefix(ByteSource.withTerminator(b1, s1.asComparableBytes(version)), ByteSource.withTerminator(b2, s2.asComparableBytes(version)));
}
private int randomTerminator()
{
int term;
do
{
term = ThreadLocalRandom.current().nextInt(ByteSource.MIN_SEPARATOR, ByteSource.MAX_SEPARATOR + 1);
}
while (term >= ByteSource.MIN_NEXT_COMPONENT && term <= ByteSource.MAX_NEXT_COMPONENT);
return term;
}
<K, V, M extends Map<K, V>> void testMap(MapType<K, V> tt, K[] keys, V[] values, Supplier<M> gen, Random rand)
{
List<M> tests = new ArrayList<>();
tests.add(gen.get());
for (int c = 1; c <= 3; ++c)
for (int j = 0; j < 5; ++j)
{
M l = gen.get();
for (int i = 0; i < c; ++i)
l.put(keys[rand.nextInt(keys.length)], values[rand.nextInt(values.length)]);
tests.add(l);
}
testType(tt, tests);
}
/*
* Convert type to a comparable.
*/
private ByteComparable typeToComparable(AbstractType<?> type, ByteBuffer value)
{
return new ByteComparable()
{
@Override
public ByteSource asComparableBytes(Version v)
{
return type.asComparableBytes(value, v);
}
@Override
public String toString()
{
return type.getString(value);
}
};
}
public <T> void testType(AbstractType<T> type, Object[] values)
{
testType(type, Iterables.transform(Arrays.asList(values), x -> (T) x));
}
public <T> void testType(AbstractType<? super T> type, Iterable<T> values)
{
for (T i : values) {
ByteBuffer b = decomposeAndRandomPad(type, i);
logger.info("Value {} ({}) bytes {} ByteSource {}",
safeStr(i),
safeStr(type.getSerializer().toCQLLiteral(b)),
safeStr(ByteBufferUtil.bytesToHex(b)),
typeToComparable(type, b).byteComparableAsString(Version.OSS50));
}
for (T i : values)
for (T j : values)
assertComparesSame(type, i, j);
if (!type.isReversed())
testType(ReversedType.getInstance(type), values);
}
public void testBuffers(AbstractType<?> type, List<ByteBuffer> values)
{
try
{
for (ByteBuffer b : values) {
logger.info("Value {} bytes {} ByteSource {}",
safeStr(type.getSerializer().toCQLLiteral(b)),
safeStr(ByteBufferUtil.bytesToHex(b)),
typeToComparable(type, b).byteComparableAsString(Version.OSS50));
}
}
catch (UnsupportedOperationException e)
{
// Continue without listing values.
}
for (ByteBuffer i : values)
for (ByteBuffer j : values)
assertComparesSameBuffers(type, i, j);
}
void assertComparesSameBuffers(AbstractType<?> type, ByteBuffer b1, ByteBuffer b2)
{
int expected = Integer.signum(type.compare(b1, b2));
final ByteComparable bs1 = typeToComparable(type, b1);
final ByteComparable bs2 = typeToComparable(type, b2);
for (Version version : Version.values())
{
int actual = Integer.signum(ByteComparable.compare(bs1, bs2, version));
assertEquals(String.format("Failed comparing %s(%s) and %s(%s)", ByteBufferUtil.bytesToHex(b1), bs1.byteComparableAsString(version), ByteBufferUtil.bytesToHex(b2), bs2.byteComparableAsString(version)),
expected,
actual);
maybeCheck41Properties(expected, bs1, bs2, version);
}
}
public void testDecoratedKeys(IPartitioner type, List<ByteBuffer> values)
{
for (ByteBuffer i : values)
for (ByteBuffer j : values)
assertComparesSameDecoratedKeys(type, i, j);
for (ByteBuffer i : values)
assertDecoratedKeyBounds(type, i);
}
void assertComparesSameDecoratedKeys(IPartitioner type, ByteBuffer b1, ByteBuffer b2)
{
DecoratedKey k1 = type.decorateKey(b1);
DecoratedKey k2 = type.decorateKey(b2);
int expected = Integer.signum(k1.compareTo(k2));
for (Version version : Version.values())
{
int actual = Integer.signum(ByteComparable.compare(k1, k2, version));
assertEquals(String.format("Failed comparing %s[%s](%s) and %s[%s](%s)\npartitioner %s version %s",
ByteBufferUtil.bytesToHex(b1),
k1,
k1.byteComparableAsString(version),
ByteBufferUtil.bytesToHex(b2),
k2,
k2.byteComparableAsString(version),
type,
version),
expected,
actual);
maybeAssertNotPrefix(k1, k2, version);
}
}
void assertDecoratedKeyBounds(IPartitioner type, ByteBuffer b)
{
Version version = Version.OSS50;
DecoratedKey k = type.decorateKey(b);
final ByteComparable after = k.asComparableBound(false);
final ByteComparable before = k.asComparableBound(true);
int actual = Integer.signum(ByteComparable.compare(k, before, version));
assertEquals(String.format("Failed comparing bound before (%s) for %s[%s](%s)\npartitioner %s version %s",
before.byteComparableAsString(version),
ByteBufferUtil.bytesToHex(b),
k,
k.byteComparableAsString(version),
type,
version),
1,
actual);
maybeAssertNotPrefix(k, before, version);
actual = Integer.signum(ByteComparable.compare(k, after, version));
assertEquals(String.format("Failed comparing bound after (%s) for %s[%s](%s)\npartitioner %s version %s",
after.byteComparableAsString(version),
ByteBufferUtil.bytesToHex(b),
k,
k.byteComparableAsString(version),
type,
version),
-1,
actual);
maybeAssertNotPrefix(k, after, version);
actual = Integer.signum(ByteComparable.compare(before, after, version));
assertEquals(String.format("Failed comparing bound before (%s) to after (%s) for %s[%s](%s)\npartitioner %s version %s",
before.byteComparableAsString(version),
after.byteComparableAsString(version),
ByteBufferUtil.bytesToHex(b),
k,
k.byteComparableAsString(version),
type,
version),
-1,
actual);
maybeAssertNotPrefix(after, before, version);
}
static Object safeStr(Object i)
{
if (i == null)
return null;
String s = i.toString();
if (s.length() > 100)
s = s.substring(0, 100) + "...";
return s.replaceAll("\0", "<0>");
}
public <T> void testDirect(Function<T, ByteSource> convertor, BiFunction<T, T, Integer> comparator, T[] values)
{
for (T i : values) {
if (i == null)
continue;
logger.info("Value {} ByteSource {}\n",
safeStr(i),
convertor.apply(i));
}
for (T i : values)
if (i != null)
for (T j : values)
if (j != null)
assertComparesSame(convertor, comparator, i, j);
}
<T> void assertComparesSame(Function<T, ByteSource> convertor, BiFunction<T, T, Integer> comparator, T v1, T v2)
{
ByteComparable b1 = v -> convertor.apply(v1);
ByteComparable b2 = v -> convertor.apply(v2);
int expected = Integer.signum(comparator.apply(v1, v2));
int actual = Integer.signum(ByteComparable.compare(b1, b2, null)); // version ignored above
assertEquals(String.format("Failed comparing %s and %s", v1, v2), expected, actual);
}
<T> void assertComparesSame(AbstractType<T> type, T v1, T v2)
{
ByteBuffer b1 = decomposeAndRandomPad(type, v1);
ByteBuffer b2 = decomposeAndRandomPad(type, v2);
int expected = Integer.signum(type.compare(b1, b2));
final ByteComparable bc1 = typeToComparable(type, b1);
final ByteComparable bc2 = typeToComparable(type, b2);
for (Version version : Version.values())
{
int actual = Integer.signum(ByteComparable.compare(bc1, bc2, version));
if (expected != actual)
{
if (type.isReversed())
{
// This can happen for reverse of nulls and prefixes. Check that it's ok within multi-component
ClusteringComparator cc = new ClusteringComparator(type);
ByteComparable c1 = cc.asByteComparable(Clustering.make(b1));
ByteComparable c2 = cc.asByteComparable(Clustering.make(b2));
int actualcc = Integer.signum(ByteComparable.compare(c1, c2, version));
if (actualcc == expected)
return;
assertEquals(String.format("Failed comparing reversed %s(%s, %s) and %s(%s, %s) direct (%d) and as clustering", safeStr(v1), ByteBufferUtil.bytesToHex(b1), c1, safeStr(v2), ByteBufferUtil.bytesToHex(b2), c2, actual), expected, actualcc);
}
else
assertEquals(String.format("Failed comparing %s(%s BC %s) and %s(%s BC %s) version %s",
safeStr(v1),
ByteBufferUtil.bytesToHex(b1),
bc1.byteComparableAsString(version),
safeStr(v2),
ByteBufferUtil.bytesToHex(b2),
bc2.byteComparableAsString(version),
version),
expected,
actual);
}
maybeCheck41Properties(expected, bc1, bc2, version);
}
}
<T> ByteBuffer decomposeAndRandomPad(AbstractType<T> type, T v)
{
ByteBuffer b = type.decompose(v);
Random rand = new Random(0);
int padBefore = rand.nextInt(16);
int padAfter = rand.nextInt(16);
int paddedCapacity = b.remaining() + padBefore + padAfter;
ByteBuffer padded = allocateBuffer(paddedCapacity);
rand.ints(padBefore).forEach(x -> padded.put((byte) x));
padded.put(b.duplicate());
rand.ints(padAfter).forEach(x -> padded.put((byte) x));
padded.clear().limit(padded.capacity() - padAfter).position(padBefore);
return padded;
}
protected ByteBuffer allocateBuffer(int paddedCapacity)
{
return ByteBuffer.allocate(paddedCapacity);
}
}