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 }