/** * 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.server.types.value; import com.foundationdb.qp.operator.QueryContext; import com.foundationdb.server.collation.AkCollator; import com.foundationdb.server.spatial.Spatial; import com.foundationdb.server.service.blob.BlobRef; import com.foundationdb.server.types.TClass; import com.foundationdb.server.types.TExecutionContext; import com.foundationdb.server.types.TInstance; import com.foundationdb.server.types.TPreptimeValue; import com.foundationdb.server.types.aksql.aktypes.AkBool; import com.foundationdb.server.types.aksql.aktypes.AkGeometry; import com.foundationdb.server.types.aksql.aktypes.AkGUID; import com.foundationdb.server.types.aksql.aktypes.AkBlob; import com.foundationdb.server.types.common.BigDecimalWrapper; import com.foundationdb.server.types.common.BigDecimalWrapperImpl; import com.foundationdb.server.types.common.types.StringFactory; import com.foundationdb.server.types.mcompat.mtypes.MApproximateNumber; import com.foundationdb.server.types.mcompat.mtypes.MBinary; import com.foundationdb.server.types.mcompat.mtypes.MNumeric; import com.foundationdb.server.types.mcompat.mtypes.MString; import com.foundationdb.util.ByteSource; import com.foundationdb.util.WrappingByteSource; import com.geophile.z.SpatialObject; import com.geophile.z.spatialobject.jts.JTSSpatialObject; import com.vividsolutions.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Arrays; import java.util.Collections; import java.util.UUID; public final class ValueSources { private static final Logger logger = LoggerFactory.getLogger(ValueSources.class); public static TClass tClass(ValueSource source) { return TInstance.tClass(source.getType()); } public static UnderlyingType underlyingType(ValueSource source) { return TInstance.underlyingType(source.getType()); } /** * Gets a long from one of the signed int methods on source. The source instance must have a non-null raw value * of one of the signed INT types. * @param source the source to extract a long from * @return the source's value as a long */ public static long getLong(ValueSource source) { switch (underlyingType(source)) { case INT_8: return source.getInt8(); case INT_16: return source.getInt16(); case INT_32: return source.getInt32(); case INT_64: return source.getInt64(); default: throw new UnsupportedOperationException(source.getType().toString()); } } public static Value valuefromObject(Object object, TInstance type) { return valuefromObject(object, type, null); } public static Value valuefromObject(Object object, TInstance type, QueryContext queryContext) { Value value = new Value(type); if (object == null) { value.putNull(); } else if (object instanceof String) { // This is the common case, so let's test for it first if (TInstance.underlyingType(type) == UnderlyingType.STRING) value.putString((String)object, null); else if (type == null) { type = MString.VARCHAR.instance( ((String)object).length(), StringFactory.DEFAULT_CHARSET.ordinal(), StringFactory.NULL_COLLATION_ID, false); value = new Value(type, (String)object); } } else if (type == null) { value = fromObject(object); } else if (object instanceof BlobRef) { value.putObject(object); } else { switch (TInstance.underlyingType(type)) { case INT_8: case INT_16: case UINT_16: case INT_32: case INT_64: if (object instanceof Number) valueFromLong(((Number)object).longValue(), value); break; case FLOAT: if (object instanceof Number) value.putFloat(((Number)object).floatValue()); break; case DOUBLE: if (object instanceof Number) value.putDouble(((Number)object).doubleValue()); break; case BYTES: if (object instanceof byte[]) value.putBytes((byte[])object); else if (object instanceof ByteSource) value.putBytes(((ByteSource)object).toByteSubarray()); else if (object instanceof JTSSpatialObject) { if (AkBlob.isBlob(type.typeClass())) { byte[] content = Spatial.serializeWKB((JTSSpatialObject) object); value.putObject(new BlobRef(content)); } } break; case STRING: if ((object instanceof JTSSpatialObject)) { value = new Value(type, Spatial.serializeWKT((JTSSpatialObject) object)); } else { throw new IllegalArgumentException("Unsafe toString(): " + object.getClass()); } break; case BOOL: if (object instanceof Boolean) value.putBool((Boolean)object); break; } } if (!value.hasAnyValue()) { if (type == null) { value = fromObject(object); } else { value = convertFromObject(object, type, queryContext); } } return value; } private static Value convertFromObject (Object object, TInstance type, QueryContext queryContext) { Value in = fromObject(object); TInstance inType = in.getType(); Value out = null; if (!inType.equals(type)) { TExecutionContext context = new TExecutionContext(Collections.singletonList(in.getType()), type, queryContext); out = new Value(type); type.typeClass().fromObject(context, in, out); } else { out = in; } return out; } public static TPreptimeValue preptimeValueFromObject(Object object) { if (object == null) { Value nv = new Value(null); return new TPreptimeValue(nv); } else { Value value = fromObject(object); return new TPreptimeValue(value); } } /** * Reflectively create a {@linkplain TPreptimeValue} from the given object and the {@linkplain TInstance} * * @param object * @param type * @return */ public static TPreptimeValue fromObject(Object object, TInstance type) { ValueSource value = valuefromObject(object, type); if (type == null) { if (value.getType() == null) { return new TPreptimeValue(value.getType()); } return new TPreptimeValue(value); } return new TPreptimeValue (type,value); } /** * Creates a Value from the given object, deriving the type from the object. * @throws java.lang.UnsupportedOperationException if the type cannot be inferred */ public static Value fromObject(Object object) { final TInstance type; Value value = null; if (object == null) { value = new Value(null); value.putNull(); } else if (object instanceof String) { String s = (String) object; type = MString.VARCHAR.instance( s.length(), StringFactory.DEFAULT_CHARSET.ordinal(), StringFactory.NULL_COLLATION_ID, false); value = new Value(type, s); } else if (object instanceof Long) { type = MNumeric.BIGINT.instance(false); value = new Value(type, (Long)object); } else if (object instanceof Integer) { type = MNumeric.INT.instance(false); value = new Value(type, (Integer) object); } else if (object instanceof Double) { type = MApproximateNumber.DOUBLE.instance(false); value = new Value(type, (Double) object); } else if (object instanceof Float) { type = MApproximateNumber.FLOAT.instance(false); value = new Value(type, (Float)object); } else if (object instanceof BigDecimalWrapper) { BigDecimalWrapper bdw = (BigDecimalWrapper)object; type = MNumeric.DECIMAL.instance(bdw.getPrecision(), bdw.getScale(), false); value = new Value(type); value.putObject(bdw); } else if (object instanceof BigDecimal) { BigDecimal bd = (BigDecimal) object; type = MNumeric.DECIMAL.instance(BigDecimalWrapperImpl.sqlPrecision(bd), BigDecimalWrapperImpl.sqlScale(bd), false); value = new Value(type); value.putObject(new BigDecimalWrapperImpl(bd)); } else if (object instanceof ByteSource || object instanceof byte[]) { byte[] bytes; if (object instanceof byte[]) { bytes = (byte[])object; } else { ByteSource source = (ByteSource) object; byte[] srcArray = source.byteArray(); int offset = source.byteArrayOffset(); int end = offset + source.byteArrayLength(); bytes = Arrays.copyOfRange(srcArray, offset, end); } type = MBinary.VARBINARY.instance(bytes.length, false); value = new Value(type, bytes); } else if (object instanceof BigInteger) { type = MNumeric.BIGINT_UNSIGNED.instance(false); BigInteger bi = (BigInteger) object; value = new Value(type, bi.longValue()); } else if (object instanceof Boolean) { type = AkBool.INSTANCE.instance(false); value = new Value(type, (Boolean)object); } else if (object instanceof Character) { type = MString.VARCHAR.instance(1, false); value = new Value(type, object.toString()); } else if (object instanceof Short) { type = MNumeric.SMALLINT.instance(false); value = new Value(type, (Short)object); } else if (object instanceof Byte) { type = MNumeric.TINYINT.instance(false); value = new Value(type, (Byte)object); } else if (object instanceof UUID) { type = AkGUID.INSTANCE.instance(false); value = new Value(type); value.putObject(object); } else if (object instanceof BlobRef) { type = AkBlob.INSTANCE.instance(false); value = new Value(type); value.putObject(type); } else { throw new UnsupportedOperationException("can't convert " + object + " of type " + object.getClass()); } return value; } public static void valueFromLong(long value, Value result) { UnderlyingType underlying = underlyingType(result); switch (underlying) { case INT_8: result.putInt8((byte)value); break; case INT_16: result.putInt16((short)value); break; case UINT_16: result.putUInt16((char)value); break; case INT_32: result.putInt32((int)value); break; case INT_64: result.putInt64(value); break; /* case BYTES: ByteBuffer buffer = ByteBuffer.allocate(8); buffer.putLong(value); result.putBytes(buffer.array()); break; case STRING: result.putString(Long.toString(value), null); break; case DOUBLE: result.putDouble(Long.valueOf(value).doubleValue()); break; case FLOAT: result.putFloat(Long.valueOf(value).floatValue()); break; */ default: throw new AssertionError(underlying); } } public static Object toObject(ValueSource source) { if (source == null || source.isNull()) { return null; } if (source.getType().typeClass() == MNumeric.DECIMAL || source.getType().typeClass() == MNumeric.DECIMAL_UNSIGNED) { if (source.getObject() instanceof BigDecimalWrapperImpl) { return ((BigDecimalWrapperImpl)source.getObject()).asBigDecimal(); } logger.error("MDecimal with underlying object of : {}", source.getObject().getClass()); } if (source.getType().typeClass() == AkGUID.INSTANCE.widestComparable()) { if (source.getObject() instanceof UUID) { return (UUID) source.getObject(); } logger.error("GUID with underlying object of : {}", source.getObject().getClass()); } if (AkBlob.isBlob(source.getType().typeClass())) { if (source.getObject() instanceof BlobRef) { return ((BlobRef)source.getObject()).getValue(); } logger.error("Blob with underlying object of : {}", source.getObject().getClass()); } if (source.getType().typeClass() == AkGeometry.INSTANCE.widestComparable()) { if (source.getObject() instanceof Geometry) { return (Geometry) source.getObject(); } logger.error("Geometry with underlying object of : {}", source.getObject().getClass()); } if (source.getType().typeClass() == MBinary.VARBINARY) { return new WrappingByteSource(source.getBytes()); } UnderlyingType underlying = underlyingType(source); switch (underlying) { case BOOL: return source.getBoolean(); case INT_8: return source.getInt8(); case INT_16: return source.getInt16(); case UINT_16: return source.getUInt16(); case INT_32: return source.getInt32(); case INT_64: return source.getInt64(); case FLOAT: return source.getFloat(); case DOUBLE: return source.getDouble(); case BYTES: return source.getBytes(); case STRING: return source.getString(); default: throw new AssertionError(underlying); } } public static int hash(ValueSource source, AkCollator collator) { if (source.isNull()) return 0; final long hash; switch (underlyingType(source)) { case BOOL: hash = source.getBoolean() ? 1 : 0; break; case INT_8: hash = source.getInt8(); break; case INT_16: hash = source.getInt16(); break; case UINT_16: hash = source.getUInt16(); break; case INT_32: hash = source.getInt32(); break; case INT_64: hash = source.getInt64(); break; case FLOAT: hash = Float.floatToRawIntBits(source.getFloat()); break; case DOUBLE: hash = Double.doubleToRawLongBits(source.getDouble()); break; case BYTES: hash = Arrays.hashCode(source.getBytes()); break; case STRING: hash = AkCollator.hashValue(source, collator); break; default: throw new AssertionError(source.getType()); } return ((int) (hash >> 32)) ^ (int) hash; } public static ValueSource getNullSource(TInstance underlying) { Value result = new Value(underlying); result.putNull(); return result; } private ValueSources() {} public static void toStringSimple(ValueSource source, StringBuilder out) { if (source.isNull()) { out.append("NULL"); return; } if (!source.hasAnyValue()) { out.append("<unset>"); return; } if (source.hasCacheValue()) { out.append(source.getObject()); return; } switch (underlyingType(source)) { case BOOL: out.append(source.getBoolean()); break; case INT_8: out.append(source.getInt8()); break; case INT_16: out.append(source.getInt16()); break; case UINT_16: // display as int instead of char, to reinforce that it's not a char, it's an int with unsigned collation out.append((int)source.getUInt16()); break; case INT_32: out.append(source.getInt32()); break; case INT_64: out.append(source.getInt64()); break; case FLOAT: out.append(source.getFloat()); break; case DOUBLE: out.append(source.getDouble()); break; case BYTES: out.append(Arrays.toString(source.getBytes())); break; case STRING: out.append(source.getString()); break; default: // toStrings are non-critical, so let's not throw an error logger.warn("unknown ValueSource underlying type: {} ({})", source.getType(), source); out.append("<?>"); break; } } public static String toStringSimple(ValueSource source) { StringBuilder sb = new StringBuilder(); toStringSimple(source, sb); return sb.toString(); } }