org.voltdb.VoltTableRow Maven / Gradle / Ivy
Show all versions of voltdbclient Show documentation
/* 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 .
*/
package org.voltdb;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import org.json_voltpatches.JSONException;
import org.json_voltpatches.JSONStringer;
import org.voltdb.types.GeographyPointValue;
import org.voltdb.types.GeographyValue;
import org.voltdb.types.TimestampType;
import org.voltdb.types.VoltDecimalHelper;
import org.voltdb.utils.Encoder;
import org.voltdb.utils.VoltTypeUtil;
/**
* Represents the interface to a row in a VoltTable result set.
*
* Accessing Row Fields
*
* Row fields are acessed via methods of the form
* get
<Type>(
col_index|
col_name)
.
* Note: it is more performant to access rows by column index than by column name.
*
* Advancing and Iterating through a Table
*
* VoltTableRow represents both a row in a table and an iterator for all the
* rows in the table. For a given table, each VoltTableRow instance has a position
* value which represents the index represented in the table. To increment the
* position, call {@link #advanceRow()}. To reset the position, call
* {@link #resetRowPosition()}, which moves the position before the first row.
* Note that before you can access fields after a call to resetRowPosition,
* advanceRow must be called to move to the first row.
*
* Example
*
*
* VoltTableRow row = table.fetchRow(5);
* System.out.println(row.getString("foo");
* row.resetRowPosition();
* while (row.advanceRow()) {
* System.out.println(row.getLong(7));
* }
*
*/
public abstract class VoltTableRow {
/**
* Size in bytes of the maximum length for a VoltDB tuple.
* This is inclusive of the 4-byte row length prefix.
*
* 2 megs to allow a max size string/varbinary + length prefix + some other stuff
*/
public static final int MAX_TUPLE_LENGTH = 2097152;
/**
* String representation of MAX_TUPLE_LENGTH
.
*/
public static final String MAX_TUPLE_LENGTH_STR = String.valueOf(MAX_TUPLE_LENGTH / 1024) + "k";
static final int ROW_HEADER_SIZE = Integer.SIZE/8;
static final int ROW_COUNT_SIZE = Integer.SIZE/8;
static final int STRING_LEN_SIZE = Integer.SIZE/8;
static final int VARBINARY_LEN_SIZE = Integer.SIZE/8;
static final int INVALID_ROW_INDEX = -1;
/** Stores the row data (and possibly much more) */
ByteBuffer m_buffer;
/** Was the last value retrieved null? */
boolean m_wasNull = false;
/** Where in the buffer is the start of the active row's data */
int m_position = -1;
/** Offsets of each column in the buffer */
int[] m_offsets;
/** Have the offsets been calculated */
boolean m_hasCalculatedOffsets = false;
int m_activeRowIndex = INVALID_ROW_INDEX;
VoltTableRow() {}
/**
* Return the {@link VoltType type} of the column with the specified index.
* @param columnIndex Index of the column
* @return {@link VoltType VoltType} of the column
*/
abstract public VoltType getColumnType(int columnIndex);
/**
* Return the index of the column with the specified column name.
* @param columnName Name of the column
* @return Index of the column
*/
abstract public int getColumnIndex(String columnName);
/**
* Returns the number of columns in the table schema
* @return Number of columns in the table schema
*/
abstract public int getColumnCount();
/**
* Returns the number of rows.
* @return Number of rows in the table
*/
abstract int getRowCount();
abstract int getRowStart();
/**
* Provide a way to efficiently compare two schemas belonging to a table or row.
* @return A byte string uniquely identifying the schema of the table.
*/
abstract byte[] getSchemaString();
/**
* Clone a row. The new instance returned will have an independent
* position from the original instance.
* @return A deep copy of the current VoltTableRow instance.
*/
public abstract VoltTableRow cloneRow();
private final void ensureCalculatedOffsets() {
if (m_hasCalculatedOffsets == true)
return;
m_offsets[0] = m_position;
for (int i = 1; i < getColumnCount(); i++) {
final VoltType type = getColumnType(i - 1);
// handle variable length types specially
if (type.isVariableLength()) {
final int len = m_buffer.getInt(m_offsets[i - 1]);
if (len == VoltTable.NULL_STRING_INDICATOR) {
m_offsets[i] = m_offsets[i - 1] + STRING_LEN_SIZE;
}
else if (len < 0) {
throw new RuntimeException("Invalid object length for column: " + i);
}
else {
m_offsets[i] = m_offsets[i - 1] + len + STRING_LEN_SIZE;
}
}
else {
m_offsets[i] = m_offsets[i - 1] + type.getLengthInBytesForFixedTypes();
}
}
m_hasCalculatedOffsets = true;
}
public final int getOffset(int index) {
ensureCalculatedOffsets();
assert(index >= 0);
assert(index < m_offsets.length);
return m_offsets[index];
}
/**
* Sets the active position indicator so that the next call
* to {@link #advanceRow()} will make the first record active.
* This never needs to be called if the table is only going to
* be scanned once. After this call {@link #getActiveRowIndex()}
* will return -1 until {@link #advanceRow()} is called.
*/
public void resetRowPosition() {
m_activeRowIndex = INVALID_ROW_INDEX;
}
/**
* Get the position in the table of this row instance, starting
* at zero for the first row.
* @return The index of the active row or -1 if none.
*/
public int getActiveRowIndex() {
return m_activeRowIndex;
}
/**
* Makes the next row active so calls to getXXX() will return
* values from the current record. At initialization time, the
* active row index is -1, which is invalid. If advanced past the
* end of the resultset, {@link #resetRowPosition()} must be
* called to re-iterate through the rows.
* @return True if a valid row became active. False otherwise.
*/
public boolean advanceRow() {
return advanceToRow(m_activeRowIndex + 1);
}
/**
* Advance to a specific row so calls to getXXX() will return values from
* the current record. At initialization time, the active row index is -1,
* which is invalid. If advanced past the end of the resultset,
* {@link #resetRowPosition()} must be called to re-iterate through the
* rows.
*
* @param rowIndex The row to jump to.
* @return True if a valid row became active. False otherwise.
*/
public boolean advanceToRow(int rowIndex) {
int rows_to_move = rowIndex - m_activeRowIndex;
m_activeRowIndex = rowIndex;
if (m_activeRowIndex >= getRowCount()) {
return false;
}
if (rows_to_move < 0) {// this is "advance" to row, don't move backwards
return false;
}
m_hasCalculatedOffsets = false;
if (m_offsets == null) {
m_offsets = new int[getColumnCount()];
}
if (m_activeRowIndex == 0) {
m_position = getRowStart() + ROW_COUNT_SIZE + ROW_HEADER_SIZE;
}
else {
// Move n rows - this code assumes rows can be variable size, so we
// have to fetch the size of each row in order to advance to the
// next row
if (rows_to_move > 0 && m_position < 0)
throw new RuntimeException(
"VoltTableRow is in an invalid state. Consider calling advanceRow().");
for (int i = 0; i < rows_to_move; i++) {
int rowlength = m_buffer.getInt(m_position - ROW_HEADER_SIZE);
if (rowlength <= 0) {
throw new RuntimeException("Invalid row length.");
}
m_position += (rowlength + ROW_HEADER_SIZE);
}
if (m_position >= m_buffer.limit()) {
throw new RuntimeException("Row length exceeds table boundary.");
}
}
return true;
}
/**
* Retrieve a value from the row by specifying the column index and the {@link VoltType type}.
* This method is slower then linking directly against the type specific getter. Prefer the
* type specific getter methods where viable. Looking at the return value is not a reliable
* way to check if the value is null. Use {@link #wasNull()} instead.
* @param columnIndex Index of the column
* @param type {@link VoltType} of the value
* @return The value or null if the value is not set.
* @see #wasNull()
*/
public final Object get(int columnIndex, VoltType type) {
Object ret = null;
switch (type) {
case TINYINT:
ret = new Byte((byte)getLong(columnIndex));
break;
case SMALLINT:
ret = new Short((short)getLong(columnIndex));
break;
case INTEGER:
ret = new Integer((int)getLong(columnIndex));
break;
case BIGINT:
ret = getLong(columnIndex);
break;
case FLOAT:
ret = getDouble(columnIndex);
break;
case STRING:
ret = getString(columnIndex);
break;
case VARBINARY:
ret = getVarbinary(columnIndex);
break;
case TIMESTAMP:
ret = getTimestampAsTimestamp(columnIndex);
break;
case DECIMAL:
ret = getDecimalAsBigDecimal(columnIndex);
break;
case GEOGRAPHY_POINT:
ret = getGeographyPointValue(columnIndex);
break;
case GEOGRAPHY:
ret = getGeographyValue(columnIndex);
break;
default:
throw new IllegalArgumentException("Invalid type '" + type + "'");
}
return ret;
}
/**
* Retrieve a value from the row by specifying the column name and the
* {@link VoltType type}. This method is slower then linking directly
* against the type specific getter. Prefer the type specific getter methods
* where viable. Looking at the return value is not a reliable way to check
* if the value is null. Use {@link #wasNull()} instead.
*
* @param columnName
* Name of the column
* @param type
* {@link VoltType} of the value
* @return The value or null if the value is not set.
* @see #wasNull()
*/
public final Object get(String columnName, VoltType type) {
final int colIndex = getColumnIndex(columnName);
return get(colIndex, type);
}
/**
* Retrieve the long value stored in the column specified by index.
* Looking at the return value is not a reliable way to check if the value
* is null. Use {@link #wasNull()} instead.
*
* @param columnIndex
* Index of the column
* @return long value stored in the specified column
* @see #wasNull()
*/
public final long getLong(int columnIndex) {
if (columnIndex < 0) {
throw new IndexOutOfBoundsException("Column index " + columnIndex + " is negative");
}
if (columnIndex >= getColumnCount()) {
throw new IndexOutOfBoundsException("Column index " + columnIndex +
" is beyond number of columns " + getColumnCount());
}
final VoltType type = getColumnType(columnIndex);
if (m_activeRowIndex == INVALID_ROW_INDEX)
throw new RuntimeException("VoltTableRow.advanceRow() must be called to advance to the first row before any access.");
switch (type) {
case TINYINT:
final byte value1 = m_buffer.get(getOffset(columnIndex));
m_wasNull = (value1 == VoltType.NULL_TINYINT);
return value1;
case SMALLINT:
final short value2 = m_buffer.getShort(getOffset(columnIndex));
m_wasNull = (value2 == VoltType.NULL_SMALLINT);
return value2;
case INTEGER:
final int value3 = m_buffer.getInt(getOffset(columnIndex));
m_wasNull = (value3 == VoltType.NULL_INTEGER);
return value3;
case BIGINT:
final long value4 = m_buffer.getLong(getOffset(columnIndex));
m_wasNull = (value4 == VoltType.NULL_BIGINT);
return value4;
default:
throw new IllegalArgumentException("getLong() called on non-integral column.");
}
}
/**
* Retrieve the long value stored in the column
* specified by name. Avoid retrieving via this method as it is slower than specifying the
* column by index. Use {@link #getLong(int)} instead.
* Looking at the return value is not a reliable way to check if the value
* is null. Use {@link #wasNull()} instead.
* @param columnName Name of the column
* @return long value stored in the specified column
* @see #wasNull()
* @see #getLong(int)
*/
public final long getLong(String columnName) {
final int colIndex = getColumnIndex(columnName);
return getLong(colIndex);
}
/**
* Returns whether last retrieved value was null.
* Some special values that are NOT Java's NULL represents null the SQL notion of null
* in our system.
* @return true if the value was null, false otherwise.
*/
public final boolean wasNull() {
return m_wasNull;
}
/**
* @return A slice of the underlying buffer containing the raw data of a row
* in a format that can be blindly copied into a VoltDB with sufficient space
* and the exact same schema.
*/
final ByteBuffer getRawRow() {
int rowSize = m_buffer.getInt(m_position - ROW_HEADER_SIZE);
int pos = m_buffer.position();
m_buffer.position(m_position - ROW_HEADER_SIZE);
ByteBuffer retval = m_buffer.slice();
m_buffer.position(pos);
retval.limit(ROW_HEADER_SIZE + rowSize);
return retval;
}
/**
* A way to get a column value in raw byte form without doing any
* expensive conversions, like date processing or string encoding.
*
* Next optimization is to do this without an allocation (maybe).
*
* @param columnIndex Index of the column
* @return A byte string containing the raw byte value.
*/
final byte[] getRaw(int columnIndex) {
byte[] retval;
int pos = m_buffer.position();
int offset = getOffset(columnIndex);
VoltType type = getColumnType(columnIndex);
switch(type) {
case TINYINT:
case SMALLINT:
case INTEGER:
case BIGINT:
case TIMESTAMP:
case FLOAT:
case DECIMAL:
case GEOGRAPHY_POINT: {
// all of these types are fixed length, so easy to get raw type
int length = type.getLengthInBytesForFixedTypesWithoutCheck();
retval = new byte[length];
m_buffer.position(offset);
m_buffer.get(retval);
m_buffer.position(pos);
return retval;
}
case STRING:
case VARBINARY:
case GEOGRAPHY: {
// all of these types are variable length with a prefix
int length = m_buffer.getInt(offset);
if (length == VoltTable.NULL_STRING_INDICATOR) {
length = 0;
}
length += 4;
retval = new byte[length];
m_buffer.position(offset);
m_buffer.get(retval);
m_buffer.position(pos);
return retval;
}
default:
throw new RuntimeException("Unknown type");
}
}
/**
* Retrieve the double value stored in the column specified by index.
* Looking at the return value is not a reliable way to check if the value
* is null. Use {@link #wasNull()} instead.
* @param columnIndex Index of the column
* @return double value stored in the specified column
* @see #wasNull()
*/
public final double getDouble(int columnIndex) {
validateColumnType(columnIndex, VoltType.FLOAT);
final double value = m_buffer.getDouble(getOffset(columnIndex));
m_wasNull = (value <= VoltType.NULL_FLOAT); // see value.h
return value;
}
/**
* Retrieve the double value stored in the column
* specified by name. Avoid retrieving via this method as it is slower than specifying the
* column by index. Use {@link #getDouble(int)} instead.
* Looking at the return value is not a reliable way to check if the value
* is null. Use {@link #wasNull()} instead.
* @param columnName Name of the column
* @return double value stored in the specified column
* @see #wasNull()
* @see #getDouble(int)
*/
public final double getDouble(String columnName) {
final int colIndex = getColumnIndex(columnName);
return getDouble(colIndex);
}
/**
* Retrieve the {@link java.lang.String String} value stored in the column specified by index.
* Looking at the return value is not a reliable way to check if the value
* is null. Use {@link #wasNull()} instead. If at all possible you should use {@link #getStringAsBytes(int)} instead
* and avoid the overhead of constructing the {@link java.lang.String String} object.
* @param columnIndex Index of the column
* @return {@link java.lang.String String} value stored in the specified column
* @see #wasNull()
*/
public final String getString(int columnIndex) {
validateColumnType(columnIndex, VoltType.STRING);
String retval = readString(getOffset(columnIndex), VoltTable.ROWDATA_ENCODING);
m_wasNull = (retval == null);
return retval;
}
/**
* Retrieve the {@link java.lang.String String} value stored in the column
* specified by name. Avoid retrieving via this method as it is slower than specifying the
* column by index. Use {@link #getString(int)} instead.
* Looking at the return value is not a reliable way to check if the value
* is null. Use {@link #wasNull()} instead. If at all possible you should use {@link #getStringAsBytes(int)} instead
* and avoid the overhead of constructing the {@link java.lang.String String} object.
* @param columnName Name of the column
* @return {@link java.lang.String String} value stored in the specified column
* @see #wasNull()
* @see #getString(int)
*/
public final String getString(String columnName) {
final int colIndex = getColumnIndex(columnName);
return getString(colIndex);
}
/**
* Retrieve the string value stored in the column specified by index as
* an array of bytes. Assume UTF-8 encoding for all string values in VoltDB.
* Looking at the return value is not a reliable way to check if the value
* is null. Use {@link #wasNull()} instead.
* @param columnIndex Index of the column
* @return string value stored in the specified column as a byte[]
* @see #wasNull()
*/
public final byte[] getStringAsBytes(int columnIndex) {
validateColumnType(columnIndex, VoltType.STRING);
int pos = m_buffer.position();
m_buffer.position(getOffset(columnIndex));
int len = m_buffer.getInt();
if (len == VoltTable.NULL_STRING_INDICATOR) {
m_wasNull = true;
m_buffer.position(pos);
return null;
}
m_wasNull = false;
byte[] data = new byte[len];
m_buffer.get(data);
m_buffer.position(pos);
return data;
}
/**
* Retrieve the string value stored in the column
* specified by name as an array of bytes. Assume UTF-8 encoding for all
* string values in VoltDB.Avoid retrieving via this method as it is slower
* than specifying the column by index. Use {@link #getStringAsBytes(int)} instead.
* Looking at the return value is not a reliable way to check if the value
* is null. Use {@link #wasNull()} instead.
* @param columnName Name of the column
* @return string value stored in the specified column as a byte[]
* @see #wasNull()
* @see #getStringAsBytes(int)
*/
public final byte[] getStringAsBytes(String columnName) {
final int colIndex = getColumnIndex(columnName);
return getStringAsBytes(colIndex);
}
/**
* Retrieve the varbinary value stored in the column specified by index.
* Looking at the return value is not a reliable way to check if the value
* is null. Use {@link #wasNull()} instead.
* @param columnIndex Index of the column
* @return Varbinary value stored in the specified column
* @see #wasNull()
*/
public final byte[] getVarbinary(int columnIndex) {
validateColumnType(columnIndex, VoltType.VARBINARY);
int pos = m_buffer.position();
m_buffer.position(getOffset(columnIndex));
// Sanity check the varbinary size int position.
if (VARBINARY_LEN_SIZE > m_buffer.remaining()) {
throw new RuntimeException(String.format(
"VoltTableRow::getVarbinary: Can't read varbinary size as %d byte integer " +
"from buffer with %d bytes remaining.",
VARBINARY_LEN_SIZE, m_buffer.remaining()));
}
int len = m_buffer.getInt();
if (len == VoltTable.NULL_STRING_INDICATOR) {
m_wasNull = true;
m_buffer.position(pos);
return null;
}
// Sanity check the size against the remaining buffer size.
if (len > m_buffer.remaining()) {
throw new RuntimeException(String.format(
"VoltTableRow::getVarbinary: Can't read %d byte varbinary " +
"from buffer with %d bytes remaining.", len, m_buffer.remaining()));
}
m_wasNull = false;
byte[] data = new byte[len];
m_buffer.get(data);
m_buffer.position(pos);
return data;
}
/**
* Retrieve the varbinary value stored in the column
* specified by name. Avoid retrieving via this method as it is slower than specifying the
* column by index. Use {@link #getVarbinary(int)} instead.
* Looking at the return value is not a reliable way to check if the value
* is null. Use {@link #wasNull()} instead.
* @param columnName Name of the column
* @return Varbinary value stored in the specified column
* @see #wasNull()
* @see #getVarbinary(int)
*/
public final byte[] getVarbinary(String columnName) {
final int colIndex = getColumnIndex(columnName);
return getVarbinary(colIndex);
}
/**
* Retrieve the GeographyPointValue value stored in the column specified by index.
* Looking at the return value is not a reliable way to check if the value is
* null. Use {@link #wasNull()} instead.
* @param columnIndex Index of the column
* @return GeographyPointValue value stored in the specified column
* @see #wasNull()
*/
public final GeographyPointValue getGeographyPointValue(int columnIndex) {
validateColumnType(columnIndex, VoltType.GEOGRAPHY_POINT);
GeographyPointValue pt = GeographyPointValue.unflattenFromBuffer(m_buffer, getOffset(columnIndex));
m_wasNull = (pt == null);
return pt;
}
/**
* Retrieve the GeographyPointValue value stored in the column specified by name.
* Avoid retrieving via this method as it is slower than specifying the column
* by index. Use {@link #getGeographyPointValue(int)} instead.
* Looking at the return value is not a reliable way to check if the value
* is null. Use {@link #wasNull()} instead.
* @param columnName Name of the column
* @return GeographyPointValue value stored in the specified column
* @see #wasNull()
* @see #getGeographyPointValue(int)
*/
public final GeographyPointValue getGeographyPointValue(String columnName) {
final int colIndex = getColumnIndex(columnName);
return getGeographyPointValue(colIndex);
}
/**
* Retrieve the GeographyValue value stored in the column specified by index.
* Looking at the return value is not a reliable way to check if the value is
* null. Use {@link #wasNull()} instead.
* @param columnIndex Index of the column
* @return GeographyValue value stored in the specified column
* @see #wasNull()
*/
public final GeographyValue getGeographyValue(int columnIndex) {
validateColumnType(columnIndex, VoltType.GEOGRAPHY);
int offset = getOffset(columnIndex);
int len = m_buffer.getInt(offset);
if (len == VoltTable.NULL_STRING_INDICATOR) {
m_wasNull = true;
return null;
}
m_wasNull = false;
offset += 4;
GeographyValue gv = GeographyValue.unflattenFromBuffer(m_buffer, offset);
return gv;
}
/**
* Retrieve the GeographyValue value stored in the column specified by name.
* Avoid retrieving via this method as it is slower than specifying the column
* by index. Use {@link #getGeographyValue(int)} instead.
* Looking at the return value is not a reliable way to check if the value
* is null. Use {@link #wasNull()} instead.
* @param columnName Name of the column
* @return GeographyValue value stored in the specified column
* @see #wasNull()
* @see #getGeographyPointValue(int)
*/
public final GeographyValue getGeographyValue(String columnName) {
final int colIndex = getColumnIndex(columnName);
return getGeographyValue(colIndex);
}
/**
* Retrieve the long timestamp 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. Timestamps represent microseconds from epoch.
* @param columnIndex Index of the column
* @return long timestamp value stored in the specified column
* @see #wasNull()
* @see #getTimestampAsTimestamp(int)
*/
public final long getTimestampAsLong(int columnIndex) {
validateColumnType(columnIndex, VoltType.TIMESTAMP);
final long value = m_buffer.getLong(getOffset(columnIndex));
m_wasNull = (value == Long.MIN_VALUE); // see value.h
return value;
}
/**
* Retrieve the long timestamp 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.
* Avoid retrieving via this method as it is slower than specifying the
* column by index. Use {@link #getTimestampAsLong(int)} instead.
* @param columnName Name of the column
* @return long timestamp value stored in the specified column
* @see #wasNull()
* @see #getTimestampAsLong(int)
* @see #getTimestampAsTimestamp(String)
*/
public final long getTimestampAsLong(String columnName) {
final int colIndex = getColumnIndex(columnName);
return getTimestampAsLong(colIndex);
}
/**
* Retrieve the {@link org.voltdb.types.TimestampType TimestampType}
* 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.
* @param columnIndex Index of the column
* @return {@link org.voltdb.types.TimestampType TimestampType}
* value stored in the specified column
* @see #wasNull()
*/
public final TimestampType getTimestampAsTimestamp(int columnIndex) {
final long timestamp = getTimestampAsLong(columnIndex);
if (m_wasNull) return null;
return new TimestampType(timestamp);
}
/**
* 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.
* Avoid retrieving via this method as it is slower than specifying the
* column by index. Use {@link #getTimestampAsTimestamp(int)} instead.
* @param columnName Name of the column
* @return {@link org.voltdb.types.TimestampType TimestampType}
* value stored in the specified column
* @see #wasNull()
* @see #getTimestampAsTimestamp(int)
*/
public final TimestampType getTimestampAsTimestamp(String columnName) {
final int colIndex = getColumnIndex(columnName);
return getTimestampAsTimestamp(colIndex);
}
/**
* Retrieve the java.sql.Timestamp 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 java.sql.Timestamp 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;
return VoltTypeUtil.getSqlTimestampFromMicrosSinceEpoch(timestamp);
}
/**
* Retrieve the java.sql.Timestamp 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 java.sql.Timestamp 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 {@link BigDecimal} value stored in the column
* specified by the index. All DECIMAL types have a fixed
* scale when represented as BigDecimals.
*
* @param columnIndex Index of the column
* @return BigDecimal representation.
* @see #wasNull()
*/
public final BigDecimal getDecimalAsBigDecimal(int columnIndex) {
validateColumnType(columnIndex, VoltType.DECIMAL);
final int position = m_buffer.position();
m_buffer.position(getOffset(columnIndex));
final BigDecimal bd = VoltDecimalHelper.deserializeBigDecimal(m_buffer);
m_buffer.position(position);
m_wasNull = bd == null ? true : false;
return bd;
}
/**
* Retrieve the {@link BigDecimal} value stored in the column
* specified by columnName. All DECIMAL types have a
* fixed scale when represented as BigDecimals.
* @param columnName Name of the column
* @return BigDecimal representation.
* @see #wasNull()
* @see #getDecimalAsBigDecimal(int)
*/
public BigDecimal getDecimalAsBigDecimal(String columnName) {
int colIndex = getColumnIndex(columnName);
return getDecimalAsBigDecimal(colIndex);
}
static final String GEOJSON_TYPE_KEY = "type";
static final String GEOJSON_COORDS_KEY = "coordinates";
// This is not "GeographyPoint". This is used in geojson syntax.
static final String GEOJSON_POINT_TYPE_SIGIL = "Point";
static final String GEOJSON_POLYGON_TYPE_SIGIL = "Polygon";
/**
*
* @param columnIndex
* @param js
* @throws JSONException
*/
void putJSONRep(int columnIndex, JSONStringer js) throws JSONException {
long value; double dvalue;
VoltType columnType = getColumnType(columnIndex);
switch (columnType) {
case TINYINT:
value = getLong(columnIndex);
if (wasNull()) {
js.valueNull();
}
else {
js.value(value);
}
break;
case SMALLINT:
value = getLong(columnIndex);
if (wasNull()) {
js.valueNull();
}
else {
js.value(value);
}
break;
case INTEGER:
value = getLong(columnIndex);
if (wasNull()) {
js.valueNull();
}
else {
js.value(value);
}
break;
case BIGINT:
value = getLong(columnIndex);
if (wasNull()) {
js.valueNull();
}
else {
js.value(value);
}
break;
case TIMESTAMP:
value = getTimestampAsLong(columnIndex);
if (wasNull()) {
js.valueNull();
}
else {
js.value(value);
}
break;
case FLOAT:
dvalue = getDouble(columnIndex);
if (wasNull()) {
js.valueNull();
}
else if (Double.isNaN(dvalue)) {
js.value(Double.toString(dvalue));
}
else if (Double.isInfinite(dvalue)) {
js.value(Double.toString(dvalue));
}
else {
js.value(dvalue);
}
break;
case STRING:
js.value(getString(columnIndex));
break;
case VARBINARY:
byte[] bin = getVarbinary(columnIndex);
js.value(Encoder.hexEncode(bin));
break;
case DECIMAL:
Object dec = getDecimalAsBigDecimal(columnIndex);
if (wasNull()) {
js.valueNull();
}
else {
js.value(dec.toString());
}
break;
case GEOGRAPHY_POINT:
GeographyPointValue pt = getGeographyPointValue(columnIndex);
if (wasNull()) {
js.valueNull();
}
else {
js.value(pt.toString());
}
break;
case GEOGRAPHY:
GeographyValue gv = getGeographyValue(columnIndex);
if (wasNull()) {
js.valueNull();
}
else {
js.value(gv.toString());
}
break;
// VoltType includes a few values that aren't valid column value types
case INVALID:
break;
case NULL:
break;
case NUMERIC:
break;
case BOOLEAN:
break;
case VOLTTABLE:
break;
}
}
/**
* This converts a GeographyValue to GEOJSON. We currently don't use it
* because the VMC does not know what to do with GEOJSON. But it's left here
* in the hope that it will be useful in the future.
*
* @param gv
* @param js
* @throws JSONException
@SuppressWarnings("unused")
static private void geographyValueToJSON(GeographyValue gv, JSONStringer js) throws JSONException {
js.object()
.key(GEOJSON_TYPE_KEY)
.value(GEOJSON_POLYGON_TYPE_SIGIL)
.key(GEOJSON_COORDS_KEY)
.array();
for (List loops : gv.getLoops()) {
js.array();
for (GeographyPointValue pt : loops) {
js.array();
js.value(pt.getLatitude())
.value(pt.getLongitude());
js.endArray();
}
js.endArray();
}
js.endArray();
}
*/
/**
* This converts a GeographyValue to GEOJSON. We currently don't use it
* because the VMC does not know what to do with GEOJSON. But it's left here
* in the hope that it will be useful in the future.
*
* @param gv
* @param js
* @throws JSONException
@SuppressWarnings("unused")
static private void pointToJSON(GeographyPointValue pt, JSONStringer js) throws JSONException {
js.object()
.key(GEOJSON_TYPE_KEY)
.value(GEOJSON_POINT_TYPE_SIGIL)
.key(GEOJSON_COORDS_KEY)
.array()
.value(pt.getLatitude())
.value(pt.getLongitude())
.endArray()
.endObject();
}
*/
/** Validates that type and columnIndex match and are valid. */
final void validateColumnType(int columnIndex, VoltType... types) {
if (m_position < 0)
throw new RuntimeException("VoltTableRow is in an invalid state. Consider calling advanceRow().");
if ((columnIndex >= getColumnCount()) || (columnIndex < 0)) {
throw new IndexOutOfBoundsException("Column index " + columnIndex + " is greater than the number of columns");
}
final VoltType columnType = getColumnType(columnIndex);
for (VoltType type : types)
if (columnType == type)
return;
throw new IllegalArgumentException("Column index " + columnIndex + " is type " + columnType);
}
/** Reads a string from a buffer with a specific encoding. */
final String readString(int position, Charset encoding) {
// Sanity check the string size int position. Note that the eventual
// m_buffer.get() does check for underflow, getInt() does not.
if (STRING_LEN_SIZE > m_buffer.limit() - position) {
throw new RuntimeException(String.format(
"VoltTableRow::readString: Can't read string size as %d byte integer " +
"from buffer with %d bytes remaining.",
STRING_LEN_SIZE, m_buffer.limit() - position));
}
final int len = m_buffer.getInt(position);
//System.out.println(len);
// check for null string
if (len == VoltTable.NULL_STRING_INDICATOR)
return null;
if (len < 0) {
throw new RuntimeException("Invalid object length.");
}
// Sanity check the size against the remaining buffer size.
if (position + STRING_LEN_SIZE + len > m_buffer.limit()) {
throw new RuntimeException(String.format(
"VoltTableRow::readString: Can't read %d byte string " +
"from buffer with %d bytes remaining.",
len, m_buffer.limit() - position - STRING_LEN_SIZE));
}
// this is a bit slower than directly getting the array (see below)
// but that caused bugs
byte[] stringData = new byte[len];
int oldPos = m_buffer.position();
m_buffer.position(position + STRING_LEN_SIZE);
m_buffer.get(stringData);
m_buffer.position(oldPos);
return new String(stringData, encoding);
}
}