blob: 6728da2e266b044bfe924e0a58147e75e1c944ce [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.cql3;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.regex.Pattern;
import org.junit.Test;
import org.apache.cassandra.db.marshal.*;
import org.apache.cassandra.serializers.*;
import org.apache.cassandra.transport.ProtocolVersion;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.UUIDGen;
import static org.junit.Assert.assertEquals;
/**
* Test functionality to re-create a CQL literal from its serialized representation.
* This test uses some randomness to generate the values and nested structures (collections,tuples,UDTs).
*/
public class CQL3TypeLiteralTest
{
private static final Pattern QUOTE = Pattern.compile("'");
/**
* Container holding the expected CQL literal for a type and serialized value.
* The CQL literal is generated independently from the code in {@link CQL3Type}.
*/
static class Value
{
final String expected;
final CQL3Type cql3Type;
final ByteBuffer value;
Value(String expected, CQL3Type cql3Type, ByteBuffer value)
{
this.expected = expected;
this.cql3Type = cql3Type;
this.value = value;
}
}
static final Map<CQL3Type.Native, List<Value>> nativeTypeValues = new EnumMap<>(CQL3Type.Native.class);
static void addNativeValue(String expected, CQL3Type.Native cql3Type, ByteBuffer value)
{
List<Value> l = nativeTypeValues.get(cql3Type);
if (l == null)
nativeTypeValues.put(cql3Type, l = new ArrayList<>());
l.add(new Value(expected, cql3Type, value));
}
static
{
// Add some (random) values for each native type.
// Also adds null values and empty values, if the type allows this.
for (int i = 0; i < 20; i++)
{
String v = randString(true);
addNativeValue(quote(v), CQL3Type.Native.ASCII, AsciiSerializer.instance.serialize(v));
}
addNativeValue("''", CQL3Type.Native.ASCII, AsciiSerializer.instance.serialize(""));
addNativeValue("''", CQL3Type.Native.ASCII, ByteBufferUtil.EMPTY_BYTE_BUFFER);
addNativeValue("null", CQL3Type.Native.ASCII, null);
for (int i = 0; i < 20; i++)
{
String v = randString(false);
addNativeValue(quote(v), CQL3Type.Native.TEXT, UTF8Serializer.instance.serialize(v));
}
addNativeValue("''", CQL3Type.Native.TEXT, UTF8Serializer.instance.serialize(""));
addNativeValue("''", CQL3Type.Native.TEXT, ByteBufferUtil.EMPTY_BYTE_BUFFER);
addNativeValue("null", CQL3Type.Native.TEXT, null);
for (int i = 0; i < 20; i++)
{
String v = randString(false);
addNativeValue(quote(v), CQL3Type.Native.VARCHAR, UTF8Serializer.instance.serialize(v));
}
addNativeValue("''", CQL3Type.Native.VARCHAR, UTF8Serializer.instance.serialize(""));
addNativeValue("''", CQL3Type.Native.VARCHAR, ByteBufferUtil.EMPTY_BYTE_BUFFER);
addNativeValue("null", CQL3Type.Native.VARCHAR, null);
addNativeValue("0", CQL3Type.Native.BIGINT, LongType.instance.decompose(0L));
for (int i = 0; i < 20; i++)
{
long v = randLong();
addNativeValue(Long.toString(v), CQL3Type.Native.BIGINT, LongType.instance.decompose(v));
}
addNativeValue("null", CQL3Type.Native.BIGINT, ByteBufferUtil.EMPTY_BYTE_BUFFER);
addNativeValue("null", CQL3Type.Native.BIGINT, null);
addNativeValue("0", CQL3Type.Native.COUNTER, LongType.instance.decompose(0L));
for (int i = 0; i < 20; i++)
{
long v = randLong();
addNativeValue(Long.toString(v), CQL3Type.Native.COUNTER, LongType.instance.decompose(v));
}
addNativeValue("null", CQL3Type.Native.COUNTER, ByteBufferUtil.EMPTY_BYTE_BUFFER);
addNativeValue("null", CQL3Type.Native.COUNTER, null);
addNativeValue("0", CQL3Type.Native.INT, Int32Type.instance.decompose(0));
for (int i = 0; i < 20; i++)
{
int v = randInt();
addNativeValue(Integer.toString(v), CQL3Type.Native.INT, Int32Type.instance.decompose(v));
}
addNativeValue("null", CQL3Type.Native.INT, ByteBufferUtil.EMPTY_BYTE_BUFFER);
addNativeValue("null", CQL3Type.Native.INT, null);
addNativeValue("0", CQL3Type.Native.SMALLINT, ShortType.instance.decompose((short) 0));
for (int i = 0; i < 20; i++)
{
short v = randShort();
addNativeValue(Short.toString(v), CQL3Type.Native.SMALLINT, ShortType.instance.decompose(v));
}
addNativeValue("null", CQL3Type.Native.SMALLINT, null);
addNativeValue("0", CQL3Type.Native.TINYINT, ByteType.instance.decompose((byte) 0));
for (int i = 0; i < 20; i++)
{
byte v = randByte();
addNativeValue(Short.toString(v), CQL3Type.Native.TINYINT, ByteType.instance.decompose(v));
}
addNativeValue("null", CQL3Type.Native.TINYINT, null);
addNativeValue("0.0", CQL3Type.Native.FLOAT, FloatType.instance.decompose((float) 0));
for (int i = 0; i < 20; i++)
{
float v = randFloat();
addNativeValue(Float.toString(v), CQL3Type.Native.FLOAT, FloatType.instance.decompose(v));
}
addNativeValue("NaN", CQL3Type.Native.FLOAT, FloatType.instance.decompose(Float.NaN));
addNativeValue("null", CQL3Type.Native.FLOAT, ByteBufferUtil.EMPTY_BYTE_BUFFER);
addNativeValue("null", CQL3Type.Native.FLOAT, null);
addNativeValue("0.0", CQL3Type.Native.DOUBLE, DoubleType.instance.decompose((double) 0));
for (int i = 0; i < 20; i++)
{
double v = randDouble();
addNativeValue(Double.toString(v), CQL3Type.Native.DOUBLE, DoubleType.instance.decompose(v));
}
addNativeValue("NaN", CQL3Type.Native.DOUBLE, DoubleType.instance.decompose(Double.NaN));
addNativeValue("null", CQL3Type.Native.DOUBLE, ByteBufferUtil.EMPTY_BYTE_BUFFER);
addNativeValue("null", CQL3Type.Native.DOUBLE, null);
addNativeValue("0", CQL3Type.Native.DECIMAL, DecimalType.instance.decompose(BigDecimal.ZERO));
for (int i = 0; i < 20; i++)
{
BigDecimal v = BigDecimal.valueOf(randDouble());
addNativeValue(v.toString(), CQL3Type.Native.DECIMAL, DecimalType.instance.decompose(v));
}
addNativeValue("null", CQL3Type.Native.DECIMAL, ByteBufferUtil.EMPTY_BYTE_BUFFER);
addNativeValue("null", CQL3Type.Native.DECIMAL, null);
addNativeValue("0", CQL3Type.Native.VARINT, IntegerType.instance.decompose(BigInteger.ZERO));
for (int i = 0; i < 20; i++)
{
BigInteger v = BigInteger.valueOf(randLong());
addNativeValue(v.toString(), CQL3Type.Native.VARINT, IntegerType.instance.decompose(v));
}
addNativeValue("null", CQL3Type.Native.VARINT, ByteBufferUtil.EMPTY_BYTE_BUFFER);
addNativeValue("null", CQL3Type.Native.VARINT, null);
// boolean doesn't have that many possible values...
addNativeValue("false", CQL3Type.Native.BOOLEAN, BooleanType.instance.decompose(false));
addNativeValue("true", CQL3Type.Native.BOOLEAN, BooleanType.instance.decompose(true));
addNativeValue("null", CQL3Type.Native.BOOLEAN, ByteBufferUtil.EMPTY_BYTE_BUFFER);
addNativeValue("null", CQL3Type.Native.BOOLEAN, null);
// (mostly generates date values with surreal values like in year 14273)
for (int i = 0; i < 20; i++)
{
int v = randInt();
addNativeValue(SimpleDateSerializer.instance.toString(v), CQL3Type.Native.DATE, SimpleDateSerializer.instance.serialize(v));
}
addNativeValue("null", CQL3Type.Native.DATE, null);
for (int i = 0; i < 100; i++)
{
long v = randLong(24L * 60 * 60 * 1000 * 1000 * 1000);
addNativeValue(TimeSerializer.instance.toString(v), CQL3Type.Native.TIME, TimeSerializer.instance.serialize(v));
}
addNativeValue("null", CQL3Type.Native.TIME, null);
for (int i = 0; i < 100; i++)
{
Duration duration = Duration.newInstance(Math.abs(randInt()), Math.abs(randInt()), Math.abs(randLong()));
addNativeValue(DurationSerializer.instance.toString(duration), CQL3Type.Native.DURATION, DurationSerializer.instance.serialize(duration));
}
addNativeValue("null", CQL3Type.Native.DURATION, null);
// (mostly generates timestamp values with surreal values like in year 14273)
for (int i = 0; i < 20; i++)
{
long v = randLong();
addNativeValue(TimestampSerializer.instance.toStringUTC(new Date(v)), CQL3Type.Native.TIMESTAMP, TimestampType.instance.fromString(Long.toString(v)));
}
addNativeValue("null", CQL3Type.Native.TIMESTAMP, ByteBufferUtil.EMPTY_BYTE_BUFFER);
addNativeValue("null", CQL3Type.Native.TIMESTAMP, null);
for (int i = 0; i < 20; i++)
{
UUID v = UUIDGen.getTimeUUID(randLong(System.currentTimeMillis()));
addNativeValue(v.toString(), CQL3Type.Native.TIMEUUID, TimeUUIDType.instance.decompose(v));
}
addNativeValue("null", CQL3Type.Native.TIMEUUID, ByteBufferUtil.EMPTY_BYTE_BUFFER);
addNativeValue("null", CQL3Type.Native.TIMEUUID, null);
for (int i = 0; i < 20; i++)
{
UUID v = UUID.randomUUID();
addNativeValue(v.toString(), CQL3Type.Native.UUID, UUIDType.instance.decompose(v));
}
addNativeValue("null", CQL3Type.Native.UUID, ByteBufferUtil.EMPTY_BYTE_BUFFER);
addNativeValue("null", CQL3Type.Native.UUID, null);
for (int i = 0; i < 20; i++)
{
ByteBuffer v = randBytes();
addNativeValue("0x" + BytesSerializer.instance.toString(v), CQL3Type.Native.BLOB, BytesType.instance.decompose(v));
}
addNativeValue("0x", CQL3Type.Native.BLOB, ByteBufferUtil.EMPTY_BYTE_BUFFER);
addNativeValue("null", CQL3Type.Native.BLOB, null);
for (int i = 0; i < 20; i++)
{
InetAddress v;
try
{
v = InetAddress.getByAddress(new byte[]{ randByte(), randByte(), randByte(), randByte() });
}
catch (UnknownHostException e)
{
throw new RuntimeException(e);
}
addNativeValue(v.getHostAddress(), CQL3Type.Native.INET, InetAddressSerializer.instance.serialize(v));
}
addNativeValue("null", CQL3Type.Native.INET, ByteBufferUtil.EMPTY_BYTE_BUFFER);
addNativeValue("null", CQL3Type.Native.INET, null);
}
@Test
public void testNative()
{
// test each native type against each supported protocol version (although it doesn't make sense to
// iterate through all protocol versions as of C* 3.0).
for (ProtocolVersion version : ProtocolVersion.SUPPORTED)
{
for (Map.Entry<CQL3Type.Native, List<Value>> entry : nativeTypeValues.entrySet())
{
for (Value value : entry.getValue())
{
compareCqlLiteral(version, value);
}
}
}
}
@Test
public void testCollectionWithNatives()
{
// test 100 collections with varying element/key/value types against each supported protocol version,
// type of collection is randomly chosen
for (ProtocolVersion version : ProtocolVersion.SUPPORTED)
{
for (int n = 0; n < 100; n++)
{
Value value = generateCollectionValue(version, randomCollectionType(0), true);
compareCqlLiteral(version, value);
}
}
}
@Test
public void testCollectionNullAndEmpty()
{
// An empty collection is one with a size of 0 (note that rely on the fact that protocol version < 3 are not
// supported anymore and so the size of a collection is always on 4 bytes).
ByteBuffer emptyCollection = ByteBufferUtil.bytes(0);
for (ProtocolVersion version : ProtocolVersion.SUPPORTED)
{
for (boolean frozen : Arrays.asList(true, false))
{
// empty
Value value = new Value("[]", ListType.getInstance(UTF8Type.instance, frozen).asCQL3Type(), emptyCollection);
compareCqlLiteral(version, value);
value = new Value("{}", SetType.getInstance(UTF8Type.instance, frozen).asCQL3Type(), emptyCollection);
compareCqlLiteral(version, value);
value = new Value("{}", MapType.getInstance(UTF8Type.instance, UTF8Type.instance, frozen).asCQL3Type(), emptyCollection);
compareCqlLiteral(version, value);
// null
value = new Value("null", ListType.getInstance(UTF8Type.instance, frozen).asCQL3Type(), null);
compareCqlLiteral(version, value);
value = new Value("null", SetType.getInstance(UTF8Type.instance, frozen).asCQL3Type(), null);
compareCqlLiteral(version, value);
value = new Value("null", MapType.getInstance(UTF8Type.instance, UTF8Type.instance, frozen).asCQL3Type(), null);
compareCqlLiteral(version, value);
}
}
}
@Test
public void testTupleWithNatives()
{
// test 100 tuples with varying element/key/value types against each supported protocol version
for (ProtocolVersion version : ProtocolVersion.SUPPORTED)
{
for (int n = 0; n < 100; n++)
{
Value value = generateTupleValue(version, randomTupleType(0), true);
compareCqlLiteral(version, value);
}
}
}
@Test
public void testUserDefinedWithNatives()
{
// test 100 UDTs with varying element/key/value types against each supported protocol version
for (ProtocolVersion version : ProtocolVersion.SUPPORTED)
{
for (int n = 0; n < 100; n++)
{
Value value = generateUserDefinedValue(version, randomUserType(0), true);
compareCqlLiteral(version, value);
}
}
}
@Test
public void testNested()
{
// This is the "nice" part of this unit test - it tests (probably) nested type structures
// like 'tuple<map, list<user>, tuple, user>' or 'map<tuple<int, text>, set<inet>>' with
// random types against each supported protocol version.
for (ProtocolVersion version : ProtocolVersion.SUPPORTED)
{
for (int n = 0; n < 100; n++)
{
Value value = randomNested(version);
compareCqlLiteral(version, value);
}
}
}
static void compareCqlLiteral(ProtocolVersion version, Value value)
{
ByteBuffer buffer = value.value != null ? value.value.duplicate() : null;
String msg = "Failed to get expected value for type " + value.cql3Type + " / " + value.cql3Type.getType() + " with protocol-version " + version + " expected:\"" + value.expected + '"';
try
{
assertEquals(msg,
value.expected,
value.cql3Type.toCQLLiteral(buffer, version));
}
catch (RuntimeException e)
{
throw new RuntimeException(msg, e);
}
}
static Value randomNested(ProtocolVersion version)
{
AbstractType type = randomNestedType(2);
return generateAnyValue(version, type.asCQL3Type());
}
/**
* Generates type of randomly nested type structures.
*/
static AbstractType randomNestedType(int level)
{
if (level == 0)
return randomNativeType();
switch (randInt(level == 2 ? 3 : 4))
{
case 0:
return randomCollectionType(level - 1);
case 1:
return randomTupleType(level - 1);
case 2:
return randomUserType(level - 1);
case 3:
return randomNativeType();
}
throw new AssertionError();
}
static Value generateCollectionValue(ProtocolVersion version, CollectionType collectionType, boolean allowNull)
{
StringBuilder expected = new StringBuilder();
ByteBuffer buffer;
if (allowNull && randBool(0.05d))
{
expected.append("null");
buffer = null;
}
else
{
int size = randInt(20);
CQL3Type elements;
CQL3Type values = null;
char bracketOpen;
char bracketClose;
switch (collectionType.kind)
{
case LIST:
elements = ((ListType) collectionType).getElementsType().asCQL3Type();
bracketOpen = '[';
bracketClose = ']';
break;
case SET:
elements = ((SetType) collectionType).getElementsType().asCQL3Type();
bracketOpen = '{';
bracketClose = '}';
break;
case MAP:
elements = ((MapType) collectionType).getKeysType().asCQL3Type();
values = ((MapType) collectionType).getValuesType().asCQL3Type();
bracketOpen = '{';
bracketClose = '}';
break;
default:
throw new AssertionError();
}
expected.append(bracketOpen);
Collection<ByteBuffer> buffers = new ArrayList<>();
Set<ByteBuffer> added = new HashSet<>();
for (int i = 0; i < size; i++)
{
Value el = generateAnyValue(version, elements);
if (!added.add(el.value))
continue;
buffers.add(el.value.duplicate());
if (expected.length() > 1)
expected.append(", ");
expected.append(el.cql3Type.toCQLLiteral(el.value, version));
if (collectionType.kind == CollectionType.Kind.MAP)
{
// add map value
el = generateAnyValue(version, values);
buffers.add(el.value.duplicate());
expected.append(": ");
expected.append(el.cql3Type.toCQLLiteral(el.value, version));
}
}
expected.append(bracketClose);
buffer = CollectionSerializer.pack(buffers, added.size(), version);
}
return new Value(expected.toString(), collectionType.asCQL3Type(), buffer);
}
/**
* Generates a value for any type or type structure.
*/
static Value generateAnyValue(ProtocolVersion version, CQL3Type type)
{
if (type instanceof CQL3Type.Native)
return generateNativeValue(type, false);
if (type instanceof CQL3Type.Tuple)
return generateTupleValue(version, (TupleType) type.getType(), false);
if (type instanceof CQL3Type.UserDefined)
return generateUserDefinedValue(version, (UserType) type.getType(), false);
if (type instanceof CQL3Type.Collection)
return generateCollectionValue(version, (CollectionType) type.getType(), false);
throw new AssertionError();
}
static Value generateTupleValue(ProtocolVersion version, TupleType tupleType, boolean allowNull)
{
StringBuilder expected = new StringBuilder();
ByteBuffer buffer;
if (allowNull && randBool(0.05d))
{
// generate 'null' collection
expected.append("null");
buffer = null;
}
else
{
expected.append('(');
// # of fields in this value
int fields = tupleType.size();
if (randBool(0.2d))
fields = randInt(fields);
ByteBuffer[] buffers = new ByteBuffer[fields];
for (int i = 0; i < fields; i++)
{
AbstractType<?> fieldType = tupleType.type(i);
if (i > 0)
expected.append(", ");
if (allowNull && randBool(.1))
{
expected.append("null");
continue;
}
Value value = generateAnyValue(version, fieldType.asCQL3Type());
expected.append(value.expected);
buffers[i] = value.value.duplicate();
}
expected.append(')');
buffer = TupleType.buildValue(buffers);
}
return new Value(expected.toString(), tupleType.asCQL3Type(), buffer);
}
static Value generateUserDefinedValue(ProtocolVersion version, UserType userType, boolean allowNull)
{
StringBuilder expected = new StringBuilder();
ByteBuffer buffer;
if (allowNull && randBool(0.05d))
{
// generate 'null' collection
expected.append("null");
buffer = null;
}
else
{
expected.append('{');
// # of fields in this value
int fields = userType.size();
if (randBool(0.2d))
fields = randInt(fields);
ByteBuffer[] buffers = new ByteBuffer[fields];
for (int i = 0; i < fields; i++)
{
AbstractType<?> fieldType = userType.type(i);
if (i > 0)
expected.append(", ");
expected.append(ColumnIdentifier.maybeQuote(userType.fieldNameAsString(i)));
expected.append(": ");
if (randBool(.1))
{
expected.append("null");
continue;
}
Value value = generateAnyValue(version, fieldType.asCQL3Type());
expected.append(value.expected);
buffers[i] = value.value.duplicate();
}
expected.append('}');
buffer = TupleType.buildValue(buffers);
}
return new Value(expected.toString(), userType.asCQL3Type(), buffer);
}
static Value generateNativeValue(CQL3Type type, boolean allowNull)
{
List<Value> values = nativeTypeValues.get(type);
assert values != null : type.toString() + " needs to be defined";
while (true)
{
Value v = values.get(randInt(values.size()));
if (allowNull || v.value != null)
return v;
}
}
static CollectionType randomCollectionType(int level)
{
CollectionType.Kind kind = CollectionType.Kind.values()[randInt(CollectionType.Kind.values().length)];
switch (kind)
{
case LIST:
case SET:
return ListType.getInstance(randomNestedType(level), randBool());
case MAP:
return MapType.getInstance(randomNestedType(level), randomNestedType(level), randBool());
}
throw new AssertionError();
}
static TupleType randomTupleType(int level)
{
int typeCount = 2 + randInt(5);
List<AbstractType<?>> types = new ArrayList<>();
for (int i = 0; i < typeCount; i++)
types.add(randomNestedType(level));
return new TupleType(types);
}
static UserType randomUserType(int level)
{
int typeCount = 2 + randInt(5);
List<FieldIdentifier> names = new ArrayList<>();
List<AbstractType<?>> types = new ArrayList<>();
for (int i = 0; i < typeCount; i++)
{
names.add(FieldIdentifier.forQuoted('f' + randLetters(i)));
types.add(randomNestedType(level));
}
return new UserType("ks", UTF8Type.instance.fromString("u" + randInt(1000000)), names, types, true);
}
//
// Following methods are just helper methods. Mostly to generate many kinds of random values.
//
private static String randLetters(int len)
{
StringBuilder sb = new StringBuilder(len);
while (len-- > 0)
{
int i = randInt(52);
if (i < 26)
sb.append((char) ('A' + i));
else
sb.append((char) ('a' + i - 26));
}
return sb.toString();
}
static AbstractType randomNativeType()
{
while (true)
{
CQL3Type.Native t = CQL3Type.Native.values()[randInt(CQL3Type.Native.values().length)];
if (t != CQL3Type.Native.EMPTY)
return t.getType();
}
}
static boolean randBool()
{
return randBool(0.5d);
}
static boolean randBool(double probability)
{
return ThreadLocalRandom.current().nextDouble() < probability;
}
static long randLong()
{
return ThreadLocalRandom.current().nextLong();
}
static long randLong(long max)
{
return ThreadLocalRandom.current().nextLong(max);
}
static int randInt()
{
return ThreadLocalRandom.current().nextInt();
}
static int randInt(int max)
{
return ThreadLocalRandom.current().nextInt(max);
}
static short randShort()
{
return (short) ThreadLocalRandom.current().nextInt();
}
static byte randByte()
{
return (byte) ThreadLocalRandom.current().nextInt();
}
static double randDouble()
{
return ThreadLocalRandom.current().nextDouble();
}
static float randFloat()
{
return ThreadLocalRandom.current().nextFloat();
}
static String randString(boolean ascii)
{
int l = randInt(20);
StringBuilder sb = new StringBuilder(l);
for (int i = 0; i < l; i++)
{
if (randBool(.05))
sb.append('\'');
else
{
char c = (char) (ascii ? randInt(128) : randShort());
sb.append(c);
}
}
return UTF8Serializer.instance.deserialize(UTF8Serializer.instance.serialize(sb.toString()));
}
static ByteBuffer randBytes()
{
int l = randInt(20);
byte[] v = new byte[l];
for (int i = 0; i < l; i++)
{
v[i] = randByte();
}
return ByteBuffer.wrap(v);
}
private static String quote(String v)
{
return '\'' + QUOTE.matcher(v).replaceAll("''") + '\'';
}
}