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