1 /++
2 Use a DB via plain SQL statements (safe version).
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 This is the @safe version of mysql's command module, and as such uses the @safe
11 rows and result ranges, and the `MySQLVal` type. For the `Variant` unsafe
12 version, please import `mysql.unsafe.commands`.
13 
14 $(SAFE_MIGRATION)
15 +/
16 
17 module mysql.safe.commands;
18 
19 import std.conv;
20 import std.exception;
21 import std.range;
22 import std.typecons;
23 import std.variant;
24 
25 import mysql.safe.connection;
26 import mysql.exceptions;
27 import mysql.safe.prepared;
28 import mysql.protocol.comms;
29 import mysql.protocol.constants;
30 import mysql.protocol.extra_types;
31 import mysql.protocol.packets;
32 import mysql.impl.result;
33 import mysql.types;
34 
35 /// This feature is not yet implemented. It currently has no effect.
36 /+
37 A struct to represent specializations of returned statement columns.
38 
39 If you are executing a query that will include result columns that are large objects,
40 it may be expedient to deal with the data as it is received rather than first buffering
41 it to some sort of byte array. These two variables allow for this. If both are provided
42 then the corresponding column will be fed to the stipulated delegate in chunks of
43 `chunkSize`, with the possible exception of the last chunk, which may be smaller.
44 The bool argument `finished` will be set to true when the last chunk is set.
45 
46 Be aware when specifying types for column specializations that for some reason the
47 field descriptions returned for a resultset have all of the types TINYTEXT, MEDIUMTEXT,
48 TEXT, LONGTEXT, TINYBLOB, MEDIUMBLOB, BLOB, and LONGBLOB lumped as type 0xfc
49 contrary to what it says in the protocol documentation.
50 +/
51 struct ColumnSpecialization
52 {
53 	size_t  cIndex;    // parameter number 0 - number of params-1
54 	ushort  type;
55 	uint    chunkSize; /// In bytes
56 	void delegate(const(ubyte)[] chunk, bool finished) @safe chunkDelegate;
57 }
58 ///ditto
59 alias CSN = ColumnSpecialization;
60 
61 @safe:
62 
63 /++
64 Execute an SQL command or prepared statement, such as INSERT/UPDATE/CREATE/etc.
65 
66 This method is intended for commands such as which do not produce a result set
67 (otherwise, use one of the `query` functions instead.) If the SQL command does
68 produces a result set (such as SELECT), `mysql.exceptions.MYXResultRecieved`
69 will be thrown.
70 
71 If `args` is supplied, the sql string will automatically be used as a prepared
72 statement. Prepared statements are automatically cached by mysql-native,
73 so there's no performance penalty for using this multiple times for the
74 same statement instead of manually preparing a statement.
75 
76 If `args` and `prepared` are both provided, `args` will be used,
77 and any arguments that are already set in the prepared statement
78 will automatically be replaced with `args` (note, just like calling
79 `mysql.impl.prepared.SafePrepared.setArgs`, this will also remove all
80 `mysql.impl.prepared.SafeParameterSpecialization` that may have been applied).
81 
82 Only use the `const(char[]) sql` overload that doesn't take `args`
83 when you are not going to be using the same
84 command repeatedly and you are CERTAIN all the data you're sending is properly
85 escaped. Otherwise, consider using overload that takes a `Prepared`.
86 
87 If you need to use any `mysql.impl.prepared.SafeParameterSpecialization`, use
88 `mysql.safe.connection.prepare` to manually create a
89 `mysql.impl.prepared.SafePrepared`, and set your parameter specializations using
90 `mysql.impl.prepared.SafePrepared.setArg` or
91 `mysql.impl.prepared.SafePrepared.setArgs`.
92 
93 Type_Mappings: $(TYPE_MAPPINGS)
94 
95 Params:
96 conn = An open `mysql.impl.connection.Connection` to the database.
97 sql = The SQL command to be run.
98 prepared = The prepared statement to be run.
99 args = The arguments to be passed in the `mysql.impl.prepared.SafePrepared`.
100 
101 Returns: The number of rows affected.
102 
103 Example:
104 ---
105 auto myInt = 7;
106 auto rowsAffected = myConnection.exec("INSERT INTO `myTable` (`a`) VALUES (?)", myInt);
107 ---
108 +/
109 ulong exec(Connection conn, const(char[]) sql)
110 {
111 	return execImpl(conn, ExecQueryImplInfo(false, sql));
112 }
113 ///ditto
114 ulong exec(T...)(Connection conn, const(char[]) sql, T args)
115 	if(T.length > 0 && !is(T[0] == Variant[]) && !is(T[0] == MySQLVal[]))
116 {
117 	auto prepared = conn.prepare(sql);
118 	prepared.setArgs(args);
119 	return exec(conn, prepared);
120 }
121 ///ditto
122 ulong exec(Connection conn, const(char[]) sql, MySQLVal[] args)
123 {
124 	auto prepared = conn.prepare(sql);
125 	prepared.setArgs(args);
126 	return exec(conn, prepared);
127 }
128 
129 ///ditto
130 ulong exec(Connection conn, ref Prepared prepared)
131 {
132 	auto preparedInfo = conn.registerIfNeeded(prepared.sql);
133 	auto ra = execImpl(conn, prepared.getExecQueryImplInfo(preparedInfo.statementId));
134 	prepared._lastInsertID = conn.lastInsertID;
135 	return ra;
136 }
137 ///ditto
138 ulong exec(T...)(Connection conn, ref Prepared prepared, T args)
139 	if(T.length > 0 && !is(T[0] == Variant[]) && !is(T[0] == MySQLVal[]))
140 {
141 	prepared.setArgs(args);
142 	return exec(conn, prepared);
143 }
144 
145 ///ditto
146 ulong exec(Connection conn, ref Prepared prepared, MySQLVal[] args)
147 {
148 	prepared.setArgs(args);
149 	return exec(conn, prepared);
150 }
151 
152 /// Common implementation for `exec` overloads
153 package ulong execImpl(Connection conn, ExecQueryImplInfo info)
154 {
155 	ulong rowsAffected;
156 	bool receivedResultSet = execQueryImpl(conn, info, rowsAffected);
157 	if(receivedResultSet)
158 	{
159 		conn.purgeResult();
160 		throw new MYXResultRecieved();
161 	}
162 
163 	return rowsAffected;
164 }
165 
166 /++
167 Execute an SQL SELECT command or prepared statement.
168 
169 This returns an input range of `mysql.impl.result.SafeRow`, so if you need random
170 access to the `mysql.impl.result.SafeRow` elements, simply call
171 $(LINK2 https://dlang.org/phobos/std_array.html#array, `std.array.array()`)
172 on the result.
173 
174 If the SQL command does not produce a result set (such as INSERT/CREATE/etc),
175 then `mysql.exceptions.MYXNoResultRecieved` will be thrown. Use
176 `exec` instead for such commands.
177 
178 If `args` is supplied, the sql string will automatically be used as a prepared
179 statement. Prepared statements are automatically cached by mysql-native,
180 so there's no performance penalty for using this multiple times for the
181 same statement instead of manually preparing a statement.
182 
183 If `args` and `prepared` are both provided, `args` will be used,
184 and any arguments that are already set in the prepared statement
185 will automatically be replaced with `args` (note, just like calling
186 `mysql.impl.prepared.SafePrepared.setArgs`, this will also remove all
187 `mysql.impl.prepared.SafeParameterSpecialization` that may have been applied).
188 
189 Only use the `const(char[]) sql` overload that doesn't take `args`
190 when you are not going to be using the same
191 command repeatedly and you are CERTAIN all the data you're sending is properly
192 escaped. Otherwise, consider using overload that takes a `Prepared`.
193 
194 If you need to use any `mysql.safe.prepared.ParameterSpecialization`, use
195 `mysql.safe.connection.prepare` to manually create a
196 `mysql.impl.prepared.SafePrepared`, and set your parameter specializations using
197 `mysql.impl.prepared.SafePrepared.setArg` or
198 `mysql.impl.prepared.SafePrepared.setArgs`.
199 
200 Type_Mappings: $(TYPE_MAPPINGS)
201 
202 Params:
203 conn = An open `mysql.impl.connection.Connection` to the database.
204 sql = The SQL command to be run.
205 prepared = The prepared statement to be run.
206 csa = Not yet implemented.
207 args = Arguments to the SQL statement or `mysql.safe.prepared.Prepared` struct.
208 
209 Returns: A (possibly empty) `mysql.safe.result.ResultRange`.
210 
211 Example:
212 ---
213 ResultRange oneAtATime = myConnection.query("SELECT * from `myTable`");
214 Row[]       allAtOnce  = myConnection.query("SELECT * from `myTable`").array;
215 
216 auto myInt = 7;
217 ResultRange rows = myConnection.query("SELECT * FROM `myTable` WHERE `a` = ?", myInt);
218 ---
219 +/
220 /+
221 Future text:
222 If there are long data items among the expected result columns you can use
223 the `csa` param to specify that they are to be subject to chunked transfer via a
224 delegate.
225 
226 csa = An optional array of `ColumnSpecialization` structs. If you need to
227 use this with a prepared statement, please use `mysql.prepared.Prepared.columnSpecials`.
228 +/
229 SafeResultRange query(Connection conn, const(char[]) sql, ColumnSpecialization[] csa = null)
230 {
231 	return queryImpl(csa, conn, ExecQueryImplInfo(false, sql));
232 }
233 ///ditto
234 SafeResultRange query(T...)(Connection conn, const(char[]) sql, T args)
235 	if(T.length > 0 && !is(T[0] == Variant[]) && !is(T[0] == MySQLVal[]) && !is(T[0] == ColumnSpecialization) && !is(T[0] == ColumnSpecialization[]))
236 {
237 	auto prepared = conn.prepare(sql);
238 	prepared.setArgs(args);
239 	return query(conn, prepared);
240 }
241 
242 ///ditto
243 SafeResultRange query(Connection conn, const(char[]) sql, MySQLVal[] args)
244 {
245 	auto prepared = conn.prepare(sql);
246 	prepared.setArgs(args);
247 	return query(conn, prepared);
248 }
249 
250 ///ditto
251 SafeResultRange query(Connection conn, ref Prepared prepared)
252 {
253 	auto preparedInfo = conn.registerIfNeeded(prepared.sql);
254 	auto result = queryImpl(prepared.columnSpecials, conn, prepared.getExecQueryImplInfo(preparedInfo.statementId));
255 	prepared._lastInsertID = conn.lastInsertID; // Conceivably, this might be needed when multi-statements are enabled.
256 	return result;
257 }
258 ///ditto
259 SafeResultRange query(T...)(Connection conn, ref Prepared prepared, T args)
260 	if(T.length > 0 && !is(T[0] == Variant[]) && !is(T[0] == MySQLVal[]) && !is(T[0] == ColumnSpecialization) && !is(T[0] == ColumnSpecialization[]))
261 {
262 	prepared.setArgs(args);
263 	return query(conn, prepared);
264 }
265 ///ditto
266 SafeResultRange query(Connection conn, ref Prepared prepared, MySQLVal[] args)
267 {
268 	prepared.setArgs(args);
269 	return query(conn, prepared);
270 }
271 
272 /// Common implementation for `query` overloads
273 package SafeResultRange queryImpl(ColumnSpecialization[] csa,
274 	Connection conn, ExecQueryImplInfo info)
275 {
276 	ulong ra;
277 	enforce!MYXNoResultRecieved(execQueryImpl(conn, info, ra));
278 
279 	conn._rsh = ResultSetHeaders(conn, conn._fieldCount);
280 	if(csa !is null)
281 		conn._rsh.addSpecializations(csa);
282 
283 	conn._headersPending = false;
284 	return SafeResultRange(conn, conn._rsh, conn._rsh.fieldNames);
285 }
286 
287 /++
288 Execute an SQL SELECT command or prepared statement where you only want the
289 first `mysql.impl.result.SafeRow`, if any.
290 
291 If the SQL command does not produce a result set (such as INSERT/CREATE/etc),
292 then `mysql.exceptions.MYXNoResultRecieved` will be thrown. Use
293 `exec` instead for such commands.
294 
295 If `args` is supplied, the sql string will automatically be used as a prepared
296 statement. Prepared statements are automatically cached by mysql-native,
297 so there's no performance penalty for using this multiple times for the
298 same statement instead of manually preparing a statement.
299 
300 If `args` and `prepared` are both provided, `args` will be used,
301 and any arguments that are already set in the prepared statement
302 will automatically be replaced with `args` (note, just like calling
303 `mysql.impl.prepared.SafePrepared.setArgs`, this will also remove all
304 `mysql.impl.prepared.SafeParameterSpecialization` that may have been applied).
305 
306 Only use the `const(char[]) sql` overload that doesn't take `args`
307 when you are not going to be using the same
308 command repeatedly and you are CERTAIN all the data you're sending is properly
309 escaped. Otherwise, consider using overload that takes a `Prepared`.
310 
311 If you need to use any `mysql.impl.prepared.SafeParameterSpecialization`, use
312 `mysql.safe.connection.prepare` to manually create a
313 `mysql.impl.prepared.SafePrepared`, and set your parameter specializations using
314 `mysql.impl.prepared.SafePrepared.setArg` or
315 `mysql.impl.prepared.SafePrepared.setArgs`.
316 
317 Type_Mappings: $(TYPE_MAPPINGS)
318 
319 Params:
320 conn = An open `mysql.impl.connection.Connection` to the database.
321 sql = The SQL command to be run.
322 prepared = The prepared statement to be run.
323 csa = Not yet implemented.
324 args = Arguments to SQL statement or `mysql.impl.prepared.SafePrepared` struct.
325 
326 Returns: `Nullable!(mysql.impl.result.SafeRow)`: This will be null (check
327 		via `Nullable.isNull`) if the query resulted in an empty result set.
328 
329 Example:
330 ---
331 auto myInt = 7;
332 Nullable!Row row = myConnection.queryRow("SELECT * FROM `myTable` WHERE `a` = ?", myInt);
333 ---
334 +/
335 /+
336 Future text:
337 If there are long data items among the expected result columns you can use
338 the `csa` param to specify that they are to be subject to chunked transfer via a
339 delegate.
340 
341 csa = An optional array of `ColumnSpecialization` structs. If you need to
342 use this with a prepared statement, please use `mysql.prepared.Prepared.columnSpecials`.
343 +/
344 /+
345 Future text:
346 If there are long data items among the expected result columns you can use
347 the `csa` param to specify that they are to be subject to chunked transfer via a
348 delegate.
349 
350 csa = An optional array of `ColumnSpecialization` structs. If you need to
351 use this with a prepared statement, please use `mysql.prepared.Prepared.columnSpecials`.
352 +/
353 Nullable!SafeRow queryRow(Connection conn, const(char[]) sql, ColumnSpecialization[] csa = null)
354 {
355 	return queryRowImpl(csa, conn, ExecQueryImplInfo(false, sql));
356 }
357 ///ditto
358 Nullable!SafeRow queryRow(T...)(Connection conn, const(char[]) sql, T args)
359 	if(T.length > 0 && !is(T[0] == Variant[]) && !is(T[0] == MySQLVal[]) && !is(T[0] == ColumnSpecialization) && !is(T[0] == ColumnSpecialization[]))
360 {
361 	auto prepared = conn.prepare(sql);
362 	prepared.setArgs(args);
363 	return queryRow(conn, prepared);
364 }
365 ///ditto
366 Nullable!SafeRow queryRow(Connection conn, const(char[]) sql, MySQLVal[] args)
367 {
368 	auto prepared = conn.prepare(sql);
369 	prepared.setArgs(args);
370 	return queryRow(conn, prepared);
371 }
372 
373 ///ditto
374 Nullable!SafeRow queryRow(Connection conn, ref Prepared prepared)
375 {
376 	auto preparedInfo = conn.registerIfNeeded(prepared.sql);
377 	auto result = queryRowImpl(prepared.columnSpecials, conn, prepared.getExecQueryImplInfo(preparedInfo.statementId));
378 	prepared._lastInsertID = conn.lastInsertID; // Conceivably, this might be needed when multi-statements are enabled.
379 	return result;
380 }
381 ///ditto
382 Nullable!SafeRow queryRow(T...)(Connection conn, ref Prepared prepared, T args)
383 	if(T.length > 0 && !is(T[0] == Variant[]) && !is(T[0] == MySQLVal[]) && !is(T[0] == ColumnSpecialization) && !is(T[0] == ColumnSpecialization[]))
384 {
385 	prepared.setArgs(args);
386 	return queryRow(conn, prepared);
387 }
388 ///ditto
389 Nullable!SafeRow queryRow(Connection conn, ref Prepared prepared, MySQLVal[] args)
390 {
391 	prepared.setArgs(args);
392 	return queryRow(conn, prepared);
393 }
394 
395 /// Common implementation for `querySet` overloads.
396 package Nullable!SafeRow queryRowImpl(ColumnSpecialization[] csa, Connection conn,
397 	ExecQueryImplInfo info)
398 {
399 	auto results = queryImpl(csa, conn, info);
400 	if(results.empty)
401 		return Nullable!SafeRow();
402 	else
403 	{
404 		auto row = results.front;
405 		results.close();
406 		return Nullable!SafeRow(row);
407 	}
408 }
409 
410 /++
411 Execute an SQL SELECT command or prepared statement where you only want the
412 first `mysql.result.Row`, and place result values into a set of D variables.
413 
414 This method will throw if any column type is incompatible with the corresponding D variable.
415 
416 Unlike the other query functions, queryRowTuple will throw
417 `mysql.exceptions.MYX` if the result set is empty
418 (and thus the reference variables passed in cannot be filled).
419 
420 If the SQL command does not produce a result set (such as INSERT/CREATE/etc),
421 then `mysql.exceptions.MYXNoResultRecieved` will be thrown. Use
422 `exec` instead for such commands.
423 
424 Only use the `const(char[]) sql` overload when you are not going to be using the same
425 command repeatedly and you are CERTAIN all the data you're sending is properly
426 escaped. Otherwise, consider using overload that takes a `Prepared`.
427 
428 Type_Mappings: $(TYPE_MAPPINGS)
429 
430 Params:
431 conn = An open `mysql.impl.connection.Connection` to the database.
432 sql = The SQL command to be run.
433 prepared = The prepared statement to be run.
434 args = The variables, taken by reference, to receive the values.
435 +/
436 void queryRowTuple(T...)(Connection conn, const(char[]) sql, ref T args)
437 {
438 	return queryRowTupleImpl(conn, ExecQueryImplInfo(false, sql), args);
439 }
440 
441 ///ditto
442 void queryRowTuple(T...)(Connection conn, ref Prepared prepared, ref T args)
443 {
444 	auto preparedInfo = conn.registerIfNeeded(prepared.sql);
445 	queryRowTupleImpl(conn, prepared.getExecQueryImplInfo(preparedInfo.statementId), args);
446 	prepared._lastInsertID = conn.lastInsertID; // Conceivably, this might be needed when multi-statements are enabled.
447 }
448 
449 /// Common implementation for `queryRowTuple` overloads.
450 package(mysql) void queryRowTupleImpl(T...)(Connection conn, ExecQueryImplInfo info, ref T args)
451 {
452 	ulong ra;
453 	enforce!MYXNoResultRecieved(execQueryImpl(conn, info, ra));
454 
455 	auto rr = conn.getNextRow();
456 	/+if (!rr._valid)   // The result set was empty - not a crime.
457 		return;+/
458 	enforce!MYX(rr._values.length == args.length, "Result column count does not match the target tuple.");
459 	foreach (size_t i, dummy; args)
460 	{
461 		import taggedalgebraic.taggedalgebraic : get, hasType;
462 		enforce!MYX(rr._values[i].hasType!(T[i]),
463 			"Tuple "~to!string(i)~" type and column type are not compatible.");
464 		// use taggedalgebraic get to avoid extra calls.
465 		args[i] = get!(T[i])(rr._values[i]);
466 	}
467 	// If there were more rows, flush them away
468 	// Question: Should I check in purgeResult and throw if there were - it's very inefficient to
469 	// allow sloppy SQL that does not ensure just one row!
470 	conn.purgeResult();
471 }
472 
473 /++
474 Execute an SQL SELECT command or prepared statement and return a single value:
475 the first column of the first row received.
476 
477 If the query did not produce any rows, or the rows it produced have zero columns,
478 this will return `Nullable!MySQLVal()`, ie, null. Test for this with
479 `result.isNull`.
480 
481 If the query DID produce a result, but the value actually received is NULL,
482 then `result.isNull` will be FALSE, and `result.get` will produce a MySQLVal
483 which CONTAINS null. Check for this with `result.get.kind == MySQLVal.Kind.Null`
484 or `result.get == null`.
485 
486 If the SQL command does not produce a result set (such as INSERT/CREATE/etc),
487 then `mysql.exceptions.MYXNoResultRecieved` will be thrown. Use
488 `exec` instead for such commands.
489 
490 If `args` is supplied, the sql string will automatically be used as a prepared
491 statement. Prepared statements are automatically cached by mysql-native,
492 so there's no performance penalty for using this multiple times for the
493 same statement instead of manually preparing a statement.
494 
495 If `args` and `prepared` are both provided, `args` will be used,
496 and any arguments that are already set in the prepared statement
497 will automatically be replaced with `args` (note, just like calling
498 `mysql.impl.prepared.SafePrepared.setArgs`, this will also remove all
499 `mysql.impl.prepared.SafeParameterSpecialization` that may have been applied).
500 
501 Only use the `const(char[]) sql` overload that doesn't take `args`
502 when you are not going to be using the same
503 command repeatedly and you are CERTAIN all the data you're sending is properly
504 escaped. Otherwise, consider using overload that takes a `Prepared`.
505 
506 If you need to use any `mysql.impl.prepared.SafeParameterSpecialization`, use
507 `mysql.safe.connection.prepare` to manually create a `mysql.impl.prepared.SafePrepared`,
508 and set your parameter specializations using `mysql.impl.prepared.SafePrepared.setArg`
509 or `mysql.impl.prepared.SafePrepared.setArgs`.
510 
511 Type_Mappings: $(TYPE_MAPPINGS)
512 
513 Params:
514 conn = An open `mysql.impl.connection.Connection` to the database.
515 sql = The SQL command to be run.
516 prepared = The prepared statement to be run.
517 csa = Not yet implemented.
518 
519 Returns: `Nullable!MySQLVal`: This will be null (check via `Nullable.isNull`) if the
520 query resulted in an empty result set.
521 
522 Example:
523 ---
524 auto myInt = 7;
525 Nullable!MySQLVal value = myConnection.queryRow("SELECT * FROM `myTable` WHERE `a` = ?", myInt);
526 ---
527 +/
528 /+
529 Future text:
530 If there are long data items among the expected result columns you can use
531 the `csa` param to specify that they are to be subject to chunked transfer via a
532 delegate.
533 
534 csa = An optional array of `ColumnSpecialization` structs. If you need to
535 use this with a prepared statement, please use `mysql.prepared.Prepared.columnSpecials`.
536 +/
537 /+
538 Future text:
539 If there are long data items among the expected result columns you can use
540 the `csa` param to specify that they are to be subject to chunked transfer via a
541 delegate.
542 
543 csa = An optional array of `ColumnSpecialization` structs. If you need to
544 use this with a prepared statement, please use `mysql.prepared.Prepared.columnSpecials`.
545 +/
546 Nullable!MySQLVal queryValue(Connection conn, const(char[]) sql, ColumnSpecialization[] csa = null)
547 {
548 	return queryValueImpl(csa, conn, ExecQueryImplInfo(false, sql));
549 }
550 ///ditto
551 Nullable!MySQLVal queryValue(T...)(Connection conn, const(char[]) sql, T args)
552 	if(T.length > 0 && !is(T[0] == Variant[]) && !is(T[0] == MySQLVal[]) && !is(T[0] == ColumnSpecialization) && !is(T[0] == ColumnSpecialization[]))
553 {
554 	auto prepared = conn.prepare(sql);
555 	prepared.setArgs(args);
556 	return queryValue(conn, prepared);
557 }
558 ///ditto
559 Nullable!MySQLVal queryValue(Connection conn, const(char[]) sql, MySQLVal[] args)
560 {
561 	auto prepared = conn.prepare(sql);
562 	prepared.setArgs(args);
563 	return queryValue(conn, prepared);
564 }
565 ///ditto
566 Nullable!MySQLVal queryValue(Connection conn, ref Prepared prepared)
567 {
568 	auto preparedInfo = conn.registerIfNeeded(prepared.sql);
569 	auto result = queryValueImpl(prepared.columnSpecials, conn, prepared.getExecQueryImplInfo(preparedInfo.statementId));
570 	prepared._lastInsertID = conn.lastInsertID; // Conceivably, this might be needed when multi-statements are enabled.
571 	return result;
572 }
573 ///ditto
574 Nullable!MySQLVal queryValue(T...)(Connection conn, ref Prepared prepared, T args)
575 	if(T.length > 0 && !is(T[0] == Variant[]) && !is(T[0] == MySQLVal[]) && !is(T[0] == ColumnSpecialization) && !is(T[0] == ColumnSpecialization[]))
576 {
577 	prepared.setArgs(args);
578 	return queryValue(conn, prepared);
579 }
580 ///ditto
581 Nullable!MySQLVal queryValue(Connection conn, ref Prepared prepared, MySQLVal[] args)
582 {
583 	prepared.setArgs(args);
584 	return queryValue(conn, prepared);
585 }
586 
587 /// Common implementation for `queryValue` overloads.
588 package Nullable!MySQLVal queryValueImpl(ColumnSpecialization[] csa, Connection conn,
589 	ExecQueryImplInfo info)
590 {
591 	auto results = queryImpl(csa, conn, info);
592 	if(results.empty)
593 		return Nullable!MySQLVal();
594 	else
595 	{
596 		auto row = results.front;
597 		results.close();
598 
599 		if(row.length == 0)
600 			return Nullable!MySQLVal();
601 		else
602 			return Nullable!MySQLVal(row[0]);
603 	}
604 }
605