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.comms;
23 import mysql.protocol.constants;
24 import mysql.protocol.extra_types;
25 import mysql.protocol.packets;
26 import mysql.result;
27 
28 /// This feature is not yet implemented. It currently has no effect.
29 /+
30 A struct to represent specializations of returned statement columns.
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 bool argument `finished` 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; /// In bytes
49 	void delegate(const(ubyte)[] chunk, bool finished) chunkDelegate;
50 }
51 ///ditto
52 alias CSN = ColumnSpecialization;
53 
54 @("columnSpecial")
55 debug(MYSQLN_TESTS)
56 unittest
57 {
58 	import std.array;
59 	import std.range;
60 	import mysql.test.common;
61 	mixin(scopedCn);
62 
63 	// Setup
64 	cn.exec("DROP TABLE IF EXISTS `columnSpecial`");
65 	cn.exec("CREATE TABLE `columnSpecial` (
66 		`data` LONGBLOB
67 	) ENGINE=InnoDB DEFAULT CHARSET=utf8");
68 
69 	immutable totalSize = 1000; // Deliberately not a multiple of chunkSize below
70 	auto alph = cast(const(ubyte)[]) "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
71 	auto data = alph.cycle.take(totalSize).array;
72 	cn.exec("INSERT INTO `columnSpecial` VALUES (\""~(cast(string)data)~"\")");
73 
74 	// Common stuff
75 	int chunkSize;
76 	immutable selectSQL = "SELECT `data` FROM `columnSpecial`";
77 	ubyte[] received;
78 	bool lastValueOfFinished;
79 	void receiver(const(ubyte)[] chunk, bool finished)
80 	{
81 		assert(lastValueOfFinished == false);
82 
83 		if(finished)
84 			assert(chunk.length == chunkSize);
85 		else
86 			assert(chunk.length < chunkSize); // Not always true in general, but true in this unittest
87 
88 		received ~= chunk;
89 		lastValueOfFinished = finished;
90 	}
91 
92 	// Sanity check
93 	auto value = cn.queryValue(selectSQL);
94 	assert(!value.isNull);
95 	assert(value.get == data);
96 
97 	// Use ColumnSpecialization with sql string,
98 	// and totalSize as a multiple of chunkSize
99 	{
100 		chunkSize = 100;
101 		assert(cast(int)(totalSize / chunkSize) * chunkSize == totalSize);
102 		auto columnSpecial = ColumnSpecialization(0, 0xfc, chunkSize, &receiver);
103 		
104 		received = null;
105 		lastValueOfFinished = false;
106 		value = cn.queryValue(selectSQL, [columnSpecial]);
107 		assert(!value.isNull);
108 		assert(value.get == data);
109 		//TODO: ColumnSpecialization is not yet implemented
110 		//assert(lastValueOfFinished == true);
111 		//assert(received == data);
112 	}
113 	
114 	// Use ColumnSpecialization with sql string,
115 	// and totalSize as a non-multiple of chunkSize
116 	{
117 		chunkSize = 64;
118 		assert(cast(int)(totalSize / chunkSize) * chunkSize != totalSize);
119 		auto columnSpecial = ColumnSpecialization(0, 0xfc, chunkSize, &receiver);
120 
121 		received = null;
122 		lastValueOfFinished = false;
123 		value = cn.queryValue(selectSQL, [columnSpecial]);
124 		assert(!value.isNull);
125 		assert(value.get == data);
126 		//TODO: ColumnSpecialization is not yet implemented
127 		//assert(lastValueOfFinished == true);
128 		//assert(received == data);
129 	}
130 
131 	// Use ColumnSpecialization with prepared statement,
132 	// and totalSize as a multiple of chunkSize
133 	{
134 		chunkSize = 100;
135 		assert(cast(int)(totalSize / chunkSize) * chunkSize == totalSize);
136 		auto columnSpecial = ColumnSpecialization(0, 0xfc, chunkSize, &receiver);
137 
138 		received = null;
139 		lastValueOfFinished = false;
140 		auto prepared = cn.prepare(selectSQL);
141 		prepared.columnSpecials = [columnSpecial];
142 		value = cn.queryValue(prepared);
143 		assert(!value.isNull);
144 		assert(value.get == data);
145 		//TODO: ColumnSpecialization is not yet implemented
146 		//assert(lastValueOfFinished == true);
147 		//assert(received == data);
148 	}
149 }
150 
151 /++
152 Execute an SQL command or prepared statement, such as INSERT/UPDATE/CREATE/etc.
153 
154 This method is intended for commands such as which do not produce a result set
155 (otherwise, use one of the `query` functions instead.) If the SQL command does
156 produces a result set (such as SELECT), `mysql.exceptions.MYXResultRecieved`
157 will be thrown.
158 
159 If `args` is supplied, the sql string will automatically be used as a prepared
160 statement. Prepared statements are automatically cached by mysql-native,
161 so there's no performance penalty for using this multiple times for the
162 same statement instead of manually preparing a statement.
163 
164 If `args` and `prepared` are both provided, `args` will be used,
165 and any arguments that are already set in the prepared statement
166 will automatically be replaced with `args` (note, just like calling
167 `mysql.prepared.Prepared.setArgs`, this will also remove all
168 `mysql.prepared.ParameterSpecialization` that may have been applied).
169 
170 Only use the `const(char[]) sql` overload that doesn't take `args`
171 when you are not going to be using the same
172 command repeatedly and you are CERTAIN all the data you're sending is properly
173 escaped. Otherwise, consider using overload that takes a `Prepared`.
174 
175 If you need to use any `mysql.prepared.ParameterSpecialization`, use
176 `mysql.connection.prepare` to manually create a `mysql.prepared.Prepared`,
177 and set your parameter specializations using `mysql.prepared.Prepared.setArg`
178 or `mysql.prepared.Prepared.setArgs`.
179 
180 Type_Mappings: $(TYPE_MAPPINGS)
181 
182 Params:
183 conn = An open `mysql.connection.Connection` to the database.
184 sql = The SQL command to be run.
185 prepared = The prepared statement to be run.
186 
187 Returns: The number of rows affected.
188 
189 Example:
190 ---
191 auto myInt = 7;
192 auto rowsAffected = myConnection.exec("INSERT INTO `myTable` (`a`) VALUES (?)", myInt);
193 ---
194 +/
195 ulong exec(Connection conn, const(char[]) sql)
196 {
197 	return execImpl(conn, ExecQueryImplInfo(false, sql));
198 }
199 ///ditto
200 ulong exec(T...)(Connection conn, const(char[]) sql, T args)
201 	if(T.length > 0 && !is(T[0] == Variant[]))
202 {
203 	auto prepared = conn.prepare(sql);
204 	prepared.setArgs(args);
205 	return exec(conn, prepared);
206 }
207 ///ditto
208 ulong exec(Connection conn, const(char[]) sql, Variant[] args)
209 {
210 	auto prepared = conn.prepare(sql);
211 	prepared.setArgs(args);
212 	return exec(conn, prepared);
213 }
214 
215 ///ditto
216 ulong exec(Connection conn, ref Prepared prepared)
217 {
218 	auto preparedInfo = conn.registerIfNeeded(prepared.sql);
219 	auto ra = execImpl(conn, prepared.getExecQueryImplInfo(preparedInfo.statementId));
220 	prepared._lastInsertID = conn.lastInsertID;
221 	return ra;
222 }
223 ///ditto
224 ulong exec(T...)(Connection conn, ref Prepared prepared, T args)
225 	if(T.length > 0 && !is(T[0] == Variant[]))
226 {
227 	prepared.setArgs(args);
228 	return exec(conn, prepared);
229 }
230 ///ditto
231 ulong exec(Connection conn, ref Prepared prepared, Variant[] args)
232 {
233 	prepared.setArgs(args);
234 	return exec(conn, prepared);
235 }
236 
237 ///ditto
238 ulong exec(Connection conn, ref BackwardCompatPrepared prepared)
239 {
240 	auto p = prepared.prepared;
241 	auto result = exec(conn, p);
242 	prepared._prepared = p;
243 	return result;
244 }
245 
246 /// Common implementation for `exec` overloads
247 package ulong execImpl(Connection conn, ExecQueryImplInfo info)
248 {
249 	ulong rowsAffected;
250 	bool receivedResultSet = execQueryImpl(conn, info, rowsAffected);
251 	if(receivedResultSet)
252 	{
253 		conn.purgeResult();
254 		throw new MYXResultRecieved();
255 	}
256 
257 	return rowsAffected;
258 }
259 
260 /++
261 Execute an SQL SELECT command or prepared statement.
262 
263 This returns an input range of `mysql.result.Row`, so if you need random access
264 to the `mysql.result.Row` elements, simply call
265 $(LINK2 https://dlang.org/phobos/std_array.html#array, `std.array.array()`)
266 on the result.
267 
268 If the SQL command does not produce a result set (such as INSERT/CREATE/etc),
269 then `mysql.exceptions.MYXNoResultRecieved` will be thrown. Use
270 `exec` instead for such commands.
271 
272 If `args` is supplied, the sql string will automatically be used as a prepared
273 statement. Prepared statements are automatically cached by mysql-native,
274 so there's no performance penalty for using this multiple times for the
275 same statement instead of manually preparing a statement.
276 
277 If `args` and `prepared` are both provided, `args` will be used,
278 and any arguments that are already set in the prepared statement
279 will automatically be replaced with `args` (note, just like calling
280 `mysql.prepared.Prepared.setArgs`, this will also remove all
281 `mysql.prepared.ParameterSpecialization` that may have been applied).
282 
283 Only use the `const(char[]) sql` overload that doesn't take `args`
284 when you are not going to be using the same
285 command repeatedly and you are CERTAIN all the data you're sending is properly
286 escaped. Otherwise, consider using overload that takes a `Prepared`.
287 
288 If you need to use any `mysql.prepared.ParameterSpecialization`, use
289 `mysql.connection.prepare` to manually create a `mysql.prepared.Prepared`,
290 and set your parameter specializations using `mysql.prepared.Prepared.setArg`
291 or `mysql.prepared.Prepared.setArgs`.
292 
293 Type_Mappings: $(TYPE_MAPPINGS)
294 
295 Params:
296 conn = An open `mysql.connection.Connection` to the database.
297 sql = The SQL command to be run.
298 prepared = The prepared statement to be run.
299 csa = Not yet implemented.
300 
301 Returns: A (possibly empty) `mysql.result.ResultRange`.
302 
303 Example:
304 ---
305 ResultRange oneAtATime = myConnection.query("SELECT * from `myTable`");
306 Row[]       allAtOnce  = myConnection.query("SELECT * from `myTable`").array;
307 
308 auto myInt = 7;
309 ResultRange rows = myConnection.query("SELECT * FROM `myTable` WHERE `a` = ?", myInt);
310 ---
311 +/
312 /+
313 Future text:
314 If there are long data items among the expected result columns you can use
315 the `csa` param to specify that they are to be subject to chunked transfer via a
316 delegate.
317 
318 csa = An optional array of `ColumnSpecialization` structs. If you need to
319 use this with a prepared statement, please use `mysql.prepared.Prepared.columnSpecials`.
320 +/
321 ResultRange query(Connection conn, const(char[]) sql, ColumnSpecialization[] csa = null)
322 {
323 	return queryImpl(csa, conn, ExecQueryImplInfo(false, sql));
324 }
325 ///ditto
326 ResultRange query(T...)(Connection conn, const(char[]) sql, T args)
327 	if(T.length > 0 && !is(T[0] == Variant[]) && !is(T[0] == ColumnSpecialization) && !is(T[0] == ColumnSpecialization[]))
328 {
329 	auto prepared = conn.prepare(sql);
330 	prepared.setArgs(args);
331 	return query(conn, prepared);
332 }
333 ///ditto
334 ResultRange query(Connection conn, const(char[]) sql, Variant[] args)
335 {
336 	auto prepared = conn.prepare(sql);
337 	prepared.setArgs(args);
338 	return query(conn, prepared);
339 }
340 
341 ///ditto
342 ResultRange query(Connection conn, ref Prepared prepared)
343 {
344 	auto preparedInfo = conn.registerIfNeeded(prepared.sql);
345 	auto result = queryImpl(prepared.columnSpecials, conn, prepared.getExecQueryImplInfo(preparedInfo.statementId));
346 	prepared._lastInsertID = conn.lastInsertID; // Conceivably, this might be needed when multi-statements are enabled.
347 	return result;
348 }
349 ///ditto
350 ResultRange query(T...)(Connection conn, ref Prepared prepared, T args)
351 	if(T.length > 0 && !is(T[0] == Variant[]) && !is(T[0] == ColumnSpecialization) && !is(T[0] == ColumnSpecialization[]))
352 {
353 	prepared.setArgs(args);
354 	return query(conn, prepared);
355 }
356 ///ditto
357 ResultRange query(Connection conn, ref Prepared prepared, Variant[] args)
358 {
359 	prepared.setArgs(args);
360 	return query(conn, prepared);
361 }
362 
363 ///ditto
364 ResultRange query(Connection conn, ref BackwardCompatPrepared prepared)
365 {
366 	auto p = prepared.prepared;
367 	auto result = query(conn, p);
368 	prepared._prepared = p;
369 	return result;
370 }
371 
372 /// Common implementation for `query` overloads
373 package ResultRange queryImpl(ColumnSpecialization[] csa,
374 	Connection conn, ExecQueryImplInfo info)
375 {
376 	ulong ra;
377 	enforce!MYXNoResultRecieved(execQueryImpl(conn, info, ra));
378 
379 	conn._rsh = ResultSetHeaders(conn, conn._fieldCount);
380 	if(csa !is null)
381 		conn._rsh.addSpecializations(csa);
382 
383 	conn._headersPending = false;
384 	return ResultRange(conn, conn._rsh, conn._rsh.fieldNames);
385 }
386 
387 /++
388 Execute an SQL SELECT command or prepared statement where you only want the
389 first `mysql.result.Row`, if any.
390 
391 If the SQL command does not produce a result set (such as INSERT/CREATE/etc),
392 then `mysql.exceptions.MYXNoResultRecieved` will be thrown. Use
393 `exec` instead for such commands.
394 
395 If `args` is supplied, the sql string will automatically be used as a prepared
396 statement. Prepared statements are automatically cached by mysql-native,
397 so there's no performance penalty for using this multiple times for the
398 same statement instead of manually preparing a statement.
399 
400 If `args` and `prepared` are both provided, `args` will be used,
401 and any arguments that are already set in the prepared statement
402 will automatically be replaced with `args` (note, just like calling
403 `mysql.prepared.Prepared.setArgs`, this will also remove all
404 `mysql.prepared.ParameterSpecialization` that may have been applied).
405 
406 Only use the `const(char[]) sql` overload that doesn't take `args`
407 when you are not going to be using the same
408 command repeatedly and you are CERTAIN all the data you're sending is properly
409 escaped. Otherwise, consider using overload that takes a `Prepared`.
410 
411 If you need to use any `mysql.prepared.ParameterSpecialization`, use
412 `mysql.connection.prepare` to manually create a `mysql.prepared.Prepared`,
413 and set your parameter specializations using `mysql.prepared.Prepared.setArg`
414 or `mysql.prepared.Prepared.setArgs`.
415 
416 Type_Mappings: $(TYPE_MAPPINGS)
417 
418 Params:
419 conn = An open `mysql.connection.Connection` to the database.
420 sql = The SQL command to be run.
421 prepared = The prepared statement to be run.
422 csa = Not yet implemented.
423 
424 Returns: `Nullable!(mysql.result.Row)`: This will be null (check via `Nullable.isNull`) if the
425 query resulted in an empty result set.
426 
427 Example:
428 ---
429 auto myInt = 7;
430 Nullable!Row row = myConnection.queryRow("SELECT * FROM `myTable` WHERE `a` = ?", myInt);
431 ---
432 +/
433 /+
434 Future text:
435 If there are long data items among the expected result columns you can use
436 the `csa` param to specify that they are to be subject to chunked transfer via a
437 delegate.
438 
439 csa = An optional array of `ColumnSpecialization` structs. If you need to
440 use this with a prepared statement, please use `mysql.prepared.Prepared.columnSpecials`.
441 +/
442 /+
443 Future text:
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 csa = An optional array of `ColumnSpecialization` structs. If you need to
449 use this with a prepared statement, please use `mysql.prepared.Prepared.columnSpecials`.
450 +/
451 Nullable!Row queryRow(Connection conn, const(char[]) sql, ColumnSpecialization[] csa = null)
452 {
453 	return queryRowImpl(csa, conn, ExecQueryImplInfo(false, sql));
454 }
455 ///ditto
456 Nullable!Row queryRow(T...)(Connection conn, const(char[]) sql, T args)
457 	if(T.length > 0 && !is(T[0] == Variant[]) && !is(T[0] == ColumnSpecialization) && !is(T[0] == ColumnSpecialization[]))
458 {
459 	auto prepared = conn.prepare(sql);
460 	prepared.setArgs(args);
461 	return queryRow(conn, prepared);
462 }
463 ///ditto
464 Nullable!Row queryRow(Connection conn, const(char[]) sql, Variant[] args)
465 {
466 	auto prepared = conn.prepare(sql);
467 	prepared.setArgs(args);
468 	return queryRow(conn, prepared);
469 }
470 
471 ///ditto
472 Nullable!Row queryRow(Connection conn, ref Prepared prepared)
473 {
474 	auto preparedInfo = conn.registerIfNeeded(prepared.sql);
475 	auto result = queryRowImpl(prepared.columnSpecials, conn, prepared.getExecQueryImplInfo(preparedInfo.statementId));
476 	prepared._lastInsertID = conn.lastInsertID; // Conceivably, this might be needed when multi-statements are enabled.
477 	return result;
478 }
479 ///ditto
480 Nullable!Row queryRow(T...)(Connection conn, ref Prepared prepared, T args)
481 	if(T.length > 0 && !is(T[0] == Variant[]) && !is(T[0] == ColumnSpecialization) && !is(T[0] == ColumnSpecialization[]))
482 {
483 	prepared.setArgs(args);
484 	return queryRow(conn, prepared);
485 }
486 ///ditto
487 Nullable!Row queryRow(Connection conn, ref Prepared prepared, Variant[] args)
488 {
489 	prepared.setArgs(args);
490 	return queryRow(conn, prepared);
491 }
492 
493 ///ditto
494 Nullable!Row queryRow(Connection conn, ref BackwardCompatPrepared prepared)
495 {
496 	auto p = prepared.prepared;
497 	auto result = queryRow(conn, p);
498 	prepared._prepared = p;
499 	return result;
500 }
501 
502 /// Common implementation for `querySet` overloads.
503 package Nullable!Row queryRowImpl(ColumnSpecialization[] csa, Connection conn,
504 	ExecQueryImplInfo info)
505 {
506 	auto results = queryImpl(csa, conn, info);
507 	if(results.empty)
508 		return Nullable!Row();
509 	else
510 	{
511 		auto row = results.front;
512 		results.close();
513 		return Nullable!Row(row);
514 	}
515 }
516 
517 /++
518 Execute an SQL SELECT command or prepared statement where you only want the
519 first `mysql.result.Row`, and place result values into a set of D variables.
520 
521 This method will throw if any column type is incompatible with the corresponding D variable.
522 
523 Unlike the other query functions, queryRowTuple will throw
524 `mysql.exceptions.MYX` if the result set is empty
525 (and thus the reference variables passed in cannot be filled).
526 
527 If the SQL command does not produce a result set (such as INSERT/CREATE/etc),
528 then `mysql.exceptions.MYXNoResultRecieved` will be thrown. Use
529 `exec` instead for such commands.
530 
531 Only use the `const(char[]) sql` overload when you are not going to be using the same
532 command repeatedly and you are CERTAIN all the data you're sending is properly
533 escaped. Otherwise, consider using overload that takes a `Prepared`.
534 
535 Type_Mappings: $(TYPE_MAPPINGS)
536 
537 Params:
538 conn = An open `mysql.connection.Connection` to the database.
539 sql = The SQL command to be run.
540 prepared = The prepared statement to be run.
541 args = The variables, taken by reference, to receive the values.
542 +/
543 void queryRowTuple(T...)(Connection conn, const(char[]) sql, ref T args)
544 {
545 	return queryRowTupleImpl(conn, ExecQueryImplInfo(false, sql), args);
546 }
547 
548 ///ditto
549 void queryRowTuple(T...)(Connection conn, ref Prepared prepared, ref T args)
550 {
551 	auto preparedInfo = conn.registerIfNeeded(prepared.sql);
552 	queryRowTupleImpl(conn, prepared.getExecQueryImplInfo(preparedInfo.statementId), args);
553 	prepared._lastInsertID = conn.lastInsertID; // Conceivably, this might be needed when multi-statements are enabled.
554 }
555 
556 ///ditto
557 void queryRowTuple(T...)(Connection conn, ref BackwardCompatPrepared prepared, ref T args)
558 {
559 	auto p = prepared.prepared;
560 	queryRowTuple(conn, p, args);
561 	prepared._prepared = p;
562 }
563 
564 /// Common implementation for `queryRowTuple` overloads.
565 package void queryRowTupleImpl(T...)(Connection conn, ExecQueryImplInfo info, ref T args)
566 {
567 	ulong ra;
568 	enforce!MYXNoResultRecieved(execQueryImpl(conn, info, ra));
569 
570 	Row rr = conn.getNextRow();
571 	/+if (!rr._valid)   // The result set was empty - not a crime.
572 		return;+/
573 	enforce!MYX(rr._values.length == args.length, "Result column count does not match the target tuple.");
574 	foreach (size_t i, dummy; args)
575 	{
576 		enforce!MYX(typeid(args[i]).toString() == rr._values[i].type.toString(),
577 			"Tuple "~to!string(i)~" type and column type are not compatible.");
578 		args[i] = rr._values[i].get!(typeof(args[i]));
579 	}
580 	// If there were more rows, flush them away
581 	// Question: Should I check in purgeResult and throw if there were - it's very inefficient to
582 	// allow sloppy SQL that does not ensure just one row!
583 	conn.purgeResult();
584 }
585 
586 // Test what happends when queryRowTuple receives no rows
587 @("queryRowTuple_noRows")
588 debug(MYSQLN_TESTS)
589 unittest
590 {
591 	import mysql.test.common : scopedCn, createCn;
592 	mixin(scopedCn);
593 
594 	cn.exec("DROP TABLE IF EXISTS `queryRowTuple_noRows`");
595 	cn.exec("CREATE TABLE `queryRowTuple_noRows` (
596 		`val` INTEGER
597 	) ENGINE=InnoDB DEFAULT CHARSET=utf8");
598 
599 	immutable selectSQL = "SELECT * FROM `queryRowTuple_noRows`";
600 	int queryTupleResult;
601 	assertThrown!MYX(cn.queryRowTuple(selectSQL, queryTupleResult));
602 }
603 
604 /++
605 Execute an SQL SELECT command or prepared statement and return a single value:
606 the first column of the first row received.
607 
608 If the query did not produce any rows, or the rows it produced have zero columns,
609 this will return `Nullable!Variant()`, ie, null. Test for this with `result.isNull`.
610 
611 If the query DID produce a result, but the value actually received is NULL,
612 then `result.isNull` will be FALSE, and `result.get` will produce a Variant
613 which CONTAINS null. Check for this with `result.get.type == typeid(typeof(null))`.
614 
615 If the SQL command does not produce a result set (such as INSERT/CREATE/etc),
616 then `mysql.exceptions.MYXNoResultRecieved` will be thrown. Use
617 `exec` instead for such commands.
618 
619 If `args` is supplied, the sql string will automatically be used as a prepared
620 statement. Prepared statements are automatically cached by mysql-native,
621 so there's no performance penalty for using this multiple times for the
622 same statement instead of manually preparing a statement.
623 
624 If `args` and `prepared` are both provided, `args` will be used,
625 and any arguments that are already set in the prepared statement
626 will automatically be replaced with `args` (note, just like calling
627 `mysql.prepared.Prepared.setArgs`, this will also remove all
628 `mysql.prepared.ParameterSpecialization` that may have been applied).
629 
630 Only use the `const(char[]) sql` overload that doesn't take `args`
631 when you are not going to be using the same
632 command repeatedly and you are CERTAIN all the data you're sending is properly
633 escaped. Otherwise, consider using overload that takes a `Prepared`.
634 
635 If you need to use any `mysql.prepared.ParameterSpecialization`, use
636 `mysql.connection.prepare` to manually create a `mysql.prepared.Prepared`,
637 and set your parameter specializations using `mysql.prepared.Prepared.setArg`
638 or `mysql.prepared.Prepared.setArgs`.
639 
640 Type_Mappings: $(TYPE_MAPPINGS)
641 
642 Params:
643 conn = An open `mysql.connection.Connection` to the database.
644 sql = The SQL command to be run.
645 prepared = The prepared statement to be run.
646 csa = Not yet implemented.
647 
648 Returns: `Nullable!Variant`: This will be null (check via `Nullable.isNull`) if the
649 query resulted in an empty result set.
650 
651 Example:
652 ---
653 auto myInt = 7;
654 Nullable!Variant value = myConnection.queryRow("SELECT * FROM `myTable` WHERE `a` = ?", myInt);
655 ---
656 +/
657 /+
658 Future text:
659 If there are long data items among the expected result columns you can use
660 the `csa` param to specify that they are to be subject to chunked transfer via a
661 delegate.
662 
663 csa = An optional array of `ColumnSpecialization` structs. If you need to
664 use this with a prepared statement, please use `mysql.prepared.Prepared.columnSpecials`.
665 +/
666 /+
667 Future text:
668 If there are long data items among the expected result columns you can use
669 the `csa` param to specify that they are to be subject to chunked transfer via a
670 delegate.
671 
672 csa = An optional array of `ColumnSpecialization` structs. If you need to
673 use this with a prepared statement, please use `mysql.prepared.Prepared.columnSpecials`.
674 +/
675 Nullable!Variant queryValue(Connection conn, const(char[]) sql, ColumnSpecialization[] csa = null)
676 {
677 	return queryValueImpl(csa, conn, ExecQueryImplInfo(false, sql));
678 }
679 ///ditto
680 Nullable!Variant queryValue(T...)(Connection conn, const(char[]) sql, T args)
681 	if(T.length > 0 && !is(T[0] == Variant[]) && !is(T[0] == ColumnSpecialization) && !is(T[0] == ColumnSpecialization[]))
682 {
683 	auto prepared = conn.prepare(sql);
684 	prepared.setArgs(args);
685 	return queryValue(conn, prepared);
686 }
687 ///ditto
688 Nullable!Variant queryValue(Connection conn, const(char[]) sql, Variant[] args)
689 {
690 	auto prepared = conn.prepare(sql);
691 	prepared.setArgs(args);
692 	return queryValue(conn, prepared);
693 }
694 
695 ///ditto
696 Nullable!Variant queryValue(Connection conn, ref Prepared prepared)
697 {
698 	auto preparedInfo = conn.registerIfNeeded(prepared.sql);
699 	auto result = queryValueImpl(prepared.columnSpecials, conn, prepared.getExecQueryImplInfo(preparedInfo.statementId));
700 	prepared._lastInsertID = conn.lastInsertID; // Conceivably, this might be needed when multi-statements are enabled.
701 	return result;
702 }
703 ///ditto
704 Nullable!Variant queryValue(T...)(Connection conn, ref Prepared prepared, T args)
705 	if(T.length > 0 && !is(T[0] == Variant[]) && !is(T[0] == ColumnSpecialization) && !is(T[0] == ColumnSpecialization[]))
706 {
707 	prepared.setArgs(args);
708 	return queryValue(conn, prepared);
709 }
710 ///ditto
711 Nullable!Variant queryValue(Connection conn, ref Prepared prepared, Variant[] args)
712 {
713 	prepared.setArgs(args);
714 	return queryValue(conn, prepared);
715 }
716 
717 ///ditto
718 Nullable!Variant queryValue(Connection conn, ref BackwardCompatPrepared prepared)
719 {
720 	auto p = prepared.prepared;
721 	auto result = queryValue(conn, p);
722 	prepared._prepared = p;
723 	return result;
724 }
725 
726 /// Common implementation for `queryValue` overloads.
727 package Nullable!Variant queryValueImpl(ColumnSpecialization[] csa, Connection conn,
728 	ExecQueryImplInfo info)
729 {
730 	auto results = queryImpl(csa, conn, info);
731 	if(results.empty)
732 		return Nullable!Variant();
733 	else
734 	{
735 		auto row = results.front;
736 		results.close();
737 		
738 		if(row.length == 0)
739 			return Nullable!Variant();
740 		else
741 			return Nullable!Variant(row[0]);
742 	}
743 }
744 
745 @("execOverloads")
746 debug(MYSQLN_TESTS)
747 unittest
748 {
749 	import std.array;
750 	import mysql.connection;
751 	import mysql.test.common;
752 	mixin(scopedCn);
753 	
754 	cn.exec("DROP TABLE IF EXISTS `execOverloads`");
755 	cn.exec("CREATE TABLE `execOverloads` (
756 		`i` INTEGER,
757 		`s` VARCHAR(50)
758 	) ENGINE=InnoDB DEFAULT CHARSET=utf8");
759 	
760 	immutable prepareSQL = "INSERT INTO `execOverloads` VALUES (?, ?)";
761 	
762 	// Do the inserts, using exec
763 	
764 	// exec: const(char[]) sql
765 	assert(cn.exec("INSERT INTO `execOverloads` VALUES (1, \"aa\")") == 1);
766 	assert(cn.exec(prepareSQL, 2, "bb") == 1);
767 	assert(cn.exec(prepareSQL, [Variant(3), Variant("cc")]) == 1);
768 
769 	// exec: prepared sql
770 	auto prepared = cn.prepare(prepareSQL);
771 	prepared.setArgs(4, "dd");
772 	assert(cn.exec(prepared) == 1);
773 
774 	assert(cn.exec(prepared, 5, "ee") == 1);
775 	assert(prepared.getArg(0) == 5);
776 	assert(prepared.getArg(1) == "ee");
777 	
778 	assert(cn.exec(prepared, [Variant(6), Variant("ff")]) == 1);
779 	assert(prepared.getArg(0) == 6);
780 	assert(prepared.getArg(1) == "ff");
781 	
782 	// exec: bcPrepared sql
783 	auto bcPrepared = cn.prepareBackwardCompatImpl(prepareSQL);
784 	bcPrepared.setArgs(7, "gg");
785 	assert(cn.exec(bcPrepared) == 1);
786 	assert(bcPrepared.getArg(0) == 7);
787 	assert(bcPrepared.getArg(1) == "gg");
788 	
789 	// Check results
790 	auto rows = cn.query("SELECT * FROM `execOverloads`").array();
791 	assert(rows.length == 7);
792 
793 	assert(rows[0].length == 2);
794 	assert(rows[1].length == 2);
795 	assert(rows[2].length == 2);
796 	assert(rows[3].length == 2);
797 	assert(rows[4].length == 2);
798 	assert(rows[5].length == 2);
799 	assert(rows[6].length == 2);
800 
801 	assert(rows[0][0] == 1);
802 	assert(rows[0][1] == "aa");
803 	assert(rows[1][0] == 2);
804 	assert(rows[1][1] == "bb");
805 	assert(rows[2][0] == 3);
806 	assert(rows[2][1] == "cc");
807 	assert(rows[3][0] == 4);
808 	assert(rows[3][1] == "dd");
809 	assert(rows[4][0] == 5);
810 	assert(rows[4][1] == "ee");
811 	assert(rows[5][0] == 6);
812 	assert(rows[5][1] == "ff");
813 	assert(rows[6][0] == 7);
814 	assert(rows[6][1] == "gg");
815 }
816 
817 @("queryOverloads")
818 debug(MYSQLN_TESTS)
819 unittest
820 {
821 	import std.array;
822 	import mysql.connection;
823 	import mysql.test.common;
824 	mixin(scopedCn);
825 	
826 	cn.exec("DROP TABLE IF EXISTS `queryOverloads`");
827 	cn.exec("CREATE TABLE `queryOverloads` (
828 		`i` INTEGER,
829 		`s` VARCHAR(50)
830 	) ENGINE=InnoDB DEFAULT CHARSET=utf8");
831 	cn.exec("INSERT INTO `queryOverloads` VALUES (1, \"aa\"), (2, \"bb\"), (3, \"cc\")");
832 
833 	immutable prepareSQL = "SELECT * FROM `queryOverloads` WHERE `i`=? AND `s`=?";
834 	
835 	// Test query
836 	{
837 		Row[] rows;
838 
839 		// String sql
840 		rows = cn.query("SELECT * FROM `queryOverloads` WHERE `i`=1 AND `s`=\"aa\"").array;
841 		assert(rows.length == 1);
842 		assert(rows[0].length == 2);
843 		assert(rows[0][0] == 1);
844 		assert(rows[0][1] == "aa");
845 
846 		rows = cn.query(prepareSQL, 2, "bb").array;
847 		assert(rows.length == 1);
848 		assert(rows[0].length == 2);
849 		assert(rows[0][0] == 2);
850 		assert(rows[0][1] == "bb");
851 
852 		rows = cn.query(prepareSQL, [Variant(3), Variant("cc")]).array;
853 		assert(rows.length == 1);
854 		assert(rows[0].length == 2);
855 		assert(rows[0][0] == 3);
856 		assert(rows[0][1] == "cc");
857 
858 		// Prepared sql
859 		auto prepared = cn.prepare(prepareSQL);
860 		prepared.setArgs(1, "aa");
861 		rows = cn.query(prepared).array;
862 		assert(rows.length == 1);
863 		assert(rows[0].length == 2);
864 		assert(rows[0][0] == 1);
865 		assert(rows[0][1] == "aa");
866 
867 		rows = cn.query(prepared, 2, "bb").array;
868 		assert(rows.length == 1);
869 		assert(rows[0].length == 2);
870 		assert(rows[0][0] == 2);
871 		assert(rows[0][1] == "bb");
872 
873 		rows = cn.query(prepared, [Variant(3), Variant("cc")]).array;
874 		assert(rows.length == 1);
875 		assert(rows[0].length == 2);
876 		assert(rows[0][0] == 3);
877 		assert(rows[0][1] == "cc");
878 
879 		// BCPrepared sql
880 		auto bcPrepared = cn.prepareBackwardCompatImpl(prepareSQL);
881 		bcPrepared.setArgs(1, "aa");
882 		rows = cn.query(bcPrepared).array;
883 		assert(rows.length == 1);
884 		assert(rows[0].length == 2);
885 		assert(rows[0][0] == 1);
886 		assert(rows[0][1] == "aa");
887 	}
888 
889 	// Test queryRow
890 	{
891 		Nullable!Row row;
892 
893 		// String sql
894 		row = cn.queryRow("SELECT * FROM `queryOverloads` WHERE `i`=1 AND `s`=\"aa\"");
895 		assert(!row.isNull);
896 		assert(row.length == 2);
897 		assert(row[0] == 1);
898 		assert(row[1] == "aa");
899 
900 		row = cn.queryRow(prepareSQL, 2, "bb");
901 		assert(!row.isNull);
902 		assert(row.length == 2);
903 		assert(row[0] == 2);
904 		assert(row[1] == "bb");
905 
906 		row = cn.queryRow(prepareSQL, [Variant(3), Variant("cc")]);
907 		assert(!row.isNull);
908 		assert(row.length == 2);
909 		assert(row[0] == 3);
910 		assert(row[1] == "cc");
911 
912 		// Prepared sql
913 		auto prepared = cn.prepare(prepareSQL);
914 		prepared.setArgs(1, "aa");
915 		row = cn.queryRow(prepared);
916 		assert(!row.isNull);
917 		assert(row.length == 2);
918 		assert(row[0] == 1);
919 		assert(row[1] == "aa");
920 
921 		row = cn.queryRow(prepared, 2, "bb");
922 		assert(!row.isNull);
923 		assert(row.length == 2);
924 		assert(row[0] == 2);
925 		assert(row[1] == "bb");
926 
927 		row = cn.queryRow(prepared, [Variant(3), Variant("cc")]);
928 		assert(!row.isNull);
929 		assert(row.length == 2);
930 		assert(row[0] == 3);
931 		assert(row[1] == "cc");
932 
933 		// BCPrepared sql
934 		auto bcPrepared = cn.prepareBackwardCompatImpl(prepareSQL);
935 		bcPrepared.setArgs(1, "aa");
936 		row = cn.queryRow(bcPrepared);
937 		assert(!row.isNull);
938 		assert(row.length == 2);
939 		assert(row[0] == 1);
940 		assert(row[1] == "aa");
941 	}
942 
943 	// Test queryRowTuple
944 	{
945 		int i;
946 		string s;
947 
948 		// String sql
949 		cn.queryRowTuple("SELECT * FROM `queryOverloads` WHERE `i`=1 AND `s`=\"aa\"", i, s);
950 		assert(i == 1);
951 		assert(s == "aa");
952 
953 		// Prepared sql
954 		auto prepared = cn.prepare(prepareSQL);
955 		prepared.setArgs(2, "bb");
956 		cn.queryRowTuple(prepared, i, s);
957 		assert(i == 2);
958 		assert(s == "bb");
959 
960 		// BCPrepared sql
961 		auto bcPrepared = cn.prepareBackwardCompatImpl(prepareSQL);
962 		bcPrepared.setArgs(3, "cc");
963 		cn.queryRowTuple(bcPrepared, i, s);
964 		assert(i == 3);
965 		assert(s == "cc");
966 	}
967 
968 	// Test queryValue
969 	{
970 		Nullable!Variant value;
971 
972 		// String sql
973 		value = cn.queryValue("SELECT * FROM `queryOverloads` WHERE `i`=1 AND `s`=\"aa\"");
974 		assert(!value.isNull);
975 		assert(value.get.type != typeid(typeof(null)));
976 		assert(value.get == 1);
977 
978 		value = cn.queryValue(prepareSQL, 2, "bb");
979 		assert(!value.isNull);
980 		assert(value.get.type != typeid(typeof(null)));
981 		assert(value.get == 2);
982 
983 		value = cn.queryValue(prepareSQL, [Variant(3), Variant("cc")]);
984 		assert(!value.isNull);
985 		assert(value.get.type != typeid(typeof(null)));
986 		assert(value.get == 3);
987 
988 		// Prepared sql
989 		auto prepared = cn.prepare(prepareSQL);
990 		prepared.setArgs(1, "aa");
991 		value = cn.queryValue(prepared);
992 		assert(!value.isNull);
993 		assert(value.get.type != typeid(typeof(null)));
994 		assert(value.get == 1);
995 
996 		value = cn.queryValue(prepared, 2, "bb");
997 		assert(!value.isNull);
998 		assert(value.get.type != typeid(typeof(null)));
999 		assert(value.get == 2);
1000 
1001 		value = cn.queryValue(prepared, [Variant(3), Variant("cc")]);
1002 		assert(!value.isNull);
1003 		assert(value.get.type != typeid(typeof(null)));
1004 		assert(value.get == 3);
1005 
1006 		// BCPrepared sql
1007 		auto bcPrepared = cn.prepareBackwardCompatImpl(prepareSQL);
1008 		bcPrepared.setArgs(1, "aa");
1009 		value = cn.queryValue(bcPrepared);
1010 		assert(!value.isNull);
1011 		assert(value.get.type != typeid(typeof(null)));
1012 		assert(value.get == 1);
1013 	}
1014 }