package com.tesora.dve.db.mysql.libmy; /* * #%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 com.tesora.dve.db.mysql.MyFieldType; import com.tesora.dve.db.mysql.common.MysqlAPIUtils; import io.netty.buffer.Unpooled; import io.netty.util.ReferenceCountUtil; import java.nio.ByteOrder; public class MyFieldPktResponse extends MyResponseMessage { public static final int CHARSET_OFFSET_RELATIVE_TO_EOF = -12;//delta applied to payload length to get to first fixed length type field. enum CacheState { NOT_CACHED, PACKED, UNPACKED} //variable length name fields. private String catalog = "def"; private String database; private String table; private String orig_table; private String column; private String orig_column; //fixed length type fields. private byte charset; private Integer column_length; private MyFieldType column_type; private short flags = 0; private byte scale; private String defaultValue=null; CacheState state = CacheState.NOT_CACHED; ByteBuf cachedBuffer = Unpooled.EMPTY_BUFFER; @Override public void marshallMessage(ByteBuf cb) { if (state == CacheState.NOT_CACHED){ ByteBuf newCache = Unpooled.buffer().order(ByteOrder.LITTLE_ENDIAN); fullPack(newCache); updateCache(newCache); } cb.writeBytes(cachedBuffer.slice()); } public void fullPack(ByteBuf cb) { MysqlAPIUtils.putLengthCodedString(cb, catalog, true); MysqlAPIUtils.putLengthCodedString(cb, database, true); MysqlAPIUtils.putLengthCodedString(cb, table, true); MysqlAPIUtils.putLengthCodedString(cb, orig_table, true); MysqlAPIUtils.putLengthCodedString(cb, column, true); MysqlAPIUtils.putLengthCodedString(cb, orig_column, true); // The "spec" I used said this next byte was "filler", thru investigation // we determined that it is the number of bytes to the end of // the packet (not including the default). It seems to be always 12 cb.writeByte((byte) 12); cb.writeByte(charset); cb.writeZero(1); cb.writeInt(column_length); cb.writeByte(column_type.getByteValue()); cb.writeShort(flags); cb.writeByte(scale); cb.writeZero(2); } @Override public void unmarshallMessage(ByteBuf cb) { ByteBuf newCache = Unpooled.buffer(cb.readableBytes()).order(ByteOrder.LITTLE_ENDIAN); newCache.writeBytes( cb ); updateCache(newCache); //parsing variable length name info is expensive, only unpack fixed length type info by default. unpackTypeInfo(cachedBuffer.slice()); state = CacheState.PACKED; } protected void updateCache(ByteBuf newCache) { ByteBuf oldCache = cachedBuffer; cachedBuffer = newCache; ReferenceCountUtil.release(oldCache); } private void unpackTypeInfo(ByteBuf cb) { int remainingPayload = cb.readableBytes(); int charsetOffset = remainingPayload + CHARSET_OFFSET_RELATIVE_TO_EOF; cb.skipBytes(charsetOffset); charset = cb.readByte(); cb.skipBytes(1); column_length = cb.readInt();//TODO: this should be an unsigned int. Will cause problems if length exceeds Integer.MAX_VALUE. column_type = MyFieldType.fromByte(cb.readByte()); flags = cb.readShort(); scale = cb.readByte(); //plus two pad bytes of zero } private void unpackNameInfo(ByteBuf cb) { catalog = MysqlAPIUtils.getLengthCodedString(cb); database = MysqlAPIUtils.getLengthCodedString(cb); table = MysqlAPIUtils.getLengthCodedString(cb); orig_table = MysqlAPIUtils.getLengthCodedString(cb); column = MysqlAPIUtils.getLengthCodedString(cb); orig_column = MysqlAPIUtils.getLengthCodedString(cb); cb.skipBytes(1); } protected void ensureAllFieldsAreReadable(){ if (state == CacheState.PACKED){ //type info is unpacked by default, unpack the name info here. unpackNameInfo(cachedBuffer.slice()); state = CacheState.UNPACKED; } } protected void ensureNotCached(){ if (state == CacheState.NOT_CACHED) return; ensureAllFieldsAreReadable();//unpack any unread fields so we don't lose them. updateCache(Unpooled.EMPTY_BUFFER); state = CacheState.NOT_CACHED; } @Override public MyMessageType getMessageType() { return MyMessageType.FIELDPKT_RESPONSE; } public String getCatalog() { ensureAllFieldsAreReadable(); return catalog; } public void setCatalog(String catalog) { ensureNotCached(); this.catalog = catalog; } public String getDatabase() { ensureAllFieldsAreReadable(); return database; } public void setDatabase(String database) { ensureNotCached(); this.database = database; } public String getTable() { ensureAllFieldsAreReadable(); return table; } public void setTable(String table) { ensureNotCached(); this.table = table; } public String getOrig_table() { ensureAllFieldsAreReadable(); return orig_table; } public void setOrig_table(String orig_table) { ensureNotCached(); this.orig_table = orig_table; } public String getColumn() { ensureAllFieldsAreReadable(); return column; } public void setColumn(String column) { ensureNotCached(); this.column = column; } public String getOrig_column() { ensureAllFieldsAreReadable(); return orig_column; } public void setOrig_column(String orig_column) { ensureNotCached(); this.orig_column = orig_column; } public byte getCharset() { //fixed length type field, it is unpacked by default. return charset; } public void setCharset(byte serverCharset) { ensureNotCached(); this.charset = serverCharset; } public int getColumn_length() { //fixed length type field, it is unpacked by default. return column_length; } public void setColumn_length(int column_length) { ensureNotCached(); this.column_length = column_length; } public MyFieldType getColumn_type() { //fixed length type field, it is unpacked by default. return column_type; } public void setColumn_type(MyFieldType fieldTypeVarString) { ensureNotCached(); this.column_type = fieldTypeVarString; } public short getFlags() { //fixed length type field, it is unpacked by default. return flags; } public void setFlags(short flags) { ensureNotCached(); this.flags = flags; } public byte getScale() { //fixed length type field, it is unpacked by default. return scale; } public void setScale(byte scale) { ensureNotCached(); this.scale = scale; } public void setFlags(Integer flags) { //delegate call, cache control handled there. setFlags( flags.shortValue() ); } public void setScale(Integer scale) { //delegate call, cache control handled there. setScale(scale.byteValue()); } public String getDefaultValue() { //not persisted field, no cache affect. return defaultValue; } public void setDefaultValue(String defaultValue) { //not a persisted field, no cache affect this.defaultValue = defaultValue; } public static class Factory { public MyFieldPktResponse newInstance() { return new MyFieldPktResponse(); } } }