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 }