blob: 6b685dcd1efa8aa273ab303a01d923633f48560c [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;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.apache.cassandra.cql3.FieldIdentifier;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.AsciiType;
import org.apache.cassandra.db.marshal.BooleanType;
import org.apache.cassandra.db.marshal.ByteType;
import org.apache.cassandra.db.marshal.BytesType;
import org.apache.cassandra.db.marshal.DoubleType;
import org.apache.cassandra.db.marshal.EmptyType;
import org.apache.cassandra.db.marshal.FloatType;
import org.apache.cassandra.db.marshal.InetAddressType;
import org.apache.cassandra.db.marshal.Int32Type;
import org.apache.cassandra.db.marshal.ListType;
import org.apache.cassandra.db.marshal.LongType;
import org.apache.cassandra.db.marshal.MapType;
import org.apache.cassandra.db.marshal.ReversedType;
import org.apache.cassandra.db.marshal.SetType;
import org.apache.cassandra.db.marshal.ShortType;
import org.apache.cassandra.db.marshal.TimestampType;
import org.apache.cassandra.db.marshal.TupleType;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.db.marshal.UUIDType;
import org.apache.cassandra.db.marshal.UserType;
import org.quicktheories.core.Gen;
import org.quicktheories.core.RandomnessSource;
import org.quicktheories.generators.SourceDSL;
import static org.apache.cassandra.utils.Generators.IDENTIFIER_GEN;
public final class AbstractTypeGenerators
{
private static final Gen<Integer> VERY_SMALL_POSITIVE_SIZE_GEN = SourceDSL.integers().between(1, 3);
private static final Gen<Boolean> BOOLEAN_GEN = SourceDSL.booleans().all();
private static final Map<AbstractType<?>, TypeSupport<?>> PRIMITIVE_TYPE_DATA_GENS =
Stream.of(TypeSupport.of(BooleanType.instance, BOOLEAN_GEN),
TypeSupport.of(ByteType.instance, SourceDSL.integers().between(0, Byte.MAX_VALUE * 2 + 1).map(Integer::byteValue)),
TypeSupport.of(ShortType.instance, SourceDSL.integers().between(0, Short.MAX_VALUE * 2 + 1).map(Integer::shortValue)),
TypeSupport.of(Int32Type.instance, SourceDSL.integers().all()),
TypeSupport.of(LongType.instance, SourceDSL.longs().all()),
TypeSupport.of(FloatType.instance, SourceDSL.floats().any()),
TypeSupport.of(DoubleType.instance, SourceDSL.doubles().any()),
TypeSupport.of(BytesType.instance, Generators.bytes(1, 1024)),
TypeSupport.of(UUIDType.instance, Generators.UUID_RANDOM_GEN),
TypeSupport.of(InetAddressType.instance, Generators.INET_ADDRESS_UNRESOLVED_GEN), // serialization strips the hostname, only keeps the address
TypeSupport.of(AsciiType.instance, SourceDSL.strings().ascii().ofLengthBetween(1, 1024)),
TypeSupport.of(UTF8Type.instance, Generators.utf8(1, 1024)),
TypeSupport.of(TimestampType.instance, Generators.DATE_GEN),
// null is desired here as #decompose will call org.apache.cassandra.serializers.EmptySerializer.serialize which ignores the input and returns empty bytes
TypeSupport.of(EmptyType.instance, rnd -> null)
//TODO add the following
// IntegerType.instance,
// DecimalType.instance,
// TimeUUIDType.instance,
// LexicalUUIDType.instance,
// SimpleDateType.instance,
// TimeType.instance,
// DurationType.instance,
).collect(Collectors.toMap(t -> t.type, t -> t));
// NOTE not supporting reversed as CQL doesn't allow nested reversed types
// when generating part of the clustering key, it would be good to allow reversed types as the top level
private static final Gen<AbstractType<?>> PRIMITIVE_TYPE_GEN = SourceDSL.arbitrary().pick(new ArrayList<>(PRIMITIVE_TYPE_DATA_GENS.keySet()));
private AbstractTypeGenerators()
{
}
public enum TypeKind
{PRIMITIVE, SET, LIST, MAP, TUPLE, UDT}
private static final Gen<TypeKind> TYPE_KIND_GEN = SourceDSL.arbitrary().enumValuesWithNoOrder(TypeKind.class);
public static Gen<AbstractType<?>> primitiveTypeGen()
{
return PRIMITIVE_TYPE_GEN;
}
public static Gen<AbstractType<?>> typeGen()
{
return typeGen(3);
}
public static Gen<AbstractType<?>> typeGen(int maxDepth)
{
return typeGen(maxDepth, TYPE_KIND_GEN, VERY_SMALL_POSITIVE_SIZE_GEN);
}
public static Gen<AbstractType<?>> typeGen(int maxDepth, Gen<TypeKind> typeKindGen, Gen<Integer> sizeGen)
{
assert maxDepth >= 0 : "max depth must be positive or zero; given " + maxDepth;
boolean atBottom = maxDepth == 0;
return rnd -> {
// figure out type to get
TypeKind kind = typeKindGen.generate(rnd);
switch (kind)
{
case PRIMITIVE:
return PRIMITIVE_TYPE_GEN.generate(rnd);
case SET:
return setTypeGen(atBottom ? PRIMITIVE_TYPE_GEN : typeGen(maxDepth - 1, typeKindGen, sizeGen)).generate(rnd);
case LIST:
return listTypeGen(atBottom ? PRIMITIVE_TYPE_GEN : typeGen(maxDepth - 1, typeKindGen, sizeGen)).generate(rnd);
case MAP:
return mapTypeGen(atBottom ? PRIMITIVE_TYPE_GEN : typeGen(maxDepth - 1, typeKindGen, sizeGen)).generate(rnd);
case TUPLE:
return tupleTypeGen(atBottom ? PRIMITIVE_TYPE_GEN : typeGen(maxDepth - 1, typeKindGen, sizeGen), sizeGen).generate(rnd);
case UDT:
return userTypeGen(atBottom ? PRIMITIVE_TYPE_GEN : typeGen(maxDepth - 1, typeKindGen, sizeGen), sizeGen).generate(rnd);
default:
throw new IllegalArgumentException("Unknown kind: " + kind);
}
};
}
@SuppressWarnings("unused")
public static Gen<SetType<?>> setTypeGen()
{
return setTypeGen(typeGen(2)); // lower the default depth since this is already a nested type
}
public static Gen<SetType<?>> setTypeGen(Gen<AbstractType<?>> typeGen)
{
return rnd -> SetType.getInstance(typeGen.generate(rnd), BOOLEAN_GEN.generate(rnd));
}
@SuppressWarnings("unused")
public static Gen<ListType<?>> listTypeGen()
{
return listTypeGen(typeGen(2)); // lower the default depth since this is already a nested type
}
public static Gen<ListType<?>> listTypeGen(Gen<AbstractType<?>> typeGen)
{
return rnd -> ListType.getInstance(typeGen.generate(rnd), BOOLEAN_GEN.generate(rnd));
}
@SuppressWarnings("unused")
public static Gen<MapType<?, ?>> mapTypeGen()
{
return mapTypeGen(typeGen(2)); // lower the default depth since this is already a nested type
}
public static Gen<MapType<?, ?>> mapTypeGen(Gen<AbstractType<?>> typeGen)
{
return mapTypeGen(typeGen, typeGen);
}
public static Gen<MapType<?, ?>> mapTypeGen(Gen<AbstractType<?>> keyGen, Gen<AbstractType<?>> valueGen)
{
return rnd -> MapType.getInstance(keyGen.generate(rnd), valueGen.generate(rnd), BOOLEAN_GEN.generate(rnd));
}
public static Gen<TupleType> tupleTypeGen()
{
return tupleTypeGen(typeGen(2)); // lower the default depth since this is already a nested type
}
public static Gen<TupleType> tupleTypeGen(Gen<AbstractType<?>> elementGen)
{
return tupleTypeGen(elementGen, VERY_SMALL_POSITIVE_SIZE_GEN);
}
public static Gen<TupleType> tupleTypeGen(Gen<AbstractType<?>> elementGen, Gen<Integer> sizeGen)
{
return rnd -> {
int numElements = sizeGen.generate(rnd);
List<AbstractType<?>> elements = new ArrayList<>(numElements);
for (int i = 0; i < numElements; i++)
elements.add(elementGen.generate(rnd));
return new TupleType(elements);
};
}
public static Gen<UserType> userTypeGen()
{
return userTypeGen(typeGen(2)); // lower the default depth since this is already a nested type
}
public static Gen<UserType> userTypeGen(Gen<AbstractType<?>> elementGen)
{
return userTypeGen(elementGen, VERY_SMALL_POSITIVE_SIZE_GEN);
}
public static Gen<UserType> userTypeGen(Gen<AbstractType<?>> elementGen, Gen<Integer> sizeGen)
{
Gen<FieldIdentifier> fieldNameGen = IDENTIFIER_GEN.map(FieldIdentifier::forQuoted);
return rnd -> {
boolean multiCell = BOOLEAN_GEN.generate(rnd);
int numElements = sizeGen.generate(rnd);
List<AbstractType<?>> fieldTypes = new ArrayList<>(numElements);
LinkedHashSet<FieldIdentifier> fieldNames = new LinkedHashSet<>(numElements);
String ks = IDENTIFIER_GEN.generate(rnd);
ByteBuffer name = AsciiType.instance.decompose(IDENTIFIER_GEN.generate(rnd));
Gen<FieldIdentifier> distinctNameGen = Generators.filter(fieldNameGen, 30, e -> !fieldNames.contains(e));
// UDTs don't allow duplicate names, so make sure all names are unique
for (int i = 0; i < numElements; i++)
{
fieldTypes.add(elementGen.generate(rnd));
fieldNames.add(distinctNameGen.generate(rnd));
}
return new UserType(ks, name, new ArrayList<>(fieldNames), fieldTypes, multiCell);
};
}
public static Gen<AbstractType<?>> allowReversed(Gen<AbstractType<?>> gen)
{
return rnd -> BOOLEAN_GEN.generate(rnd) ? ReversedType.getInstance(gen.generate(rnd)) : gen.generate(rnd);
}
/**
* For a type, create generators for data that matches that type
*/
public static <T> TypeSupport<T> getTypeSupport(AbstractType<T> type)
{
return getTypeSupport(type, VERY_SMALL_POSITIVE_SIZE_GEN);
}
/**
* For a type, create generators for data that matches that type
*/
public static <T> TypeSupport<T> getTypeSupport(AbstractType<T> type, Gen<Integer> sizeGen)
{
// this doesn't affect the data, only sort order, so drop it
if (type.isReversed())
type = ((ReversedType<T>) type).baseType;
// cast is safe since type is a constant and was type cast while inserting into the map
@SuppressWarnings("unchecked")
TypeSupport<T> gen = (TypeSupport<T>) PRIMITIVE_TYPE_DATA_GENS.get(type);
if (gen != null)
return gen;
// might be... complex...
if (type instanceof SetType)
{
// T = Set<A> so can not use T here
SetType<Object> setType = (SetType<Object>) type;
TypeSupport<?> elementSupport = getTypeSupport(setType.getElementsType(), sizeGen);
@SuppressWarnings("unchecked")
TypeSupport<T> support = (TypeSupport<T>) TypeSupport.of(setType, rnd -> {
int size = sizeGen.generate(rnd);
HashSet<Object> set = Sets.newHashSetWithExpectedSize(size);
for (int i = 0; i < size; i++)
set.add(elementSupport.valueGen.generate(rnd));
return set;
});
return support;
}
else if (type instanceof ListType)
{
// T = List<A> so can not use T here
ListType<Object> listType = (ListType<Object>) type;
TypeSupport<?> elementSupport = getTypeSupport(listType.getElementsType(), sizeGen);
@SuppressWarnings("unchecked")
TypeSupport<T> support = (TypeSupport<T>) TypeSupport.of(listType, rnd -> {
int size = sizeGen.generate(rnd);
List<Object> list = new ArrayList<>(size);
for (int i = 0; i < size; i++)
list.add(elementSupport.valueGen.generate(rnd));
return list;
});
return support;
}
else if (type instanceof MapType)
{
// T = Map<A, B> so can not use T here
MapType<Object, Object> mapType = (MapType<Object, Object>) type;
TypeSupport<?> keySupport = getTypeSupport(mapType.getKeysType(), sizeGen);
TypeSupport<?> valueSupport = getTypeSupport(mapType.getValuesType(), sizeGen);
@SuppressWarnings("unchecked")
TypeSupport<T> support = (TypeSupport<T>) TypeSupport.of(mapType, rnd -> {
int size = sizeGen.generate(rnd);
Map<Object, Object> map = Maps.newHashMapWithExpectedSize(size);
// if there is conflict thats fine
for (int i = 0; i < size; i++)
map.put(keySupport.valueGen.generate(rnd), valueSupport.valueGen.generate(rnd));
return map;
});
return support;
}
else if (type instanceof TupleType) // includes UserType
{
// T is ByteBuffer
TupleType tupleType = (TupleType) type;
@SuppressWarnings("unchecked")
TypeSupport<T> support = (TypeSupport<T>) TypeSupport.of(tupleType, new TupleGen(tupleType, sizeGen));
return support;
}
throw new UnsupportedOperationException("Unsupported type: " + type);
}
private static final class TupleGen implements Gen<ByteBuffer>
{
private final List<TypeSupport<Object>> elementsSupport;
@SuppressWarnings("unchecked")
private TupleGen(TupleType tupleType, Gen<Integer> sizeGen)
{
this.elementsSupport = tupleType.allTypes().stream().map(t -> getTypeSupport((AbstractType<Object>) t, sizeGen)).collect(Collectors.toList());
}
public ByteBuffer generate(RandomnessSource rnd)
{
List<TypeSupport<Object>> eSupport = this.elementsSupport;
ByteBuffer[] elements = new ByteBuffer[eSupport.size()];
for (int i = 0; i < eSupport.size(); i++)
{
TypeSupport<Object> support = eSupport.get(i);
elements[i] = support.type.decompose(support.valueGen.generate(rnd));
}
return TupleType.buildValue(elements);
}
}
/**
* Pair of {@link AbstractType} and a Generator of values that are handled by that type.
*/
public static final class TypeSupport<T>
{
public final AbstractType<T> type;
public final Gen<T> valueGen;
private TypeSupport(AbstractType<T> type, Gen<T> valueGen)
{
this.type = Objects.requireNonNull(type);
this.valueGen = Objects.requireNonNull(valueGen);
}
public static <T> TypeSupport<T> of(AbstractType<T> type, Gen<T> valueGen)
{
return new TypeSupport<>(type, valueGen);
}
/**
* Generator which composes the values gen with {@link AbstractType#decompose(Object)}
*/
public Gen<ByteBuffer> bytesGen()
{
return rnd -> type.decompose(valueGen.generate(rnd));
}
public String toString()
{
return "TypeSupport{" +
"type=" + type +
'}';
}
}
}