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