com.tesora.dve.db.mysql.common.MysqlAPIUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.tesora.dve.db.mysql.common.MysqlAPIUtils.java

Source

package com.tesora.dve.db.mysql.common;

/*
 * #%L
 * Tesora Inc.
 * Database Virtualization Engine
 * %%
 * Copyright (C) 2011 - 2014 Tesora Inc.
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 * 
 * 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/>.
 * #L%
 */

import io.netty.buffer.ByteBuf;
import io.netty.util.CharsetUtil;

import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.sql.Time;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DateUtils;

import com.tesora.dve.exceptions.PEException;

public class MysqlAPIUtils {
    public static short LEN_CODED_8_BITS = (short) 250;
    public static short LEN_CODED_NULL = (short) 251;
    public static short LEN_CODED_16_BITS = (short) 252;
    public static short LEN_CODED_24_BITS = (short) 253;
    public static short LEN_CODED_64_BITS = (short) 254;

    private static int UNSIGNED_SHORT_MAX = 65535;
    private static int UNSIGNED_MEDIUM_MAX = 16777215;

    /**
     * Method to create a Length Coded string that has a Length Code Binary
     * 
     * As described in MySQL internals doc: Length Coded String: a
     * variable-length string. Used instead of Null-Terminated String,
     * especially for character strings which might contain '\0' or might be
     * very long. The first part of a Length Coded String is a Length Coded
     * Binary number (the length); the second part of a Length Coded String is
     * the actual data. An example of a short Length Coded String is these three
     * hexadecimal bytes: 02 61 62, which means "length = 2, contents = 'ab'".
     * 
     * Length Coded Binary: a variable-length number. To compute the value of a
     * Length Coded Binary, one must examine the value of its first byte.
     * 
     * Value Of # Of Bytes Description First Byte Following 0-250 0 = value of
     * first byte 251 0 column value = NULL only appropriate in a Row Data
     * Packet 252 2 = value of following 16-bit word 253 3 = value of following
     * 24-bit word 254 8 = value of following 64-bit word
     * 
     * Thus the length of a Length Coded Binary, including the first byte, will
     * vary from 1 to 9 bytes. The relevant MySQL source program is
     * sql/protocol.cc net_store_length().
     * 
     * All numbers are stored with the least significant byte first. All numbers
     * are unsigned.
     * 
     * @param cb
     *            - ByteBuf - length coded string is written here
     * @param data
     *            - string to put in buffer
     */
    public static void putLengthCodedString(ByteBuf cb, String data, boolean codeNullasZero) {
        putLengthCodedString(cb, ((data == null) ? null : data.getBytes(CharsetUtil.UTF_8)), codeNullasZero);
    }

    public static void putLengthCodedString(ByteBuf cb, byte[] data, boolean codeNullasZero) {
        if (data != null) {
            // need to handle the case of empty string (NULL and empty string are different)
            // mysql puts (byte)0 in the buffer for empty string
            if (data.length > 0) {
                putLengthCodedLong(cb, data.length);
                cb.writeBytes(data);
            } else
                cb.writeZero(1);
        } else {
            if (!codeNullasZero)
                cb.writeByte(LEN_CODED_NULL); // this indicates the string is NULL
            else
                cb.writeZero(1);
        }
    }

    public static void putLengthCodedString(ByteBuf cb, Object obj) {
        if (obj == null) {
            cb.writeByte(LEN_CODED_NULL); // this indicates the string is NULL
        } else if (obj instanceof byte[]) {
            putLengthCodedBinary(cb, (byte[]) obj);
        } else {
            putLengthCodedBinary(cb, obj.toString().getBytes());
        }
    }

    public static void putLengthCodedBinary(ByteBuf cb, byte[] data) {
        final int length = data.length;
        if (length > 0) {
            putLengthCodedLong(cb, length);
            cb.writeBytes(data);
        } else {
            cb.writeZero(1);
        }
    }

    public static void putLengthCodedLong(ByteBuf cb, long length) {
        if (length <= LEN_CODED_8_BITS) {
            cb.writeByte((int) length); // length is 1 byte
        } else if (length <= UNSIGNED_SHORT_MAX) {
            cb.writeByte(LEN_CODED_16_BITS); // length is 2 bytes
            cb.writeShort((int) length);
        } else if (length <= UNSIGNED_MEDIUM_MAX) {
            cb.writeByte(LEN_CODED_24_BITS); // length is 3 bytes
            cb.writeMedium((int) length);
        } else {
            cb.writeByte(LEN_CODED_64_BITS); // length is 8 bytes
            cb.writeLong(length);
        }
    }

    public static long getLengthCodedLong(ByteBuf cb) {
        short byte1 = cb.readUnsignedByte();
        if (byte1 == LEN_CODED_NULL)
            return 0;
        if (byte1 <= LEN_CODED_8_BITS) {
            return byte1;
        } else if (byte1 == LEN_CODED_16_BITS) {
            return cb.readUnsignedShort();
        } else if (byte1 == LEN_CODED_24_BITS)
            return cb.readUnsignedMedium();

        return cb.readLong();
    }

    static boolean isLengthCodedLongNull(ByteBuf cb) {
        boolean isNull = (cb.getUnsignedByte(cb.readerIndex()) == LEN_CODED_NULL);
        if (isNull)
            cb.readUnsignedByte();

        return isNull;
    }

    public static String getLengthCodedString(ByteBuf cb) {
        return getLengthCodedString(cb, CharsetUtil.UTF_8);
    }

    public static String getLengthCodedString(ByteBuf cb, Charset decoder) {

        if (isLengthCodedLongNull(cb))
            return null;

        int length = (int) getLengthCodedLong(cb);
        if (length == 0)
            return StringUtils.EMPTY;

        return cb.readSlice((int) length).toString(decoder);
    }

    /**
     * Returns the offsets of a series of length encoded strings in the provided buffer.  This method does not alter the provided buffer's
     * read/write indexes or capacity .
     * @param cb a ByteBuf containing zero or more length encoded strings.
     * @return the number of length encoded strings.
     */
    public static ArrayList<Integer> locateLengthCodedStrings(ByteBuf cb) {
        return locateLengthCodedStrings(cb, 0);
    }

    public static ArrayList<Integer> locateLengthCodedStrings(ByteBuf cb, int skipAmount) {
        ArrayList<Integer> offsets = new ArrayList<>();
        ByteBuf slice = cb.slice().order(ByteOrder.LITTLE_ENDIAN);
        slice.skipBytes(skipAmount);
        while (slice.isReadable()) {
            offsets.add(slice.readerIndex());
            skipLengthCodedString(slice);
        }
        return offsets;
    }

    public static void skipLengthCodedString(ByteBuf cb) {
        cb.skipBytes((int) getLengthCodedLong(cb));
    }

    public static byte[] getLengthCodedBinary(ByteBuf cb) {

        if (isLengthCodedLongNull(cb))
            return null;

        int length = (int) getLengthCodedLong(cb);
        if (length == 0)
            return ArrayUtils.EMPTY_BYTE_ARRAY;

        return readBytes(cb, length);
    }

    public static byte[] readBytes(ByteBuf cb, int len) {
        byte[] ret = new byte[len];
        cb.readBytes(ret);
        return ret;
    }

    public static byte[] readBytes(ByteBuf cb) {
        return readBytes(cb, cb.readableBytes());
    }

    /**
     * Returns a byte[] that matches the readable content of the provided ByteBuf.  If possible, this method will
     * return a direct reference to the backing array, rather than copy into a new array.  If the readable content
     * is smaller than the backing array, or there is no backing array because the buffer is direct, the contents
     * are copied into a new byte[].  The readIndex and writeIndex of the provided ByteBuf are not modified by this call.
     * @param cb source of bytes
     * @return direct reference to backing byte[] data, or a copy if needed,
     */
    public static byte[] unwrapOrCopyReadableBytes(ByteBuf cb) {
        if (cb.hasArray() && cb.readableBytes() == cb.array().length) {
            //already a heap array, and readable length is entire array
            return cb.array();
        } else {
            byte[] copy = new byte[cb.readableBytes()];
            cb.slice().readBytes(copy);
            return copy;
        }
    }

    public static String readBytesAsString(ByteBuf cb, int len, Charset cs) {
        return cb.readBytes(len).toString(cs);
    }

    public static String readBytesAsString(ByteBuf cb, Charset cs) {
        return readBytesAsString(cb, cb.readableBytes(), cs);
    }

    /**
     * Methods to read and write length coded dates in the Binary Protocol
     * 
     * if year, month, day, hour, minutes, seconds and micro_seconds are all 0,
     * length is 0 and no other field is sent if hour, minutes, seconds and
     * micro_seconds are all 0, length is 4 and no other field is sent if
     * micro_seconds is 0, length is 7 and micro_seconds is not sent otherwise
     * length is 11
     * 
     * Fields 
     *       length (1) -- number of bytes following (valid values: 0, 4, 7, 11) 
     *       year (2) -- year 
     *       month (1) -- month 
     *       day (1) -- day 
     *       hour (1) -- hour
     *       minute (1) -- minutes 
     *       second (1) -- seconds 
     *       micro_second (4) -- micro-seconds
     * 
     */
    public static Date getLengthCodedDate(ByteBuf cb) throws PEException {
        final Calendar cal = Calendar.getInstance();
        cal.clear();

        short length = cb.readUnsignedByte();
        if (length >= 4) {
            cal.set(Calendar.YEAR, cb.readUnsignedShort());
            cal.set(Calendar.MONTH, cb.readByte() - 1); // MONTH is zero based
            cal.set(Calendar.DAY_OF_MONTH, cb.readByte());

            if (length >= 7) {
                cal.set(Calendar.HOUR_OF_DAY, cb.readByte());
                cal.set(Calendar.MINUTE, cb.readByte());
                cal.set(Calendar.SECOND, cb.readByte());
            }

            if (length == 11) {
                long microSeconds = cb.readUnsignedInt();
                cal.set(Calendar.MILLISECOND, (int) (microSeconds / 1000));
            }

            if (length > 11) {
                throw new PEException("Invalid length specified to date type (" + length + ")");
            }

            return cal.getTime();
        }

        return null;
    }

    public static void putLengthCodedDate(ByteBuf cb, Date inDate) {
        byte length = 0;
        Calendar cal = null;
        if (inDate != null) {
            cal = DateUtils.toCalendar(inDate);
            if (cal.get(Calendar.MILLISECOND) > 0) { // indicates we need full 11 byte date
                length = 11;
            } else if (cal.get(Calendar.HOUR_OF_DAY) > 0 || cal.get(Calendar.MINUTE) > 0
                    || cal.get(Calendar.SECOND) > 0) { // this is the 7 byte format
                length = 7;
            } else if (cal.get(Calendar.YEAR) > 0 || cal.get(Calendar.MONTH) > 0
                    || cal.get(Calendar.DAY_OF_MONTH) > 0) {
                length = 4;
            }
        }
        cb.writeByte(length);
        if (length >= 4) {
            cb.writeShort(cal.get(Calendar.YEAR));
            cb.writeByte(cal.get(Calendar.MONTH) + 1); // MONTH is zero based
            cb.writeByte(cal.get(Calendar.DAY_OF_MONTH));
        }
        if (length >= 7) {
            cb.writeByte(cal.get(Calendar.HOUR_OF_DAY));
            cb.writeByte(cal.get(Calendar.MINUTE));
            cb.writeByte(cal.get(Calendar.SECOND));
        }
        if (length == 11) {
            // 1 millisecond = 1000 microseconds, right?
            int microSeconds = cal.get(Calendar.MILLISECOND) * 1000;
            cb.writeInt(microSeconds);
        }
    }

    /**
     * Methods to read and write length coded times in the Binary Protocol
     * 
     * if days, hours, minutes, seconds and micro_seconds are all 0, length is 0
     * and no other field is sent
     * 
     * if micro_seconds is 0, length is 8 and micro_seconds is not sent
     * otherwise length is 12
     * 
     * Fields 
     *       length (1) -- number of bytes following (valid values: 0, 8, 12)
     *       is_negative (1) -- (1 if minus, 0 for plus) 
     *       days (4) -- days 
     *       hours (1) -- hours
     *       minutes (1) -- minutes
     *       seconds (1) -- seconds
     *       micro_seconds (4) -- micro-seconds
     */
    public static Time getLengthCodedTime(ByteBuf cb) throws PEException {
        Calendar cal = Calendar.getInstance();
        cal.clear();
        short length = cb.readUnsignedByte();

        if (length == 0)
            return null;

        Time ret = null;

        if (length >= 8) {
            cb.skipBytes(1); // this is the sign - we are ignoring this for now
            cb.skipBytes(4); // this is "days" - we are ignoring this for now
            cal.set(Calendar.HOUR_OF_DAY, cb.readByte());
            cal.set(Calendar.MINUTE, cb.readByte());
            cal.set(Calendar.SECOND, cb.readByte());
            if (length == 12) {
                long microSeconds = cb.readUnsignedInt();
                cal.set(Calendar.MILLISECOND, (int) (microSeconds / 1000));
            }
            if (length > 12) {
                throw new PEException("Invalid length specified to date type (" + length + ")");
            }

            ret = new Time(cal.getTimeInMillis());
        }
        return ret;
    }

    public static void putLengthCodedTime(ByteBuf cb, Time inTime) {
        byte length = 0;
        Calendar cal = null;
        if (inTime != null) {
            cal = DateUtils.toCalendar(inTime);
            if (cal.get(Calendar.MILLISECOND) > 0) { // indicates we need full 12 byte date
                length = 12;
            } else if (cal.get(Calendar.HOUR_OF_DAY) > 0 || cal.get(Calendar.MINUTE) > 0
                    || cal.get(Calendar.SECOND) > 0) { // this is the 8 byte format
                length = 8;
            }
        }
        cb.writeByte(length);
        if (length >= 8) {
            cb.writeZero(5); // this is the sign and days - we are not supporting this
            cb.writeByte(cal.get(Calendar.HOUR_OF_DAY));
            cb.writeByte(cal.get(Calendar.MINUTE));
            cb.writeByte(cal.get(Calendar.SECOND));
        }
        if (length == 12) {
            // 1 millisecond = 1000 microseconds, right?
            int microSeconds = cal.get(Calendar.MILLISECOND) * 1000;
            cb.writeInt(microSeconds);
        }
    }
}