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