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