1 module mysql.result;
2 
3 import std.algorithm;
4 import std.conv;
5 import std.datetime;
6 import std.digest.sha;
7 import std.exception;
8 import std.range;
9 import std.socket;
10 import std.stdio;
11 import std.string;
12 import std.traits;
13 import std.variant;
14 
15 import mysql.common;
16 import mysql.connection;
17 import mysql.protocol.commands;
18 import mysql.protocol.packets;
19 
20 /++
21 A struct to represent a single row of a result set.
22 
23 The row struct is used for both 'traditional' and 'prepared' result sets.
24 It consists of parallel arrays of Variant and bool, with the bool array
25 indicating which of the result set columns are NULL.
26 
27 I have been agitating for some kind of null indicator that can be set for a
28 Variant without destroying its inherent type information. If this were the
29 case, then the bool array could disappear.
30 +/
31 struct Row
32 {
33 	import mysql.connection;
34 
35 package:
36 	Variant[]   _values; // Temporarily "package" instead of "private"
37 private:
38 	bool[]      _nulls;
39 
40 	private static uint calcBitmapLength(uint fieldCount) pure nothrow
41 	{
42 		return (fieldCount+7+2)/8;
43 	}
44 
45 	static bool[] consumeNullBitmap(ref ubyte[] packet, uint fieldCount) pure
46 	{
47 		uint bitmapLength = calcBitmapLength(fieldCount);
48 		enforceEx!MYXProtocol(packet.length >= bitmapLength, "Packet too small to hold null bitmap for all fields");
49 		auto bitmap = packet.consume(bitmapLength);
50 		return decodeNullBitmap(bitmap, fieldCount);
51 	}
52 
53 	// This is to decode the bitmap in a binary result row. First two bits are skipped
54 	static bool[] decodeNullBitmap(ubyte[] bitmap, uint numFields) pure nothrow
55 	in
56 	{
57 		assert(bitmap.length >= calcBitmapLength(numFields),
58 				"bitmap not large enough to store all null fields");
59 	}
60 	out(result)
61 	{
62 		assert(result.length == numFields);
63 	}
64 	body
65 	{
66 		bool[] nulls;
67 		nulls.length = numFields;
68 
69 		// the current byte we are processing for nulls
70 		ubyte bits = bitmap.front();
71 		// strip away the first two bits as they are reserved
72 		bits >>= 2;
73 		// .. and then we only have 6 bits left to process for this byte
74 		ubyte bitsLeftInByte = 6;
75 		foreach(ref isNull; nulls)
76 		{
77 			assert(bitsLeftInByte <= 8);
78 			// processed all bits? fetch new byte
79 			if (bitsLeftInByte == 0)
80 			{
81 				assert(bits == 0, "not all bits are processed!");
82 				assert(!bitmap.empty, "bits array too short for number of columns");
83 				bitmap.popFront();
84 				bits = bitmap.front;
85 				bitsLeftInByte = 8;
86 			}
87 			assert(bitsLeftInByte > 0);
88 			isNull = (bits & 0b0000_0001) != 0;
89 
90 			// get ready to process next bit
91 			bits >>= 1;
92 			--bitsLeftInByte;
93 		}
94 		return nulls;
95 	}
96 
97 public:
98 
99 	/++
100 	A constructor to extract the column data from a row data packet.
101 	
102 	If the data for the row exceeds the server's maximum packet size, then several packets will be
103 	sent for the row that taken together constitute a logical row data packet. The logic of the data
104 	recovery for a Row attempts to minimize the quantity of data that is bufferred. Users can assist
105 	in this by specifying chunked data transfer in cases where results sets can include long
106 	column values.
107 	
108 	The row struct is used for both 'traditional' and 'prepared' result sets. It consists of parallel arrays
109 	of Variant and bool, with the bool array indicating which of the result set columns are NULL.
110 	
111 	I have been agitating for some kind of null indicator that can be set for a Variant without destroying
112 	its inherent type information. If this were the case, then the bool array could disappear.
113 	However, this inherent type information was never actually used, or even tracked, by struct Row for null fields.
114 	So this is may be nothing to be concerned about. If such info is needed later, perhaps
115 	`_values` could store its elements as `Nullable!T`?
116 	+/
117 	this(Connection con, ref ubyte[] packet, ResultSetHeaders rh, bool binary)
118 	in
119 	{
120 		assert(rh.fieldCount <= uint.max);
121 	}
122 	body
123 	{
124 		scope(failure) con.kill();
125 
126 		uint fieldCount = cast(uint)rh.fieldCount;
127 		_values.length = _nulls.length = fieldCount;
128 
129 		if (binary)
130 		{
131 			// There's a null byte header on a binary result sequence, followed by some bytes of bitmap
132 			// indicating which columns are null
133 			enforceEx!MYXProtocol(packet.front == 0, "Expected null header byte for binary result row");
134 			packet.popFront();
135 			_nulls = consumeNullBitmap(packet, fieldCount);
136 		}
137 
138 		foreach (size_t i; 0..fieldCount)
139 		{
140 			if(binary && _nulls[i])
141 				continue;
142 
143 			SQLValue sqlValue;
144 			do
145 			{
146 				FieldDescription fd = rh[i];
147 				sqlValue = packet.consumeIfComplete(fd.type, binary, fd.unsigned, fd.charSet);
148 				// TODO: Support chunk delegate
149 				if(sqlValue.isIncomplete)
150 					packet ~= con.getPacket();
151 			} while(sqlValue.isIncomplete);
152 			assert(!sqlValue.isIncomplete);
153 
154 			if(sqlValue.isNull)
155 			{
156 				assert(!binary);
157 				assert(!_nulls[i]);
158 				_nulls[i] = true;
159 				_values[i] = null;
160 			}
161 			else
162 			{
163 				_values[i] = sqlValue.value;
164 			}
165 		}
166 	}
167 
168 	/++
169 	Simplify retrieval of a column value by index.
170 	
171 	To check for null, use Variant's .type property:
172 	`row[index].type == typeid(typeof(null))`
173 	
174 	Params: i = the zero based index of the column whose value is required.
175 	Returns: A Variant holding the column value.
176 	+/
177 	inout(Variant) opIndex(size_t i) inout
178 	{
179 		enforceEx!MYX(_nulls.length > 0, format("Cannot get column index %d. There are no columns", i));
180 		enforceEx!MYX(i < _nulls.length, format("Cannot get column index %d. The last available index is %d", i, _nulls.length-1));
181 		return _values[i];
182 	}
183 
184 	/++
185 	Check if a column in the result row was NULL
186 	
187 	Params: i = The zero based column index.
188 	+/
189 	@property bool isNull(size_t i) const pure nothrow { return _nulls[i]; }
190 
191 	/++
192 	Get the number of elements (columns) in this row.
193 	+/
194 	@property size_t length() const pure nothrow { return _values.length; }
195 
196 	///ditto
197 	alias opDollar = length;
198 
199 	/++
200 	Move the content of the row into a compatible struct
201 	
202 	This method takes no account of NULL column values. If a column was NULL,
203 	the corresponding Variant value would be unchanged in those cases.
204 	
205 	The method will throw if the type of the Variant is not implicitly
206 	convertible to the corresponding struct member.
207 	
208 	Params: S = a struct type.
209 	               s = an ref instance of the type
210 	+/
211 	void toStruct(S)(ref S s) if (is(S == struct))
212 	{
213 		foreach (i, dummy; s.tupleof)
214 		{
215 			static if(__traits(hasMember, s.tupleof[i], "nullify") &&
216 					  is(typeof(s.tupleof[i].nullify())) && is(typeof(s.tupleof[i].get)))
217 			{
218 				if(!_nulls[i])
219 				{
220 					enforceEx!MYX(_values[i].convertsTo!(typeof(s.tupleof[i].get))(),
221 						"At col "~to!string(i)~" the value is not implicitly convertible to the structure type");
222 					s.tupleof[i] = _values[i].get!(typeof(s.tupleof[i].get));
223 				}
224 				else
225 					s.tupleof[i].nullify();
226 			}
227 			else
228 			{
229 				if(!_nulls[i])
230 				{
231 					enforceEx!MYX(_values[i].convertsTo!(typeof(s.tupleof[i]))(),
232 						"At col "~to!string(i)~" the value is not implicitly convertible to the structure type");
233 					s.tupleof[i] = _values[i].get!(typeof(s.tupleof[i]));
234 				}
235 				else
236 					s.tupleof[i] = typeof(s.tupleof[i]).init;
237 			}
238 		}
239 	}
240 
241 	void show()
242 	{
243 		foreach(Variant v; _values)
244 			writef("%s, ", v.toString());
245 		writeln("");
246 	}
247 }
248 
249 /++
250 Composite representation of a column value
251 
252 Another case where a null flag on Variant would simplify matters.
253 +/
254 struct DBValue
255 {
256 	Variant value;
257 	bool isNull;
258 }
259 
260 /++
261 A Random access range of Rows.
262 
263 This is the entity that is returned by the Command methods execSQLResult and
264 execPreparedResult
265 
266 MySQL result sets can be up to 2^^64 rows, and the 32 bit implementation of the
267 MySQL C API accomodates such potential massive result sets by storing the rows in
268 a doubly linked list. I have taken the view that users who have a need for result sets
269 up to this size should be working with a 64 bit system, and as such the 32 bit
270 implementation will throw if the number of rows exceeds the 32 bit size_t.max.
271 +/
272 struct ResultSet
273 {
274 private:
275 	Row[]          _rows;      // all rows in ResultSet, we store this to be able to revert() to it's original state
276 	string[]       _colNames;
277 	Row[]          _curRows;   // current rows in ResultSet
278 	size_t[string] _colNameIndicies;
279 
280 package:
281 	this (Row[] rows, string[] colNames)
282 	{
283 		_rows = rows;
284 		_curRows = _rows[];
285 		_colNames = colNames;
286 	}
287 
288 public:
289 	/++
290 	Make the ResultSet behave as a random access range - empty
291 	
292 	+/
293 	@property bool empty() const pure nothrow { return _curRows.length == 0; }
294 
295 	/++
296 	Make the ResultSet behave as a random access range - save
297 	
298 	+/
299 	@property ResultSet save() pure nothrow
300 	{
301 		return this;
302 	}
303 
304 	/++
305 	Make the ResultSet behave as a random access range - front
306 	
307 	Gets the first row in whatever remains of the Range.
308 	+/
309 	@property inout(Row) front() pure inout
310 	{
311 		enforceEx!MYX(_curRows.length, "Attempted to get front of an empty ResultSet");
312 		return _curRows[0];
313 	}
314 
315 	/++
316 	Make the ResultSet behave as a random access range - back
317 	
318 	Gets the last row in whatever remains of the Range.
319 	+/
320 	@property inout(Row) back() pure inout
321 	{
322 		enforceEx!MYX(_curRows.length, "Attempted to get back on an empty ResultSet");
323 		return _curRows[$-1];
324 	}
325 
326 	/++
327 	Make the ResultSet behave as a random access range - popFront()
328 	
329 	+/
330 	void popFront() pure
331 	{
332 		enforceEx!MYX(_curRows.length, "Attempted to popFront() on an empty ResultSet");
333 		_curRows = _curRows[1..$];
334 	}
335 
336 	/++
337 	Make the ResultSet behave as a random access range - popBack
338 	
339 	+/
340 	void popBack() pure
341 	{
342 		enforceEx!MYX(_curRows.length, "Attempted to popBack() on an empty ResultSet");
343 		_curRows = _curRows[0 .. $-1];
344 	}
345 
346 	/++
347 	Make the ResultSet behave as a random access range - opIndex
348 	
349 	Gets the i'th row of whatever remains of the range
350 	+/
351 	Row opIndex(size_t i) pure
352 	{
353 		enforceEx!MYX(_curRows.length, "Attempted to index into an empty ResultSet range.");
354 		enforceEx!MYX(i < _curRows.length, "Requested range index out of range");
355 		return _curRows[i];
356 	}
357 
358 	/++
359 	Make the ResultSet behave as a random access range - length
360 	
361 	+/
362 	@property size_t length() pure const nothrow { return _curRows.length; }
363 	alias opDollar = length; ///ditto
364 
365 	/++
366 	Restore the range to its original span.
367 	
368 	Since the range is just a view of the data, we can easily revert to the
369 	initial state.
370 	+/
371 	void revert() pure nothrow
372 	{
373 		_curRows = _rows[];
374 	}
375 
376 	/++
377 	Get a row as an associative array by column name
378 	
379 	The row in question will be that which was the most recent subject of
380 	front, back, or opIndex. If there have been no such references it will be front.
381 	+/
382 	DBValue[string] asAA()
383 	{
384 		enforceEx!MYX(_curRows.length, "Attempted use of empty ResultSet as an associative array.");
385 		DBValue[string] aa;
386 		foreach (size_t i, string s; _colNames)
387 		{
388 			DBValue value;
389 			value.value  = front._values[i];
390 			value.isNull = front._nulls[i];
391 			aa[s]        = value;
392 		}
393 		return aa;
394 	}
395 
396 	/// Get the names of all the columns
397 	@property const(string)[] colNames() const pure nothrow { return _colNames; }
398 
399 	/// An AA to lookup a column's index by name
400 	@property const(size_t[string]) colNameIndicies() pure nothrow
401 	{
402 		if(_colNameIndicies is null)
403 		{
404 			foreach(index, name; _colNames)
405 				_colNameIndicies[name] = index;
406 		}
407 
408 		return _colNameIndicies;
409 	}
410 }
411 
412 /++
413 An input range of Rows.
414 
415 This is the entity that is returned by the Command methods execSQLSequence and
416 execPreparedSequence
417 
418 MySQL result sets can be up to 2^^64 rows. This interface allows for iteration
419 through a result set of that size.
420 +/
421 struct ResultRange
422 {
423 private:
424 	Connection       _con;
425 	ResultSetHeaders _rsh;
426 	Row              _row; // current row
427 	string[]         _colNames;
428 	size_t[string]   _colNameIndicies;
429 	ulong            _numRowsFetched;
430 	ulong            _commandId; // So we can keep track of when this is invalidated
431 
432 	void ensureValid() const pure
433 	{
434 		enforceEx!MYXInvalidatedRange(isValid,
435 			"This ResultRange has been invalidated and can no longer be used.");
436 	}
437 
438 package:
439 	this (Connection con, ResultSetHeaders rsh, string[] colNames)
440 	{
441 		_con       = con;
442 		_rsh       = rsh;
443 		_colNames  = colNames;
444 		_commandId = con.lastCommandId;
445 		popFront();
446 	}
447 
448 public:
449 	~this()
450 	{
451 		close();
452 	}
453 
454 	/// Check whether the range can still we used, or has been invalidated
455 	@property bool isValid() const pure nothrow
456 	{
457 		return _commandId == _con.lastCommandId;
458 	}
459 
460 	/// Make the ResultRange behave as an input range - empty
461 	@property bool empty() const pure nothrow
462 	{
463 		if(!isValid)
464 			return true;
465 
466 		return !_con._rowsPending;
467 	}
468 
469 	/++
470 	Make the ResultRange behave as an input range - front
471 	
472 	Gets the current row
473 	+/
474 	@property inout(Row) front() pure inout
475 	{
476 		ensureValid();
477 		enforceEx!MYX(!empty, "Attempted 'front' on exhausted result sequence.");
478 		return _row;
479 	}
480 
481 	/++
482 	Make the ResultRange behave as am input range - popFront()
483 	
484 	Progresses to the next row of the result set - that will then be 'front'
485 	+/
486 	void popFront()
487 	{
488 		ensureValid();
489 		enforceEx!MYX(!empty, "Attempted 'popFront' when no more rows available");
490 		_row = _con.getNextRow();
491 		_numRowsFetched++;
492 	}
493 
494 	/++
495 	Get the current row as an associative array by column name
496 	+/
497 	 DBValue[string] asAA()
498 	 {
499 		ensureValid();
500 		enforceEx!MYX(!empty, "Attempted 'front' on exhausted result sequence.");
501 		DBValue[string] aa;
502 		foreach (size_t i, string s; _colNames)
503 		{
504 			DBValue value;
505 			value.value  = _row._values[i];
506 			value.isNull = _row._nulls[i];
507 			aa[s]        = value;
508 		}
509 		return aa;
510 	}
511 
512 	/// Get the names of all the columns
513 	@property const(string)[] colNames() const pure nothrow { return _colNames; }
514 
515 	/// An AA to lookup a column's index by name
516 	@property const(size_t[string]) colNameIndicies() pure nothrow
517 	{
518 		if(_colNameIndicies is null)
519 		{
520 			foreach(index, name; _colNames)
521 				_colNameIndicies[name] = index;
522 		}
523 
524 		return _colNameIndicies;
525 	}
526 
527 	/// Explicitly clean up the MySQL resources and cancel pending results
528 	void close()
529 	out{ assert(!isValid); }
530 	body
531 	{
532 		if(isValid)
533 			_con.purgeResult();
534 	}
535 
536 	/++
537 	Get the number of currently retrieved.
538 	
539 	Note that this is not neccessarlly the same as the length of the range.
540 	+/
541 	@property ulong rowCount() const pure nothrow { return _numRowsFetched; }
542 }
543 
544 ///ditto
545 deprecated("Use ResultRange instead.")
546 alias ResultSequence = ResultRange;