1 /* 2 * Copyright (C) 2019, HuntLabs 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 * 16 */ 17 module hunt.database.driver.mysql.impl.codec.PrepareStatementCodec; 18 19 import hunt.database.driver.mysql.impl.codec.ColumnDefinition; 20 import hunt.database.driver.mysql.impl.codec.CommandCodec; 21 import hunt.database.driver.mysql.impl.codec.CommandType; 22 import hunt.database.driver.mysql.impl.codec.DataFormat; 23 import hunt.database.driver.mysql.impl.codec.MySQLEncoder; 24 import hunt.database.driver.mysql.impl.codec.MySQLParamDesc; 25 import hunt.database.driver.mysql.impl.codec.MySQLRowDesc; 26 import hunt.database.driver.mysql.impl.codec.MySQLPreparedStatement; 27 import hunt.database.driver.mysql.impl.codec.Packets; 28 29 import hunt.net.buffer.ByteBuf; 30 import hunt.database.base.impl.PreparedStatement; 31 import hunt.database.base.impl.command.CommandResponse; 32 import hunt.database.base.impl.command.PrepareStatementCommand; 33 34 import hunt.logging; 35 import hunt.Exceptions; 36 import hunt.text.Charset; 37 38 /** 39 * 40 */ 41 class PrepareStatementCodec : CommandCodec!(PreparedStatement, PrepareStatementCommand) { 42 43 private CommandHandlerState commandHandlerState = CommandHandlerState.INIT; 44 private long statementId; 45 private int processingIndex; 46 private ColumnDefinition[] paramDescs; 47 private ColumnDefinition[] columnDescs; 48 49 this(PrepareStatementCommand cmd) { 50 super(cmd); 51 } 52 53 override 54 void encode(MySQLEncoder encoder) { 55 super.encode(encoder); 56 sendStatementPrepareCommand(); 57 } 58 59 override 60 void decodePayload(ByteBuf payload, int payloadLength, int sequenceId) { 61 switch (commandHandlerState) { 62 case CommandHandlerState.INIT: 63 int firstByte = payload.getUnsignedByte(payload.readerIndex()); 64 if (firstByte == Packets.ERROR_PACKET_HEADER) { 65 handleErrorPacketPayload(payload); 66 } else { 67 // handle COM_STMT_PREPARE response 68 payload.readUnsignedByte(); // 0x00: OK 69 long statementId = payload.readUnsignedIntLE(); 70 int numberOfColumns = payload.readUnsignedShortLE(); 71 int numberOfParameters = payload.readUnsignedShortLE(); 72 payload.readByte(); // [00] filler 73 int numberOfWarnings = payload.readShortLE(); 74 75 // handle metadata here 76 this.statementId = statementId; 77 this.paramDescs = new ColumnDefinition[numberOfParameters]; 78 this.columnDescs = new ColumnDefinition[numberOfColumns]; 79 80 if (numberOfParameters != 0) { 81 processingIndex = 0; 82 this.commandHandlerState = CommandHandlerState.HANDLING_PARAM_COLUMN_DEFINITION; 83 } else if (numberOfColumns != 0) { 84 processingIndex = 0; 85 this.commandHandlerState = CommandHandlerState.HANDLING_COLUMN_COLUMN_DEFINITION; 86 } else { 87 handleReadyForQuery(); 88 resetIntermediaryResult(); 89 } 90 } 91 break; 92 case CommandHandlerState.HANDLING_PARAM_COLUMN_DEFINITION: 93 paramDescs[processingIndex++] = decodeColumnDefinitionPacketPayload(payload); 94 if (processingIndex == paramDescs.length) { 95 if (isDeprecatingEofFlagEnabled()) { 96 // we enabled the DEPRECATED_EOF flag and don't need to accept an EOF_Packet 97 handleParamDefinitionsDecodingCompleted(); 98 } else { 99 // we need to decode an EOF_Packet before handling rows, to be compatible with MySQL version below 5.7.5 100 commandHandlerState = CommandHandlerState.PARAM_DEFINITIONS_DECODING_COMPLETED; 101 } 102 } 103 break; 104 case CommandHandlerState.PARAM_DEFINITIONS_DECODING_COMPLETED: 105 skipEofPacketIfNeeded(payload); 106 handleParamDefinitionsDecodingCompleted(); 107 break; 108 case CommandHandlerState.HANDLING_COLUMN_COLUMN_DEFINITION: 109 columnDescs[processingIndex++] = decodeColumnDefinitionPacketPayload(payload); 110 if (processingIndex == columnDescs.length) { 111 if (isDeprecatingEofFlagEnabled()) { 112 // we enabled the DEPRECATED_EOF flag and don't need to accept an EOF_Packet 113 handleColumnDefinitionsDecodingCompleted(); 114 } else { 115 // we need to decode an EOF_Packet before handling rows, to be compatible with MySQL version below 5.7.5 116 commandHandlerState = CommandHandlerState.COLUMN_DEFINITIONS_DECODING_COMPLETED; 117 } 118 } 119 break; 120 case CommandHandlerState.COLUMN_DEFINITIONS_DECODING_COMPLETED: 121 handleColumnDefinitionsDecodingCompleted(); 122 break; 123 124 default: 125 warningf("Unhandled state: %d", commandHandlerState); 126 break; 127 } 128 } 129 130 private void sendStatementPrepareCommand() { 131 ByteBuf packet = allocateBuffer(); 132 // encode packet header 133 int packetStartIdx = packet.writerIndex(); 134 packet.writeMediumLE(0); // will set payload length later by calculation 135 packet.writeByte(sequenceId); 136 137 // encode packet payload 138 packet.writeByte(CommandType.COM_STMT_PREPARE); 139 packet.writeCharSequence(cmd.sql(), StandardCharsets.UTF_8); 140 141 // set payload length 142 int payloadLength = packet.writerIndex() - packetStartIdx - 4; 143 packet.setMediumLE(packetStartIdx, payloadLength); 144 145 sendPacket(packet, payloadLength); 146 } 147 148 private void handleReadyForQuery() { 149 if(completionHandler !is null) { 150 completionHandler(succeededResponse!(PreparedStatement)(new MySQLPreparedStatement( 151 cmd.sql(), 152 this.statementId, 153 new MySQLParamDesc(paramDescs), 154 new MySQLRowDesc(columnDescs, DataFormat.BINARY)))); 155 } 156 } 157 158 private void resetIntermediaryResult() { 159 commandHandlerState = CommandHandlerState.INIT; 160 statementId = 0; 161 processingIndex = 0; 162 paramDescs = null; 163 columnDescs = null; 164 } 165 166 private void handleParamDefinitionsDecodingCompleted() { 167 if (columnDescs.length == 0) { 168 handleReadyForQuery(); 169 resetIntermediaryResult(); 170 } else { 171 processingIndex = 0; 172 this.commandHandlerState = CommandHandlerState.HANDLING_COLUMN_COLUMN_DEFINITION; 173 } 174 } 175 176 private void handleColumnDefinitionsDecodingCompleted() { 177 handleReadyForQuery(); 178 resetIntermediaryResult(); 179 } 180 181 } 182 183 184 private enum CommandHandlerState { 185 INIT, 186 HANDLING_PARAM_COLUMN_DEFINITION, 187 PARAM_DEFINITIONS_DECODING_COMPLETED, 188 HANDLING_COLUMN_COLUMN_DEFINITION, 189 COLUMN_DEFINITIONS_DECODING_COMPLETED 190 }