/* 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.utils;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.logging.Logger;
import org.voltdb.VoltType;
import org.voltdb.VoltTypeException;
import org.voltdb.types.TimestampType;
import org.voltdb.types.VoltDecimalHelper;
public abstract class VoltTypeUtil {
private static final Logger LOG = Logger.getLogger(VoltTypeUtil.class.getName());
protected final static Random rand = new Random();
protected static final Long DATE_STOP = System.currentTimeMillis() / 1000;
protected static final Long DATE_START = VoltTypeUtil.DATE_STOP - 153792000;
public static Object getRandomValue(VoltType type) {
return getRandomValue(type, 32, 0, rand);
}
public static Object getRandomValue(VoltType type, int maxSize) {
return getRandomValue(type, maxSize, 0, rand);
}
public static Object getRandomValue(VoltType type, int maxSize, double nullFraction, Random r) {
assert(type != null);
assert(maxSize >= 0);
assert(nullFraction >= 0.0);
assert(nullFraction <= 1.0);
assert(r != null);
// return null for some fraction of requests
if (r.nextDouble() < nullFraction) {
return null;
}
Object ret = null;
switch (type) {
// --------------------------------
// INTEGERS
// --------------------------------
case TINYINT:
ret = Byte.valueOf((byte) Math.abs(r.nextInt() % 128));
break;
case SMALLINT:
ret = Short.valueOf((short) Math.abs(r.nextInt() % 32768));
break;
case INTEGER:
ret = Integer.valueOf(Math.abs(r.nextInt() % 100000));
break;
case BIGINT:
ret = Long.valueOf(Math.abs(r.nextInt() % 100000));
break;
// --------------------------------
// FLOATS
// --------------------------------
case FLOAT:
ret = Double.valueOf(Math.abs(r.nextDouble()));
break;
// --------------------------------
// STRINGS
// --------------------------------
case STRING: {
assert(maxSize > 0);
int size = r.nextInt(maxSize) + 1;
char[] str = new char[size];
for (int ctr = 0; ctr < size; ctr++) {
char data = (char)(r.nextInt(128));
//
// Skip quotation marks
//
if (Character.isLetter(data) == false) {
ctr--;
} else {
str[ctr] = data;
}
}
ret = new String(str);
break;
}
// --------------------------------
// VARBINARY
// --------------------------------
case VARBINARY: {
assert(maxSize > 0);
int size = r.nextInt(maxSize) + 1;
byte[] bytestr = new byte[size];
r.nextBytes(bytestr);
ret = bytestr;
break;
}
// --------------------------------
// TIMESTAMP
// --------------------------------
case TIMESTAMP: {
long timestamp = r.nextInt((int)(VoltTypeUtil.DATE_STOP - VoltTypeUtil.DATE_START)) + VoltTypeUtil.DATE_START;
ret = new TimestampType(Long.valueOf(timestamp * 1000));
break;
}
// --------------------------------
// DECIMAL
// --------------------------------
case DECIMAL: {
BigDecimal bd = new BigDecimal(r.nextDouble());
ret = VoltDecimalHelper.setDefaultScale(bd);
break;
}
// --------------------------------
// INVALID
// --------------------------------
default:
LOG.severe("ERROR: Unable to generate random value for invalid ValueType '" + type + "'");
} // SWITCH
return (ret);
}
private static final VoltType CAST_ORDER[] = {
VoltType.STRING,
VoltType.FLOAT,
VoltType.DECIMAL,
VoltType.TIMESTAMP,
VoltType.BIGINT,
};
public static final String VoltTypeCastErrorMessage = "ERROR: Unable to determine cast type for '%s' and '%s' types";
public static VoltType determineImplicitCasting(VoltType left, VoltType right) {
//
// Make sure both are valid
//
if (left == VoltType.INVALID || right == VoltType.INVALID) {
throw new VoltTypeException(String.format(VoltTypeCastErrorMessage, left, right));
}
// Check for NULL first, if either type is NULL the output is always NULL
// XXX do we need to actually check for all NULL_foo types here?
else if (left == VoltType.NULL || right == VoltType.NULL)
{
return VoltType.NULL;
}
//
// No mixing of strings and numbers
//
else if ((left == VoltType.STRING && right != VoltType.STRING) ||
(left != VoltType.STRING && right == VoltType.STRING))
{
throw new VoltTypeException(String.format(VoltTypeCastErrorMessage, left, right));
}
// No mixing of numbers and non-numbers
else if ((left.isNumber() && !right.isNumber()) ||
(right.isNumber() && !left.isNumber()))
{
throw new VoltTypeException(String.format(VoltTypeCastErrorMessage, left, right));
}
//
// The following list contains the rules that use for casting:
//
// (1) If both types are a STRING, the output is always a STRING
// Note that up above we made sure that they do not mix strings and numbers
// Example: STRING + STRING -> STRING
// (2) Floating-point types take precedence over integers
// Example: FLOAT + INTEGER/DECIMAL -> FLOAT
// (3) If one type is a DECIMAL, the output is always a DECIMAL (FLOAT has been handled already)
// Note that at this step we made sure that DECIMAL only mixes with EXACT number
//
// (4) Specific types for floating-point and integer types take precedence
// over the more general types
// Example: MONEY + FLOAT -> MONEY
// Example: TIMESTAMP + INTEGER -> TIMESTAMP
for (VoltType cast_type : CAST_ORDER) {
//
// If any one of the types is the current cast type, we'll use that
//
if (left == cast_type || right == cast_type)
{
return cast_type;
}
}
// If we have INT types smaller than BIGINT
// promote the output up to BIGINT
if ((left == VoltType.INTEGER || left == VoltType.SMALLINT || left == VoltType.TINYINT) &&
(right == VoltType.INTEGER || right == VoltType.SMALLINT || right == VoltType.TINYINT))
{
return VoltType.BIGINT;
}
// If we get here, we couldn't figure out what to do
throw new VoltTypeException("ERROR: Unable to determine cast type for '" +
left + "' and '" + right + "' types");
}
/**
* Returns a casted object of the input value string based on the given type
* @throws ParseException
*/
public static Object getObjectFromString(VoltType type, String value)
throws ParseException
{
Object ret = null;
switch (type) {
// NOTE: All runtime integer parameters are actually Longs,so we will have problems
// if we actually try to convert the object to one of the smaller numeric sizes
// --------------------------------
// INTEGERS
// --------------------------------
case TINYINT:
//ret = Byte.valueOf(value);
//break;
case SMALLINT:
//ret = Short.valueOf(value);
//break;
case INTEGER:
//ret = Integer.valueOf(value);
//break;
case BIGINT:
ret = Long.valueOf(value);
break;
// --------------------------------
// FLOATS
// --------------------------------
case FLOAT:
ret = Double.valueOf(value);
break;
// --------------------------------
// STRINGS
// --------------------------------
case STRING:
ret = value;
break;
case DECIMAL:
case VARBINARY:
if (value != null) {
throw new RuntimeException("Only NULL default values for DECIMAL " +
"and VARBINARY columns are supported right now");
}
break;
// --------------------------------
// TIMESTAMP
// --------------------------------
case TIMESTAMP: {
// Support either long values (microseconds since epoch) or timestamp strings.
try {
// Try to parse it as a long first.
ret = new TimestampType(Long.parseLong(value));
}
catch (NumberFormatException e) {
// It failed to parse as a long - parse it as a timestamp string.
Date date = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy").parse(value);
ret = new TimestampType(date.getTime() * 1000);
}
break;
}
// --------------------------------
// INVALID
// --------------------------------
default:
LOG.severe("ERROR: Unable to get object from string for invalid ValueType '" + type + "'");
}
return (ret);
}
public static java.sql.Timestamp getSqlTimestampFromMicrosSinceEpoch(long timestamp) {
java.sql.Timestamp result;
// The lower 6 digits of the microsecond timestamp (including the "double-counted" millisecond digits)
// must be scaled up to get the 9-digit (rounded) nanosecond value.
int remaining = (int) (timestamp % 1000000);
// timestamp after epoch or timestamp without fractional seconds
if (timestamp >= 0 || remaining == 0) {
result = new java.sql.Timestamp(timestamp/1000);
result.setNanos(remaining*1000);
} else {
// timestamp before epoch that has fractional seconds
result = new java.sql.Timestamp((timestamp/1000000 - 1) * 1000);
result.setNanos((remaining+1000000) * 1000 );
}
return result;
}
/**
* Randomizer that can be used to repeatedly generate random values based on type.
* If unique is true it tracks generated values and retries until new ones are unique.
*/
public static class Randomizer {
private final VoltType type;
private final int maxSize;
private final double nullFraction;
private final Set<Object> uniqueValues;
private final Random rand;
public Randomizer(VoltType type, int maxSize, double nullFraction, boolean unique, Random rand) {
this.type = type;
this.maxSize = maxSize;
this.nullFraction = nullFraction;
this.uniqueValues = unique ? new HashSet<Object>() : null;
this.rand = rand;
}
public Object getValue() {
Object value = null;
while (value == null) {
value = getRandomValue(this.type, this.maxSize, this.nullFraction, this.rand);
// If we need a unique value and it isn't force a retry.
if (this.uniqueValues != null && uniqueValues.contains(value)) {
value = null;
}
}
if (uniqueValues != null) {
// Keep track of unique values.
uniqueValues.add(value);
}
return value;
}
}
/**
* If the type is NUMERIC from hsqldb, VoltDB has to decide its real type.
* It's either INTEGER or DECIMAL according to the SQL Standard.
* Thanks for Hsqldb 1.9, FLOAT literal values have been handled well with E sign.
* @param vt
* @param value
* @return
*/
public static VoltType getNumericLiteralType(VoltType vt, String value) {
try {
Long.parseLong(value);
} catch (NumberFormatException e) {
// Our DECIMAL may not be bigger/smaller enough to store the constant value
return VoltType.DECIMAL;
}
return vt;
}
}