Skip to content

Commit

Permalink
Merge pull request #26 from VoltDB/bettertimes
Browse files Browse the repository at this point in the history
ENG-2945 java Date and Timestamp support
  • Loading branch information
paulmartel committed May 30, 2012
2 parents dd28db8 + 38e4e87 commit 6e104df
Show file tree
Hide file tree
Showing 16 changed files with 613 additions and 68 deletions.
36 changes: 32 additions & 4 deletions src/frontend/org/voltdb/ParameterSet.java
Expand Up @@ -234,7 +234,9 @@ else if (cls == Double.class)
out.writeString((String) obj);
break;
case TIMESTAMP:
out.writeTimestamp((TimestampType) obj);
// FastSerializer does not need to distinguish time stamp types from long counts of microseconds.
long micros = timestampToMicroseconds(obj);
out.writeLong(micros);
break;
case DECIMAL:
VoltDecimalHelper.serializeBigDecimal((BigDecimal)obj, out);
Expand Down Expand Up @@ -427,7 +429,8 @@ static private Object readOneParameter(FastDeserializer in) throws IOException {
}
return bin_val;
case TIMESTAMP:
return in.readTimestamp();
final long micros = in.readLong();
return new TimestampType(micros);
case VOLTTABLE:
return in.readObject(VoltTable.class);
case DECIMAL: {
Expand Down Expand Up @@ -730,7 +733,7 @@ else if (obj == VoltType.NULL_DECIMAL) {
else if (cls == Double.class)
buf.putDouble(((Double) obj).doubleValue());
else
throw new RuntimeException("Can't cast paramter type to Double");
throw new RuntimeException("Can't cast parameter type to Double");
break;
case STRING:
if (!strIter.hasNext()) {
Expand All @@ -740,7 +743,8 @@ else if (cls == Double.class)
FastSerializer.writeString(strIter.next(), buf);
break;
case TIMESTAMP:
buf.putLong(((TimestampType) obj).getTime());
long micros = timestampToMicroseconds(obj);
buf.putLong(micros);
break;
case DECIMAL:
VoltDecimalHelper.serializeBigDecimal((BigDecimal)obj, buf);
Expand All @@ -753,4 +757,28 @@ else if (cls == Double.class)
}
}
}

static long timestampToMicroseconds(Object obj) {
long micros = 0;
// Adapt the Java standard classes' millisecond count to TIMESTAMP's microseconds.
if (obj instanceof java.util.Date) {
micros = ((java.util.Date) obj).getTime()*1000;
// For Timestamp, also preserve exactly the right amount of fractional second precision.
if (obj instanceof java.sql.Timestamp) {
long nanos = ((java.sql.Timestamp) obj).getNanos();
// XXX: This may be slightly controversial, but...
// Throw a conversion error rather than silently rounding/dropping sub-microsecond precision.
if ((nanos % 1000) != 0) {
throw new RuntimeException("Can't serialize TIMESTAMP value with fractional microseconds");
}
// Use MOD 1000000 to prevent double-counting of milliseconds which figure into BOTH getTime() and getNanos().
// DIVIDE nanoseconds by 1000 to get microseconds.
micros += ((nanos % 1000000)/1000);
}
} else if (obj instanceof TimestampType) {
// Let this throw a cast exception if obj is not actually a TimestampType instance.
micros = ((TimestampType) obj).getTime();
}
return micros;
}
}
57 changes: 49 additions & 8 deletions src/frontend/org/voltdb/ProcedureRunner.java
Expand Up @@ -685,11 +685,52 @@ else if (Array.getLength(param) == 0) {
return new TimestampType((String)param);
}
catch (IllegalArgumentException e) {
// ignore errors if it's not the right format
// Defer errors to the generic Exception throw below, if it's not the right format
}
}
}
if (slot == BigDecimal.class) {
else if (slot == java.sql.Timestamp.class) {
if (param instanceof java.sql.Timestamp) return param;
if (param instanceof java.util.Date) return new java.sql.Timestamp(((java.util.Date) param).getTime());
if (param instanceof TimestampType) return ((TimestampType) param).asJavaTimestamp();
// If a string is given for a date, use java's JDBC parsing.
if (pclass == String.class) {
try {
return java.sql.Timestamp.valueOf((String) param);
}
catch (IllegalArgumentException e) {
// Defer errors to the generic Exception throw below, if it's not the right format
}
}
}
else if (slot == java.sql.Date.class) {
if (param instanceof java.sql.Date) return param; // covers java.sql.Date and java.sql.Timestamp
if (param instanceof java.util.Date) return new java.sql.Date(((java.util.Date) param).getTime());
if (param instanceof TimestampType) return ((TimestampType) param).asExactJavaSqlDate();
// If a string is given for a date, use java's JDBC parsing.
if (pclass == String.class) {
try {
return new java.sql.Date(TimestampType.millisFromJDBCformat((String) param));
}
catch (IllegalArgumentException e) {
// Defer errors to the generic Exception throw below, if it's not the right format
}
}
}
else if (slot == java.util.Date.class) {
if (param instanceof java.util.Date) return param; // covers java.sql.Date and java.sql.Timestamp
if (param instanceof TimestampType) return ((TimestampType) param).asExactJavaDate();
// If a string is given for a date, use the default format parser for the default locale.
if (pclass == String.class) {
try {
return new java.util.Date(TimestampType.millisFromJDBCformat((String) param));
}
catch (IllegalArgumentException e) {
// Defer errors to the generic Exception throw below, if it's not the right format
}
}
}
else if (slot == BigDecimal.class) {
if ((pclass == Long.class) || (pclass == Integer.class) ||
(pclass == Short.class) || (pclass == Byte.class)) {
BigInteger bi = new BigInteger(param.toString());
Expand All @@ -707,14 +748,14 @@ else if (Array.getLength(param) == 0) {
return bd;
}
}
if (slot == VoltTable.class && pclass == VoltTable.class) {
else if (slot == VoltTable.class && pclass == VoltTable.class) {
return param;
}

// handle truncation for integers

// Long targeting int parameter
if ((slot == int.class) && (pclass == Long.class)) {
else if ((slot == int.class) && (pclass == Long.class)) {
long val = ((Number) param).longValue();

// if it's in the right range, and not null (target null), crop the value and return
Expand All @@ -723,7 +764,7 @@ else if (Array.getLength(param) == 0) {
}

// Long or Integer targeting short parameter
if ((slot == short.class) && (pclass == Long.class || pclass == Integer.class)) {
else if ((slot == short.class) && (pclass == Long.class || pclass == Integer.class)) {
long val = ((Number) param).longValue();

// if it's in the right range, and not null (target null), crop the value and return
Expand All @@ -732,7 +773,7 @@ else if (Array.getLength(param) == 0) {
}

// Long, Integer or Short targeting byte parameter
if ((slot == byte.class) && (pclass == Long.class || pclass == Integer.class || pclass == Short.class)) {
else if ((slot == byte.class) && (pclass == Long.class || pclass == Integer.class || pclass == Short.class)) {
long val = ((Number) param).longValue();

// if it's in the right range, and not null (target null), crop the value and return
Expand All @@ -741,8 +782,8 @@ else if (Array.getLength(param) == 0) {
}

throw new Exception(
"tryToMakeCompatible: Unable to match parameters or out of range for taget param: "
+ slot.getName() + " to provided " + pclass.getName());
"tryToMakeCompatible: The provided value: (" + param.toString() + ") of type: " + pclass.getName() +
"is not a match or is out of range for the target parameter type: " + slot.getName());
}

/**
Expand Down
16 changes: 9 additions & 7 deletions src/frontend/org/voltdb/VoltTable.java
Expand Up @@ -702,16 +702,18 @@ public final void addRow(Object... values) {
}

case TIMESTAMP: {
// Accept long and TimestampType
if (value instanceof TimestampType) {
m_buffer.putLong(((TimestampType)value).getTime());
}
else if (value instanceof BigDecimal) {
if (value instanceof BigDecimal) {
throw new ClassCastException();
}
else {
m_buffer.putLong(((Number) value).longValue());
long micros;
// Accept long and TimestampType and any kind of Date
if (value instanceof java.util.Date ||
value instanceof TimestampType) {
micros = ParameterSet.timestampToMicroseconds(value);
} else {
micros = ((Number) value).longValue();
}
m_buffer.putLong(micros);
break;
}

Expand Down
38 changes: 37 additions & 1 deletion src/frontend/org/voltdb/VoltTableRow.java
Expand Up @@ -570,7 +570,7 @@ public final TimestampType getTimestampAsTimestamp(int columnIndex) {
}

/**
* Retrieve the {@link java.util.Date Date} value stored in the column
* Retrieve the {@link org.voltdb.types.TimestampType TimestampType} value stored in the column
* specified by name. Note that VoltDB uses GMT universally within its
* process space. Date objects sent over the wire from clients may seem
* to be different times because of this, but it is just a time zone offset.
Expand All @@ -588,6 +588,41 @@ public final TimestampType getTimestampAsTimestamp(String columnName) {
}

/**
* Retrieve the <tt>java.sql.Timestamp</tt> equivalent to the value stored in the column specified by index.
* Note that VoltDB uses GMT universally within its process space. Date objects sent over
* the wire from clients may seem to be different times because of this, but it is just
* a time zone offset. VoltDB Timestamps are stored as long integer microseconds from epoch.
* The resulting value is accurate to no finer than microsecond granularity.
* @param columnIndex Index of the column
* @return the <tt>java.sql.Timestamp</tt> equivalent to the value stored in the specified column
*/
public final java.sql.Timestamp getTimestampAsSqlTimestamp(int columnIndex) {
final long timestamp = getTimestampAsLong(columnIndex);
if (m_wasNull) return null;
java.sql.Timestamp result = new java.sql.Timestamp(timestamp/1000);
// 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.
result.setNanos(((int) (timestamp % 1000000))*1000);
return result;
}

/**
* Retrieve the <tt>java.sql.Timestamp</tt> equivalent to the value stored in the column specified by name.
* Note that VoltDB uses GMT universally within its process space. Date objects sent over
* the wire from clients may seem to be different times because of this, but it is just
* a time zone offset. VoltDB Timestamps are stored as long integer microseconds from epoch.
* The resulting value is accurate to no finer than microsecond granularity.
* Avoid retrieving via this method as it is slower than specifying the
* column by index. Use {@link #getTimestampAsSqlTimestamp(int)} instead.
* @param columnName name of the column
* @return the <tt>java.sql.Timestamp</tt> equivalent to the value stored in the specified column
*/
public java.sql.Timestamp getTimestampAsSqlTimestamp(String columnName) {
final int colIndex = getColumnIndex(columnName);
return getTimestampAsSqlTimestamp(colIndex);
}

/*
* Retrieve the BigDecimal value stored in the column
* specified by the index. All DECIMAL types have a fixed
* scale when represented as BigDecimals.
Expand Down Expand Up @@ -732,4 +767,5 @@ final String readString(int position, String encoding) {
}
return retval;
}

}
3 changes: 2 additions & 1 deletion src/frontend/org/voltdb/VoltType.java
Expand Up @@ -92,7 +92,8 @@ public enum VoltType {
* 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}, 'p'),
TIMESTAMP ((byte)11, 8, "timestamp",
new Class[] {TimestampType.class, java.util.Date.class, java.sql.Date.class, java.sql.Timestamp.class}, 'p'),

/**
* UTF-8 string with up to 32K chars.
Expand Down
10 changes: 9 additions & 1 deletion src/frontend/org/voltdb/compiler/ProcedureCompiler.java
Expand Up @@ -33,6 +33,7 @@
import org.voltdb.VoltProcedure;
import org.voltdb.VoltTable;
import org.voltdb.VoltType;
import org.voltdb.VoltTypeException;
import org.voltdb.catalog.Catalog;
import org.voltdb.catalog.CatalogMap;
import org.voltdb.catalog.Column;
Expand Down Expand Up @@ -395,12 +396,19 @@ else if (catalogStmt.getIsorderdeterministic() == false) {
try {
type = VoltType.typeFromClass(cls);
}
catch (RuntimeException e) {
catch (VoltTypeException e) {
// handle the case where the type is invalid
String msg = "Procedure: " + shortName + " has a parameter with invalid type: ";
msg += cls.getSimpleName();
throw compiler.new VoltCompilerException(msg);
}
catch (RuntimeException e) {
String msg = "Procedure: " + shortName + " unexpectedly failed a check on a parameter of type: ";
msg += cls.getSimpleName();
msg += " with error: ";
msg += e.toString();
throw compiler.new VoltCompilerException(msg);
}

param.setType(type.getValue());
}
Expand Down
12 changes: 0 additions & 12 deletions src/frontend/org/voltdb/messaging/FastDeserializer.java
Expand Up @@ -139,18 +139,6 @@ public FastSerializable readObject(final FastSerializable obj, final Deserializa
return obj;
}

/**
* Read a timestamp from the stream. Timestamps are stored as a long
* value representing microseconds since the epoch.
* @return The {@link org.voltdb.types.TimestampType TimestampType}
* object read from the stream.
* @throws IOException Rethrows any IOExceptions.
*/
public TimestampType readTimestamp() throws IOException {
final long val = readLong();
return new TimestampType(val);
}

/**
* Read a string in the standard VoltDB way without
* wrapping the byte buffer[
Expand Down
14 changes: 0 additions & 14 deletions src/frontend/org/voltdb/messaging/FastSerializer.java
Expand Up @@ -26,7 +26,6 @@

import org.voltcore.utils.DBBPool;
import org.voltcore.utils.DBBPool.BBContainer;

import org.voltdb.VoltTable;
import org.voltdb.VoltType;
import org.voltdb.types.TimestampType;
Expand Down Expand Up @@ -223,19 +222,6 @@ public void writeObject(FastSerializable obj) throws IOException {
obj.writeExternal(this);
}

/**
* Write a timestamp to a FastSerializer. Store the value as a long
* representing microseconds from the epoch.
*
* @param timestamp The {@link org.voltdb.types.TimestampType TimestampType} to serialize.
* @throws IOException Rethrows any IOExceptions thrown.
*/
public void writeTimestamp(TimestampType timestamp) throws IOException {
assert timestamp != null;
long val = timestamp.getTime();
writeLong(val);
}

/**
* Write a string in the standard VoltDB way without
* wrapping the byte buffer.
Expand Down

0 comments on commit 6e104df

Please sign in to comment.