Package org.voltdb

Source Code of org.voltdb.ParameterSet$OneParamInfo

/* This file is part of VoltDB.
* Copyright (C) 2008-2014 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 <http://www.gnu.org/licenses/>.
*/

package org.voltdb;

import java.io.IOException;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.util.Arrays;

import org.json_voltpatches.JSONArray;
import org.json_voltpatches.JSONException;
import org.json_voltpatches.JSONObject;
import org.json_voltpatches.JSONString;
import org.json_voltpatches.JSONStringer;
import org.voltdb.common.Constants;
import org.voltdb.types.TimestampType;
import org.voltdb.types.VoltDecimalHelper;
import org.voltdb.utils.SerializationHelper;

/**
* The ordered set of parameters of the proper types that is passed into
* a stored procedure OR a plan fragment.
*
*/
public class ParameterSet implements JSONString {

    static final byte ARRAY = -99;

    static class OneParamInfo {
        Object value;
        byte[] encodedString;
        byte[][] encodedStringArray;
    }

    private final Object m_params[];

    /*
     * The same ParameterSet instance could be accessed by multiple threads to
     * serialize the parameters. These two member variables keeps the encoded
     * copy of strings and string arrays besides the decoded versions in
     * m_params. There should only be one thread that adds the encoded strings.
     * Once created, they are not mutated. So it should be safe to not
     * synchronize on them.
     */
    private final byte[][] m_encodedStrings;
    private final byte[][][] m_encodedStringArrays;
    // memoized serialized size (start assuming valid size for empty ParameterSet)
    private final int m_serializedSize;

    public static ParameterSet emptyParameterSet() {
        return fromArrayNoCopy();
    }

    public static ParameterSet fromArrayWithCopy(Object... params) {
        return fromArrayNoCopy(params.clone());
    }

    public static ParameterSet fromArrayNoCopy(Object... params) {
        byte[][][] encodedStringArrays = new byte[params.length][][];
        byte[][] encodedStrings = new byte[params.length][];

        int size = 2;

        for (int ii = 0; ii < params.length; ii++) {
            Object obj = params[ii];
            if ((obj == null) || (obj == JSONObject.NULL)) {
                size++;
                continue;
            }
            size += 1;//everything has a type even arrays and null
            Class<?> cls = obj.getClass();
            if (cls.isArray()) {

                if (obj instanceof byte[]) {
                    final byte[] b = (byte[]) obj;
                    size += 4 + b.length;
                    continue;
                }

                VoltType type;
                try {
                    type = VoltType.typeFromClass(cls.getComponentType());
                }
                catch (VoltTypeException e) {
                    obj = getAKosherArray((Object[]) obj);
                    cls = obj.getClass();
                    type = VoltType.typeFromClass(cls.getComponentType());
                }

                size +=  1 + 2;// component type, array length
                switch (type) {
                    case SMALLINT:
                        size += 2 * ((short[])obj).length;
                        break;
                    case INTEGER:
                        size += 4 * ((int[])obj).length;
                        break;
                    case BIGINT:
                        size += 8 * ((long[])obj).length;
                        break;
                    case FLOAT:
                        size += 8 * ((double[])obj).length;
                        break;
                    case STRING:
                        String strings[] = (String[]) obj;
                        byte arrayEncodedStrings[][] = new byte[strings.length][];
                        for (int zz = 0; zz < strings.length; zz++) {
                            if (strings[zz] == null) {
                                size += 4;
                            } else {
                                arrayEncodedStrings[zz] = strings[zz].getBytes(Constants.UTF8ENCODING);
                                size += 4 + arrayEncodedStrings[zz].length;
                            }
                        }
                        encodedStringArrays[ii] = arrayEncodedStrings;
                        break;
                    case TIMESTAMP:
                        size += 8 * ((TimestampType[])obj).length;
                        break;
                    case DECIMAL:
                        size += 16 * ((BigDecimal[])obj).length;
                        break;
                    case VOLTTABLE:
                        for (VoltTable vt : (VoltTable[]) obj) {
                            size += vt.getSerializedSize();
                        }
                        break;
                    case VARBINARY:
                        for (byte[] buf : (byte[][]) obj) {
                            size += 4; // length prefix
                            if (buf != null) {
                                size += buf.length;
                            }
                        }
                        break;
                    default:
                        throw new RuntimeException("FIXME: Unsupported type " + type);
                }
                continue;
            }

            // Handle NULL mappings not encoded by type.min_value convention
            if (obj == VoltType.NULL_TIMESTAMP) {
                size += 8;
                continue;
            }
            else if (obj == VoltType.NULL_STRING_OR_VARBINARY) {
                size += 4;
                continue;
            }
            else if (obj == VoltType.NULL_DECIMAL) {
                size += 16;
                continue;
            }

            VoltType type = VoltType.typeFromClass(cls);
            switch (type) {
                case TINYINT:
                    size++;
                    break;
                case SMALLINT:
                    size += 2;
                    break;
                case INTEGER:
                    size += 4;
                    break;
                case BIGINT:
                    size += 8;
                    break;
                case FLOAT:
                    size += 8;
                    break;
                case STRING:
                    byte encodedString[] = ((String)obj).getBytes(Constants.UTF8ENCODING);
                    size += 4 + encodedString.length;
                    encodedStrings[ii] = encodedString;
                    break;
                case TIMESTAMP:
                    size += 8;
                    break;
                case DECIMAL:
                    size += 16;
                    break;
                case VOLTTABLE:
                    size += ((VoltTable) obj).getSerializedSize();
                    break;
                default:
                    throw new RuntimeException("FIXME: Unsupported type " + type);
            }
        }

        return new ParameterSet(params, size, encodedStrings, encodedStringArrays);
    }

    public static ParameterSet fromJSONString(String json) throws JSONException, IOException {
        JSONArray jArray = new JSONArray(json);
        return fromJSONArray(jArray);
    }

    public static ParameterSet fromJSONArray(JSONArray paramArray) throws JSONException, IOException {
        Object[] params = new Object[paramArray.length()];
        for (int i = 0; i < paramArray.length(); i++) {
            params[i] = paramFromPossibleJSON(paramArray.get(i));
        }
        return fromArrayNoCopy(params);
    }

    public static ParameterSet fromByteBuffer(ByteBuffer buffer) throws IOException {
        int startPos = buffer.position();

        short count = buffer.getShort();
        Object[] params = new Object[count];
        byte[][] encodedStrings = null;
        byte[][][] encodedStringArrays = null;

        for (int i = 0; i < count; ++i) {
            OneParamInfo opi = readOneParameter(buffer);
            params[i] = opi.value;
            if (opi.encodedString != null) {
                if (encodedStrings == null) {
                    encodedStrings = new byte[count][];
                }
                encodedStrings[i] = opi.encodedString;
            }
            if (opi.encodedStringArray != null) {
                if (encodedStringArrays == null) {
                    encodedStringArrays = new byte[count][][];
                }
                encodedStringArrays[i] = opi.encodedStringArray;
            }
        }

        int size = buffer.position() - startPos;

        return new ParameterSet(params, size, encodedStrings, encodedStringArrays);
    }

    private ParameterSet(Object[] params, int serializedSize, byte[][] encodedStrings, byte[][][] encodedStringArrays) {
        m_params = params;
        m_serializedSize = serializedSize;
        m_encodedStrings = encodedStrings;
        m_encodedStringArrays = encodedStringArrays;
    }

    static Object limitType(Object o) {
        Class<?> ctype = o.getClass();
        if (ctype == Integer.class) {
            return ((Integer) o).longValue();
        }

        return o;
    }

    public Object getParam(int index) {
        return m_params[index];
    }

    /**
     * Returns a copy of the parameter array
     * @return
     */
    public Object[] toArray() {
        return m_params.clone();
    }

    public int size() {
        return m_params.length;
    }

    public int getSerializedSize() {
        assert(m_serializedSize >= 2);
        return m_serializedSize;
    }

    public ByteBuffer serialize() {
        return null;
    }

    /*
     * Get a single indexed parameter. No size limits are enforced.
     * Do not use for large strings or varbinary (> 1MB).
     */
    static Object getParameterAtIndex(int partitionIndex, ByteBuffer unserializedParams) throws IOException {
        int paramLen = unserializedParams.getShort();
        if (partitionIndex >= paramLen) {
            // error if caller desires out of bounds parameter
            throw new RuntimeException("Invalid partition parameter requested.");
        }
        for (int i = 0; i < partitionIndex; ++i) {
            readOneParameter(unserializedParams);
        }
        OneParamInfo opi = readOneParameter(unserializedParams);
        unserializedParams.rewind();
        return opi.value;
    }

    static Object getAKosherArray(Object[] array) {
        int tables = 0;
        int integers = 0;
        int strings = 0;
        int doubles = 0;
        int nulls = 0;

        // handle empty arrays (too bad this is ints...)
        if (array.length == 0)
            return new int[0];

        // A note on Object[] null handling.  For most object array types, nulls can be bassed in as 'null' and/or
        // the VoltType Null equivalent.  Note that Object[] containing Strings supports null and VoltType nulls
        // as array elements.  But any other Sigil type class (big decimal, timestamp, varbinary)
        // DO NOT support nulls or VoltType nulls in Object[] arrays.  Also note that currently we do not
        // support timestamp or varbinary in Object arrays.  Future work...

        // first pass counts value types
        for (int i = 0; i < array.length; i++) {
            if (array[i] instanceof VoltTable) tables++;
            else if (array[i] instanceof Double) doubles++;
            else if (array[i] instanceof Float) doubles++;
            else if (array[i] instanceof Byte) integers++;
            else if (array[i] instanceof Short) integers++;
            else if (array[i] instanceof Integer) integers++;
            else if (array[i] instanceof Long) integers++;
            else if (array[i] instanceof String) strings++;
            else if (array[i] == VoltType.NULL_STRING_OR_VARBINARY) nulls++;
            else if (null == array[i]) nulls++;  // Handle nulls in an Object array.  Note only support nulls in STRING type, later we'll reject all other null usage.
            else {
                String msg = String.format("Type %s not supported in parameter set arrays.",
                                        array[i].getClass().toString());
                throw new RuntimeException(msg);
            }
        }

        // validate and choose type

        if (tables > 0) {
            if ((integers + strings + doubles) > 0) {
                String msg = "Cannot mix tables and other types in parameter set arrays.";
                throw new RuntimeException(msg);
            }
            assert(tables == array.length);
            VoltTable[] retval = new VoltTable[tables];
            for (int i = 0; i < array.length; i++)
                retval[i] = (VoltTable) array[i];
            return retval;
        }

        // note: there can't be any tables past this point

        // Verify that we don't have all null values.
        if (nulls == array.length)
            throw new RuntimeException("Unable to determine type. Parameter set array contains all NULL values.");

        if (strings > 0) {
            if ((integers + doubles) > 0) {
                String msg = "Cannot mix strings and numbers in parameter set arrays.";
                throw new RuntimeException(msg);
            }
            assert((strings + nulls) == array.length);
            String[] retval = new String[array.length];
            for (int i = 0; i < array.length; i++)
            {
                if (array[i] == VoltType.NULL_STRING_OR_VARBINARY) {
                    array[i] = null;
                }
                retval[i] = (String) array[i];
            }
            return retval;
        }

        // note: there can't be any strings or nulls past this point

        if (nulls > 0) {
            String msg = "Cannot mix numerics and nulls in parameter set arrays.";
            throw new RuntimeException(msg);
        }

        if (doubles > 0) {
            // note, ok to mix integers and doubles
            assert((doubles + integers) == array.length);
            double[] retval = new double[array.length];
            for (int i = 0; i < array.length; i++) {
                Number numberval = (Number) array[i];
                retval[i] = numberval.doubleValue();
            }
            return retval;
        }

        // all ints from here out

        assert(integers == array.length);
        long[] retval = new long[array.length];
        for (int i = 0; i < array.length; i++) {
            Number numberval = (Number) array[i];
            retval[i] = numberval.longValue();
        }
        return retval;
    }

    @Override
    public String toString() {
        StringBuilder b = new StringBuilder();
        b.append("ParameterSet:");
        for (int i = 0; i < m_params.length; ++i) {
            b.append(",param[" + i + "]=" + (m_params[i] == null ? "NULL"
                    : m_params[i].toString() + "(" + m_params[i].getClass().getName() + ")"));
        }
        return new String(b);
    }

    @Override
    public String toJSONString() {
        JSONStringer js = new JSONStringer();
        try {
            js.array();
            for (Object o : m_params) {
                js.value(o);
            }
            js.endArray();
        }
        catch (JSONException e) {
            e.printStackTrace();
            throw new RuntimeException("Failed to serialize a parameter set to JSON.", e);
        }
        return js.toString();
    }

    static Object paramFromPossibleJSON(Object value) throws JSONException, IOException {
        if (value instanceof JSONObject) {
            JSONObject jsonObj = (JSONObject) value;
            return VoltTable.fromJSONObject(jsonObj);
        }
        if (value instanceof JSONArray) {
            JSONArray array = (JSONArray) value;
            Object[] retval = new Object[array.length()];
            for (int i = 0; i < array.length(); i++) {
                Object valueAtIndex = array.get(i);
                retval[i] = paramFromPossibleJSON(valueAtIndex);
            }
            return retval;
        }
        return value;
    }

    static private OneParamInfo readOneParameter(ByteBuffer in)
            throws IOException {
        Object value;
        int len;
        byte[] encodedString = null;
        byte[][] encodedStringArray = null;

        byte nextTypeByte = in.get();
        if (nextTypeByte == ARRAY) {
            VoltType nextType = null;
            byte etype = in.get();
            try {
                nextType = VoltType.get(etype);
            } catch (AssertionError ae) {
                throw new RuntimeException("ParameterSet doesn't support type " + etype);
            }
            if (nextType == null) {
                value = null;
            }
            else if (nextType == VoltType.STRING) {
                encodedStringArray = (byte[][]) SerializationHelper.readArray(byte[].class, in);
                String[] sval = new String[encodedStringArray.length];
                for (int i = 0; i < encodedStringArray.length; ++i) {
                    if (encodedStringArray[i] == null) {
                        sval[i] = null;
                    }
                    else {
                        sval[i] = new String(encodedStringArray[i], Constants.UTF8ENCODING);
                    }
                }
                value = sval;
            }
            else {
                value = SerializationHelper.readArray(nextType.classFromType(), in);
            }
        }
        else {
            VoltType nextType;
            try {
                nextType = VoltType.get(nextTypeByte);
            } catch (AssertionError ae) {
                throw new RuntimeException("ParameterSet doesn't support type " + nextTypeByte);
            }
            switch (nextType) {
                case NULL:
                    value = null;
                    break;
                case TINYINT:
                    value = in.get();
                    break;
                case SMALLINT:
                    value = in.getShort();
                    break;
                case INTEGER:
                    value = in.getInt();
                    break;
                case BIGINT:
                    value = in.getLong();
                    break;
                case FLOAT:
                    value = in.getDouble();
                    break;
                case STRING:
                    len = in.getInt();
                    if (len == VoltType.NULL_STRING_LENGTH) {
                        value = VoltType.NULL_STRING_OR_VARBINARY;
                    }
                    else {
                        encodedString = new byte[len];
                        in.get(encodedString);
                        value = new String(encodedString, Constants.UTF8ENCODING);
                    }
                    break;
                case VARBINARY:
                    len = in.getInt();
                    if (len == VoltType.NULL_STRING_LENGTH) {
                        value = VoltType.NULL_STRING_OR_VARBINARY;
                    }
                    else {
                        encodedString = new byte[len];
                        in.get(encodedString);
                        value = encodedString;
                    }
                    break;
                case TIMESTAMP:
                    final long micros = in.getLong();
                    value = new TimestampType(micros);
                    break;
                case VOLTTABLE:
                    final int tableSize = in.getInt();
                    byte[] tableBytes = new byte[tableSize];
                    in.get(tableBytes);
                    value = PrivateVoltTableFactory.createVoltTableFromBuffer(ByteBuffer.wrap(tableBytes), false);
                    break;
                case DECIMAL: {
                    BigDecimal decimal_val = SerializationHelper.getBigDecimal(in);
                    if (decimal_val == null) {
                        value = VoltType.NULL_DECIMAL;
                    }
                    else {
                        value = decimal_val;
                    }
                    break;
                }
                default:
                    throw new RuntimeException("ParameterSet doesn't support type " + nextType);
            }
        }

        OneParamInfo retval = new OneParamInfo();
        retval.value = value;
        retval.encodedString = encodedString;
        retval.encodedStringArray = encodedStringArray;
        return retval;
    }

    public void flattenToBuffer(ByteBuffer buf) throws IOException {

        buf.putShort((short)m_params.length);

        for (int i = 0; i < m_params.length; i++) {
            Object obj = m_params[i];
            if ((obj == null) || (obj == JSONObject.NULL)) {
                VoltType type = VoltType.NULL;
                buf.put(type.getValue());
                continue;
            }

            Class<?> cls = obj.getClass();
            if (cls.isArray()) {

                // Since arrays of bytes could be varbinary or strings,
                // and they are the only kind of array needed by the EE,
                // special case them as the VARBINARY type.
                if (obj instanceof byte[]) {
                    final byte[] b = (byte[]) obj;
                    // commented out this bit... presumably the EE will do this check upon recipt
                    /*if (b.length > VoltType.MAX_VALUE_LENGTH) {
                        throw new IOException("Value of byte[] larger than allowed max string or varbinary " + VoltType.MAX_VALUE_LENGTH_STR);
                    }*/
                    buf.put(VoltType.VARBINARY.getValue());
                    buf.putInt(b.length);
                    buf.put(b);
                    continue;
                }

                buf.put(ARRAY);

                VoltType type;
                try {
                    type = VoltType.typeFromClass(cls.getComponentType());
                }
                catch (VoltTypeException e) {
                    obj = getAKosherArray((Object[]) obj);
                    cls = obj.getClass();
                    type = VoltType.typeFromClass(cls.getComponentType());
                }

                buf.put(type.getValue());
                switch (type) {
                    case SMALLINT:
                        SerializationHelper.writeArray((short[]) obj, buf);
                        break;
                    case INTEGER:
                        SerializationHelper.writeArray((int[]) obj, buf);
                        break;
                    case BIGINT:
                        SerializationHelper.writeArray((long[]) obj, buf);
                        break;
                    case FLOAT:
                        SerializationHelper.writeArray((double[]) obj, buf);
                        break;
                    case STRING:
                        if (m_encodedStringArrays[i] == null) {
                            // should not happen
                            throw new IOException("String array not encoded");
                        }
                        // This check used to be done by FastSerializer.writeArray(), but things changed?
                        if (m_encodedStringArrays[i].length > Short.MAX_VALUE) {
                            throw new IOException("Array exceeds maximum length of "
                                                  + Short.MAX_VALUE + " bytes");
                        }
                        buf.putShort((short)m_encodedStringArrays[i].length);
                        for (int zz = 0; zz < m_encodedStringArrays[i].length; zz++) {
                            SerializationHelper.writeVarbinary(m_encodedStringArrays[i][zz], buf);
                        }
                        break;
                    case TIMESTAMP:
                        SerializationHelper.writeArray((TimestampType[]) obj, buf);
                        break;
                    case DECIMAL:
                        // converted long128 in serializer api
                        SerializationHelper.writeArray((BigDecimal[]) obj, buf);
                        break;
                    case VOLTTABLE:
                        SerializationHelper.writeArray((VoltTable[]) obj, buf);
                        break;
                    case VARBINARY:
                        SerializationHelper.writeArray((byte[][]) obj, buf);
                        break;
                    default:
                        throw new RuntimeException("FIXME: Unsupported type " + type);
                }
                continue;
            }

            // Handle NULL mappings not encoded by type.min_value convention
            if (obj == VoltType.NULL_TIMESTAMP) {
                buf.put(VoltType.TIMESTAMP.getValue());
                buf.putLong(VoltType.NULL_BIGINT)// corresponds to EE value.h isNull()
                continue;
            }
            else if (obj == VoltType.NULL_STRING_OR_VARBINARY) {
                buf.put(VoltType.STRING.getValue());
                buf.putInt(VoltType.NULL_STRING_LENGTH);
                continue;
            }
            else if (obj == VoltType.NULL_DECIMAL) {
                buf.put(VoltType.DECIMAL.getValue());
                VoltDecimalHelper.serializeNull(buf);
                continue;
            }

            VoltType type = VoltType.typeFromClass(cls);
            buf.put(type.getValue());
            switch (type) {
                case TINYINT:
                    buf.put((Byte)obj);
                    break;
                case SMALLINT:
                    buf.putShort((Short)obj);
                    break;
                case INTEGER:
                    buf.putInt((Integer) obj);
                    break;
                case BIGINT:
                    buf.putLong((Long) obj);
                    break;
                case FLOAT:
                    if (cls == Float.class)
                        buf.putDouble(((Float) obj).doubleValue());
                    else if (cls == Double.class)
                        buf.putDouble(((Double) obj).doubleValue());
                    else
                        throw new RuntimeException("Can't cast parameter type to Double");
                    break;
                case STRING:
                    if (m_encodedStrings[i] == null) {
                        // should not happen
                        throw new IOException("String not encoded: " + (String) obj);
                    }
                    SerializationHelper.writeVarbinary(m_encodedStrings[i], buf);
                    break;
                case TIMESTAMP:
                    long micros = timestampToMicroseconds(obj);
                    buf.putLong(micros);
                    break;
                case DECIMAL:
                    VoltDecimalHelper.serializeBigDecimal((BigDecimal)obj, buf);
                    break;
                case VOLTTABLE:
                    ((VoltTable)obj).flattenToBuffer(buf);
                    break;
                default:
                    throw new RuntimeException("FIXME: Unsupported type " + type);
            }
        }
    }

    static long timestampToMicroseconds(Object obj) {
        long micros = 0;
        // Adapt the Java standard classes' millisecond count to TIMESTAMP's microseconds.
        if (obj instanceof java.util.Date) {
            micros = ((java.util.Date) obj).getTime()*1000;
            // For Timestamp, also preserve exactly the right amount of fractional second precision.
            if (obj instanceof java.sql.Timestamp) {
                long nanos = ((java.sql.Timestamp) obj).getNanos();
                // This may be slightly controversial, but...
                // Throw a conversion error rather than silently rounding/dropping sub-microsecond precision.
                if ((nanos % 1000) != 0) {
                    throw new RuntimeException("Can't serialize TIMESTAMP value with fractional microseconds");
                }
                // Use MOD 1000000 to prevent double-counting of milliseconds which figure into BOTH getTime() and getNanos().
                // DIVIDE nanoseconds by 1000 to get microseconds.
                micros += ((nanos % 1000000)/1000);
            }
        } else if (obj instanceof TimestampType) {
            // Let this throw a cast exception if obj is not actually a TimestampType instance.
            micros = ((TimestampType) obj).getTime();
        }
        return micros;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof ParameterSet)) {
            return false;
        }
        ParameterSet other = (ParameterSet) obj;
        return Arrays.deepEquals(m_params, other.m_params);
    }

    /* (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        assert false : "hashCode not designed";
        return 42; // any arbitrary constant will do
    }
}
TOP

Related Classes of org.voltdb.ParameterSet$OneParamInfo

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.