package com.tesora.dve.mysqlapi.repl.messages;
/*
* #%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.buffer.PooledByteBufAllocator;
import io.netty.buffer.Unpooled;
import io.netty.util.CharsetUtil;
import java.math.BigDecimal;
import java.nio.ByteOrder;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import com.tesora.dve.db.mysql.common.MysqlAPIUtils;
import com.tesora.dve.exceptions.PEException;
public class MyUserVarLogEvent extends MyLogEventPacket {
private static final Logger logger = Logger
.getLogger(MyUserVarLogEvent.class);
private static final int DIG_PER_DEC1 = 9;
private static final int dig2bytes[] = { 0, 1, 1, 2, 2, 3, 3, 4, 4, 4 };
int variableNameLen;
String variableName;
byte nullByte;
MyItemResultCode valueType;
int valueCharSet;
int valueLen;
ByteBuf valueBytes;
String variableValue;
public enum MyItemResultCode {
STRING_RESULT((byte) 0x00),
REAL_RESULT((byte) 0x01),
INT_RESULT((byte) 0x02),
ROW_RESULT((byte) 0x03),
DECIMAL_RESULT((byte) 0x04);
private final byte code;
MyItemResultCode(byte b) {
code = b;
}
public static MyItemResultCode fromByte(byte b) {
for (MyItemResultCode mt : values()) {
if (mt.code == b) {
return mt;
}
}
return null;
}
public byte getByteValue() {
return code;
}
}
public MyUserVarLogEvent(MyReplEventCommonHeader ch) {
super(ch);
}
@Override
public void accept(ReplicationVisitorTarget visitorTarget) throws PEException {
visitorTarget.visit((MyUserVarLogEvent)this);
}
@Override
public void unmarshallMessage(ByteBuf cb) throws PEException {
variableNameLen = cb.readInt();
variableName = MysqlAPIUtils.readBytesAsString(cb, variableNameLen, CharsetUtil.UTF_8);
nullByte = cb.readByte();
if (nullByte != 1) {
variableValue = processVariableValue(cb);
} else {
variableValue = "NULL";
}
}
String processVariableValue(ByteBuf cb) throws PEException {
String value = StringUtils.EMPTY;
valueType = MyItemResultCode.fromByte(cb.readByte());
valueCharSet = cb.readInt();
valueLen = cb.readInt();
valueBytes = Unpooled.buffer(cb.readableBytes()).order(ByteOrder.LITTLE_ENDIAN);
valueBytes.writeBytes(cb);
switch(valueType) {
case DECIMAL_RESULT:
value = processDecimalValue(valueBytes, valueLen);
break;
case INT_RESULT:
value = processIntValue(valueBytes, valueLen);
break;
case REAL_RESULT:
value = Double.toString(valueBytes.readDouble());
break;
case STRING_RESULT:
value = "'" + StringUtils.replace(MysqlAPIUtils.readBytesAsString(valueBytes, valueLen, CharsetUtil.UTF_8), "'", "''") + "'";
break;
case ROW_RESULT:
default:
throw new PEException("Unsupported variable type '" + valueType + "' for variable '" + variableName + "'");
}
return value;
}
String processDecimalValue(ByteBuf cb, int valueLen) throws PEException {
String value = StringUtils.EMPTY;
byte precision = cb.readByte();
byte scale = cb.readByte();
int intg = (int) precision - (int) scale;
int intg0 = intg / DIG_PER_DEC1;
int frac0 = (int) scale / DIG_PER_DEC1;
int intg0x = intg - intg0 * DIG_PER_DEC1;
int frac0x = (int) scale - frac0 * DIG_PER_DEC1;
int firstValue = intg0 * 4 + dig2bytes[intg0x];
int secondValue = frac0 * 4 + dig2bytes[frac0x];
int binSize = firstValue + secondValue;
int readableBytes = cb.readableBytes();
if ((firstValue < 1 && secondValue < 1) || readableBytes < binSize) {
throw new PEException("Cannot decode binary decimal");
}
ByteBuf chunk = PooledByteBufAllocator.DEFAULT.heapBuffer(binSize);
cb.readBytes(chunk);
// 1st byte is special cause it determines the sign
byte firstByte = chunk.getByte(0);
int sign = (firstByte & 0x80) == 0x80 ? 1 : -1;
// invert sign
chunk.setByte(0, (firstByte ^ 0x80) );
if (sign == -1) {
// invert all the bytes
for (int i = 0; i < binSize; i++) {
chunk.setByte(i, ~chunk.getByte(i));
}
}
BigDecimal integerPortion = decodeBinDecimal(chunk, firstValue, true);
BigDecimal fractionPortion = decodeBinDecimal(chunk, secondValue, false);
value = ((sign == -1) ? "-" : StringUtils.EMPTY) + integerPortion.toPlainString() + "." + fractionPortion.toPlainString();
return value;
}
BigDecimal decodeBinDecimal(ByteBuf cb, int bufferLen, boolean isIntegerPortion) throws PEException {
BigDecimal decimalPortion = new BigDecimal(0);
if (bufferLen > 0) {
ByteBuf decimalPortionBuf = cb.readBytes(bufferLen);
if (isIntegerPortion) {
int initialBytes = bufferLen % 4;
if (initialBytes > 0) {
long intValue = readValue(decimalPortionBuf, initialBytes);
decimalPortion = BigDecimal.valueOf(intValue);
}
}
int decimalPortionLen = decimalPortionBuf.readableBytes();
while (decimalPortionLen > 0) {
int nextLen = (decimalPortionLen < 4) ? decimalPortionLen : 4;
long intValue = readValue(decimalPortionBuf, nextLen);
if (intValue > 0) {
if (decimalPortion.longValue() == 0) {
decimalPortion = decimalPortion.add(BigDecimal.valueOf(intValue));
} else {
int digits = (int)(Math.log10(intValue)+1);
decimalPortion = decimalPortion.movePointRight(digits).add(BigDecimal.valueOf(intValue));
}
}
decimalPortionLen = decimalPortionBuf.readableBytes();
}
}
return decimalPortion;
}
long readValue(ByteBuf decimalPortionBuf, int valueLen) throws PEException {
if (valueLen < 1 || valueLen > 4) throw new PEException("Cannot decode decimal buffer. Invalid read length of " + valueLen);
long value = 0;
if (valueLen == 4) {
value = decimalPortionBuf.readUnsignedInt();
} else if (valueLen == 3) {
value = decimalPortionBuf.readUnsignedMedium();
} else if (valueLen == 2) {
value = decimalPortionBuf.readUnsignedShort();
} else if (valueLen == 1) {
value = decimalPortionBuf.readUnsignedByte();
}
return value;
}
String processIntValue(ByteBuf cb, int valueLen) throws PEException {
String value = StringUtils.EMPTY;
switch(valueLen) {
case 8:
value = Long.toString(cb.readLong());
break;
case 7:
case 6:
case 5:
throw new PEException("Cannot decode INT value of length '" + valueLen + "' for variable '" + variableName + "'");
case 4:
value = Long.toString(cb.readInt());
break;
case 3:
value = Long.toString(cb.readMedium());
break;
case 2:
value = Long.toString(cb.readShort());
break;
case 1:
value = Byte.toString(cb.readByte());
break;
}
return value;
}
@Override
public void marshallMessage(ByteBuf cb) {
cb.writeInt(variableNameLen);
cb.writeBytes(variableName.getBytes(CharsetUtil.UTF_8));
cb.writeByte(nullByte);
if (nullByte != 1) {
cb.writeByte(valueType.getByteValue());
cb.writeInt(valueCharSet);
cb.writeInt(valueLen);
cb.writeBytes(valueBytes);
}
}
public String getVariableName() {
return variableName;
}
public String getVariableValue() {
return variableValue;
}
}