1 module hunt.database.driver.mysql.impl.codec.ExtendedQueryCommandBaseCodec;
2 
3 import hunt.database.driver.mysql.impl.codec.ColumnDefinition;
4 import hunt.database.driver.mysql.impl.codec.CommandType;
5 import hunt.database.driver.mysql.impl.codec.DataFormat;
6 import hunt.database.driver.mysql.impl.codec.DataType;
7 import hunt.database.driver.mysql.impl.codec.DataTypeCodec;
8 import hunt.database.driver.mysql.impl.codec.DataTypeDesc;
9 import hunt.database.driver.mysql.impl.codec.MySQLPreparedStatement;
10 import hunt.database.driver.mysql.impl.codec.Packets;
11 import hunt.database.driver.mysql.impl.codec.QueryCommandBaseCodec;
12 
13 import hunt.database.driver.mysql.impl.MySQLCollation;
14 import hunt.database.base.Tuple;
15 import hunt.database.base.impl.command.ExtendedQueryCommandBase;
16 
17 import hunt.Exceptions;
18 import hunt.net.buffer.ByteBuf;
19 import hunt.text.Charset;
20 // import java.time.Duration;
21 // import java.time.LocalDate;
22 // import java.time.LocalDateTime;
23 
24 import std.variant;
25 
26 /**
27  * 
28  */
29 abstract class ExtendedQueryCommandBaseCodec(R, C) : QueryCommandBaseCodec!(R, C) {
30         // C extends ExtendedQueryCommandBase!(R)
31     // TODO handle re-bound situations?
32     // Flag if parameters must be re-bound
33     protected byte sendType = 1;
34 
35     protected MySQLPreparedStatement statement;
36 
37     this(C cmd) {
38         super(cmd, DataFormat.BINARY);
39         statement = cast(MySQLPreparedStatement) cmd.preparedStatement();
40     }
41 
42     override
43     protected void handleInitPacket(ByteBuf payload) {
44         // may receive ERR_Packet, OK_Packet, Binary Protocol Resultset
45         int firstByte = payload.getUnsignedByte(payload.readerIndex());
46         if (firstByte == Packets.OK_PACKET_HEADER) {
47             OkPacket okPacket = decodeOkPacketPayload(payload, StandardCharsets.UTF_8);
48             handleSingleResultsetDecodingCompleted(okPacket.serverStatusFlags(), 
49                 cast(int) okPacket.affectedRows(), cast(int) okPacket.lastInsertId());
50         } else if (firstByte == Packets.ERROR_PACKET_HEADER) {
51             handleErrorPacketPayload(payload);
52         } else {
53             handleResultsetColumnCountPacketBody(payload);
54         }
55     }
56 
57     protected void sendStatementExecuteCommand(long statementId, ColumnDefinition[] paramsColumnDefinitions, 
58                 byte sendType, Tuple params, byte cursorType) {
59         ByteBuf packet = allocateBuffer();
60         // encode packet header
61         int packetStartIdx = packet.writerIndex();
62         packet.writeMediumLE(0); // will set payload length later by calculation
63         packet.writeByte(sequenceId);
64 
65         // encode packet payload
66         packet.writeByte(CommandType.COM_STMT_EXECUTE);
67         packet.writeIntLE(cast(int) statementId);
68         packet.writeByte(cursorType);
69         // iteration count, always 1
70         packet.writeIntLE(1);
71 
72         int numOfParams = cast(int)paramsColumnDefinitions.length;
73         int bitmapLength = (numOfParams + 7) / 8;
74         byte[] nullBitmap = new byte[bitmapLength];
75 
76         int pos = packet.writerIndex();
77 
78         if (numOfParams > 0) {
79             // write a dummy bitmap first
80             packet.writeBytes(nullBitmap);
81             packet.writeByte(sendType);
82             if (sendType == 1) {
83                 for (int i = 0; i < numOfParams; i++) {
84                     Variant value = params.getValue(i);
85                     packet.writeByte(parseDataTypeByEncodingValue(value).id);
86                     packet.writeByte(0); // parameter flag: signed
87                 }
88             }
89 
90             for (int i = 0; i < numOfParams; i++) {
91                 Variant value = params.getValue(i);
92                 if (value.hasValue() && value != null) {
93                     MySQLCollation collation = MySQLCollation.valueOfId(paramsColumnDefinitions[i].characterSet());
94                     DataTypeCodec.encodeBinary(cast(DataType)parseDataTypeByEncodingValue(value).id,
95                         (collation.mappedCharsetName()), value, packet); // Charset.forName
96                 } else {
97                     nullBitmap[i / 8] |= (1 << (i & 7));
98                 }
99             }
100 
101             // padding null-bitmap content
102             packet.setBytes(pos, nullBitmap);
103         }
104 
105         // set payload length
106         int payloadLength = packet.writerIndex() - packetStartIdx - 4;
107         packet.setMediumLE(packetStartIdx, payloadLength);
108 
109         sendPacket(packet, payloadLength);
110     }
111 
112     private DataTypeDesc parseDataTypeByEncodingValue(ref Variant value) {
113         // FIXME: Needing refactor or cleanup -@zxp at 9/7/2019, 9:54:11 AM
114         // 
115         if (value == null) {
116             // ProtocolBinary::MYSQL_TYPE_NULL
117             return DataTypes.NULL;
118         } else if (value.type == typeid(byte) || value.type == typeid(ubyte)) {
119             // ProtocolBinary::MYSQL_TYPE_TINY
120             return DataTypes.INT1;
121         } else if (value.type == typeid(bool)) {
122             // ProtocolBinary::MYSQL_TYPE_TINY
123             return DataTypes.INT1;
124         } else if (value.type == typeid(short) || value.type == typeid(ushort)) {
125             // ProtocolBinary::MYSQL_TYPE_SHORT, ProtocolBinary::MYSQL_TYPE_YEAR
126             return DataTypes.INT2;
127         } else if (value.type == typeid(int) || value.type == typeid(uint)) {
128             // ProtocolBinary::MYSQL_TYPE_LONG, ProtocolBinary::MYSQL_TYPE_INT24
129             return DataTypes.INT4;
130         } else if (value.type == typeid(long) || value.type == typeid(ulong)) {
131             // ProtocolBinary::MYSQL_TYPE_LONGLONG
132             return DataTypes.INT8;
133         } else if (value.type == typeid(double)) {
134             // ProtocolBinary::MYSQL_TYPE_DOUBLE
135             return DataTypes.DOUBLE;
136         } else if (value.type == typeid(float)) {
137             // ProtocolBinary::MYSQL_TYPE_FLOAT
138             return DataTypes.FLOAT;
139         // } else if (value instanceof LocalDate) {
140         //     // ProtocolBinary::MYSQL_TYPE_DATE
141         //     return DataTypes.DATE;
142         // } else if (value instanceof Duration) {
143         //     // ProtocolBinary::MYSQL_TYPE_TIME
144         //     return DataTypes.TIME;
145         } else if (value.type == typeid(byte[]) || value.type == typeid(ubyte[])) {
146             // ProtocolBinary::MYSQL_TYPE_LONG_BLOB, ProtocolBinary::MYSQL_TYPE_MEDIUM_BLOB, ProtocolBinary::MYSQL_TYPE_BLOB, ProtocolBinary::MYSQL_TYPE_TINY_BLOB
147             return DataTypes.BLOB;
148         // } else if (value instanceof LocalDateTime) {
149         //     // ProtocolBinary::MYSQL_TYPE_DATETIME, ProtocolBinary::MYSQL_TYPE_TIMESTAMP
150         //     return DataTypes.DATETIME;
151         } else {
152             /*
153                 ProtocolBinary::MYSQL_TYPE_STRING, ProtocolBinary::MYSQL_TYPE_VARCHAR, ProtocolBinary::MYSQL_TYPE_VAR_STRING,
154                 ProtocolBinary::MYSQL_TYPE_ENUM, ProtocolBinary::MYSQL_TYPE_SET, ProtocolBinary::MYSQL_TYPE_GEOMETRY,
155                 ProtocolBinary::MYSQL_TYPE_BIT, ProtocolBinary::MYSQL_TYPE_DECIMAL, ProtocolBinary::MYSQL_TYPE_NEWDECIMAL
156              */
157             return DataTypes.STRING;
158         }
159     }
160 }