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