/* This file is part of VoltDB.
* Copyright (C) 2008-2010 VoltDB L.L.C.
*
* VoltDB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* VoltDB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with VoltDB. If not, see <http://www.gnu.org/licenses/>.
*/
package org.voltdb;
import java.math.BigDecimal;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.voltdb.types.TimestampType;
/**
* Represents a type in a VoltDB Stored Procedure or {@link VoltTable VoltTable}.
* Note that types in the database don't map 1-1 with types in the
* Stored Procedure API. Varchars, Chars and Char Arrays in the DB
* map to Strings in the API.
*/
public enum VoltType {
/**
* Used for uninitialized types in some places. Not a valid value
* for actual user data.
*/
INVALID ((byte)0, -1, null, new Class[] {}),
/**
* Used to type java null values that have no type. Not a valid value
* for actual user data.
*/
NULL ((byte)1, -1, null, new Class[] {}),
/**
* Used for some literal constants parsed by our SQL parser. Not a
* valid value for actual user data. See {@link #DECIMAL} for decimal
* type.
*/
NUMERIC ((byte)2, 0, null, new Class[] {}),
/**
* 1-byte signed 2s-compliment byte.
* Lowest value means NULL in the database.
*/
TINYINT ((byte)3, 1, "tinyint", new Class[] {byte.class, Byte.class}),
/**
* 2-byte signed 2s-compliment short.
* Lowest value means NULL in the database.
*/
SMALLINT ((byte)4, 2, "smallint", new Class[] {short.class, Short.class}),
/**
* 4-byte signed 2s-compliment integer.
* Lowest value means NULL in the database.
*/
INTEGER ((byte)5, 4, "integer",
new Class[] {int.class, Integer.class, AtomicInteger.class}),
/**
* 8-byte signed 2s-compliment long.
* Lowest value means NULL in the database.
*/
BIGINT ((byte)6, 8, "bigint",
new Class[] {long.class, Long.class, AtomicLong.class}),
/**
* 8-bytes in IEEE 754 "double format".
* Some NaN values may represent NULL in the database (TBD).
*/
FLOAT ((byte)8, 8, "float",
new Class[] {double.class, Double.class, float.class, Float.class}),
/**
* 8-byte long value representing milliseconds after the epoch.
* The epoch is Jan. 1 1970 00:00:00 GMT. Negative values represent
* time before the epoch. This covers roughly 4000BC to 8000AD.
*/
TIMESTAMP ((byte)11, 8, "timestamp", new Class[] {TimestampType.class, Date.class}),
/**
* UTF-8 string with up to 32K chars.
* The database supports char arrays and varchars
* but the API uses strings.
*/
STRING ((byte)9, -1, "varchar",
new Class[] {String.class, byte[].class, Byte[].class}),
/**
* VoltTable type for Procedure parameters
*/
VOLTTABLE ((byte)21, -1, null, new Class[] {VoltTable.class}),
/**
* Fixed precision=38, scale=12 storing sign and null-status in a preceding byte
*/
DECIMAL ((byte)22, 16, "decimal", new Class[] {BigDecimal.class}),
/**
* Fixed precision=38, scale=12 string representation of a decimal
*/
DECIMAL_STRING ((byte)23, 9, "decimal", new Class[] {}),
/**
* Boolean hack...
*/
BOOLEAN ((byte)24, 1, "boolean", new Class[] {boolean.class, Boolean.class});
/**
* Size in bytes of the maximum length for a VoltDB field value, presumably a string or blob
*/
public static final int MAX_VALUE_LENGTH = 1048576;
public static final String MAX_VALUE_LENGTH_STR = String.valueOf(MAX_VALUE_LENGTH / 1024) + "k";
/**
* Fixed precision 8-byte value with 4 decimal places of precision.
* Stored as an 8-byte long value representing 10,000x the actual value.
*/
//MONEY ((byte)20, 8, "money", new Class[] {});
private final byte m_val;
private final int m_lengthInBytes;
private final String m_sqlString;
private final Class<?>[] m_classes;
private VoltType(byte val, int lengthInBytes,
String sqlString, Class<?>[] classes) {
m_val = val;
m_lengthInBytes = lengthInBytes;
m_sqlString = sqlString;
m_classes = classes;
}
private static Map<Class<?>, VoltType> s_classes;
static {
s_classes = new HashMap<Class<?>, VoltType>();
for (VoltType type : values()) {
for (Class<?> cls : type.m_classes) {
s_classes.put(cls, type);
}
}
}
protected static final VoltType idx_lookup[] = new VoltType[VoltType.BOOLEAN.m_val+1];
protected static final Map<String, VoltType> name_lookup = new HashMap<String, VoltType>();
static {
for (VoltType vt : EnumSet.allOf(VoltType.class)) {
VoltType.idx_lookup[vt.m_val] = vt;
VoltType.name_lookup.put(vt.name().toLowerCase().intern(), vt);
}
}
/**
* Gets the byte that corresponds to the enum value (for serialization).
* @return A byte representing the enum value
*/
public byte getValue() {
return m_val;
}
/**
* Return the java class that is matched to a given <tt>VoltType</tt>.
* @return A java class object.
* @throws RuntimeException if a type doesn't have an associated class,
* such as {@link #INVALID}.
* @see #typeFromClass
*/
public Class<?> classFromType() {
if (m_classes.length == 0) {
throw new RuntimeException("Unsupported type " + this);
}
return m_classes[0];
}
/**
* Statically create an enum value from the corresponding byte.
* @param val A byte representing an enum value
* @return The appropriate enum value
*/
// public static VoltType get(byte val) {
// assert(val < idx_lookup.length) : "Unknown type: " + val;
// VoltType type = idx_lookup[val];
// assert(type != null) : "Unknown type: " + val;
// return (type);
// }
public static VoltType get(int val) {
assert(val < idx_lookup.length) : "Unknown type: " + val;
VoltType type = idx_lookup[val];
assert(type != null) : "Unknown type: " + val;
return (type);
}
private boolean matchesString(String str) {
return str.endsWith(name());
}
/**
* Converts string representations to an enum value.
* @param str A string in the form "VoltType.TYPENAME"
* @return One of the valid enum values for VoltType
*/
public static VoltType typeFromString(String str) {
for (VoltType type: values()) {
if (type.matchesString(str)) {
return type;
}
}
if (str.equals("DECIMAL")) return DECIMAL;
if (str.endsWith("DOUBLE")) return FLOAT;
// if (str.endsWith("VARCHAR")) return STRING;
if (str.endsWith("CHAR")) return STRING;
throw new RuntimeException("Can't find type: " + str);
}
/**
* Ascertain the most appropriate <tt>VoltType</tt> given a
* java object.
* @param obj The java object to type.
* @return A <tt>VoltType</tt> or invalid if none applies.
* @see #typeFromClass
*/
public static VoltType typeFromObject(Object obj) {
assert obj != null;
Class<?> cls = obj.getClass();
return typeFromClass(cls);
}
/**
* Ascertain the most appropriate <tt>VoltType</tt> given a
* java class.
* @param cls The java class to type.
* @return A <tt>VoltType</tt> or invalid if none applies.
* @see #typeFromObject
* @see #classFromType
*/
public static VoltType typeFromClass(Class<?> cls) {
VoltType type = s_classes.get(cls);
if (type == null) {
throw new VoltTypeException("Unimplemented Object Type: " + cls);
}
return type;
}
/**
* Return the string representation of this type. Note that
* <tt>VoltType.typeFromString(voltTypeInstance.toString) == true</tt>.
* @return The string representation of this type.
*/
@Override public String toString() {
return "VoltType." + name();
}
public int getLengthInBytesForFixedTypes() {
if (m_lengthInBytes == -1) {
throw new RuntimeException(
"Asking for fixed size for non-fixed or unknown type.");
}
return m_lengthInBytes;
}
/**
* Get the corresponding SQL type as for a given <tt>VoltType</tt> enum.
* For example, {@link #STRING} will probably convert to "VARCHAR".
* @return A string representing the SQL type.
*/
public String toSQLString() {
return m_sqlString;
}
// Really hacky cast overflow detection for primitive types
// Comparison to MIN_VALUEs are <= to avoid collisions with the NULL
// bit pattern
// Probably eventually want a generic wouldCastDiscardInfo() call or
// something
boolean wouldCastOverflow(Number value)
{
boolean retval = false;
switch (this)
{
case TINYINT:
if (value.longValue() <= Byte.MIN_VALUE ||
value.longValue() > Byte.MAX_VALUE)
{
retval = true;
}
break;
case SMALLINT:
if (value.longValue() <= Short.MIN_VALUE ||
value.longValue() > Short.MAX_VALUE)
{
retval = true;
}
break;
case INTEGER:
if (value.longValue() <= Integer.MIN_VALUE ||
value.longValue() > Integer.MAX_VALUE)
{
retval = true;
}
break;
case BIGINT:
// overflow isn't detectable for Longs, just look for NULL value
// In practice, I believe that we should never get here in VoltTable
// since we check for NULL before checking for cast overflow
if (value.longValue() == NULL_BIGINT)
{
retval = true;
}
break;
case FLOAT:
// this really should never occur, also, just look for NULL
// In practice, I believe that we should never get here in VoltTable
// since we check for NULL before checking for cast overflow
if (value.doubleValue() == NULL_FLOAT)
{
retval = true;
}
break;
default:
throw new VoltTypeException("Unhandled cast overflow case, " +
"casting to: " + toString());
}
return retval;
}
// XXX I feel like it should be possible to jam this into the enum
// constructor somehow but java hates me when I move constant definitions
// above the enum constructors, so, meh
/**
* Get a value representing whichever null value is appropriate for
* the current <tt>VoltType</tt> enum. For example, if this type is
* {@link #TINYINT}, this will return a java <tt>byte</tt> with value
* -128, which is the constant NULL_TINYINT in VoltDB.
* @return A new final instance with value equal to null for a given
* type.
*/
public Object getNullValue()
{
switch (this)
{
case TINYINT:
case BOOLEAN:
return NULL_TINYINT;
case SMALLINT:
return NULL_SMALLINT;
case INTEGER:
return NULL_INTEGER;
case BIGINT:
return NULL_BIGINT;
case FLOAT:
return NULL_FLOAT;
case STRING:
return NULL_STRING;
case TIMESTAMP:
return NULL_TIMESTAMP;
case DECIMAL:
return NULL_DECIMAL;
default:
throw new VoltTypeException("No NULL value for " + toString());
}
}
static boolean isNullVoltType(Object obj)
{
boolean retval = false;
if (obj == null)
{
retval = true;
}
else if (obj == VoltType.NULL_TIMESTAMP ||
obj == VoltType.NULL_STRING ||
obj == VoltType.NULL_DECIMAL)
{
retval = true;
}
else
{
switch(typeFromObject(obj))
{
case BOOLEAN:
retval = false; // HACK
break;
case TINYINT:
retval = (((Number) obj).byteValue() == NULL_TINYINT);
break;
case SMALLINT:
retval = (((Number) obj).shortValue() == NULL_SMALLINT);
break;
case INTEGER:
retval = (((Number) obj).intValue() == NULL_INTEGER);
break;
case BIGINT:
retval = (((Number) obj).longValue() == NULL_BIGINT);
break;
case FLOAT:
retval = (((Number) obj).doubleValue() == NULL_FLOAT);
break;
case TIMESTAMP:
retval = (obj == VoltType.NULL_TIMESTAMP);
break;
case STRING:
retval = (obj == VoltType.NULL_STRING);
break;
case DECIMAL:
retval = (obj == VoltType.NULL_DECIMAL);
break;
default:
throw new VoltTypeException("Unsupported type: " +
typeFromObject(obj));
}
}
return retval;
}
/**
* Is the type a number and is it an exact value (no rounding errors)?
* @return true for integers and decimals. False for floats and strings
* and anything else.
*/
public boolean isExactNumeric() {
switch(this) {
case TINYINT:
case SMALLINT:
case INTEGER:
case BIGINT:
case DECIMAL:
return true;
default:
return false;
}
}
public static final int NULL_STRING_LENGTH = -1;
public static final byte NULL_TINYINT = Byte.MIN_VALUE;
public static final short NULL_SMALLINT = Short.MIN_VALUE;
public static final int NULL_INTEGER = Integer.MIN_VALUE;
public static final long NULL_BIGINT = Long.MIN_VALUE;
// TODO(evanj): make this a specific bit pattern?
public static final double NULL_FLOAT = -1.7E+308;
public static final Double NULL_DOUBLE = new Double(-1.7976931348623157E+308);
// for consistency at the API level, provide symbolic nulls for these types, too
private static final class NullTimestampSigil{}
public static final NullTimestampSigil NULL_TIMESTAMP = new NullTimestampSigil();
private static final class NullStringSigil{}
public static final NullStringSigil NULL_STRING = new NullStringSigil();
private static final class NullDecimalSigil{}
public static final NullDecimalSigil NULL_DECIMAL = new NullDecimalSigil();
}