package io.vertx.mysqlclient.impl.codec;
import io.netty.buffer.ByteBuf;
import io.vertx.mysqlclient.impl.MySQLParamDesc;
import io.vertx.mysqlclient.impl.MySQLRowDesc;
import io.vertx.mysqlclient.impl.datatype.DataFormat;
import io.vertx.mysqlclient.impl.protocol.ColumnDefinition;
import io.vertx.mysqlclient.impl.protocol.CommandType;
import io.vertx.sqlclient.impl.PreparedStatement;
import io.vertx.sqlclient.impl.command.CommandResponse;
import io.vertx.sqlclient.impl.command.PrepareStatementCommand;
import static io.vertx.mysqlclient.impl.protocol.Packets.ERROR_PACKET_HEADER;
class PrepareStatementCodec extends CommandCodec<PreparedStatement, PrepareStatementCommand> {
private CommandHandlerState commandHandlerState = CommandHandlerState.INIT;
private long statementId;
private int processingIndex;
private ColumnDefinition[] paramDescs;
private ColumnDefinition[] columnDescs;
PrepareStatementCodec(PrepareStatementCommand cmd) {
super(cmd);
}
@Override
void encode(MySQLEncoder encoder) {
super.encode(encoder);
sendStatementPrepareCommand();
}
@Override
void decodePayload(ByteBuf payload, int payloadLength) {
switch (commandHandlerState) {
case INIT:
int firstByte = payload.getUnsignedByte(payload.readerIndex());
if (firstByte == ERROR_PACKET_HEADER) {
handleErrorPacketPayload(payload);
} else {
payload.readUnsignedByte();
long statementId = payload.readUnsignedIntLE();
int numberOfColumns = payload.readUnsignedShortLE();
int numberOfParameters = payload.readUnsignedShortLE();
payload.readByte();
int numberOfWarnings = payload.readShortLE();
this.statementId = statementId;
this.paramDescs = new ColumnDefinition[numberOfParameters];
this.columnDescs = new ColumnDefinition[numberOfColumns];
if (numberOfParameters != 0) {
processingIndex = 0;
this.commandHandlerState = CommandHandlerState.HANDLING_PARAM_COLUMN_DEFINITION;
} else if (numberOfColumns != 0) {
processingIndex = 0;
this.commandHandlerState = CommandHandlerState.HANDLING_COLUMN_COLUMN_DEFINITION;
} else {
handleReadyForQuery();
resetIntermediaryResult();
}
}
break;
case HANDLING_PARAM_COLUMN_DEFINITION:
paramDescs[processingIndex++] = decodeColumnDefinitionPacketPayload(payload);
if (processingIndex == paramDescs.length) {
if (isDeprecatingEofFlagEnabled()) {
handleParamDefinitionsDecodingCompleted();
} else {
commandHandlerState = CommandHandlerState.PARAM_DEFINITIONS_DECODING_COMPLETED;
}
}
break;
case PARAM_DEFINITIONS_DECODING_COMPLETED:
skipEofPacketIfNeeded(payload);
handleParamDefinitionsDecodingCompleted();
break;
case HANDLING_COLUMN_COLUMN_DEFINITION:
columnDescs[processingIndex++] = decodeColumnDefinitionPacketPayload(payload);
if (processingIndex == columnDescs.length) {
if (isDeprecatingEofFlagEnabled()) {
handleColumnDefinitionsDecodingCompleted();
} else {
commandHandlerState = CommandHandlerState.COLUMN_DEFINITIONS_DECODING_COMPLETED;
}
}
break;
case COLUMN_DEFINITIONS_DECODING_COMPLETED:
handleColumnDefinitionsDecodingCompleted();
break;
}
}
private void sendStatementPrepareCommand() {
ByteBuf packet = allocateBuffer();
int packetStartIdx = packet.writerIndex();
packet.writeMediumLE(0);
packet.writeByte(sequenceId);
packet.writeByte(CommandType.COM_STMT_PREPARE);
packet.writeCharSequence(cmd.sql(), encoder.encodingCharset);
int payloadLength = packet.writerIndex() - packetStartIdx - 4;
packet.setMediumLE(packetStartIdx, payloadLength);
sendPacket(packet, payloadLength);
}
private void handleReadyForQuery() {
completionHandler.handle(CommandResponse.success(new MySQLPreparedStatement(
cmd.sql(),
this.statementId,
new MySQLParamDesc(paramDescs),
new MySQLRowDesc(columnDescs, DataFormat.BINARY),
!cmd.isManaged())));
}
private void resetIntermediaryResult() {
commandHandlerState = CommandHandlerState.INIT;
statementId = 0;
processingIndex = 0;
paramDescs = null;
columnDescs = null;
}
private void handleParamDefinitionsDecodingCompleted() {
if (columnDescs.length == 0) {
handleReadyForQuery();
resetIntermediaryResult();
} else {
processingIndex = 0;
this.commandHandlerState = CommandHandlerState.HANDLING_COLUMN_COLUMN_DEFINITION;
}
}
private void handleColumnDefinitionsDecodingCompleted() {
handleReadyForQuery();
resetIntermediaryResult();
}
private enum CommandHandlerState {
INIT,
HANDLING_PARAM_COLUMN_DEFINITION,
PARAM_DEFINITIONS_DECODING_COMPLETED,
HANDLING_COLUMN_COLUMN_DEFINITION,
COLUMN_DEFINITIONS_DECODING_COMPLETED
}
}