/*
* Copyright (C) 2012-2015 DataStax Inc.
*
* Licensed 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 com.datastax.driver.core;
import com.datastax.driver.core.exceptions.DriverInternalError;
import com.datastax.driver.core.utils.MoreObjects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import io.netty.buffer.ByteBuf;
import java.util.*;
/**
* Data types supported by cassandra.
*/
public abstract class DataType {
/**
* The CQL type name.
*/
public enum Name {
CUSTOM(0),
ASCII(1),
BIGINT(2),
BLOB(3),
BOOLEAN(4),
COUNTER(5),
DECIMAL(6),
DOUBLE(7),
FLOAT(8),
INT(9),
TEXT(10) {
@Override
public boolean isCompatibleWith(Name that) {
return this == that || that == VARCHAR;
}
},
TIMESTAMP(11),
UUID(12),
VARCHAR(13) {
@Override
public boolean isCompatibleWith(Name that) {
return this == that || that == TEXT;
}
},
VARINT(14),
TIMEUUID(15),
INET(16),
DATE(17, ProtocolVersion.V4),
TIME(18, ProtocolVersion.V4),
SMALLINT(19, ProtocolVersion.V4),
TINYINT(20, ProtocolVersion.V4),
DURATION(21, ProtocolVersion.V5),
LIST(32),
MAP(33),
SET(34),
UDT(48, ProtocolVersion.V3),
TUPLE(49, ProtocolVersion.V3);
final int protocolId;
final ProtocolVersion minProtocolVersion;
private static final Name[] nameToIds;
static {
int maxCode = -1;
for (Name name : Name.values())
maxCode = Math.max(maxCode, name.protocolId);
nameToIds = new Name[maxCode + 1];
for (Name name : Name.values()) {
if (nameToIds[name.protocolId] != null)
throw new IllegalStateException("Duplicate Id");
nameToIds[name.protocolId] = name;
}
}
private Name(int protocolId) {
this(protocolId, ProtocolVersion.V1);
}
private Name(int protocolId, ProtocolVersion minProtocolVersion) {
this.protocolId = protocolId;
this.minProtocolVersion = minProtocolVersion;
}
static Name fromProtocolId(int id) {
Name name = nameToIds[id];
if (name == null)
throw new DriverInternalError("Unknown data type protocol id: " + id);
return name;
}
/**
* Return {@code true} if the provided Name is equal to this one,
* or if they are aliases for each other, and {@code false} otherwise.
*
* @param that the Name to compare with the current one.
* @return {@code true} if the provided Name is equal to this one,
* or if they are aliases for each other, and {@code false} otherwise.
*/
public boolean isCompatibleWith(Name that) {
return this == that;
}
@Override
public String toString() {
return super.toString().toLowerCase();
}
}
private static final Map<Name, DataType> primitiveTypeMap = new EnumMap<Name, DataType>(Name.class);
static {
primitiveTypeMap.put(Name.ASCII, new DataType.NativeType(Name.ASCII));
primitiveTypeMap.put(Name.BIGINT, new DataType.NativeType(Name.BIGINT));
primitiveTypeMap.put(Name.BLOB, new DataType.NativeType(Name.BLOB));
primitiveTypeMap.put(Name.BOOLEAN, new DataType.NativeType(Name.BOOLEAN));
primitiveTypeMap.put(Name.COUNTER, new DataType.NativeType(Name.COUNTER));
primitiveTypeMap.put(Name.DECIMAL, new DataType.NativeType(Name.DECIMAL));
primitiveTypeMap.put(Name.DOUBLE, new DataType.NativeType(Name.DOUBLE));
primitiveTypeMap.put(Name.FLOAT, new DataType.NativeType(Name.FLOAT));
primitiveTypeMap.put(Name.INET, new DataType.NativeType(Name.INET));
primitiveTypeMap.put(Name.INT, new DataType.NativeType(Name.INT));
primitiveTypeMap.put(Name.TEXT, new DataType.NativeType(Name.TEXT));
primitiveTypeMap.put(Name.TIMESTAMP, new DataType.NativeType(Name.TIMESTAMP));
primitiveTypeMap.put(Name.UUID, new DataType.NativeType(Name.UUID));
primitiveTypeMap.put(Name.VARCHAR, new DataType.NativeType(Name.VARCHAR));
primitiveTypeMap.put(Name.VARINT, new DataType.NativeType(Name.VARINT));
primitiveTypeMap.put(Name.TIMEUUID, new DataType.NativeType(Name.TIMEUUID));
primitiveTypeMap.put(Name.SMALLINT, new DataType.NativeType(Name.SMALLINT));
primitiveTypeMap.put(Name.TINYINT, new DataType.NativeType(Name.TINYINT));
primitiveTypeMap.put(Name.DATE, new DataType.NativeType(Name.DATE));
primitiveTypeMap.put(Name.TIME, new DataType.NativeType(Name.TIME));
primitiveTypeMap.put(Name.DURATION, new DataType.NativeType(Name.DURATION));
}
private static final Set<DataType> primitiveTypeSet = ImmutableSet.copyOf(primitiveTypeMap.values());
protected final DataType.Name name;
protected DataType(DataType.Name name) {
this.name = name;
}
static DataType decode(ByteBuf buffer, ProtocolVersion protocolVersion, CodecRegistry codecRegistry) {
Name name = Name.fromProtocolId(buffer.readUnsignedShort());
switch (name) {
case CUSTOM:
String className = CBUtil.readString(buffer);
if (DataTypeClassNameParser.isDuration(className)) {
return DataType.duration();
} else if (DataTypeClassNameParser.isUserType(className) ||
DataTypeClassNameParser.isTupleType(className)) {
return DataTypeClassNameParser.parseOne(className, protocolVersion, codecRegistry);
} else {
return custom(className);
}
case LIST:
return list(decode(buffer, protocolVersion, codecRegistry));
case SET:
return set(decode(buffer, protocolVersion, codecRegistry));
case MAP:
DataType keys = decode(buffer, protocolVersion, codecRegistry);
DataType values = decode(buffer, protocolVersion, codecRegistry);
return map(keys, values);
case UDT:
String keyspace = CBUtil.readString(buffer);
String type = CBUtil.readString(buffer);
int nFields = buffer.readShort() & 0xffff;
List<UserType.Field> fields = new ArrayList<UserType.Field>(nFields);
for (int i = 0; i < nFields; i++) {
String fieldName = CBUtil.readString(buffer);
DataType fieldType = decode(buffer, protocolVersion, codecRegistry);
fields.add(new UserType.Field(fieldName, fieldType));
}
return new UserType(keyspace, type, false, fields, protocolVersion, codecRegistry);
case TUPLE:
nFields = buffer.readShort() & 0xffff;
List<DataType> types = new ArrayList<DataType>(nFields);
for (int i = 0; i < nFields; i++) {
types.add(decode(buffer, protocolVersion, codecRegistry));
}
return new TupleType(types, protocolVersion, codecRegistry);
default:
return primitiveTypeMap.get(name);
}
}
/**
* Returns the ASCII type.
*
* @return The ASCII type.
*/
public static DataType ascii() {
return primitiveTypeMap.get(Name.ASCII);
}
/**
* Returns the BIGINT type.
*
* @return The BIGINT type.
*/
public static DataType bigint() {
return primitiveTypeMap.get(Name.BIGINT);
}
/**
* Returns the BLOB type.
*
* @return The BLOB type.
*/
public static DataType blob() {
return primitiveTypeMap.get(Name.BLOB);
}
/**
* Returns the BOOLEAN type.
*
* @return The BOOLEAN type.
*/
public static DataType cboolean() {
return primitiveTypeMap.get(Name.BOOLEAN);
}
/**
* Returns the COUNTER type.
*
* @return The COUNTER type.
*/
public static DataType counter() {
return primitiveTypeMap.get(Name.COUNTER);
}
/**
* Returns the DECIMAL type.
*
* @return The DECIMAL type.
*/
public static DataType decimal() {
return primitiveTypeMap.get(Name.DECIMAL);
}
/**
* Returns the DOUBLE type.
*
* @return The DOUBLE type.
*/
public static DataType cdouble() {
return primitiveTypeMap.get(Name.DOUBLE);
}
/**
* Returns the FLOAT type.
*
* @return The FLOAT type.
*/
public static DataType cfloat() {
return primitiveTypeMap.get(Name.FLOAT);
}
/**
* Returns the INET type.
*
* @return The INET type.
*/
public static DataType inet() {
return primitiveTypeMap.get(Name.INET);
}
/**
* Returns the TINYINT type.
*
* @return The TINYINT type.
*/
public static DataType tinyint() {
return primitiveTypeMap.get(Name.TINYINT);
}
/**
* Returns the SMALLINT type.
*
* @return The SMALLINT type.
*/
public static DataType smallint() {
return primitiveTypeMap.get(Name.SMALLINT);
}
/**
* Returns the INT type.
*
* @return The INT type.
*/
public static DataType cint() {
return primitiveTypeMap.get(Name.INT);
}
/**
* Returns the TEXT type.
*
* @return The TEXT type.
*/
public static DataType text() {
return primitiveTypeMap.get(Name.TEXT);
}
/**
* Returns the TIMESTAMP type.
*
* @return The TIMESTAMP type.
*/
public static DataType timestamp() {
return primitiveTypeMap.get(Name.TIMESTAMP);
}
/**
* Returns the DATE type.
*
* @return The DATE type.
*/
public static DataType date() {
return primitiveTypeMap.get(Name.DATE);
}
/**
* Returns the TIME type.
*
* @return The TIME type.
*/
public static DataType time() {
return primitiveTypeMap.get(Name.TIME);
}
/**
* Returns the UUID type.
*
* @return The UUID type.
*/
public static DataType uuid() {
return primitiveTypeMap.get(Name.UUID);
}
/**
* Returns the VARCHAR type.
*
* @return The VARCHAR type.
*/
public static DataType varchar() {
return primitiveTypeMap.get(Name.VARCHAR);
}
/**
* Returns the VARINT type.
*
* @return The VARINT type.
*/
public static DataType varint() {
return primitiveTypeMap.get(Name.VARINT);
}
/**
* Returns the TIMEUUID type.
*
* @return The TIMEUUID type.
*/
public static DataType timeuuid() {
return primitiveTypeMap.get(Name.TIMEUUID);
}
/**
* Returns the type of lists of {@code elementType} elements.
*
* @param elementType the type of the list elements.
* @param frozen whether the list is frozen.
* @return the type of lists of {@code elementType} elements.
*/
public static CollectionType list(DataType elementType, boolean frozen) {
return new DataType.CollectionType(Name.LIST, ImmutableList.of(elementType), frozen);
}
/**
* Returns the type of "not frozen" lists of {@code elementType} elements.
* <p/>
* This is a shorthand for {@code list(elementType, false);}.
*
* @param elementType the type of the list elements.
* @return the type of "not frozen" lists of {@code elementType} elements.
*/
public static CollectionType list(DataType elementType) {
return list(elementType, false);
}
/**
* Returns the type of frozen lists of {@code elementType} elements.
* <p/>
* This is a shorthand for {@code list(elementType, true);}.
*
* @param elementType the type of the list elements.
* @return the type of frozen lists of {@code elementType} elements.
*/
public static CollectionType frozenList(DataType elementType) {
return list(elementType, true);
}
/**
* Returns the type of sets of {@code elementType} elements.
*
* @param elementType the type of the set elements.
* @param frozen whether the set is frozen.
* @return the type of sets of {@code elementType} elements.
*/
public static CollectionType set(DataType elementType, boolean frozen) {
return new DataType.CollectionType(Name.SET, ImmutableList.of(elementType), frozen);
}
/**
* Returns the type of "not frozen" sets of {@code elementType} elements.
* <p/>
* This is a shorthand for {@code set(elementType, false);}.
*
* @param elementType the type of the set elements.
* @return the type of "not frozen" sets of {@code elementType} elements.
*/
public static CollectionType set(DataType elementType) {
return set(elementType, false);
}
/**
* Returns the type of frozen sets of {@code elementType} elements.
* <p/>
* This is a shorthand for {@code set(elementType, true);}.
*
* @param elementType the type of the set elements.
* @return the type of frozen sets of {@code elementType} elements.
*/
public static CollectionType frozenSet(DataType elementType) {
return set(elementType, true);
}
/**
* Returns the type of maps of {@code keyType} to {@code valueType} elements.
*
* @param keyType the type of the map keys.
* @param valueType the type of the map values.
* @param frozen whether the map is frozen.
* @return the type of maps of {@code keyType} to {@code valueType} elements.
*/
public static CollectionType map(DataType keyType, DataType valueType, boolean frozen) {
return new DataType.CollectionType(Name.MAP, ImmutableList.of(keyType, valueType), frozen);
}
/**
* Returns the type of "not frozen" maps of {@code keyType} to {@code valueType} elements.
* <p/>
* This is a shorthand for {@code map(keyType, valueType, false);}.
*
* @param keyType the type of the map keys.
* @param valueType the type of the map values.
* @return the type of "not frozen" maps of {@code keyType} to {@code valueType} elements.
*/
public static CollectionType map(DataType keyType, DataType valueType) {
return map(keyType, valueType, false);
}
/**
* Returns the type of frozen maps of {@code keyType} to {@code valueType} elements.
* <p/>
* This is a shorthand for {@code map(keyType, valueType, true);}.
*
* @param keyType the type of the map keys.
* @param valueType the type of the map values.
* @return the type of frozen maps of {@code keyType} to {@code valueType} elements.
*/
public static CollectionType frozenMap(DataType keyType, DataType valueType) {
return map(keyType, valueType, true);
}
/**
* Returns a Custom type.
* <p/>
* A custom type is defined by the name of the class used on the Cassandra
* side to implement it. Note that the support for custom types by the
* driver is limited.
* <p/>
* The use of custom types is rarely useful and is thus not encouraged.
*
* @param typeClassName the server-side fully qualified class name for the type.
* @return the custom type for {@code typeClassName}.
*/
public static DataType.CustomType custom(String typeClassName) {
if (typeClassName == null)
throw new NullPointerException();
return new DataType.CustomType(Name.CUSTOM, typeClassName);
}
/**
* Returns the Duration type, introduced in Cassandra 3.10.
* <p/>
* Note that a Duration type does not have a native representation in CQL, and
* technically, is merely a special {@link DataType#custom(String) custom type}
* from the driver's point of view.
*
* @return the Duration type. The returned instance is a singleton.
*/
public static DataType duration() {
return primitiveTypeMap.get(Name.DURATION);
}
/**
* Returns the name of that type.
*
* @return the name of that type.
*/
public Name getName() {
return name;
}
/**
* Returns whether this data type is frozen.
* <p/>
* This applies to User Defined Types, tuples and nested collections. Frozen types are serialized as a single value in
* Cassandra's storage engine, whereas non-frozen types are stored in a form that allows updates to individual subfields.
*
* @return whether this data type is frozen.
*/
public abstract boolean isFrozen();
/**
* Returns whether this data type represent a CQL {@link com.datastax.driver.core.DataType.CollectionType collection type},
* that is, a list, set or map.
*
* @return whether this data type name represent the name of a collection type.
*/
public boolean isCollection() {
return this instanceof CollectionType;
}
/**
* Returns the type arguments of this type.
* <p/>
* Note that only the collection types (LIST, MAP, SET) have type
* arguments. For the other types, this will return an empty list.
* <p/>
* For the collection types:
* <ul>
* <li>For lists and sets, this method returns one argument, the type of
* the elements.</li>
* <li>For maps, this method returns two arguments, the first one is the
* type of the map keys, the second one is the type of the map
* values.</li>
* </ul>
*
* @return an immutable list containing the type arguments of this type.
*/
public List<DataType> getTypeArguments() {
return Collections.emptyList();
}
/**
* Returns a set of all the primitive types, where primitive types are
* defined as the types that don't have type arguments (that is excluding
* lists, sets, maps, tuples and udts).
*
* @return returns a set of all the primitive types.
*/
public static Set<DataType> allPrimitiveTypes() {
return primitiveTypeSet;
}
/**
* Returns a String representation of this data type
* suitable for inclusion as a parameter type
* in a function or aggregate signature.
* <p/>
* In such places, the String representation might vary
* from the canonical one as returned by {@link #toString()};
* e.g. the {@code frozen} keyword is not accepted.
*
* @return a String representation of this data type
* suitable for inclusion as a parameter type
* in a function or aggregate signature.
*/
public String asFunctionParameterString() {
return toString();
}
/**
* Instances of this class represent CQL native types,
* also known as CQL primitive types.
*/
public static class NativeType extends DataType {
private NativeType(DataType.Name name) {
super(name);
}
@Override
public boolean isFrozen() {
return false;
}
@Override
public final int hashCode() {
return (name == Name.TEXT)
? Name.VARCHAR.hashCode()
: name.hashCode();
}
@Override
public final boolean equals(Object o) {
if (!(o instanceof DataType.NativeType))
return false;
NativeType that = (DataType.NativeType) o;
return this.name.isCompatibleWith(that.name);
}
@Override
public String toString() {
return name.toString();
}
}
/**
* Instances of this class represent collection types, that is,
* lists, sets or maps.
*/
public static class CollectionType extends DataType {
private final List<DataType> typeArguments;
private boolean frozen;
private CollectionType(DataType.Name name, List<DataType> typeArguments, boolean frozen) {
super(name);
this.typeArguments = typeArguments;
this.frozen = frozen;
}
@Override
public boolean isFrozen() {
return frozen;
}
@Override
public List<DataType> getTypeArguments() {
return typeArguments;
}
@Override
public final int hashCode() {
return MoreObjects.hashCode(name, typeArguments);
}
@Override
public final boolean equals(Object o) {
if (!(o instanceof DataType.CollectionType))
return false;
DataType.CollectionType d = (DataType.CollectionType) o;
return name == d.name && typeArguments.equals(d.typeArguments);
}
@Override
public String toString() {
if (name == Name.MAP) {
String template = frozen ? "frozen<%s<%s, %s>>" : "%s<%s, %s>";
return String.format(template, name, typeArguments.get(0), typeArguments.get(1));
} else {
String template = frozen ? "frozen<%s<%s>>" : "%s<%s>";
return String.format(template, name, typeArguments.get(0));
}
}
@Override
public String asFunctionParameterString() {
if (name == Name.MAP) {
String template = "%s<%s, %s>";
return String.format(template, name, typeArguments.get(0).asFunctionParameterString(), typeArguments.get(1).asFunctionParameterString());
} else {
String template = "%s<%s>";
return String.format(template, name, typeArguments.get(0).asFunctionParameterString());
}
}
}
/**
* A "custom" type is a type that cannot be expressed as a CQL type.
* <p/>
* Each custom type is merely identified by the fully qualified
* {@link #getCustomTypeClassName() class name}
* that represents this type server-side.
* <p/>
* The driver provides a minimal support for such types through
* instances of this class.
* <p/>
* A codec for custom types can be obtained via {@link TypeCodec#custom(DataType.CustomType)}.
*/
public static class CustomType extends DataType {
private final String customClassName;
private CustomType(DataType.Name name, String className) {
super(name);
this.customClassName = className;
}
@Override
public boolean isFrozen() {
return false;
}
/**
* Returns the fully qualified name
* of the subtype of
* {@code org.apache.cassandra.db.marshal.AbstractType}
* that represents this type server-side.
*
* @return the fully qualified name
* of the subtype of
* {@code org.apache.cassandra.db.marshal.AbstractType}
* that represents this type server-side.
*/
public String getCustomTypeClassName() {
return customClassName;
}
@Override
public final int hashCode() {
return MoreObjects.hashCode(name, customClassName);
}
@Override
public final boolean equals(Object o) {
if (!(o instanceof DataType.CustomType))
return false;
DataType.CustomType d = (DataType.CustomType) o;
return name == d.name && MoreObjects.equal(customClassName, d.customClassName);
}
@Override
public String toString() {
return String.format("'%s'", customClassName);
}
}
}