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 }