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 MYXReceived = MySQLReceivedException; 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(const(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 }