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