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