/** * <pre> * 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 this program; * if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * </pre> */ package com.meidusa.amoeba.mysql.net.packet; import java.io.UnsupportedEncodingException; import java.util.Arrays; import java.util.Map; import org.apache.log4j.Logger; import com.meidusa.amoeba.mysql.handler.PreparedStatmentInfo; import com.meidusa.amoeba.mysql.jdbc.MysqlDefs; import com.meidusa.amoeba.net.packet.AbstractPacketBuffer; import com.meidusa.amoeba.util.StringFillFormat; /** * <pre> * Bytes Name * ----- ---- * 1 code * 4 statement_id * 1 flags * 4 iteration_count * (param_count+7)/8 null_bit_map * 1 new_parameter_bound_flag ���Ϊ1��ʾpreparedStatment�в����󶨣�������Ϊ0 * n*2 type of parameters (only if new_params_bound = 1) * * code: always COM_EXECUTE * * statement_id: statement identifier * * flags: reserved for future use. In MySQL 4.0, always 0. * In MySQL 5.0: * 0: CURSOR_TYPE_NO_CURSOR * 1: CURSOR_TYPE_READ_ONLY * 2: CURSOR_TYPE_FOR_UPDATE * 4: CURSOR_TYPE_SCROLLABLE * * iteration_count: reserved for future use. Currently always 1. * * null_bit_map: A bitmap indicating parameters that are NULL. * Bits are counted from LSB, using as many bytes * as necessary ((param_count+7)/8) * i.e. if the first parameter (parameter 0) is NULL, then * the least significant bit in the first byte will be 1. * * new_parameter_bound_flag: Contains 1 if this is the first time * that "execute" has been called, or if * the parameters have been rebound. * * type: Occurs once for each parameter that is not NULL. * The highest significant bit of this 16-bit value * encodes the unsigned property. The other 15 bits * are reserved for the type (only 8 currently used). * This block is sent when parameters have been rebound * or when a prepared statement is executed for the * first time. * </pre> * * @author <a href=mailto:piratebase@sina.com>Struct chen</a> * @author hexianmao */ public class ExecutePacket extends CommandPacket { private static Logger logger = Logger.getLogger(ExecutePacket.class); public long statementId; public byte flags; public long iterationCount; public byte newParameterBoundFlag; public BindValue[] values; protected transient int parameterCount; protected transient PreparedStatmentInfo pInfo; private Map<Integer, Object> longPrameters; public ExecutePacket(PreparedStatmentInfo pInfo, Map<Integer, Object> longPrameters){ this.pInfo = pInfo; this.parameterCount = pInfo.getParameterCount(); this.values = new BindValue[parameterCount]; this.longPrameters = longPrameters; } public static long readStatmentID(byte[] buffer) { byte[] b = buffer; int position = 5; return ((long) b[position++] & 0xff) | (((long) b[position++] & 0xff) << 8) | ((long) (b[position++] & 0xff) << 16) | ((long) (b[position++] & 0xff) << 24); } @Override public void init(AbstractPacketBuffer myBuffer) { super.init(myBuffer); MysqlPacketBuffer buffer = (MysqlPacketBuffer) myBuffer; statementId = buffer.readLong(); flags = buffer.readByte(); iterationCount = buffer.readLong(); if(parameterCount >0){ int nullCount = (this.parameterCount + 7) / 8; byte[] nullBitsBuffer = new byte[nullCount]; for (int i = 0; i < nullCount; i++) { nullBitsBuffer[i] = buffer.readByte(); } newParameterBoundFlag = buffer.readByte(); for (int i = 0; i < this.parameterCount; i++) { if (values[i] == null) { values[i] = new BindValue(); } } // ��newParameterBoundFlag=1ʱ������pInfo.parameterTypesֵ // ����ʹ��pInfo.parameterTypes��Ϊvalues[i].bufferType��ֵ if (newParameterBoundFlag == (byte) 1) { int[] bindTypes = new int[parameterCount]; for (int i = 0; i < parameterCount; i++) { bindTypes[i] = buffer.readInt(); values[i].bufferType = bindTypes[i]; } pInfo.setParameterTypes(bindTypes); } else { int[] bindTypes = pInfo.getParameterTypes(); for (int i = 0; i < parameterCount; i++) { values[i].bufferType = bindTypes[i]; } } for (int i = 0; i < this.parameterCount; i++) { if (longPrameters != null && longPrameters.get(i) != null) { values[i].isLongData = true; values[i].value = longPrameters.get(i); } else { if ((nullBitsBuffer[i / 8] & (1 << (i & 7))) != 0) { values[i].isNull = true; } else { PacketUtil.readBindValue(buffer, values[i]); } } } } } @Override public void write2Buffer(AbstractPacketBuffer myBuffer) throws UnsupportedEncodingException { super.write2Buffer(myBuffer); MysqlPacketBuffer buffer = (MysqlPacketBuffer) myBuffer; buffer.writeLong(statementId); buffer.writeByte(flags); buffer.writeLong(iterationCount); buffer.writeByte(newParameterBoundFlag); int nullCount = (this.parameterCount + 7) / 8; int nullBitsPosition = buffer.getPosition(); for (int i = 0; i < nullCount; i++) { buffer.writeByte((byte) 0); } byte[] nullBitsBuffer = new byte[nullCount]; if (newParameterBoundFlag == (byte) 1) { for (int i = 0; i < this.parameterCount; i++) { buffer.writeInt(this.values[i].bufferType); } } for (int i = 0; i < this.parameterCount; i++) { if (!this.values[i].isLongData) { if (!this.values[i].isNull) { PacketUtil.storeBinding(buffer, this.values[i], buffer.getCharset()); } else { nullBitsBuffer[i / 8] |= (1 << (i & 7)); } } } int endPosition = buffer.getPosition(); buffer.setPosition(nullBitsPosition); buffer.writeBytesNoNull(nullBitsBuffer); buffer.setPosition(endPosition); } public Object[] getParameters() { Object[] result = new Object[values.length]; int index = 0; for (BindValue bindValue : values) { if (bindValue.isNull) { index++; } else { switch (bindValue.bufferType) { case MysqlDefs.FIELD_TYPE_TINY: result[index++] = bindValue.byteBinding; break; case MysqlDefs.FIELD_TYPE_SHORT: result[index++] = bindValue.shortBinding; break; case MysqlDefs.FIELD_TYPE_LONG: result[index++] = bindValue.longBinding; break; case MysqlDefs.FIELD_TYPE_LONGLONG: result[index++] = bindValue.longBinding; break; case MysqlDefs.FIELD_TYPE_FLOAT: result[index++] = bindValue.floatBinding; break; case MysqlDefs.FIELD_TYPE_DOUBLE: result[index++] = bindValue.doubleBinding; break; case MysqlDefs.FIELD_TYPE_TIME: result[index++] = bindValue.value; break; case MysqlDefs.FIELD_TYPE_DATE: case MysqlDefs.FIELD_TYPE_DATETIME: case MysqlDefs.FIELD_TYPE_TIMESTAMP: result[index++] = bindValue.value; break; case MysqlDefs.FIELD_TYPE_VAR_STRING: case MysqlDefs.FIELD_TYPE_STRING: case MysqlDefs.FIELD_TYPE_VARCHAR: case MysqlDefs.FIELD_TYPE_DECIMAL: case MysqlDefs.FIELD_TYPE_NEW_DECIMAL: case MysqlDefs.FIELD_TYPE_TINY_BLOB: case MysqlDefs.FIELD_TYPE_BLOB: result[index++] = bindValue.value; break; case MysqlDefs.FIELD_TYPE_NULL: index++; break; default: { index++; logger.error("error jdbc type:" + bindValue.bufferType); } } } } return result; } protected int calculatePacketSize() { int packLength = super.calculatePacketSize(); packLength += 10;// 4 + 1 + 4 + 1; return packLength; } @Override public String toString() { StringBuilder s = new StringBuilder(); s.append("Length=").append(StringFillFormat.format(packetLength, 4)); s.append(", PacketId=").append(StringFillFormat.format(packetId, 2)); s.append(", Command=").append(StringFillFormat.format(command, 2)); s.append(", StatementId=").append(StringFillFormat.format(statementId, 2)); return s.toString(); } public static void main(String[] args) { int parameterCount = 12; int nullCount = (parameterCount + 7) / 8; byte[] nullBitsBuffer = new byte[nullCount]; for (int i = 0; i < parameterCount; i++) { nullBitsBuffer[i / 8] |= (1 << (i & 7)); } System.out.println(Arrays.toString(nullBitsBuffer)); } }