/**
* Copyright (C) 2009-2013 FoundationDB, LLC
*
* 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.foundationdb.sql.server;
import com.foundationdb.server.error.NoSuchCastException;
import com.foundationdb.server.types.TCast;
import com.foundationdb.server.types.TClass;
import com.foundationdb.server.types.TExecutionContext;
import com.foundationdb.server.types.TInstance;
import com.foundationdb.server.types.FormatOptions;
import com.foundationdb.server.types.common.types.TypesTranslator;
import com.foundationdb.server.types.value.Value;
import com.foundationdb.server.types.value.ValueSource;
import com.foundationdb.server.types.value.ValueSources;
import com.foundationdb.util.AkibanAppender;
import com.foundationdb.util.WrappingByteSource;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URL;
import java.sql.Array;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Date;
import java.sql.NClob;
import java.sql.Ref;
import java.sql.ResultSet;
import java.sql.RowId;
import java.sql.SQLXML;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.Calendar;
import java.util.Collections;
/** Make something like an array of typed values (for instance, a
* <code>Row</code> or a <code>QueryContext</code>) accessible using
* standard Java types, includings ones from the <code>java.sql</code>
* package.
*
* API like <code>ResultSet</code> or <code>PreparedStatement</code>.
*/
public abstract class ServerJavaValues
{
public static final int RETURN_VALUE_INDEX = -1;
protected abstract int size();
protected abstract ServerQueryContext getContext();
protected abstract ValueSource getValue(int index);
protected abstract TInstance getType(int index);
protected abstract void setValue(int index, ValueSource source);
protected abstract ResultSet toResultSet(int index, Object resultSet);
private boolean wasNull;
private CachedCast[] cachedCasts;
protected ValueSource value(int index) {
ValueSource value = getValue(index);
wasNull = value.isNull();
return value;
}
protected TypesTranslator getTypesTranslator() {
return getContext().getTypesTranslator();
}
protected static class CachedCast {
TClass targetClass;
TCast tcast;
TExecutionContext tcontext;
Value target;
protected CachedCast(TInstance sourceInstance, TClass targetClass,
ServerQueryContext context) {
this.targetClass = targetClass;
TInstance targetInstance = targetClass.instance(sourceInstance == null || sourceInstance.nullability());
tcast = context.getServer().typesRegistryService().getCastsResolver()
.cast(sourceInstance, targetInstance);
if (tcast == null)
throw new NoSuchCastException(sourceInstance, targetInstance);
tcontext = new TExecutionContext(Collections.singletonList(sourceInstance),
targetInstance,
context);
target = new Value(targetInstance);
}
protected boolean matches(TClass required) {
return required.equals(targetClass);
}
protected ValueSource apply(ValueSource value) {
tcast.evaluate(tcontext, value, target);
return target;
}
}
/** Cast as necessary to <code>required</code>.
* A cache is maintained for each index with the last class, on
* the assumption the caller will be applying the same
* <code>getXxx</code> / <code>setXxx</code> to the same field each time.
*/
protected ValueSource cachedCast(int index, ValueSource value, int jdbcType) {
return cachedCast(index, value, getType(index), jdbcType);
}
protected ValueSource cachedCast(int index, ValueSource source, TInstance sourceType, int jdbcType) {
if (jdbcType == getTypesTranslator().jdbcType(sourceType))
return source;
return cachedCast(index, source, sourceType,
getTypesTranslator().typeClassForJDBCType(jdbcType));
}
protected ValueSource cachedCast(int index, ValueSource value, TClass required) {
return cachedCast(index, value, getType(index), required);
}
protected ValueSource cachedCast(int index, ValueSource source, TInstance sourceType, TClass required) {
if (required.equals(sourceType.typeClass()))
return source; // Already of the required class.
// Leave room for return value (index does not matter -- only used here).
if (cachedCasts == null)
cachedCasts = new CachedCast[size() + 1];
if (index == RETURN_VALUE_INDEX)
index = cachedCasts.length - 1;
CachedCast cast = cachedCasts[index];
if ((cast == null) || !cast.matches(required)) {
cast = new CachedCast(sourceType, required, getContext());
cachedCasts[index] = cast;
}
return cast.apply(source);
}
protected TInstance jdbcInstance(int jdbcType) {
return getTypesTranslator().typeClassForJDBCType(jdbcType).instance(true);
}
protected void setValue(int index, Object value, TInstance sourceType) {
TInstance targetType = this.getType(index);
if (sourceType == null) {
sourceType = targetType;
}
ValueSource source = ValueSources.valuefromObject(value, sourceType);
if (targetType != null)
source = cachedCast(index, source, sourceType, targetType.typeClass());
setValue(index, source);
}
public boolean wasNull() {
return wasNull;
}
public String getString(int index) {
ValueSource value = value(index);
if (wasNull)
return null;
else
return cachedCast(index, value, Types.VARCHAR).getString();
}
public String getNString(int index) {
throw new UnsupportedOperationException();
}
public boolean getBoolean(int index) {
ValueSource value = value(index);
if (wasNull)
return false;
else
return cachedCast(index, value, Types.BOOLEAN).getBoolean();
}
public byte getByte(int index) {
ValueSource value = value(index);
if (wasNull)
return 0;
else
return (byte)getTypesTranslator()
.getIntegerValue(cachedCast(index, value, Types.TINYINT));
}
public short getShort(int index) {
ValueSource value = value(index);
if (wasNull)
return 0;
else
return (short)getTypesTranslator()
.getIntegerValue(cachedCast(index, value, Types.SMALLINT));
}
public int getInt(int index) {
ValueSource value = value(index);
if (wasNull)
return 0;
else
return (int)getTypesTranslator()
.getIntegerValue(cachedCast(index, value, Types.INTEGER));
}
public long getLong(int index) {
ValueSource value = value(index);
if (wasNull)
return 0;
else
return getTypesTranslator()
.getIntegerValue(cachedCast(index, value, Types.BIGINT));
}
public float getFloat(int index) {
ValueSource value = value(index);
if (wasNull)
return 0.0f;
else
return cachedCast(index, value, Types.FLOAT).getFloat();
}
public double getDouble(int index) {
ValueSource value = value(index);
if (wasNull)
return 0.0;
else
return cachedCast(index, value, Types.DOUBLE).getDouble();
}
public BigDecimal getBigDecimal(int index) {
ValueSource value = value(index);
if (wasNull)
return null;
else
return getTypesTranslator().getDecimalValue(cachedCast(index, value, Types.DECIMAL));
}
public byte[] getBytes(int index) {
ValueSource value = value(index);
if (wasNull)
return null;
else
return cachedCast(index, value, Types.VARBINARY).getBytes();
}
public Date getDate(int index) {
ValueSource value = value(index);
if (wasNull)
return null;
else
return new Date(getTypesTranslator().getTimestampMillisValue(value));
}
public Time getTime(int index) {
ValueSource value = value(index);
if (wasNull)
return null;
else {
return new Time(getTypesTranslator().getTimestampMillisValue(value));
}
}
public Timestamp getTimestamp(int index) {
ValueSource value = value(index);
if (wasNull)
return null;
else {
Timestamp result = new Timestamp(getTypesTranslator().getTimestampMillisValue(value));
result.setNanos(getTypesTranslator().getTimestampNanosValue(value));
return result;
}
}
public Date getDate(int index, Calendar cal) {
return getDate(index);
}
public Time getTime(int index, Calendar cal) {
return getTime(index);
}
public Timestamp getTimestamp(int index, Calendar cal) {
return getTimestamp(index);
}
public ResultSet getResultSet(int index) {
ValueSource value = value(index);
if (wasNull)
return null;
else
return toResultSet(index, value.getObject());
}
public Object getObject(int index) {
return getObject(index, getTypesTranslator().jdbcClass(getType(index)));
}
public Object getObject(int index, Class<?> type) {
if (type == String.class)
return getString(index);
else if (type == BigDecimal.class)
return getBigDecimal(index);
else if ((type == Boolean.class) || (type == Boolean.TYPE)) {
boolean value = getBoolean(index);
return wasNull() ? null : Boolean.valueOf(value);
}
else if ((type == Byte.class) || (type == Byte.TYPE)) {
byte value = getByte(index);
return wasNull() ? null : Byte.valueOf(value);
}
else if ((type == Short.class) || (type == Short.TYPE)) {
short value = getShort(index);
return wasNull() ? null : Short.valueOf(value);
}
else if ((type == Integer.class) || (type == Integer.TYPE)) {
int value = getInt(index);
return wasNull() ? null : Integer.valueOf(value);
}
else if ((type == Long.class) || (type == Long.TYPE)) {
long value = getLong(index);
return wasNull() ? null : Long.valueOf(value);
}
else if ((type == Float.class) || (type == Float.TYPE)) {
float value = getFloat(index);
return wasNull() ? null : Float.valueOf(value);
}
else if ((type == Double.class) || (type == Double.TYPE)) {
double value = getDouble(index);
return wasNull() ? null : Double.valueOf(value);
}
else if (type == byte[].class)
return getBytes(index);
else if (type == Date.class)
return getDate(index);
else if (type == Time.class)
return getTime(index);
else if (type == Timestamp.class)
return getTimestamp(index);
else if (type == ResultSet.class)
return getResultSet(index);
else if (type == Array.class)
return getArray(index);
else if (type == Blob.class)
return getBlob(index);
else if (type == Clob.class)
return getClob(index);
else if (type == Ref.class)
return getRef(index);
else if (type == URL.class)
return getURL(index);
else if (type == RowId.class)
return getRowId(index);
else if (type == NClob.class)
return getNClob(index);
else if (type == SQLXML.class)
return getSQLXML(index);
else
throw new UnsupportedOperationException("Unsupported type " + type);
}
public InputStream getAsciiStream(int index) {
throw new UnsupportedOperationException();
}
public InputStream getUnicodeStream(int index) {
throw new UnsupportedOperationException();
}
public Reader getCharacterStream(int index) {
throw new UnsupportedOperationException();
}
public Reader getNCharacterStream(int index) {
throw new UnsupportedOperationException();
}
public InputStream getBinaryStream(int index) {
throw new UnsupportedOperationException();
}
public Ref getRef(int index) {
throw new UnsupportedOperationException();
}
public Blob getBlob(int index) {
throw new UnsupportedOperationException();
}
public Clob getClob(int index) {
throw new UnsupportedOperationException();
}
public NClob getNClob(int index) {
throw new UnsupportedOperationException();
}
public SQLXML getSQLXML(int index) {
throw new UnsupportedOperationException();
}
public RowId getRowId(int index) {
throw new UnsupportedOperationException();
}
public Array getArray(int index) {
throw new UnsupportedOperationException();
}
public URL getURL(int index) {
throw new UnsupportedOperationException();
}
public void setNull(int index) {
setValue(index, (Object)null, jdbcInstance(Types.INTEGER));
}
public void setBoolean(int index, boolean x) {
setValue(index, x, jdbcInstance(Types.BOOLEAN));
}
public void setByte(int index, byte x) {
setValue(index, (int)x, jdbcInstance(Types.TINYINT));
}
public void setShort(int index, short x) {
setValue(index, (int)x, jdbcInstance(Types.SMALLINT));
}
public void setInt(int index, int x) {
setValue(index, x, jdbcInstance(Types.INTEGER));
}
public void setLong(int index, long x) {
setValue(index, x, jdbcInstance(Types.BIGINT));
}
public void setFloat(int index, float x) {
setValue(index, x, jdbcInstance(Types.FLOAT));
}
public void setDouble(int index, double x) {
setValue(index, x, jdbcInstance(Types.DOUBLE));
}
public void setBigDecimal(int index, BigDecimal x) {
setValue(index, x, jdbcInstance(Types.DECIMAL));
}
public void setString(int index, String x) {
setValue(index, x, jdbcInstance(Types.VARCHAR));
}
public void setBytes(int index, byte x[]) {
setValue(index, new WrappingByteSource(x), jdbcInstance(Types.VARBINARY));
}
public void setDate(int index, Date x) {
Value value = new Value(jdbcInstance(Types.DATE));
getTypesTranslator().setTimestampMillisValue(value, x.getTime(), 0);
setValue(index, value);
}
public void setTime(int index, Time x) {
Value value = new Value(jdbcInstance(Types.TIME));
getTypesTranslator().setTimestampMillisValue(value, x.getTime(), 0);
setValue(index, value);
}
public void setTimestamp(int index, Timestamp x) {
Value value = new Value(jdbcInstance(Types.TIMESTAMP));
getTypesTranslator().setTimestampMillisValue(value, x.getTime(), x.getNanos());
setValue(index, value);
}
public void setDate(int index, Date x, Calendar cal) {
setDate(index, x);
}
public void setTime(int index, Time x, Calendar cal) {
setTime(index, x);
}
public void setTimestamp(int index, Timestamp x, Calendar cal) {
setTimestamp(index, x);
}
public void setObject(int index, Object x) {
if (x == null)
setNull(index);
else if (x instanceof String)
setString(index, (String)x);
else if (x instanceof BigDecimal)
setBigDecimal(index, (BigDecimal)x);
else if (x instanceof Boolean)
setBoolean(index, (Boolean)x);
else if (x instanceof Byte)
setByte(index, (Byte)x);
else if (x instanceof Short)
setShort(index, (Short)x);
else if (x instanceof Integer)
setInt(index, (Integer)x);
else if (x instanceof Long)
setLong(index, (Long)x);
else if (x instanceof Float)
setFloat(index, (Float)x);
else if (x instanceof Double)
setDouble(index, (Double)x);
else if (x instanceof byte[])
setBytes(index, (byte[])x);
else if (x instanceof Date)
setDate(index, (Date)x);
else if (x instanceof Time)
setTime(index, (Time)x);
else if (x instanceof Timestamp)
setTimestamp(index, (Timestamp)x);
else if (x instanceof Array)
setArray(index, (Array)x);
else if (x instanceof Blob)
setBlob(index, (Blob)x);
else if (x instanceof Clob)
setClob(index, (Clob)x);
else if (x instanceof Ref)
setRef(index, (Ref)x);
else if (x instanceof URL)
setURL(index, (URL)x);
else if (x instanceof RowId)
setRowId(index, (RowId)x);
else if (x instanceof NClob)
setNClob(index, (NClob)x);
else if (x instanceof SQLXML)
setSQLXML(index, (SQLXML)x);
else if (x instanceof BigInteger)
setLong(index, ((BigInteger)x).longValue());
else
throw new UnsupportedOperationException("Unsupported type " + x);
}
public void setNString(int index, String value) {
setString(index, value);
}
public void setAsciiStream(int index, InputStream x, int length) throws IOException {
String value;
byte[] b = new byte[length];
int l = x.read(b);
value = new String(b, 0, l, "ASCII");
setValue(index, value, jdbcInstance(Types.VARCHAR));
}
public void setUnicodeStream(int index, InputStream x, int length) throws IOException {
String value;
byte[] b = new byte[length];
int l = x.read(b);
value = new String(b, 0, l, "UTF-8");
setValue(index, value, jdbcInstance(Types.VARCHAR));
}
public void setBinaryStream(int index, InputStream x, int length) throws IOException {
WrappingByteSource value;
byte[] b = new byte[length];
int l = x.read(b);
value = new WrappingByteSource().wrap(b, 0, l);
setValue(index, value, jdbcInstance(Types.VARBINARY));
}
public void setCharacterStream(int index, Reader reader, int length) throws IOException {
String value;
char[] c = new char[length];
int l = reader.read(c);
value = new String(c, 0, l);
setValue(index, value, jdbcInstance(Types.VARCHAR));
}
public void setNCharacterStream(int index, Reader value, long length) throws IOException {
setCharacterStream(index, value, length);
}
public void setAsciiStream(int index, InputStream x, long length) throws IOException {
setAsciiStream(index, x, (int)length);
}
public void setBinaryStream(int index, InputStream x, long length) throws IOException {
setBinaryStream(index, x, (int)length);
}
public void setCharacterStream(int index, Reader reader, long length) throws IOException {
setCharacterStream(index, reader, (int)length);
}
public void setAsciiStream(int index, InputStream x) throws IOException {
String value;
ByteArrayOutputStream ostr = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
while (true) {
int len = x.read(buf);
if (len < 0) break;
ostr.write(buf, 0, len);
}
value = new String(ostr.toByteArray(), "ASCII");
setValue(index, value, jdbcInstance(Types.VARCHAR));
}
public void setBinaryStream(int index, InputStream x) throws IOException {
WrappingByteSource value;
ByteArrayOutputStream ostr = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
while (true) {
int len = x.read(buf);
if (len < 0) break;
ostr.write(buf, 0, len);
}
value = new WrappingByteSource(ostr.toByteArray());
setValue(index, value, jdbcInstance(Types.VARBINARY));
}
public void setCharacterStream(int index, Reader reader) throws IOException {
String value;
StringWriter ostr = new StringWriter();
char[] buf = new char[1024];
while (true) {
int len = reader.read(buf);
if (len < 0) break;
ostr.write(buf, 0, len);
}
value = ostr.toString();
setValue(index, value, jdbcInstance(Types.VARCHAR));
}
public void setNCharacterStream(int index, Reader value) throws IOException {
setCharacterStream(index, value);
}
public void setRef(int index, Ref x) {
throw new UnsupportedOperationException();
}
public void setBlob(int index, Blob x) {
throw new UnsupportedOperationException();
}
public void setClob(int index, Clob x) {
throw new UnsupportedOperationException();
}
public void setClob(int index, Reader reader) {
throw new UnsupportedOperationException();
}
public void setBlob(int index, InputStream inputStream) {
throw new UnsupportedOperationException();
}
public void setNClob(int index, Reader reader) {
throw new UnsupportedOperationException();
}
public void setNClob(int index, NClob value) {
throw new UnsupportedOperationException();
}
public void setClob(int index, Reader reader, long length) {
throw new UnsupportedOperationException();
}
public void setBlob(int index, InputStream inputStream, long length) {
throw new UnsupportedOperationException();
}
public void setNClob(int index, Reader reader, long length) {
throw new UnsupportedOperationException();
}
public void setSQLXML(int index, SQLXML xmlObject) {
throw new UnsupportedOperationException();
}
public void setURL(int index, URL x) {
throw new UnsupportedOperationException();
}
public void setRowId(int index, RowId x) {
throw new UnsupportedOperationException();
}
public void setArray(int index, Array x) {
throw new UnsupportedOperationException();
}
public void formatAsJson(int index, AkibanAppender appender, FormatOptions options) {
ValueSource value = getValue(index);
value.getType().formatAsJson(value, appender, options);
}
}