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