1 /// Use a DB via SQL prepared statements.
2 module mysql.prepared;
3 
4 import std.algorithm;
5 import std.conv;
6 import std.datetime;
7 import std.exception;
8 import std.range;
9 import std.traits;
10 import std.typecons;
11 import std.variant;
12 
13 import mysql.commands;
14 import mysql.connection;
15 import mysql.exceptions;
16 import mysql.protocol.constants;
17 import mysql.protocol.extra_types;
18 import mysql.protocol.packets;
19 import mysql.protocol.packet_helpers;
20 import mysql.protocol.sockets;
21 import mysql.result;
22 import mysql.types;
23 
24 /++
25 A struct to represent specializations of prepared statement parameters.
26 
27 Strongly considering the removal of the isNull field, now that Prepared
28 can handle `null` as a value just fine.
29 
30 There are two specializations. First you can set an isNull flag to indicate that the
31 parameter is to have the SQL NULL value.
32 
33 Second, if you need to send large objects to the database it might be convenient to
34 send them in pieces. These two variables allow for this. If both are provided
35 then the corresponding column will be populated by calling the delegate repeatedly.
36 the source should fill the indicated slice with data and arrange for the delegate to
37 return the length of the data supplied. Af that is less than the chunkSize
38 then the chunk will be assumed to be the last one.
39 +/
40 struct ParameterSpecialization
41 {
42 	import mysql.protocol.constants;
43 	
44 	size_t pIndex;    //parameter number 0 - number of params-1
45 	SQLType type = SQLType.INFER_FROM_D_TYPE;
46 	uint chunkSize;
47 	uint delegate(ubyte[]) chunkDelegate;
48 }
49 ///ditto
50 alias PSN = ParameterSpecialization;
51 
52 /++
53 Encapsulation of a prepared statement.
54 
55 Commands that are expected to return a result set - queries - have distinctive
56 methods that are enforced. That is it will be an error to call such a method
57 with an SQL command that does not produce a result set. So for commands like
58 SELECT, use the `query` functions. For other commands, like
59 INSERT/UPDATE/CREATE/etc, use `exec`.
60 
61 Internally, `Prepared` simply wraps a `PreparedImpl` with
62 $(LINK2 https://dlang.org/phobos/std_typecons.html#.RefCounted, `RefCounted`),
63 and offers access to the `PreparedImpl` members via "alias this".
64 
65 See the `PreparedImpl` documentation for the bulk of the `Prepared` interface.
66 +/
67 struct Prepared
68 {
69 	RefCounted!(PreparedImpl, RefCountedAutoInitialize.no) preparedImpl;
70 	alias preparedImpl this;
71 	
72 	@property bool isPrepared() pure const
73 	{
74 		return preparedImpl.refCountedStore.isInitialized && !preparedImpl.isReleased;
75 	}
76 }
77 
78 /++
79 Submit an SQL command to the server to be compiled into a prepared statement.
80 
81 The result of a successful outcome will be a statement handle - an ID -
82 for the prepared statement, a count of the parameters required for
83 excution of the statement, and a count of the columns that will be present
84 in any result set that the command generates. These values will be stored
85 in the Command struct.
86 
87 The server will then proceed to send prepared statement headers,
88 including parameter descriptions, and result set field descriptions,
89 followed by an EOF packet.
90 
91 Throws: MySQLException if there are pending result set items, or if the
92 server has a problem.
93 +/
94 Prepared prepare(Connection conn, string sql)
95 {
96 	return Prepared( refCounted(PreparedImpl(conn, sql)) );
97 }
98 
99 /++
100 Convenience function to create a prepared statement which calls a stored function.
101 
102 Throws: MySQLException if there are pending result set items, or if the
103 server has a problem.
104 
105 Params:
106 	name = The name of the stored function.
107 	numArgs = The number of arguments the stored procedure takes.
108 +/
109 Prepared prepareFunction(Connection conn, string name, int numArgs)
110 {
111 	auto sql = "select " ~ name ~ preparedPlaceholderArgs(numArgs);
112 	return prepare(conn, sql);
113 }
114 
115 ///
116 unittest
117 {
118 	debug(MYSQL_INTEGRATION_TESTS)
119 	{
120 		import mysql.test.common;
121 		mixin(scopedCn);
122 
123 		exec(cn, `DROP FUNCTION IF EXISTS hello`);
124 		exec(cn, `
125 			CREATE FUNCTION hello (s CHAR(20))
126 			RETURNS CHAR(50) DETERMINISTIC
127 			RETURN CONCAT('Hello ',s,'!')
128 		`);
129 
130 		auto preparedHello = prepareFunction(cn, "hello", 1);
131 		preparedHello.setArgs("World");
132 		ResultSet rs = preparedHello.querySet();
133 		assert(rs.length == 1);
134 		assert(rs[0][0] == "Hello World!");
135 	}
136 }
137 
138 /++
139 Convenience function to create a prepared statement which calls a stored procedure.
140 
141 OUT parameters are currently not supported. It should generally be
142 possible with MySQL to present them as a result set.
143 
144 Throws: MySQLException if there are pending result set items, or if the
145 server has a problem.
146 
147 Params:
148 	name = The name of the stored procedure.
149 	numArgs = The number of arguments the stored procedure takes.
150 
151 +/
152 Prepared prepareProcedure(Connection conn, string name, int numArgs)
153 {
154 	auto sql = "call " ~ name ~ preparedPlaceholderArgs(numArgs);
155 	return prepare(conn, sql);
156 }
157 
158 ///
159 unittest
160 {
161 	debug(MYSQL_INTEGRATION_TESTS)
162 	{
163 		import mysql.test.common;
164 		import mysql.test.integration;
165 		mixin(scopedCn);
166 		initBaseTestTables(cn);
167 
168 		exec(cn, `DROP PROCEDURE IF EXISTS insert2`);
169 		exec(cn, `
170 			CREATE PROCEDURE insert2 (IN p1 INT, IN p2 CHAR(50))
171 			BEGIN
172 				INSERT INTO basetest (intcol, stringcol) VALUES(p1, p2);
173 			END
174 		`);
175 
176 		auto preparedInsert2 = prepareProcedure(cn, "insert2", 2);
177 		preparedInsert2.setArgs(2001, "inserted string 1");
178 		preparedInsert2.exec();
179 
180 		ResultSet rs = querySet(cn, "SELECT stringcol FROM basetest WHERE intcol=2001");
181 		assert(rs.length == 1);
182 		assert(rs[0][0] == "inserted string 1");
183 	}
184 }
185 
186 private string preparedPlaceholderArgs(int numArgs)
187 {
188 	auto sql = "(";
189 	bool comma = false;
190 	foreach(i; 0..numArgs)
191 	{
192 		if (comma)
193 			sql ~= ",?";
194 		else
195 		{
196 			sql ~= "?";
197 			comma = true;
198 		}
199 	}
200 	sql ~= ")";
201 
202 	return sql;
203 }
204 
205 debug(MYSQL_INTEGRATION_TESTS)
206 unittest
207 {
208 	assert(preparedPlaceholderArgs(3) == "(?,?,?)");
209 	assert(preparedPlaceholderArgs(2) == "(?,?)");
210 	assert(preparedPlaceholderArgs(1) == "(?)");
211 	assert(preparedPlaceholderArgs(0) == "()");
212 }
213 
214 /++
215 This is the internal implementation of `Prepared`. It is not intended to be
216 used directly, as `Prepared` wraps a `PreparedImpl` with
217 $(LINK2 https://dlang.org/phobos/std_typecons.html#.RefCounted, `RefCounted`),
218 and offers access to the public `PreparedImpl` members via "alias this".
219 +/
220 struct PreparedImpl
221 {
222 private:
223 	Connection _conn;
224 	string _sql;
225 
226 	void enforceNotReleased()
227 	{
228 		enforceNotReleased(_hStmt);
229 	}
230 
231 	static void enforceNotReleased(uint hStmt)
232 	{
233 		enforceEx!MYXNotPrepared(hStmt);
234 	}
235 
236 	debug(MYSQL_INTEGRATION_TESTS)
237 	unittest
238 	{
239 		import mysql.prepared;
240 		import mysql.test.common;
241 		mixin(scopedCn);
242 
243 		cn.exec("DROP TABLE IF EXISTS `enforceNotReleased`");
244 		cn.exec("CREATE TABLE `enforceNotReleased` (
245 			`val` INTEGER
246 		) ENGINE=InnoDB DEFAULT CHARSET=utf8");
247 
248 		immutable insertSQL = "INSERT INTO `enforceNotReleased` VALUES (1), (2)";
249 		immutable selectSQL = "SELECT * FROM `enforceNotReleased`";
250 		Prepared preparedInsert;
251 		Prepared preparedSelect;
252 		int queryTupleResult;
253 		assertNotThrown!MYXNotPrepared(preparedInsert = cn.prepare(insertSQL));
254 		assertNotThrown!MYXNotPrepared(preparedSelect = cn.prepare(selectSQL));
255 		assertNotThrown!MYXNotPrepared(preparedInsert.exec());
256 		assertNotThrown!MYXNotPrepared(preparedSelect.querySet());
257 		assertNotThrown!MYXNotPrepared(preparedSelect.query().each());
258 		assertNotThrown!MYXNotPrepared(preparedSelect.queryRowTuple(queryTupleResult));
259 		
260 		preparedInsert.release();
261 		assertThrown!MYXNotPrepared(preparedInsert.exec());
262 		assertNotThrown!MYXNotPrepared(preparedSelect.querySet());
263 		assertNotThrown!MYXNotPrepared(preparedSelect.query().each());
264 		assertNotThrown!MYXNotPrepared(preparedSelect.queryRowTuple(queryTupleResult));
265 
266 		preparedSelect.release();
267 		assertThrown!MYXNotPrepared(preparedInsert.exec());
268 		assertThrown!MYXNotPrepared(preparedSelect.querySet());
269 		assertThrown!MYXNotPrepared(preparedSelect.query().each());
270 		assertThrown!MYXNotPrepared(preparedSelect.queryRowTuple(queryTupleResult));
271 	}
272 
273 	@disable this(this); // Not copyable
274 
275 	/++
276 	Submit an SQL command to the server to be compiled into a prepared statement.
277 
278 	The result of a successful outcome will be a statement handle - an ID -
279 	for the prepared statement, a count of the parameters required for
280 	excution of the statement, and a count of the columns that will be present
281 	in any result set that the command generates. These values will be stored
282 	in the Command struct.
283 
284 	The server will then proceed to send prepared statement headers,
285 	including parameter descriptions, and result set field descriptions,
286 	followed by an EOF packet.
287 
288 	If there is an existing statement handle in the Command struct, that
289 	prepared statement is released.
290 	+/
291 	public this(Connection conn, string sql)
292 	{
293 		this._conn = conn;
294 		this._sql = sql;
295 
296 		scope(failure) conn.kill();
297 
298 		conn.sendCmd(CommandType.STMT_PREPARE, sql);
299 		conn._fieldCount = 0;
300 
301 		//TODO: All packet handling should be moved into the mysql.protocol package.
302 		ubyte[] packet = conn.getPacket();
303 		if (packet.front == ResultPacketMarker.ok)
304 		{
305 			packet.popFront();
306 			_hStmt              = packet.consume!int();
307 			conn._fieldCount    = packet.consume!short();
308 			_psParams           = packet.consume!short();
309 
310 			_inParams.length    = _psParams;
311 			_psa.length         = _psParams;
312 
313 			packet.popFront(); // one byte filler
314 			_psWarnings         = packet.consume!short();
315 
316 			// At this point the server also sends field specs for parameters
317 			// and columns if there were any of each
318 			_psh = PreparedStmtHeaders(conn, conn._fieldCount, _psParams);
319 		}
320 		else if(packet.front == ResultPacketMarker.error)
321 		{
322 			auto error = OKErrorPacket(packet);
323 			enforcePacketOK(error);
324 			assert(0); // FIXME: what now?
325 		}
326 		else
327 			assert(0); // FIXME: what now?
328 	}
329 
330 package:
331 	uint _hStmt; // Server's identifier for this prepared statement. This is 0 when released.
332 	ushort _psParams, _psWarnings;
333 	PreparedStmtHeaders _psh;
334 	Variant[] _inParams;
335 	ParameterSpecialization[] _psa;
336 
337 	static ubyte[] makeBitmap(in Variant[] inParams)
338 	{
339 		size_t bml = (inParams.length+7)/8;
340 		ubyte[] bma;
341 		bma.length = bml;
342 		foreach (i; 0..inParams.length)
343 		{
344 			if(inParams[i].type != typeid(typeof(null)))
345 				continue;
346 			size_t bn = i/8;
347 			size_t bb = i%8;
348 			ubyte sr = 1;
349 			sr <<= bb;
350 			bma[bn] |= sr;
351 		}
352 		return bma;
353 	}
354 
355 	static ubyte[] makePSPrefix(uint hStmt, ubyte flags = 0) pure nothrow
356 	{
357 		ubyte[] prefix;
358 		prefix.length = 14;
359 
360 		prefix[4] = CommandType.STMT_EXECUTE;
361 		hStmt.packInto(prefix[5..9]);
362 		prefix[9] = flags;   // flags, no cursor
363 		prefix[10] = 1; // iteration count - currently always 1
364 		prefix[11] = 0;
365 		prefix[12] = 0;
366 		prefix[13] = 0;
367 
368 		return prefix;
369 	}
370 
371 	//TODO: All low-level commms should be moved into the mysql.protocol package.
372 	static ubyte[] analyseParams(Variant[] inParams, ParameterSpecialization[] psa,
373 		out ubyte[] vals, out bool longData)
374 	{
375 		size_t pc = inParams.length;
376 		ubyte[] types;
377 		types.length = pc*2;
378 		size_t alloc = pc*20;
379 		vals.length = alloc;
380 		uint vcl = 0, len;
381 		int ct = 0;
382 
383 		void reAlloc(size_t n)
384 		{
385 			if (vcl+n < alloc)
386 				return;
387 			size_t inc = (alloc*3)/2;
388 			if (inc <  n)
389 				inc = n;
390 			alloc += inc;
391 			vals.length = alloc;
392 		}
393 
394 		foreach (size_t i; 0..pc)
395 		{
396 			enum UNSIGNED  = 0x80;
397 			enum SIGNED    = 0;
398 			if (psa[i].chunkSize)
399 				longData= true;
400 			if (inParams[i].type == typeid(typeof(null)))
401 			{
402 				types[ct++] = SQLType.NULL;
403 				types[ct++] = SIGNED;
404 				continue;
405 			}
406 			Variant v = inParams[i];
407 			SQLType ext = psa[i].type;
408 			string ts = v.type.toString();
409 			bool isRef;
410 			if (ts[$-1] == '*')
411 			{
412 				ts.length = ts.length-1;
413 				isRef= true;
414 			}
415 
416 			switch (ts)
417 			{
418 				case "bool":
419 					if (ext == SQLType.INFER_FROM_D_TYPE)
420 						types[ct++] = SQLType.BIT;
421 					else
422 						types[ct++] = cast(ubyte) ext;
423 					types[ct++] = SIGNED;
424 					reAlloc(2);
425 					bool bv = isRef? *(v.get!(bool*)): v.get!(bool);
426 					vals[vcl++] = 1;
427 					vals[vcl++] = bv? 0x31: 0x30;
428 					break;
429 				case "byte":
430 					types[ct++] = SQLType.TINY;
431 					types[ct++] = SIGNED;
432 					reAlloc(1);
433 					vals[vcl++] = isRef? *(v.get!(byte*)): v.get!(byte);
434 					break;
435 				case "ubyte":
436 					types[ct++] = SQLType.TINY;
437 					types[ct++] = UNSIGNED;
438 					reAlloc(1);
439 					vals[vcl++] = isRef? *(v.get!(ubyte*)): v.get!(ubyte);
440 					break;
441 				case "short":
442 					types[ct++] = SQLType.SHORT;
443 					types[ct++] = SIGNED;
444 					reAlloc(2);
445 					short si = isRef? *(v.get!(short*)): v.get!(short);
446 					vals[vcl++] = cast(ubyte) (si & 0xff);
447 					vals[vcl++] = cast(ubyte) ((si >> 8) & 0xff);
448 					break;
449 				case "ushort":
450 					types[ct++] = SQLType.SHORT;
451 					types[ct++] = UNSIGNED;
452 					reAlloc(2);
453 					ushort us = isRef? *(v.get!(ushort*)): v.get!(ushort);
454 					vals[vcl++] = cast(ubyte) (us & 0xff);
455 					vals[vcl++] = cast(ubyte) ((us >> 8) & 0xff);
456 					break;
457 				case "int":
458 					types[ct++] = SQLType.INT;
459 					types[ct++] = SIGNED;
460 					reAlloc(4);
461 					int ii = isRef? *(v.get!(int*)): v.get!(int);
462 					vals[vcl++] = cast(ubyte) (ii & 0xff);
463 					vals[vcl++] = cast(ubyte) ((ii >> 8) & 0xff);
464 					vals[vcl++] = cast(ubyte) ((ii >> 16) & 0xff);
465 					vals[vcl++] = cast(ubyte) ((ii >> 24) & 0xff);
466 					break;
467 				case "uint":
468 					types[ct++] = SQLType.INT;
469 					types[ct++] = UNSIGNED;
470 					reAlloc(4);
471 					uint ui = isRef? *(v.get!(uint*)): v.get!(uint);
472 					vals[vcl++] = cast(ubyte) (ui & 0xff);
473 					vals[vcl++] = cast(ubyte) ((ui >> 8) & 0xff);
474 					vals[vcl++] = cast(ubyte) ((ui >> 16) & 0xff);
475 					vals[vcl++] = cast(ubyte) ((ui >> 24) & 0xff);
476 					break;
477 				case "long":
478 					types[ct++] = SQLType.LONGLONG;
479 					types[ct++] = SIGNED;
480 					reAlloc(8);
481 					long li = isRef? *(v.get!(long*)): v.get!(long);
482 					vals[vcl++] = cast(ubyte) (li & 0xff);
483 					vals[vcl++] = cast(ubyte) ((li >> 8) & 0xff);
484 					vals[vcl++] = cast(ubyte) ((li >> 16) & 0xff);
485 					vals[vcl++] = cast(ubyte) ((li >> 24) & 0xff);
486 					vals[vcl++] = cast(ubyte) ((li >> 32) & 0xff);
487 					vals[vcl++] = cast(ubyte) ((li >> 40) & 0xff);
488 					vals[vcl++] = cast(ubyte) ((li >> 48) & 0xff);
489 					vals[vcl++] = cast(ubyte) ((li >> 56) & 0xff);
490 					break;
491 				case "ulong":
492 					types[ct++] = SQLType.LONGLONG;
493 					types[ct++] = UNSIGNED;
494 					reAlloc(8);
495 					ulong ul = isRef? *(v.get!(ulong*)): v.get!(ulong);
496 					vals[vcl++] = cast(ubyte) (ul & 0xff);
497 					vals[vcl++] = cast(ubyte) ((ul >> 8) & 0xff);
498 					vals[vcl++] = cast(ubyte) ((ul >> 16) & 0xff);
499 					vals[vcl++] = cast(ubyte) ((ul >> 24) & 0xff);
500 					vals[vcl++] = cast(ubyte) ((ul >> 32) & 0xff);
501 					vals[vcl++] = cast(ubyte) ((ul >> 40) & 0xff);
502 					vals[vcl++] = cast(ubyte) ((ul >> 48) & 0xff);
503 					vals[vcl++] = cast(ubyte) ((ul >> 56) & 0xff);
504 					break;
505 				case "float":
506 					types[ct++] = SQLType.FLOAT;
507 					types[ct++] = SIGNED;
508 					reAlloc(4);
509 					float f = isRef? *(v.get!(float*)): v.get!(float);
510 					ubyte* ubp = cast(ubyte*) &f;
511 					vals[vcl++] = *ubp++;
512 					vals[vcl++] = *ubp++;
513 					vals[vcl++] = *ubp++;
514 					vals[vcl++] = *ubp;
515 					break;
516 				case "double":
517 					types[ct++] = SQLType.DOUBLE;
518 					types[ct++] = SIGNED;
519 					reAlloc(8);
520 					double d = isRef? *(v.get!(double*)): v.get!(double);
521 					ubyte* ubp = cast(ubyte*) &d;
522 					vals[vcl++] = *ubp++;
523 					vals[vcl++] = *ubp++;
524 					vals[vcl++] = *ubp++;
525 					vals[vcl++] = *ubp++;
526 					vals[vcl++] = *ubp++;
527 					vals[vcl++] = *ubp++;
528 					vals[vcl++] = *ubp++;
529 					vals[vcl++] = *ubp;
530 					break;
531 				case "std.datetime.date.Date":
532 				case "std.datetime.Date":
533 					types[ct++] = SQLType.DATE;
534 					types[ct++] = SIGNED;
535 					Date date = isRef? *(v.get!(Date*)): v.get!(Date);
536 					ubyte[] da = pack(date);
537 					size_t l = da.length;
538 					reAlloc(l);
539 					vals[vcl..vcl+l] = da[];
540 					vcl += l;
541 					break;
542 				case "std.datetime.TimeOfDay":
543 				case "std.datetime.Time":
544 					types[ct++] = SQLType.TIME;
545 					types[ct++] = SIGNED;
546 					TimeOfDay time = isRef? *(v.get!(TimeOfDay*)): v.get!(TimeOfDay);
547 					ubyte[] ta = pack(time);
548 					size_t l = ta.length;
549 					reAlloc(l);
550 					vals[vcl..vcl+l] = ta[];
551 					vcl += l;
552 					break;
553 				case "std.datetime.date.DateTime":
554 				case "std.datetime.DateTime":
555 					types[ct++] = SQLType.DATETIME;
556 					types[ct++] = SIGNED;
557 					DateTime dt = isRef? *(v.get!(DateTime*)): v.get!(DateTime);
558 					ubyte[] da = pack(dt);
559 					size_t l = da.length;
560 					reAlloc(l);
561 					vals[vcl..vcl+l] = da[];
562 					vcl += l;
563 					break;
564 				case "connect.Timestamp":
565 					types[ct++] = SQLType.TIMESTAMP;
566 					types[ct++] = SIGNED;
567 					Timestamp tms = isRef? *(v.get!(Timestamp*)): v.get!(Timestamp);
568 					DateTime dt = mysql.protocol.packet_helpers.toDateTime(tms.rep);
569 					ubyte[] da = pack(dt);
570 					size_t l = da.length;
571 					reAlloc(l);
572 					vals[vcl..vcl+l] = da[];
573 					vcl += l;
574 					break;
575 				case "immutable(char)[]":
576 					if (ext == SQLType.INFER_FROM_D_TYPE)
577 						types[ct++] = SQLType.VARCHAR;
578 					else
579 						types[ct++] = cast(ubyte) ext;
580 					types[ct++] = SIGNED;
581 					string s = isRef? *(v.get!(string*)): v.get!(string);
582 					ubyte[] packed = packLCS(cast(void[]) s);
583 					reAlloc(packed.length);
584 					vals[vcl..vcl+packed.length] = packed[];
585 					vcl += packed.length;
586 					break;
587 				case "char[]":
588 					if (ext == SQLType.INFER_FROM_D_TYPE)
589 						types[ct++] = SQLType.VARCHAR;
590 					else
591 						types[ct++] = cast(ubyte) ext;
592 					types[ct++] = SIGNED;
593 					char[] ca = isRef? *(v.get!(char[]*)): v.get!(char[]);
594 					ubyte[] packed = packLCS(cast(void[]) ca);
595 					reAlloc(packed.length);
596 					vals[vcl..vcl+packed.length] = packed[];
597 					vcl += packed.length;
598 					break;
599 				case "byte[]":
600 					if (ext == SQLType.INFER_FROM_D_TYPE)
601 						types[ct++] = SQLType.TINYBLOB;
602 					else
603 						types[ct++] = cast(ubyte) ext;
604 					types[ct++] = SIGNED;
605 					byte[] ba = isRef? *(v.get!(byte[]*)): v.get!(byte[]);
606 					ubyte[] packed = packLCS(cast(void[]) ba);
607 					reAlloc(packed.length);
608 					vals[vcl..vcl+packed.length] = packed[];
609 					vcl += packed.length;
610 					break;
611 				case "ubyte[]":
612 					if (ext == SQLType.INFER_FROM_D_TYPE)
613 						types[ct++] = SQLType.TINYBLOB;
614 					else
615 						types[ct++] = cast(ubyte) ext;
616 					types[ct++] = SIGNED;
617 					ubyte[] uba = isRef? *(v.get!(ubyte[]*)): v.get!(ubyte[]);
618 					ubyte[] packed = packLCS(cast(void[]) uba);
619 					reAlloc(packed.length);
620 					vals[vcl..vcl+packed.length] = packed[];
621 					vcl += packed.length;
622 					break;
623 				case "void":
624 					throw new MYX("Unbound parameter " ~ to!string(i), __FILE__, __LINE__);
625 				default:
626 					throw new MYX("Unsupported parameter type " ~ ts, __FILE__, __LINE__);
627 			}
628 		}
629 		vals.length = vcl;
630 		return types;
631 	}
632 
633 	static void sendLongData(Connection conn, uint hStmt, ParameterSpecialization[] psa)
634 	{
635 		assert(psa.length <= ushort.max); // parameter number is sent as short
636 		foreach (ushort i, PSN psn; psa)
637 		{
638 			if (!psn.chunkSize) continue;
639 			uint cs = psn.chunkSize;
640 			uint delegate(ubyte[]) dg = psn.chunkDelegate;
641 
642 			//TODO: All low-level commms should be moved into the mysql.protocol package.
643 			ubyte[] chunk;
644 			chunk.length = cs+11;
645 			chunk.setPacketHeader(0 /*each chunk is separate cmd*/);
646 			chunk[4] = CommandType.STMT_SEND_LONG_DATA;
647 			hStmt.packInto(chunk[5..9]); // statement handle
648 			packInto(i, chunk[9..11]); // parameter number
649 
650 			// byte 11 on is payload
651 			for (;;)
652 			{
653 				uint sent = dg(chunk[11..cs+11]);
654 				if (sent < cs)
655 				{
656 					if (sent == 0)    // data was exact multiple of chunk size - all sent
657 						break;
658 					sent += 7;        // adjust for non-payload bytes
659 					chunk.length = chunk.length - (cs-sent);     // trim the chunk
660 					packInto!(uint, true)(cast(uint)sent, chunk[0..3]);
661 					conn.send(chunk);
662 					break;
663 				}
664 				conn.send(chunk);
665 			}
666 		}
667 	}
668 
669 	static void sendCommand(Connection conn, uint hStmt, PreparedStmtHeaders psh,
670 		Variant[] inParams, ParameterSpecialization[] psa)
671 	{
672 		conn.autoPurge();
673 		
674 		//TODO: All low-level commms should be moved into the mysql.protocol package.
675 		ubyte[] packet;
676 		conn.resetPacket();
677 
678 		ubyte[] prefix = makePSPrefix(hStmt, 0);
679 		size_t len = prefix.length;
680 		bool longData;
681 
682 		if (psh.paramCount)
683 		{
684 			ubyte[] one = [ 1 ];
685 			ubyte[] vals;
686 			ubyte[] types = analyseParams(inParams, psa, vals, longData);
687 			ubyte[] nbm = makeBitmap(inParams);
688 			packet = prefix ~ nbm ~ one ~ types ~ vals;
689 		}
690 		else
691 			packet = prefix;
692 
693 		if (longData)
694 			sendLongData(conn, hStmt, psa);
695 
696 		assert(packet.length <= uint.max);
697 		packet.setPacketHeader(conn.pktNumber);
698 		conn.bumpPacket();
699 		conn.send(packet);
700 	}
701 
702 	//TODO: This awkward func is only needed by the deprecated Command struct.
703 	//      Remove this once Command struct is finally deleted.
704 	bool execQueryImpl2(out ulong ra)
705 	{
706 		return execQueryImpl(_conn,
707 			ExecQueryImplInfo(true, null, _hStmt, _psh, _inParams, _psa), ra);
708 	}
709 
710 	/// Has this statement been released?
711 	@property bool isReleased() pure const nothrow
712 	{
713 		return _hStmt == 0;
714 	}
715 
716 public:
717 	/++
718 	Execute a prepared command, such as INSERT/UPDATE/CREATE/etc.
719 	
720 	This method is intended for commands which do not produce a result set
721 	(otherwise, use one of the query functions instead.) If the SQL command does
722 	produces a result set (such as SELECT), `mysql.exceptions.MySQLResultRecievedException`
723 	will be thrown.
724 	
725 	Returns: The number of rows affected.
726 	+/
727 	ulong exec()
728 	{
729 		enforceNotReleased();
730 		return execImpl(
731 			_conn,
732 			ExecQueryImplInfo(true, null, _hStmt, _psh, _inParams, _psa)
733 		);
734 	}
735 
736 	/++
737 	Execute a prepared SQL SELECT command where you expect the entire
738 	result set all at once.
739 
740 	This is being considered for deprecation in a future release of mysql-native,
741 	because the same thing can be achieved via `query`().
742 	$(LINK2 https://dlang.org/phobos/std_array.html#array, `array()`).
743 
744 	If the SQL command does not produce a result set (such as INSERT/CREATE/etc),
745 	then `mysql.exceptions.MySQLNoResultRecievedException` will be thrown. Use
746 	`exec` instead for such commands.
747 
748 	If there are long data items among the expected result columns you can use
749 	the csa param to specify that they are to be subject to chunked transfer via a
750 	delegate.
751 
752 	Params: csa = An optional array of ColumnSpecialization structs.
753 	Returns: A (possibly empty) ResultSet.
754 	+/
755 	ResultSet querySet(ColumnSpecialization[] csa = null)
756 	{
757 		enforceNotReleased();
758 		return querySetImpl(
759 			csa, true, _conn,
760 			ExecQueryImplInfo(true, null, _hStmt, _psh, _inParams, _psa)
761 		);
762 	}
763 
764 	///ditto
765 	deprecated("Use querySet instead.")
766 	alias queryResult = querySet;
767 
768 	/++
769 	Execute a prepared SQL SELECT command where you want to deal with the
770 	result set one row at a time.
771 
772 	If you need random access to the resulting Row elements,
773 	simply call $(LINK2 https://dlang.org/phobos/std_array.html#array, `std.array.array()`)
774 	on the result.
775 
776 	If the SQL command does not produce a result set (such as INSERT/CREATE/etc),
777 	then `mysql.exceptions.MySQLNoResultRecievedException` will be thrown. Use
778 	`exec` instead for such commands.
779 
780 	If there are long data items among the expected result columns you can use
781 	the csa param to specify that they are to be subject to chunked transfer via a
782 	delegate.
783 
784 	Params: csa = An optional array of ColumnSpecialization structs.
785 	Returns: A (possibly empty) ResultRange.
786 	+/
787 	ResultRange query(ColumnSpecialization[] csa = null)
788 	{
789 		enforceNotReleased();
790 		return queryImpl(
791 			csa, _conn,
792 			ExecQueryImplInfo(true, null, _hStmt, _psh, _inParams, _psa)
793 		);
794 	}
795 
796 	///ditto
797 	deprecated("Use query instead.")
798 	alias querySequence = query;
799 
800 	/++
801 	Execute a prepared SQL SELECT command where you only want the first Row (if any).
802 
803 	If the SQL command does not produce a result set (such as INSERT/CREATE/etc),
804 	then `mysql.exceptions.MySQLNoResultRecievedException` will be thrown. Use
805 	`exec` instead for such commands.
806 
807 	If there are long data items among the expected result columns you can use
808 	the csa param to specify that they are to be subject to chunked transfer via a
809 	delegate.
810 
811 	Params: csa = An optional array of ColumnSpecialization structs.
812 	Returns: Nullable!Row: This will be null (check via Nullable.isNull) if the
813 	query resulted in an empty result set.
814 	+/
815 	Nullable!Row queryRow(ColumnSpecialization[] csa = null)
816 	{
817 		enforceNotReleased();
818 		return queryRowImpl(csa, _conn,
819 			ExecQueryImplInfo(true, null, _hStmt, _psh, _inParams, _psa));
820 	}
821 
822 	/++
823 	Execute a prepared SQL SELECT command where you only want the first Row, and
824 	place result values into a set of D variables.
825 	
826 	This method will throw if any column type is incompatible with the corresponding D variable.
827 
828 	Unlike the other query functions, queryRowTuple will throw
829 	`mysql.exceptions.MySQLException` if the result set is empty
830 	(and thus the reference variables passed in cannot be filled).
831 
832 	If the SQL command does not produce a result set (such as INSERT/CREATE/etc),
833 	then `mysql.exceptions.MySQLNoResultRecievedException` will be thrown. Use
834 	`exec` instead for such commands.
835 	
836 	Params: args = A tuple of D variables to receive the results.
837 	+/
838 	void queryRowTuple(T...)(ref T args)
839 	{
840 		enforceNotReleased();
841 		return queryRowTupleImpl(
842 			_conn,
843 			ExecQueryImplInfo(true, null, _hStmt, _psh, _inParams, _psa),
844 			args
845 		);
846 	}
847 
848 	///ditto
849 	deprecated("Use queryRowTuple instead.")
850 	alias queryTuple = queryRowTuple;
851 
852 	/++
853 	Execute a prepared SQL SELECT command and returns a single value,
854 	the first column of the first row received.
855 
856 	If the query did not produce any rows, OR the rows it produced have zero columns,
857 	this will return `Nullable!Variant()`, ie, null. Test for this with `result.isNull`.
858 
859 	If the query DID produce a result, but the value actually received is NULL,
860 	then `result.isNull` will be FALSE, and `result.get` will produce a Variant
861 	which CONTAINS null. Check for this with `result.get.type == typeid(typeof(null))`.
862 
863 	If the SQL command does not produce a result set (such as INSERT/CREATE/etc),
864 	then `mysql.exceptions.MySQLNoResultRecievedException` will be thrown. Use
865 	`exec` instead for such commands.
866 
867 	If there are long data items among the expected result columns you can use
868 	the csa param to specify that they are to be subject to chunked transfer via a
869 	delegate.
870 
871 	Params: csa = An optional array of ColumnSpecialization structs.
872 	Returns: Nullable!Variant: This will be null (check via Nullable.isNull) if the
873 	query resulted in an empty result set.
874 	+/
875 	Nullable!Variant queryValue(ColumnSpecialization[] csa = null)
876 	{
877 		return queryValueImpl(csa, _conn,
878 			ExecQueryImplInfo(true, null, _hStmt, _psh, _inParams, _psa));
879 	}
880 
881 	/++
882 	Prepared statement parameter setter.
883 
884 	The value may, but doesn't have to be, wrapped in a Variant. If so,
885 	null is handled correctly.
886 	
887 	The value may, but doesn't have to be, a pointer to the desired value.
888 
889 	The value may, but doesn't have to be, wrapped in a Nullable!T. If so,
890 	null is handled correctly.
891 
892 	The value can be null.
893 
894 	Params: index = The zero based index
895 	+/
896 	void setArg(T)(size_t index, T val, ParameterSpecialization psn = PSN(0, SQLType.INFER_FROM_D_TYPE, 0, null))
897 		if(!isInstanceOf!(Nullable, T))
898 	{
899 		// Now in theory we should be able to check the parameter type here, since the
900 		// protocol is supposed to send us type information for the parameters, but this
901 		// capability seems to be broken. This assertion is supported by the fact that
902 		// the same information is not available via the MySQL C API either. It is up
903 		// to the programmer to ensure that appropriate type information is embodied
904 		// in the variant array, or provided explicitly. This sucks, but short of
905 		// having a client side SQL parser I don't see what can be done.
906 
907 		enforceNotReleased();
908 		enforceEx!MYX(index < _psParams, "Parameter index out of range.");
909 
910 		_inParams[index] = val;
911 		psn.pIndex = index;
912 		_psa[index] = psn;
913 	}
914 
915 	void setArg(T)(size_t index, Nullable!T val, ParameterSpecialization psn = PSN(0, SQLType.INFER_FROM_D_TYPE, 0, null))
916 	{
917 		enforceNotReleased();
918 		if(val.isNull)
919 			setArg(index, null, psn);
920 		else
921 			setArg(index, val.get(), psn);
922 	}
923 
924 	/++
925 	Bind a tuple of D variables to the parameters of a prepared statement.
926 	
927 	You can use this method to bind a set of variables if you don't need any specialization,
928 	that is chunked transfer is not neccessary.
929 	
930 	The tuple must match the required number of parameters, and it is the programmer's
931 	responsibility to ensure that they are of appropriate types.
932 	+/
933 	void setArgs(T...)(T args)
934 		if(T.length == 0 || !is(T[0] == Variant[]))
935 	{
936 		enforceNotReleased();
937 		enforceEx!MYX(args.length == _psParams, "Argument list supplied does not match the number of parameters.");
938 
939 		foreach (size_t i, arg; args)
940 			setArg(i, arg);
941 	}
942 
943 	/++
944 	Bind a Variant[] as the parameters of a prepared statement.
945 	
946 	You can use this method to bind a set of variables in Variant form to
947 	the parameters of a prepared statement.
948 	
949 	Parameter specializations can be added if required. This method could be
950 	used to add records from a data entry form along the lines of
951 	------------
952 	auto c = Command(con, "insert into table42 values(?, ?, ?)");
953 	c.prepare();
954 	Variant[] va;
955 	va.length = 3;
956 	DataRecord dr;    // Some data input facility
957 	ulong ra;
958 	do
959 	{
960 	    dr.get();
961 	    va[0] = dr("Name");
962 	    va[1] = dr("City");
963 	    va[2] = dr("Whatever");
964 	    c.bindParameters(va);
965 	    c.execPrepared(ra);
966 	} while(tod < "17:30");
967 	------------
968 	Params: va = External list of Variants to be used as parameters
969 	               psnList = any required specializations
970 	+/
971 	void setArgs(Variant[] va, ParameterSpecialization[] psnList= null)
972 	{
973 		enforceNotReleased();
974 		enforceEx!MYX(va.length == _psParams, "Param count supplied does not match prepared statement");
975 		_inParams[] = va[];
976 		if (psnList !is null)
977 		{
978 			foreach (PSN psn; psnList)
979 				_psa[psn.pIndex] = psn;
980 		}
981 	}
982 
983 	/++
984 	Prepared statement parameter getter.
985 
986 	Params: index = The zero based index
987 	+/
988 	Variant getArg(size_t index)
989 	{
990 		enforceNotReleased();
991 		enforceEx!MYX(index < _psParams, "Parameter index out of range.");
992 		return _inParams[index];
993 	}
994 
995 	/++
996 	Sets a prepared statement parameter to NULL.
997 	
998 	This is here mainly for legacy reasons. You can set a field to null
999 	simply by saying `prepared.setArg(index, null);`
1000 
1001 	Params: index = The zero based index
1002 	+/
1003 	void setNullArg(size_t index)
1004 	{
1005 		enforceNotReleased();
1006 		setArg(index, null);
1007 	}
1008 
1009 	/// Gets the SQL command for this prepared statement
1010 	string sql()
1011 	{
1012 		return _sql;
1013 	}
1014 
1015 	debug(MYSQL_INTEGRATION_TESTS)
1016 	unittest
1017 	{
1018 		import mysql.prepared;
1019 		import mysql.test.common;
1020 		mixin(scopedCn);
1021 
1022 		cn.exec("DROP TABLE IF EXISTS `setNullArg`");
1023 		cn.exec("CREATE TABLE `setNullArg` (
1024 			`val` INTEGER
1025 		) ENGINE=InnoDB DEFAULT CHARSET=utf8");
1026 
1027 		immutable insertSQL = "INSERT INTO `setNullArg` VALUES (?)";
1028 		immutable selectSQL = "SELECT * FROM `setNullArg`";
1029 		auto preparedInsert = cn.prepare(insertSQL);
1030 		assert(preparedInsert.sql == insertSQL);
1031 		ResultSet rs;
1032 
1033 		{
1034 			Nullable!int nullableInt;
1035 			nullableInt.nullify();
1036 			preparedInsert.setArg(0, nullableInt);
1037 			assert(preparedInsert.getArg(0).type == typeid(typeof(null)));
1038 			nullableInt = 7;
1039 			preparedInsert.setArg(0, nullableInt);
1040 			assert(preparedInsert.getArg(0) == 7);
1041 
1042 			nullableInt.nullify();
1043 			preparedInsert.setArgs(nullableInt);
1044 			assert(preparedInsert.getArg(0).type == typeid(typeof(null)));
1045 			nullableInt = 7;
1046 			preparedInsert.setArgs(nullableInt);
1047 			assert(preparedInsert.getArg(0) == 7);
1048 		}
1049 
1050 		preparedInsert.setArg(0, 5);
1051 		preparedInsert.exec();
1052 		rs = cn.querySet(selectSQL);
1053 		assert(rs.length == 1);
1054 		assert(rs[0][0] == 5);
1055 
1056 		preparedInsert.setArg(0, null);
1057 		preparedInsert.exec();
1058 		rs = cn.querySet(selectSQL);
1059 		assert(rs.length == 2);
1060 		assert(rs[0][0] == 5);
1061 		assert(rs[1].isNull(0));
1062 		assert(rs[1][0].type == typeid(typeof(null)));
1063 
1064 		preparedInsert.setArg(0, Variant(null));
1065 		preparedInsert.exec();
1066 		rs = cn.querySet(selectSQL);
1067 		assert(rs.length == 3);
1068 		assert(rs[0][0] == 5);
1069 		assert(rs[1].isNull(0));
1070 		assert(rs[2].isNull(0));
1071 		assert(rs[1][0].type == typeid(typeof(null)));
1072 		assert(rs[2][0].type == typeid(typeof(null)));
1073 	}
1074 
1075 	/++
1076 	Release a prepared statement.
1077 	
1078 	This method tells the server that it can dispose of the information it
1079 	holds about the current prepared statement.
1080 
1081 	This method can be called during a GC collection. Allocations should be
1082 	avoided if possible as it could crash the GC.
1083 	
1084 	Notes:
1085 	
1086 	In actuality, the server might not immediately be told to release the
1087 	statement (although this instance of Prepared will still behave as though
1088 	it's been released, regardless).
1089 	
1090 	This is because there could be a ResultRange with results still pending
1091 	for retreival, and the protocol doesn't allow sending commands (such as
1092 	"release a prepared statement") to the server while data is pending.
1093 	Therefore, this function may instead queue the statement to be released
1094 	when it is safe to do so: Either the next time a result set is purged or
1095 	the next time a command (such as query or exec) is performed (because
1096 	such commands automatically purge any pending results).
1097 	+/
1098 	void release()
1099 	{
1100 		if(_conn is null || !_hStmt || _conn.closed())
1101 			return;
1102 
1103 		_conn.statementsToRelease.add(_hStmt);
1104 		_hStmt = 0;
1105 	}
1106 	package static void immediateRelease(Connection conn, uint statementId)
1107 	{
1108 		if(!statementId)
1109 			return;
1110 
1111 		scope(failure) conn.kill();
1112 
1113 		if(conn.closed())
1114 			return;
1115 
1116 		//TODO: All low-level commms should be moved into the mysql.protocol package.
1117 		ubyte[9] packet_buf;
1118 		ubyte[] packet = packet_buf;
1119 		packet.setPacketHeader(0/*packet number*/);
1120 		conn.bumpPacket();
1121 		packet[4] = CommandType.STMT_CLOSE;
1122 		statementId.packInto(packet[5..9]);
1123 		conn.purgeResult();
1124 		conn.send(packet);
1125 		// It seems that the server does not find it necessary to send a response
1126 		// for this command.
1127 	}
1128 
1129 	/// Gets the number of arguments this prepared statement expects to be passed in.
1130 	@property ushort numArgs() pure const nothrow
1131 	{
1132 		return _psParams;
1133 	}
1134 
1135 	/// Gets the prepared header's field descriptions.
1136 	@property FieldDescription[] preparedFieldDescriptions() pure { return _psh.fieldDescriptions; }
1137 
1138 	/// Gets the prepared header's param descriptions.
1139 	@property ParamDescription[] preparedParamDescriptions() pure { return _psh.paramDescriptions; }
1140 }