1 module mysql.protocol.packets; 2 3 import std.algorithm; 4 import std.conv; 5 import std.datetime; 6 import std.digest.sha; 7 import std.exception; 8 import std.range; 9 import std.socket; 10 import std.stdio; 11 import std.string; 12 import std.traits; 13 import std.variant; 14 15 import mysql.common; 16 import mysql.protocol.constants; 17 import mysql.protocol.extra_types; 18 public import mysql.protocol.packet_helpers; 19 20 /** 21 * The server sent back a MySQL error code and message. If the server is 4.1+, 22 * there should also be an ANSI/ODBC-standard SQLSTATE error code. 23 * 24 * See_Also: https://dev.mysql.com/doc/refman/5.5/en/error-messages-server.html 25 */ 26 class MySQLReceivedException: MySQLException 27 { 28 ushort errorCode; 29 char[5] sqlState; 30 31 this(OKErrorPacket okp, string file, size_t line) pure 32 { 33 this(okp.message, okp.serverStatus, okp.sqlState, file, line); 34 } 35 36 this(string msg, ushort errorCode, char[5] sqlState, string file, size_t line) pure 37 { 38 this.errorCode = errorCode; 39 this.sqlState = sqlState; 40 super("MySQL error: " ~ msg, file, line); 41 } 42 } 43 alias MySQLReceivedException MYXReceived; 44 45 void enforcePacketOK(string file = __FILE__, size_t line = __LINE__)(OKErrorPacket okp) 46 { 47 enforce(!okp.error, new MYXReceived(okp, file, line)); 48 } 49 50 /** 51 * A struct representing an OK or Error packet 52 * See_Also: http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol#Types_Of_Result_Packets 53 * OK packets begin with a zero byte - Error packets with 0xff 54 */ 55 struct OKErrorPacket 56 { 57 bool error; 58 ulong affected; 59 ulong insertID; 60 ushort serverStatus; 61 ushort warnings; 62 char[5] sqlState; 63 string message; 64 65 this(ubyte[] packet) 66 { 67 if (packet.front == ResultPacketMarker.error) 68 { 69 packet.popFront(); // skip marker/field code 70 error = true; 71 72 enforceEx!MYXProtocol(packet.length > 2, "Malformed Error packet - Missing error code"); 73 serverStatus = packet.consume!short(); // error code into server state 74 if (packet.front == cast(ubyte) '#') //4.1+ error packet 75 { 76 packet.popFront(); // skip 4.1 marker 77 enforceEx!MYXProtocol(packet.length > 5, "Malformed Error packet - Missing SQL state"); 78 sqlState[] = (cast(char[])packet[0 .. 5])[]; 79 packet = packet[5..$]; 80 } 81 } 82 else if(packet.front == ResultPacketMarker.ok) 83 { 84 packet.popFront(); // skip marker/field code 85 86 enforceEx!MYXProtocol(packet.length > 1, "Malformed OK packet - Missing affected rows"); 87 auto lcb = packet.consumeIfComplete!LCB(); 88 assert(!lcb.isNull); 89 assert(!lcb.isIncomplete); 90 affected = lcb.value; 91 92 enforceEx!MYXProtocol(packet.length > 1, "Malformed OK packet - Missing insert id"); 93 lcb = packet.consumeIfComplete!LCB(); 94 assert(!lcb.isNull); 95 assert(!lcb.isIncomplete); 96 insertID = lcb.value; 97 98 enforceEx!MYXProtocol(packet.length > 2, 99 format("Malformed OK packet - Missing server status. Expected length > 2, got %d", packet.length)); 100 serverStatus = packet.consume!short(); 101 102 enforceEx!MYXProtocol(packet.length >= 2, "Malformed OK packet - Missing warnings"); 103 warnings = packet.consume!short(); 104 } 105 else 106 throw new MYXProtocol("Malformed OK/Error packet - Incorrect type of packet", __FILE__, __LINE__); 107 108 // both OK and Error packets end with a message for the rest of the packet 109 message = cast(string)packet.idup; 110 } 111 } 112 113 /** 114 * A struct representing a field (column) description packet 115 * 116 * These packets, one for each column are sent before the data of a result set, 117 * followed by an EOF packet. 118 * 119 * See_Also: http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol#Field_Packet 120 */ 121 struct FieldDescription 122 { 123 private: 124 string _db; 125 string _table; 126 string _originalTable; 127 string _name; 128 string _originalName; 129 ushort _charSet; 130 uint _length; 131 SQLType _type; 132 FieldFlags _flags; 133 ubyte _scale; 134 ulong _deflt; 135 uint chunkSize; 136 void delegate(ubyte[], bool) chunkDelegate; 137 138 public: 139 /** 140 * Construct a FieldDescription from the raw data packet 141 * 142 * Parameters: packet = The packet contents excluding the 4 byte packet header 143 */ 144 this(ubyte[] packet) 145 in 146 { 147 assert(packet.length); 148 } 149 out 150 { 151 assert(!packet.length, "not all bytes read during FieldDescription construction"); 152 } 153 body 154 { 155 packet.skip(4); // Skip catalog - it's always 'def' 156 _db = packet.consume!LCS(); 157 _table = packet.consume!LCS(); 158 _originalTable = packet.consume!LCS(); 159 _name = packet.consume!LCS(); 160 _originalName = packet.consume!LCS(); 161 162 enforceEx!MYXProtocol(packet.length >= 13, "Malformed field specification packet"); 163 packet.popFront(); // one byte filler here 164 _charSet = packet.consume!short(); 165 _length = packet.consume!int(); 166 _type = cast(SQLType)packet.consume!ubyte(); 167 _flags = cast(FieldFlags)packet.consume!short(); 168 _scale = packet.consume!ubyte(); 169 packet.skip(2); // two byte filler 170 171 if(packet.length) 172 { 173 packet.skip(1); // one byte filler 174 auto lcb = packet.consumeIfComplete!LCB(); 175 assert(!lcb.isNull); 176 assert(!lcb.isIncomplete); 177 _deflt = lcb.value; 178 } 179 } 180 181 /// Database name for column as string 182 @property string db() pure const nothrow { return _db; } 183 184 /// Table name for column as string - this could be an alias as in 'from tablename as foo' 185 @property string table() pure const nothrow { return _table; } 186 187 /// Real table name for column as string 188 @property string originalTable() pure const nothrow { return _originalTable; } 189 190 /// Column name as string - this could be an alias 191 @property string name() pure const nothrow { return _name; } 192 193 /// Real column name as string 194 @property string originalName() pure const nothrow { return _originalName; } 195 196 /// The character set in force 197 @property ushort charSet() pure const nothrow { return _charSet; } 198 199 /// The 'length' of the column as defined at table creation 200 @property uint length() pure const nothrow { return _length; } 201 202 /// The type of the column hopefully (but not always) corresponding to enum SQLType. 203 /// Only the low byte currently used. 204 @property SQLType type() pure const nothrow { return _type; } 205 206 /// Column flags - unsigned, binary, null and so on 207 @property FieldFlags flags() pure const nothrow { return _flags; } 208 209 /// Precision for floating point values 210 @property ubyte scale() pure const nothrow { return _scale; } 211 212 /// NotNull from flags 213 @property bool notNull() pure const nothrow { return (_flags & FieldFlags.NOT_NULL) != 0; } 214 215 /// Unsigned from flags 216 @property bool unsigned() pure const nothrow { return (_flags & FieldFlags.UNSIGNED) != 0; } 217 218 /// Binary from flags 219 @property bool binary() pure const nothrow { return (_flags & FieldFlags.BINARY) != 0; } 220 221 /// Is-enum from flags 222 @property bool isenum() pure const nothrow { return (_flags & FieldFlags.ENUM) != 0; } 223 224 /// Is-set (a SET column that is) from flags 225 @property bool isset() pure const nothrow { return (_flags & FieldFlags.SET) != 0; } 226 227 void show() const 228 { 229 writefln("%s %d %x %016b", _name, _length, _type, _flags); 230 } 231 } 232 233 /** 234 * A struct representing a prepared statement parameter description packet 235 * 236 * These packets, one for each parameter are sent in response to the prepare 237 * command, followed by an EOF packet. 238 * 239 * Sadly it seems that this facility is only a stub. The correct number of 240 * packets is sent, but they contain no useful information and are all the same. 241 */ 242 struct ParamDescription 243 { 244 private: 245 ushort _type; 246 FieldFlags _flags; 247 ubyte _scale; 248 uint _length; 249 250 public: 251 this(ubyte[] packet) 252 { 253 _type = packet.consume!short(); 254 _flags = cast(FieldFlags)packet.consume!short(); 255 _scale = packet.consume!ubyte(); 256 _length = packet.consume!int(); 257 assert(!packet.length); 258 } 259 @property uint length() pure const nothrow { return _length; } 260 @property ushort type() pure const nothrow { return _type; } 261 @property FieldFlags flags() pure const nothrow { return _flags; } 262 @property ubyte scale() pure const nothrow { return _scale; } 263 @property bool notNull() pure const nothrow { return (_flags & FieldFlags.NOT_NULL) != 0; } 264 @property bool unsigned() pure const nothrow { return (_flags & FieldFlags.UNSIGNED) != 0; } 265 } 266 267 bool isEOFPacket(in ubyte[] packet) pure nothrow 268 in 269 { 270 assert(!packet.empty); 271 } 272 body 273 { 274 return packet.front == ResultPacketMarker.eof && packet.length < 9; 275 } 276 277 /** 278 * A struct representing an EOF packet from the server 279 * 280 * An EOF packet is sent from the server after each sequence of field 281 * description and parameter description packets, and after a sequence of 282 * result set row packets. 283 * An EOF packet is also called "Last Data Packet" or "End Packet". 284 * 285 * These EOF packets contain a server status and a warning count. 286 * 287 * See_Also: http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol#EOF_Packet 288 */ 289 struct EOFPacket 290 { 291 private: 292 ushort _warnings; 293 ushort _serverStatus; 294 295 public: 296 297 /** 298 * Construct an EOFPacket struct from the raw data packet 299 * 300 * Parameters: packet = The packet contents excluding the 4 byte packet header 301 */ 302 this(ubyte[] packet) 303 in 304 { 305 assert(packet.isEOFPacket()); 306 assert(packet.length == 5); 307 } 308 out 309 { 310 assert(!packet.length); 311 } 312 body 313 { 314 packet.popFront(); // eof marker 315 _warnings = packet.consume!short(); 316 _serverStatus = packet.consume!short(); 317 } 318 319 /// Retrieve the warning count 320 @property ushort warnings() pure const nothrow { return _warnings; } 321 322 /// Retrieve the server status 323 @property ushort serverStatus() pure const nothrow { return _serverStatus; } 324 } 325 326 327 /** 328 * A struct representing the collation of a sequence of FieldDescription packets. 329 * 330 * This data gets filled in after a query (prepared or otherwise) that creates 331 * a result set completes. All the FD packets, and an EOF packet must be eaten 332 * before the row data packets can be read. 333 */ 334 struct ResultSetHeaders 335 { 336 import mysql.connection; 337 338 private: 339 FieldDescription[] _fieldDescriptions; 340 string[] _fieldNames; 341 ushort _warnings; 342 343 public: 344 345 /** 346 * Construct a ResultSetHeaders struct from a sequence of FieldDescription 347 * packets and an EOF packet. 348 * 349 * Parameters: 350 * con = A Connection via which the packets are read 351 * fieldCount = the number of fields/columns generated by the query 352 */ 353 this(Connection con, uint fieldCount) 354 { 355 scope(failure) con.kill(); 356 357 _fieldNames.length = _fieldDescriptions.length = fieldCount; 358 foreach (size_t i; 0 .. fieldCount) 359 { 360 auto packet = con.getPacket(); 361 enforceEx!MYXProtocol(!packet.isEOFPacket(), 362 "Expected field description packet, got EOF packet in result header sequence"); 363 364 _fieldDescriptions[i] = FieldDescription(packet); 365 _fieldNames[i] = _fieldDescriptions[i]._name; 366 } 367 auto packet = con.getPacket(); 368 enforceEx!MYXProtocol(packet.isEOFPacket(), 369 "Expected EOF packet in result header sequence"); 370 auto eof = EOFPacket(packet); 371 con._serverStatus = eof._serverStatus; 372 _warnings = eof._warnings; 373 } 374 375 /** 376 * Add specialization information to one or more field descriptions. 377 * 378 * Currently the only specialization supported is the capability to deal with long data 379 * e.g. BLOB or TEXT data in chunks by stipulating a chunkSize and a delegate to sink 380 * the data. 381 * 382 * Parameters: 383 * csa = An array of ColumnSpecialization structs 384 */ 385 void addSpecializations(ColumnSpecialization[] csa) 386 { 387 foreach(CSN csn; csa) 388 { 389 enforceEx!MYX(csn.cIndex < fieldCount && _fieldDescriptions[csn.cIndex].type == csn.type, 390 "Column specialization index or type does not match the corresponding column."); 391 _fieldDescriptions[csn.cIndex].chunkSize = csn.chunkSize; 392 _fieldDescriptions[csn.cIndex].chunkDelegate = csn.chunkDelegate; 393 } 394 } 395 396 /// Index into the set of field descriptions 397 FieldDescription opIndex(size_t i) pure nothrow { return _fieldDescriptions[i]; } 398 /// Get the number of fields in a result row. 399 @property size_t fieldCount() pure const nothrow { return _fieldDescriptions.length; } 400 /// Get the warning count as per the EOF packet 401 @property ushort warnings() pure const nothrow { return _warnings; } 402 /// Get an array of strings representing the column names 403 @property string[] fieldNames() pure nothrow { return _fieldNames; } 404 /// Get an array of the field descriptions 405 @property FieldDescription[] fieldDescriptions() pure nothrow { return _fieldDescriptions; } 406 407 void show() const 408 { 409 foreach (FieldDescription fd; _fieldDescriptions) 410 fd.show(); 411 } 412 } 413 414 /** 415 * A struct representing the collation of a prepared statement parameter description sequence 416 * 417 * As noted above - parameter descriptions are not fully implemented by MySQL. 418 */ 419 struct PreparedStmtHeaders 420 { 421 import mysql.connection; 422 423 package: 424 Connection _con; 425 ushort _colCount, _paramCount; 426 FieldDescription[] _colDescriptions; 427 ParamDescription[] _paramDescriptions; 428 ushort _warnings; 429 430 bool getEOFPacket() 431 { 432 auto packet = _con.getPacket(); 433 if (!packet.isEOFPacket()) 434 return false; 435 EOFPacket eof = EOFPacket(packet); 436 _con._serverStatus = eof._serverStatus; 437 _warnings += eof._warnings; 438 return true; 439 } 440 441 public: 442 this(Connection con, ushort cols, ushort params) 443 { 444 scope(failure) con.kill(); 445 446 _con = con; 447 _colCount = cols; 448 _paramCount = params; 449 _colDescriptions.length = cols; 450 _paramDescriptions.length = params; 451 452 // The order in which fields are sent is params first, followed by EOF, 453 // then cols followed by EOF The parameter specs are useless - they are 454 // all the same. This observation is coroborated by the fact that the 455 // C API does not have any information about parameter types either. 456 // WireShark gives up on these records also. 457 foreach (size_t i; 0.._paramCount) 458 _con.getPacket(); // just eat them - they are not useful 459 460 if (_paramCount) 461 enforceEx!MYXProtocol(getEOFPacket(), "Expected EOF packet in result header sequence"); 462 463 foreach(size_t i; 0.._colCount) 464 _colDescriptions[i] = FieldDescription(_con.getPacket()); 465 466 if (_colCount) 467 enforceEx!MYXProtocol(getEOFPacket(), "Expected EOF packet in result header sequence"); 468 } 469 470 ParamDescription param(size_t i) pure const nothrow { return _paramDescriptions[i]; } 471 FieldDescription col(size_t i) pure const nothrow { return _colDescriptions[i]; } 472 473 @property ParamDescription[] paramDescriptions() pure nothrow { return _paramDescriptions; } 474 @property FieldDescription[] fieldDescriptions() pure nothrow { return _colDescriptions; } 475 476 @property paramCount() pure const nothrow { return _paramCount; } 477 @property ushort warnings() pure const nothrow { return _warnings; } 478 479 void showCols() const 480 { 481 writefln("%d columns", _colCount); 482 foreach (FieldDescription fd; _colDescriptions) 483 { 484 writefln("%10s %10s %10s %10s %10s %d %d %02x %016b %d", 485 fd._db, fd._table, fd._originalTable, fd._name, fd._originalName, 486 fd._charSet, fd._length, fd._type, fd._flags, fd._scale); 487 } 488 } 489 }