1 module mysql.protocol.commands;
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.typecons;
14 import std.variant;
15 
16 import mysql.common;
17 import mysql.connection;
18 import mysql.protocol.constants;
19 import mysql.protocol.extra_types;
20 import mysql.protocol.packets;
21 import mysql.protocol.packet_helpers;
22 import mysql.protocol.prepared;
23 
24 package struct ExecQueryImplInfo
25 {
26 	bool isPrepared;
27 
28 	// For non-prepared statements:
29 	string sql;
30 
31 	// For prepared statements:
32 	uint hStmt;
33 	PreparedStmtHeaders psh;
34 	Variant[] inParams;
35 	ParameterSpecialization[] psa;
36 }
37 
38 /++
39 Internal implementation for the exec and query functions.
40 
41 Execute a one-off SQL command.
42 
43 Use this method when you are not going to be using the same command repeatedly.
44 It can be used with commands that don't produce a result set, or those that
45 do. If there is a result set its existence will be indicated by the return value.
46 
47 Any result set can be accessed vis Connection.getNextRow(), but you should really be
48 using execSQLResult() or execSQLSequence() for such queries.
49 
50 Params: ra = An out parameter to receive the number of rows affected.
51 Returns: true if there was a (possibly empty) result set.
52 +/
53 package bool execQueryImpl(Connection conn, ExecQueryImplInfo info, out ulong ra)
54 {
55 	conn.enforceNothingPending();
56 	scope(failure) conn.kill();
57 
58 	// Send data
59 	if(info.isPrepared)
60 		Prepared.sendCommand(conn, info.hStmt, info.psh, info.inParams, info.psa);
61 	else
62 	{
63 		conn.sendCmd(CommandType.QUERY, info.sql);
64 		conn._fieldCount = 0;
65 	}
66 
67 	// Handle response
68 	ubyte[] packet = conn.getPacket();
69 	bool rv;
70 	if (packet.front == ResultPacketMarker.ok || packet.front == ResultPacketMarker.error)
71 	{
72 		conn.resetPacket();
73 		auto okp = OKErrorPacket(packet);
74 		enforcePacketOK(okp);
75 		ra = okp.affected;
76 		conn._serverStatus = okp.serverStatus;
77 		conn._insertID = okp.insertID;
78 		rv = false;
79 	}
80 	else
81 	{
82 		// There was presumably a result set
83 		assert(packet.front >= 1 && packet.front <= 250); // ResultSet packet header should have this value
84 		conn._headersPending = conn._rowsPending = true;
85 		conn._binaryPending = info.isPrepared;
86 		auto lcb = packet.consumeIfComplete!LCB();
87 		assert(!lcb.isNull);
88 		assert(!lcb.isIncomplete);
89 		conn._fieldCount = cast(ushort)lcb.value;
90 		assert(conn._fieldCount == lcb.value);
91 		rv = true;
92 		ra = 0;
93 	}
94 	return rv;
95 }
96 
97 ///ditto
98 package bool execQueryImpl(Connection conn, ExecQueryImplInfo info)
99 {
100 	ulong rowsAffected;
101 	return execQueryImpl(conn, info, rowsAffected);
102 }
103 
104 /++
105 Execute a one-off SQL command.
106 
107 Use this method when you are not going to be using the same command repeatedly.
108 It can be used with commands that don't produce a result set, or those that
109 do. If there is a result set its existence will be indicated by the return value.
110 
111 Any result set can be accessed vis Connection.getNextRow(), but you should really be
112 using execSQLResult() or execSQLSequence() for such queries.
113 
114 Params: ra = An out parameter to receive the number of rows affected.
115 Returns: true if there was a (possibly empty) result set.
116 +/
117 ulong exec(Connection conn, string sql)
118 {
119 	return execImpl(conn, ExecQueryImplInfo(false, sql));
120 }
121 
122 /// Common implementation for mysql.protocol.commands.exec and Prepared.exec
123 package ulong execImpl(Connection conn, ExecQueryImplInfo info)
124 {
125 	ulong rowsAffected;
126 	bool receivedResultSet = execQueryImpl(conn, info, rowsAffected);
127 	if(receivedResultSet)
128 	{
129 		conn.purgeResult();
130 		throw new MYXResultRecieved();
131 	}
132 
133 	return rowsAffected;
134 }
135 
136 /++
137 Execute a one-off SQL command for the case where you expect a result set,
138 and want it all at once.
139 
140 Use this method when you are not going to be using the same command repeatedly.
141 This method will throw if the SQL command does not produce a result set.
142 
143 If there are long data items among the expected result columns you can specify
144 that they are to be subject to chunked transfer via a delegate.
145 
146 Params: csa = An optional array of ColumnSpecialization structs.
147 Returns: A (possibly empty) ResultSet.
148 +/
149 ResultSet querySet(Connection conn, string sql, ColumnSpecialization[] csa = null)
150 {
151 	return querySetImpl(csa, false, conn, ExecQueryImplInfo(false, sql));
152 }
153 
154 ///ditto
155 deprecated("Use querySet instead.")
156 alias queryResult = querySet;
157 
158 /// Common implementation for mysql.protocol.commands.querySet and Prepared.querySet
159 package ResultSet querySetImpl(ColumnSpecialization[] csa, bool binary,
160 	Connection conn, ExecQueryImplInfo info)
161 {
162 	ulong ra;
163 	enforceEx!MYXNoResultRecieved(execQueryImpl(conn, info, ra));
164 
165 	conn._rsh = ResultSetHeaders(conn, conn._fieldCount);
166 	if (csa !is null)
167 		conn._rsh.addSpecializations(csa);
168 	conn._headersPending = false;
169 
170 	Row[] rows;
171 	while(true)
172 	{
173 		scope(failure) conn.kill();
174 
175 		auto packet = conn.getPacket();
176 		if(packet.isEOFPacket())
177 			break;
178 		rows ~= Row(conn, packet, conn._rsh, binary);
179 		// As the row fetches more data while incomplete, it might already have
180 		// fetched the EOF marker, so we have to check it again
181 		if(!packet.empty && packet.isEOFPacket())
182 			break;
183 	}
184 	conn._rowsPending = conn._binaryPending = false;
185 
186 	return ResultSet(rows, conn._rsh.fieldNames);
187 }
188 
189 /++
190 Execute a one-off SQL command for the case where you expect a result set,
191 and want to deal with it a row at a time.
192 
193 Use this method when you are not going to be using the same command repeatedly.
194 This method will throw if the SQL command does not produce a result set.
195 
196 If there are long data items among the expected result columns you can specify
197 that they are to be subject to chunked transfer via a delegate.
198 
199 Params: csa = An optional array of ColumnSpecialization structs.
200 Returns: A (possibly empty) ResultRange.
201 +/
202 ResultRange query(Connection conn, string sql, ColumnSpecialization[] csa = null)
203 {
204 	return queryImpl(csa, conn, ExecQueryImplInfo(false, sql));
205 }
206 
207 ///ditto
208 deprecated("Use query instead.")
209 alias querySequence = query;
210 
211 /// Common implementation for mysql.protocol.commands.query and Prepared.query
212 package ResultRange queryImpl(ColumnSpecialization[] csa,
213 	Connection conn, ExecQueryImplInfo info)
214 {
215 	ulong ra;
216 	enforceEx!MYXNoResultRecieved(execQueryImpl(conn, info, ra));
217 
218 	conn._rsh = ResultSetHeaders(conn, conn._fieldCount);
219 	if (csa !is null)
220 		conn._rsh.addSpecializations(csa);
221 
222 	conn._headersPending = false;
223 	return ResultRange(conn, conn._rsh, conn._rsh.fieldNames);
224 }
225 
226 /++
227 Executes a one-off SQL command and returns the first row received, or null
228 if none. Useful for the case where you expect a (possibly empty) result set,
229 and you're either only expecting one row, or only care about the first row.
230 
231 Use this method when you are not going to be using the same command repeatedly.
232 This method will throw if the SQL command does not produce a result set.
233 
234 If there are long data items among the expected result columns you can specify
235 that they are to be subject to chunked transfer via a delegate.
236 
237 Params: csa = An optional array of ColumnSpecialization structs.
238 Returns: Nullable!Row: This will be null (check via Nullable.isNull) if the
239 query resulted in an empty result set.
240 +/
241 Nullable!Row queryRow(Connection conn, string sql, ColumnSpecialization[] csa = null)
242 {
243 	return queryRowImpl(csa, conn, ExecQueryImplInfo(false, sql));
244 }
245 
246 /// Common implementation for mysql.protocol.commands.querySet and Prepared.querySet
247 package Nullable!Row queryRowImpl(ColumnSpecialization[] csa, Connection conn,
248 	ExecQueryImplInfo info)
249 {
250 	auto results = queryImpl(csa, conn, info);
251 	if(results.empty)
252 		return Nullable!Row();
253 	else
254 	{
255 		auto row = results.front;
256 		results.close();
257 		return Nullable!Row(row);
258 	}
259 }
260 
261 /++
262 Execute a one-off SQL command to place result values into a set of D variables.
263 
264 Use this method when you are not going to be using the same command repeatedly.
265 It will throw if the specified command does not produce a result set, or if
266 any column type is incompatible with the corresponding D variable.
267 
268 Params: args = A tuple of D variables to receive the results.
269 +/
270 void queryRowTuple(T...)(Connection conn, string sql, ref T args)
271 {
272 	return queryRowTupleImpl(conn, ExecQueryImplInfo(false, sql), args);
273 }
274 
275 ///ditto
276 deprecated("Use queryRowTuple instead.")
277 alias queryTuple = queryRowTuple;
278 
279 /// Common implementation for mysql.protocol.commands.queryRowTuple and Prepared.queryRowTuple
280 package void queryRowTupleImpl(T...)(Connection conn, ExecQueryImplInfo info, ref T args)
281 {
282 	ulong ra;
283 	enforceEx!MYXNoResultRecieved(execQueryImpl(conn, info, ra));
284 
285 	Row rr = conn.getNextRow();
286 	/+if (!rr._valid)   // The result set was empty - not a crime.
287 		return;+/
288 	enforceEx!MYX(rr._values.length == args.length, "Result column count does not match the target tuple.");
289 	foreach (size_t i, dummy; args)
290 	{
291 		enforceEx!MYX(typeid(args[i]).toString() == rr._values[i].type.toString(),
292 			"Tuple "~to!string(i)~" type and column type are not compatible.");
293 		args[i] = rr._values[i].get!(typeof(args[i]));
294 	}
295 	// If there were more rows, flush them away
296 	// Question: Should I check in purgeResult and throw if there were - it's very inefficient to
297 	// allow sloppy SQL that does not ensure just one row!
298 	conn.purgeResult();
299 }
300 
301 /++
302 Executes a one-off SQL command and returns a single value: The the first column
303 of the first row received. Useful for the case where you expect a
304 (possibly empty) result set, and you're either only expecting one value, or
305 only care about the first value.
306 
307 If the query did not produce any rows, or the rows it produced have zero columns,
308 this will return `Nullable!Variant()`, ie, null. Test for this with `result.isNull`.
309 
310 If the query DID produce a result, but the value actually received is NULL,
311 then `result.isNull` will be FALSE, and `result.get` will produce a Variant
312 which CONTAINS null. Check for this with `result.get.type == typeid(typeof(null))`.
313 
314 Use this method when you are not going to be using the same command repeatedly.
315 This method will throw if the SQL command does not produce a result set.
316 
317 If there are long data items among the expected result columns you can specify
318 that they are to be subject to chunked transfer via a delegate.
319 
320 Params: csa = An optional array of ColumnSpecialization structs.
321 Returns: Nullable!Variant: This will be null (check via Nullable.isNull) if the
322 query resulted in an empty result set.
323 +/
324 Nullable!Variant queryValue(Connection conn, string sql, ColumnSpecialization[] csa = null)
325 {
326 	return queryValueImpl(csa, conn, ExecQueryImplInfo(false, sql));
327 }
328 
329 /// Common implementation for mysql.protocol.commands.querySet and Prepared.querySet
330 package Nullable!Variant queryValueImpl(ColumnSpecialization[] csa, Connection conn,
331 	ExecQueryImplInfo info)
332 {
333 	auto results = queryImpl(csa, conn, info);
334 	if(results.empty)
335 		return Nullable!Variant();
336 	else
337 	{
338 		auto row = results.front;
339 		results.close();
340 		
341 		if(row.length == 0)
342 			return Nullable!Variant();
343 		else
344 			return Nullable!Variant(row[0]);
345 	}
346 }
347 
348 /++
349 Encapsulation of an SQL command or query.
350 
351 A Command be be either a one-off SQL query, or may use a prepared statement.
352 Commands that are expected to return a result set - queries - have distinctive methods
353 that are enforced. That is it will be an error to call such a method with an SQL command
354 that does not produce a result set.
355 +/
356 struct Command
357 {
358 package:
359 	Connection _con;    // This can disappear along with Command
360 	string _sql; // This can disappear along with Command
361 	string _prevFunc; // Has to do with stored procedures
362 	Prepared _prepared; // The current prepared statement info
363 
364 public:
365 
366 	/++
367 	Construct a naked Command object
368 	
369 	Params: con = A Connection object to communicate with the server
370 	+/
371 	// This can disappear along with Command
372 	this(Connection con)
373 	{
374 		_con = con;
375 		_con.resetPacket();
376 	}
377 
378 	/++
379 	Construct a Command object complete with SQL
380 	
381 	Params: con = A Connection object to communicate with the server
382 	               sql = SQL command string.
383 	+/
384 	// This can disappear along with Command
385 	this(Connection con, const(char)[] sql)
386 	{
387 		_sql = sql.idup;
388 		this(con);
389 	}
390 
391 	@property
392 	{
393 		/// Get the current SQL for the Command
394 		// This can disappear along with Command
395 		const(char)[] sql() pure const nothrow { return _sql; }
396 
397 		/++
398 		Set a new SQL command.
399 		
400 		This can have quite profound side effects. It resets the Command to
401 		an initial state. If a query has been issued on the Command that
402 		produced a result set, then all of the result set packets - field
403 		description sequence, EOF packet, result rows sequence, EOF packet
404 		must be flushed from the server before any further operation can be
405 		performed on the Connection. If you want to write speedy and efficient
406 		MySQL programs, you should bear this in mind when designing your
407 		queries so that you are not requesting many rows when one would do.
408 		
409 		Params: sql = SQL command string.
410 		+/
411 		// This can disappear along with Command
412 		const(char)[] sql(const(char)[] sql)
413 		{
414 			if (_prepared.isPrepared)
415 			{
416 				_prepared.release();
417 				_prevFunc = null; 
418 			}
419 			return this._sql = sql.idup;
420 		}
421 	}
422 
423 	/++
424 	Submit an SQL command to the server to be compiled into a prepared statement.
425 	
426 	The result of a successful outcome will be a statement handle - an ID -
427 	for the prepared statement, a count of the parameters required for
428 	excution of the statement, and a count of the columns that will be present
429 	in any result set that the command generates. Thes values will be stored
430 	in in the Command struct.
431 	
432 	The server will then proceed to send prepared statement headers,
433 	including parameter descriptions, and result set field descriptions,
434 	followed by an EOF packet.
435 	
436 	If there is an existing statement handle in the Command struct, that
437 	prepared statement is released.
438 	
439 	Throws: MySQLException if there are pending result set items, or if the
440 	server has a problem.
441 	+/
442 	deprecated("Use Prepare.this(Connection conn, string sql) instead")
443 	void prepare()
444 	{
445 		_prepared = .prepare(_con, _sql);
446 	}
447 
448 	/++
449 	Release a prepared statement.
450 	
451 	This method tells the server that it can dispose of the information it
452 	holds about the current prepared statement, and resets the Command
453 	object to an initial state in that respect.
454 	+/
455 	deprecated("Use Prepared.release instead")
456 	void releaseStatement()
457 	{
458 		if (_prepared.isPrepared)
459 			_prepared.release();
460 	}
461 
462 	/++
463 	Flush any outstanding result set elements.
464 	
465 	When the server responds to a command that produces a result set, it
466 	queues the whole set of corresponding packets over the current connection.
467 	Before that Connection can embark on any new command, it must receive
468 	all of those packets and junk them.
469 	http://www.mysqlperformanceblog.com/2007/07/08/mysql-net_write_timeout-vs-wait_timeout-and-protocol-notes/
470 	+/
471 	deprecated("Use Connection.purgeResult() instead.")
472 	ulong purgeResult()
473 	{
474 		return _con.purgeResult();
475 	}
476 
477 	/++
478 	Bind a D variable to a prepared statement parameter.
479 	
480 	In this implementation, binding comprises setting a value into the
481 	appropriate element of an array of Variants which represent the
482 	parameters, and setting any required specializations.
483 	
484 	To bind to some D variable, we set the corrsponding variant with its
485 	address, so there is no need to rebind between calls to execPreparedXXX.
486 	+/
487 	deprecated("Use Prepared.setArg instead")
488 	void bindParameter(T)(ref T val, size_t pIndex, ParameterSpecialization psn = PSN(0, SQLType.INFER_FROM_D_TYPE, 0, null))
489 	{
490 		enforceEx!MYX(_prepared.isPrepared, "The statement must be prepared before parameters are bound.");
491 		_prepared.setArg(pIndex, &val, psn);
492 	}
493 
494 	/++
495 	Bind a tuple of D variables to the parameters of a prepared statement.
496 	
497 	You can use this method to bind a set of variables if you don't need any specialization,
498 	that is there will be no null values, and chunked transfer is not neccessary.
499 	
500 	The tuple must match the required number of parameters, and it is the programmer's
501 	responsibility to ensure that they are of appropriate types.
502 	+/
503 	deprecated("Use Prepared.setArgs instead")
504 	void bindParameterTuple(T...)(ref T args)
505 	{
506 		enforceEx!MYX(_prepared.isPrepared, "The statement must be prepared before parameters are bound.");
507 		enforceEx!MYX(args.length == _prepared.numArgs, "Argument list supplied does not match the number of parameters.");
508 		foreach (size_t i, dummy; args)
509 			_prepared.setArg(&args[i], i);
510 	}
511 
512 	/++
513 	Bind a Variant[] as the parameters of a prepared statement.
514 	
515 	You can use this method to bind a set of variables in Variant form to
516 	the parameters of a prepared statement.
517 	
518 	Parameter specializations can be added if required. This method could be
519 	used to add records from a data entry form along the lines of
520 	------------
521 	auto c = Command(con, "insert into table42 values(?, ?, ?)");
522 	c.prepare();
523 	Variant[] va;
524 	va.length = 3;
525 	DataRecord dr;    // Some data input facility
526 	ulong ra;
527 	do
528 	{
529 	    dr.get();
530 	    va[0] = dr("Name");
531 	    va[1] = dr("City");
532 	    va[2] = dr("Whatever");
533 	    c.bindParameters(va);
534 	    c.execPrepared(ra);
535 	} while(tod < "17:30");
536 	------------
537 	Params: va = External list of Variants to be used as parameters
538 	               psnList = any required specializations
539 	+/
540 	deprecated("Use Prepared.setArgs instead")
541 	void bindParameters(Variant[] va, ParameterSpecialization[] psnList= null)
542 	{
543 		_prepared.setArgs(va, psnList);
544 	}
545 
546 	/++
547 	Access a prepared statement parameter for update.
548 	
549 	Another style of usage would simply update the parameter Variant directly
550 	
551 	------------
552 	c.param(0) = 42;
553 	c.param(1) = "The answer";
554 	------------
555 	Params: index = The zero based index
556 	+/
557 	deprecated("Use Prepared.getArg to get and Prepared.setArg to set.")
558 	ref Variant param(size_t index) pure
559 	{
560 		enforceEx!MYX(_prepared.isPrepared, "The statement must be prepared before parameters are bound.");
561 		enforceEx!MYX(index < _prepared.numArgs, "Parameter index out of range.");
562 		return _prepared._inParams[index];
563 	}
564 
565 	/++
566 	Prepared statement parameter getter.
567 
568 	Params: index = The zero based index
569 	+/
570 	deprecated("Use Prepared.getArg instead.")
571 	Variant getArg(size_t index)
572 	{
573 		enforceEx!MYX(_prepared.isPrepared, "The statement must be prepared before parameters are bound.");
574 		return _prepared.getArg(index);
575 	}
576 
577 	/++
578 	Sets a prepared statement parameter to NULL.
579 	
580 	Params: index = The zero based index
581 	+/
582 	deprecated("Use Prepared.setNullArg instead.")
583 	void setNullParam(size_t index)
584 	{
585 		enforceEx!MYX(_prepared.isPrepared, "The statement must be prepared before parameters are bound.");
586 		_prepared.setNullArg(index);
587 	}
588 
589 	/++
590 	Execute a one-off SQL command.
591 	
592 	Use this method when you are not going to be using the same command repeatedly.
593 	It can be used with commands that don't produce a result set, or those that
594 	do. If there is a result set its existence will be indicated by the return value.
595 	
596 	Any result set can be accessed vis Connection.getNextRow(), but you should really be
597 	using execSQLResult() or execSQLSequence() for such queries.
598 	
599 	Params: ra = An out parameter to receive the number of rows affected.
600 	Returns: true if there was a (possibly empty) result set.
601 	+/
602 	deprecated("Use the free-standing function .exec instead")
603 	bool execSQL(out ulong ra)
604 	{
605 		return .execQueryImpl(_con, ExecQueryImplInfo(false, _sql), ra);
606 	}
607 
608 	///ditto
609 	deprecated("Use the free-standing function .exec instead")
610 	bool execSQL()
611 	{
612 		ulong ra;
613 		return .execQueryImpl(_con, ExecQueryImplInfo(false, _sql), ra);
614 	}
615 	
616 	/++
617 	Execute a one-off SQL command for the case where you expect a result set,
618 	and want it all at once.
619 	
620 	Use this method when you are not going to be using the same command repeatedly.
621 	This method will throw if the SQL command does not produce a result set.
622 	
623 	If there are long data items among the expected result columns you can specify
624 	that they are to be subject to chunked transfer via a delegate.
625 	
626 	Params: csa = An optional array of ColumnSpecialization structs.
627 	Returns: A (possibly empty) ResultSet.
628 	+/
629 	deprecated("Use the free-standing function .querySet instead")
630 	ResultSet execSQLResult(ColumnSpecialization[] csa = null)
631 	{
632 		return .querySet(_con, _sql, csa);
633 	}
634 
635 	/++
636 	Execute a one-off SQL command for the case where you expect a result set,
637 	and want to deal with it a row at a time.
638 	
639 	Use this method when you are not going to be using the same command repeatedly.
640 	This method will throw if the SQL command does not produce a result set.
641 	
642 	If there are long data items among the expected result columns you can specify
643 	that they are to be subject to chunked transfer via a delegate.
644 
645 	Params: csa = An optional array of ColumnSpecialization structs.
646 	Returns: A (possibly empty) ResultRange.
647 	+/
648 	deprecated("Use the free-standing function .query instead")
649 	ResultRange execSQLSequence(ColumnSpecialization[] csa = null)
650 	{
651 		return .query(_con, _sql, csa);
652 	}
653 
654 	/++
655 	Execute a one-off SQL command to place result values into a set of D variables.
656 	
657 	Use this method when you are not going to be using the same command repeatedly.
658 	It will throw if the specified command does not produce a result set, or if
659 	any column type is incompatible with the corresponding D variable.
660 	
661 	Params: args = A tuple of D variables to receive the results.
662 	Returns: true if there was a (possibly empty) result set.
663 	+/
664 	deprecated("Use the free-standing function .queryRowTuple instead")
665 	void execSQLTuple(T...)(ref T args)
666 	{
667 		.queryRowTuple(_con, _sql, args);
668 	}
669 
670 	/++
671 	Execute a prepared command.
672 	
673 	Use this method when you will use the same SQL command repeatedly.
674 	It can be used with commands that don't produce a result set, or those that
675 	do. If there is a result set its existence will be indicated by the return value.
676 	
677 	Any result set can be accessed vis Connection.getNextRow(), but you should really be
678 	using execPreparedResult() or execPreparedSequence() for such queries.
679 	
680 	Params: ra = An out parameter to receive the number of rows affected.
681 	Returns: true if there was a (possibly empty) result set.
682 	+/
683 	deprecated("Use Prepared.exec instead")
684 	bool execPrepared(out ulong ra)
685 	{
686 		enforceEx!MYX(_prepared.isPrepared, "The statement must be prepared.");
687 		return _prepared.execQueryImpl2(ra);
688 	}
689 
690 	/++
691 	Execute a prepared SQL command for the case where you expect a result set,
692 	and want it all at once.
693 	
694 	Use this method when you will use the same command repeatedly.
695 	This method will throw if the SQL command does not produce a result set.
696 	
697 	If there are long data items among the expected result columns you can specify
698 	that they are to be subject to chunked transfer via a delegate.
699 	
700 	Params: csa = An optional array of ColumnSpecialization structs.
701 	Returns: A (possibly empty) ResultSet.
702 	+/
703 	deprecated("Use Prepared.querySet instead")
704 	ResultSet execPreparedResult(ColumnSpecialization[] csa = null)
705 	{
706 		enforceEx!MYX(_prepared.isPrepared, "The statement must be prepared.");
707 		return _prepared.querySet(csa);
708 	}
709 
710 	/++
711 	Execute a prepared SQL command for the case where you expect a result set,
712 	and want to deal with it one row at a time.
713 	
714 	Use this method when you will use the same command repeatedly.
715 	This method will throw if the SQL command does not produce a result set.
716 	
717 	If there are long data items among the expected result columns you can
718 	specify that they are to be subject to chunked transfer via a delegate.
719 
720 	Params: csa = An optional array of ColumnSpecialization structs.
721 	Returns: A (possibly empty) ResultRange.
722 	+/
723 	deprecated("Use Prepared.query instead")
724 	ResultRange execPreparedSequence(ColumnSpecialization[] csa = null)
725 	{
726 		enforceEx!MYX(_prepared.isPrepared, "The statement must be prepared.");
727 		return _prepared.query(csa);
728 	}
729 
730 	/++
731 	Execute a prepared SQL command to place result values into a set of D variables.
732 	
733 	Use this method when you will use the same command repeatedly.
734 	It will throw if the specified command does not produce a result set, or
735 	if any column type is incompatible with the corresponding D variable
736 	
737 	Params: args = A tuple of D variables to receive the results.
738 	Returns: true if there was a (possibly empty) result set.
739 	+/
740 	deprecated("Use Prepared.queryRowTuple instead")
741 	void execPreparedTuple(T...)(ref T args)
742 	{
743 		enforceEx!MYX(_prepared.isPrepared, "The statement must be prepared.");
744 		_prepared.queryRowTuple(args);
745 	}
746 
747 	/++
748 	Get the next Row of a pending result set.
749 	
750 	This method can be used after either execSQL() or execPrepared() have returned true
751 	to retrieve result set rows sequentially.
752 	
753 	Similar functionality is available via execSQLSequence() and execPreparedSequence() in
754 	which case the interface is presented as a forward range of Rows.
755 	
756 	This method allows you to deal with very large result sets either a row at a time,
757 	or by feeding the rows into some suitable container such as a linked list.
758 	
759 	Returns: A Row object.
760 	+/
761 	deprecated("Use Connection.getNextRow() instead.")
762 	Row getNextRow()
763 	{
764 		return _con.getNextRow();
765 	}
766 
767 	/++
768 	Execute a stored function, with any required input variables, and store the
769 	return value into a D variable.
770 	
771 	For this method, no query string is to be provided. The required one is of
772 	the form "select foo(?, ? ...)". The method generates it and the appropriate
773 	bindings - in, and out. Chunked transfers are not supported in either
774 	direction. If you need them, create the parameters separately, then use
775 	execPreparedResult() to get a one-row, one-column result set.
776 	
777 	If it is not possible to convert the column value to the type of target,
778 	then execFunction will throw. If the result is NULL, that is indicated
779 	by a false return value, and target is unchanged.
780 	
781 	In the interest of performance, this method assumes that the user has the
782 	equired information about the number and types of IN parameters and the
783 	type of the output variable. In the same interest, if the method is called
784 	repeatedly for the same stored function, prepare() is omitted after the first call.
785 	
786 	WARNING: This function is not currently unittested.
787 
788 	Params:
789 	   T = The type of the variable to receive the return result.
790 	   U = type tuple of arguments
791 	   name = The name of the stored function.
792 	   target = the D variable to receive the stored function return result.
793 	   args = The list of D variables to act as IN arguments to the stored function.
794 	
795 	+/
796 	deprecated("Use prepareFunction instead")
797 	bool execFunction(T, U...)(string name, ref T target, U args)
798 	{
799 		bool repeatCall = name == _prevFunc;
800 		enforceEx!MYX(repeatCall || !_prepared.isPrepared, "You must not prepare a statement before calling execFunction");
801 
802 		if(!repeatCall)
803 		{
804 			_prepared = prepareFunction(_con, name, U.length);
805 			_prevFunc = name;
806 		}
807 
808 		_prepared.setArgs(args);
809 		ulong ra;
810 		enforceEx!MYX(_prepared.execQueryImpl2(ra), "The executed query did not produce a result set.");
811 		Row rr = _con.getNextRow();
812 		/+enforceEx!MYX(rr._valid, "The result set was empty.");+/
813 		enforceEx!MYX(rr._values.length == 1, "Result was not a single column.");
814 		enforceEx!MYX(typeid(target).toString() == rr._values[0].type.toString(),
815 						"Target type and column type are not compatible.");
816 		if (!rr.isNull(0))
817 			target = rr._values[0].get!(T);
818 		// If there were more rows, flush them away
819 		// Question: Should I check in purgeResult and throw if there were - it's very inefficient to
820 		// allow sloppy SQL that does not ensure just one row!
821 		_con.purgeResult();
822 		return !rr.isNull(0);
823 	}
824 
825 	/++
826 	Execute a stored procedure, with any required input variables.
827 	
828 	For this method, no query string is to be provided. The required one is
829 	of the form "call proc(?, ? ...)". The method generates it and the
830 	appropriate in bindings. Chunked transfers are not supported. If you
831 	need them, create the parameters separately, then use execPrepared() or
832 	execPreparedResult().
833 	
834 	In the interest of performance, this method assumes that the user has
835 	the required information about the number and types of IN parameters.
836 	In the same interest, if the method is called repeatedly for the same
837 	stored function, prepare() and other redundant operations are omitted
838 	after the first call.
839 	
840 	OUT parameters are not currently supported. It should generally be
841 	possible with MySQL to present them as a result set.
842 	
843 	WARNING: This function is not currently unittested.
844 
845 	Params:
846 		T = Type tuple
847 		name = The name of the stored procedure.
848 		args = Tuple of args
849 	Returns: True if the SP created a result set.
850 	+/
851 	deprecated("Use prepareProcedure instead")
852 	bool execProcedure(T...)(string name, ref T args)
853 	{
854 		bool repeatCall = name == _prevFunc;
855 		enforceEx!MYX(repeatCall || !_prepared.isPrepared, "You must not prepare a statement before calling execProcedure");
856 
857 		if(!repeatCall)
858 		{
859 			_prepared = prepareProcedure(_con, name, T.length);
860 			_prevFunc = name;
861 		}
862 
863 		_prepared.setArgs(args);
864 		ulong ra;
865 		return _prepared.execQueryImpl2(ra);
866 	}
867 
868 	/// After a command that inserted a row into a table with an auto-increment
869 	/// ID column, this method allows you to retrieve the last insert ID.
870 	deprecated("Use Connection.lastInsertID instead")
871 	@property ulong lastInsertID() pure const nothrow { return _con.lastInsertID; }
872 
873 	/// Gets the number of parameters in this Command
874 	deprecated("Use Prepared.numArgs instead")
875 	@property ushort numParams() pure const nothrow
876 	{
877 		return _prepared.numArgs;
878 	}
879 
880 	/// Gets whether rows are pending
881 	deprecated("Use Connection.rowsPending instead")
882 	@property bool rowsPending() pure const nothrow { return _con.rowsPending; }
883 
884 	/// Gets the result header's field descriptions.
885 	deprecated("Use Connection.resultFieldDescriptions instead")
886 	@property FieldDescription[] resultFieldDescriptions() pure { return _con.resultFieldDescriptions; }
887 
888 	/// Gets the prepared header's field descriptions.
889 	deprecated("Use Prepared.preparedFieldDescriptions instead")
890 	@property FieldDescription[] preparedFieldDescriptions() pure { return _prepared._psh.fieldDescriptions; }
891 
892 	/// Gets the prepared header's param descriptions.
893 	deprecated("Use Prepared.preparedParamDescriptions instead")
894 	@property ParamDescription[] preparedParamDescriptions() pure { return _prepared._psh.paramDescriptions; }
895 }