Package com.foundationdb.sql.server

Source Code of com.foundationdb.sql.server.ServerValueEncoder

/**
* 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.InvalidParameterValueException;
import com.foundationdb.server.error.UnsupportedCharsetException;
import com.foundationdb.server.error.ZeroDateTimeException;
import com.foundationdb.server.types.FormatOptions;
import com.foundationdb.server.types.TExecutionContext;
import com.foundationdb.server.types.TInstance;
import com.foundationdb.server.types.aksql.aktypes.AkGUID;
import com.foundationdb.server.types.common.types.TString;
import com.foundationdb.server.types.common.types.TypesTranslator;
import com.foundationdb.server.types.mcompat.mtypes.MDateAndTime;
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.Strings;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import java.math.BigDecimal;
import java.sql.Types;
import java.util.Collections;
import java.util.Date;
import java.io.*;

/** Encode result values for transmission. */
public class ServerValueEncoder
{
   
    public static enum ZeroDateTimeBehavior {
        NONE(null),
        EXCEPTION("exception"),
        ROUND("round"),
        CONVERT_TO_NULL("convertToNull");

        private String propertyName;

        ZeroDateTimeBehavior(String propertyName) {
            this.propertyName = propertyName;
        }
       
        public static ZeroDateTimeBehavior fromProperty(String name) {
            if (name == null) return NONE;
            for (ZeroDateTimeBehavior zdtb : values()) {
                if (name.equals(zdtb.propertyName))
                    return zdtb;
            }
            throw new InvalidParameterValueException(String.format("Invalid name: %s for ZeroDateTimeBehavior", name));
        }
    }

 
    public static final ValueSource ROUND_ZERO_DATETIME_SOURCE = new Value(MDateAndTime.DATETIME.instance(false),
                                                                           MDateAndTime.encodeDateTime(1, 1, 1, 0, 0, 0));
    public static final ValueSource ROUND_ZERO_DATE_SOURCE = new Value(MDateAndTime.DATE.instance(false),
                                                                       MDateAndTime.encodeDate(1, 1, 1));
   
    private final TypesTranslator typesTranslator;
    private final String encoding;
    private ZeroDateTimeBehavior zeroDateTimeBehavior;
    private FormatOptions options;
    private final ByteArrayOutputStream byteStream;
    private final PrintWriter printWriter;
    private final AkibanAppender appender;
    private DataOutputStream dataStream;

    public ServerValueEncoder(TypesTranslator typesTranslator, String encoding, FormatOptions options) {
        this(typesTranslator, encoding, new ByteArrayOutputStream(), options);
    }

    public ServerValueEncoder(TypesTranslator typesTranslator, String encoding,
                              ZeroDateTimeBehavior zeroDateTimeBehavior, FormatOptions options) {
        this(typesTranslator, encoding, options);
        this.zeroDateTimeBehavior = zeroDateTimeBehavior;
    }

    public ServerValueEncoder(TypesTranslator typesTranslator, String encoding, ByteArrayOutputStream byteStream,
                              FormatOptions options) {
        this.typesTranslator = typesTranslator;
        this.encoding = encoding;
        this.byteStream = byteStream;
        this.options = options;
        try {
            printWriter = new PrintWriter(new OutputStreamWriter(byteStream, encoding));
        }
        catch (UnsupportedEncodingException ex) {
            throw new UnsupportedCharsetException(encoding);
        }
        // If the target encoding is UTF-8, we can support
        // canAppendBytes() for properly encoded source strings.
        if ("UTF-8".equals(encoding))
            appender = AkibanAppender.of(byteStream, printWriter, "UTF-8");
        else
            appender = AkibanAppender.of(printWriter);
    }

    public String getEncoding() {
        return encoding;
    }

    public ByteArrayOutputStream getByteStream() {
        printWriter.flush();
        return byteStream;
    }

    public AkibanAppender getAppender() {
        return appender;
    }

    public DataOutputStream getDataStream() {
        printWriter.flush();
        if (dataStream == null)
            dataStream = new DataOutputStream(byteStream);
        return dataStream;
    }

 
    /**
     * Encode the given value into a stream that can then be passed
     * to
     * <code>writeByteStream</code>.
     */
    public ByteArrayOutputStream encodeValue(ValueSource value, ServerType type,
                                             boolean binary) throws IOException {
        if (value.isNull())
            return null;
        if ((zeroDateTimeBehavior != ZeroDateTimeBehavior.NONE) &&
            (((type.getType().typeClass() == MDateAndTime.DATE) &&
              (value.getInt32() == 0)) ||
             ((type.getType().typeClass() == MDateAndTime.DATETIME) &&
              (value.getInt64() == 0)))) {
            switch (zeroDateTimeBehavior) {
            case EXCEPTION:
                throw new ZeroDateTimeException();
            case ROUND:
                value = (type.getType().typeClass() == MDateAndTime.DATETIME)
                        ? ROUND_ZERO_DATETIME_SOURCE
                        : ROUND_ZERO_DATE_SOURCE;
                break;
            case CONVERT_TO_NULL:
                return null;
            }
        }
        reset();
        appendValue(value, type, binary);
        return getByteStream();
    }

    /** Encode the given direct value. */
    public ByteArrayOutputStream encodePObject(Object value, ServerType type,
                                               boolean binary) throws IOException {
        if (value == null)
            return null;
        reset();
        appendPObject(value, type, binary);
        return getByteStream();
    }

    /** Reset the contents of the buffer. */
    public void reset() {
        getByteStream().reset();
    }
   
    /** Append the given value to the buffer. */
    public void appendValue(ValueSource value, ServerType type, boolean binary)
            throws IOException {
        if (!binary) {
            // Handle unusual text encoding of binary types.
            switch (type.getBinaryEncoding()) {
            case BINARY_OCTAL_TEXT:
                processBinaryText(value);
                break;

            default:
                type.getType().format(value, appender);
                break;
            }
        }
        else {
            switch (type.getBinaryEncoding()) {
            case BINARY_OCTAL_TEXT:
                getByteStream().write(value.getBytes());
                break;
            case INT_8:
                getDataStream().write((byte)typesTranslator.getIntegerValue(value));
                break;
            case INT_16:
                getDataStream().writeShort((short)typesTranslator.getIntegerValue(value));
                break;
            case INT_32:
                getDataStream().writeInt((int)typesTranslator.getIntegerValue(value));
                break;
            case INT_64:
                getDataStream().writeLong(typesTranslator.getIntegerValue(value));
                break;
            case FLOAT_32:
                getDataStream().writeFloat(value.getFloat());
                break;
            case FLOAT_64:
                getDataStream().writeDouble(value.getDouble());
                break;
            case STRING_BYTES:
                getByteStream().write(value.getString().getBytes(encoding));
                break;
            case BOOLEAN_C:
                getDataStream().write(value.getBoolean() ? 1 : 0);
                break;
            case TIMESTAMP_FLOAT64_SECS_2000_NOTZ:
                getDataStream().writeDouble(seconds2000NoTZ(typesTranslator.getTimestampMillisValue(value)) +
                                            typesTranslator.getTimestampNanosValue(value) / 1.0e9);
                break;
            case TIMESTAMP_INT64_MICROS_2000_NOTZ:
                getDataStream().writeLong(seconds2000NoTZ(typesTranslator.getTimestampMillisValue(value)) * 1000000L +
                                          typesTranslator.getTimestampNanosValue(value) / 1000);
                break;
            case DAYS_2000:
                getDataStream().writeInt(days2000(typesTranslator.getTimestampMillisValue(value)));
                break;
            case TIME_FLOAT64_SECS_NOTZ:
                getDataStream().writeDouble(timeSecsNoTZ(typesTranslator.getTimestampMillisValue(value)));
                break;
            case TIME_INT64_MICROS_NOTZ:
                getDataStream().writeLong(timeSecsNoTZ(typesTranslator.getTimestampMillisValue(value)) * 1000000L);
                break;
            case DECIMAL_PG_NUMERIC_VAR:
                for (short d : pgNumericVar(typesTranslator.getDecimalValue(value))) {
                    getDataStream().writeShort(d);
                }
                break;
            case UUID:
                getDataStream().write(AkGUID.uuidToBytes((java.util.UUID) value.getObject()));
                break;
            case NONE:
            default:
                throw new UnsupportedOperationException("No binary encoding for " + type);
            }
        }
    }  

    private void processBinaryText(ValueSource value) {
        FormatOptions.BinaryFormatOption bfo = options.get(FormatOptions.BinaryFormatOption.class);
        String formattedString = bfo.format(value.getBytes());
        printWriter.append(formattedString);
    }
   
    /** Append the given direct object to the buffer. */
    public void appendPObject(Object value, ServerType type, boolean binary)
            throws IOException {
        if (type.getType().typeClass() instanceof TString && value instanceof String)
        {
            // Optimize the common case of directly encoding a string.
            printWriter.write((String)value);
            return;
        }

        ValueSource source = valuefromObject(value, type);
        appendValue(source, type, binary);
    }

    public ValueSource valuefromObject(Object value, ServerType type) {
        if (value instanceof Date) {
            TInstance dateType = javaDateTInstance(value);
            Value dateValue = new Value(dateType);
            typesTranslator.setTimestampMillisValue(dateValue, ((Date)value).getTime(),
                                                    (value instanceof java.sql.Timestamp) ?
                                                    ((java.sql.Timestamp)value).getNanos() : 0);
            TInstance targetType = type.getType();
            if (dateType.equals(targetType))
                return dateValue;
            TExecutionContext context =
                new TExecutionContext(Collections.singletonList(dateType),
                                      targetType, null);
            Value result = new Value(targetType);
            targetType.typeClass().fromObject(context, dateValue, result);
            return result;
        }
        else {
            // TODO this is inefficient, but I want to get it working.
            return ValueSources.valuefromObject(value, type.getType());
        }
    }
   
    private TInstance javaDateTInstance(Object value) {
        int jdbcType;
        if (value instanceof java.sql.Date) {
            jdbcType = Types.DATE;
        } else if (value instanceof java.sql.Time) {
            jdbcType = Types.TIME;
        } else {
            jdbcType = Types.TIMESTAMP;
        }
        return typesTranslator.typeClassForJDBCType(jdbcType).instance(true);
    }

    public void appendString(String string) throws IOException {
        printWriter.write(string);
    }

    public PrintWriter getWriter() {
        return printWriter;
    }

    /** Adjust milliseconds since 1970-01-01 00:00:00-UTC to seconds since
     * 2000-01-01 00:00:00 timezoneless. A conversion from local time
     * to UTC involves an offset that varies for Summer time. A
     * conversion from local time to timezoneless just removes the
     * zone as though all days were the same length.
     */
    private static long seconds2000NoTZ(long millis) {
        DateTimeZone dtz = DateTimeZone.getDefault();
        millis += dtz.getOffset(millis);
        return millis / 1000 - 946684800; // 2000-01-01 00:00:00-UTC.
    }

    public static int days2000(long millis) {
        long secs = seconds2000NoTZ(millis);
        return (int)(secs / 86400);
    }

    public static int timeSecsNoTZ(long millis) {
        DateTime dt = new DateTime(millis);
        return dt.getSecondOfDay();
    }

    private static final short NUMERIC_POS = 0x0000;
    private static final short NUMERIC_NEG = 0x4000;
    private static final short NUMERIC_NAN = (short)0xC000;

    private static short[] pgNumericVar(BigDecimal n) {
        short ndigits, weight, sign, dscale;
        dscale = (short)n.scale();
        if (dscale < 0) dscale = 0;
        String s = n.toPlainString();
        int lpos = 0;
        sign = NUMERIC_POS;
        if (s.charAt(lpos) == '-') {
            sign = NUMERIC_NEG;
            lpos++;
        }
        int dposl = s.indexOf('.', lpos), dposr;
        if (dposl < 0)
            dposr = dposl = s.length();
        else
            dposr = dposl + 1;
        int nleft = (dposl - lpos + 3) / 4;
        weight = (short)(nleft - 1);
        int nright = (s.length() - dposr + 3) / 4;
        ndigits = (short)(nleft + nright);
        while ((ndigits > 0) && (pgNumericDigit(s, ndigits-1,
                                                lpos, dposl, dposr,
                                                nleft, nright) == 0)) {
            ndigits--;
        }
        short[] digits = new short[ndigits+4];
        digits[0] = ndigits;
        digits[1] = weight;
        digits[2] = sign;
        digits[3] = dscale;
        for (int i = 0; i < ndigits; i++) {
            digits[i + 4] = pgNumericDigit(s, i, lpos, dposl, dposr, nleft, nright);
        }
        return digits;
    }

    private static short pgNumericDigit(String s, int index,
                                        int lpos, int dposl, int dposr,
                                        int nleft, int nright) {
        short result = 0;
        if (index < nleft) {
            int pos = dposl + (index - nleft) * 4;
            for (int i = 0; i < 4; i++) {
                result = (short)(result * 10);
                if (pos >= lpos)
                    result += s.charAt(pos) - '0';
                pos++;
            }
        }
        else {
            int pos = dposr + (index - nleft) * 4;
            for (int i = 0; i < 4; i++) {
                result = (short)(result * 10);
                if (pos < s.length())
                    result += s.charAt(pos) - '0';
                pos++;
            }
        }
        return result;
    }

}
TOP

Related Classes of com.foundationdb.sql.server.ServerValueEncoder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.