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