/* This file is part of VoltDB. * Copyright (C) 2008-2017 VoltDB Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with VoltDB. If not, see <http://www.gnu.org/licenses/>. */ package org.voltdb; import java.math.BigDecimal; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.HashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import org.voltdb.common.Constants; import org.voltdb.types.GeographyPointValue; import org.voltdb.types.GeographyValue; import org.voltdb.types.TimestampType; import org.voltdb.types.VoltDecimalHelper; import com.google_voltpatches.common.collect.ImmutableMap; /** * Represents a type for a {@link VoltTable VoltTable} column or a SQLStmt * parameter. * Note that types in the database don't map 1-1 with types in the * Java Stored Procedure API. For example, * VARBINARY has no equivalent java class -- just byte[]. * TIMESTAMP corresponds "best" to VoltDB.TimeStampType but * also, conveniently, to java.sql.Types.TIMESTAMP. */ public enum VoltType { // This implementation tries to take an 80/20 approach to modeling the // behavior of all the specific types. That is, the VoltType class // typically provides not-too-complex method implementations that suit // the majority of the current and anticipated future types // (VoltType enum instances). Yet, specific instances, ideally as few // as possible for any given method, will use the java 7 "smart enum" // feature to override default method implementations. The intent is that // adding new instances will have a minimal and localized impact -- // requiring few default method implementations to be changed or to be // overridden by the new instance. // Secondarily, the need for overrides on existing types, especially // jdbc-invisible types, numeric types, and variable length types is // minimized by making many of the default method implementations // sensitive to these general type categories. /** * Used for uninitialized types in some places. Not a valid value * for actual user data. */ INVALID ((byte)0, new Class[] {}, null), /** * Used to type java null values that have no type. Not a valid value * for actual user data. */ NULL ((byte)1, new Class[] {}, null), /** * 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, new Class[] {}, null), /** * 1-byte signed 2s-compliment byte. * Lowest value means NULL in the database. */ TINYINT ((byte)3, "tinyint", 1, new Class[] {byte.class, Byte.class}, byte[].class, 't', java.sql.Types.TINYINT, // java.sql.Types DATA_TYPE java.sql.DatabaseMetaData.typePredBasic, // where-clauses supported "java.lang.Byte"), // getObject return type /** * 2-byte signed 2s-compliment short. * Lowest value means NULL in the database. */ SMALLINT ((byte)4, "smallint", 2, new Class[] {short.class, Short.class}, short[].class, 's', java.sql.Types.SMALLINT, // java.sql.Types DATA_TYPE java.sql.DatabaseMetaData.typePredBasic, // where-clauses supported "java.lang.Short"), // getObject return type /** * 4-byte signed 2s-compliment integer. * Lowest value means NULL in the database. */ INTEGER ((byte)5, "integer", 4, new Class[] {int.class, Integer.class, AtomicInteger.class}, int[].class, 'i', java.sql.Types.INTEGER, // java.sql.Types DATA_TYPE java.sql.DatabaseMetaData.typePredBasic, // where-clauses supported "java.lang.Integer"), // getObject return type /** * 8-byte signed 2s-compliment long. * Lowest value means NULL in the database. */ BIGINT ((byte)6, "bigint", 8, new Class[] {long.class, Long.class, AtomicLong.class}, long[].class, 'b', java.sql.Types.BIGINT, // java.sql.Types DATA_TYPE java.sql.DatabaseMetaData.typePredBasic, // where-clauses supported "java.lang.Long"), // getObject return type /** * Special purpose internal type to describe expectations for parameters to * statements that contain syntax like " integer_expr IN ? ". * This type most commonly occurs as an expected parameter type for * such statements. * It is not expected to ever be used as a VoltTable column type. */ INLIST_OF_BIGINT ((byte)7, // enum value "INLIST OF BIGINT", // unused SQL name -1, // variable length // Normally, only compatible NON-ARRAY types are listed, // but long array is included here as the special case // most suitable representation. new Class[] {long[].class}, long[][].class, // unused vector type 'B', // take-off on 'b' for bigint/long java.sql.Types.OTHER, // unused JDBC getObject result type java.sql.DatabaseMetaData.typePredNone, // basic where-clauses supported "org.voltdb.types.Long[]") // unused JDBC getObject return type { private final Class<?> COMPATIBLE_ARRAYS[] = new Class<?>[] {long[].class, Long[].class, int[].class, Integer[].class, short[].class, Short[].class, byte[].class, Byte[].class,}; @Override public boolean acceptsArray(Class<?> arrayArgClass) { for (Class<?> allowedArray : COMPATIBLE_ARRAYS) { if (allowedArray == arrayArgClass) { return true; } } return false; } }, /** * 8-bytes in IEEE 754 "double format". * Some NaN values may represent NULL in the database (TBD). */ FLOAT ((byte)8, "float", 8, new Class[] {double.class, Double.class, float.class, Float.class}, double[].class, 'f', java.sql.Types.FLOAT, // java.sql.Types DATA_TYPE java.sql.DatabaseMetaData.typePredBasic, // where-clauses supported "java.lang.Double"), // getObject return type /** * UTF-8 string with up to 32K chars. * The database supports char arrays and varchars * but the API uses strings. */ STRING ((byte)9, "varchar", new LengthRange("max_length"), new Class[] {String.class}, String[].class, 'v', java.sql.Types.VARCHAR, // java.sql.Types DATA_TYPE java.sql.DatabaseMetaData.typeSearchable, // where-clauses supported "java.lang.String") // getObject return type { @Override public boolean acceptsArray(Class<?> arrayArgClass) { return byte[].class == arrayArgClass; } @Override public boolean isCaseSensitive() { return true; } @Override public String getLiteralPrefix() { return "'"; } @Override public String getLiteralSuffix() { return "'"; } // Non-numeric and yet indexable. @Override public boolean isIndexable() { return true; } @Override public boolean isUniqueIndexable() { return true; } }, /** * Special purpose internal type to describe expectations for parameters to * statements that contain syntax like " varchar_expr IN ? ". * This type most commonly occurs as an expected parameter type for * such statements. * It is not expected to ever be used as a VoltTable column type. */ INLIST_OF_STRING ((byte)10, // enum value "INLIST OF STRING", // unused SQL name -1, // variable length // Normally, only compatible NON-ARRAY types are listed, // but String array is included here as the special case // most suitable representation. new Class[] {String[].class}, String[][].class, // unused vector type 'V', // take-off on 'v' for varchar/STRING java.sql.Types.OTHER, // unused JDBC getObject result type java.sql.DatabaseMetaData.typePredNone, // basic where-clauses supported "org.voltdb.types.String[]") // unused getObject return type { @Override public boolean acceptsArray(Class<?> arrayArgClass) { return String[].class == arrayArgClass; } }, /** * 8-byte long value representing microseconds 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, "timestamp", 8, new Class[] {TimestampType.class, java.util.Date.class, java.sql.Date.class, java.sql.Timestamp.class}, TimestampType[].class, 'p', java.sql.Types.TIMESTAMP, // java.sql.Types DATA_TYPE java.sql.DatabaseMetaData.typePredBasic, // where-clauses supported "java.sql.Timestamp") // getObject return type { @Override public String getLiteralPrefix() { return "'"; } @Override public String getLiteralSuffix() { return "'"; } // Non-numeric and yet indexable. @Override public boolean isIndexable() { return true; } @Override public boolean isUniqueIndexable() { return true; } }, /** * VoltTable type for Procedure parameters */ VOLTTABLE ((byte)21, new Class[] {VoltTable.class}, VoltTable[].class), /** * Fixed precision=38, scale=12 storing sign and null-status in a preceding byte */ DECIMAL ((byte)22, "decimal", 16, new Class[] {BigDecimal.class}, BigDecimal[].class, 'd', java.sql.Types.DECIMAL, // java.sql.Types DATA_TYPE java.sql.DatabaseMetaData.typePredBasic, // where-clauses supported "java.math.BigDecimal") // getObject return type { @Override public Integer getMinimumScale() { return 12; } @Override public Integer getMaximumScale() { return 12; } }, /** * Boolean type. Not (yet) a valid value for actual user data. */ BOOLEAN ((byte)23, "boolean", 1, new Class[] {boolean.class, Boolean.class}, boolean[].class, 'o', //'b' is taken by BIGINT java.sql.Types.BOOLEAN, // java.sql.Types DATA_TYPE java.sql.DatabaseMetaData.typePredBasic, // where-clauses supported "java.lang.Boolean") // getObject return type { // These MAY not actually be necessary, // since BOOLEAN is not really a numeric type? @Override public Integer getMinimumScale() { return 0; } @Override public Integer getMaximumScale() { return 0; } }, /** * Array of bytes of variable length */ VARBINARY ((byte)25, "varbinary", new LengthRange("max_length"), // Normally, only compatible NON-ARRAY types are listed, // but byte array is included here as the special case // most suitable representation of VARBINARY. new Class[] {byte[].class, }, byte[][].class, 'l', java.sql.Types.VARBINARY, // java.sql.Types DATA_TYPE java.sql.DatabaseMetaData.typePredBasic, // where-clauses supported "java.lang.Byte[]") // getObject return type { // Streamlined implementation avoids any copying. @Override public Object bytesToValue(byte[] value) { assert(value != null); return value; } @Override public boolean acceptsArray(Class<?> arrayArgClass) { return byte[].class == arrayArgClass || Byte[].class == arrayArgClass; } @Override public String getLiteralPrefix() { return "'"; } @Override public String getLiteralSuffix() { return "'"; } // Non-numeric and yet indexable. @Override public boolean isIndexable() { return true; } @Override public boolean isUniqueIndexable() { return true; } }, /** * Point type, for a geographical point (long, lat) */ GEOGRAPHY_POINT ((byte)26, // enum value "GEOGRAPHY_POINT", // SQL name GeographyPointValue.getLengthInBytes(), new Class[] {GeographyPointValue.class}, // Java types supported in conversion GeographyPointValue[].class, // vector type 'P', // signature char java.sql.Types.OTHER, // JDBC type (this is used for vendor specific types) java.sql.DatabaseMetaData.typePredBasic, // basic where-clauses supported "org.voltdb.types.GeographyPointValue"), // JDBC getObject return type /** * Geography type, for geographical objects (polygons, etc) */ GEOGRAPHY ((byte)27, // enum value "GEOGRAPHY", // SQL name new LengthRange(GeographyValue.MIN_SERIALIZED_LENGTH, GeographyValue.MAX_SERIALIZED_LENGTH, GeographyValue.DEFAULT_LENGTH, "max_length"), // variable length new Class[] {GeographyValue.class}, // Java types supported in conversion GeographyValue[].class, // vector type 'g', // signature char java.sql.Types.OTHER, // JDBC type (this is used for vendor specific types) java.sql.DatabaseMetaData.typePredBasic, // basic where-clauses supported "org.voltdb.types.GeographyValue") // JDBC getObject return type { /** GEOGRAPHY values ARE indexable within the limitations of the * specialized geo indexes, but one of these limitations is no * support for uniqueness. */ @Override public boolean isIndexable() { return true; } @Override public boolean isUniqueIndexable() { return false; } }, ; // PUBLIC STATIC members /** Size in bytes of the maximum length for a VoltDB field value, presumably a * <code>STRING</code> or <code>VARBINARY</code> */ public static final int MAX_VALUE_LENGTH = 1048576; public static final int MAX_VALUE_LENGTH_IN_CHARACTERS = MAX_VALUE_LENGTH / 4; /** String representation of <code>MAX_VALUE_LENGTH</code>. * @param size The size you want to represent in human readable string. * @return String representation of Size passed in. */ public static String humanReadableSize(int size) { if (size > 9999) { return String.valueOf(size / 1024) + "K"; } return String.valueOf(size) + "B"; } // INSTANCE members /** The unique enum value for this VoltType, * allows a compact representation of the VoltType. */ private final byte m_value; /** The value length in bytes if fixed length, otherwise -1 */ private final int m_lengthInBytes; /** The value length range and default in bytes if variable length, * and the associated sql type length param name, * otherwise null */ private LengthRange m_lengthAsBytesRange; /** How this type is named in sql, * for example, when declaring a column or "casting as". */ private final String m_sqlString; /** Java classes for values (normally not including arrays) that are * convertible to this type. * The first entry (if any) is considered the best matching Java class * and is also used by default in the reverse process when converting * FROM SQL column data TO java values. */ private final Class<?>[] m_classes; /** The java array type which is the preferred format for representing * multiple values of the type. These make useful procedure parameter * types. */ private final Class<?> m_vectorClass; /** Yet another compact unique representation for this VoltType, * handier than m_value for concatenating into strings that describe * compound type structures like table schema. */ private final char m_signatureChar; /** Is this type visible to JDBC? * If false, the other m_jdbc* members are ignored. */ private final boolean m_jdbcVisible; // JDBC getTypeInfo values // If we add yet more stuff to this for MySQL or ODBC or something, // it might be time to consider some sort of data-driven type specification // mechanism. /** Either a typical SQL column type well known to jdbc or the special * OTHER value for VoltDB's non-standard vendor-specific column types. */ private final int m_jdbcSqlDataType; /** It's unclear (at least to Paul) how the jdbc users/systems/tools * makes use of this jdbc standard attribute. */ private final int m_jdbcSearchable; /** This is the type that will be returned by JDBC's ResultSet.getObject(), * which usually corresponds to to VoltTable.get(), except for timestamps. */ private final String m_jdbcClass; // CONSTRUCTORS /** Constructor for non-JDBC-visible types. * This can safely stub out any attributes that are only used by jdbc. */ private VoltType(byte value, Class<?>[] classes, Class<?> vectorClass) { this(value, -1, null, null, classes, vectorClass, '0', false, // With m_jdbcVisible set false, these remaining attributes // related to JDBC are just stubbed out with // "don't care" values. They are actually N/A. java.sql.Types.OTHER, Integer.MIN_VALUE, null); } /** Constructor for JDBC-visible types. Only types constructed in this way * appear in the JDBC getTypeInfo() metadata. * Note that this includes the standard JDBC SQL types as well as VoltDB's * vendor-specific types that are exposed as JDBC extensions. * The latter specify a jdbcSqlDataType of OTHER and typically * name a VoltDB defined class as their jdbcClass value. */ private VoltType(byte value, String sqlString, int lengthInBytes, Class<?>[] classes, Class<?> vectorClass, char signatureChar, int jdbcSqlDataType, int jdbcSearchable, String jdbcClass) { this(value, lengthInBytes, null, sqlString, classes, vectorClass, signatureChar, true, jdbcSqlDataType, jdbcSearchable, jdbcClass); } /** Constructor for JDBC-visible types. Only types constructed in this way * appear in the JDBC getTypeInfo() metadata. * Note that this includes the standard JDBC SQL types as well as VoltDB's * vendor-specific types that are exposed as JDBC extensions. * The latter specify a jdbcSqlDataType of OTHER and typically * name a VoltDB defined class as their jdbcClass value. */ private VoltType(byte value, String sqlString, LengthRange lengthAsBytesRange, Class<?>[] classes, Class<?> vectorClass, char signatureChar, int jdbcSqlDataType, int jdbcSearchable, String jdbcClass) { this(value, -1, lengthAsBytesRange, sqlString, classes, vectorClass, signatureChar, true, jdbcSqlDataType, jdbcSearchable, jdbcClass); } /** Common constructor implementation for JDBC-visible and JDBC-invisible types. * This common code should ALWAYS be called and should ONLY be called through * the other special-case constructors defined above. */ private VoltType(byte value, int lengthInBytes, LengthRange lengthAsBytesRange, String sqlString, Class<?>[] classes, Class<?> vectorClass, char signatureChar, boolean jdbcVisible, int jdbcSqlDataType, int jdbcSearchable, String jdbcClass) { if (value > VOLT_TYPE_MAX_ENUM) { // The last time Paul checked (admittedly back in Java 7), a // RuntimeException gave better diagnostics than an assert when a // programmer error caused an unrecoverable issue during class // initialization. // So, throw one here and hope it shows up somewhere in the noisy // traces reported by the jvm. throw new RuntimeException("The VoltType enum byte value " + value + "falls outside the expected range. Consider reassigning " + "the value to fill a gap within the existing range OR " + "extending the range from its current value of " + "VOLT_TYPE_MAX_ENUM = " + VOLT_TYPE_MAX_ENUM); } m_value = value; m_lengthInBytes = lengthInBytes; m_lengthAsBytesRange = lengthAsBytesRange; m_sqlString = sqlString; m_classes = classes; m_vectorClass = vectorClass; m_signatureChar = signatureChar; m_jdbcVisible = jdbcVisible; m_jdbcSqlDataType = jdbcSqlDataType; m_jdbcSearchable = jdbcSearchable; m_jdbcClass = jdbcClass; } /** Support class to represent optional value length variability. */ private static final class LengthRange { private final int m_min; private final int m_max; private final int m_default; private final String m_name; /** The simplest variable length type model matches the usage of * STRING and VARBINARY. */ LengthRange(String maxLengthParamName) { this(1, MAX_VALUE_LENGTH, DEFAULT_COLUMN_SIZE, maxLengthParamName); } /** The variable length type model can also be customized to meet * the needs of new classes with constraints different from * STRING and VARBINARY, for example GEOGRAPHY. */ LengthRange(int minBytes, int maxBytes, int defaultLength, String maxLengthParamName) { m_min = minBytes; m_max = maxBytes; m_default = defaultLength; m_name = maxLengthParamName; } // These constants should be kept up-to-date with those in DDLCompiler. // Don't reference DDLCompiler here since this class is used in the client. private static final int MAX_COLUMNS = 1024; private static final int MAX_ROW_SIZE = 1024 * 1024 * 2; private static final int DEFAULT_COLUMN_SIZE = MAX_ROW_SIZE / MAX_COLUMNS; int getMaxLengthInBytes() { return m_max; } int getMinLengthInBytes() { return m_min; } int getDefaultLengthInBytes() { return m_default; } String getLengthParamName() { return m_name; } }; // PRIVATE STATIC members: private final static ImmutableMap<Class<?>, VoltType> s_classes; /** The maximum byte value that is allotted to a VoltType instance. * Update this MAX if you MUST add a VoltType byte value beyond the * current range -- instead of filling gaps in the range. * This MAX allows s_types, the byte-value-to-VoltType-object "map" to be * implemented with a simple (small, dense) VoltType array. * There's a hard ceiling of 127 imposed by the use of signed bytes * as unique keys/indexes. */ private final static byte VOLT_TYPE_MAX_ENUM = 30; private final static VoltType s_types[] = new VoltType[(VOLT_TYPE_MAX_ENUM)+1]; static { ImmutableMap.Builder<Class<?>, VoltType> b = ImmutableMap.builder(); HashMap<Class<?>, VoltType> validation = new HashMap<>(); for (VoltType type : values()) { s_types[type.m_value] = type; for (Class<?> cls : type.m_classes) { // Avoid subtle effects when VoltTypes have duplicate m_classes entries (java classes), // so that the association of a java class with the earlier VoltType gets obliterated // by its association with the later VoltType. // The effects of an assert in the middle of class initialization is surprisingly cryptic, // at least when exercised by the "ant junit" suite, so for a SLIGHTLY less cryptic response, // throw a generic runtime exception. // assert(s_classes.get(cls) == null); if (validation.get(cls) != null) { // This message seems to just get buried by the java runtime. throw new RuntimeException("Associate each java class like " + cls.getSimpleName() + " with at most one VoltType."); } validation.put(cls, type); b.put(cls, type); } } s_classes = b.build(); } // PUBLIC ACCESSORS and other instance methods and static conversion methods /** Gets the byte that corresponds to the VoltType (for serialization). * @return A byte representing the VoltType */ public byte getValue() { return m_value; } /** 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]; } /** 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<?> vectorClassFromType() { if (m_vectorClass == null) { throw new RuntimeException("Unsupported type " + this); } return m_vectorClass; } /** 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) { VoltType type = (val < s_types.length) ? s_types[val] : null; if (type == null) { throw new AssertionError("Unknown type: " + String.valueOf(val)); } return type; } private boolean matchesString(String str) { return str.toUpperCase().endsWith(name()); } /** Converts string representations to an enum value. * @param str A string in the form "TYPENAME" or "VoltType.TYPENAME", * e.g. "BIGINT" or "VoltType.VARCHAR" * @return One of the valid instances of VoltType */ public static VoltType typeFromString(String str) { if (str == null) { return NULL; } if (str.startsWith("VoltType.")) { str = str.substring("VoltType.".length()); } if (str.compareToIgnoreCase("null") == 0) { return NULL; } for (VoltType type: values()) { if (type.matchesString(str)) { return type; } } if (str.equalsIgnoreCase("DOUBLE")) { return FLOAT; } if (str.equalsIgnoreCase("CHARACTER") || str.equalsIgnoreCase("CHAR") || str.equalsIgnoreCase("VARCHAR")) { 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>. * @throws VoltTypeException 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>. * @throws VoltTypeException 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) == voltTypeInstance</tt>. * @return The string representation of this type. */ @Override public String toString() { return "VoltType." + name(); } public String getName() { return name(); } public boolean isVariableLength() { return m_lengthAsBytesRange != null; } /** Get the number of bytes required to store the fixed length type. * Variable-length types should throw a RuntimeException. * @return An integer value representing a number of bytes. */ public int getLengthInBytesForFixedTypes() { if (m_lengthAsBytesRange != null) { throw new RuntimeException( "Asking for fixed size for non-fixed or unknown type:" + m_sqlString); } return m_lengthInBytes; } // Variable-length types simply return -1. public int getLengthInBytesForFixedTypesWithoutCheck() { return m_lengthInBytes; } /** Get the minimum number of bytes required to store the type * @return An integer value representing a number of bytes. */ public int getMinLengthInBytes() { return m_lengthAsBytesRange == null ? m_lengthInBytes : m_lengthAsBytesRange.getMinLengthInBytes(); } /** Get the maximum number of bytes required to store the type * @return An integer value representing a number of bytes. */ public int getMaxLengthInBytes() { return m_lengthAsBytesRange == null ? m_lengthInBytes : m_lengthAsBytesRange.getMaxLengthInBytes(); } // JDBC getTypeInfo() accessors /** For JDBC, returns the prefix (if any, otherwise null) * used with SQL literal constants of this type. * Individual VoltTypes can override to enable this, * typically to return a single quote. * @return null, or, if overridden for a type, * the prefix string. */ public String getLiteralPrefix() { return null; } /** For JDBC, returns the suffix (if any, otherwise null) * used with SQL literal constants of this type. * Individual VoltTypes can override to enable this, * typically to return a single quote. * @return null, or, if overridden for a type, * the suffix string */ public String getLiteralSuffix() { return null; } /** For JDBC, the name(s) of any type-specific parameter(s), * e.g. "max_length" used when defining sql columns of this type. * FUTURE?: It's not clear what format the JDBC would expect if there * were more than one -- maybe comma separated? * @return null for fixed-length types, * usually "max_length" for variable length type */ public String getCreateParams() { return m_lengthAsBytesRange == null ? null : m_lengthAsBytesRange.getLengthParamName(); } /** Individual VoltTypes like String can override to enable this functionality. * Normally, other types ignore case when expressed as strings, like in * hex values for varbinary, or wkt values for geo types. * @return false unless overridden for a case sensitivite type like String */ public boolean isCaseSensitive() { return false; } /** Non-integer numeric VoltTypes must override this method. * @return 0 for integer types, null for non-numeric, or some * other value if overridden for a specific type like DECIMAL */ public Integer getMinimumScale() { return isAnyIntegerType() ? (Integer)0 : null; } /** Non-integer numeric VoltTypes must override this method. * @return 0 for integer types, null for non-numeric, or some * other value if overridden for a specific type like DECIMAL */ public Integer getMaximumScale() { return isAnyIntegerType() ? (Integer)0 : null; } /** VoltTypes for indexable non-numeric values must override. * @return true if the type is supported by VoltDB indexes */ public boolean isIndexable() { return isNumber(); } /** VoltTypes with special restrictions about uniqueness support must override. * @return true if the type is supported by VoltDB (assume)unique indexes */ public boolean isUniqueIndexable() { return isNumber(); } /** Most VoltTypes are not compatible with an array-typed value. * @see VARBINARY * @param arrayArgClass a java array class like byte[] or String[] * @return false, unless overridden to enable a specific VoltType * (like VARBINARY) to support certain specific array types (like byte[]). */ public boolean acceptsArray(Class<?> arrayArgClass) { return false; } /** 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; } /** <p>Is this type visible to JDBC</p> * @return JDBC visibility */ public boolean isJdbcVisible() { return m_jdbcVisible; } /** Get the java.sql.Types type of this type. * @return int representing SQL type of the VoltDB type. */ public int getJdbcSqlType() { return m_jdbcSqlDataType; } /** VoltDB treats nullability as orthogonal to type, * so all types are nullable. * @return the jdbc constant representing a nullable type */ public int getNullable() { return java.sql.DatabaseMetaData.typeNullable; } public int getSearchable() { return m_jdbcSearchable; } /** Numeric types are all signed types, so return false. * isUnsigned is N/A to other types, so return null. * If/when we support unsigned types, their VoltTypes * should override this function to return true. * @return null for non-numeric types, false for numeric, * unless overridden by a (hypothetical) unsigned numeric type */ public Boolean isUnsigned() { return isNumber() ? (Boolean)false : null; } public String getJdbcClass() { return m_jdbcClass; } /** 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() { return isAnyIntegerType() || this == DECIMAL; } /** Is this type an integer type in the EE? * True for <code>TINYINT</code>, <code>SMALLINT</code>, * <code>INTEGER</code>, <code>BIGINT</code> and <code>TIMESTAMP</code>. * @return True if integer type. False if not */ public boolean isBackendIntegerType() { return isAnyIntegerType() || this == TIMESTAMP; } /** Is this type an integer type? True for <code>TINYINT</code>, <code>SMALLINT</code>, * <code>INTEGER</code>, <code>BIGINT</code>. * @return True if integer type. False if not */ public boolean isAnyIntegerType() { switch (this) { case TINYINT: case SMALLINT: case INTEGER: case BIGINT: return true; default: return false; } } public boolean isNumber() { switch (this) { case TINYINT: case SMALLINT: case INTEGER: case BIGINT: case FLOAT: case DECIMAL: return true; default: return false; } } /* Indicate whether a value can be assigned to this type without loss of * range or precision, important for index key and partition key * initialization */ public boolean canExactlyRepresentAnyValueOf(VoltType otherType) { // self to self conversion is obviously fine. if (this == otherType) return true; if (otherType.isBackendIntegerType()) { if (this.isBackendIntegerType()) { // Don't allow integers getting smaller. return this.getMaxLengthInBytes() >= otherType.getMaxLengthInBytes(); } else if (this == VoltType.FLOAT) { // Non-big integers make acceptable (exact) floats if (otherType != VoltType.BIGINT) { return true; } } // Not sure about integer-to-decimal: for now, just give up. } return false; } /** Get a char that uniquely identifies a type. * Used to create concise schema signatures. * @return A char representing the type. */ public char getSignatureChar() { // This should never be called for an incomplete or invalid VoltType. assert(m_signatureChar != '0'); return m_signatureChar; } // Integer[0] is the column size and Integer[1] is the radix // I'd love to get this magic into the type construction, but // not happening this go-round. --izzy public Integer[] getTypePrecisionAndRadix() { Integer[] col_size_radix = {null, null}; switch (this) { case TINYINT: case SMALLINT: case INTEGER: case BIGINT: case TIMESTAMP: col_size_radix[0] = (getLengthInBytesForFixedTypes() * 8) - 1; col_size_radix[1] = 2; break; case FLOAT: col_size_radix[0] = 53; // magic for double col_size_radix[1] = 2; break; case DECIMAL: col_size_radix[0] = VoltDecimalHelper.kDefaultPrecision; col_size_radix[1] = 10; break; case STRING: case VARBINARY: case GEOGRAPHY: col_size_radix[0] = VoltType.MAX_VALUE_LENGTH; break; default: // What's the right behavior here? } return col_size_radix; } /** The size specifier for columns with a variable-length type is optional in a * CREATE TABLE or ALTER TABLE statement. If no size is specified, VoltDB chooses * a default size. * @return the default size for the given type */ public int defaultLengthForVariableLengthType() { assert(m_lengthAsBytesRange != null); return m_lengthAsBytesRange.getDefaultLengthInBytes(); } public String getMostCompatibleJavaTypeName() { if (m_classes.length > 0) { Class<?> javaClass = m_classes[0]; return javaClass.getSimpleName(); } return "(unknown?)"; } // OTHER METHODS that are as much about specific VALUES as about their TYPES public String getMaxValueForKeyPadding() { switch (this) { case TINYINT: return MAX_TINYINT.toString(); case SMALLINT: return MAX_SMALLINT.toString(); case INTEGER: return MAX_INTEGER.toString(); case BIGINT: return MAX_BIGINT.toString(); case TIMESTAMP: return MAX_TIMESTAMP.toString(); case FLOAT: return MAX_FLOAT.toString(); default: return null; } } // 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) { switch (this) { case TINYINT: return (value.longValue() <= Byte.MIN_VALUE || value.longValue() > Byte.MAX_VALUE); case SMALLINT: return (value.longValue() <= Short.MIN_VALUE || value.longValue() > Short.MAX_VALUE); case INTEGER: return (value.longValue() <= Integer.MIN_VALUE || value.longValue() > Integer.MAX_VALUE); 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 return (value.longValue() == NULL_BIGINT); 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 return (value.doubleValue() == NULL_FLOAT); default: throw new VoltTypeException("Unhandled cast overflow case, " + "casting to: " + toString()); } } /** 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: 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_OR_VARBINARY; case TIMESTAMP: return NULL_TIMESTAMP; case DECIMAL: return NULL_DECIMAL; case VARBINARY: return NULL_STRING_OR_VARBINARY; case GEOGRAPHY_POINT: return NULL_POINT; case GEOGRAPHY: return NULL_GEOGRAPHY; default: throw new VoltTypeException("No NULL value for " + toString()); } } public static boolean isVoltNullValue(Object obj) { if ((obj == null) || (obj == VoltType.NULL_TIMESTAMP) || (obj == VoltType.NULL_STRING_OR_VARBINARY) || (obj == VoltType.NULL_DECIMAL) || (obj == VoltType.NULL_POINT) || (obj == VoltType.NULL_GEOGRAPHY)) { return true; } switch (typeFromObject(obj)) { case TINYINT: return (((Number) obj).byteValue() == NULL_TINYINT); case SMALLINT: return (((Number) obj).shortValue() == NULL_SMALLINT); case INTEGER: return (((Number) obj).intValue() == NULL_INTEGER); case BIGINT: return (((Number) obj).longValue() == NULL_BIGINT); case FLOAT: return (((Number) obj).doubleValue() == NULL_FLOAT); case TIMESTAMP: case STRING: case VARBINARY: case DECIMAL: case GEOGRAPHY_POINT: case GEOGRAPHY: // already checked these above return false; default: throw new VoltTypeException("Unsupported type: " + typeFromObject(obj)); } } /** Converts the object into bytes for hashing. * @param obj a value to be hashed * @return a byte array representation of obj * OR null if the obj is java null or any other Volt representation * of a null value. */ public static byte[] valueToBytes(Object obj) { if (isVoltNullValue(obj)) { return null; } if (obj instanceof byte[]) { return (byte[]) obj; } if (obj instanceof String ) { return ((String) obj).getBytes(Constants.UTF8ENCODING); } long value = 0; if (obj instanceof Long) { value = ((Long) obj).longValue(); } else if (obj instanceof Integer) { value = ((Integer)obj).intValue(); } else if (obj instanceof Short) { value = ((Short)obj).shortValue(); } else if (obj instanceof Byte) { value = ((Byte)obj).byteValue(); } ByteBuffer buf = ByteBuffer.allocate(8); buf.order(ByteOrder.LITTLE_ENDIAN); buf.putLong(value); return buf.array(); } /** Converts a byte array with type back to the original partition value. * This is the inverse of @see VoltType#valueToBytes(Object) valueToBytes * @param value Byte array representation of partition parameter. * @return Java object of the correct type. */ public Object bytesToValue(byte[] value) { assert(value != null); if ((this == NULL)) { return null; } ByteBuffer buf = ByteBuffer.wrap(value); buf.order(ByteOrder.LITTLE_ENDIAN); switch (this) { case BIGINT: return buf.getLong(); case STRING: return new String(value, Constants.UTF8ENCODING); case INTEGER: return buf.getInt(); case SMALLINT: return buf.getShort(); case TINYINT: return buf.get(); default: throw new RuntimeException( "bytesToValue failed to convert a non-partitionable type."); } } // VALUE constants /** Length value for a null string. */ public static final int NULL_STRING_LENGTH = -1; /** Null value for <code>TINYINT</code>. */ public static final byte NULL_TINYINT = Byte.MIN_VALUE; /** Null value for <code>SMALLINT</code>. */ public static final short NULL_SMALLINT = Short.MIN_VALUE; /** Null value for <code>INTEGER</code>. */ public static final int NULL_INTEGER = Integer.MIN_VALUE; /** Null value for <code>BIGINT</code>. */ public static final long NULL_BIGINT = Long.MIN_VALUE; /** Null value for <code>FLOAT</code>. */ public static final double NULL_FLOAT = -1.7E+308; /** Max value for a <code>TINYINT</code> index component. */ private static final Byte MAX_TINYINT = new Byte(Byte.MAX_VALUE); /** Max value for a <code>SMALLINT</code> index component. */ private static final Short MAX_SMALLINT = new Short(Short.MAX_VALUE); /** Max value for a <code>INTEGER</code> index component. */ private static final Integer MAX_INTEGER = new Integer(Integer.MAX_VALUE); /** Max value for a <code>BIGINT</code> index component. */ private static final Long MAX_BIGINT = new Long(Long.MAX_VALUE); /** Max value for a <code>TIMESTAMP</code> index component. */ private static final Long MAX_TIMESTAMP = new Long(Long.MAX_VALUE); /** Max value for a <code>FLOAT</code> index component. */ private static final Float MAX_FLOAT = new Float(Float.MAX_VALUE); // for consistency at the API level, provide symbolic nulls for these types, too private static final class NullTimestampSigil{} /** Null value for <code>TIMESTAMP</code>. */ public static final NullTimestampSigil NULL_TIMESTAMP = new NullTimestampSigil(); private static final class NullStringOrVarbinarySigil{} /** Null value for <code>STRING</code> or <code>VARBINARY</code>. */ public static final NullStringOrVarbinarySigil NULL_STRING_OR_VARBINARY = new NullStringOrVarbinarySigil(); private static final class NullDecimalSigil{} /** Null value for <code>DECIMAL</code>. */ public static final NullDecimalSigil NULL_DECIMAL = new NullDecimalSigil(); private static final class NullPointSigil{} /** Null value for <code>GEOGRAPHY_POINT</code>. */ public static final NullPointSigil NULL_POINT = new NullPointSigil(); private static final class NullGeographySigil{} /** Null value for <code>GEOGRAPHY</code>. */ public static final NullGeographySigil NULL_GEOGRAPHY = new NullGeographySigil(); }