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 com.tesora.dve.db.mysql.MyFieldType;
import com.tesora.dve.db.mysql.MysqlNativeConstants;
import com.tesora.dve.resultset.ColumnSet;
import io.netty.buffer.ByteBuf;
import io.netty.util.CharsetUtil;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.time.FastDateFormat;
import com.tesora.dve.exceptions.PEException;
import com.tesora.dve.resultset.ColumnMetadata;
public class DBTypeBasedUtils {
// private static final Logger logger = Logger.getLogger(DBTypeBasedUtils.class);
public static Map<Integer, DataTypeValueFunc> SQLTypeValueFuncMap =
new HashMap<Integer, DataTypeValueFunc>();
public static Map<MyFieldType, DataTypeValueFunc> MysqlTypeValueFuncMap =
new HashMap<MyFieldType, DataTypeValueFunc>();
public static Map<Class<?>, DataTypeValueFunc> JavaTypeValueFuncMap =
new HashMap<Class<?>, DataTypeValueFunc>();
static {
SQLTypeValueFuncMap.put(Types.BIT, new BitValueFunc());
SQLTypeValueFuncMap.put(Types.TINYINT, new ByteValueFunc());
SQLTypeValueFuncMap.put(Types.SMALLINT, new ShortValueFunc());
SQLTypeValueFuncMap.put(Types.INTEGER, new IntegerValueFunc());
SQLTypeValueFuncMap.put(Types.BIGINT, new LongValueFunc());
SQLTypeValueFuncMap.put(Types.FLOAT, new FloatValueFunc());
SQLTypeValueFuncMap.put(Types.DOUBLE, new DoubleValueFunc());
SQLTypeValueFuncMap.put(Types.REAL, new FloatValueFunc());
SQLTypeValueFuncMap.put(Types.NUMERIC, new DecimalValueFunc());
SQLTypeValueFuncMap.put(Types.DECIMAL, new DecimalValueFunc());
SQLTypeValueFuncMap.put(Types.CHAR, new CharValueFunc());
SQLTypeValueFuncMap.put(Types.VARCHAR, new CharValueFunc());
SQLTypeValueFuncMap.put(Types.LONGVARCHAR, new CharValueFunc());
SQLTypeValueFuncMap.put(Types.DATE, new DateValueFunc());
SQLTypeValueFuncMap.put(Types.TIME, new TimeValueFunc());
SQLTypeValueFuncMap.put(Types.TIMESTAMP, new TimestampValueFunc());
}
static {
MysqlTypeValueFuncMap.put(MyFieldType.FIELD_TYPE_BIT, new BitValueFunc());
MysqlTypeValueFuncMap.put(MyFieldType.FIELD_TYPE_TINY, new TinyValueFunc());
MysqlTypeValueFuncMap.put(MyFieldType.FIELD_TYPE_DECIMAL, new DecimalValueFunc());
MysqlTypeValueFuncMap.put(MyFieldType.FIELD_TYPE_DOUBLE, new DoubleValueFunc());
MysqlTypeValueFuncMap.put(MyFieldType.FIELD_TYPE_FLOAT, new FloatValueFunc());
MysqlTypeValueFuncMap.put(MyFieldType.FIELD_TYPE_INT24, new MediumValueFunc());
MysqlTypeValueFuncMap.put(MyFieldType.FIELD_TYPE_LONG, new IntegerValueFunc());
MysqlTypeValueFuncMap.put(MyFieldType.FIELD_TYPE_LONGLONG, new LongValueFunc());
MysqlTypeValueFuncMap.put(MyFieldType.FIELD_TYPE_NEWDECIMAL, new DecimalValueFunc());
MysqlTypeValueFuncMap.put(MyFieldType.FIELD_TYPE_DECIMAL, new DecimalValueFunc());
MysqlTypeValueFuncMap.put(MyFieldType.FIELD_TYPE_SHORT, new ShortValueFunc());
MysqlTypeValueFuncMap.put(MyFieldType.FIELD_TYPE_NULL, new NullValueFunc());
MysqlTypeValueFuncMap.put(MyFieldType.FIELD_TYPE_VARCHAR, new CharValueFunc());
MysqlTypeValueFuncMap.put(MyFieldType.FIELD_TYPE_VAR_STRING, new CharValueFunc());
MysqlTypeValueFuncMap.put(MyFieldType.FIELD_TYPE_STRING, new CharValueFunc());
MysqlTypeValueFuncMap.put(MyFieldType.FIELD_TYPE_BLOB, new CharValueFunc());
MysqlTypeValueFuncMap.put(MyFieldType.FIELD_TYPE_LONG_BLOB, new CharValueFunc());
MysqlTypeValueFuncMap.put(MyFieldType.FIELD_TYPE_MEDIUM_BLOB, new CharValueFunc());
MysqlTypeValueFuncMap.put(MyFieldType.FIELD_TYPE_TINY_BLOB, new CharValueFunc());
MysqlTypeValueFuncMap.put(MyFieldType.FIELD_TYPE_NEWDATE, new DateValueFunc());
MysqlTypeValueFuncMap.put(MyFieldType.FIELD_TYPE_DATE, new DateValueFunc());
MysqlTypeValueFuncMap.put(MyFieldType.FIELD_TYPE_TIME, new TimeValueFunc());
MysqlTypeValueFuncMap.put(MyFieldType.FIELD_TYPE_DATETIME, new DateTimeValueFunc());
MysqlTypeValueFuncMap.put(MyFieldType.FIELD_TYPE_TIMESTAMP, new TimestampValueFunc());
}
static {
for ( DataTypeValueFunc dtvf : SQLTypeValueFuncMap.values() ) {
if ( dtvf.getJavaClass() != null ) {
JavaTypeValueFuncMap.put( dtvf.getJavaClass(), dtvf);
}
}
}
static DataTypeValueFunc BINARY_VALUE_FUNC = new BinaryValueFunc();
static DataTypeValueFunc BYTE_VALUE_FUNC = new ByteValueFunc();
public static DataTypeValueFunc getJavaTypeFunc(Class<?> clazz) {
if (!JavaTypeValueFuncMap.containsKey(clazz))
throw new IllegalArgumentException("Java Type " + clazz + " not found in type map");
return JavaTypeValueFuncMap.get(clazz);
}
public static DataTypeValueFunc getMysqlTypeFunc(MyFieldType type) throws PEException {
return getMysqlTypeFunc(type, 0, 0);
}
public static DataTypeValueFunc getMysqlTypeFunc(MyFieldType type, int length, int flags) throws PEException {
// Special case for binary() and tinyint - unfortunately...
if (MyFieldType.FIELD_TYPE_STRING.equals(type) && (flags & MysqlNativeConstants.FLDPKT_FLAG_BINARY) > 0)
return BINARY_VALUE_FUNC;
else if (MyFieldType.FIELD_TYPE_TINY.equals(type) && length == 1)
return BYTE_VALUE_FUNC;
if (!MysqlTypeValueFuncMap.containsKey(type))
throw new PEException("Mysql type " + type + " not found in type map");
return MysqlTypeValueFuncMap.get(type);
}
public static DataTypeValueFunc getMysqlTypeFunc(ColumnMetadata columnMetadata) throws PEException {
return getMysqlTypeFunc(MyFieldType.fromByte(columnMetadata.getNativeTypeId()), columnMetadata.getSize(), columnMetadata.getNativeTypeFlags());
}
public static List<DataTypeValueFunc> getMysqlTypeFunctions(ColumnSet columns) throws PEException {
List<DataTypeValueFunc> funcs = new ArrayList<>();
for (ColumnMetadata meta : columns.getColumnList())
funcs.add(getMysqlTypeFunc(meta));
return funcs;
}
public static DataTypeValueFunc getSQLTypeFunc(int type) throws PEException {
if (!SQLTypeValueFuncMap.containsKey(type))
throw new PEException("SQL type " + type + " not found in type map");
return SQLTypeValueFuncMap.get(type);
}
/**
* The BIT data type is used to store bit-field values. A type of BIT(M)
* enables storage of M-bit values. M can range from 1 to 64.
* MySQL Binary Protocol handles BIT data as length encoded strings.
*
* @see http://dev.mysql.com/doc/refman/5.6/en/bit-type.html
* @see https://dev.mysql.com/doc/internals/en/binary-protocol-value.html
*/
static class BitValueFunc implements DataTypeValueFunc {
@Override
public void writeObject(ByteBuf cb, Object value) {
if (value instanceof Boolean) {
MysqlAPIUtils.putLengthCodedString(cb, BooleanUtils.toIntegerObject((Boolean) value));
} else {
MysqlAPIUtils.putLengthCodedString(cb, value);
}
}
@Override
public Object readObject(ByteBuf cb) {
return MysqlAPIUtils.getLengthCodedBinary(cb);
}
@Override
public String getParamReplacement(Object value, boolean pstmt) {
return new String((byte[]) value, CharsetUtil.ISO_8859_1);
}
@Override
public String getMysqlTypeName() {
return "BIT";
}
@Override
public Object convertStringToObject(String value, ColumnMetadata colMd) throws PEException {
return value.getBytes(CharsetUtil.ISO_8859_1);
}
@Override
public Class<?> getJavaClass() {
return byte[].class;
}
@Override
public MyFieldType getMyFieldType() {
return MyFieldType.FIELD_TYPE_BIT;
}
}
static class ByteValueFunc implements DataTypeValueFunc {
@Override
public void writeObject(ByteBuf cb, Object value) {
byte byteValue=0;
if ( value instanceof Byte )
byteValue = (Byte) value;
else if ( value instanceof Integer )
byteValue = ((Integer) value).byteValue();
else
throw new IllegalArgumentException("Type " + value.getClass().getSimpleName() + " not handled by ByteValueFunc");
cb.writeByte(byteValue);
}
@Override
public Object readObject(ByteBuf cb) {
return cb.readByte();
}
@Override
public String getParamReplacement(Object value, boolean pstmt) {
return value.toString();
}
@Override
public String getMysqlTypeName() {
return "BYTE";
}
@Override
public Object convertStringToObject(String value, ColumnMetadata colMd) throws PEException {
return Byte.valueOf(value);
}
@Override
public Class<?> getJavaClass() {
return Byte.class;
}
@Override
public MyFieldType getMyFieldType() {
return MyFieldType.FIELD_TYPE_TINY;
}
}
static class TinyValueFunc extends ByteValueFunc {
@Override
public Object readObject(ByteBuf cb) {
return Integer.valueOf(cb.readByte());
}
@Override
public Class<?> getJavaClass() {
return null;
}
}
static class ShortValueFunc implements DataTypeValueFunc {
@Override
public void writeObject(ByteBuf cb, Object value) {
short shortValue=0;
if ( value instanceof Short )
shortValue = (Short) value;
else if ( value instanceof Integer )
shortValue = ((Integer) value).shortValue();
else
throw new IllegalArgumentException("Type " + value.getClass().getSimpleName() + " not handled by ShortValueFunc");
cb.writeShort(shortValue);
}
@Override
public Object readObject(ByteBuf cb) {
return cb.readShort();
}
@Override
public String getParamReplacement(Object value, boolean pstmt) {
return ((Short) value).toString();
}
@Override
public String getMysqlTypeName() {
return "SHORT";
}
@Override
public Object convertStringToObject(String value, ColumnMetadata colMd) throws PEException {
// This is so that we match the behaviour of the mysql jdbc driver (so Proxy-based tests
// will pass.
if ( colMd.getSize() >= 5 )
return Integer.valueOf(value);
return Short.valueOf(value);
}
@Override
public Class<?> getJavaClass() {
return Short.class;
}
@Override
public MyFieldType getMyFieldType() {
return MyFieldType.FIELD_TYPE_SHORT;
}
}
static class CharValueFunc implements DataTypeValueFunc {
@Override
public void writeObject(ByteBuf cb, Object value) {
MysqlAPIUtils.putLengthCodedString(cb, (String) value, false);
}
@Override
public Object readObject(ByteBuf cb) {
return MysqlAPIUtils.getLengthCodedString(cb);
}
@Override
public String getParamReplacement(Object value, boolean pstmt) {
// escape any embedded single quotes that are not already escaped
String in = (String) value;
String out = null;
if (pstmt) {
out = escape(in);
} else {
StringBuilder buf = new StringBuilder(in.length() + 2);
buf.append("'").append(in).append("'");
out = buf.toString();
}
return out;
}
@Override
public String getMysqlTypeName() {
return "CHAR";
}
@Override
public Object convertStringToObject(String value, ColumnMetadata colMd) throws PEException {
return value;
}
@Override
public Class<?> getJavaClass() {
return String.class;
}
@Override
public MyFieldType getMyFieldType() {
return MyFieldType.FIELD_TYPE_VAR_STRING;
}
}
public static String escape(String in) {
int len = in.length();
StringBuilder out = new StringBuilder((int) (len * 1.1));
out.append("'");
if (len == 0) {
out.append("'");
return out.toString();
}
char p = in.charAt(0);
if (p == '\'' || p == '\\') {
out.append('\\');
}
out.append(p);
for(int i = 1; i < len; i++) {
char c = in.charAt(i);
if ((c == '\'' || c == '\\') /*&& p != '\\'*/)
out.append('\\');
out.append(c);
p = c;
}
out.append("'");
return out.toString();
}
static class BinaryValueFunc implements DataTypeValueFunc {
@Override
public void writeObject(ByteBuf cb, Object value) {
MysqlAPIUtils.putLengthCodedString(cb, value);
}
@Override
public Object readObject(ByteBuf cb) {
return MysqlAPIUtils.getLengthCodedBinary(cb);
}
@Override
public String getParamReplacement(Object value, boolean pstmt) {
return "'" + value + "'";
}
@Override
public String getMysqlTypeName() {
return "BINARY";
}
@Override
public Object convertStringToObject(String value, ColumnMetadata colMd) throws PEException {
return value.getBytes();
}
@Override
public Class<?> getJavaClass() {
return byte[].class;
}
@Override
public MyFieldType getMyFieldType() {
return MyFieldType.FIELD_TYPE_BLOB;
}
}
static class IntegerValueFunc implements DataTypeValueFunc {
@Override
public void writeObject(ByteBuf cb, Object value) {
if ( value instanceof Long )
cb.writeInt(((Long) value).intValue());
else
cb.writeInt((Integer) value);
}
@Override
public Object readObject(ByteBuf cb) {
return cb.readInt();
}
@Override
public String getParamReplacement(Object value, boolean pstmt) {
if (value instanceof Long) {
return ((Long) value).toString();
}
return ((Integer) value).toString();
}
@Override
public String getMysqlTypeName() {
return "INT";
}
@Override
public Object convertStringToObject(String value, ColumnMetadata colMd) throws PEException {
if ( colMd.isUnsigned() )
return Long.valueOf(value);
return Integer.valueOf(value);
}
@Override
public Class<?> getJavaClass() {
return Integer.class;
}
@Override
public MyFieldType getMyFieldType() {
return MyFieldType.FIELD_TYPE_LONG;
}
}
static class MediumValueFunc implements DataTypeValueFunc {
@Override
public void writeObject(ByteBuf cb, Object value) {
cb.writeInt((Integer) value);
}
@Override
public Object readObject(ByteBuf cb) {
return cb.readInt();
}
@Override
public String getParamReplacement(Object value, boolean pstmt) {
return ((Integer) value).toString();
}
@Override
public String getMysqlTypeName() {
return "INT24";
}
@Override
public Object convertStringToObject(String value, ColumnMetadata colMd) throws PEException {
return Integer.valueOf(value);
}
@Override
public Class<?> getJavaClass() {
return null;
}
@Override
public MyFieldType getMyFieldType() {
return MyFieldType.FIELD_TYPE_INT24;
}
}
static class LongValueFunc implements DataTypeValueFunc {
@Override
public void writeObject(ByteBuf cb, Object value) {
if ( value instanceof BigInteger )
cb.writeLong(((BigInteger) value).longValue());
else
cb.writeLong((Long) value);
}
@Override
public Object readObject(ByteBuf cb) {
return cb.readLong();
}
@Override
public String getParamReplacement(Object value, boolean pstmt) {
if (value instanceof BigInteger) {
return ((BigInteger) value).toString();
}
return ((Long) value).toString();
}
@Override
public String getMysqlTypeName() {
return "LONGLONG";
}
@Override
public Object convertStringToObject(String value, ColumnMetadata colMd) throws PEException {
return Long.valueOf(value);
}
@Override
public Class<?> getJavaClass() {
return Long.class;
}
@Override
public MyFieldType getMyFieldType() {
return MyFieldType.FIELD_TYPE_LONGLONG;
}
}
static class FloatValueFunc implements DataTypeValueFunc {
@Override
public void writeObject(ByteBuf cb, Object value) {
cb.writeFloat((Float) value);
}
@Override
public Object readObject(ByteBuf cb) {
return cb.readFloat();
}
@Override
public String getParamReplacement(Object value, boolean pstmt) {
return ((Float) value).toString();
}
@Override
public String getMysqlTypeName() {
return "FLOAT";
}
@Override
public Object convertStringToObject(String value, ColumnMetadata colMd) throws PEException {
return Float.valueOf(value);
}
@Override
public Class<?> getJavaClass() {
return Float.class;
}
@Override
public MyFieldType getMyFieldType() {
return MyFieldType.FIELD_TYPE_FLOAT;
}
}
static class DoubleValueFunc implements DataTypeValueFunc {
@Override
public void writeObject(ByteBuf cb, Object value) {
cb.writeDouble((Double) value);
}
@Override
public Object readObject(ByteBuf cb) {
return cb.readDouble();
}
@Override
public String getParamReplacement(Object value, boolean pstmt) {
return ((Double) value).toString();
}
@Override
public String getMysqlTypeName() {
return "DOUBLE";
}
@Override
public Object convertStringToObject(String value, ColumnMetadata colMd) throws PEException {
return Double.valueOf(value);
}
@Override
public Class<?> getJavaClass() {
return Double.class;
}
@Override
public MyFieldType getMyFieldType() {
return MyFieldType.FIELD_TYPE_DOUBLE;
}
}
static class DecimalValueFunc implements DataTypeValueFunc {
@Override
public void writeObject(ByteBuf cb, Object value) {
MysqlAPIUtils.putLengthCodedString(cb, ((BigDecimal) value).toPlainString(), false);
}
@Override
public Object readObject(ByteBuf cb) {
return new BigDecimal(MysqlAPIUtils.getLengthCodedString(cb));
}
@Override
public String getParamReplacement(Object value, boolean pstmt) {
return ((BigDecimal) value).toPlainString();
}
@Override
public String getMysqlTypeName() {
return "DECIMAL";
}
@Override
public Object convertStringToObject(String value, ColumnMetadata colMd) throws PEException {
return new BigDecimal(value);
}
@Override
public Class<?> getJavaClass() {
return BigDecimal.class;
}
@Override
public MyFieldType getMyFieldType() {
return MyFieldType.FIELD_TYPE_DECIMAL;
}
}
static class NullValueFunc implements DataTypeValueFunc {
@Override
public void writeObject(ByteBuf cb, Object value) {
cb.writeZero(1);
}
@Override
public Object readObject(ByteBuf cb) {
return null;
}
@Override
public String getParamReplacement(Object value, boolean pstmt) {
return "null";
}
@Override
public String getMysqlTypeName() {
return "NULL";
}
@Override
public Object convertStringToObject(String value, ColumnMetadata colMd) throws PEException {
return null;
}
@Override
public Class<?> getJavaClass() {
return null;
}
@Override
public MyFieldType getMyFieldType() {
return MyFieldType.FIELD_TYPE_NULL;
}
}
static class DateValueFunc implements DataTypeValueFunc {
@Override
public void writeObject(ByteBuf cb, Object value) {
MysqlAPIUtils.putLengthCodedDate(cb, (Date) value);
}
@Override
public Object readObject(ByteBuf cb) throws PEException {
return MysqlAPIUtils.getLengthCodedDate(cb);
}
@Override
public String getParamReplacement(Object value, boolean pstmt) {
FastDateFormat fdf = FastDateFormat.getInstance(MysqlNativeConstants.MYSQL_DATE_FORMAT);
return "'" + fdf.format((Date) value) + "'";
}
@Override
public String getMysqlTypeName() {
return "DATE";
}
@Override
public Object convertStringToObject(String value, ColumnMetadata colMd) throws PEException {
Date dateValue = null;
// Alas, the FastDateFormat doesn't support parse...
try {
dateValue = new SimpleDateFormat(MysqlNativeConstants.MYSQL_DATE_FORMAT, Locale.ENGLISH).parse(value);
} catch (ParseException e) {
throw new PEException("Cannot convert value to Date type", e);
}
return dateValue;
}
@Override
public Class<?> getJavaClass() {
return Date.class;
}
@Override
public MyFieldType getMyFieldType() {
return MyFieldType.FIELD_TYPE_DATE;
}
}
static class DateTimeValueFunc extends DateValueFunc {
@Override
public String getParamReplacement(Object value, boolean pstmt) {
FastDateFormat fdf = FastDateFormat.getInstance(MysqlNativeConstants.MYSQL_DATETIME_FORMAT);
return "'" + fdf.format((Date) value) + "'";
}
@Override
public String getMysqlTypeName() {
return "DATETIME";
}
@Override
public Object convertStringToObject(String value, ColumnMetadata colMd) throws PEException {
return Timestamp.valueOf(value);
}
@Override
public MyFieldType getMyFieldType() {
return MyFieldType.FIELD_TYPE_DATETIME;
}
}
static class TimestampValueFunc extends DateValueFunc {
@Override
public String getParamReplacement(Object value, boolean pstmt) {
FastDateFormat fdf = FastDateFormat.getInstance(MysqlNativeConstants.MYSQL_TIMESTAMP_FORMAT);
return "'" + fdf.format((Date) value) + "'";
}
@Override
public String getMysqlTypeName() {
return "TIMESTAMP";
}
@Override
public Object convertStringToObject(String value, ColumnMetadata colMd) throws PEException {
return Timestamp.valueOf(value);
}
@Override
public Class<?> getJavaClass() {
return Timestamp.class;
}
@Override
public MyFieldType getMyFieldType() {
return MyFieldType.FIELD_TYPE_TIMESTAMP;
}
}
static class TimeValueFunc implements DataTypeValueFunc {
@Override
public void writeObject(ByteBuf cb, Object value) {
MysqlAPIUtils.putLengthCodedTime(cb, (Time) value);
}
@Override
public Object readObject(ByteBuf cb) throws PEException {
return MysqlAPIUtils.getLengthCodedTime(cb);
}
@Override
public String getParamReplacement(Object value, boolean pstmt) {
FastDateFormat fdf = FastDateFormat.getInstance(MysqlNativeConstants.MYSQL_TIME_FORMAT_MS);
return "'" + fdf.format((Time) value) + "'";
}
@Override
public String getMysqlTypeName() {
return "TIME";
}
@Override
public Object convertStringToObject(String value, ColumnMetadata colMd) throws PEException {
return Time.valueOf(value);
}
@Override
public Class<?> getJavaClass() {
return Time.class;
}
@Override
public MyFieldType getMyFieldType() {
return MyFieldType.FIELD_TYPE_TIME;
}
}
}