/** * 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.qp.operator.QueryBindings; import com.foundationdb.qp.operator.QueryContext; import com.foundationdb.server.error.AkibanInternalException; import com.foundationdb.server.error.UnknownDataTypeException; import com.foundationdb.server.error.UnsupportedCharsetException; import com.foundationdb.server.types.TCast; import com.foundationdb.server.types.TExecutionContext; import com.foundationdb.server.types.TInstance; import com.foundationdb.server.types.aksql.aktypes.AkBool; import com.foundationdb.server.types.aksql.aktypes.AkGUID; import com.foundationdb.server.types.common.types.TBinary; import com.foundationdb.server.types.common.types.TypesTranslator; import com.foundationdb.server.types.mcompat.mtypes.MApproximateNumber; import com.foundationdb.server.types.mcompat.mtypes.MBinary; import com.foundationdb.server.types.mcompat.mtypes.MDateAndTime; import com.foundationdb.server.types.mcompat.mtypes.MNumeric; import com.foundationdb.server.types.mcompat.mtypes.MString; import com.foundationdb.server.types.service.TypesRegistryService; import com.foundationdb.server.types.value.Value; import com.foundationdb.server.types.value.ValueSource; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import java.math.BigDecimal; import java.sql.Types; import java.util.Collections; import java.io.*; /** Decode values from external representation into query bindings. */ public class ServerValueDecoder { private final TypesTranslator typesTranslator; private final String encoding; public ServerValueDecoder(TypesTranslator typesTranslator, String encoding) { this.typesTranslator = typesTranslator; this.encoding = encoding; } /** Decode the given value into a the given bindings at the given position. */ public void decodeValue(byte[] encoded, ServerType type, boolean binary, QueryBindings bindings, int index, QueryContext queryContext, TypesRegistryService typesRegistryService) { TInstance targetType = type != null ? type.getType() : null; if (targetType == null && encoded != null) { throw new UnknownDataTypeException(null); } ValueSource source; if (encoded == null) { Value value = new Value(targetType); value.putNull(); bindings.setValue(index, value); return; } else if (!binary) { try { source = new Value(MString.varchar(), new String(encoded, encoding)); } catch (UnsupportedEncodingException ex) { throw new UnsupportedCharsetException(encoding); } } else { try { switch (type.getBinaryEncoding()) { case BINARY_OCTAL_TEXT: source = new Value(MBinary.VARBINARY.instance(false), encoded); break; case INT_8: case INT_16: case INT_32: case INT_64: // Types.BIGINT // Go by the length sent rather than the implied type. source = decodeIntegerType(encoded); break; case FLOAT_32: source = new Value(MApproximateNumber.FLOAT.instance(false), getDataStream(encoded).readFloat()); break; case FLOAT_64: source = new Value(MApproximateNumber.DOUBLE.instance(false), getDataStream(encoded).readDouble()); break; case BOOLEAN_C: source = new Value(AkBool.INSTANCE.instance(false), encoded[0] != 0); break; case TIMESTAMP_INT64_MICROS_2000_NOTZ: // Types.TIMESTAMP source = decodeTimestampInt64Micros2000NoTZ(encoded); break; case UUID: Value value = new Value(AkGUID.INSTANCE.instance(false)); value.putObject(AkGUID.bytesToUUID(encoded, 0)); source = value; break; case STRING_BYTES: { String s = new String(encoded, encoding); source = new Value(MString.VARCHAR.instance(s.length(), false), s); break; } // Note: these types had previous implementations, but I couldn't exercise them in tests to verify // either with jdbc or pg8000. If you run into them, try looking at the log for this file, it most // likely has a correct starting point case TIMESTAMP_FLOAT64_SECS_2000_NOTZ: // Types.TIMESTAMP case DAYS_2000: // DATE case TIME_FLOAT64_SECS_NOTZ: // TIME case TIME_INT64_MICROS_NOTZ: // TIME case DECIMAL_PG_NUMERIC_VAR: default: throw new UnknownDataTypeException(type.toString()); } } catch (UnsupportedEncodingException ex) { throw new UnsupportedCharsetException(encoding); } catch (IOException ex) { throw new AkibanInternalException("IO error reading from byte array", ex); } } TCast cast = typesRegistryService.getCastsResolver().cast(source.getType(), targetType); TExecutionContext context = new TExecutionContext(Collections.singletonList(source.getType()), targetType, queryContext); Value target = new Value(targetType); cast.evaluate(context, source, target); bindings.setValue(index, target); } private ValueSource decodeTimestampInt64Micros2000NoTZ(byte[] encoded) throws IOException { long micros = getDataStream(encoded).readLong(); long secs = micros / 1000000; long milliseconds = seconds2000NoTZ(secs); int nanos = (int) (micros - secs * 1000000) * 1000; Value source = new Value(MDateAndTime.TIMESTAMP.instance(false)); typesTranslator.setTimestampMillisValue(source, milliseconds, nanos); return source; } public ValueSource decodeIntegerType(byte[] encoded) throws IOException { switch (encoded.length) { case 1: return new Value(MNumeric.TINYINT.instance(false), getDataStream(encoded).read()); case 2: return new Value(MNumeric.SMALLINT.instance(false), getDataStream(encoded).readShort()); case 4: return new Value(MNumeric.INT.instance(false), getDataStream(encoded).readInt()); case 8: return new Value(MNumeric.BIGINT.instance(false), getDataStream(encoded).readLong()); default: throw new AkibanInternalException("Not an integer size: " + encoded); } } private static DataInputStream getDataStream(byte[] bytes) { return new DataInputStream(new ByteArrayInputStream(bytes)); } private static long seconds2000NoTZ(long secs) { long millis = (secs + 946684800) * 1000; // 2000-01-01 00:00:00-UTC. DateTimeZone dtz = DateTimeZone.getDefault(); millis -= dtz.getOffset(millis); return millis; } }