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