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 }